Abstracted the DB layer for use by Zotero utilities

All the former Zotero.DB methods are now part of an instantiable Zotero.DBConnection object, and Zotero.DB is just one instance of it. Utilities can create and access a new SQLite database within the Zotero data folder by instantiating the DBConnection object:

this.DB = new Zotero.DBConnection('myutility');

Utilities have access to everything the DB layer provides, including automatic backup and restore of databases. Utility writers are on their own for schema management, at least for now.

Also:

- Cleared non-English DB restore localized strings after change.
- Disabled shutdown observer in Zotero object after moving DB backup code to DB layer
This commit is contained in:
Dan Stillman 2007-02-02 05:43:44 +00:00
parent f35f9f4827
commit 49b0f28f26
14 changed files with 673 additions and 693 deletions

View File

@ -20,36 +20,24 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Zotero.DB = new function(){ Zotero.DBConnection = function(dbName) {
// Private members if (!dbName) {
var _connection; throw ('DB name not provided in Zotero.DBConnection()');
var _transactionRollback; }
var _transactionNestingLevel = 0;
var _callbacks = { begin: [], commit: [], rollback: [] };
this.query = query; // Private members
this.valueQuery = valueQuery; this._dbName = dbName;
this.rowQuery = rowQuery; this._shutdown = false;
this.columnQuery = columnQuery; this._connection = null;
this.getStatement = getStatement; this._transactionRollback = null;
this.getLastInsertID = getLastInsertID; this._transactionNestingLevel = 0;
this.getLastErrorString = getLastErrorString; this._callbacks = { begin: [], commit: [], rollback: [] };
this.beginTransaction = beginTransaction; this._self = this;
this.commitTransaction = commitTransaction; }
this.rollbackTransaction = rollbackTransaction;
this.addCallback = addCallback;
this.removeCallback = removeCallback;
this.transactionInProgress = transactionInProgress;
this.commitAllTransactions = commitAllTransactions;
this.tableExists = tableExists;
this.getColumns = getColumns;
this.getColumnHash = getColumnHash;
this.getNextID = getNextID;
this.getNextName = getNextName;
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// //
// Privileged methods // Public methods
// //
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
@ -65,8 +53,8 @@ Zotero.DB = new function(){
* - TRUE for other successful queries * - TRUE for other successful queries
* - FALSE on error * - FALSE on error
*/ */
function query(sql,params){ Zotero.DBConnection.prototype.query = function (sql,params) {
var db = _getDBConnection(); var db = this._getDBConnection();
try { try {
// Parse out the SQL command being used // Parse out the SQL command being used
@ -80,14 +68,14 @@ Zotero.DB = new function(){
// Until the native dataset methods work (or at least exist), // Until the native dataset methods work (or at least exist),
// we build a multi-dimensional associative array manually // we build a multi-dimensional associative array manually
var statement = getStatement(sql, params); var statement = this.getStatement(sql, params);
var dataset = new Array(); var dataset = new Array();
while (statement.executeStep()) { while (statement.executeStep()) {
var row = new Array(); var row = new Array();
for(var i=0; i<statement.columnCount; i++) { for(var i=0; i<statement.columnCount; i++) {
row[statement.getColumnName(i)] = _getTypedValue(statement, i); row[statement.getColumnName(i)] = this._getTypedValue(statement, i);
} }
dataset.push(row); dataset.push(row);
} }
@ -97,11 +85,11 @@ Zotero.DB = new function(){
} }
else { else {
if (params) { if (params) {
var statement = getStatement(sql, params); var statement = this.getStatement(sql, params);
statement.execute(); statement.execute();
} }
else { else {
Zotero.debug(sql,5); this._debug(sql,5);
db.executeSimpleSQL(sql); db.executeSimpleSQL(sql);
} }
@ -125,8 +113,8 @@ Zotero.DB = new function(){
/* /*
* Query a single value and return it * Query a single value and return it
*/ */
function valueQuery(sql,params){ Zotero.DBConnection.prototype.valueQuery = function (sql,params) {
var statement = getStatement(sql, params); var statement = this.getStatement(sql, params);
// No rows // No rows
if (!statement.executeStep()) { if (!statement.executeStep()) {
@ -134,7 +122,7 @@ Zotero.DB = new function(){
return false; return false;
} }
var value = _getTypedValue(statement, 0); var value = this._getTypedValue(statement, 0);
statement.reset(); statement.reset();
return value; return value;
} }
@ -143,7 +131,7 @@ Zotero.DB = new function(){
/* /*
* Run a query and return the first row * Run a query and return the first row
*/ */
function rowQuery(sql,params){ Zotero.DBConnection.prototype.rowQuery = function (sql,params) {
var result = query(sql,params); var result = query(sql,params);
if (result) { if (result) {
return result[0]; return result[0];
@ -154,13 +142,13 @@ Zotero.DB = new function(){
/* /*
* Run a query and return the first column as a numerically-indexed array * Run a query and return the first column as a numerically-indexed array
*/ */
function columnQuery(sql,params){ Zotero.DBConnection.prototype.columnQuery = function (sql,params) {
var statement = getStatement(sql, params); var statement = this.getStatement(sql, params);
if (statement) { if (statement) {
var column = new Array(); var column = new Array();
while (statement.executeStep()) { while (statement.executeStep()) {
column.push(_getTypedValue(statement, 0)); column.push(this._getTypedValue(statement, 0));
} }
statement.reset(); statement.reset();
return column.length ? column : false; return column.length ? column : false;
@ -179,11 +167,11 @@ Zotero.DB = new function(){
* Optional _params_ is an array of bind parameters in the form * Optional _params_ is an array of bind parameters in the form
* [1,"hello",3] or [{'int':2},{'string':'foobar'}] * [1,"hello",3] or [{'int':2},{'string':'foobar'}]
*/ */
function getStatement(sql, params){ Zotero.DBConnection.prototype.getStatement = function (sql, params) {
var db = _getDBConnection(); var db = this._getDBConnection();
try { try {
Zotero.debug(sql,5); this._debug(sql,5);
var statement = db.createStatement(sql); var statement = db.createStatement(sql);
} }
catch (e) { catch (e) {
@ -239,19 +227,19 @@ Zotero.DB = new function(){
// Bind the parameter as the correct type // Bind the parameter as the correct type
switch (type) { switch (type) {
case 'int': case 'int':
Zotero.debug('Binding parameter ' + (i+1) this._debug('Binding parameter ' + (i+1)
+ ' of type int: ' + value, 5); + ' of type int: ' + value, 5);
statement.bindInt32Parameter(i, value); statement.bindInt32Parameter(i, value);
break; break;
case 'string': case 'string':
Zotero.debug('Binding parameter ' + (i+1) this._debug('Binding parameter ' + (i+1)
+ ' of type string: "' + value + '"', 5); + ' of type string: "' + value + '"', 5);
statement.bindUTF8StringParameter(i, value); statement.bindUTF8StringParameter(i, value);
break; break;
case 'null': case 'null':
Zotero.debug('Binding parameter ' + (i+1) this._debug('Binding parameter ' + (i+1)
+ ' of type NULL', 5); + ' of type NULL', 5);
statement.bindNullParameter(i); statement.bindNullParameter(i);
break; break;
@ -263,65 +251,65 @@ Zotero.DB = new function(){
/* /*
* Only for use externally with getStatement() * Only for use externally with this.getStatement()
*/ */
function getLastInsertID(){ Zotero.DBConnection.prototype.getLastInsertID = function () {
var db = _getDBConnection(); var db = this._getDBConnection();
return db.lastInsertRowID; return db.lastInsertRowID;
} }
/* /*
* Only for use externally with getStatement() * Only for use externally with this.getStatement()
*/ */
function getLastErrorString(){ Zotero.DBConnection.prototype.getLastErrorString = function () {
var db = _getDBConnection(); var db = this._getDBConnection();
return db.lastErrorString; return db.lastErrorString;
} }
function beginTransaction(){ Zotero.DBConnection.prototype.beginTransaction = function () {
var db = _getDBConnection(); var db = this._getDBConnection();
if (db.transactionInProgress) { if (db.transactionInProgress) {
_transactionNestingLevel++; this._transactionNestingLevel++;
Zotero.debug('Transaction in progress -- increasing level to ' this._debug('Transaction in progress -- increasing level to '
+ _transactionNestingLevel, 5); + this._transactionNestingLevel, 5);
} }
else { else {
Zotero.debug('Beginning DB transaction', 5); this._debug('Beginning DB transaction', 5);
db.beginTransaction(); db.beginTransaction();
// Run callbacks // Run callbacks
for (var i=0; i<_callbacks.begin.length; i++) { for (var i=0; i<this._callbacks.begin.length; i++) {
if (_callbacks.begin[i]) { if (this._callbacks.begin[i]) {
_callbacks.begin[i](); this._callbacks.begin[i]();
} }
} }
} }
} }
function commitTransaction(){ Zotero.DBConnection.prototype.commitTransaction = function () {
var db = _getDBConnection(); var db = this._getDBConnection();
if (_transactionNestingLevel){ if (this._transactionNestingLevel) {
_transactionNestingLevel--; this._transactionNestingLevel--;
Zotero.debug('Decreasing transaction level to ' + _transactionNestingLevel, 5); this._debug('Decreasing transaction level to ' + this._transactionNestingLevel, 5);
} }
else if (_transactionRollback){ else if (this._transactionRollback) {
Zotero.debug('Rolling back previously flagged transaction', 5); this._debug('Rolling back previously flagged transaction', 5);
db.rollbackTransaction(); db.rollbackTransaction();
} }
else { else {
Zotero.debug('Committing transaction',5); this._debug('Committing transaction',5);
try { try {
db.commitTransaction(); db.commitTransaction();
// Run callbacks // Run callbacks
for (var i=0; i<_callbacks.commit.length; i++) { for (var i=0; i<this._callbacks.commit.length; i++) {
if (_callbacks.commit[i]) { if (this._callbacks.commit[i]) {
_callbacks.commit[i](); this._callbacks.commit[i]();
} }
} }
} }
@ -334,29 +322,29 @@ Zotero.DB = new function(){
} }
function rollbackTransaction(){ Zotero.DBConnection.prototype.rollbackTransaction = function () {
var db = _getDBConnection(); var db = this._getDBConnection();
if (!db.transactionInProgress) { if (!db.transactionInProgress) {
Zotero.debug("Transaction is not in progress in rollbackTransaction()", 2); this._debug("Transaction is not in progress in rollbackTransaction()", 2);
return; return;
} }
if (_transactionNestingLevel){ if (this._transactionNestingLevel) {
_transactionNestingLevel--; this._transactionNestingLevel--;
_transactionRollback = true; this._transactionRollback = true;
Zotero.debug('Flagging nested transaction for rollback', 5); this._debug('Flagging nested transaction for rollback', 5);
} }
else { else {
Zotero.debug('Rolling back transaction', 5); this._debug('Rolling back transaction', 5);
_transactionRollback = false; this._transactionRollback = false;
try { try {
db.rollbackTransaction(); db.rollbackTransaction();
// Run callbacks // Run callbacks
for (var i=0; i<_callbacks.rollback.length; i++) { for (var i=0; i<this._callbacks.rollback.length; i++) {
if (_callbacks.rollback[i]) { if (this._callbacks.rollback[i]) {
_callbacks.rollback[i](); this._callbacks.rollback[i]();
} }
} }
} }
@ -369,7 +357,7 @@ Zotero.DB = new function(){
} }
function addCallback(type, cb) { Zotero.DBConnection.prototype.addCallback = function (type, cb) {
switch (type) { switch (type) {
case 'begin': case 'begin':
case 'commit': case 'commit':
@ -380,13 +368,13 @@ Zotero.DB = new function(){
throw ("Invalid callback type '" + type + "' in DB.addCallback()"); throw ("Invalid callback type '" + type + "' in DB.addCallback()");
} }
var id = _callbacks[type].length; var id = this._callbacks[type].length;
_callbacks[type][id] = cb; this._callbacks[type][id] = cb;
return id; return id;
} }
function removeCallback(type, id) { Zotero.DBConnection.prototype.removeCallback = function (type, id) {
switch (type) { switch (type) {
case 'begin': case 'begin':
case 'commit': case 'commit':
@ -397,12 +385,12 @@ Zotero.DB = new function(){
throw ("Invalid callback type '" + type + "' in DB.removeCallback()"); throw ("Invalid callback type '" + type + "' in DB.removeCallback()");
} }
delete _callbacks[type][id]; delete this._callbacks[type][id];
} }
function transactionInProgress(){ Zotero.DBConnection.prototype.transactionInProgress = function () {
var db = _getDBConnection(); var db = this._getDBConnection();
return db.transactionInProgress; return db.transactionInProgress;
} }
@ -411,12 +399,12 @@ Zotero.DB = new function(){
* Safety function used on shutdown to make sure we're not stuck in the * Safety function used on shutdown to make sure we're not stuck in the
* middle of a transaction * middle of a transaction
*/ */
function commitAllTransactions(){ Zotero.DBConnection.prototype.commitAllTransactions = function () {
if (transactionInProgress()){ if (this.transactionInProgress()) {
var level = _transactionNestingLevel; var level = this._transactionNestingLevel;
_transactionNestingLevel = 0; this._transactionNestingLevel = 0;
try { try {
Zotero.DB.commitTransaction(); this.commitTransaction();
} }
catch (e) {} catch (e) {}
return level ? level : true; return level ? level : true;
@ -425,17 +413,17 @@ Zotero.DB = new function(){
} }
function tableExists(table){ Zotero.DBConnection.prototype.tableExists = function (table) {
return _getDBConnection().tableExists(table); return this._getDBConnection().tableExists(table);
} }
function getColumns(table){ Zotero.DBConnection.prototype.getColumns = function (table) {
var db = _getDBConnection(); var db = this._getDBConnection();
try { try {
var sql = "SELECT * FROM " + table + " LIMIT 1"; var sql = "SELECT * FROM " + table + " LIMIT 1";
var statement = getStatement(sql); var statement = this.getStatement(sql);
var cols = new Array(); var cols = new Array();
for (var i=0,len=statement.columnCount; i<len; i++) { for (var i=0,len=statement.columnCount; i<len; i++) {
cols.push(statement.getColumnName(i)); cols.push(statement.getColumnName(i));
@ -444,14 +432,14 @@ Zotero.DB = new function(){
return cols; return cols;
} }
catch (e) { catch (e) {
Zotero.debug(e,1); this._debug(e,1);
return false; return false;
} }
} }
function getColumnHash(table){ Zotero.DBConnection.prototype.getColumnHash = function (table) {
var cols = getColumns(table); var cols = this.getColumns(table);
var hash = new Array(); var hash = new Array();
if (cols.length) { if (cols.length) {
for (var i=0; i<cols.length; i++) { for (var i=0; i<cols.length; i++) {
@ -468,9 +456,9 @@ Zotero.DB = new function(){
* Note: This retrieves all the rows of the column, so it's not really * Note: This retrieves all the rows of the column, so it's not really
* meant for particularly large tables. * meant for particularly large tables.
**/ **/
function getNextID(table, column){ Zotero.DBConnection.prototype.getNextID = function (table, column) {
var sql = 'SELECT ' + column + ' FROM ' + table + ' ORDER BY ' + column; var sql = 'SELECT ' + column + ' FROM ' + table + ' ORDER BY ' + column;
var vals = Zotero.DB.columnQuery(sql); var vals = this.columnQuery(sql);
if (!vals) { if (!vals) {
return 1; return 1;
@ -500,11 +488,11 @@ Zotero.DB = new function(){
* *
* If _name_ alone is available, returns that * If _name_ alone is available, returns that
**/ **/
function getNextName(table, field, name) Zotero.DBConnection.prototype.getNextName = function (table, field, name)
{ {
var sql = "SELECT " + field + " FROM " + table + " WHERE " + field var sql = "SELECT " + field + " FROM " + table + " WHERE " + field
+ " LIKE ? ORDER BY " + field + " COLLATE NOCASE"; + " LIKE ? ORDER BY " + field + " COLLATE NOCASE";
var untitleds = Zotero.DB.columnQuery(sql, name + '%'); var untitleds = this.columnQuery(sql, name + '%');
if (!untitleds || untitleds[0]!=name) { if (!untitleds || untitleds[0]!=name) {
return name; return name;
@ -514,7 +502,7 @@ Zotero.DB = new function(){
var num = 2; var num = 2;
while (untitleds[i] && untitleds[i]==(name + ' ' + num)) { while (untitleds[i] && untitleds[i]==(name + ' ' + num)) {
while (untitleds[i+1] && untitleds[i]==untitleds[i+1]) { while (untitleds[i+1] && untitleds[i]==untitleds[i+1]) {
Zotero.debug('Next ' + i + ' is ' + untitleds[i]); this._debug('Next ' + i + ' is ' + untitleds[i]);
i++; i++;
} }
@ -526,6 +514,78 @@ Zotero.DB = new function(){
} }
/*
* Shutdown observer
*/
Zotero.DBConnection.prototype.observe = function(subject, topic, data) {
switch (topic) {
case 'xpcom-shutdown':
if (this._shutdown) {
this._debug('returning');
return;
}
var level = this.commitAllTransactions();
if (level) {
level = level === true ? '0' : level;
this._debug("A transaction in DB '" + this._dbName + "' was still open! (level " + level + ")", 2);
}
this._shutdown = true;
this.backupDatabase();
break;
}
}
Zotero.DBConnection.prototype.backupDatabase = function () {
if (this.transactionInProgress()) {
this._debug("Transaction in progress--skipping backup of DB '" + this._dbName + "'", 2);
return false;
}
this._debug("Backing up database '" + this._dbName + "'");
var file = Zotero.getZoteroDatabase(this._dbName);
var backupFile = Zotero.getZoteroDatabase(this._dbName, 'bak');
// Copy via a temporary file so we don't run into disk space issues
// after deleting the old backup file
var tmpFile = Zotero.getZoteroDatabase(this._dbName, 'tmp');
if (tmpFile.exists()) {
tmpFile.remove(null);
}
try {
file.copyTo(file.parent, tmpFile.leafName);
}
catch (e){
// TODO: deal with low disk space
throw (e);
}
try {
var store = Components.classes["@mozilla.org/storage/service;1"].
getService(Components.interfaces.mozIStorageService);
var connection = store.openDatabase(tmpFile);
}
catch (e){
this._debug("Database file '" + tmpFile.leafName + "' is corrupt--skipping backup");
return false;
}
// Remove old backup file
if (backupFile.exists()) {
backupFile.remove(null);
}
tmpFile.moveTo(tmpFile.parent, backupFile.leafName);
return true;
}
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// //
@ -536,19 +596,23 @@ Zotero.DB = new function(){
/* /*
* Retrieve a link to the data store * Retrieve a link to the data store
*/ */
function _getDBConnection(){ Zotero.DBConnection.prototype._getDBConnection = function () {
if (_connection){ if (this._connection) {
return _connection; return this._connection;
} }
this._debug("Opening database '" + this._dbName + "'");
// Get the storage service // Get the storage service
var store = Components.classes["@mozilla.org/storage/service;1"]. var store = Components.classes["@mozilla.org/storage/service;1"].
getService(Components.interfaces.mozIStorageService); getService(Components.interfaces.mozIStorageService);
var file = Zotero.getZoteroDatabase(); var file = Zotero.getZoteroDatabase(this._dbName);
var backupFile = Zotero.getZoteroDatabase('bak'); var backupFile = Zotero.getZoteroDatabase(this._dbName, 'bak');
if (ZOTERO_CONFIG['DB_REBUILD']){ var fileName = this._dbName + '.sqlite';
if (this._dbName == 'zotero' && ZOTERO_CONFIG['DB_REBUILD']) {
if (confirm('Erase all user data and recreate database from schema?')) { if (confirm('Erase all user data and recreate database from schema?')) {
// Delete existing Zotero database // Delete existing Zotero database
if (file.exists()) { if (file.exists()) {
@ -567,63 +631,63 @@ Zotero.DB = new function(){
// Test the backup file (to make sure the backup mechanism is working) // Test the backup file (to make sure the backup mechanism is working)
if (backupFile.exists()) { if (backupFile.exists()) {
try { try {
_connection = store.openDatabase(backupFile); this._connection = store.openDatabase(backupFile);
} }
catch (e) { catch (e) {
Zotero.debug('Backup file was corrupt', 1); this._debug("Backup file '" + backupFile.leafName + "' was corrupt!", 1);
} }
_connection = undefined; this._connection = undefined;
} }
catchBlock: try { catchBlock: try {
_connection = store.openDatabase(file); this._connection = store.openDatabase(file);
} }
catch (e) { catch (e) {
if (e.name=='NS_ERROR_FILE_CORRUPTED') { if (e.name=='NS_ERROR_FILE_CORRUPTED') {
Zotero.debug('Database file corrupted', 1); this._debug("Database file '" + file.leafName + "' corrupted", 1);
// No backup file! Eek! // No backup file! Eek!
if (!backupFile.exists()) { if (!backupFile.exists()) {
Zotero.debug('No backup file exists', 1); this._debug("No backup file for DB '" + this._dbName + "' exists", 1);
// Save damaged filed // Save damaged filed
Zotero.debug('Saving damaged DB file with .damaged extension', 1); this._debug('Saving damaged DB file with .damaged extension', 1);
var damagedFile = Zotero.getZoteroDatabase('damaged'); var damagedFile = Zotero.getZoteroDatabase(this._dbName, 'damaged');
Zotero.moveToUnique(file, damagedFile); Zotero.moveToUnique(file, damagedFile);
// Create new main database // Create new main database
var file = Zotero.getZoteroDatabase(); var file = Zotero.getZoteroDatabase(this._dbName);
_connection = store.openDatabase(file); this._connection = store.openDatabase(file);
alert(Zotero.getString('db.dbCorruptedNoBackup')); alert(Zotero.getString('db.dbCorruptedNoBackup', fileName));
break catchBlock; break catchBlock;
} }
// Save damaged file // Save damaged file
Zotero.debug('Saving damaged DB file with .damaged extension', 1); this._debug('Saving damaged DB file with .damaged extension', 1);
var damagedFile = Zotero.getZoteroDatabase('damaged'); var damagedFile = Zotero.getZoteroDatabase(this._dbName, 'damaged');
Zotero.moveToUnique(file, damagedFile); Zotero.moveToUnique(file, damagedFile);
// Test the backup file // Test the backup file
try { try {
_connection = store.openDatabase(backupFile); this._connection = store.openDatabase(backupFile);
} }
// Can't open backup either // Can't open backup either
catch (e) { catch (e) {
// Create new main database // Create new main database
var file = Zotero.getZoteroDatabase(); var file = Zotero.getZoteroDatabase(this._dbName);
_connection = store.openDatabase(file); this._connection = store.openDatabase(file);
alert(Zotero.getString('db.dbRestoreFailed')); alert(Zotero.getString('db.dbRestoreFailed', fileName));
break catchBlock; break catchBlock;
} }
_connection = undefined; this._connection = undefined;
// Copy backup file to main DB file // Copy backup file to main DB file
Zotero.debug('Restoring main database from backup file', 1); this._debug("Restoring database '" + this._dbName + "' from backup file", 1);
try { try {
backupFile.copyTo(backupFile.parent, ZOTERO_CONFIG['DB_FILE']); backupFile.copyTo(backupFile.parent, fileName);
} }
catch (e) { catch (e) {
// TODO: deal with low disk space // TODO: deal with low disk space
@ -632,10 +696,11 @@ Zotero.DB = new function(){
// Open restored database // Open restored database
var file = Zotero.getZoteroDirectory(); var file = Zotero.getZoteroDirectory();
file.append(ZOTERO_CONFIG['DB_FILE']); file.append(fileName);
_connection = store.openDatabase(file); this._connection = store.openDatabase(file);
Zotero.debug('Database restored', 1); this._debug('Database restored', 1);
var msg = Zotero.getString('db.dbRestored', [ var msg = Zotero.getString('db.dbRestored', [
fileName,
Zotero.Date.getFileDateString(backupFile), Zotero.Date.getFileDateString(backupFile),
Zotero.Date.getFileTimeString(backupFile) Zotero.Date.getFileTimeString(backupFile)
]); ]);
@ -648,11 +713,24 @@ Zotero.DB = new function(){
throw (e); throw (e);
} }
return _connection;
// Register shutdown handler to call this.onShutdown() for DB backup
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "xpcom-shutdown", false);
return this._connection;
} }
function _getTypedValue(statement, i){ Zotero.DBConnection.prototype._debug = function (str, level) {
var prefix = this._dbName == 'zotero' ? '' : '[' + this._dbName + '] ';
Zotero.debug(prefix + str, level);
}
Zotero.DBConnection.prototype._getTypedValue = function (statement, i) {
var type = statement.getTypeOfIndex(i); var type = statement.getTypeOfIndex(i);
switch (type) { switch (type) {
case statement.VALUE_TYPE_INTEGER: case statement.VALUE_TYPE_INTEGER:
@ -673,4 +751,9 @@ Zotero.DB = new function(){
return func(i); return func(i);
} }
}
// Initialize main database connection
Zotero.DB = new Zotero.DBConnection('zotero');

View File

@ -22,7 +22,6 @@
const ZOTERO_CONFIG = { const ZOTERO_CONFIG = {
GUID: 'zotero@chnm.gmu.edu', GUID: 'zotero@chnm.gmu.edu',
DB_FILE: 'zotero.sqlite',
DB_REBUILD: false, // erase DB and recreate from schema DB_REBUILD: false, // erase DB and recreate from schema
REPOSITORY_URL: 'http://www.zotero.org/repo', REPOSITORY_URL: 'http://www.zotero.org/repo',
REPOSITORY_CHECK_INTERVAL: 86400, // 24 hours REPOSITORY_CHECK_INTERVAL: 86400, // 24 hours
@ -36,17 +35,16 @@ var Zotero = new function(){
var _initialized = false; var _initialized = false;
var _debugLogging; var _debugLogging;
var _debugLevel; var _debugLevel;
var _shutdown = false; //var _shutdown = false;
var _localizedStringBundle; var _localizedStringBundle;
// Privileged (public) methods // Privileged (public) methods
this.init = init; this.init = init;
this.shutdown = shutdown; //this.shutdown = shutdown;
this.getProfileDirectory = getProfileDirectory; this.getProfileDirectory = getProfileDirectory;
this.getZoteroDirectory = getZoteroDirectory; this.getZoteroDirectory = getZoteroDirectory;
this.getStorageDirectory = getStorageDirectory; this.getStorageDirectory = getStorageDirectory;
this.getZoteroDatabase = getZoteroDatabase; this.getZoteroDatabase = getZoteroDatabase;
this.backupDatabase = backupDatabase;
this.debug = debug; this.debug = debug;
this.varDump = varDump; this.varDump = varDump;
this.safeDebug = safeDebug; this.safeDebug = safeDebug;
@ -77,6 +75,7 @@ var Zotero = new function(){
} }
// Register shutdown handler to call Zotero.shutdown() // Register shutdown handler to call Zotero.shutdown()
/*
var observerService = Components.classes["@mozilla.org/observer-service;1"] var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService); .getService(Components.interfaces.nsIObserverService);
observerService.addObserver({ observerService.addObserver({
@ -84,6 +83,7 @@ var Zotero = new function(){
Zotero.shutdown(subject, topic, data) Zotero.shutdown(subject, topic, data)
} }
}, "xpcom-shutdown", false); }, "xpcom-shutdown", false);
*/
// Load in the preferences branch for the extension // Load in the preferences branch for the extension
Zotero.Prefs.init(); Zotero.Prefs.init();
@ -143,24 +143,15 @@ var Zotero = new function(){
} }
/*
function shutdown(subject, topic, data){ function shutdown(subject, topic, data){
// Called twice otherwise, for some reason // Called twice otherwise, for some reason
if (_shutdown){ if (_shutdown){
return false; return false;
} }
var level = Zotero.DB.commitAllTransactions();
if (level){
level = level===true ? '0' : level;
Zotero.debug("A transaction was still open! (level " + level + ")", 2);
}
_shutdown = true;
Zotero.backupDatabase();
return true; return true;
} }
*/
function getProfileDirectory(){ function getProfileDirectory(){
@ -193,66 +184,16 @@ var Zotero = new function(){
return file; return file;
} }
function getZoteroDatabase(ext){ function getZoteroDatabase(name, ext){
name = name ? name + '.sqlite' : 'zotero.sqlite';
ext = ext ? '.' + ext : ''; ext = ext ? '.' + ext : '';
var file = Zotero.getZoteroDirectory(); var file = Zotero.getZoteroDirectory();
file.append(ZOTERO_CONFIG['DB_FILE'] + ext); file.append(name + ext);
return file; return file;
} }
/*
* Back up the main database file
*/
function backupDatabase(){
if (Zotero.DB.transactionInProgress()){
Zotero.debug('Transaction in progress--skipping DB backup', 2);
return false;
}
Zotero.debug('Backing up database');
var file = Zotero.getZoteroDatabase();
var backupFile = Zotero.getZoteroDatabase('bak');
// Copy via a temporary file so we don't run into disk space issues
// after deleting the old backup file
var tmpFile = Zotero.getZoteroDatabase('tmp');
if (tmpFile.exists()){
tmpFile.remove(null);
}
try {
file.copyTo(file.parent, tmpFile.leafName);
}
catch (e){
// TODO: deal with low disk space
throw (e);
}
try {
var store = Components.classes["@mozilla.org/storage/service;1"].
getService(Components.interfaces.mozIStorageService);
var connection = store.openDatabase(tmpFile);
}
catch (e){
Zotero.debug("Database file is corrupt--skipping backup");
return false;
}
// Remove old backup file
if (backupFile.exists()){
backupFile.remove(null);
}
tmpFile.moveTo(tmpFile.parent, backupFile.leafName);
return true;
}
/* /*
* Debug logging function * Debug logging function
* *

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=Artikel gespeichert.
ingester.scrapeError=Artikel konnte nicht gespeichert werden. ingester.scrapeError=Artikel konnte nicht gespeichert werden.
ingester.scrapeErrorDescription=Ein Fehler ist aufgetreten bei dem Versuch, diesen Artikel zu speichern. Wenn dieser Fehler weiterhin auftritt, kontaktieren Sie bitte den Autor des Übersetzers. ingester.scrapeErrorDescription=Ein Fehler ist aufgetreten bei dem Versuch, diesen Artikel zu speichern. Wenn dieser Fehler weiterhin auftritt, kontaktieren Sie bitte den Autor des Übersetzers.
db.dbCorruptedNoBackup=Die Zotero-Datenbank wurde offensichtlich beschädigt und es gibt kein automatisches Backup.\n\nEine neue Datenbank wurde erstellt. Die beschädigte Datei bleibt im Zotero-Ordner gespeichert.
db.dbRestored=Die Zotero-Datenbank wurde offensichtlich beschädigt.\n\nDie Daten wurde aus dem letztigen automatischen Backup vom %1$S um %2$S wiederhergestellt. Die beschädigte Datei bleibt im Zotero-Ordner gespeichert.
db.dbRestoreFailed=Die Zotero-Datenbank wurde offensichtlich beschädigt, und der Versuch, die Daten aus dem letztigen automatischen Backup wiederherzustellen, hat nicht funktioniert.\n\nEine neue Datenbank wurde erstellt. Die beschädigte Datei bleibt im Zotero-Ordner gespeichert.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=Artikel gespeichert.
ingester.scrapeError=Artikel konnte nicht gespeichert werden. ingester.scrapeError=Artikel konnte nicht gespeichert werden.
ingester.scrapeErrorDescription=Ein Fehler ist aufgetreten bei dem Versuch, diesen Artikel zu speichern. Wenn dieser Fehler weiterhin auftritt, kontaktieren Sie bitte den Autor des Übersetzers. ingester.scrapeErrorDescription=Ein Fehler ist aufgetreten bei dem Versuch, diesen Artikel zu speichern. Wenn dieser Fehler weiterhin auftritt, kontaktieren Sie bitte den Autor des Übersetzers.
db.dbCorruptedNoBackup=Die Zotero-Datenbank wurde offensichtlich beschädigt und es gibt kein automatisches Backup.\n\nEine neue Datenbank wurde erstellt. Die beschädigte Datei bleibt im Zotero-Ordner gespeichert.
db.dbRestored=Die Zotero-Datenbank wurde offensichtlich beschädigt.\n\nDie Daten wurde aus dem letztigen automatischen Backup vom %1$S um %2$S wiederhergestellt. Die beschädigte Datei bleibt im Zotero-Ordner gespeichert.
db.dbRestoreFailed=Die Zotero-Datenbank wurde offensichtlich beschädigt, und der Versuch, die Daten aus dem letztigen automatischen Backup wiederherzustellen, hat nicht funktioniert.\n\nEine neue Datenbank wurde erstellt. Die beschädigte Datei bleibt im Zotero-Ordner gespeichert.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=Artikel gespeichert.
ingester.scrapeError=Artikel konnte nicht gespeichert werden. ingester.scrapeError=Artikel konnte nicht gespeichert werden.
ingester.scrapeErrorDescription=Ein Fehler ist aufgetreten bei dem Versuch, diesen Artikel zu speichern. Wenn dieser Fehler weiterhin auftritt, kontaktieren Sie bitte den Autor des Übersetzers. ingester.scrapeErrorDescription=Ein Fehler ist aufgetreten bei dem Versuch, diesen Artikel zu speichern. Wenn dieser Fehler weiterhin auftritt, kontaktieren Sie bitte den Autor des Übersetzers.
db.dbCorruptedNoBackup=Die Zotero-Datenbank wurde offensichtlich beschädigt und es gibt kein automatisches Backup.\n\nEine neue Datenbank wurde erstellt. Die beschädigte Datei bleibt im Zotero-Ordner gespeichert.
db.dbRestored=Die Zotero-Datenbank wurde offensichtlich beschädigt.\n\nDie Daten wurde aus dem letztigen automatischen Backup vom %1$S um %2$S wiederhergestellt. Die beschädigte Datei bleibt im Zotero-Ordner gespeichert.
db.dbRestoreFailed=Die Zotero-Datenbank wurde offensichtlich beschädigt, und der Versuch, die Daten aus dem letztigen automatischen Backup wiederherzustellen, hat nicht funktioniert.\n\nEine neue Datenbank wurde erstellt. Die beschädigte Datei bleibt im Zotero-Ordner gespeichert.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -226,9 +226,9 @@ ingester.scrapeComplete = Item Saved.
ingester.scrapeError = Could Not Save Item. ingester.scrapeError = Could Not Save Item.
ingester.scrapeErrorDescription = An error occurred while saving this item. Please try again. If this error persists, contact the translator author. ingester.scrapeErrorDescription = An error occurred while saving this item. Please try again. If this error persists, contact the translator author.
db.dbCorruptedNoBackup = The Zotero database appears to have become corrupted, and no automatic backup is available.\n\nA new database file has been created. The damaged file was saved in your Zotero directory. db.dbCorruptedNoBackup = The Zotero database '%S' appears to have become corrupted, and no automatic backup is available.\n\nA new database file has been created. The damaged file was saved in your Zotero directory.
db.dbRestored = The Zotero database appears to have become corrupted.\n\nYour data was restored from the last automatic backup made on %1$S at %2$S. The damaged file was saved in your Zotero directory. db.dbRestored = The Zotero database '%1$S' appears to have become corrupted.\n\nYour data was restored from the last automatic backup made on %2$S at %3$S. The damaged file was saved in your Zotero directory.
db.dbRestoreFailed = The Zotero database appears to have become corrupted, and an attempt to restore from the last automatic backup failed.\n\nA new database file has been created. The damaged file was saved in your Zotero directory. db.dbRestoreFailed = The Zotero database '%S' appears to have become corrupted, and an attempt to restore from the last automatic backup failed.\n\nA new database file has been created. The damaged file was saved in your Zotero directory.
zotero.preferences.update.updated = Updated zotero.preferences.update.updated = Updated
zotero.preferences.update.upToDate = Up to date zotero.preferences.update.upToDate = Up to date

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=Élément enregistré.
ingester.scrapeError=Échec de l'enregistrement. ingester.scrapeError=Échec de l'enregistrement.
ingester.scrapeErrorDescription=Une erreur s'est produite lors de l'enregistrement de cet élément. Veuillez réessayer. Si cette erreur persiste, contactez l'auteur du collecteur de données. ingester.scrapeErrorDescription=Une erreur s'est produite lors de l'enregistrement de cet élément. Veuillez réessayer. Si cette erreur persiste, contactez l'auteur du collecteur de données.
db.dbCorruptedNoBackup=La base de données Zotero semble avoir été corrompue et aucune sauvegarde automatique n'est disponible.\n\nUn nouveau fichier de base de données a été créé. Le fichier endommagé a été enregistré dans le dossier Zotero.
db.dbRestored=La base de données Zotero semble avoir été corrompue.\n\nVos données ont été rétablies à partir de la dernière sauvegarde automatique faite le %1$S à %2$S. Le fichier endommagé a été enregistré dans le dossier Zotero.
db.dbRestoreFailed=La base de données Zotero semble avoir été corrompue et une tentative de rétablissement à partir de la dernière sauvegarde automatique a échoué.\n\nUn nouveau fichier de base de données a été créé. Le fichier endommagé a été enregistré dans le dossier Zotero.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=Elemento salvato
ingester.scrapeError=Impossibile salvare elemento ingester.scrapeError=Impossibile salvare elemento
ingester.scrapeErrorDescription=Si è verificato un errore durante il salvataggio dell'elemento. Provare nuovamente. Se l'errore persiste, contattare l'autore del motore di ricerca. ingester.scrapeErrorDescription=Si è verificato un errore durante il salvataggio dell'elemento. Provare nuovamente. Se l'errore persiste, contattare l'autore del motore di ricerca.
db.dbCorruptedNoBackup=Il database di Zotero risulta essere corrotto e nessun backup risulta essere disponibile. \n\nÈ stato creato un nuovo database. Il file danneggiato è stato salvato all'interno della cartella di Zotero.
db.dbRestored=Il database di Zotero risulta essere corrotto. \n\nI dati sono stati ripristinati dall'ultimo backup automatico eseguito il %1$S alle %2$S. Il file danneggiato è stato salvato all'interno della cartella di Zotero.
db.dbRestoreFailed=Il database di Zotero risulta essere corrotto e il tentativo di ripristinare l'ultimo backup automatico ha dato esito negativo. \n\nÈ stato creato un nuovo database. Il file danneggiato è stato salvato all'interno della cartella di Zotero.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=アイテムを保存しました。
ingester.scrapeError=アイテムを保存できませんでした。 ingester.scrapeError=アイテムを保存できませんでした。
ingester.scrapeErrorDescription=アイテムの保存中にエラーが発生しました。もう一度試してください。それでもエラーが発生する場合、スクレーパの作成者までご連絡ください。 ingester.scrapeErrorDescription=アイテムの保存中にエラーが発生しました。もう一度試してください。それでもエラーが発生する場合、スクレーパの作成者までご連絡ください。
db.dbCorruptedNoBackup=Zoteroのデータベースが破損しているようですが、自動バックアップはありません。\n\n新規データベース・ファイルが作成されました。破損されたファイルをZoteroフォルダに保存しました。
db.dbRestored=Zoteroのデータベースが破損しているようです。\n\nデータは最終の自動バックアップ%1$S %2$Sから復元されました。破損されたファイルをZoteroフォルダに保存しました。
db.dbRestoreFailed=Zoteroのデータベースが破損しているようですが、データを最終の自動バックアップから復元できませんでした。\n\n新規データベース・ファイルが作成されました。破損されたファイルをZoteroフォルダに保存しました。
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=항목 저장됨.
ingester.scrapeError=항목을 저장할 수 없습니다. ingester.scrapeError=항목을 저장할 수 없습니다.
ingester.scrapeErrorDescription=항목 저장중 오류가 발생했습니다. 다시 시도해 주세요. 오류가 지족적으로 발생하는 경우, 작성자에게 연락해 주십시오. ingester.scrapeErrorDescription=항목 저장중 오류가 발생했습니다. 다시 시도해 주세요. 오류가 지족적으로 발생하는 경우, 작성자에게 연락해 주십시오.
db.dbCorruptedNoBackup=Zotero의 데이타베이스가 훼손되어 있는 것으로 보이며, 자동 백업본이 존재하지 않습니다. \n\n신규 데이타베이스·파일이 생성되었습니다. 파손된 파일을 Zotero 폴더에 저장했습니다.
db.dbRestored=Zotero의 데이타베이스가 훼손되어 있는 것으로 보입니다. \n\n데이터는 최종 자동 백업(%1$S %2$S)으로부터 복원되었습니다. 파손된 파일을 Zotero 폴더에 저장했습니다.
db.dbRestoreFailed=Zotero의 데이타베이스가 훼손되어 있는 것으로 보이며, 최종 자동 백업으로부터 복원에 실패했습니다. \n\n신규 데이타베이스 파일이 생성되었습니다. 파손된 파일을 Zotero 폴더에 저장했습니다.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=Element lagret.
ingester.scrapeError=Kunne ikke lagre element. ingester.scrapeError=Kunne ikke lagre element.
ingester.scrapeErrorDescription=En feil oppsto mens dette elementet ble forsøkt lagret. Vennligst prøv igjen. Vennligst kontakt forfatteren av oversetteren dersom denne feilen vedvarer. ingester.scrapeErrorDescription=En feil oppsto mens dette elementet ble forsøkt lagret. Vennligst prøv igjen. Vennligst kontakt forfatteren av oversetteren dersom denne feilen vedvarer.
db.dbCorruptedNoBackup=Zotero-databasen synes å ha blitt korrupt, og ingen automatisk backup er tilgjengelig.\n\nEn ny database har blitt skapt. Den skadde filen ble lagret i Zotero-mappen.
db.dbRestored=Zotero-databasen synes å ha blitt korrupt.\n\nDataene dine ble gjenopprettet fra den siste automatiske backupfilen, skapt %1$S den %2$S. Den skadde filen ble lagret i Zotero-mappen.
db.dbRestoreFailed=Zotero-databasen synes å ha blitt korrupt, og et forsøk på å gjenopprette fra sist backup feilet.\n\nEn ny database har blitt skapt. Den skadde filen ble lagret i Zotero-mappen.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=Item Opgeslagen.
ingester.scrapeError=Dit Item Kon Niet Opgeslagen Worden. ingester.scrapeError=Dit Item Kon Niet Opgeslagen Worden.
ingester.scrapeErrorDescription=Er trad een fout op bij het opslagen van dit item. Probeer nogmaals. Indien deze fout zich nog voordoet, contacteer dan auteur van de site-vertaler. ingester.scrapeErrorDescription=Er trad een fout op bij het opslagen van dit item. Probeer nogmaals. Indien deze fout zich nog voordoet, contacteer dan auteur van de site-vertaler.
db.dbCorruptedNoBackup=De Zotero gegevensbank blijkt corrupt, en er is geen automatische backup beschikbaar. Er werd een nieuwe gevevensbank aangemaakt. Het beschadigde bestand werd opgeslagen in je Zotero map.
db.dbRestored=De Zotero gegevensbank blijkt corrupt.\n\nJe data werden hersteld sinds de laatste automatische backup, aangemaakt op %1$S om %2$S. Het beschadigde bestand werd opgeslagen in je Zotero map.
db.dbRestoreFailed=De Zotero gegevensbank blijkt corrupt, en een poging om te herstellen sinds de laatste automatische backup mislukte. Er werd een nieuwe gegevensbank aangemaakt. Het beschadigde bestand werd opgeslagen in je Zotero map.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=Ставка сачувана.
ingester.scrapeError=Ставка није могле бити сачувана. ingester.scrapeError=Ставка није могле бити сачувана.
ingester.scrapeErrorDescription=Грешка је начињена током сачувавања ставке. Покушајте поново. Ако грешка остане, контактирајте аутора „преводиоца странице“. ingester.scrapeErrorDescription=Грешка је начињена током сачувавања ставке. Покушајте поново. Ако грешка остане, контактирајте аутора „преводиоца странице“.
db.dbCorruptedNoBackup=Зотеро датабаза се игледа оштетила, а нема ни једне самонаправљене резервне копије. \n\nНова датотека датабазе је направљена. Остећена дадотека је сачувана у вашем Зотеро директоријуму.
db.dbRestored=Зотеро дата база се изгледа оштетила.\n\nВаши подаци су обновљени из задње резервне копије самонаправљене %1$S у %2$S. Оштећена датотека је сачувана у вашем Зотеро директоријуму.
db.dbRestoreFailed=Зотеро датабаза се игледа оштетила, а покушај да се подаци обнове из задње сачуване резервне копије није успео. \n\nНова датотека датабазе је направљена. Остећена дадотека је сачувана у вашем Зотеро директоријуму.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error

View File

@ -217,10 +217,6 @@ ingester.scrapeComplete=条目已保存.
ingester.scrapeError=无法保存条目. ingester.scrapeError=无法保存条目.
ingester.scrapeErrorDescription=保存条目时发生错误. 请重试. 如果错误依旧, 请联系转换器作者. ingester.scrapeErrorDescription=保存条目时发生错误. 请重试. 如果错误依旧, 请联系转换器作者.
db.dbCorruptedNoBackup=Zotero 数据库似乎已损坏, 而且无可用的自动备份.\n\n已建立一新的数据库. 损坏的文件已保存到您的 Zotero 目录中.
db.dbRestored=Zotero 数据库似乎已损坏.\n\n您的数据已经从最新的自动备份里恢复, 所使用的备份建立于%1$S 位于 %2$S. 损坏的文件已保存到您的 Zotero 目录中.
db.dbRestoreFailed=Zotero 数据库似乎已损坏. 试图从最新的自动备份里恢复时失败. \n\n已创建一新的数据库. 损坏的文件已保存到您的 Zotero 目录中.
zotero.preferences.update.updated=Updated zotero.preferences.update.updated=Updated
zotero.preferences.update.upToDate=Up to date zotero.preferences.update.upToDate=Up to date
zotero.preferences.update.error=Error zotero.preferences.update.error=Error