
- Still experimental, but committing for testing - Sync conflicts with deleted items aren't yet supported Unrelated: deprecated ZoteroPane.deleteSelectedItem() in favor of more accurately named deleteSelectedItems()
2193 lines
85 KiB
JavaScript
2193 lines
85 KiB
JavaScript
/*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (c) 2006 Center for History and New Media
|
|
George Mason University, Fairfax, Virginia, USA
|
|
http://chnm.gmu.edu
|
|
|
|
Licensed under the Educational Community License, Version 1.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.opensource.org/licenses/ecl1.php
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
*/
|
|
|
|
Zotero.Schema = new function(){
|
|
this.userDataUpgradeRequired = userDataUpgradeRequired;
|
|
this.showUpgradeWizard = showUpgradeWizard;
|
|
this.updateSchema = updateSchema;
|
|
this.stopRepositoryTimer = stopRepositoryTimer;
|
|
|
|
this.skipDefaultData = false;
|
|
this.dbInitialized = false;
|
|
this.goToChangeLog = false;
|
|
|
|
var _dbVersions = [];
|
|
var _schemaVersions = [];
|
|
var _repositoryTimer;
|
|
var _remoteUpdateInProgress = false;
|
|
|
|
var self = this;
|
|
|
|
function userDataUpgradeRequired() {
|
|
var dbVersion = _getDBVersion('userdata');
|
|
var schemaVersion = _getSchemaSQLVersion('userdata');
|
|
|
|
return dbVersion && (dbVersion < schemaVersion);
|
|
}
|
|
|
|
|
|
function showUpgradeWizard() {
|
|
var dbVersion = _getDBVersion('userdata');
|
|
var schemaVersion = _getSchemaSQLVersion('userdata');
|
|
|
|
var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
|
|
.getService(Components.interfaces.nsIWindowWatcher);
|
|
var obj = { Zotero: Zotero, data: { success: false } };
|
|
var io = { wrappedJSObject: obj };
|
|
var win = ww.openWindow(null, "chrome://zotero/content/upgrade.xul",
|
|
"zotero-schema-upgrade", "chrome,centerscreen,modal", io);
|
|
|
|
if (obj.data.e) {
|
|
var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
|
|
.getService(Components.interfaces.nsIWindowWatcher);
|
|
var data = {
|
|
msg: obj.data.msg,
|
|
e: obj.data.e,
|
|
extraData: "Schema upgrade from " + dbVersion + " to " + schemaVersion
|
|
};
|
|
var io = { wrappedJSObject: { Zotero: Zotero, data: data } };
|
|
var win = ww.openWindow(null, "chrome://zotero/content/errorReport.xul",
|
|
"zotero-error-report", "chrome,centerscreen,modal", io);
|
|
}
|
|
|
|
return obj.data.success;
|
|
}
|
|
|
|
|
|
/*
|
|
* Checks if the DB schema exists and is up-to-date, updating if necessary
|
|
*/
|
|
function updateSchema(){
|
|
var dbVersion = _getDBVersion('userdata');
|
|
|
|
// 'schema' check is for old (<= 1.0b1) schema system,
|
|
// 'user' is for pre-1.0b2 'user' table
|
|
if (!dbVersion && !_getDBVersion('schema') && !_getDBVersion('user')){
|
|
Zotero.debug('Database does not exist -- creating\n');
|
|
_initializeSchema();
|
|
return;
|
|
}
|
|
|
|
var schemaVersion = _getSchemaSQLVersion('userdata');
|
|
|
|
try {
|
|
Zotero.UnresponsiveScriptIndicator.disable();
|
|
|
|
// If upgrading userdata, make backup of database first
|
|
if (dbVersion < schemaVersion){
|
|
Zotero.DB.backupDatabase(dbVersion);
|
|
}
|
|
|
|
Zotero.DB.beginTransaction();
|
|
|
|
try {
|
|
// Old schema system
|
|
if (!dbVersion){
|
|
// Check for pre-1.0b2 'user' table
|
|
var user = _getDBVersion('user');
|
|
if (user)
|
|
{
|
|
dbVersion = user;
|
|
var sql = "UPDATE version SET schema=? WHERE schema=?";
|
|
Zotero.DB.query(sql, ['userdata', 'user']);
|
|
}
|
|
else
|
|
{
|
|
dbVersion = 0;
|
|
}
|
|
}
|
|
|
|
var up1 = _migrateUserDataSchema(dbVersion);
|
|
var up2 = _updateSchema('system');
|
|
var up3 = _updateSchema('triggers');
|
|
|
|
Zotero.DB.commitTransaction();
|
|
}
|
|
catch(e){
|
|
Zotero.debug(e);
|
|
Zotero.DB.rollbackTransaction();
|
|
throw(e);
|
|
}
|
|
|
|
if (up1) {
|
|
// Upgrade seems to have been a success -- delete any previous backups
|
|
var maxPrevious = dbVersion - 1;
|
|
var file = Zotero.getZoteroDirectory();
|
|
// directoryEntries.hasMoreElements() throws an error (possibly
|
|
// because of the temporary SQLite journal file?), so we just look
|
|
// for all versions
|
|
for (var i=maxPrevious; i>=29; i--) {
|
|
var fileName = 'zotero.sqlite.' + i + '.bak';
|
|
file.append(fileName);
|
|
if (file.exists()) {
|
|
Zotero.debug('Removing previous backup file ' + fileName);
|
|
file.remove(null);
|
|
}
|
|
file = file.parent;
|
|
}
|
|
}
|
|
|
|
var up4 = this.updateBundledFiles('translators');
|
|
var up5 = this.updateBundledFiles('styles');
|
|
|
|
if (up2 || up3 || up4 || up5) {
|
|
// Run a manual scraper update if upgraded and pref set
|
|
if (Zotero.Prefs.get('automaticScraperUpdates')){
|
|
this.updateFromRepository(2);
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
Zotero.UnresponsiveScriptIndicator.enable();
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* Update styles and translators in data directory with versions from
|
|
* ZIP file (XPI) or directory (SVN) in extension directory
|
|
*
|
|
* @param {String} mode 'translators' or 'styles'
|
|
*/
|
|
this.updateBundledFiles = function (mode) {
|
|
switch (mode) {
|
|
case "translators":
|
|
var titleField = 'label';
|
|
var fileExt = ".js";
|
|
break;
|
|
|
|
case "styles":
|
|
var titleField = 'title';
|
|
var fileExt = ".csl";
|
|
var hiddenDir = Zotero.getTranslatorsDirectory();
|
|
hiddenDir.append('hidden');
|
|
break;
|
|
|
|
default:
|
|
throw ("Invalid mode '" + mode + "' in Zotero.Schema.updateBundledFiles()");
|
|
}
|
|
|
|
var modes = mode;
|
|
mode = mode.substr(0, mode.length - 1);
|
|
var Mode = mode[0].toUpperCase() + mode.substr(1);
|
|
var Modes = Mode + "s";
|
|
|
|
var extDir = Zotero.getInstallDirectory();
|
|
|
|
var repotime = extDir.clone();
|
|
repotime.append('repotime.txt');
|
|
repotime = Zotero.File.getContents(repotime);
|
|
var date = Zotero.Date.sqlToDate(repotime, true);
|
|
repotime = Zotero.Date.toUnixTimestamp(date);
|
|
|
|
var zipFile = extDir.clone();
|
|
zipFile.append(modes + ".zip");
|
|
|
|
var fileNameRE = new RegExp("^[^\.].+\\" + fileExt + "$");
|
|
|
|
var destDir = Zotero["get" + Modes + "Directory"]();
|
|
|
|
// If directory is empty, force reinstall
|
|
var forceReinstall = true;
|
|
var entries = destDir.directoryEntries;
|
|
while (entries.hasMoreElements()) {
|
|
var file = entries.getNext();
|
|
file.QueryInterface(Components.interfaces.nsIFile);
|
|
if (!file.leafName.match(fileNameRE) || file.isDirectory()) {
|
|
continue;
|
|
}
|
|
// Not empty
|
|
forceReinstall = false;
|
|
break;
|
|
}
|
|
|
|
var sql = "SELECT version FROM version WHERE schema=?";
|
|
var lastModTime = Zotero.DB.valueQuery(sql, modes);
|
|
|
|
if (zipFile.exists()) {
|
|
var modTime = Math.round(zipFile.lastModifiedTime / 1000);
|
|
|
|
if (!forceReinstall && lastModTime && modTime <= lastModTime) {
|
|
Zotero.debug("Installed " + modes + " are up-to-date with " + modes + ".zip");
|
|
return 0;
|
|
}
|
|
|
|
Zotero.debug("Updating installed " + modes + " from " + modes + ".zip");
|
|
|
|
var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
|
|
.getService(Components.interfaces.nsIZipReader);
|
|
zipReader.open(zipFile);
|
|
var tmpDir = Zotero.getTempDirectory();
|
|
var entries = zipReader.findEntries(null);
|
|
while (entries.hasMore()) {
|
|
var entry = entries.getNext();
|
|
|
|
var tmpFile = tmpDir.clone();
|
|
tmpFile.append(entry);
|
|
if (tmpFile.exists()) {
|
|
tmpFile.remove(false);
|
|
}
|
|
zipReader.extract(entry, tmpFile);
|
|
var newObj = new Zotero[Mode](tmpFile);
|
|
|
|
var existingObj = Zotero[Modes].get(newObj[mode + "ID"]);
|
|
if (!existingObj) {
|
|
Zotero.debug("Installing " + mode + " '" + newObj[titleField] + "'");
|
|
}
|
|
else {
|
|
Zotero.debug("Updating "
|
|
+ (existingObj.hidden ? "hidden " : "")
|
|
+ mode + " '" + existingObj[titleField] + "'");
|
|
if (existingObj.file.exists()) {
|
|
existingObj.file.remove(false);
|
|
}
|
|
}
|
|
|
|
if (mode == 'translator') {
|
|
var fileName = Zotero.File.getValidFileName(newObj[titleField]) + fileExt;
|
|
|
|
var destFile = destDir.clone();
|
|
destFile.append(fileName);
|
|
if (destFile.exists()) {
|
|
var msg = "Overwriting translator with same filename '"
|
|
+ fileName + "'";
|
|
Zotero.debug(msg, 1);
|
|
Components.utils.reportError(msg + " in Zotero.Schema.updateBundledFiles()");
|
|
destFile.remove(false);
|
|
}
|
|
}
|
|
else if (mode == 'style') {
|
|
var fileName = tmpFile.leafName;
|
|
}
|
|
|
|
if (!existingObj || !existingObj.hidden) {
|
|
tmpFile.moveTo(destDir, fileName);
|
|
}
|
|
else {
|
|
tmpFile.moveTo(hiddenDir, fileName);
|
|
}
|
|
}
|
|
zipReader.close();
|
|
}
|
|
else {
|
|
var sourceDir = extDir.clone();
|
|
sourceDir.append(modes);
|
|
if (!sourceDir.exists()) {
|
|
Components.utils.reportError("No " + modes + " ZIP file or directory "
|
|
+ " in Zotero.Schema.updateBundledFiles()");
|
|
return -1;
|
|
}
|
|
|
|
var entries = sourceDir.directoryEntries;
|
|
var modTime = 0;
|
|
while (entries.hasMoreElements()) {
|
|
var file = entries.getNext();
|
|
file.QueryInterface(Components.interfaces.nsIFile);
|
|
// File might not exist in an SVN build with style symlinks
|
|
if (!file.exists()
|
|
|| !file.leafName.match(fileNameRE)
|
|
|| file.isDirectory()) {
|
|
continue;
|
|
}
|
|
var fileModTime = Math.round(file.lastModifiedTime / 1000);
|
|
if (fileModTime > modTime) {
|
|
modTime = fileModTime;
|
|
}
|
|
}
|
|
|
|
if (!forceReinstall && lastModTime && modTime <= lastModTime) {
|
|
Zotero.debug("Installed " + modes + " are up-to-date with " + modes + " directory");
|
|
return 0;
|
|
}
|
|
|
|
Zotero.debug("Updating installed " + modes + " from " + modes + " directory");
|
|
|
|
var entries = sourceDir.directoryEntries;
|
|
while (entries.hasMoreElements()) {
|
|
var file = entries.getNext();
|
|
file.QueryInterface(Components.interfaces.nsIFile);
|
|
if (!file.exists() || !file.leafName.match(fileNameRE) || file.isDirectory()) {
|
|
continue;
|
|
}
|
|
var newObj = new Zotero[Mode](file);
|
|
var existingObj = Zotero[Modes].get(newObj[mode + "ID"]);
|
|
if (!existingObj) {
|
|
Zotero.debug("Installing " + mode + " '" + newObj[titleField] + "'");
|
|
}
|
|
else {
|
|
Zotero.debug("Updating "
|
|
+ (existingObj.hidden ? "hidden " : "")
|
|
+ mode + " '" + existingObj[titleField] + "'");
|
|
if (existingObj.file.exists()) {
|
|
existingObj.file.remove(false);
|
|
}
|
|
}
|
|
|
|
if (mode == 'translator') {
|
|
var fileName = Zotero.File.getValidFileName(newObj[titleField]) + fileExt
|
|
|
|
var destFile = destDir.clone();
|
|
destFile.append(fileName);
|
|
if (destFile.exists()) {
|
|
var msg = "Overwriting translator with same filename '"
|
|
+ fileName + "'";
|
|
Zotero.debug(msg, 1);
|
|
Components.utils.reportError(msg + " in Zotero.Schema.updateBundledFiles()");
|
|
destFile.remove(false);
|
|
}
|
|
}
|
|
else if (mode == 'style') {
|
|
var fileName = file.leafName;
|
|
}
|
|
|
|
if (!existingObj || !existingObj.hidden) {
|
|
file.copyTo(destDir, fileName);
|
|
}
|
|
else {
|
|
file.copyTo(hiddenDir, fileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
Zotero.DB.beginTransaction();
|
|
|
|
var sql = "REPLACE INTO version VALUES (?, ?)";
|
|
Zotero.DB.query(sql, [modes, modTime]);
|
|
|
|
var sql = "REPLACE INTO version VALUES ('repository', ?)";
|
|
Zotero.DB.query(sql, repotime);
|
|
|
|
Zotero.DB.commitTransaction();
|
|
|
|
Zotero[Modes].init();
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send XMLHTTP request for updated translators and styles to the central repository
|
|
*
|
|
* @param {Boolean} force Force a repository query regardless of how
|
|
* long it's been since the last check
|
|
* @param {Function} callback
|
|
*/
|
|
this.updateFromRepository = function (force, callback) {
|
|
// Little hack to manually update CSLs from repo on upgrades
|
|
if (!force && Zotero.Prefs.get('automaticScraperUpdates')) {
|
|
var syncTargetVersion = 3; // increment this when releasing new version that requires it
|
|
var syncVersion = _getDBVersion('sync');
|
|
if (syncVersion < syncTargetVersion) {
|
|
force = true;
|
|
var forceCSLUpdate = true;
|
|
}
|
|
}
|
|
|
|
if (!force){
|
|
if (_remoteUpdateInProgress) {
|
|
Zotero.debug("A remote update is already in progress -- not checking repository");
|
|
return false;
|
|
}
|
|
|
|
// Check user preference for automatic updates
|
|
if (!Zotero.Prefs.get('automaticScraperUpdates')){
|
|
Zotero.debug('Automatic repository updating disabled -- not checking repository', 4);
|
|
return false;
|
|
}
|
|
|
|
// Determine the earliest local time that we'd query the repository again
|
|
var nextCheck = new Date();
|
|
nextCheck.setTime((parseInt(_getDBVersion('lastcheck'))
|
|
+ ZOTERO_CONFIG['REPOSITORY_CHECK_INTERVAL']) * 1000); // JS uses ms
|
|
var now = new Date();
|
|
|
|
// If enough time hasn't passed, don't update
|
|
if (now < nextCheck){
|
|
Zotero.debug('Not enough time since last update -- not checking repository', 4);
|
|
// Set the repository timer to the remaining time
|
|
_setRepositoryTimer(Math.round((nextCheck.getTime() - now.getTime()) / 1000));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If transaction already in progress, delay by ten minutes
|
|
if (Zotero.DB.transactionInProgress()){
|
|
Zotero.debug('Transaction in progress -- delaying repository check', 4)
|
|
_setRepositoryTimer(600);
|
|
return false;
|
|
}
|
|
|
|
// Get the last timestamp we got from the server
|
|
var lastUpdated = _getDBVersion('repository');
|
|
|
|
var url = ZOTERO_CONFIG['REPOSITORY_URL'] + '/updated?'
|
|
+ (lastUpdated ? 'last=' + lastUpdated + '&' : '')
|
|
+ 'version=' + Zotero.version;
|
|
|
|
Zotero.debug('Checking repository for updates');
|
|
|
|
_remoteUpdateInProgress = true;
|
|
|
|
if (force) {
|
|
if (force == 2) {
|
|
url += '&m=2';
|
|
}
|
|
else {
|
|
url += '&m=1';
|
|
}
|
|
|
|
// Force updating of all public CSLs
|
|
if (forceCSLUpdate) {
|
|
url += '&cslup=' + syncTargetVersion;
|
|
}
|
|
}
|
|
|
|
var get = Zotero.Utilities.HTTP.doGet(url, function (xmlhttp) {
|
|
var updated = _updateFromRepositoryCallback(xmlhttp, !!force);
|
|
if (callback) {
|
|
callback(xmlhttp, updated)
|
|
}
|
|
});
|
|
|
|
// TODO: instead, add an observer to start and stop timer on online state change
|
|
if (!get){
|
|
Zotero.debug('Browser is offline -- skipping check');
|
|
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_RETRY_INTERVAL']);
|
|
}
|
|
}
|
|
|
|
|
|
function stopRepositoryTimer(){
|
|
if (_repositoryTimer){
|
|
Zotero.debug('Stopping repository check timer');
|
|
_repositoryTimer.cancel();
|
|
}
|
|
}
|
|
|
|
|
|
this.resetTranslatorsAndStyles = function (callback) {
|
|
Zotero.debug("Resetting translators and styles");
|
|
|
|
var sql = "DELETE FROM version WHERE schema IN "
|
|
+ "('translators', 'styles', 'repository', 'lastcheck')";
|
|
Zotero.DB.query(sql);
|
|
_dbVersions.repository = null;
|
|
_dbVersions.lastcheck = null;
|
|
|
|
var translatorsDir = Zotero.getTranslatorsDirectory();
|
|
translatorsDir.remove(true);
|
|
Zotero.getTranslatorsDirectory(); // recreate directory
|
|
Zotero.Translators.init();
|
|
this.updateBundledFiles('translators');
|
|
|
|
var stylesDir = Zotero.getStylesDirectory();
|
|
stylesDir.remove(true);
|
|
Zotero.getStylesDirectory(); // recreate directory
|
|
Zotero.Styles.init();
|
|
this.updateBundledFiles('styles');
|
|
|
|
// Run a manual update from repository if pref set
|
|
if (Zotero.Prefs.get('automaticScraperUpdates')) {
|
|
this.updateFromRepository(2, callback);
|
|
}
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// Private methods
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* Retrieve the DB schema version
|
|
*/
|
|
function _getDBVersion(schema){
|
|
if (_dbVersions[schema]){
|
|
return _dbVersions[schema];
|
|
}
|
|
|
|
if (Zotero.DB.tableExists('version')){
|
|
var dbVersion = Zotero.DB.valueQuery("SELECT version FROM "
|
|
+ "version WHERE schema='" + schema + "'");
|
|
_dbVersions[schema] = dbVersion;
|
|
return dbVersion;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Retrieve the version from the top line of the schema SQL file
|
|
*/
|
|
function _getSchemaSQLVersion(schema){
|
|
if (!schema){
|
|
throw ('Schema type not provided to _getSchemaSQLVersion()');
|
|
}
|
|
|
|
var schemaFile = schema + '.sql';
|
|
|
|
if (_schemaVersions[schema]){
|
|
return _schemaVersions[schema];
|
|
}
|
|
|
|
var file = Components.classes["@mozilla.org/extensions/manager;1"]
|
|
.getService(Components.interfaces.nsIExtensionManager)
|
|
.getInstallLocation(ZOTERO_CONFIG['GUID'])
|
|
.getItemLocation(ZOTERO_CONFIG['GUID']);
|
|
file.append(schemaFile);
|
|
|
|
// Open an input stream from file
|
|
var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
|
.createInstance(Components.interfaces.nsIFileInputStream);
|
|
istream.init(file, 0x01, 0444, 0);
|
|
istream.QueryInterface(Components.interfaces.nsILineInputStream);
|
|
|
|
var line = {};
|
|
|
|
// Fetch the schema version from the first line of the file
|
|
istream.readLine(line);
|
|
var schemaVersion = line.value.match(/-- ([0-9]+)/)[1];
|
|
istream.close();
|
|
|
|
_schemaVersions[schema] = schemaVersion;
|
|
return schemaVersion;
|
|
}
|
|
|
|
|
|
/*
|
|
* Load in SQL schema
|
|
*
|
|
* Returns the contents of an SQL file for feeding into query()
|
|
*/
|
|
function _getSchemaSQL(schema){
|
|
if (!schema){
|
|
throw ('Schema type not provided to _getSchemaSQL()');
|
|
}
|
|
|
|
var schemaFile = schema + '.sql';
|
|
|
|
// We pull the schema from an external file so we only have to process
|
|
// it when necessary
|
|
var file = Components.classes["@mozilla.org/extensions/manager;1"]
|
|
.getService(Components.interfaces.nsIExtensionManager)
|
|
.getInstallLocation(ZOTERO_CONFIG['GUID'])
|
|
.getItemLocation(ZOTERO_CONFIG['GUID']);
|
|
file.append(schemaFile);
|
|
|
|
// Open an input stream from file
|
|
var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
|
.createInstance(Components.interfaces.nsIFileInputStream);
|
|
istream.init(file, 0x01, 0444, 0);
|
|
istream.QueryInterface(Components.interfaces.nsILineInputStream);
|
|
|
|
var line = {}, sql = '', hasmore;
|
|
|
|
// Skip the first line, which contains the schema version
|
|
istream.readLine(line);
|
|
//var schemaVersion = line.value.match(/-- ([0-9]+)/)[1];
|
|
|
|
do {
|
|
hasmore = istream.readLine(line);
|
|
sql += line.value + "\n";
|
|
} while(hasmore);
|
|
|
|
istream.close();
|
|
|
|
return sql;
|
|
}
|
|
|
|
|
|
/*
|
|
* Determine the SQL statements necessary to drop the tables and indexed
|
|
* in a given schema file
|
|
*
|
|
* NOTE: This is not currently used.
|
|
*
|
|
* Returns the SQL statements as a string for feeding into query()
|
|
*/
|
|
function _getDropCommands(schema){
|
|
if (!schema){
|
|
throw ('Schema type not provided to _getSchemaSQL()');
|
|
}
|
|
|
|
var schemaFile = schema + '.sql';
|
|
|
|
// We pull the schema from an external file so we only have to process
|
|
// it when necessary
|
|
var file = Components.classes["@mozilla.org/extensions/manager;1"]
|
|
.getService(Components.interfaces.nsIExtensionManager)
|
|
.getInstallLocation(ZOTERO_CONFIG['GUID'])
|
|
.getItemLocation(ZOTERO_CONFIG['GUID']);
|
|
file.append(schemaFile);
|
|
|
|
// Open an input stream from file
|
|
var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
|
.createInstance(Components.interfaces.nsIFileInputStream);
|
|
istream.init(file, 0x01, 0444, 0);
|
|
istream.QueryInterface(Components.interfaces.nsILineInputStream);
|
|
|
|
var line = {}, str = '', hasmore;
|
|
|
|
// Skip the first line, which contains the schema version
|
|
istream.readLine(line);
|
|
|
|
do {
|
|
hasmore = istream.readLine(line);
|
|
var matches =
|
|
line.value.match(/CREATE (TABLE|INDEX) IF NOT EXISTS ([^\s]+)/);
|
|
if (matches){
|
|
str += "DROP " + matches[1] + " IF EXISTS " + matches[2] + ";\n";
|
|
}
|
|
} while(hasmore);
|
|
|
|
istream.close();
|
|
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* Create new DB schema
|
|
*/
|
|
function _initializeSchema(){
|
|
Zotero.DB.beginTransaction();
|
|
try {
|
|
// Enable auto-vacuuming
|
|
Zotero.DB.query("PRAGMA page_size = 4096");
|
|
Zotero.DB.query("PRAGMA encoding = 'UTF-8'");
|
|
Zotero.DB.query("PRAGMA auto_vacuum = 1");
|
|
|
|
Zotero.DB.query(_getSchemaSQL('system'));
|
|
Zotero.DB.query(_getSchemaSQL('userdata'));
|
|
Zotero.DB.query(_getSchemaSQL('triggers'));
|
|
|
|
_updateDBVersion('system', _getSchemaSQLVersion('system'));
|
|
_updateDBVersion('userdata', _getSchemaSQLVersion('userdata'));
|
|
_updateDBVersion('triggers', _getSchemaSQLVersion('triggers'));
|
|
|
|
if (!Zotero.Schema.skipDefaultData) {
|
|
/*
|
|
TODO: uncomment for release
|
|
var sql = "INSERT INTO items VALUES(1, 14, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'AJ4PT6IT')";
|
|
Zotero.DB.query(sql);
|
|
var sql = "INSERT INTO itemAttachments VALUES (1, NULL, 3, 'text/html', 25, NULL, NULL)";
|
|
Zotero.DB.query(sql);
|
|
var sql = "INSERT INTO itemDataValues VALUES (?, ?)";
|
|
Zotero.DB.query(sql, [1, "Zotero - " + Zotero.getString('install.quickStartGuide')]);
|
|
var sql = "INSERT INTO itemData VALUES (1, 110, 1)";
|
|
Zotero.DB.query(sql);
|
|
var sql = "INSERT INTO itemDataValues VALUES (2, 'http://www.zotero.org/documentation/quick_start_guide')";
|
|
Zotero.DB.query(sql);
|
|
var sql = "INSERT INTO itemData VALUES (1, 1, 2)";
|
|
Zotero.DB.query(sql);
|
|
var sql = "INSERT INTO itemDataValues VALUES (3, CURRENT_TIMESTAMP)";
|
|
Zotero.DB.query(sql);
|
|
var sql = "INSERT INTO itemData VALUES (1, 27, 3)";
|
|
Zotero.DB.query(sql);
|
|
var sql = "INSERT INTO itemNotes (itemID, sourceItemID, note) VALUES (1, NULL, ?)";
|
|
var msg = Zotero.getString('install.quickStartGuide.message.welcome')
|
|
+ " " + Zotero.getString('install.quickStartGuide.message.clickViewPage')
|
|
+ "\n\n" + Zotero.getString('install.quickStartGuide.message.thanks');
|
|
Zotero.DB.query(sql, msg);
|
|
*/
|
|
}
|
|
Zotero.DB.commitTransaction();
|
|
|
|
self.dbInitialized = true;
|
|
}
|
|
catch(e){
|
|
Zotero.debug(e, 1);
|
|
Components.utils.reportError(e);
|
|
Zotero.DB.rollbackTransaction();
|
|
alert('Error initializing Zotero database');
|
|
throw(e);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Update a DB schema version tag in an existing database
|
|
*/
|
|
function _updateDBVersion(schema, version){
|
|
_dbVersions[schema] = version;
|
|
var sql = "REPLACE INTO version (schema,version) VALUES (?,?)";
|
|
return Zotero.DB.query(sql, [{'string':schema},{'int':version}]);
|
|
}
|
|
|
|
|
|
function _updateSchema(schema){
|
|
var dbVersion = _getDBVersion(schema);
|
|
var schemaVersion = _getSchemaSQLVersion(schema);
|
|
|
|
if (dbVersion == schemaVersion){
|
|
return false;
|
|
}
|
|
else if (dbVersion < schemaVersion){
|
|
Zotero.DB.beginTransaction();
|
|
try {
|
|
Zotero.DB.query(_getSchemaSQL(schema));
|
|
_updateDBVersion(schema, schemaVersion);
|
|
Zotero.DB.commitTransaction();
|
|
}
|
|
catch (e){
|
|
Zotero.debug(e, 1);
|
|
Zotero.DB.rollbackTransaction();
|
|
throw(e);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
throw ("Zotero '" + schema + "' DB version (" + dbVersion
|
|
+ ") is newer than SQL file (" + schemaVersion + ")");
|
|
}
|
|
|
|
|
|
/**
|
|
* Process the response from the repository
|
|
**/
|
|
function _updateFromRepositoryCallback(xmlhttp, manual){
|
|
if (!xmlhttp.responseXML){
|
|
try {
|
|
if (xmlhttp.status>1000){
|
|
Zotero.debug('No network connection', 2);
|
|
}
|
|
else {
|
|
Zotero.debug('Invalid response from repository', 2);
|
|
}
|
|
}
|
|
catch (e){
|
|
Zotero.debug('Repository cannot be contacted');
|
|
}
|
|
|
|
if (!manual){
|
|
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_RETRY_INTERVAL']);
|
|
}
|
|
|
|
_remoteUpdateInProgress = false;
|
|
return false;
|
|
}
|
|
|
|
var currentTime = xmlhttp.responseXML.
|
|
getElementsByTagName('currentTime')[0].firstChild.nodeValue;
|
|
var translatorUpdates = xmlhttp.responseXML.getElementsByTagName('translator');
|
|
var styleUpdates = xmlhttp.responseXML.getElementsByTagName('style');
|
|
|
|
Zotero.DB.beginTransaction();
|
|
|
|
try {
|
|
var re = /cslup=([0-9]+)/;
|
|
var matches = re.exec(xmlhttp.channel.URI.spec);
|
|
if (matches) {
|
|
_updateDBVersion('sync', matches[1]);
|
|
}
|
|
}
|
|
catch (e) {
|
|
Zotero.debug(e);
|
|
}
|
|
|
|
// Store the timestamp provided by the server
|
|
_updateDBVersion('repository', currentTime);
|
|
|
|
if (!manual){
|
|
// And the local timestamp of the update time
|
|
var d = new Date();
|
|
_updateDBVersion('lastcheck', Math.round(d.getTime()/1000)); // JS uses ms
|
|
}
|
|
|
|
if (!translatorUpdates.length && !styleUpdates.length){
|
|
Zotero.debug('All translators and styles are up-to-date');
|
|
Zotero.DB.commitTransaction();
|
|
if (!manual){
|
|
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_CHECK_INTERVAL']);
|
|
}
|
|
_remoteUpdateInProgress = false;
|
|
return -1;
|
|
}
|
|
|
|
try {
|
|
for (var i=0, len=translatorUpdates.length; i<len; i++){
|
|
_translatorXMLToFile(translatorUpdates[i]);
|
|
}
|
|
|
|
for (var i=0, len=styleUpdates.length; i<len; i++){
|
|
_styleXMLToFile(styleUpdates[i]);
|
|
}
|
|
|
|
// Rebuild caches
|
|
Zotero.Translators.init();
|
|
Zotero.Styles.init();
|
|
}
|
|
catch (e) {
|
|
Zotero.debug(e, 1);
|
|
Zotero.DB.rollbackTransaction();
|
|
if (!manual){
|
|
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_RETRY_INTERVAL']);
|
|
}
|
|
_remoteUpdateInProgress = false;
|
|
return false;
|
|
}
|
|
|
|
Zotero.DB.commitTransaction();
|
|
if (!manual){
|
|
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_CHECK_INTERVAL']);
|
|
}
|
|
_remoteUpdateInProgress = false;
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the interval between repository queries
|
|
*
|
|
* We add an additional two seconds to avoid race conditions
|
|
**/
|
|
function _setRepositoryTimer(interval){
|
|
if (!interval){
|
|
interval = ZOTERO_CONFIG['REPOSITORY_CHECK_INTERVAL'];
|
|
}
|
|
|
|
var fudge = 2; // two seconds
|
|
var displayInterval = interval + fudge;
|
|
var interval = (interval + fudge) * 1000; // convert to ms
|
|
|
|
if (!_repositoryTimer || _repositoryTimer.delay!=interval){
|
|
Zotero.debug('Setting repository check interval to ' + displayInterval + ' seconds');
|
|
_repositoryTimer = Components.classes["@mozilla.org/timer;1"].
|
|
createInstance(Components.interfaces.nsITimer);
|
|
_repositoryTimer.initWithCallback({
|
|
// implements nsITimerCallback
|
|
notify: function(timer){
|
|
Zotero.Schema.updateFromRepository();
|
|
}
|
|
}, interval, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Traverse an XML translator node from the repository and
|
|
* update the local translators folder with the translator data
|
|
**/
|
|
function _translatorXMLToFile(xmlnode) {
|
|
// Don't split >4K chunks into multiple nodes
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=194231
|
|
xmlnode.normalize();
|
|
var translatorID = xmlnode.getAttribute('id');
|
|
var translator = Zotero.Translators.get(translatorID);
|
|
|
|
// Delete local version of remote translators with priority 0
|
|
if (xmlnode.getElementsByTagName('priority')[0].firstChild.nodeValue === "0") {
|
|
if (translator && translator.file.exists()) {
|
|
Zotero.debug("Deleting translator '" + translator.label + "'");
|
|
translator.file.remove(false);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
var metadata = {
|
|
translatorID: translatorID,
|
|
translatorType: parseInt(xmlnode.getAttribute('type')),
|
|
label: xmlnode.getElementsByTagName('label')[0].firstChild.nodeValue,
|
|
creator: xmlnode.getElementsByTagName('creator')[0].firstChild.nodeValue,
|
|
target: (xmlnode.getElementsByTagName('target').item(0) &&
|
|
xmlnode.getElementsByTagName('target')[0].firstChild)
|
|
? xmlnode.getElementsByTagName('target')[0].firstChild.nodeValue
|
|
: null,
|
|
minVersion: xmlnode.getAttribute('minVersion'),
|
|
maxVersion: xmlnode.getAttribute('maxVersion'),
|
|
priority: parseInt(
|
|
xmlnode.getElementsByTagName('priority')[0].firstChild.nodeValue
|
|
),
|
|
inRepository: true,
|
|
lastUpdated: xmlnode.getAttribute('lastUpdated')
|
|
};
|
|
|
|
// detectCode can not exist or be empty
|
|
var detectCode = (xmlnode.getElementsByTagName('detectCode').item(0) &&
|
|
xmlnode.getElementsByTagName('detectCode')[0].firstChild)
|
|
? xmlnode.getElementsByTagName('detectCode')[0].firstChild.nodeValue
|
|
: null;
|
|
var code = xmlnode.getElementsByTagName('code')[0].firstChild.nodeValue;
|
|
|
|
var fileName = Zotero.Translators.getFileNameFromLabel(metadata.label);
|
|
var destFile = Zotero.getTranslatorsDirectory();
|
|
destFile.append(fileName);
|
|
|
|
var nsIJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
|
|
var metadataJSON = nsIJSON.encode(metadata);
|
|
|
|
var str = metadataJSON + "\n\n" + (detectCode ? detectCode + "\n\n" : "") + code;
|
|
|
|
if (translator && destFile.equals(translator.file)) {
|
|
var sameFile = true;
|
|
}
|
|
|
|
if (!sameFile && destFile.exists()) {
|
|
var msg = "Overwriting translator with same filename '"
|
|
+ fileName + "'";
|
|
Zotero.debug(msg, 1);
|
|
Zotero.debug(metadata, 1);
|
|
Components.utils.reportError(msg + " in Zotero.Schema._translatorXMLToFile()");
|
|
}
|
|
|
|
if (translator && translator.file.exists()) {
|
|
translator.file.remove(false);
|
|
}
|
|
|
|
Zotero.debug("Saving translator '" + metadata.label + "'");
|
|
Zotero.File.putContents(destFile, str);
|
|
return destFile;
|
|
}
|
|
|
|
|
|
/**
|
|
* Traverse an XML style node from the repository and
|
|
* update the local styles folder with the style data
|
|
*/
|
|
function _styleXMLToFile(xmlnode) {
|
|
// Don't split >4K chunks into multiple nodes
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=194231
|
|
xmlnode.normalize();
|
|
|
|
var uri = xmlnode.getAttribute('id');
|
|
|
|
// Delete local style if CSL code is empty
|
|
if (!xmlnode.getElementsByTagName('csl')[0].firstChild) {
|
|
var style = Zotero.Styles.get(uri);
|
|
if (style) {
|
|
style.file.remove(null);
|
|
}
|
|
return;
|
|
}
|
|
|
|
var str = xmlnode.getElementsByTagName('csl')[0].firstChild.nodeValue;
|
|
|
|
var style = Zotero.Styles.get(uri);
|
|
if (style) {
|
|
if (style.file.exists()) {
|
|
style.file.remove(false);
|
|
}
|
|
var destFile = style.file;
|
|
}
|
|
else {
|
|
// Get last part of URI for filename
|
|
var matches = uri.match(/([^\/]+)$/);
|
|
if (!matches) {
|
|
throw ("Invalid style URI '" + uri + "' from repository");
|
|
}
|
|
var destFile = Zotero.getStylesDirectory();
|
|
destFile.append(matches[1]);
|
|
if (destFile.exists()) {
|
|
throw ("Different style with filename '" + matches[1]
|
|
+ "' already exists in Zotero.Schema._styleXMLToFile()");
|
|
}
|
|
}
|
|
|
|
Zotero.debug("Saving style '" + uri + "'");
|
|
Zotero.File.putContents(destFile, str);
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Migrate user data schema from an older version, preserving data
|
|
*/
|
|
function _migrateUserDataSchema(fromVersion){
|
|
var toVersion = _getSchemaSQLVersion('userdata');
|
|
|
|
if (fromVersion==toVersion){
|
|
return false;
|
|
}
|
|
|
|
if (fromVersion > toVersion){
|
|
throw("Zotero user data DB version is newer than SQL file");
|
|
}
|
|
|
|
Zotero.debug('Updating user data tables from version ' + fromVersion + ' to ' + toVersion);
|
|
|
|
var ZU = new Zotero.Utilities;
|
|
|
|
Zotero.DB.beginTransaction();
|
|
|
|
try {
|
|
// Step through version changes until we reach the current version
|
|
//
|
|
// Each block performs the changes necessary to move from the
|
|
// previous revision to that one.
|
|
for (var i=fromVersion + 1; i<=toVersion; i++){
|
|
if (i==1){
|
|
Zotero.DB.query("DELETE FROM version WHERE schema='schema'");
|
|
}
|
|
|
|
if (i==5){
|
|
Zotero.DB.query("REPLACE INTO itemData SELECT itemID, 1, originalPath FROM itemAttachments WHERE linkMode=1");
|
|
Zotero.DB.query("REPLACE INTO itemData SELECT itemID, 1, path FROM itemAttachments WHERE linkMode=3");
|
|
Zotero.DB.query("REPLACE INTO itemData SELECT itemID, 27, dateAdded FROM items NATURAL JOIN itemAttachments WHERE linkMode IN (1,3)");
|
|
Zotero.DB.query("UPDATE itemAttachments SET originalPath=NULL WHERE linkMode=1");
|
|
Zotero.DB.query("UPDATE itemAttachments SET path=NULL WHERE linkMode=3");
|
|
try { Zotero.DB.query("DELETE FROM fulltextItems WHERE itemID IS NULL"); } catch(e){}
|
|
}
|
|
|
|
if (i==6){
|
|
Zotero.DB.query("CREATE TABLE creatorsTemp (creatorID INT, firstName INT, lastName INT, fieldMode INT)");
|
|
Zotero.DB.query("INSERT INTO creatorsTemp SELECT * FROM creators");
|
|
Zotero.DB.query("DROP TABLE creators");
|
|
Zotero.DB.query("CREATE TABLE creators (\n creatorID INT,\n firstName INT,\n lastName INT,\n fieldMode INT,\n PRIMARY KEY (creatorID)\n);");
|
|
Zotero.DB.query("INSERT INTO creators SELECT * FROM creatorsTemp");
|
|
Zotero.DB.query("DROP TABLE creatorsTemp");
|
|
}
|
|
|
|
if (i==7){
|
|
Zotero.DB.query("DELETE FROM itemData WHERE fieldID=17");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=64 WHERE fieldID=20");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=69 WHERE fieldID=24 AND itemID IN (SELECT itemID FROM items WHERE itemTypeID=7)");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=65 WHERE fieldID=24 AND itemID IN (SELECT itemID FROM items WHERE itemTypeID=8)");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=66 WHERE fieldID=24 AND itemID IN (SELECT itemID FROM items WHERE itemTypeID=9)");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=59 WHERE fieldID=24 AND itemID IN (SELECT itemID FROM items WHERE itemTypeID=12)");
|
|
}
|
|
|
|
if (i==8){
|
|
Zotero.DB.query("DROP TABLE IF EXISTS translators");
|
|
Zotero.DB.query("DROP TABLE IF EXISTS csl");
|
|
}
|
|
|
|
// 1.0b2 (1.0.0b2.r1)
|
|
|
|
if (i==9){
|
|
var attachments = Zotero.DB.query("SELECT itemID, linkMode, path FROM itemAttachments");
|
|
for each(var row in attachments){
|
|
var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
|
|
try {
|
|
var refDir = (row.linkMode==Zotero.Attachments.LINK_MODE_LINKED_FILE) ? Zotero.getZoteroDirectory() : Zotero.getStorageDirectory();
|
|
file.setRelativeDescriptor(refDir, row.path);
|
|
Zotero.DB.query("UPDATE itemAttachments SET path=? WHERE itemID=?", [file.persistentDescriptor, row.itemID]);
|
|
}
|
|
catch (e){}
|
|
}
|
|
}
|
|
|
|
// 1.0.0b2.r2
|
|
|
|
if (i==10){
|
|
var dates = Zotero.DB.query("SELECT itemID, value FROM itemData WHERE fieldID=14");
|
|
for each(var row in dates){
|
|
if (!Zotero.Date.isMultipart(row.value)){
|
|
Zotero.DB.query("UPDATE itemData SET value=? WHERE itemID=? AND fieldID=14", [Zotero.Date.strToMultipart(row.value), row.itemID]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i==11){
|
|
var attachments = Zotero.DB.query("SELECT itemID, linkMode, path FROM itemAttachments WHERE linkMode IN (0,1)");
|
|
for each(var row in attachments){
|
|
var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
|
|
try {
|
|
file.persistentDescriptor = row.path;
|
|
var storageDir = Zotero.getStorageDirectory();
|
|
storageDir.QueryInterface(Components.interfaces.nsILocalFile);
|
|
var path = file.getRelativeDescriptor(storageDir);
|
|
Zotero.DB.query("UPDATE itemAttachments SET path=? WHERE itemID=?", [path, row.itemID]);
|
|
}
|
|
catch (e){}
|
|
}
|
|
}
|
|
|
|
if (i==12){
|
|
Zotero.DB.query("CREATE TABLE translatorsTemp (translatorID TEXT PRIMARY KEY, lastUpdated DATETIME, inRepository INT, priority INT, translatorType INT, label TEXT, creator TEXT, target TEXT, detectCode TEXT, code TEXT);");
|
|
if (Zotero.DB.tableExists('translators')) {
|
|
Zotero.DB.query("INSERT INTO translatorsTemp SELECT * FROM translators");
|
|
Zotero.DB.query("DROP TABLE translators");
|
|
}
|
|
Zotero.DB.query("CREATE TABLE translators (\n translatorID TEXT PRIMARY KEY,\n minVersion TEXT,\n maxVersion TEXT,\n lastUpdated DATETIME,\n inRepository INT,\n priority INT,\n translatorType INT,\n label TEXT,\n creator TEXT,\n target TEXT,\n detectCode TEXT,\n code TEXT\n);");
|
|
Zotero.DB.query("INSERT INTO translators SELECT translatorID, '', '', lastUpdated, inRepository, priority, translatorType, label, creator, target, detectCode, code FROM translatorsTemp");
|
|
Zotero.DB.query("CREATE INDEX translators_type ON translators(translatorType)");
|
|
Zotero.DB.query("DROP TABLE translatorsTemp");
|
|
}
|
|
|
|
if (i==13) {
|
|
Zotero.DB.query("CREATE TABLE itemNotesTemp (itemID INT, sourceItemID INT, note TEXT, PRIMARY KEY (itemID), FOREIGN KEY (itemID) REFERENCES items(itemID), FOREIGN KEY (sourceItemID) REFERENCES items(itemID))");
|
|
Zotero.DB.query("INSERT INTO itemNotesTemp SELECT * FROM itemNotes");
|
|
Zotero.DB.query("DROP TABLE itemNotes");
|
|
Zotero.DB.query("CREATE TABLE itemNotes (\n itemID INT,\n sourceItemID INT,\n note TEXT,\n isAbstract INT DEFAULT NULL,\n PRIMARY KEY (itemID),\n FOREIGN KEY (itemID) REFERENCES items(itemID),\n FOREIGN KEY (sourceItemID) REFERENCES items(itemID)\n);");
|
|
Zotero.DB.query("INSERT INTO itemNotes SELECT itemID, sourceItemID, note, NULL FROM itemNotesTemp");
|
|
Zotero.DB.query("CREATE INDEX itemNotes_sourceItemID ON itemNotes(sourceItemID)");
|
|
Zotero.DB.query("DROP TABLE itemNotesTemp");
|
|
}
|
|
|
|
// 1.0.0b3.r1
|
|
|
|
// Repair for interrupted B4 upgrades
|
|
if (i==14) {
|
|
var hash = Zotero.DB.getColumnHash('itemNotes');
|
|
if (!hash.isAbstract) {
|
|
// See if itemDataValues exists
|
|
if (!Zotero.DB.tableExists('itemDataValues')) {
|
|
// Copied from step 23
|
|
var notes = Zotero.DB.query("SELECT itemID, note FROM itemNotes WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=1)");
|
|
if (notes) {
|
|
var f = function(text) { text = text + ''; var t = text.substring(0, 80); var ln = t.indexOf("\n"); if (ln>-1 && ln<80) { t = t.substring(0, ln); } return t; }
|
|
for (var j=0; j<notes.length; j++) {
|
|
Zotero.DB.query("REPLACE INTO itemNoteTitles VALUES (?,?)", [notes[j]['itemID'], f(notes[j]['note'])]);
|
|
}
|
|
}
|
|
|
|
Zotero.DB.query("CREATE TABLE itemDataValues (\n valueID INT,\n value,\n PRIMARY KEY (valueID)\n);");
|
|
var values = Zotero.DB.columnQuery("SELECT DISTINCT value FROM itemData");
|
|
if (values) {
|
|
for (var j=0; j<values.length; j++) {
|
|
var valueID = Zotero.ID.get('itemDataValues');
|
|
Zotero.DB.query("INSERT INTO itemDataValues VALUES (?,?)", [valueID, values[j]]);
|
|
}
|
|
}
|
|
|
|
Zotero.DB.query("CREATE TEMPORARY TABLE itemDataTemp AS SELECT itemID, fieldID, (SELECT valueID FROM itemDataValues WHERE value=ID.value) AS valueID FROM itemData ID");
|
|
Zotero.DB.query("DROP TABLE itemData");
|
|
Zotero.DB.query("CREATE TABLE itemData (\n itemID INT,\n fieldID INT,\n valueID INT,\n PRIMARY KEY (itemID, fieldID),\n FOREIGN KEY (itemID) REFERENCES items(itemID),\n FOREIGN KEY (fieldID) REFERENCES fields(fieldID)\n FOREIGN KEY (valueID) REFERENCES itemDataValues(valueID)\n);");
|
|
Zotero.DB.query("INSERT INTO itemData SELECT * FROM itemDataTemp");
|
|
Zotero.DB.query("DROP TABLE itemDataTemp");
|
|
|
|
i = 23;
|
|
continue;
|
|
}
|
|
|
|
var rows = Zotero.DB.query("SELECT * FROM itemData WHERE valueID NOT IN (SELECT valueID FROM itemDataValues)");
|
|
if (rows) {
|
|
for (var j=0; j<rows.length; j++) {
|
|
for (var j=0; j<values.length; j++) {
|
|
var valueID = Zotero.ID.get('itemDataValues');
|
|
Zotero.DB.query("INSERT INTO itemDataValues VALUES (?,?)", [valueID, values[j]]);
|
|
Zotero.DB.query("UPDATE itemData SET valueID=? WHERE itemID=? AND fieldID=?", [valueID, rows[j]['itemID'], rows[j]['fieldID']]);
|
|
}
|
|
}
|
|
i = 23;
|
|
continue;
|
|
}
|
|
|
|
i = 27;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (i==15) {
|
|
Zotero.DB.query("DROP TABLE IF EXISTS annotations");
|
|
}
|
|
|
|
if (i==16) {
|
|
Zotero.DB.query("CREATE TABLE tagsTemp (tagID INT, tag TEXT, PRIMARY KEY (tagID))");
|
|
if (Zotero.DB.tableExists("tags")) {
|
|
Zotero.DB.query("INSERT INTO tagsTemp SELECT * FROM tags");
|
|
Zotero.DB.query("DROP TABLE tags");
|
|
}
|
|
Zotero.DB.query("CREATE TABLE tags (\n tagID INT,\n tag TEXT,\n tagType INT,\n PRIMARY KEY (tagID),\n UNIQUE (tag, tagType)\n);");
|
|
Zotero.DB.query("INSERT INTO tags SELECT tagID, tag, 0 FROM tagsTemp");
|
|
Zotero.DB.query("DROP TABLE tagsTemp");
|
|
|
|
// Compensate for csl table drop in step 8 for upgraders from early versions,
|
|
// in case we do something with it in a later step
|
|
Zotero.DB.query("CREATE TABLE IF NOT EXISTS csl (\n cslID TEXT PRIMARY KEY,\n updated DATETIME,\n title TEXT,\n csl TEXT\n);");
|
|
}
|
|
|
|
if (i==17) {
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=89 WHERE fieldID=8 AND itemID IN (SELECT itemID FROM items WHERE itemTypeID=7)");
|
|
}
|
|
|
|
if (i==19) {
|
|
Zotero.DB.query("INSERT INTO itemData SELECT sourceItemID, 90, note FROM itemNotes WHERE isAbstract=1");
|
|
Zotero.DB.query("DELETE FROM items WHERE itemID IN (SELECT itemID FROM itemNotes WHERE isAbstract=1)");
|
|
Zotero.DB.query("DELETE FROM itemData WHERE itemID IN (SELECT itemID FROM itemNotes WHERE isAbstract=1)");
|
|
Zotero.DB.query("CREATE TEMPORARY TABLE itemNotesTemp (itemID INT, sourceItemID INT, note TEXT)");
|
|
Zotero.DB.query("INSERT INTO itemNotesTemp SELECT itemID, sourceItemID, note FROM itemNotes WHERE isAbstract IS NULL");
|
|
Zotero.DB.query("DROP TABLE itemNotes");
|
|
Zotero.DB.query("CREATE TABLE itemNotes (\n itemID INT,\n sourceItemID INT,\n note TEXT, \n PRIMARY KEY (itemID),\n FOREIGN KEY (itemID) REFERENCES items(itemID),\n FOREIGN KEY (sourceItemID) REFERENCES items(itemID)\n);");
|
|
Zotero.DB.query("INSERT INTO itemNotes SELECT * FROM itemNotesTemp")
|
|
Zotero.DB.query("DROP TABLE itemNotesTemp");
|
|
}
|
|
|
|
if (i==20) {
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=91 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=13) AND fieldID=12;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=92 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=15) AND fieldID=60;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=93 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=16) AND fieldID=60;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=94 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=16) AND fieldID=4;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=95 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=16) AND fieldID=10;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=96 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=17) AND fieldID=14;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=97 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=17) AND fieldID=4;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=98 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=17) AND fieldID=10;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=99 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=18) AND fieldID=60;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=100 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=20) AND fieldID=14;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=101 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=20) AND fieldID=60;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=102 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=19) AND fieldID=7;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=103 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=19) AND fieldID=60;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=104 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=25) AND fieldID=12;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=105 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=29) AND fieldID=60;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=105 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=30) AND fieldID=60;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=105 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=31) AND fieldID=60;");
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=107 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=23) AND fieldID=12;");
|
|
Zotero.DB.query("INSERT OR IGNORE INTO itemData SELECT itemID, 52, value FROM itemData WHERE fieldID IN (14, 52) AND itemID IN (SELECT itemID FROM items WHERE itemTypeID=19) LIMIT 1");
|
|
Zotero.DB.query("DELETE FROM itemData WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=19) AND fieldID=14");
|
|
}
|
|
|
|
if (i==21) {
|
|
Zotero.DB.query("INSERT INTO itemData SELECT itemID, 110, title FROM items WHERE title IS NOT NULL AND itemTypeID NOT IN (1,17,20,21)");
|
|
Zotero.DB.query("INSERT INTO itemData SELECT itemID, 111, title FROM items WHERE title IS NOT NULL AND itemTypeID = 17");
|
|
Zotero.DB.query("INSERT INTO itemData SELECT itemID, 112, title FROM items WHERE title IS NOT NULL AND itemTypeID = 20");
|
|
Zotero.DB.query("INSERT INTO itemData SELECT itemID, 113, title FROM items WHERE title IS NOT NULL AND itemTypeID = 21");
|
|
Zotero.DB.query("CREATE TEMPORARY TABLE itemsTemp AS SELECT itemID, itemTypeID, dateAdded, dateModified FROM items");
|
|
Zotero.DB.query("DROP TABLE items");
|
|
Zotero.DB.query("CREATE TABLE IF NOT EXISTS items (\n itemID INTEGER PRIMARY KEY,\n itemTypeID INT,\n dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP,\n dateModified DATETIME DEFAULT CURRENT_TIMESTAMP\n);");
|
|
Zotero.DB.query("INSERT INTO items SELECT * FROM itemsTemp");
|
|
Zotero.DB.query("DROP TABLE itemsTemp");
|
|
}
|
|
|
|
if (i==22) {
|
|
if (Zotero.DB.valueQuery("SELECT COUNT(*) FROM items WHERE itemID=0")) {
|
|
var itemID = Zotero.ID.get('items', true);
|
|
Zotero.DB.query("UPDATE items SET itemID=? WHERE itemID=?", [itemID, 0]);
|
|
Zotero.DB.query("UPDATE itemData SET itemID=? WHERE itemID=?", [itemID, 0]);
|
|
Zotero.DB.query("UPDATE itemNotes SET itemID=? WHERE itemID=?", [itemID, 0]);
|
|
Zotero.DB.query("UPDATE itemAttachments SET itemID=? WHERE itemID=?", [itemID, 0]);
|
|
}
|
|
if (Zotero.DB.valueQuery("SELECT COUNT(*) FROM collections WHERE collectionID=0")) {
|
|
var collectionID = Zotero.ID.get('collections', true);
|
|
Zotero.DB.query("UPDATE collections SET collectionID=? WHERE collectionID=0", [collectionID]);
|
|
Zotero.DB.query("UPDATE collectionItems SET collectionID=? WHERE collectionID=0", [collectionID]);
|
|
}
|
|
Zotero.DB.query("DELETE FROM tags WHERE tagID=0");
|
|
Zotero.DB.query("DELETE FROM itemTags WHERE tagID=0");
|
|
Zotero.DB.query("DELETE FROM savedSearches WHERE savedSearchID=0");
|
|
}
|
|
|
|
if (i==23) {
|
|
Zotero.DB.query("CREATE TABLE IF NOT EXISTS itemNoteTitles (\n itemID INT,\n title TEXT,\n PRIMARY KEY (itemID),\n FOREIGN KEY (itemID) REFERENCES itemNotes(itemID)\n);");
|
|
var notes = Zotero.DB.query("SELECT itemID, note FROM itemNotes WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=1)");
|
|
if (notes) {
|
|
var f = function(text) { var t = text.substring(0, 80); var ln = t.indexOf("\n"); if (ln>-1 && ln<80) { t = t.substring(0, ln); } return t; }
|
|
for (var j=0; j<notes.length; j++) {
|
|
Zotero.DB.query("INSERT INTO itemNoteTitles VALUES (?,?)", [notes[j]['itemID'], f(notes[j]['note'])]);
|
|
}
|
|
}
|
|
|
|
Zotero.DB.query("CREATE TABLE IF NOT EXISTS itemDataValues (\n valueID INT,\n value,\n PRIMARY KEY (valueID)\n);");
|
|
var values = Zotero.DB.columnQuery("SELECT DISTINCT value FROM itemData");
|
|
if (values) {
|
|
for (var j=0; j<values.length; j++) {
|
|
var valueID = Zotero.ID.get('itemDataValues');
|
|
Zotero.DB.query("INSERT INTO itemDataValues VALUES (?,?)", [valueID, values[j]]);
|
|
}
|
|
}
|
|
|
|
Zotero.DB.query("CREATE TEMPORARY TABLE itemDataTemp AS SELECT itemID, fieldID, (SELECT valueID FROM itemDataValues WHERE value=ID.value) AS valueID FROM itemData ID");
|
|
Zotero.DB.query("DROP TABLE itemData");
|
|
Zotero.DB.query("CREATE TABLE itemData (\n itemID INT,\n fieldID INT,\n valueID INT,\n PRIMARY KEY (itemID, fieldID),\n FOREIGN KEY (itemID) REFERENCES items(itemID),\n FOREIGN KEY (fieldID) REFERENCES fields(fieldID)\n FOREIGN KEY (valueID) REFERENCES itemDataValues(valueID)\n);");
|
|
Zotero.DB.query("INSERT INTO itemData SELECT * FROM itemDataTemp");
|
|
Zotero.DB.query("DROP TABLE itemDataTemp");
|
|
}
|
|
|
|
if (i==24) {
|
|
var rows = Zotero.DB.query("SELECT * FROM itemData NATURAL JOIN itemDataValues WHERE fieldID IN (52,96,100)");
|
|
if (rows) {
|
|
for (var j=0; j<rows.length; j++) {
|
|
if (!Zotero.Date.isMultipart(rows[j]['value'])) {
|
|
var value = Zotero.Date.strToMultipart(rows[j]['value']);
|
|
var valueID = Zotero.DB.valueQuery("SELECT valueID FROM itemDataValues WHERE value=?", rows[j]['value']);
|
|
if (!valueID) {
|
|
var valueID = Zotero.ID.get('itemDataValues');
|
|
Zotero.DB.query("INSERT INTO itemDataValues VALUES (?,?)", [valueID, value]);
|
|
}
|
|
Zotero.DB.query("UPDATE itemData SET valueID=? WHERE itemID=? AND fieldID=?", [valueID, rows[j]['itemID'], rows[j]['fieldID']]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i==25) {
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=100 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=15) AND fieldID=14;")
|
|
}
|
|
|
|
if (i==26) {
|
|
Zotero.DB.query("INSERT INTO itemData SELECT itemID, 114, valueID FROM itemData WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=33) AND fieldID=84");
|
|
}
|
|
|
|
if (i==27) {
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=115 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=3) AND fieldID=12");
|
|
}
|
|
|
|
// 1.0.0b4.r1
|
|
|
|
if (i==28) {
|
|
var childNotes = Zotero.DB.query("SELECT * FROM itemNotes WHERE itemID IN (SELECT itemID FROM items) AND sourceItemID IS NOT NULL");
|
|
if (!childNotes.length) {
|
|
continue;
|
|
}
|
|
Zotero.DB.query("CREATE TEMPORARY TABLE itemNotesTemp AS SELECT * FROM itemNotes WHERE note IN (SELECT itemID FROM items) AND sourceItemID IS NOT NULL");
|
|
Zotero.DB.query("CREATE INDEX tmp_itemNotes_pk ON itemNotesTemp(note, sourceItemID);");
|
|
var num = Zotero.DB.valueQuery("SELECT COUNT(*) FROM itemNotesTemp");
|
|
if (!num) {
|
|
continue;
|
|
}
|
|
for (var j=0; j<childNotes.length; j++) {
|
|
var reversed = Zotero.DB.query("SELECT * FROM itemNotesTemp WHERE note=? AND sourceItemID=?", [childNotes[j].itemID, childNotes[j].sourceItemID]);
|
|
if (!reversed.length) {
|
|
continue;
|
|
}
|
|
var maxLength = 0;
|
|
for (var k=0; k<reversed.length; k++) {
|
|
if (reversed[k].itemID.length > maxLength) {
|
|
maxLength = reversed[k].itemID.length;
|
|
var maxLengthIndex = k;
|
|
}
|
|
}
|
|
if (maxLengthIndex) {
|
|
Zotero.DB.query("UPDATE itemNotes SET note=? WHERE itemID=?", [reversed[maxLengthIndex].itemID, childNotes[j].itemID]);
|
|
var f = function(text) { text = text + ''; var t = text.substring(0, 80); var ln = t.indexOf("\n"); if (ln>-1 && ln<80) { t = t.substring(0, ln); } return t; }
|
|
Zotero.DB.query("UPDATE itemNoteTitles SET title=? WHERE itemID=?", [f(reversed[maxLengthIndex].itemID), childNotes[j].itemID]);
|
|
}
|
|
Zotero.DB.query("DELETE FROM itemNotes WHERE note=? AND sourceItemID=?", [childNotes[j].itemID, childNotes[j].sourceItemID]);
|
|
}
|
|
}
|
|
|
|
// 1.0.0b4.r2
|
|
|
|
if (i==29) {
|
|
Zotero.DB.query("CREATE TABLE IF NOT EXISTS settings (\n setting TEXT,\n key TEXT,\n value,\n PRIMARY KEY (setting, key)\n);");
|
|
}
|
|
|
|
if (i==31) {
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=14 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=15) AND fieldID=100");
|
|
}
|
|
|
|
if (i==32) {
|
|
Zotero.DB.query("UPDATE itemData SET fieldID=100 WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=20) AND fieldID=14;");
|
|
}
|
|
|
|
// 1.0.0b4.r3
|
|
|
|
if (i==33) {
|
|
var rows = Zotero.DB.query("SELECT * FROM itemNotes WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
if (rows) {
|
|
var colID = Zotero.ID.get('collections');
|
|
Zotero.DB.query("INSERT INTO collections VALUES (?,?,?)", [colID, "[Recovered Notes]", null]);
|
|
|
|
for (var j=0; j<rows.length; j++) {
|
|
if (rows[j].sourceItemID) {
|
|
var count = Zotero.DB.valueQuery("SELECT COUNT(*) FROM items WHERE itemID=?", rows[j].sourceItemID);
|
|
if (count == 0) {
|
|
Zotero.DB.query("UPDATE itemNotes SET sourceItemID=NULL WHERE itemID=?", rows[j].sourceItemID);
|
|
}
|
|
}
|
|
var parsedID = parseInt(rows[j].itemID);
|
|
if ((parsedID + '').length != rows[j].itemID) {
|
|
if (parseInt(rows[j].note) != rows[j].note ||
|
|
(parseInt(rows[j].note) + '').length != rows[j].note.length) {
|
|
Zotero.DB.query("DELETE FROM itemNotes WHERE itemID=?", rows[j].itemID);
|
|
continue;
|
|
}
|
|
var exists = Zotero.DB.valueQuery("SELECT COUNT(*) FROM itemNotes WHERE itemID=?", rows[j].note);
|
|
if (exists) {
|
|
var noteItemID = Zotero.ID.get('items', true);
|
|
}
|
|
else {
|
|
var noteItemID = rows[j].note;
|
|
}
|
|
Zotero.DB.query("UPDATE itemNotes SET itemID=?, sourceItemID=NULL, note=? WHERE itemID=? AND sourceItemID=?", [noteItemID, rows[j].itemID, rows[j].itemID, rows[j].sourceItemID]);
|
|
var f = function(text) { text = text + ''; var t = text.substring(0, 80); var ln = t.indexOf("\n"); if (ln>-1 && ln<80) { t = t.substring(0, ln); } return t; }
|
|
Zotero.DB.query("REPLACE INTO itemNoteTitles VALUES (?,?)", [noteItemID, f(rows[j].itemID)]);
|
|
Zotero.DB.query("INSERT OR IGNORE INTO items (itemID, itemTypeID) VALUES (?,?)", [noteItemID, 1]);
|
|
var max = Zotero.DB.valueQuery("SELECT COUNT(*) FROM collectionItems WHERE collectionID=?", colID);
|
|
Zotero.DB.query("INSERT OR IGNORE INTO collectionItems VALUES (?,?,?)", [colID, noteItemID, max]);
|
|
continue;
|
|
}
|
|
else if (parsedID != rows[j].itemID) {
|
|
Zotero.DB.query("DELETE FROM itemNotes WHERE itemID=?", rows[j].itemID);
|
|
continue;
|
|
}
|
|
Zotero.DB.query("INSERT INTO items (itemID, itemTypeID) VALUES (?,?)", [rows[j].itemID, 1]);
|
|
var max = Zotero.DB.valueQuery("SELECT COUNT(*) FROM collectionItems WHERE collectionID=?", colID);
|
|
Zotero.DB.query("INSERT INTO collectionItems VALUES (?,?,?)", [colID, rows[j].itemID, max]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1.0.0b4.r5
|
|
|
|
if (i==34) {
|
|
if (!Zotero.DB.tableExists('annotations')) {
|
|
Zotero.DB.query("CREATE TABLE annotations (\n annotationID INTEGER PRIMARY KEY,\n itemID INT,\n parent TEXT,\n textNode INT,\n offset INT,\n x INT,\n y INT,\n cols INT,\n rows INT,\n text TEXT,\n collapsed BOOL,\n dateModified DATE,\n FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)\n)");
|
|
Zotero.DB.query("CREATE INDEX annotations_itemID ON annotations(itemID)");
|
|
}
|
|
else {
|
|
Zotero.DB.query("ALTER TABLE annotations ADD collapsed BOOL");
|
|
Zotero.DB.query("ALTER TABLE annotations ADD dateModified DATETIME");
|
|
}
|
|
if (!Zotero.DB.tableExists('highlights')) {
|
|
Zotero.DB.query("CREATE TABLE highlights (\n highlightID INTEGER PRIMARY KEY,\n itemID INTEGER,\n startParent TEXT,\n startTextNode INT,\n startOffset INT,\n endParent TEXT,\n endTextNode INT,\n endOffset INT,\n dateModified DATE,\n FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)\n)");
|
|
Zotero.DB.query("CREATE INDEX highlights_itemID ON highlights(itemID)");
|
|
}
|
|
else {
|
|
Zotero.DB.query("ALTER TABLE highlights ADD dateModified DATETIME");
|
|
}
|
|
Zotero.DB.query("UPDATE annotations SET dateModified = DATETIME('now')");
|
|
Zotero.DB.query("UPDATE highlights SET dateModified = DATETIME('now')");
|
|
}
|
|
|
|
if (i==35) {
|
|
Zotero.DB.query("ALTER TABLE fulltextItems RENAME TO fulltextItemWords");
|
|
Zotero.DB.query("CREATE TABLE fulltextItems (\n itemID INT,\n version INT,\n PRIMARY KEY (itemID),\n FOREIGN KEY (itemID) REFERENCES items(itemID)\n);");
|
|
}
|
|
|
|
if (i==36) {
|
|
Zotero.DB.query("ALTER TABLE fulltextItems ADD indexedPages INT");
|
|
Zotero.DB.query("ALTER TABLE fulltextItems ADD totalPages INT");
|
|
Zotero.DB.query("ALTER TABLE fulltextItems ADD indexedChars INT");
|
|
Zotero.DB.query("ALTER TABLE fulltextItems ADD totalChars INT");
|
|
Zotero.DB.query("DELETE FROM version WHERE schema='fulltext'");
|
|
}
|
|
|
|
// 1.5 Sync Preview 1
|
|
if (i==37) {
|
|
// Some data cleanup from the pre-FK-trigger days
|
|
Zotero.DB.query("DELETE FROM annotations WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM collectionItems WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM fulltextItems WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM fulltextItemWords WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM highlights WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM itemAttachments WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM itemCreators WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM itemData WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM itemNotes WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM itemNoteTitles WHERE itemID NOT IN (SELECT itemID FROM itemNotes)");
|
|
Zotero.DB.query("DELETE FROM itemSeeAlso WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM itemSeeAlso WHERE linkedItemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM itemTags WHERE itemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("DELETE FROM itemTags WHERE tagID NOT IN (SELECT tagID FROM tags)");
|
|
Zotero.DB.query("DELETE FROM savedSearchConditions WHERE savedSearchID NOT IN (select savedSearchID FROM savedSearches)");
|
|
|
|
Zotero.DB.query("DELETE FROM itemData WHERE valueID NOT IN (SELECT valueID FROM itemDataValues)");
|
|
Zotero.DB.query("DELETE FROM fulltextItemWords WHERE wordID NOT IN (SELECT wordID FROM fulltextWords)");
|
|
Zotero.DB.query("DELETE FROM collectionItems WHERE collectionID NOT IN (SELECT collectionID FROM collections)");
|
|
Zotero.DB.query("DELETE FROM itemCreators WHERE creatorID NOT IN (SELECT creatorID FROM creators)");
|
|
Zotero.DB.query("DELETE FROM itemTags WHERE tagID NOT IN (SELECT tagID FROM tags)");
|
|
Zotero.DB.query("DELETE FROM itemData WHERE fieldID NOT IN (SELECT fieldID FROM fields)");
|
|
Zotero.DB.query("DELETE FROM itemData WHERE valueID NOT IN (SELECT valueID FROM itemDataValues)");
|
|
|
|
Zotero.DB.query("DROP TABLE IF EXISTS userFieldMask");
|
|
Zotero.DB.query("DROP TABLE IF EXISTS userItemTypes");
|
|
Zotero.DB.query("DROP TABLE IF EXISTS userItemTypeMask");
|
|
Zotero.DB.query("DROP TABLE IF EXISTS userFields");
|
|
Zotero.DB.query("DROP TABLE IF EXISTS userItemTypeFields");
|
|
|
|
// Index corruption can allow duplicate values
|
|
var wordIDs = Zotero.DB.columnQuery("SELECT GROUP_CONCAT(wordID) AS wordIDs FROM fulltextWords GROUP BY word HAVING COUNT(*)>1");
|
|
if (wordIDs.length) {
|
|
Zotero.DB.query("CREATE TEMPORARY TABLE deleteWordIDs (wordID INTEGER PRIMARY KEY)");
|
|
for (var j=0, len=wordIDs.length; j<len; j++) {
|
|
var ids = wordIDs[j].split(',');
|
|
for (var k=1; k<ids.length; k++) {
|
|
Zotero.DB.query("INSERT INTO deleteWordIDs VALUES (?)", ids[k]);
|
|
}
|
|
}
|
|
Zotero.DB.query("DELETE FROM fulltextWords WHERE wordID IN (SELECT wordID FROM deleteWordIDs)");
|
|
Zotero.DB.query("DROP TABLE deleteWordIDs");
|
|
}
|
|
|
|
Zotero.DB.query("DROP INDEX IF EXISTS fulltextWords_word");
|
|
|
|
Zotero.DB.query("REINDEX");
|
|
Zotero.DB.transactionVacuum = true;
|
|
|
|
// Set page cache size to 8MB
|
|
var pageSize = Zotero.DB.valueQuery("PRAGMA page_size");
|
|
var cacheSize = 8192000 / pageSize;
|
|
Zotero.DB.query("PRAGMA default_cache_size=" + cacheSize);
|
|
|
|
// Orphaned child attachment
|
|
Zotero.DB.query("UPDATE itemAttachments SET sourceItemID=NULL WHERE sourceItemID NOT IN (SELECT itemID FROM items)");
|
|
Zotero.DB.query("UPDATE itemNotes SET sourceItemID=NULL WHERE sourceItemID NOT IN (SELECT itemID FROM items)");
|
|
|
|
// Create sync delete log
|
|
Zotero.DB.query("CREATE TABLE syncDeleteLog (\n syncObjectTypeID INT NOT NULL,\n objectID INT NOT NULL,\n key TEXT NOT NULL,\n timestamp INT NOT NULL,\n FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)\n);");
|
|
Zotero.DB.query("CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp);");
|
|
|
|
// Note titles
|
|
Zotero.DB.query("ALTER TABLE itemNotes ADD COLUMN title TEXT");
|
|
var notes = Zotero.DB.query("SELECT itemID, title FROM itemNoteTitles");
|
|
if (notes) {
|
|
var statement = Zotero.DB.getStatement("UPDATE itemNotes SET title=? WHERE itemID=?");
|
|
for (var j=0, len=notes.length; j<len; j++) {
|
|
statement.bindUTF8StringParameter(0, notes[j].title);
|
|
statement.bindInt32Parameter(1, notes[j].itemID);
|
|
try {
|
|
statement.execute();
|
|
}
|
|
catch (e) {
|
|
throw (Zotero.DB.getLastErrorString());
|
|
}
|
|
}
|
|
statement.reset();
|
|
}
|
|
Zotero.DB.query("DROP TABLE itemNoteTitles");
|
|
|
|
// Creator data
|
|
Zotero.DB.query("CREATE TABLE creatorData (\n creatorDataID INTEGER PRIMARY KEY,\n firstName TEXT,\n lastName TEXT,\n shortName TEXT,\n fieldMode INT,\n birthYear INT\n)");
|
|
Zotero.DB.query("INSERT INTO creatorData SELECT NULL, firstName, lastName, NULL, fieldMode, NULL FROM creators WHERE creatorID IN (SELECT creatorID FROM itemCreators)");
|
|
var creatorsOld = Zotero.DB.query("SELECT * FROM creators");
|
|
Zotero.DB.query("DROP TABLE creators");
|
|
Zotero.DB.query("CREATE TABLE creators (\n creatorID INTEGER PRIMARY KEY,\n creatorDataID INT,\n dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,\n key TEXT NOT NULL,\n FOREIGN KEY (creatorDataID) REFERENCES creatorData(creatorDataID)\n);");
|
|
|
|
var data = Zotero.DB.query("SELECT * FROM creatorData");
|
|
if (data) {
|
|
var oldCreatorIDHash = {};
|
|
for (var j=0, len=creatorsOld.length; j<len; j++) {
|
|
oldCreatorIDHash[
|
|
ZU.md5(
|
|
creatorsOld[j].firstName + '_' +
|
|
creatorsOld[j].lastName + '_' +
|
|
creatorsOld[j].fieldMode
|
|
)
|
|
] = creatorsOld[j].creatorID;
|
|
}
|
|
|
|
var updatedIDs = {};
|
|
var insertStatement = Zotero.DB.getStatement("INSERT INTO creators (creatorID, creatorDataID, key) VALUES (?, ?, ?)");
|
|
var updateStatement = Zotero.DB.getStatement("UPDATE itemCreators SET creatorID=? WHERE creatorID=?");
|
|
for (var j=0, len=data.length; j<len; j++) {
|
|
insertStatement.bindInt32Parameter(0, data[j].creatorDataID);
|
|
insertStatement.bindInt32Parameter(1, data[j].creatorDataID);
|
|
var key = Zotero.ID.getKey();
|
|
insertStatement.bindStringParameter(2, key);
|
|
|
|
var oldCreatorID = oldCreatorIDHash[
|
|
ZU.md5(
|
|
data[j].firstName + '_' +
|
|
data[j].lastName + '_' +
|
|
data[j].fieldMode
|
|
)
|
|
];
|
|
|
|
if (updatedIDs[oldCreatorID]) {
|
|
continue;
|
|
}
|
|
updatedIDs[oldCreatorID] = true;
|
|
|
|
updateStatement.bindInt32Parameter(0, data[j].creatorDataID);
|
|
updateStatement.bindInt32Parameter(1, oldCreatorID);
|
|
|
|
try {
|
|
insertStatement.execute();
|
|
updateStatement.execute();
|
|
}
|
|
catch (e) {
|
|
throw (Zotero.DB.getLastErrorString());
|
|
}
|
|
}
|
|
insertStatement.reset();
|
|
updateStatement.reset();
|
|
}
|
|
|
|
Zotero.DB.query("CREATE INDEX creators_creatorDataID ON creators(creatorDataID)");
|
|
|
|
// Items
|
|
Zotero.DB.query("ALTER TABLE items ADD COLUMN key TEXT");
|
|
|
|
var items = Zotero.DB.query("SELECT itemID, itemTypeID, dateAdded FROM items");
|
|
var titles = Zotero.DB.query("SELECT itemID, value FROM itemData NATURAL JOIN itemDataValues WHERE fieldID BETWEEN 110 AND 112");
|
|
var statement = Zotero.DB.getStatement("UPDATE items SET key=? WHERE itemID=?");
|
|
for (var j=0, len=items.length; j<len; j++) {
|
|
var key = Zotero.ID.getKey();
|
|
if (key == 'AJ4PT6IT') {
|
|
j--;
|
|
continue;
|
|
}
|
|
else if (items[j].itemID == 123456789) {
|
|
key = 'AJ4PT6IT';
|
|
}
|
|
statement.bindStringParameter(0, key);
|
|
statement.bindInt32Parameter(1, items[j].itemID);
|
|
try {
|
|
statement.execute();
|
|
}
|
|
catch (e) {
|
|
throw (Zotero.DB.getLastErrorString());
|
|
}
|
|
}
|
|
statement.reset();
|
|
Zotero.DB.query("CREATE UNIQUE INDEX items_key ON items(key)");
|
|
|
|
var rows = Zotero.DB.columnQuery("SELECT GROUP_CONCAT(valueID) FROM itemDataValues GROUP BY value HAVING COUNT(*) > 1");
|
|
for each(var row in rows) {
|
|
var ids = row.split(',');
|
|
var deleteIDs = [];
|
|
for (var j=1; j<ids.length; j++) {
|
|
deleteIDs.push(parseInt(ids[j]));
|
|
}
|
|
Zotero.DB.query("UPDATE itemData SET valueID=? WHERE valueID IN (" + deleteIDs.map(function () '?').join() + ")", [parseInt(ids[0])].concat(deleteIDs));
|
|
Zotero.DB.query("DELETE FROM itemDataValues WHERE valueID IN (" + deleteIDs.map(function () '?').join() + ")", deleteIDs);
|
|
}
|
|
Zotero.DB.query("CREATE UNIQUE INDEX itemDataValues_value ON itemDataValues(value)");
|
|
|
|
// Collections
|
|
var collections = Zotero.DB.query("SELECT * FROM collections");
|
|
Zotero.DB.query("DROP TABLE collections");
|
|
Zotero.DB.query("CREATE TABLE collections (\n collectionID INTEGER PRIMARY KEY,\n collectionName TEXT,\n parentCollectionID INT,\n dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,\n key TEXT NOT NULL UNIQUE,\n FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID)\n);");
|
|
var statement = Zotero.DB.getStatement("INSERT INTO collections (collectionID, collectionName, parentCollectionID, key) VALUES (?,?,?,?)");
|
|
for (var j=0, len=collections.length; j<len; j++) {
|
|
statement.bindInt32Parameter(0, collections[j].collectionID);
|
|
statement.bindUTF8StringParameter(1, collections[j].collectionName);
|
|
if (collections[j].parentCollectionID) {
|
|
statement.bindInt32Parameter(2, collections[j].parentCollectionID);
|
|
}
|
|
else {
|
|
statement.bindNullParameter(2);
|
|
}
|
|
var key = Zotero.ID.getKey();
|
|
statement.bindStringParameter(3, key);
|
|
|
|
try {
|
|
statement.execute();
|
|
}
|
|
catch (e) {
|
|
throw (Zotero.DB.getLastErrorString());
|
|
}
|
|
}
|
|
statement.reset();
|
|
|
|
// Saved searches
|
|
var searches = Zotero.DB.query("SELECT * FROM savedSearches");
|
|
Zotero.DB.query("DROP TABLE savedSearches");
|
|
Zotero.DB.query("CREATE TABLE savedSearches (\n savedSearchID INTEGER PRIMARY KEY,\n savedSearchName TEXT,\n dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,\n key TEXT NOT NULL UNIQUE\n);");
|
|
var statement = Zotero.DB.getStatement("INSERT INTO savedSearches (savedSearchID, savedSearchName, key) VALUES (?,?,?)");
|
|
for (var j=0, len=searches.length; j<len; j++) {
|
|
statement.bindInt32Parameter(0, searches[j].savedSearchID);
|
|
statement.bindUTF8StringParameter(1, searches[j].savedSearchName);
|
|
var key = Zotero.ID.getKey();
|
|
statement.bindStringParameter(2, key);
|
|
|
|
try {
|
|
statement.execute();
|
|
}
|
|
catch (e) {
|
|
throw (Zotero.DB.getLastErrorString());
|
|
}
|
|
}
|
|
statement.reset();
|
|
|
|
// Tags
|
|
var tags = Zotero.DB.query("SELECT tagID, tag AS tag, tagType FROM tags");
|
|
var newTags = [];
|
|
var cases = {};
|
|
if (tags) {
|
|
// Find tags with multiple case forms
|
|
for each(var row in tags) {
|
|
var l = row.tag.toLowerCase();
|
|
if (!cases[l]) {
|
|
cases[l] = [];
|
|
}
|
|
if (cases[l].indexOf(row.tag) == -1) {
|
|
cases[l].push(row.tag);
|
|
}
|
|
}
|
|
var done = {};
|
|
for each(var row in tags) {
|
|
var l = row.tag.toLowerCase();
|
|
|
|
if (done[l]) {
|
|
continue;
|
|
}
|
|
done[l] = true;
|
|
|
|
// Only one tag -- use
|
|
if (cases[l].length == 1) {
|
|
newTags.push(row);
|
|
continue;
|
|
}
|
|
|
|
// Use most frequent
|
|
var counts = Zotero.DB.query("SELECT tag, COUNT(*) AS numItems FROM tags NATURAL JOIN itemTags WHERE tag LIKE ? GROUP BY tag ORDER BY numItems DESC", l);
|
|
if (counts[0].numItems != counts[1].numItems) {
|
|
var newTag = counts[0].tag;
|
|
}
|
|
// Use earliest
|
|
else {
|
|
var newTag = Zotero.DB.valueQuery("SELECT tag FROM tags NATURAL JOIN itemTags WHERE tag IN (SELECT tag FROM tags NATURAL JOIN itemTags NATURAL JOIN items WHERE tag LIKE ? ORDER BY dateAdded LIMIT 1) GROUP BY tag", l);
|
|
}
|
|
|
|
// Point old to new
|
|
var types = Zotero.DB.columnQuery("SELECT DISTINCT tagType FROM tags WHERE tag LIKE ?", l);
|
|
for each(var type in types) {
|
|
var newTagID = Zotero.DB.valueQuery("SELECT tagID FROM tags WHERE tag=? AND tagType=?", [newTag, type]);
|
|
var oldIDs = Zotero.DB.columnQuery("SELECT tagID FROM tags WHERE tag LIKE ? AND tag != ? AND tagType=?", [l, newTag, type]);
|
|
if (oldIDs) {
|
|
if (!newTagID) {
|
|
newTagID = oldIDs[0];
|
|
}
|
|
Zotero.DB.query("UPDATE OR REPLACE itemTags SET tagID=? WHERE tagID IN (" + oldIDs.map(function () '?').join() + ")", [newTagID].concat(oldIDs));
|
|
}
|
|
newTags.push({ tagID: newTagID, tag: newTag, tagType: type });
|
|
}
|
|
}
|
|
}
|
|
|
|
Zotero.DB.query("DROP TABLE tags");
|
|
Zotero.DB.query("CREATE TABLE tags (\n tagID INTEGER PRIMARY KEY,\n name TEXT COLLATE NOCASE,\n type INT,\n dateModified DEFAULT CURRENT_TIMESTAMP NOT NULL,\n key TEXT NOT NULL UNIQUE,\n UNIQUE (name, type)\n)");
|
|
var statement = Zotero.DB.getStatement("INSERT INTO tags (tagID, name, type, key) VALUES (?,?,?,?)");
|
|
for (var j=0, len=newTags.length; j<len; j++) {
|
|
statement.bindInt32Parameter(0, newTags[j].tagID);
|
|
statement.bindUTF8StringParameter(1, newTags[j].tag);
|
|
statement.bindInt32Parameter(2, newTags[j].tagType);
|
|
var key = Zotero.ID.getKey();
|
|
statement.bindStringParameter(3, key);
|
|
|
|
try {
|
|
statement.execute();
|
|
}
|
|
catch (e) {
|
|
throw (Zotero.DB.getLastErrorString());
|
|
}
|
|
}
|
|
statement.reset();
|
|
|
|
// Migrate attachment folders to secondary keys
|
|
Zotero.DB.query("UPDATE itemAttachments SET path=REPLACE(path, itemID || '/', 'storage:') WHERE path REGEXP '^[0-9]+/'");
|
|
|
|
if (Zotero.Prefs.get('useDataDir')) {
|
|
var dataDir = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
|
|
dataDir.persistentDescriptor = Zotero.Prefs.get('dataDir');
|
|
}
|
|
else {
|
|
var dataDir = Zotero.getProfileDirectory();
|
|
dataDir.append('zotero');
|
|
}
|
|
if (!dataDir.exists() || !dataDir.isDirectory()){
|
|
var e = { name: "NS_ERROR_FILE_NOT_FOUND" };
|
|
throw (e);
|
|
}
|
|
var movedFiles37 = {};
|
|
var moveReport = '';
|
|
var orphaned = dataDir.clone();
|
|
var storage37 = dataDir.clone();
|
|
var moveReportFile = dataDir.clone();
|
|
orphaned.append('orphaned-files');
|
|
storage37.append('storage');
|
|
moveReportFile.append('zotero.moved-files.' + fromVersion + '.bak');
|
|
var keys = {};
|
|
var rows = Zotero.DB.query("SELECT itemID, key FROM items");
|
|
for each(var row in rows) {
|
|
keys[row.itemID] = row.key;
|
|
}
|
|
if (storage37.exists()) {
|
|
var entries = storage37.directoryEntries;
|
|
entries.QueryInterface(Components.interfaces.nsIDirectoryEnumerator);
|
|
var file;
|
|
var renameQueue = [];
|
|
var orphanQueue = [];
|
|
while (file = entries.nextFile) {
|
|
var id = parseInt(file.leafName);
|
|
if (!file.isDirectory() || file.leafName != id) {
|
|
continue;
|
|
}
|
|
if (keys[id]) {
|
|
var renameTarget = storage37.clone();
|
|
renameTarget.append(keys[id]);
|
|
if (renameTarget.exists()) {
|
|
orphanQueue.push({
|
|
id: id,
|
|
file: renameTarget
|
|
});
|
|
}
|
|
renameQueue.push({
|
|
id: id,
|
|
file: file,
|
|
key: keys[id]
|
|
});
|
|
}
|
|
else {
|
|
orphanQueue.push({
|
|
id: id,
|
|
file: file
|
|
});
|
|
}
|
|
}
|
|
entries.close();
|
|
|
|
if (orphanQueue.length) {
|
|
if (!orphaned.exists()) {
|
|
orphaned.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
|
|
}
|
|
for each(var orphan in orphanQueue) {
|
|
var target = orphaned.clone();
|
|
target.append(orphan.file.leafName);
|
|
var newName = null;
|
|
if (target.exists()) {
|
|
try {
|
|
target.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
|
|
newName = target.leafName;
|
|
}
|
|
catch (e) {
|
|
// DEBUG: Work around createUnique() brokenness on Windows
|
|
// as of Fx3.0.4 (https://bugzilla.mozilla.org/show_bug.cgi?id=452217)
|
|
//
|
|
// We just delete the conflicting file
|
|
if (Zotero.isWin && e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
|
|
target.remove(true);
|
|
}
|
|
else {
|
|
throw (e);
|
|
}
|
|
}
|
|
if (newName) {
|
|
target.remove(false);
|
|
}
|
|
}
|
|
orphan.file.moveTo(orphaned, newName);
|
|
movedFiles37[orphan.id] = orphan.file;
|
|
}
|
|
}
|
|
|
|
for each(var dir in renameQueue) {
|
|
Zotero.debug("Moving " + dir.file.leafName + " to " + dir.key);
|
|
dir.file.moveTo(null, dir.key);
|
|
moveReport += dir.key + ' ' + dir.id + "\n";
|
|
movedFiles37[dir.id] = dir.file;
|
|
}
|
|
|
|
if (moveReport) {
|
|
moveReport = 'The following directory names in storage were changed:\n'
|
|
+ '------------------------------------------------------\n'
|
|
+ moveReport;
|
|
Zotero.File.putContents(moveReportFile, moveReport);
|
|
}
|
|
}
|
|
|
|
|
|
// Migrate big integers
|
|
var itemIDs = Zotero.DB.columnQuery("SELECT itemID FROM items WHERE itemID>16777215");
|
|
var smalls = Zotero.DB.columnQuery("SELECT itemID FROM items WHERE itemID<300000");
|
|
var newID = smalls ? Math.max.apply(this, smalls) : 0;
|
|
for each(var oldID in itemIDs) {
|
|
do {
|
|
newID = newID + 1;
|
|
var exists = Zotero.DB.valueQuery("SELECT COUNT(*) FROM items WHERE itemID=?", newID);
|
|
}
|
|
while (exists);
|
|
var params = [newID, oldID];
|
|
Zotero.DB.query("UPDATE items SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE annotations SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE collectionItems SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE highlights SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE itemCreators SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE itemAttachments SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE itemAttachments SET sourceItemID=? WHERE sourceItemID=?", params);
|
|
Zotero.DB.query("UPDATE itemData SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE itemNotes SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE itemNotes SET sourceItemID=? WHERE sourceItemID=?", params);
|
|
Zotero.DB.query("UPDATE itemSeeAlso SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE itemSeeAlso SET linkedItemID=? WHERE linkedItemID=?", params);
|
|
Zotero.DB.query("UPDATE itemTags SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE fulltextItemWords SET itemID=? WHERE itemID=?", params);
|
|
Zotero.DB.query("UPDATE fulltextItems SET itemID=? WHERE itemID=?", params);
|
|
}
|
|
}
|
|
|
|
// 1.5 Sync Preview 2
|
|
if (i==38) {
|
|
var ids = Zotero.DB.columnQuery("SELECT itemID FROM items WHERE itemTypeID=14 AND itemID NOT IN (SELECT itemID FROM itemAttachments)");
|
|
for each(var id in ids) {
|
|
Zotero.DB.query("INSERT INTO itemAttachments (itemID, linkMode) VALUES (?, ?)", [id, 3]);
|
|
}
|
|
}
|
|
|
|
if (i==39) {
|
|
Zotero.DB.query("CREATE TABLE proxies (\n proxyID INTEGER PRIMARY KEY,\n multiHost INT,\n autoAssociate INT,\n scheme TEXT\n)");
|
|
Zotero.DB.query("CREATE TABLE proxyHosts (\n hostID INTEGER PRIMARY KEY,\n proxyID INTEGER,\n hostname TEXT,\n FOREIGN KEY (proxyID) REFERENCES proxies(proxyID)\n)");
|
|
Zotero.DB.query("CREATE INDEX proxyHosts_proxyID ON proxyHosts(proxyID)");
|
|
}
|
|
|
|
if (i==40) {
|
|
Zotero.DB.query("ALTER TABLE itemAttachments ADD COLUMN syncState INT DEFAULT 0");
|
|
Zotero.DB.query("ALTER TABLE itemAttachments ADD COLUMN storageModTime INT");
|
|
Zotero.DB.query("CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState)");
|
|
Zotero.DB.query("CREATE TABLE storageDeleteLog (\n key TEXT PRIMARY KEY,\n timestamp INT NOT NULL\n)");
|
|
Zotero.DB.query("CREATE INDEX storageDeleteLog_timestamp ON storageDeleteLog(timestamp)");
|
|
}
|
|
|
|
// 1.5 Sync Preview 2.2
|
|
if (i==41) {
|
|
var translators = Zotero.DB.query("SELECT * FROM translators WHERE inRepository!=1");
|
|
if (translators) {
|
|
var dir = Zotero.getTranslatorsDirectory();
|
|
if (dir.exists()) {
|
|
dir.remove(true);
|
|
}
|
|
Zotero.getTranslatorsDirectory()
|
|
for each(var row in translators) {
|
|
var file = dir.clone();
|
|
var fileName = Zotero.Translators.getFileNameFromLabel(row.label);
|
|
file.append(fileName);
|
|
var metadata = { translatorID: row.translatorID, translatorType: parseInt(row.translatorType), label: row.label, creator: row.creator, target: row.target ? row.target : null, minVersion: row.minVersion, maxVersion: row.maxVersion, priority: parseInt(row.priority), inRepository: row.inRepository == 1 ? true : false, lastUpdated: row.lastUpdated };
|
|
var nsIJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
|
|
var metadataJSON = nsIJSON.encode(metadata);
|
|
var str = metadataJSON + "\n\n" + (row.detectCode ? row.detectCode + "\n\n" : "") + row.code;
|
|
Zotero.debug("Extracting translator '" + row.label + "' from database");
|
|
Zotero.File.putContents(file, str);
|
|
}
|
|
Zotero.Translators.init();
|
|
}
|
|
var styles = Zotero.DB.query("SELECT * FROM csl");
|
|
if (styles) {
|
|
var dir = Zotero.getStylesDirectory();
|
|
if (dir.exists()) {
|
|
dir.remove(true);
|
|
}
|
|
Zotero.getStylesDirectory()
|
|
for each(var row in styles) {
|
|
var file = dir.clone();
|
|
var matches = row.cslID.match(/([^\/]+)$/);
|
|
if (!matches) {
|
|
continue;
|
|
}
|
|
file.append(matches[1]);
|
|
Zotero.debug("Extracting styles '" + matches[1] + "' from database");
|
|
Zotero.File.putContents(file, row.csl);
|
|
}
|
|
Zotero.Styles.init();
|
|
}
|
|
Zotero.DB.query("DROP TABLE translators");
|
|
Zotero.DB.query("DROP TABLE csl");
|
|
}
|
|
|
|
if (i==42) {
|
|
Zotero.DB.query("UPDATE itemAttachments SET syncState=0");
|
|
}
|
|
|
|
// 1.5 Sync Preview 2.3
|
|
if (i==43) {
|
|
Zotero.DB.query("UPDATE itemNotes SET note='<div class=\"zotero-note znv1\">' || TEXT2HTML(note) || '</div>' WHERE note NOT LIKE '<div class=\"zotero-note %'");
|
|
}
|
|
|
|
// 1.5 Sync Preview 3 (i==44)
|
|
// 1.5 Sync Preview 3.1
|
|
if (i==45) {
|
|
Zotero.DB.query("DELETE FROM itemData WHERE valueID IN (SELECT valueID FROM itemDataValues WHERE value REGEXP '^\\s*$')");
|
|
Zotero.DB.query("DELETE FROM itemDataValues WHERE value REGEXP '^\\s*$'");
|
|
var rows = Zotero.DB.query("SELECT * FROM itemDataValues WHERE value REGEXP '(^\\s+|\\s+$)'");
|
|
if (rows) {
|
|
for each(var row in rows) {
|
|
var trimmed = Zotero.Utilities.prototype.trim(row.value);
|
|
var valueID = Zotero.DB.valueQuery("SELECT valueID FROM itemDataValues WHERE value=?", trimmed);
|
|
if (valueID) {
|
|
Zotero.DB.query("UPDATE OR REPLACE itemData SET valueID=? WHERE valueID=?", [valueID, row.valueID]);
|
|
Zotero.DB.query("DELETE FROM itemDataValues WHERE valueID=?", row.valueID);
|
|
}
|
|
else {
|
|
Zotero.DB.query("UPDATE itemDataValues SET value=? WHERE valueID=?", [trimmed, row.valueID]);
|
|
}
|
|
}
|
|
}
|
|
|
|
Zotero.DB.query("UPDATE creatorData SET firstName=TRIM(firstName), lastName=TRIM(lastName)");
|
|
var rows = Zotero.DB.query("SELECT * FROM creatorData ORDER BY lastName, firstName, creatorDataID");
|
|
if (rows) {
|
|
for (var j=0; j<rows.length-1; j++) {
|
|
var k = j + 1;
|
|
while (k < rows.length &&
|
|
rows[k].lastName == rows[j].lastName &&
|
|
rows[k].firstName == rows[j].firstName &&
|
|
rows[k].fieldMode == rows[j].fieldMode) {
|
|
Zotero.DB.query("UPDATE creators SET creatorDataID=? WHERE creatorDataID=?", [rows[j].creatorDataID, rows[k].creatorDataID]);
|
|
Zotero.DB.query("DELETE FROM creatorData WHERE creatorDataID=?", rows[k].creatorDataID);
|
|
k++;
|
|
}
|
|
}
|
|
}
|
|
|
|
Zotero.DB.query("DELETE FROM itemTags WHERE tagID IN (SELECT tagID FROM tags WHERE name REGEXP '^\\s*$')");
|
|
Zotero.DB.query("DELETE FROM tags WHERE name REGEXP '^\\s*$'");
|
|
var rows = Zotero.DB.query("SELECT * FROM tags WHERE name REGEXP '(^\\s+|\\s+$)'");
|
|
if (rows) {
|
|
for each(var row in rows) {
|
|
var trimmed = Zotero.Utilities.prototype.trim(row.name);
|
|
var tagID = Zotero.DB.valueQuery("SELECT tagID FROM tags WHERE name=?", trimmed);
|
|
if (tagID) {
|
|
Zotero.DB.query("UPDATE OR REPLACE itemTags SET tagID=? WHERE tagID=?", [tagID, row.tagID]);
|
|
Zotero.DB.query("DELETE FROM tags WHERE tagID=?", row.tagID);
|
|
}
|
|
else {
|
|
Zotero.DB.query("UPDATE tags SET name=? WHERE tagID=?", [trimmed, row.tagID]);
|
|
}
|
|
}
|
|
}
|
|
|
|
Zotero.DB.query("UPDATE itemNotes SET note=TRIM(note)");
|
|
Zotero.DB.query("UPDATE collections SET collectionName=TRIM(collectionName)");
|
|
Zotero.DB.query("UPDATE savedSearches SET savedSearchName=TRIM(savedSearchName)");
|
|
}
|
|
|
|
// 1.5 Sync Preview 3.2
|
|
if (i==46) {
|
|
if (fromVersion < 37) {
|
|
continue;
|
|
}
|
|
|
|
if (Zotero.Prefs.get('useDataDir')) {
|
|
var dataDir = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
|
|
dataDir.persistentDescriptor = Zotero.Prefs.get('dataDir');
|
|
}
|
|
else {
|
|
var dataDir = Zotero.getProfileDirectory();
|
|
dataDir.append('zotero');
|
|
}
|
|
if (!dataDir.exists() || !dataDir.isDirectory()){
|
|
var e = { name: "NS_ERROR_FILE_NOT_FOUND" };
|
|
throw (e);
|
|
}
|
|
var movedFiles46 = {};
|
|
var orphaned = dataDir.clone();
|
|
var storage46 = dataDir.clone();
|
|
orphaned.append('orphaned-files');
|
|
storage46.append('storage');
|
|
var keys = {};
|
|
var rows = Zotero.DB.query("SELECT itemID, key FROM items NATURAL JOIN itemAttachments");
|
|
for each(var row in rows) {
|
|
keys[row.itemID] = row.key;
|
|
}
|
|
if (storage46.exists()) {
|
|
var entries = storage46.directoryEntries;
|
|
entries.QueryInterface(Components.interfaces.nsIDirectoryEnumerator);
|
|
var file;
|
|
var renameQueue = [];
|
|
var orphanQueue = [];
|
|
while (file = entries.nextFile) {
|
|
var id = parseInt(file.leafName);
|
|
if (!file.isDirectory() || file.leafName != id) {
|
|
continue;
|
|
}
|
|
if (keys[id]) {
|
|
var renameTarget = storage46.clone();
|
|
renameTarget.append(keys[id]);
|
|
if (renameTarget.exists()) {
|
|
orphanQueue.push({
|
|
id: id,
|
|
file: renameTarget
|
|
});
|
|
}
|
|
renameQueue.push({
|
|
id: id,
|
|
file: file,
|
|
key: keys[id]
|
|
});
|
|
}
|
|
else {
|
|
orphanQueue.push({
|
|
id: id,
|
|
file: file
|
|
});
|
|
}
|
|
}
|
|
entries.close();
|
|
|
|
if (orphanQueue.length) {
|
|
if (!orphaned.exists()) {
|
|
orphaned.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
|
|
}
|
|
for each(var orphan in orphanQueue) {
|
|
var target = orphaned.clone();
|
|
target.append(orphan.file.leafName);
|
|
var newName = null;
|
|
if (target.exists()) {
|
|
try {
|
|
target.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
|
|
newName = target.leafName;
|
|
}
|
|
catch (e) {
|
|
// DEBUG: Work around createUnique() brokenness on Windows
|
|
// as of Fx3.0.4 (https://bugzilla.mozilla.org/show_bug.cgi?id=452217)
|
|
//
|
|
// We just delete the conflicting file
|
|
if (Zotero.isWin && e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
|
|
target.remove(true);
|
|
}
|
|
else {
|
|
throw (e);
|
|
}
|
|
}
|
|
if (newName) {
|
|
target.remove(false);
|
|
}
|
|
}
|
|
orphan.file.moveTo(orphaned, newName);
|
|
movedFiles46[orphan.id] = orphan.file;
|
|
}
|
|
}
|
|
|
|
for each(var dir in renameQueue) {
|
|
Zotero.debug("Moving " + dir.file.leafName + " to " + dir.key);
|
|
dir.file.moveTo(null, dir.key);
|
|
movedFiles46[dir.id] = dir.file;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1.5 Sync Preview 3.6
|
|
if (i==47) {
|
|
Zotero.DB.query("ALTER TABLE syncDeleteLog RENAME TO syncDeleteLogOld");
|
|
Zotero.DB.query("DROP INDEX syncDeleteLog_timestamp");
|
|
Zotero.DB.query("CREATE TABLE syncDeleteLog (\n syncObjectTypeID INT NOT NULL,\n key TEXT NOT NULL UNIQUE,\n timestamp INT NOT NULL,\n FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)\n);");
|
|
Zotero.DB.query("CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp);");
|
|
Zotero.DB.query("INSERT OR IGNORE INTO syncDeleteLog SELECT syncObjectTypeID, key, timestamp FROM syncDeleteLogOld ORDER BY timestamp DESC");
|
|
Zotero.DB.query("DROP TABLE syncDeleteLogOld");
|
|
}
|
|
|
|
//
|
|
if (i==48) {
|
|
Zotero.DB.query("CREATE TABLE deletedItems (\n itemID INTEGER PRIMARY KEY,\n dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL\n);");
|
|
}
|
|
}
|
|
|
|
_updateDBVersion('userdata', toVersion);
|
|
|
|
Zotero.DB.commitTransaction();
|
|
}
|
|
catch (e) {
|
|
if (movedFiles37) {
|
|
for (var id in movedFiles37) {
|
|
try {
|
|
movedFiles37[id].moveTo(storage37, id);
|
|
}
|
|
catch (e2) { Zotero.debug(e2); }
|
|
}
|
|
}
|
|
if (movedFiles46) {
|
|
for (var id in movedFiles46) {
|
|
try {
|
|
movedFiles46[id].moveTo(storage46, id);
|
|
}
|
|
catch (e2) { Zotero.debug(e2); }
|
|
}
|
|
}
|
|
Zotero.DB.rollbackTransaction();
|
|
throw(e);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|