www

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

syncFullTextEngine.js (6395B)


      1 /*
      2     ***** BEGIN LICENSE BLOCK *****
      3     
      4     Copyright © 2015 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 if (!Zotero.Sync.Data) {
     27 	Zotero.Sync.Data = {};
     28 }
     29 
     30 Zotero.Sync.Data.FullTextEngine = function (options) {
     31 	if (options.apiClient == undefined) {
     32 		throw new Error("options.apiClient not set");
     33 	}
     34 	if (options.libraryID == undefined) {
     35 		throw new Error("options.libraryID not set");
     36 	}
     37 	
     38 	this.MAX_BATCH_SIZE = 500000;
     39 	this.MAX_BATCH_ITEMS = 10;
     40 	
     41 	this.apiClient = options.apiClient;
     42 	this.libraryID = options.libraryID;
     43 	this.library = Zotero.Libraries.get(options.libraryID);
     44 	this.setStatus = options.setStatus || function () {};
     45 	this.onError = options.onError || function (e) {};
     46 	this.stopOnError = options.stopOnError;
     47 	this._stopping = false;
     48 	this.failed = false;
     49 }
     50 
     51 Zotero.Sync.Data.FullTextEngine.prototype.start = Zotero.Promise.coroutine(function* () {
     52 	Zotero.debug("Starting full-text sync for " + this.library.name);
     53 	
     54 	// Get last full-text version in settings
     55 	var libraryVersion = yield Zotero.FullText.getLibraryVersion(this.libraryID);
     56 	// If main library version has changed, check to see if there's no full-text content
     57 	if (this.library.libraryVersion > libraryVersion) {
     58 		yield this._download(libraryVersion);
     59 	}
     60 	else {
     61 		Zotero.debug("Library version hasn't changed -- skipping full-text download");
     62 	}
     63 	
     64 	this._stopCheck();
     65 	
     66 	yield this._upload();
     67 })
     68 
     69 
     70 Zotero.Sync.Data.FullTextEngine.prototype._download = Zotero.Promise.coroutine(function* (libraryVersion) {
     71 	Zotero.debug("Downloading full-text content for " + this.library.name);
     72 	
     73 	// Get changed with ?since
     74 	var results = yield this.apiClient.getFullTextVersions(
     75 		this.library.libraryType,
     76 		this.library.libraryTypeID,
     77 		libraryVersion
     78 	);
     79 	
     80 	// Go through, checking local version against returned version
     81 	var keys = [];
     82 	for (let key in results.versions) {
     83 		let id = Zotero.Items.getIDFromLibraryAndKey(this.libraryID, key);
     84 		if (!id) {
     85 			Zotero.debug(`Skipping full-text for missing item ${this.library.name}`);
     86 			continue;
     87 		}
     88 		
     89 		// Skip full text that's already up-to-date, which could happen due to a full sync or
     90 		// interrupted sync
     91 		let version = yield Zotero.Fulltext.getItemVersion(id);
     92 		if (version == results.versions[key]) {
     93 			Zotero.debug(`Skipping up-to-date full text for ${this.library.name}`);
     94 			continue;
     95 		}
     96 		keys.push(key);
     97 	}
     98 	
     99 	this.requestPromises = [];
    100 	
    101 	yield Zotero.Promise.map(
    102 		keys,
    103 		(key) => {
    104 			this._stopCheck();
    105 			return this.apiClient.getFullTextForItem(
    106 				this.library.libraryType, this.library.libraryTypeID, key
    107 			)
    108 			.then((results) => {
    109 				this._stopCheck();
    110 				if (!results) return;
    111 				return Zotero.Fulltext.setItemContent(
    112 					this.libraryID, key, results.data, results.version
    113 				)
    114 			})
    115 		},
    116 		// Prepare twice the number of concurrent requests
    117 		{ concurrency: 8 }
    118 	);
    119 	
    120 	yield Zotero.FullText.setLibraryVersion(this.libraryID, results.libraryVersion);
    121 });
    122 
    123 
    124 Zotero.Sync.Data.FullTextEngine.prototype._upload = Zotero.Promise.coroutine(function* () {
    125 	if (!this.library.editable) return;
    126 	
    127 	Zotero.debug("Uploading full-text content for " + this.library.name);
    128 	
    129 	var libraryVersion = this.library.libraryVersion;
    130 	var props = ['key', 'content', 'indexedChars', 'totalChars', 'indexedPages', 'totalPages'];
    131 	
    132 	let lastItemID = 0;
    133 	while (true) {
    134 		this._stopCheck();
    135 		
    136 		let objs = yield Zotero.FullText.getUnsyncedContent(this.libraryID, {
    137 			maxSize: this.MAX_BATCH_SIZE,
    138 			maxItems: this.MAX_BATCH_ITEMS,
    139 			lastItemID
    140 		});
    141 		if (!objs.length) {
    142 			break;
    143 		}
    144 		
    145 		let jsonArray = [];
    146 		let results;
    147 		
    148 		for (let obj of objs) {
    149 			let json = {};
    150 			for (let prop of props) {
    151 				json[prop] = obj[prop];
    152 			}
    153 			jsonArray.push(json);
    154 			lastItemID = obj.itemID;
    155 		}
    156 		({ libraryVersion, results } = yield this.apiClient.setFullTextForItems(
    157 			this.library.libraryType,
    158 			this.library.libraryTypeID,
    159 			libraryVersion,
    160 			jsonArray
    161 		));
    162 		yield Zotero.DB.executeTransaction(function* () {
    163 			for (let state of ['successful', 'unchanged']) {
    164 				for (let index in results[state]) {
    165 					let key = results[state][index].key;
    166 					let itemID = Zotero.Items.getIDFromLibraryAndKey(this.libraryID, key);
    167 					yield Zotero.FullText.setItemSynced(itemID, libraryVersion);
    168 				}
    169 			}
    170 			// Set both the library version and the full-text library version. The latter is necessary
    171 			// because full-text sync can be turned off at any time, so we have to keep track of the
    172 			// last version we've seen for full-text in case the main library version has advanced since.
    173 			yield Zotero.FullText.setLibraryVersion(this.libraryID, libraryVersion);
    174 			this.library.libraryVersion = libraryVersion;
    175 			yield this.library.save();
    176 		}.bind(this));
    177 		
    178 		for (let index in results.failed) {
    179 			let { code, message, data } = results.failed[index];
    180 			let e = new Error(message);
    181 			e.name = "ZoteroObjectUploadError";
    182 			e.code = code;
    183 			if (data) {
    184 				e.data = data;
    185 			}
    186 			Zotero.logError("Error uploading full text for item " + jsonArray[index].key + " in "
    187 				+ this.library.name + ":\n\n" + e);
    188 			
    189 			if (this.onError) {
    190 				this.onError(e);
    191 			}
    192 			if (this.stopOnError) {
    193 				throw new Error(e);
    194 			}
    195 		}
    196 	}
    197 });
    198 
    199 
    200 Zotero.Sync.Data.FullTextEngine.prototype.stop = Zotero.Promise.coroutine(function* () {
    201 	// TODO: Cancel requests?
    202 	this._stopping = true;
    203 })
    204 
    205 
    206 Zotero.Sync.Data.FullTextEngine.prototype._stopCheck = function () {
    207 	if (!this._stopping) return;
    208 	Zotero.debug("Full-text sync stopped for " + this.library.name);
    209 	throw new Zotero.Sync.UserCancelledException;
    210 }