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 }