www

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

feedItem.js (10251B)


      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 
     27 /*
     28  * Constructor for FeedItem object
     29  */
     30 Zotero.FeedItem = function(itemTypeOrID, params = {}) {
     31 	Zotero.FeedItem._super.call(this, itemTypeOrID);
     32 	
     33 	this._feedItemReadTime = null;
     34 	this._feedItemTranslatedTime = null;
     35 	
     36 	Zotero.Utilities.assignProps(this, params, ['guid']);
     37 };
     38 
     39 Zotero.extendClass(Zotero.Item, Zotero.FeedItem);
     40 
     41 Zotero.FeedItem.prototype._objectType = 'feedItem';
     42 Zotero.FeedItem.prototype._containerObject = 'feed';
     43 
     44 Zotero.defineProperty(Zotero.FeedItem.prototype, 'isFeedItem', {
     45 	value: true
     46 });
     47 
     48 Zotero.defineProperty(Zotero.FeedItem.prototype, 'guid', {
     49 	get: function() { return this._feedItemGUID; },
     50 	set: function(val) {
     51 		if (this.id) throw new Error('Cannot set GUID after item ID is already set');
     52 		if (typeof val != 'string') throw new Error('GUID must be a non-empty string');
     53 		this._feedItemGUID = val;
     54 	}
     55 });
     56 
     57 Zotero.defineProperty(Zotero.FeedItem.prototype, 'isRead', {
     58 	get: function() {
     59 		return !!this._feedItemReadTime;
     60 	},
     61 	set: function(read) {
     62 		if (!read != !this._feedItemReadTime) {
     63 			// changed
     64 			if (read) {
     65 				this._feedItemReadTime = Zotero.Date.dateToSQL(new Date(), true);
     66 			} else {
     67 				this._feedItemReadTime = null;
     68 			}
     69 			this._changed.feedItemData = true;
     70 		}
     71 	}
     72 });
     73 //
     74 Zotero.defineProperty(Zotero.FeedItem.prototype, 'isTranslated', {
     75 	get: function() {
     76 		return !!this._feedItemTranslatedTime;
     77 	}, 
     78 	set: function(state) {
     79 		if (state != !!this._feedItemTranslatedTime) {
     80 			if (state) {
     81 				this._feedItemTranslatedTime = Zotero.Date.dateToSQL(new Date(), true);
     82 			} else {
     83 				this._feedItemTranslatedTime = null;
     84 			}
     85 			this._changed.feedItemData = true;
     86 		}
     87 	}
     88 });
     89 
     90 Zotero.FeedItem.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload, failOnMissing) {
     91 	if (this.guid && !this.id) {
     92 		// fill in item ID
     93 		this.id = yield this.ObjectsClass.getIDFromGUID(this.guid);
     94 	}
     95 	yield Zotero.FeedItem._super.prototype.loadPrimaryData.apply(this, arguments);
     96 });
     97 
     98 Zotero.FeedItem.prototype.setField = function(field, value) {
     99 	if (field == 'libraryID') {
    100 		// Ensure that it references a feed
    101 		if (!Zotero.Libraries.get(value).isFeed) {
    102 			throw new Error('libraryID must reference a feed');
    103 		}
    104 	}
    105 	
    106 	return Zotero.FeedItem._super.prototype.setField.apply(this, arguments);
    107 }
    108 
    109 Zotero.FeedItem.prototype.fromJSON = function(json) {
    110 	// Spaghetti to handle weird date formats in feedItems
    111 	let val = json.date;
    112 	if (val) {
    113 		let d = Zotero.Date.sqlToDate(val, true);
    114 		if (!d || isNaN(d.getTime())) {
    115 			d = Zotero.Date.isoToDate(val);
    116 		}
    117 		if ((!d || isNaN(d.getTime())) && Zotero.Date.isHTTPDate(val)) {
    118 			d = new Date(val);
    119 		}
    120 		if (!d || isNaN(d.getTime())) {
    121 			d = Zotero.Date.strToDate(val);
    122 			if (d) {
    123 				json.date = [d.year, Zotero.Utilities.lpad(d.month+1, '0', 2), Zotero.Utilities.lpad(d.day, '0', 2)].join('-');
    124 			} else {
    125 				Zotero.logError("Discarding invalid date '" + json.date
    126 					+ "' for item " + this.libraryKey);
    127 				delete json.date;
    128 			}
    129 		} else {
    130 			json.date = Zotero.Date.dateToSQL(d, true);
    131 		}
    132 	}
    133 	Zotero.FeedItem._super.prototype.fromJSON.apply(this, arguments);
    134 }
    135 
    136 Zotero.FeedItem.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
    137 	if (!this.guid) {
    138 		throw new Error('GUID must be set before saving ' + this._ObjectType);
    139 	}
    140 	
    141 	let proceed = yield Zotero.FeedItem._super.prototype._initSave.apply(this, arguments);
    142 	if (!proceed) return proceed;
    143 	
    144 	if (env.isNew) {
    145 		// verify that GUID doesn't already exist for a new item
    146 		var item = yield this.ObjectsClass.getIDFromGUID(this.guid);
    147 		if (item) {
    148 			throw new Error('Cannot create new item with GUID ' + this.guid + '. Item already exists.');
    149 		}
    150 		
    151 		// Register GUID => itemID mapping in cache on commit
    152 		if (!env.transactionOptions) env.transactionOptions = {};
    153 		var superOnCommit = env.transactionOptions.onCommit;
    154 		env.transactionOptions.onCommit = () => {
    155 			if (superOnCommit) superOnCommit();
    156 			this.ObjectsClass._setGUIDMapping(this.guid, this._id);
    157 		};
    158 	}
    159 	
    160 	return proceed;
    161 });
    162 
    163 Zotero.FeedItem.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
    164 	yield Zotero.FeedItem._super.prototype._saveData.apply(this, arguments);
    165 	
    166 	if (this._changed.feedItemData || env.isNew) {
    167 		var sql = "REPLACE INTO feedItems VALUES (?,?,?,?)";
    168 		yield Zotero.DB.queryAsync(
    169 			sql,
    170 			[
    171 				this.id,
    172 				this.guid,
    173 				this._feedItemReadTime,
    174 				this._feedItemTranslatedTime
    175 			]
    176 		);
    177 		
    178 		this._clearChanged('feedItemData');
    179 	}
    180 });
    181 
    182 Zotero.FeedItem.prototype._finalizeErase = Zotero.Promise.coroutine(function* () {
    183 	// Set for syncing
    184 	let feed = Zotero.Feeds.get(this.libraryID);
    185 	
    186 	return Zotero.FeedItem._super.prototype._finalizeErase.apply(this, arguments);
    187 });
    188 
    189 Zotero.FeedItem.prototype.toggleRead = Zotero.Promise.coroutine(function* (state) {
    190 	state = state !== undefined ? !!state : !this.isRead;
    191 	let changed = this.isRead != state;
    192 	if (changed) {
    193 		this.isRead = state;
    194 		
    195 		yield this.saveTx();
    196 
    197 		let feed = Zotero.Feeds.get(this.libraryID);
    198 		yield feed.updateUnreadCount();
    199 	}
    200 });
    201 
    202 /**
    203  * Uses the item url to translate an existing feed item.
    204  * If libraryID empty, overwrites feed item, otherwise saves
    205  * in the library
    206  * @param libraryID {Integer} save item in library
    207  * @param collectionID {Integer} add item to collection
    208  * @return {Promise<FeedItem|Item>} translated feed item
    209  */
    210 Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (libraryID, collectionID) {
    211 	Zotero.debug("Translating feed item " + this.id + " with URL " + this.getField('url'), 2);
    212 	if (Zotero.locked) {
    213 		Zotero.debug('Zotero locked, skipping feed item translation');
    214 		return;
    215 	}
    216 
    217 	let deferred = Zotero.Promise.defer();
    218 	let error = function(e) { Zotero.debug(e, 1); deferred.reject(e); };
    219 	let translate = new Zotero.Translate.Web();
    220 	var win = Services.wm.getMostRecentWindow("navigator:browser");
    221 	let progressWindow = win.ZoteroPane.progressWindow;
    222 	
    223 	if (libraryID) {
    224 		// Show progress notifications when scraping to a library.
    225 		translate.clearHandlers("done");
    226 		translate.clearHandlers("itemDone");
    227 		translate.setHandler("done", progressWindow.Translation.doneHandler);
    228 		translate.setHandler("itemDone", progressWindow.Translation.itemDoneHandler());
    229 		if (collectionID) {
    230 			var collection = yield Zotero.Collections.getAsync(collectionID);
    231 		}
    232 		progressWindow.show();
    233 		progressWindow.Translation.scrapingTo(libraryID, collection);
    234 	}
    235 	
    236 	// Load document
    237 	let hiddenBrowser = Zotero.HTTP.loadDocuments(
    238 		this.getField('url'),
    239 		doc => deferred.resolve(doc),
    240 		() => {},
    241 		error,
    242 		true
    243 	);
    244 	let doc = yield deferred.promise;
    245 
    246 	// Set translate document
    247 	translate.setDocument(doc);
    248 	
    249 	// Load translators
    250 	deferred = Zotero.Promise.defer();
    251 	translate.setHandler('translators', (me, translators) => deferred.resolve(translators));
    252 	translate.getTranslators();
    253 	let translators = yield deferred.promise;
    254 	if (!translators || !translators.length) {
    255 		Zotero.debug("No translators detected for feed item " + this.id + " with URL " + this.getField('url') + 
    256 			' -- cloning item instead', 2);
    257 		let item = yield this.clone(libraryID, collectionID, doc);
    258 		progressWindow.Translation.itemDoneHandler()(null, null, item);
    259 		progressWindow.Translation.doneHandler(null, true);
    260 		return;
    261 	}
    262 	translate.setTranslator(translators[0]);
    263 
    264 	deferred = Zotero.Promise.defer();
    265 	
    266 	if (libraryID) {
    267 		let result = yield translate.translate({libraryID, collections: collectionID ? [collectionID] : false})
    268 			.then(items => items ? items[0] : false);
    269 		Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
    270 		if (!result) {
    271 			let item = yield this.clone(libraryID, collectionID, doc);
    272 			progressWindow.Translation.itemDoneHandler()(null, null, item);
    273 			progressWindow.Translation.doneHandler(null, true);
    274 			return;
    275 		}
    276 		return result;
    277 	}
    278 	
    279 	// Clear these to prevent saving
    280 	translate.clearHandlers('itemDone');
    281 	translate.clearHandlers('itemsDone');
    282 	translate.setHandler('error', error);
    283 	translate.setHandler('itemDone', (_, items) => deferred.resolve(items));
    284 	
    285 	translate.translate({libraryID: false, saveAttachments: false});
    286 	
    287 	let itemData = yield deferred.promise;
    288 	Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
    289 	
    290 	// clean itemData
    291 	const deleteFields = ['attachments', 'notes', 'id', 'itemID', 'path', 'seeAlso', 'version', 'dateAdded', 'dateModified'];
    292 	for (let field of deleteFields) {
    293 		delete itemData[field];
    294 	}	
    295 	
    296 	this.fromJSON(itemData);
    297 	this.isTranslated = true;
    298 	yield this.saveTx();
    299 	
    300 	return this;
    301 });
    302 
    303 /**
    304  * Clones the feed item (usually, when proper translation is unavailable)
    305  * @param libraryID {Integer} save item in library
    306  * @param collectionID {Integer} add item to collection
    307  * @return {Promise<FeedItem|Item>} translated feed item
    308  */
    309 Zotero.FeedItem.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, collectionID, doc) {
    310 	let dbItem = Zotero.Item.prototype.clone.call(this, libraryID);
    311 	if (collectionID) {
    312 		dbItem.addToCollection(collectionID);
    313 	}
    314 	yield dbItem.saveTx();
    315 	
    316 	let item = {title: dbItem.getField('title'), itemType: dbItem.itemType, attachments: []};
    317 	
    318 	// Add snapshot
    319 	if (Zotero.Libraries.get(libraryID).filesEditable) {
    320 		item.attachments = [{title: "Snapshot"}];
    321 		yield Zotero.Attachments.importFromDocument({
    322 			document: doc,
    323 			parentItemID: dbItem.id
    324 		});
    325 	}
    326 	
    327 	return item;
    328 });