creators.js (7487B)
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 Zotero.Creators = new function() { 28 this.fields = ['firstName', 'lastName', 'fieldMode']; 29 this.totes = 0; 30 31 var _cache = {}; 32 33 this.init = Zotero.Promise.coroutine(function* () { 34 var repaired = false; 35 var sql = "SELECT * FROM creators"; 36 var rows = yield Zotero.DB.queryAsync(sql); 37 for (let i = 0; i < rows.length; i++) { 38 let row = rows[i]; 39 try { 40 _cache[row.creatorID] = this.cleanData({ 41 // Avoid "DB column 'name' not found" warnings from the DB row Proxy 42 firstName: row.firstName, 43 lastName: row.lastName, 44 fieldMode: row.fieldMode 45 }); 46 } 47 catch (e) { 48 // Automatically fix DB errors and try again 49 if (!repaired) { 50 Zotero.logError(e); 51 Zotero.logError("Trying integrity check to fix creator error"); 52 yield Zotero.Schema.integrityCheck(true); 53 repaired = true; 54 rows = yield Zotero.DB.queryAsync(sql); 55 i = -1; 56 continue; 57 } 58 59 throw e; 60 } 61 } 62 }); 63 64 /* 65 * Returns creator data in internal format for a given creatorID 66 */ 67 this.get = function (creatorID) { 68 if (!creatorID) { 69 throw new Error("creatorID not provided"); 70 } 71 72 if (!_cache[creatorID]) { 73 throw new Error("Creator " + creatorID + " not found"); 74 } 75 76 // Return copy of data 77 return this.cleanData(_cache[creatorID]); 78 }; 79 80 81 this.getItemsWithCreator = function (creatorID) { 82 var sql = "SELECT DISTINCT itemID FROM itemCreators WHERE creatorID=?"; 83 return Zotero.DB.columnQueryAsync(sql, creatorID); 84 } 85 86 87 this.countItemAssociations = function (creatorID) { 88 var sql = "SELECT COUNT(*) FROM itemCreators WHERE creatorID=?"; 89 return Zotero.DB.valueQueryAsync(sql, creatorID); 90 } 91 92 93 /** 94 * Returns the creatorID matching given fields, or creates a new creator and returns its id 95 * 96 * @requireTransaction 97 * @param {Object} data Creator data in API JSON format 98 * @param {Boolean} [create=false] If no matching creator, create one 99 * @return {Promise<Integer>} creatorID 100 */ 101 this.getIDFromData = Zotero.Promise.coroutine(function* (data, create) { 102 Zotero.DB.requireTransaction(); 103 data = this.cleanData(data); 104 var sql = "SELECT creatorID FROM creators WHERE " 105 + "firstName=? AND lastName=? AND fieldMode=?"; 106 var id = yield Zotero.DB.valueQueryAsync( 107 sql, [data.firstName, data.lastName, data.fieldMode] 108 ); 109 if (!id && create) { 110 id = Zotero.ID.get('creators'); 111 let sql = "INSERT INTO creators (creatorID, firstName, lastName, fieldMode) " 112 + "VALUES (?, ?, ?, ?)"; 113 yield Zotero.DB.queryAsync( 114 sql, [id, data.firstName, data.lastName, data.fieldMode] 115 ); 116 _cache[id] = data; 117 } 118 return id; 119 }); 120 121 122 this.updateCreator = Zotero.Promise.coroutine(function* (creatorID, creatorData) { 123 var creator = yield this.get(creatorID); 124 if (!creator) { 125 throw new Error("Creator " + creatorID + " doesn't exist"); 126 } 127 creator.fieldMode = creatorData.fieldMode; 128 creator.firstName = creatorData.firstName; 129 creator.lastName = creatorData.lastName; 130 return creator.save(); 131 }); 132 133 134 /** 135 * Delete obsolete creator rows from database and clear internal cache entries 136 * 137 * @return {Promise} 138 */ 139 this.purge = Zotero.Promise.coroutine(function* () { 140 if (!Zotero.Prefs.get('purge.creators')) { 141 return; 142 } 143 144 Zotero.debug("Purging creator tables"); 145 146 var sql = 'SELECT creatorID FROM creators WHERE creatorID NOT IN ' 147 + '(SELECT creatorID FROM itemCreators)'; 148 var toDelete = yield Zotero.DB.columnQueryAsync(sql); 149 if (toDelete.length) { 150 // Clear creator entries in internal array 151 for (let i=0; i<toDelete.length; i++) { 152 delete _cache[toDelete[i]]; 153 } 154 155 var sql = "DELETE FROM creators WHERE creatorID NOT IN " 156 + "(SELECT creatorID FROM itemCreators)"; 157 yield Zotero.DB.queryAsync(sql); 158 } 159 160 Zotero.Prefs.set('purge.creators', false); 161 }); 162 163 164 this.equals = function (data1, data2) { 165 data1 = this.cleanData(data1); 166 data2 = this.cleanData(data2); 167 return data1.lastName === data2.lastName 168 && data1.firstName === data2.firstName 169 && data1.fieldMode === data2.fieldMode 170 && data1.creatorTypeID === data2.creatorTypeID; 171 }, 172 173 174 this.cleanData = function (data) { 175 // Validate data 176 if (data.name === undefined && data.lastName === undefined) { 177 throw new Error("Creator data must contain either 'name' or 'firstName'/'lastName' properties"); 178 } 179 if (data.name !== undefined && (data.firstName !== undefined || data.lastName !== undefined)) { 180 throw new Error("Creator data cannot contain both 'name' and 'firstName'/'lastName' properties"); 181 } 182 if (data.name !== undefined && data.fieldMode === 0) { 183 throw new Error("'fieldMode' cannot be 0 with 'name' property"); 184 } 185 if (data.fieldMode === 1 186 && !(data.firstName === undefined || data.firstName === "" || data.firstName === null)) { 187 throw new Error("'fieldMode' cannot be 1 with 'firstName' property"); 188 } 189 if (data.name !== undefined && typeof data.name != 'string') { 190 throw new Error("'name' must be a string"); 191 } 192 if (data.firstName !== undefined && data.firstName !== null && typeof data.firstName != 'string') { 193 throw new Error("'firstName' must be a string"); 194 } 195 if (data.lastName !== undefined && typeof data.lastName != 'string') { 196 throw new Error("'lastName' must be a string"); 197 } 198 199 var cleanedData = { 200 fieldMode: 0, 201 firstName: '', 202 lastName: '' 203 }; 204 for (let i=0; i<this.fields.length; i++) { 205 let field = this.fields[i]; 206 let val = data[field]; 207 switch (field) { 208 case 'firstName': 209 case 'lastName': 210 if (val === undefined || val === null) continue; 211 cleanedData[field] = val.trim().normalize(); 212 break; 213 214 case 'fieldMode': 215 cleanedData[field] = val ? parseInt(val) : 0; 216 break; 217 } 218 } 219 220 // Handle API JSON .name 221 if (data.name !== undefined) { 222 cleanedData.lastName = data.name.trim().normalize(); 223 cleanedData.fieldMode = 1; 224 } 225 226 var creatorType = data.creatorType || data.creatorTypeID; 227 if (creatorType) { 228 cleanedData.creatorTypeID = Zotero.CreatorTypes.getID(creatorType); 229 if (!cleanedData.creatorTypeID) { 230 let msg = "'" + creatorType + "' isn't a valid creator type"; 231 Zotero.debug(msg, 2); 232 Components.utils.reportError(msg); 233 } 234 } 235 236 return cleanedData; 237 } 238 239 240 this.internalToJSON = function (fields) { 241 var obj = {}; 242 if (fields.fieldMode == 1) { 243 obj.name = fields.lastName; 244 } 245 else { 246 obj.firstName = fields.firstName; 247 obj.lastName = fields.lastName; 248 } 249 obj.creatorType = Zotero.CreatorTypes.getName(fields.creatorTypeID); 250 return obj; 251 } 252 }