www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

storageRequest.js (8140B)


      1 /*
      2     ***** BEGIN LICENSE BLOCK *****
      3     
      4     Copyright © 2009 Center for History and New Media
      5                      George Mason University, Fairfax, Virginia, USA
      6                      http://zotero.org
      7     
      8     This file is part of Zotero.
      9     
     10     Zotero is free software: you can redistribute it and/or modify
     11     it under the terms of the GNU Affero General Public License as published by
     12     the Free Software Foundation, either version 3 of the License, or
     13     (at your option) any later version.
     14     
     15     Zotero is distributed in the hope that it will be useful,
     16     but WITHOUT ANY WARRANTY; without even the implied warranty of
     17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18     GNU Affero General Public License for more details.
     19     
     20     You should have received a copy of the GNU Affero General Public License
     21     along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
     22     
     23     ***** END LICENSE BLOCK *****
     24 */
     25 
     26 
     27 /**
     28  * Transfer request for storage sync
     29  *
     30  * @param {Object} options
     31  * @param {String} options.type
     32  * @param {Integer} options.libraryID
     33  * @param {String} options.name - Identifier for request (e.g., "[libraryID]/[key]")
     34  * @param {Function|Function[]} [options.onStart]
     35  * @param {Function|Function[]} [options.onProgress]
     36  * @param {Function|Function[]} [options.onStop]
     37  */
     38 Zotero.Sync.Storage.Request = function (options) {
     39 	if (!options.type) throw new Error("type must be provided");
     40 	if (!options.libraryID) throw new Error("libraryID must be provided");
     41 	if (!options.name) throw new Error("name must be provided");
     42 	['type', 'libraryID', 'name'].forEach(x => this[x] = options[x]);
     43 	
     44 	Zotero.debug(`Initializing ${this.type} request ${this.name}`);
     45 	
     46 	this.callbacks = ['onStart', 'onProgress', 'onStop'];
     47 	
     48 	this.Type = Zotero.Utilities.capitalize(this.type);
     49 	this.channel = null;
     50 	this.queue = null;
     51 	this.progress = 0;
     52 	this.progressMax = 0;
     53 	
     54 	this._deferred = Zotero.Promise.defer();
     55 	this._running = false;
     56 	this._stopping = false;
     57 	this._percentage = 0;
     58 	this._remaining = null;
     59 	this._maxSize = null;
     60 	this._finished = false;
     61 	
     62 	for (let name of this.callbacks) {
     63 		if (!options[name]) continue;
     64 		this['_' + name] = Array.isArray(options[name]) ? options[name] : [options[name]];
     65 	}
     66 }
     67 
     68 
     69 Zotero.Sync.Storage.Request.prototype.setMaxSize = function (size) {
     70 	this._maxSize = size;
     71 };
     72 
     73 
     74 /**
     75  * Add callbacks from another request to this request
     76  */
     77 Zotero.Sync.Storage.Request.prototype.importCallbacks = function (request) {
     78 	for (let name of this.callbacks) {
     79 		name = '_' + name;
     80 		if (request[name]) {
     81 			// If no handlers for this event, add them all
     82 			if (!this[name]) {
     83 				this[name] = request[name];
     84 				continue;
     85 			}
     86 			// Otherwise add functions that don't already exist
     87 			var add = true;
     88 			for (let newFunc of request[name]) {
     89 				for (let currentFunc of this[name]) {
     90 					if (newFunc.toString() === currentFunc.toString()) {
     91 						Zotero.debug("Callback already exists in request -- not importing");
     92 						add = false;
     93 						break;
     94 					}
     95 				}
     96 				if (add) {
     97 					this[name].push(newFunc);
     98 				}
     99 			}
    100 		}
    101 	}
    102 }
    103 
    104 
    105 Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function () {
    106 	if (this._finished) {
    107 		return 100;
    108 	}
    109 	
    110 	if (this.progressMax == 0) {
    111 		return 0;
    112 	}
    113 	
    114 	var percentage = Math.round((this.progress / this.progressMax) * 100);
    115 	if (percentage < this._percentage) {
    116 		Zotero.debug(percentage + " is less than last percentage of "
    117 			+ this._percentage + " for request " + this.name, 2);
    118 		Zotero.debug(this.progress);
    119 		Zotero.debug(this.progressMax);
    120 		percentage = this._percentage;
    121 	}
    122 	else if (percentage > 100) {
    123 		Zotero.debug(percentage + " is greater than 100 for "
    124 			+ "request " + this.name, 2);
    125 		Zotero.debug(this.progress);
    126 		Zotero.debug(this.progressMax);
    127 		percentage = 100;
    128 	}
    129 	else {
    130 		this._percentage = percentage;
    131 	}
    132 	//Zotero.debug("Request '" + this.name + "' percentage is " + percentage);
    133 	return percentage;
    134 });
    135 
    136 
    137 Zotero.Sync.Storage.Request.prototype.__defineGetter__('remaining', function () {
    138 	if (this._finished) {
    139 		return 0;
    140 	}
    141 	
    142 	if (!this.progressMax) {
    143 		if (this.type == 'upload' && this._maxSize) {
    144 			return Math.round(Zotero.Sync.Storage.compressionTracker.ratio * this._maxSize);
    145 		}
    146 		
    147 		//Zotero.debug("Remaining not yet available for request '" + this.name + "'");
    148 		return 0;
    149 	}
    150 	
    151 	var remaining = this.progressMax - this.progress;
    152 	if (this._remaining === null) {
    153 		this._remaining = remaining;
    154 	}
    155 	else if (remaining > this._remaining) {
    156 		Zotero.debug(remaining + " is greater than the last remaining amount of "
    157 				+ this._remaining + " for request " + this.name);
    158 		remaining = this._remaining;
    159 	}
    160 	else if (remaining < 0) {
    161 		Zotero.debug(remaining + " is less than 0 for request " + this.name);
    162 	}
    163 	else {
    164 		this._remaining = remaining;
    165 	}
    166 	//Zotero.debug("Request '" + this.name + "' remaining is " + remaining);
    167 	return remaining;
    168 });
    169 
    170 
    171 Zotero.Sync.Storage.Request.prototype.setChannel = function (channel) {
    172 	this.channel = channel;
    173 }
    174 
    175 
    176 Zotero.Sync.Storage.Request.prototype.start = Zotero.Promise.coroutine(function* () {
    177 	Zotero.debug("Starting " + this.type + " request " + this.name);
    178 	
    179 	if (this._running) {
    180 		throw new Error(this.type + " request " + this.name + " already running");
    181 	}
    182 	
    183 	if (!this._onStart) {
    184 		throw new Error("onStart not provided -- nothing to do!");
    185 	}
    186 	
    187 	this._running = true;
    188 	
    189 	// this._onStart is an array of promises for objects of result flags, which are combined
    190 	// into a single object here
    191 	//
    192 	// The main sync logic is triggered here.
    193 	try {
    194 		var results = yield Zotero.Promise.all(this._onStart.map(f => f(this)));
    195 		
    196 		var result = new Zotero.Sync.Storage.Result;
    197 		result.updateFromResults(results);
    198 		
    199 		Zotero.debug(this.Type + " request " + this.name + " finished");
    200 		Zotero.debug(result + "");
    201 		
    202 		return result;
    203 	}
    204 	catch (e) {
    205 		Zotero.logError(this.Type + " request " + this.name + " failed");
    206 		throw e;
    207 	}
    208 	finally {
    209 		this._finished = true;
    210 		this._running = false;
    211 		
    212 		Zotero.Sync.Storage.setItemDownloadPercentage(this.name, false);
    213 		
    214 		if (this._onStop) {
    215 			this._onStop.forEach(x => x());
    216 		}
    217 	}
    218 });
    219 
    220 
    221 Zotero.Sync.Storage.Request.prototype.isRunning = function () {
    222 	return this._running;
    223 }
    224 
    225 
    226 Zotero.Sync.Storage.Request.prototype.isFinished = function () {
    227 	return this._finished;
    228 }
    229 
    230 
    231 /**
    232  * Update counters for given request
    233  *
    234  * Also updates progress meter
    235  *
    236  * @param	{Integer}		progress			Progress so far
    237  *												(usually bytes transferred)
    238  * @param	{Integer}		progressMax		Max progress value for this request
    239  *												(usually total bytes)
    240  */
    241 Zotero.Sync.Storage.Request.prototype.onProgress = function (progress, progressMax) {
    242 	//Zotero.debug(progress + "/" + progressMax + " for request " + this.name);
    243 	
    244 	if (!this._running) {
    245 		Zotero.debug("Trying to update finished request " + this.name + " in "
    246 				+ "Zotero.Sync.Storage.Request.onProgress() "
    247 				+ "(" + progress + "/" + progressMax + ")", 2);
    248 		return;
    249 	}
    250 	
    251 	// Workaround for invalid progress values (possibly related to
    252 	// https://bugzilla.mozilla.org/show_bug.cgi?id=451991 and fixed in 3.1)
    253 	if (progress < this.progress) {
    254 		Zotero.debug("Invalid progress for request '"
    255 			+ this.name + "' (" + progress + " < " + this.progress + ")");
    256 		return;
    257 	}
    258 	
    259 	if (progressMax != this.progressMax) {
    260 		Zotero.debug("progressMax has changed from " + this.progressMax
    261 			+ " to " + progressMax + " for request '" + this.name + "'", 2);
    262 	}
    263 	
    264 	this.progress = progress;
    265 	this.progressMax = progressMax;
    266 	
    267 	if (this.type == 'download') {
    268 		Zotero.Sync.Storage.setItemDownloadPercentage(this.name, this.percentage);
    269 	}
    270 	
    271 	if (this._onProgress) {
    272 		for (let f of this._onProgress) {
    273 			f(progress, progressMax);
    274 		}
    275 	}
    276 }
    277 
    278 
    279 /**
    280  * Stop the request's underlying network request, if there is one
    281  */
    282 Zotero.Sync.Storage.Request.prototype.stop = function (force) {
    283 	if (this.channel && this.channel.isPending()) {
    284 		this._stopping = true;
    285 		
    286 		try {
    287 			Zotero.debug(`Stopping ${this.type} request '${this.name} '`);
    288 			this.channel.cancel(0x804b0002); // NS_BINDING_ABORTED
    289 		}
    290 		catch (e) {
    291 			Zotero.debug(e, 1);
    292 		}
    293 	}
    294 }