www

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

relations.js (8799B)


      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 "use strict";
     27 
     28 Zotero.Relations = new function () {
     29 	Zotero.defineProperty(this, 'relatedItemPredicate', {value: 'dc:relation'});
     30 	Zotero.defineProperty(this, 'linkedObjectPredicate', {value: 'owl:sameAs'});
     31 	Zotero.defineProperty(this, 'replacedItemPredicate', {value: 'dc:replaces'});
     32 	
     33 	this._namespaces = {
     34 		dc: 'http://purl.org/dc/elements/1.1/',
     35 		owl: 'http://www.w3.org/2002/07/owl#',
     36 		mendeleyDB: 'http://zotero.org/namespaces/mendeleyDB#'
     37 	};
     38 	
     39 	var _types = ['collection', 'item'];
     40 	var _subjectsByPredicateIDAndObject = {};
     41 	var _subjectPredicatesByObject = {};
     42 	
     43 	
     44 	this.init = Zotero.Promise.coroutine(function* () {
     45 		// Load relations for different types
     46 		for (let type of _types) {
     47 			let t = new Date();
     48 			Zotero.debug(`Loading ${type} relations`);
     49 			
     50 			let sql = "SELECT * FROM " + type + "Relations "
     51 				+ "JOIN relationPredicates USING (predicateID)";
     52 			yield Zotero.DB.queryAsync(
     53 				sql,
     54 				false,
     55 				{
     56 					onRow: function (row) {
     57 						this.register(
     58 							type,
     59 							row.getResultByIndex(0),
     60 							row.getResultByIndex(1),
     61 							row.getResultByIndex(2)
     62 						);
     63 					}.bind(this)
     64 				}
     65 			);
     66 			
     67 			Zotero.debug(`Loaded ${type} relations in ${new Date() - t} ms`);
     68 		}
     69 	});
     70 	
     71 	
     72 	this.register = function (objectType, subjectID, predicate, object) {
     73 		var predicateID = Zotero.RelationPredicates.getID(predicate);
     74 		
     75 		if (!_subjectsByPredicateIDAndObject[objectType]) {
     76 			_subjectsByPredicateIDAndObject[objectType] = {};
     77 		}
     78 		if (!_subjectPredicatesByObject[objectType]) {
     79 			_subjectPredicatesByObject[objectType] = {};
     80 		}
     81 		
     82 		// _subjectsByPredicateIDAndObject
     83 		var o = _subjectsByPredicateIDAndObject[objectType];
     84 		if (!o[predicateID]) {
     85 			o[predicateID] = {};
     86 		}
     87 		if (!o[predicateID][object]) {
     88 			o[predicateID][object] = new Set();
     89 		}
     90 		o[predicateID][object].add(subjectID);
     91 		
     92 		// _subjectPredicatesByObject
     93 		o = _subjectPredicatesByObject[objectType];
     94 		if (!o[object]) {
     95 			o[object] = {};
     96 		}
     97 		if (!o[object][predicateID]) {
     98 			o[object][predicateID] = new Set();
     99 		}
    100 		o[object][predicateID].add(subjectID);
    101 	};
    102 	
    103 	
    104 	this.unregister = function (objectType, subjectID, predicate, object) {
    105 		var predicateID = Zotero.RelationPredicates.getID(predicate);
    106 		
    107 		if (!_subjectsByPredicateIDAndObject[objectType]
    108 				|| !_subjectsByPredicateIDAndObject[objectType][predicateID]
    109 				|| !_subjectsByPredicateIDAndObject[objectType][predicateID][object]) {
    110 			return;
    111 		}
    112 		
    113 		_subjectsByPredicateIDAndObject[objectType][predicateID][object].delete(subjectID)
    114 		_subjectPredicatesByObject[objectType][object][predicateID].delete(subjectID)
    115 	};
    116 	
    117 	
    118 	/**
    119 	 * Get the data objects that are subjects with the given predicate and object
    120 	 *
    121 	 * @param {String} objectType - Type of relation to search for (e.g., 'item')
    122 	 * @param {String} predicate
    123 	 * @param {String} object
    124 	 * @return {Zotero.DataObject[]}
    125 	 */
    126 	this.getByPredicateAndObject = function (objectType, predicate, object) {
    127 		var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
    128 		if (predicate) {
    129 			predicate = this._getPrefixAndValue(predicate).join(':');
    130 		}
    131 		
    132 		var predicateID = Zotero.RelationPredicates.getID(predicate);
    133 		
    134 		var o = _subjectsByPredicateIDAndObject[objectType];
    135 		if (!o || !o[predicateID] || !o[predicateID][object]) {
    136 			return [];
    137 		}
    138 		return objectsClass.get(Array.from(o[predicateID][object].values()));
    139 	};
    140 	
    141 	
    142 	/**
    143 	 * Get the data objects that are subjects with the given predicate and object
    144 	 *
    145 	 * @param {String} objectType - Type of relation to search for (e.g., 'item')
    146 	 * @param {String} object
    147 	 * @return {Object[]} - An array of objects with a Zotero.DataObject as 'subject'
    148 	 *     and a predicate string as 'predicate'
    149 	 */
    150 	this.getByObject = Zotero.Promise.coroutine(function* (objectType, object) {
    151 		var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
    152 		var predicateIDs = [];
    153 		var o = _subjectPredicatesByObject[objectType]
    154 			? _subjectPredicatesByObject[objectType][object] : false;
    155 		if (!o) {
    156 			return [];
    157 		}
    158 		var toReturn = [];
    159 		for (let predicateID in o) {
    160 			for (let subjectID of o[predicateID]) {
    161 				var subject = yield objectsClass.getAsync(subjectID);
    162 				toReturn.push({
    163 					subject: subject,
    164 					predicate: Zotero.RelationPredicates.getName(predicateID)
    165 				});
    166 			};
    167 		}
    168 		return toReturn;
    169 	});
    170 	
    171 	
    172 	this.updateUser = Zotero.Promise.coroutine(function* (fromUserID, toUserID) {
    173 		if (!fromUserID) {
    174 			fromUserID = "local/" + Zotero.Users.getLocalUserKey();
    175 		}
    176 		if (!toUserID) {
    177 			throw new Error("Invalid target userID " + toUserID);
    178 		}
    179 		
    180 		Zotero.DB.requireTransaction();
    181 		for (let type of _types) {
    182 			let sql = `SELECT DISTINCT object FROM ${type}Relations WHERE object LIKE ?`;
    183 			let objects = yield Zotero.DB.columnQueryAsync(
    184 				sql, 'http://zotero.org/users/' + fromUserID + '/%'
    185 			);
    186 			Zotero.DB.addCurrentCallback("commit", function* () {
    187 				for (let object of objects) {
    188 					let subPrefs = yield this.getByObject(type, object);
    189 					let newObject = object.replace(
    190 						new RegExp("^http://zotero.org/users/" + fromUserID + "/(.*)"),
    191 						"http://zotero.org/users/" + toUserID + "/$1"
    192 					);
    193 					for (let subPref of subPrefs) {
    194 						this.unregister(type, subPref.subject.id, subPref.predicate, object);
    195 						this.register(type, subPref.subject.id, subPref.predicate, newObject);
    196 					}
    197 				}
    198 			}.bind(this));
    199 			
    200 			sql = "UPDATE " + type + "Relations SET "
    201 				+ "object=REPLACE(object, 'zotero.org/users/" + fromUserID + "/', "
    202 				+ "'zotero.org/users/" + toUserID + "/')";
    203 			yield Zotero.DB.queryAsync(sql);
    204 			
    205 			var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
    206 			let loadedObjects = objectsClass.getLoaded();
    207 			for (let object of loadedObjects) {
    208 				yield object.reload(['relations'], true);
    209 			}
    210 		}
    211 	});
    212 	
    213 	
    214 	this.purge = Zotero.Promise.coroutine(function* () {
    215 		Zotero.debug("Purging relations");
    216 		
    217 		Zotero.DB.requireTransaction();
    218 		var t = new Date;
    219 		let prefix = Zotero.URI.defaultPrefix;
    220 		for (let type of _types) {
    221 			let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
    222 			let getFunc = "getURI" + Zotero.Utilities.capitalize(type);
    223 			let objects = {};
    224 			
    225 			// Get all object URIs except merge-tracking ones
    226 			let sql = "SELECT " + objectsClass.idColumn + " AS id, predicate, object "
    227 				+ "FROM " + objectsClass.relationsTable
    228 				+ " JOIN relationPredicates USING (predicateID) WHERE predicate != ?";
    229 			let rows = yield Zotero.DB.queryAsync(sql, [this.replacedItemPredicate]);
    230 			for (let i = 0; i < rows.length; i++) {
    231 				let row = rows[i];
    232 				let uri = row.object;
    233 				// Erase Zotero URIs of this type that don't resolve to a local object
    234 				//
    235 				// TODO: Check for replaced-item relations and update relation rather than
    236 				// removing
    237 				if (uri.indexOf(prefix) != -1
    238 						&& uri.indexOf("/" + type + "s/") != -1
    239 						&& !(yield Zotero.URI[getFunc](uri))) {
    240 					if (!objects[row.id]) {
    241 						objects[row.id] = yield objectsClass.getAsync(row.id, { noCache: true });
    242 					}
    243 					objects[row.id].removeRelation(row.predicate, uri);
    244 				}
    245 				for (let i in objects) {
    246 					yield objects[i].save();
    247 				}
    248 			}
    249 			
    250 			Zotero.debug("Purged relations in " + ((new Date) - t) + "ms");
    251 		}
    252 	});
    253 	
    254 	
    255 	this._getPrefixAndValue = function(uri) {
    256 		var [prefix, value] = uri.split(':');
    257 		if (prefix && value) {
    258 			if (!this._namespaces[prefix]) {
    259 				throw ("Invalid prefix '" + prefix + "' in Zotero.Relations._getPrefixAndValue()");
    260 			}
    261 			return [prefix, value];
    262 		}
    263 		
    264 		for (var prefix in namespaces) {
    265 			if (uri.indexOf(namespaces[prefix]) == 0) {
    266 				var value = uri.substr(namespaces[prefix].length - 1)
    267 				return [prefix, value];
    268 			}
    269 		}
    270 		throw ("Invalid namespace in URI '" + uri + "' in Zotero.Relations._getPrefixAndValue()");
    271 	}
    272 }