root/trunk/modules/cothread.jsm

Revision 1031, 7.0 kB (checked in by MaierMan, 5 months ago)

#832: Asynchronously add new downloads

Line 
1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Mozilla Public License Version
5  * 1.1 (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  * http://www.mozilla.org/MPL/
8  *
9  * Software distributed under the License is distributed on an "AS IS" basis,
10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11  * for the specific language governing rights and limitations under the
12  * License.
13  *
14  * The Original Code is DownThemAll CoThread module.
15  *
16  * The Initial Developer of the Original Code is Nils Maier
17  * Portions created by the Initial Developer are Copyright (C) 2008
18  * the Initial Developer. All Rights Reserved.
19  *
20  * Contributor(s):
21  *   Nils Maier <MaierMan@web.de>
22  *
23  * Alternatively, the contents of this file may be used under the terms of
24  * either the GNU General Public License Version 2 or later (the "GPL"), or
25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26  * in which case the provisions of the GPL or the LGPL are applicable instead
27  * of those above. If you wish to allow use of your version of this file only
28  * under the terms of either the GPL or the LGPL, and not to allow others to
29  * use your version of this file under the terms of the MPL, indicate your
30  * decision by deleting the provisions above and replace them with the notice
31  * and other provisions required by the GPL or the LGPL. If you do not delete
32  * the provisions above, a recipient may use your version of this file under
33  * the terms of any one of the MPL, the GPL or the LGPL.
34  *
35  * ***** END LICENSE BLOCK ***** */
36
37 const EXPORTED_SYMBOLS = ['CoThread', 'CoThreadListWalker'];
38
39 const Cc = Components.classes;
40 const Ci = Components.interfaces;
41 const Cr = Components.results;
42
43 const TYPE_REPEATING_SLACK = Ci.nsITimer.TYPE_REPEATING_SLACK;
44 const Timer = Components.Constructor('@mozilla.org/timer;1', 'nsITimer', 'initWithCallback');
45
46 // "Abstract" base c'tor
47 function CoThreadBase(func, yieldEvery, thisCtx) {
48   this._thisCtx = thisCtx ? thisCtx : this;
49  
50   // default to 1
51   this._yieldEvery = typeof yieldEvery == 'number' ? Math.floor(yieldEvery) : 1;
52   if (yieldEvery < 1) {
53     throw Cr.NS_ERROR_INVALID_ARG;
54   }
55  
56   if (typeof func != 'function' && !(func instanceof Function)) {
57     throw Cr.NS_ERROR_INVALID_ARG;
58   }
59   this._func = func;
60 }
61
62 /**
63  * Constructs a new CoThread (aka. pseudo-thread).
64  * A CoThread will repeatedly call a specified function, but "breaking"
65  * the operation temporarily after a certain ammount of calls,
66  * so that the main thread gets a chance to process any outstanding
67  * events.
68  *
69  * Example:
70  *        Components.utils.import('resource://dta/cothread.jsm');
71  *        new CoThread(
72  *          // What to do with each item?
73  *          // Print it!
74  *          function(count) document.write(count + "<br>") || (count < 30000),
75  *          // When to turn over Control?
76  *          // Each 1000 items
77  *          1000
78  *        ).run();
79  *   
80  * @param {Function} func Function to be called. Is passed call count as argument. Returning false will cancel the operation.
81  * @param {Number} yieldEvery Optional. After how many items control should be turned over to the main thread
82  * @param {Object} thisCtx Optional. The function will be called in the scope of this object (or if omitted in the scope of the CoThread instance)
83  */
84 function CoThread(func, yieldEvery, thisCtx) {
85   CoThreadBase.call(this, func, yieldEvery, thisCtx);
86  
87   // fake generator so we may use a common implementation. ;)
88   this._generator = (function() { for(;;) { yield null }; })();
89 }
90
91 CoThread.prototype = {
92  
93   _idx: 0,
94   _ran: false,
95   _finishFunc: null,
96  
97   run: function CoThread_run() {
98     if (this._ran) {
99       throw new Error("You cannot run a CoThread/CoThreadListWalker instance more than once.");
100     }
101     this._ran = true;
102    
103     this._timer = new Timer(this, 10, TYPE_REPEATING_SLACK);   
104   },
105  
106   QueryInterface: function CoThread_QueryInterface(iid) {
107     if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsITimerCallback)) {
108       return this;
109     }
110     throw Cr.NS_ERROR_NO_INTERFACE;
111   },
112  
113   notify: function CoThread_notify() {
114     let y = this._yieldEvery;
115     let g = this._generator;
116     let f = this._func;
117     let ctx = this._thisCtx;
118     let callf = this._callf;
119     try {   
120       for (let i = 0; i < y; ++i) {
121         if (!callf(ctx, g.next(), this._idx++, f)) {
122           throw 'complete';
123         }
124       }
125     }
126     catch (ex) {
127       this.cancel();
128     }
129   },
130  
131   cancel: function CoThread_cancel() {
132     this._timer.cancel();
133     if (this._finishFunc) {
134       this._finishFunc.call(this._thisCtx);
135     }   
136   },
137  
138   _callf: function CoThread__callf(ctx, item, idx, func) {
139     return func.call(ctx, idx);
140   }
141 }
142
143 /**
144  * Constructs a new CoThreadListWalker (aka. pseudo-thread).
145  * A CoThreadListWalker will walk a specified list and call a specified function
146  * on each item, but "breaking" the operation temporarily after a
147  * certain ammount of processed items, so that the main thread may
148  * process any outstanding events.
149  *
150  * Example:
151  *        Components.utils.import('resource://dta/cothread.jsm');
152  *        new CoThreadListWalker(
153  *          // What to do with each item?
154  *          // Print it!
155  *          function(item, idx) document.write(item + "/" + idx + "<br>") || true,
156  *          // What items?
157  *          // 0 - 29999
158  *          (function() { for (let i = 0; i < 30000; ++i) yield i; })(),
159  *          // When to turn over Control?
160  *          // Each 1000 items
161  *          1000,
162  *          null,
163  *          function() alert('done')
164  *        ).run();
165  *   
166  * @param {Function} func Function to be called on each item. Is passed item and index as arguments. Returning false will cancel the operation.
167  * @param {Array/Generator} arrayOrGenerator Array or Generator object to be used as the input list
168  * @param {Number} yieldEvery Optional. After how many items control should be turned over to the main thread
169  * @param {Object} thisCtx Optional. The function will be called in the scope of this object (or if omitted in the scope of the CoThread instance)
170  * @param {Function} finishFunc Optional. This function will be called once the operation finishes or is cancelled.
171  */
172 function CoThreadListWalker(func, arrayOrGenerator, yieldEvery, thisCtx, finishFunc) {
173   CoThreadBase.call(this, func, yieldEvery, thisCtx);
174  
175   if (arrayOrGenerator instanceof Array) {
176     // make a generator
177     this._generator = (i for each (i in arrayOrGenerator));
178   }
179   else if (typeof arrayOrGenerator != 'function' && !(arrayOrGenerator instanceof Function)) {
180     this._generator = arrayOrGenerator;
181   }
182   else {
183     throw Cr.NS_ERROR_INVALID_ARG;
184   }
185  
186   this._finishFunc = finishFunc;
187   if (this._lastFunc && (typeof func != 'function' && !(func instanceof Function))) {
188     throw Cr.NS_ERROR_INVALID_ARG;
189   }
190 }
191
192 // not just b.prototype = a.prototype, because we wouldn't then be allowed to override methods
193 for (x in CoThread.prototype) {
194   CoThreadListWalker.prototype[x] = CoThread.prototype[x];
195 }
196 CoThreadListWalker.prototype._callf = function CoThreadListWalker__callf(ctx, item, idx, func) {
197   return func.call(ctx, item, idx);
198 }
Note: See TracBrowser for help on using the browser.