Adds WebDAV file sync

- Still experimental and incomplete, with no lock support and not much error handling

Also:

- New expiry date for sync functions
- Attachment character set was being dropped during syncing
- Possibly improves sizing issues with preferences window
- Fixes problems with attachment filenames with extended characters
- Fixes some problem with tags that I don't remember
- Makes XMLHTTPRequest calls are now background requests (no auth windows or other prompts)
- Z.U.HTTP.doOptions() now takes an nsIURI instead of a URL spec
- New methods:
  - Zotero.Utilities.rand(min, max)
  - Zotero.Utilities.probability(x)
  - Zotero.Utilities.Base64.encode(str) and decode(str)
  - Zotero.getTempDirectory()
  - Zotero.Date.dateToISO(date) - convert JS Date object to ISO 8601 UTC date/time
  - Zotero.Date.isoToDate(isoDate) - convert an ISO 8601 UTC date/time to a JS Date object
This commit is contained in:
Dan Stillman 2008-08-31 23:36:01 +00:00
parent 227b4cbfcd
commit a8bb8dae40
19 changed files with 3516 additions and 246 deletions

View file

@ -133,6 +133,10 @@
<menuitem label="Clear Server Data" oncommand="Zotero.Sync.Server.clear()"/> <menuitem label="Clear Server Data" oncommand="Zotero.Sync.Server.clear()"/>
<menuitem label="Reset Server Lock" oncommand="Zotero.Sync.Server.resetServer()"/> <menuitem label="Reset Server Lock" oncommand="Zotero.Sync.Server.resetServer()"/>
<menuitem label="Reset Client" oncommand="Zotero.Sync.Server.resetClient()"/> <menuitem label="Reset Client" oncommand="Zotero.Sync.Server.resetClient()"/>
<menuseparator id="zotero-tb-actions-storage-separator"/>
<menuitem label="Reset Storage History" oncommand="Zotero.Sync.Storage.resetAllSyncStates()"/>
<menuitem label="Purge Deleted Storage Files" oncommand="Zotero.Sync.Storage.purgeDeletedStorageFiles(function(results) { Zotero.debug(results); })"/>
<menuitem label="Purge Orphaned Storage Files" oncommand="Zotero.Sync.Storage.purgeOrphanedStorageFiles(function(results) { Zotero.debug(results); })"/>
<menuseparator id="zotero-tb-actions-separator"/> <menuseparator id="zotero-tb-actions-separator"/>
<menuitem id="zotero-tb-actions-prefs" label="&zotero.toolbar.preferences.label;" <menuitem id="zotero-tb-actions-prefs" label="&zotero.toolbar.preferences.label;"
oncommand="window.openDialog('chrome://zotero/content/preferences/preferences.xul', 'zotero-prefs', 'chrome,titlebar,toolbar,' + Zotero.Prefs.get('browser.preferences.instantApply', true) ? 'dialog=no' : 'modal')"/> oncommand="window.openDialog('chrome://zotero/content/preferences/preferences.xul', 'zotero-prefs', 'chrome,titlebar,toolbar,' + Zotero.Prefs.get('browser.preferences.instantApply', true) ? 'dialog=no' : 'modal')"/>
@ -305,16 +309,49 @@
<splitter id="zotero-view-splitter" resizebefore="closest" resizeafter="closest"/> <splitter id="zotero-view-splitter" resizebefore="closest" resizeafter="closest"/>
<vbox id="zotero-item-pane" persist="width"> <vbox id="zotero-item-pane" persist="width">
<toolbar align="right"> <toolbar align="center" pack="end">
<progressmeter id="zotero-tb-syncProgress" mode="determined"
value="0" tooltip="zotero-tb-syncProgress-tooltip"
hidden="true">
</progressmeter>
<tooltip id="zotero-tb-syncProgress-tooltip" noautohide="true">
<grid>
<columns>
<column/>
<column/>
</columns>
<rows>
<row>
<label value="&zotero.sync.storage.progress;"/>
<label id="zotero-tb-syncProgress-tooltip-progress"/>
</row>
<row>
<label value="&zotero.sync.storage.downloads;"/>
<label
id="zotero-tb-syncProgress-tooltip-downloads"/>
</row>
<row>
<label value="&zotero.sync.storage.uploads;"/>
<label
id="zotero-tb-syncProgress-tooltip-uploads"/>
</row>
</rows>
</grid>
</tooltip>
<toolbarbutton id="zotero-tb-sync" tooltip="_child" <toolbarbutton id="zotero-tb-sync" tooltip="_child"
oncommand="Zotero.Sync.Server.sync()"> oncommand="Zotero.Sync.Runner.sync()">
<tooltip <tooltip
onpopupshowing="if (Zotero.Sync.Server.lastSyncError) { this.firstChild.nextSibling.value = 'Last error: ' + Zotero.Sync.Server.lastSyncError; return; } this.firstChild.nextSibling.value = 'Last sync: ' + (Zotero.Sync.Server.lastLocalSyncTime ? new Date(Zotero.Sync.Server.lastLocalSyncTime * 1000).toLocaleString() : 'Not yet synced')" onpopupshowing="if (Zotero.Sync.Server.lastSyncError) { this.firstChild.nextSibling.value = 'Last error: ' + Zotero.Sync.Server.lastSyncError; return; } this.firstChild.nextSibling.value = 'Last sync: ' + (Zotero.Sync.Server.lastLocalSyncTime ? new Date(Zotero.Sync.Server.lastLocalSyncTime * 1000).toLocaleString() : 'Not yet synced')"
noautohide="true"><!-- localize --> noautohide="true"><!-- TODO: localize -->
<label value="Sync with Zotero Server"/> <label value="Sync with Zotero Server"/>
<label id="zotero-last-sync-time"/> <label id="zotero-last-sync-time"/>
</tooltip> </tooltip>
</toolbarbutton> </toolbarbutton>
<!--
<toolbarbutton id="zotero-tb-storage-sync"
tooltiptext="Sync with Storage Server"
oncommand="Zotero.Sync.Storage.sync()"/>
-->
<toolbarseparator/> <toolbarseparator/>
<toolbarbutton id="zotero-tb-fullscreen" tooltiptext="&zotero.toolbar.fullscreen.tooltip;" oncommand="ZoteroPane.fullScreen();"/> <toolbarbutton id="zotero-tb-fullscreen" tooltiptext="&zotero.toolbar.fullscreen.tooltip;" oncommand="ZoteroPane.fullScreen();"/>
<toolbarbutton class="tabs-closebutton" oncommand="ZoteroPane.toggleDisplay()"/> <toolbarbutton class="tabs-closebutton" oncommand="ZoteroPane.toggleDisplay()"/>

View file

@ -137,6 +137,170 @@ function populateOpenURLResolvers() {
} }
//
// Sync
//
function unverifyStorageServer() {
Zotero.debug("Clearing storage settings");
Zotero.Sync.Storage.clearSettingsCache();
Zotero.Prefs.set('sync.storage.verified', false);
}
function verifyStorageServer() {
Zotero.debug("Verifying storage");
var verifyButton = document.getElementById("storage-verify");
var abortButton = document.getElementById("storage-abort");
var progressMeter = document.getElementById("storage-progress");
var callback = function (uri, status, authRequired) {
verifyButton.hidden = false;
abortButton.hidden = true;
progressMeter.hidden = true;
var promptService =
Components.classes["@mozilla.org/network/default-prompt;1"].
createInstance(Components.interfaces.nsIPrompt);
if (uri) {
var spec = uri.scheme + '://' + uri.hostPort + uri.path;
}
switch (status) {
case Zotero.Sync.Storage.SUCCESS:
promptService.alert(
"Server configuration verified",
"File storage is successfully set up."
);
Zotero.Prefs.set("sync.storage.verified", true);
return true;
case Zotero.Sync.Storage.ERROR_NO_URL:
var errorMessage = "Please enter a URL.";
setTimeout(function () {
document.getElementById("storage-url").focus();
}, 1);
break;
case Zotero.Sync.Storage.ERROR_NO_USERNAME:
var errorMessage = "Please enter a username.";
setTimeout(function () {
document.getElementById("storage-username").focus();
}, 1);
break;
case Zotero.Sync.Storage.ERROR_NO_PASSWORD:
var errorMessage = "Please enter a password.";
setTimeout(function () {
document.getElementById("storage-password").focus();
}, 1);
break;
case Zotero.Sync.Storage.ERROR_UNREACHABLE:
var errorMessage = "The server " + uri.host + " could not be reached.";
break;
case Zotero.Sync.Storage.ERROR_NOT_DAV:
var errorMessage = spec + " is not a valid WebDAV URL.";
break;
case Zotero.Sync.Storage.ERROR_AUTH_FAILED:
var errorTitle = "Permission denied";
var errorMessage = "The server did not accept the username and "
+ "password you entered." + " "
+ "Please check your server settings "
+ "or contact your server administrator.";
break;
case Zotero.Sync.Storage.ERROR_FORBIDDEN:
var errorTitle = "Permission denied";
var errorMessage = "You don't have permission to access "
+ uri.path + " on this server." + " "
+ "Please check your server settings "
+ "or contact your server administrator.";
break;
case Zotero.Sync.Storage.ERROR_PARENT_DIR_NOT_FOUND:
var errorTitle = "Directory not found";
var parentSpec = spec.replace(/\/zotero\/$/, "");
var errorMessage = parentSpec + " does not exist.";
break;
case Zotero.Sync.Storage.ERROR_ZOTERO_DIR_NOT_FOUND:
var create = promptService.confirmEx(
// TODO: localize
"Directory not found",
spec + " does not exist.\n\nDo you want to create it now?",
promptService.BUTTON_POS_0
* promptService.BUTTON_TITLE_IS_STRING
+ promptService.BUTTON_POS_1
* promptService.BUTTON_TITLE_CANCEL,
"Create",
null, null, null, {}
);
if (create != 0) {
return;
}
Zotero.Sync.Storage.createServerDirectory(function (uri, status) {
switch (status) {
case Zotero.Sync.Storage.SUCCESS:
promptService.alert(
"Server configuration verified",
"File storage is successfully set up."
);
Zotero.Prefs.set("sync.storage.verified", true);
return true;
case Zotero.Sync.Storage.ERROR_FORBIDDEN:
var errorTitle = "Permission denied";
var errorMessage = "You do not have "
+ "permission to create a Zotero directory "
+ "at the following address:" + "\n\n" + spec;
errorMessage += "\n\n"
+ "Please check your server settings or "
+ "contact your server administrator.";
break;
}
// TEMP
if (!errorMessage) {
var errorMessage = status;
}
promptService.alert(errorTitle, errorMessage);
});
return false;
}
if (!errorTitle) {
var errorTitle = Zotero.getString("general.error");
}
// TEMP
if (!errorMessage) {
var errorMessage = status;
}
promptService.alert(errorTitle, errorMessage);
return false;
}
verifyButton.hidden = true;
abortButton.hidden = false;
progressMeter.hidden = false;
var requestHolder = Zotero.Sync.Storage.checkServer(callback);
abortButton.onclick = function () {
if (requestHolder.request) {
requestHolder.request.onreadystatechange = undefined;
requestHolder.request.abort();
verifyButton.hidden = false;
abortButton.hidden = true;
progressMeter.hidden = true;
}
}
}
/* /*
* Builds the main Quick Copy drop-down from the current global pref * Builds the main Quick Copy drop-down from the current global pref
*/ */

View file

@ -156,31 +156,116 @@ To add a new preference:
<!-- localize --> <!-- localize -->
<prefpane id="zotero-prefpane-sync" <prefpane id="zotero-prefpane-sync"
label="Sync" label="Sync"
onpaneload="document.getElementById('sync-password').value = Zotero.Sync.Server.password;" onpaneload="document.getElementById('sync-password').value = Zotero.Sync.Server.password; document.getElementById('storage-password').value = Zotero.Sync.Storage.password;"
image="chrome://zotero/skin/prefs-sync.png"> image="chrome://zotero/skin/prefs-sync.png">
<preferences> <preferences>
<preference id="pref-sync-username" name="extensions.zotero.sync.server.username" type="string"/> <preference id="pref-sync-autosync" name="extensions.zotero.sync.autoSync" type="bool"/>
<preference id="pref-sync-username" name="extensions.zotero.sync.server.username" type="string" instantApply="true"/>
<preference id="pref-storage-enabled" name="extensions.zotero.sync.storage.enabled" type="bool"/>
<preference id="pref-storage-url" name="extensions.zotero.sync.storage.url" type="string" instantApply="true"/>
<preference id="pref-storage-username" name="extensions.zotero.sync.storage.username" type="string" instantApply="true"/>
</preferences> </preferences>
<grid> <groupbox>
<columns> <caption label="Zotero Sync Server"/>
<columns/>
<columns/>
</columns>
<rows> <grid>
<row> <columns>
<label value="Username:"/> <column/>
<textbox preference="pref-sync-username" <column/>
onchange="Zotero.Prefs.set('sync.server.username', this.value); var pass = document.getElementById('sync-password'); if (pass.value) { Zotero.Sync.Server.password = pass.value; }"/> </columns>
</row>
<row> <rows>
<label value="Password:"/> <row>
<textbox id="sync-password" type="password" <label value="Username:"/>
onchange="Zotero.Sync.Server.password = this.value"/> <textbox preference="pref-sync-username"
</row> onchange="Zotero.Prefs.set('sync.server.username', this.value); var pass = document.getElementById('sync-password'); if (pass.value) { Zotero.Sync.Server.password = pass.value; }"/>
</rows> </row>
</grid> <row>
<label value="Password:"/>
<textbox id="sync-password" type="password"
onchange="Zotero.Sync.Server.password = this.value"/>
</row>
<!--
<row>
<box/>
<hbox>
<button label="Verify login"
oncommand="alert('Unimplemented')"/>
</hbox>
</row>
-->
</rows>
</grid>
<separator class="thin"/>
<hbox>
<checkbox label="Sync automatically" preference="pref-sync-autosync"/>
</hbox>
</groupbox>
<groupbox>
<caption label="Storage Server"/>
<hbox>
<checkbox label="Enable file syncing" preference="pref-storage-enabled"/>
</hbox>
<separator class="thin"/>
<grid id="storage-settings">
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row>
<label value="URL:"/>
<hbox>
<label value="https://"/>
<textbox id="storage-url" flex="1"
preference="pref-storage-url"
onkeypress="if (Zotero.isMac &amp;&amp; event.keyCode == 13) { this.blur(); verifyStorageServer(); }"
onsynctopreference="unverifyStorageServer();"
onchange="this.value = this.value.replace(/(^https?:\/\/|\/zotero\/?$|\/$)/g, '')"/>
<label value="/zotero/"/>
</hbox>
</row>
<row>
<label value="Username:"/>
<hbox>
<textbox id="storage-username"
preference="pref-storage-username"
onkeypress="if (Zotero.isMac &amp;&amp; event.keyCode == 13) { this.blur(); setTimeout('verifyStorageServer();', 1); }"
onsynctopreference="unverifyStorageServer();"
onchange="var pass = document.getElementById('storage-password'); if (pass.value) { Zotero.Sync.Storage.password = pass.value; }"/>
</hbox>
</row>
<row>
<label value="Password:"/>
<hbox>
<textbox id="storage-password" flex="0" type="password"
onkeypress="if (Zotero.isMac &amp;&amp; event.keyCode == 13) { this.blur(); setTimeout('verifyStorageServer();', 1); }"
oninput="unverifyStorageServer()"
onchange="Zotero.Sync.Storage.password = this.value"/>
</hbox>
</row>
<row>
<box/>
<hbox>
<button id="storage-verify" label="Verify Server"
oncommand="verifyStorageServer()"/>
<button id="storage-abort" label="Stop" hidden="true"/>
<progressmeter id="storage-progress" hidden="true"
mode="undetermined"/>
</hbox>
</row>
</rows>
</grid>
</groupbox>
</prefpane> </prefpane>
@ -282,8 +367,12 @@ To add a new preference:
<groupbox> <groupbox>
<caption label="&zotero.preferences.citationOptions.caption;"/> <caption label="&zotero.preferences.citationOptions.caption;"/>
<checkbox label="&zotero.preferences.export.citePaperJournalArticleURL;" preference="pref-export-citePaperJournalArticleURL"/> <checkbox label="&zotero.preferences.export.citePaperJournalArticleURL;" preference="pref-export-citePaperJournalArticleURL"/>
<label id="export-citePaperJournalArticleURL">&zotero.preferences.export.citePaperJournalArticleURL.description;</label> <!-- This doesn't wrap without an explicit wrap, for some reason -->
<label id="export-citePaperJournalArticleURL" width="45em">
&zotero.preferences.export.citePaperJournalArticleURL.description;
</label>
</groupbox> </groupbox>
<groupbox> <groupbox>
@ -511,9 +600,8 @@ To add a new preference:
</groupbox> </groupbox>
</prefpane> </prefpane>
<!-- These mess up the prefwindow (more) if they come before the prefpanes
<!-- These mess up the prefwindow if they come before the prefpanes https://bugzilla.mozilla.org/show_bug.cgi?id=296418 -->
https://bugzilla.mozilla.org/show_bug.cgi?id=296418 -->
<script src="chrome://zotero/content/include.js"/> <script src="chrome://zotero/content/include.js"/>
<script src="preferences.js"/> <script src="preferences.js"/>
</prefwindow> </prefwindow>

View file

@ -943,13 +943,62 @@ Zotero.Attachments = new function(){
function getPath(file, linkMode) { function getPath(file, linkMode) {
if (linkMode == self.LINK_MODE_IMPORTED_URL || if (linkMode == self.LINK_MODE_IMPORTED_URL ||
linkMode == self.LINK_MODE_IMPORTED_FILE) { linkMode == self.LINK_MODE_IMPORTED_FILE) {
return 'storage:' + file.leafName; file.QueryInterface(Components.interfaces.nsILocalFile);
var fileName = file.getRelativeDescriptor(file.parent);
return 'storage:' + fileName;
} }
return file.persistentDescriptor; return file.persistentDescriptor;
} }
/**
* @param {Zotero.Item} item
* @param {Boolean} [skipHidden=FALSE] Don't count hidden files
* @return {Integer} Total file size in bytes
*/
this.getTotalFileSize = function (item, skipHidden) {
var funcName = "Zotero.Attachments.getTotalFileSize()";
if (!item.isAttachment()) {
throw ("Item is not an attachment in " + funcName);
}
var linkMode = item.attachmentLinkMode;
switch (linkMode) {
case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
case Zotero.Attachments.LINK_MODE_LINKED_FILE:
break;
default:
throw ("Invalid attachment link mode in " + funcName);
}
var file = item.getFile();
if (!file) {
throw ("File not found in " + funcName);
}
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
return item.fileSize;
}
var parentDir = file.parent;
var files = parentDir.directoryEntries;
var size = 0;
while (files.hasMoreElements()) {
file = files.getNext();
file.QueryInterface(Components.interfaces.nsIFile);
if (skipHidden && file.leafName.indexOf('.') == 0) {
continue;
}
size += file.fileSize;
}
return size;
}
function _getFileNameFromURL(url, mimeType){ function _getFileNameFromURL(url, mimeType){
var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"] var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
.createInstance(Components.interfaces.nsIURL); .createInstance(Components.interfaces.nsIURL);

View file

@ -87,8 +87,9 @@ Zotero.Item.prototype._init = function () {
this._attachmentLinkMode = null; this._attachmentLinkMode = null;
this._attachmentMIMEType = null; this._attachmentMIMEType = null;
this._attachmentCharset = null; this._attachmentCharset;
this._attachmentPath = null; this._attachmentPath = null;
this._attachmentSyncState;
this._relatedItems = false; this._relatedItems = false;
} }
@ -1254,22 +1255,13 @@ Zotero.Item.prototype.save = function() {
// Attachment // Attachment
if (this.isAttachment()) { if (this.isAttachment()) {
var sql = "INSERT INTO itemAttachments (itemID, sourceItemID, linkMode, " var sql = "INSERT INTO itemAttachments (itemID, sourceItemID, linkMode, "
+ "mimeType, charsetID, path) VALUES (?,?,?,?,?,?)"; + "mimeType, charsetID, path, syncState) VALUES (?,?,?,?,?,?,?)";
var parent = this.getSource(); var parent = this.getSource();
var linkMode = this.attachmentLinkMode; var linkMode = this.attachmentLinkMode;
switch (linkMode) {
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
case Zotero.Attachments.LINK_MODE_LINKED_FILE:
case Zotero.Attachments.LINK_MODE_LINKED_URL:
break;
default:
throw ("Invalid attachment link mode " + linkMode + " in Zotero.Item.save()");
}
var mimeType = this.attachmentMIMEType; var mimeType = this.attachmentMIMEType;
var charsetID = this.attachmentCharset; var charsetID = this.attachmentCharset;
var path = this.attachmentPath; var path = this.attachmentPath;
var syncState = this.attachmentSyncState;
var bindParams = [ var bindParams = [
itemID, itemID,
@ -1277,7 +1269,8 @@ Zotero.Item.prototype.save = function() {
{ int: linkMode }, { int: linkMode },
mimeType ? { string: mimeType } : null, mimeType ? { string: mimeType } : null,
charsetID ? { int: charsetID } : null, charsetID ? { int: charsetID } : null,
path ? { string: path } : null path ? { string: path } : null,
syncState ? { int: syncState } : 0
]; ];
Zotero.DB.query(sql, bindParams); Zotero.DB.query(sql, bindParams);
} }
@ -1596,21 +1589,24 @@ Zotero.Item.prototype.save = function() {
// Attachment // Attachment
if (this._changedAttachmentData) { if (this._changedAttachmentData) {
var sql = "REPLACE INTO itemAttachments (itemID, sourceItemID, linkMode, " var sql = "UPDATE itemAttachments SET sourceItemID=?, "
+ "mimeType, charsetID, path) VALUES (?,?,?,?,?,?)"; + "linkMode=?, mimeType=?, charsetID=?, path=?, syncState=? "
+ "WHERE itemID=?";
var parent = this.getSource(); var parent = this.getSource();
var linkMode = this.attachmentLinkMode; var linkMode = this.attachmentLinkMode;
var mimeType = this.attachmentMIMEType; var mimeType = this.attachmentMIMEType;
var charsetID = this.attachmentCharset; var charsetID = this.attachmentCharset;
var path = this.attachmentPath; var path = this.attachmentPath;
var syncState = this.attachmentSyncState;
var bindParams = [ var bindParams = [
this.id,
parent ? parent : null, parent ? parent : null,
{ int: linkMode }, { int: linkMode },
mimeType ? { string: mimeType } : null, mimeType ? { string: mimeType } : null,
charsetID ? { int: charsetID } : null, charsetID ? { int: charsetID } : null,
path ? { string: path } : null path ? { string: path } : null,
syncState ? { int: syncState } : 0,
this.id
]; ];
Zotero.DB.query(sql, bindParams); Zotero.DB.query(sql, bindParams);
} }
@ -2109,7 +2105,7 @@ Zotero.Item.prototype.numAttachments = function() {
* Get an nsILocalFile for the attachment, or false if the associated file * Get an nsILocalFile for the attachment, or false if the associated file
* doesn't exist * doesn't exist
* *
* _row_ is optional itemAttachments row if available to skip query * _row_ is optional itemAttachments row if available to skip queries
* *
* Note: Always returns false for items with LINK_MODE_LINKED_URL, * Note: Always returns false for items with LINK_MODE_LINKED_URL,
* since they have no files -- use getField('url') instead * since they have no files -- use getField('url') instead
@ -2120,12 +2116,10 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
} }
if (!row) { if (!row) {
var sql = "SELECT linkMode, path FROM itemAttachments WHERE itemID=?" var row = {
var row = Zotero.DB.rowQuery(sql, this.id); linkMode: this.attachmentLinkMode,
} path: this.attachmentPath
};
if (!row) {
throw ('Attachment data not found for item ' + this.id + ' in getFile()');
} }
// No associated files for linked URLs // No associated files for linked URLs
@ -2144,7 +2138,7 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
var path = row.path.substr(8); var path = row.path.substr(8);
var file = Zotero.Attachments.getStorageDirectory(this.id); var file = Zotero.Attachments.getStorageDirectory(this.id);
file.QueryInterface(Components.interfaces.nsILocalFile); file.QueryInterface(Components.interfaces.nsILocalFile);
file.append(path); file.setRelativeDescriptor(file, path);
if (!file.exists()) { if (!file.exists()) {
Zotero.debug("Attachment file '" + path + "' not found"); Zotero.debug("Attachment file '" + path + "' not found");
throw ('File not found'); throw ('File not found');
@ -2321,7 +2315,8 @@ Zotero.Item.prototype.__defineSetter__('attachmentLinkMode', function (val) {
break; break;
default: default:
throw ("Invalid attachment link mode '" + val + "' in Zotero.Item.attachmentLinkMode setter"); throw ("Invalid attachment link mode '" + val
+ "' in Zotero.Item.attachmentLinkMode setter");
} }
if (val === this._attachmentLinkMode) { if (val === this._attachmentLinkMode) {
@ -2402,18 +2397,18 @@ Zotero.Item.prototype.__defineGetter__('attachmentCharset', function () {
return undefined; return undefined;
} }
if (this._attachmentCharset !== null) { if (this._attachmentCharset != undefined) {
return this._attachmentCharset; return this._attachmentCharset;
} }
if (!this.id) { if (!this.id) {
return ''; return null;
} }
var sql = "SELECT charsetID FROM itemAttachments WHERE itemID=?"; var sql = "SELECT charsetID FROM itemAttachments WHERE itemID=?";
var charset = Zotero.DB.valueQuery(sql, this.id); var charset = Zotero.DB.valueQuery(sql, this.id);
if (!charset) { if (!charset) {
charset = ''; charset = null;
} }
this._attachmentCharset = charset; this._attachmentCharset = charset;
return charset; return charset;
@ -2425,8 +2420,10 @@ Zotero.Item.prototype.__defineSetter__('attachmentCharset', function (val) {
throw (".attachmentCharset can only be set for attachment items"); throw (".attachmentCharset can only be set for attachment items");
} }
val = Zotero.CharacterSets.getID(val);
if (!val) { if (!val) {
val = ''; val = null;
} }
if (val == this._attachmentCharset) { if (val == this._attachmentCharset) {
@ -2489,6 +2486,90 @@ Zotero.Item.prototype.__defineSetter__('attachmentPath', function (val) {
}); });
Zotero.Item.prototype.__defineGetter__('attachmentSyncState', function () {
if (!this.isAttachment()) {
return undefined;
}
if (this._attachmentSyncState != undefined) {
return this._attachmentSyncState;
}
if (!this.id) {
return undefined;
}
var sql = "SELECT syncState FROM itemAttachments WHERE itemID=?";
var syncState = Zotero.DB.valueQuery(sql, this.id);
this._attachmentSyncState = syncState;
return syncState;
});
Zotero.Item.prototype.__defineSetter__('attachmentSyncState', function (val) {
if (!this.isAttachment()) {
throw ("attachmentSyncState can only be set for attachment items");
}
switch (this.attachmentLinkMode) {
case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
break;
default:
throw ("attachmentSyncState can only be set for snapshots and "
+ "imported files");
}
switch (val) {
case Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD:
case Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD:
case Zotero.Sync.Storage.SYNC_STATE_IN_SYNC:
break;
default:
throw ("Invalid sync state '" + val
+ "' in Zotero.Item.attachmentSyncState setter");
}
if (val == this._attachmentSyncState) {
return;
}
if (!this._changedAttachmentData) {
this._changedAttachmentData = {};
}
this._changedAttachmentData.syncState = true;
this._attachmentSyncState = val;
});
/**
* Modification time of an attachment file
*
* Note: This is the mod time of the file itself, not the last-known mod time
* of the file on the storage server as stored in the database
*
* @return {Number} File modification time as UNIX timestamp
*/
Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', function () {
if (!this.isAttachment()) {
return undefined;
}
if (!this.id) {
return undefined;
}
var file = this.getFile();
if (!file) {
return undefined;
}
return file.lastModifiedTime / 1000;
});
/** /**
* Returns an array of attachment itemIDs that have this item as a source, * Returns an array of attachment itemIDs that have this item as a source,
* or FALSE if none * or FALSE if none
@ -2579,16 +2660,26 @@ Zotero.Item.prototype.addTag = function(name, type) {
Zotero.DB.beginTransaction(); Zotero.DB.beginTransaction();
var existingTypes = Zotero.Tags.getTypes(name); var matchingTags = Zotero.Tags.getIDs(name);
if (existingTypes) { if (matchingTags) {
// If existing automatic and adding identical user, remove automatic var itemTags = this.getTags();
if (type == 0 && existingTypes.indexOf(1) != -1) { for each(var id in matchingTags) {
this.removeTag(Zotero.Tags.getID(name, 1)); if (itemTags.indexOf(id) != -1) {
} var tag = Zotero.Tags.get(id);
else { // If existing automatic and adding identical user,
Zotero.debug('Identical tag already exists -- not adding tag'); // remove automatic
Zotero.DB.commitTransaction(); if (type == 0 && tag.type == 1) {
return false; this.removeTag(id);
break;
}
// If existing user and adding automatic, skip
else if (type == 1 && tag.type == 0) {
Zotero.debug("Identical user tag '" + name
+ "' already exists -- skipping automatic tag");
Zotero.DB.commitTransaction();
return false;
}
}
} }
} }
@ -2601,9 +2692,9 @@ Zotero.Item.prototype.addTag = function(name, type) {
} }
try { try {
this.addTagByID(tagID); var added = this.addTagByID(tagID);
Zotero.DB.commitTransaction(); Zotero.DB.commitTransaction();
return tagID; return added ? tagID : false;
} }
catch (e) { catch (e) {
Zotero.DB.rollbackTransaction(); Zotero.DB.rollbackTransaction();
@ -2641,8 +2732,12 @@ Zotero.Item.prototype.addTagByID = function(tagID) {
throw ('Cannot add invalid tag ' + tagID + ' in Zotero.Item.addTagByID()'); throw ('Cannot add invalid tag ' + tagID + ' in Zotero.Item.addTagByID()');
} }
tag.addItem(this.id); var added = tag.addItem(this.id);
if (!added) {
return false;
}
tag.save(); tag.save();
return true;
} }
Zotero.Item.prototype.hasTag = function(tagID) { Zotero.Item.prototype.hasTag = function(tagID) {

View file

@ -401,6 +401,11 @@ Zotero.Items = new function() {
var sql = "DELETE FROM itemDataValues WHERE valueID NOT IN " var sql = "DELETE FROM itemDataValues WHERE valueID NOT IN "
+ "(SELECT valueID FROM itemData)"; + "(SELECT valueID FROM itemData)";
Zotero.DB.query(sql); Zotero.DB.query(sql);
var ZU = new Zotero.Utilities;
if (Zotero.Sync.Storage.active && ZU.probability(10)) {
Zotero.Sync.Storage.purgeDeletedStorageFiles();
}
} }

View file

@ -1671,6 +1671,14 @@ Zotero.Schema = new function(){
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 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)"); 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)");
}
} }
_updateDBVersion('userdata', toVersion); _updateDBVersion('userdata', toVersion);

File diff suppressed because it is too large Load diff

View file

@ -241,7 +241,7 @@ Zotero.Sync = new function() {
/** /**
* Notifier observer to add deleted objects to syncDeleteLog * Notifier observer to add deleted objects to syncDeleteLog/storageDeleteLog
* plus related methods * plus related methods
*/ */
Zotero.Sync.EventListener = new function () { Zotero.Sync.EventListener = new function () {
@ -313,17 +313,26 @@ Zotero.Sync.EventListener = new function () {
return; return;
} }
var isItem = Zotero.Sync.getObjectTypeName(objectTypeID) == 'item';
var ZU = new Zotero.Utilities; var ZU = new Zotero.Utilities;
Zotero.DB.beginTransaction(); Zotero.DB.beginTransaction();
if (event == 'delete') { if (event == 'delete') {
var sql = "INSERT INTO syncDeleteLog VALUES (?, ?, ?, ?)"; var sql = "INSERT INTO syncDeleteLog VALUES (?, ?, ?, ?)";
var statement = Zotero.DB.getStatement(sql); var syncStatement = Zotero.DB.getStatement(sql);
if (isItem && Zotero.Sync.Storage.active) {
var storageEnabled = true;
var sql = "INSERT INTO storageDeleteLog VALUES (?, ?)";
var storageStatement = Zotero.DB.getStatement(sql);
}
var storageBound = false;
var ts = Zotero.Date.getUnixTimestamp(); var ts = Zotero.Date.getUnixTimestamp();
for(var i=0, len=ids.length; i<len; i++) { for (var i=0, len=ids.length; i<len; i++) {
if (_deleteBlacklist[ids[i]]) { if (_deleteBlacklist[ids[i]]) {
Zotero.debug("Not logging blacklisted '" Zotero.debug("Not logging blacklisted '"
+ type + "' id " + ids[i] + type + "' id " + ids[i]
@ -331,24 +340,51 @@ Zotero.Sync.EventListener = new function () {
continue; continue;
} }
var key = extraData[ids[i]].old.primary.key; var oldItem = extraData[ids[i]].old;
var key = oldItem.primary.key;
statement.bindInt32Parameter(0, objectTypeID); if (!key) {
statement.bindInt32Parameter(1, ids[i]); throw("Key not provided in notifier object in "
statement.bindStringParameter(2, key); + "Zotero.Sync.EventListener.notify()");
statement.bindInt32Parameter(3, ts); }
syncStatement.bindInt32Parameter(0, objectTypeID);
syncStatement.bindInt32Parameter(1, ids[i]);
syncStatement.bindStringParameter(2, key);
syncStatement.bindInt32Parameter(3, ts);
if (storageEnabled &&
oldItem.primary.itemType == 'attachment' &&
[
Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
Zotero.Attachments.LINK_MODE_IMPORTED_URL
].indexOf(oldItem.attachment.linkMode) != -1) {
storageStatement.bindStringParameter(0, key);
storageStatement.bindInt32Parameter(1, ts);
storageBound = true;
}
try { try {
statement.execute(); syncStatement.execute();
if (storageBound) {
storageStatement.execute();
storageBound = false;
}
} }
catch(e) { catch(e) {
statement.reset(); syncStatement.reset();
if (storageEnabled) {
storageStatement.reset();
}
Zotero.DB.rollbackTransaction(); Zotero.DB.rollbackTransaction();
throw(Zotero.DB.getLastErrorString()); throw(Zotero.DB.getLastErrorString());
} }
} }
statement.reset(); syncStatement.reset();
if (storageEnabled) {
storageStatement.reset();
}
} }
Zotero.DB.commitTransaction(); Zotero.DB.commitTransaction();
@ -374,12 +410,154 @@ Zotero.Sync.EventListener = new function () {
} }
Zotero.Sync.Runner = new function () {
this.__defineGetter__("lastSyncError", function () {
return _lastSyncError;
});
this.__defineSetter__("lastSyncError", function (val) {
_lastSyncError = val ? val : '';
});
var _lastSyncError;
var _autoSyncTimer;
var _queue;
var _running;
this.init = function () {
this.EventListener.init();
}
this.sync = function () {
if (_running) {
throw ("Sync already running in Zotero.Sync.Runner.sync()");
}
_queue = [
Zotero.Sync.Storage.sync,
Zotero.Sync.Server.sync,
Zotero.Sync.Storage.sync
];
_running = true;
this.clearSyncTimeout();
this.setSyncIcon('animate');
this.next();
}
this.next = function () {
if (!_queue.length) {
this.setSyncIcon();
_running = false;
return;
}
var func = _queue.shift();
func();
}
this.reset = function () {
_queue = [];
}
this.setSyncTimeout = function () {
// check if server/auto-sync are enabled
var autoSyncTimeout = 15;
Zotero.debug('Setting auto-sync timeout to ' + autoSyncTimeout + ' seconds');
if (_autoSyncTimer) {
_autoSyncTimer.cancel();
}
else {
_autoSyncTimer = Components.classes["@mozilla.org/timer;1"].
createInstance(Components.interfaces.nsITimer);
}
// {} implements nsITimerCallback
_autoSyncTimer.initWithCallback({ notify: function (event, type, ids) {
if (event == 'refresh') {
return;
}
if (Zotero.Sync.Storage.syncInProgress) {
Zotero.debug('Storage sync already in progress -- skipping auto-sync', 4);
return;
}
if (Zotero.Sync.Server.syncInProgress) {
Zotero.debug('Sync already in progress -- skipping auto-sync', 4);
return;
}
Zotero.Sync.Runner.sync();
}}, autoSyncTimeout * 1000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
this.clearSyncTimeout = function () {
if (_autoSyncTimer) {
_autoSyncTimer.cancel();
}
}
this.setSyncIcon = function (status) {
status = status ? status : '';
switch (status) {
case '':
case 'animate':
case 'error':
break;
default:
throw ("Invalid sync icon status '" + status
+ "' in Zotero.Sync.Runner.setSyncIcon()");
}
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow('navigator:browser');
var icon = win.document.getElementById('zotero-tb-sync');
icon.setAttribute('status', status);
switch (status) {
case 'animate':
icon.setAttribute('disabled', true);
break;
default:
icon.setAttribute('disabled', false);
}
}
}
Zotero.Sync.Runner.EventListener = {
init: function () {
Zotero.Notifier.registerObserver(this);
},
notify: function (event, type, ids, extraData) {
// TODO: skip others
if (type == 'refresh') {
return;
}
if (Zotero.Prefs.get('sync.autoSync') && Zotero.Sync.Server.enabled
&& !Zotero.Sync.Server.syncInProgress
&& !Zotero.Sync.Storage.syncInProgress) {
Zotero.Sync.Runner.setSyncTimeout();
}
}
}
/** /**
* Methods for syncing with the Zotero Server * Methods for syncing with the Zotero Server
*/ */
Zotero.Sync.Server = new function () { Zotero.Sync.Server = new function () {
this.init = init;
this.login = login; this.login = login;
this.sync = sync; this.sync = sync;
this.lock = lock; this.lock = lock;
@ -388,14 +566,12 @@ Zotero.Sync.Server = new function () {
this.resetServer = resetServer; this.resetServer = resetServer;
this.resetClient = resetClient; this.resetClient = resetClient;
this.logout = logout; this.logout = logout;
this.setSyncTimeout = setSyncTimeout;
this.clearSyncTimeout = clearSyncTimeout;
this.setSyncIcon = setSyncIcon;
this.__defineGetter__('enabled', function () { this.__defineGetter__('enabled', function () {
// Set auto-sync expiry // Set auto-sync expiry
var expiry = new Date("September 1, 2008 00:00:00"); var expiry = new Date("November 1, 2008 00:00:00");
if (new Date() > expiry) { if (new Date() > expiry) {
Components.utils.reportError("Build has expired -- syncing disabled");
return false; return false;
} }
@ -469,6 +645,7 @@ Zotero.Sync.Server = new function () {
} }
}); });
this.__defineGetter__("syncInProgress", function () _syncInProgress);
this.__defineGetter__("sessionIDComponent", function () { this.__defineGetter__("sessionIDComponent", function () {
return 'sessionid=' + _sessionID; return 'sessionid=' + _sessionID;
}); });
@ -484,12 +661,6 @@ Zotero.Sync.Server = new function () {
this.__defineSetter__("lastLocalSyncTime", function (val) { this.__defineSetter__("lastLocalSyncTime", function (val) {
Zotero.DB.query("REPLACE INTO version VALUES ('lastlocalsync', ?)", { int: val }); Zotero.DB.query("REPLACE INTO version VALUES ('lastlocalsync', ?)", { int: val });
}); });
this.__defineGetter__("lastSyncError", function () {
return _lastSyncError;
});
this.__defineSetter__("lastSyncError", function (val) {
_lastSyncError = val ? val : '';
});
this.nextLocalSyncDate = false; this.nextLocalSyncDate = false;
this.apiVersion = 2; this.apiVersion = 2;
@ -508,13 +679,6 @@ Zotero.Sync.Server = new function () {
var _syncInProgress; var _syncInProgress;
var _sessionID; var _sessionID;
var _sessionLock; var _sessionLock;
var _lastSyncError;
var _autoSyncTimer;
function init() {
this.EventListener.init();
}
function login(callback) { function login(callback) {
@ -572,8 +736,7 @@ Zotero.Sync.Server = new function () {
function sync() { function sync() {
Zotero.Sync.Server.clearSyncTimeout(); Zotero.Sync.Runner.setSyncIcon('animate');
Zotero.Sync.Server.setSyncIcon('animate');
if (_attempts < 0) { if (_attempts < 0) {
_error('Too many attempts in Zotero.Sync.Server.sync()'); _error('Too many attempts in Zotero.Sync.Server.sync()');
@ -594,6 +757,7 @@ Zotero.Sync.Server = new function () {
_error("Sync operation already in progress"); _error("Sync operation already in progress");
} }
Zotero.debug("Beginning server sync");
_syncInProgress = true; _syncInProgress = true;
// Get updated data // Get updated data
@ -682,8 +846,10 @@ Zotero.Sync.Server = new function () {
Zotero.Sync.Server.lastLocalSyncTime = nextLocalSyncTime; Zotero.Sync.Server.lastLocalSyncTime = nextLocalSyncTime;
Zotero.Sync.Server.nextLocalSyncDate = false; Zotero.Sync.Server.nextLocalSyncDate = false;
Zotero.DB.commitTransaction(); Zotero.DB.commitTransaction();
Zotero.Sync.Server.unlock(); Zotero.Sync.Server.unlock(function () {
_syncInProgress = false; _syncInProgress = false;
Zotero.Sync.Runner.next();
});
return; return;
} }
@ -722,8 +888,10 @@ Zotero.Sync.Server = new function () {
//throw('break2'); //throw('break2');
Zotero.DB.commitTransaction(); Zotero.DB.commitTransaction();
Zotero.Sync.Server.unlock(); Zotero.Sync.Server.unlock(function () {
_syncInProgress = false; _syncInProgress = false;
Zotero.Sync.Runner.next();
});
} }
var compress = Zotero.Prefs.get('sync.server.compressData'); var compress = Zotero.Prefs.get('sync.server.compressData');
@ -894,12 +1062,6 @@ Zotero.Sync.Server = new function () {
if (callback) { if (callback) {
callback(); callback();
} }
// Reset sync icon and last error
if (syncInProgress) {
Zotero.Sync.Server.lastSyncError = '';
Zotero.Sync.Server.setSyncIcon();
}
}); });
} }
@ -1001,6 +1163,7 @@ Zotero.Sync.Server = new function () {
Zotero.DB.query(sql); Zotero.DB.query(sql);
Zotero.DB.query("DELETE FROM syncDeleteLog"); Zotero.DB.query("DELETE FROM syncDeleteLog");
Zotero.DB.query("DELETE FROM storageDeleteLog");
sql = "INSERT INTO version VALUES ('syncdeletelog', ?)"; sql = "INSERT INTO version VALUES ('syncdeletelog', ?)";
Zotero.DB.query(sql, Zotero.Date.getUnixTimestamp()); Zotero.DB.query(sql, Zotero.Date.getUnixTimestamp());
@ -1037,61 +1200,6 @@ Zotero.Sync.Server = new function () {
} }
function setSyncTimeout() {
// check if server/auto-sync are enabled
var autoSyncTimeout = 15;
Zotero.debug('Setting auto-sync timeout to ' + autoSyncTimeout + ' seconds');
if (_autoSyncTimer) {
_autoSyncTimer.cancel();
}
else {
_autoSyncTimer = Components.classes["@mozilla.org/timer;1"].
createInstance(Components.interfaces.nsITimer);
}
// {} implements nsITimerCallback
_autoSyncTimer.initWithCallback({ notify: function (event, type, ids) {
if (event == 'refresh') {
return;
}
if (_syncInProgress) {
Zotero.debug('Sync already in progress -- skipping auto-sync');
return;
}
Zotero.Sync.Server.sync();
}}, autoSyncTimeout * 1000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
function clearSyncTimeout() {
if (_autoSyncTimer) {
_autoSyncTimer.cancel();
}
}
function setSyncIcon(status) {
status = status ? status : '';
switch (status) {
case '':
case 'animate':
case 'error':
break;
default:
throw ("Invalid sync icon status '" + status + "' in Zotero.Sync.Server.setSyncIcon()");
}
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow('navigator:browser');
win.document.getElementById('zotero-tb-sync').setAttribute('status', status);
}
function _checkResponse(xmlhttp) { function _checkResponse(xmlhttp) {
if (!xmlhttp.responseXML || if (!xmlhttp.responseXML ||
!xmlhttp.responseXML.childNodes[0] || !xmlhttp.responseXML.childNodes[0] ||
@ -1130,14 +1238,15 @@ Zotero.Sync.Server = new function () {
Zotero.Sync.Server.unlock() Zotero.Sync.Server.unlock()
} }
Zotero.Sync.Server.setSyncIcon('error'); Zotero.Sync.Runner.setSyncIcon('error');
if (e.name) { if (e.name) {
Zotero.Sync.Server.lastSyncError = e.name; Zotero.Sync.Runner.lastSyncError = e.name;
} }
else { else {
Zotero.Sync.Server.lastSyncError = e; Zotero.Sync.Runner.lastSyncError = e;
} }
Zotero.debug(e, 1);
Zotero.Sync.Runner.reset();
throw(e); throw(e);
} }
} }
@ -1182,26 +1291,6 @@ Zotero.BufferedInputListener.prototype = {
} }
// TODO: use prototype
Zotero.Sync.Server.EventListener = {
init: function () {
Zotero.Notifier.registerObserver(this);
},
notify: function (event, type, ids, extraData) {
// TODO: skip others
if (type == 'refresh') {
return;
}
if (Zotero.Prefs.get('sync.server.autoSync') && Zotero.Sync.Server.enabled) {
Zotero.Sync.Server.setSyncTimeout();
}
}
}
Zotero.Sync.Server.Data = new function() { Zotero.Sync.Server.Data = new function() {
this.processUpdatedXML = processUpdatedXML; this.processUpdatedXML = processUpdatedXML;
this.buildUploadXML = buildUploadXML; this.buildUploadXML = buildUploadXML;
@ -1299,6 +1388,7 @@ Zotero.Sync.Server.Data = new function() {
var remoteCreatorStore = {}; var remoteCreatorStore = {};
var relatedItemsStore = {}; var relatedItemsStore = {};
var itemStorageModTimes = {};
Zotero.DB.beginTransaction(); Zotero.DB.beginTransaction();
@ -1527,10 +1617,10 @@ Zotero.Sync.Server.Data = new function() {
// Create or overwrite locally // Create or overwrite locally
obj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode, obj); obj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode, obj);
// If a local tag matches the name of a different remote tag,
// delete the local tag and add items linked to it to the
// matching remote tag
if (isNewObject && type == 'tag') { if (isNewObject && type == 'tag') {
// If a local tag matches the name of a different remote tag,
// delete the local tag and add items linked to it to the
// matching remote tag
var tagName = xmlNode.@name.toString(); var tagName = xmlNode.@name.toString();
var tagType = xmlNode.@type.toString() var tagType = xmlNode.@type.toString()
? parseInt(xmlNode.@type) : 0; ? parseInt(xmlNode.@type) : 0;
@ -1562,6 +1652,25 @@ Zotero.Sync.Server.Data = new function() {
// Don't use assigned-but-unsaved ids for new ids // Don't use assigned-but-unsaved ids for new ids
Zotero.ID.skip(types, obj.id); Zotero.ID.skip(types, obj.id);
if (type == 'item' && obj.isAttachment() &&
(obj.attachmentLinkMode ==
Zotero.Attachments.LINK_MODE_IMPORTED_FILE ||
obj.attachmentLinkMode ==
Zotero.Attachments.LINK_MODE_IMPORTED_URL)) {
// Mark new attachments for download
if (isNewObject) {
obj.attachmentSyncState =
Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD;
}
// Set existing attachments mtime update check
else {
var mtime = xmlNode.@storageModTime.toString();
if (mtime) {
itemStorageModTimes[obj.id] = parseInt(mtime);
}
}
}
} }
@ -1738,6 +1847,19 @@ Zotero.Sync.Server.Data = new function() {
Zotero[Types].erase(toDeleteParents); Zotero[Types].erase(toDeleteParents);
Zotero.Sync.EventListener.unignoreDeletions(type, toDeleteParents); Zotero.Sync.EventListener.unignoreDeletions(type, toDeleteParents);
} }
// Check mod times of updated items against stored time to see
// if they've been updated elsewhere and mark for download if so
if (type == 'item') {
var ids = [];
for (var id in itemStorageModTimes) {
ids.push(id);
}
if (ids.length > 0) {
Zotero.Sync.Storage.checkForUpdatedFiles(ids, itemStorageModTimes);
}
}
} }
var xmlstr = Zotero.Sync.Server.Data.buildUploadXML(uploadIDs); var xmlstr = Zotero.Sync.Server.Data.buildUploadXML(uploadIDs);
@ -1888,10 +2010,18 @@ Zotero.Sync.Server.Data = new function() {
if (item.primary.itemType == 'attachment') { if (item.primary.itemType == 'attachment') {
xml.@linkMode = item.attachment.linkMode; xml.@linkMode = item.attachment.linkMode;
xml.@mimeType = item.attachment.mimeType; xml.@mimeType = item.attachment.mimeType;
xml.@charset = item.attachment.charset; var charset = item.attachment.charset;
if (charset) {
xml.@charset = charset;
}
// Don't include paths for links // Include storage sync time and paths for non-links
if (item.attachment.linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) { if (item.attachment.linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
var mtime = Zotero.Sync.Storage.getSyncedModificationTime(item.primary.itemID);
if (mtime) {
xml.@storageModTime = mtime;
}
var path = <path>{item.attachment.path}</path>; var path = <path>{item.attachment.path}</path>;
xml.path += path; xml.path += path;
} }
@ -2018,8 +2148,8 @@ Zotero.Sync.Server.Data = new function() {
// Attachment metadata // Attachment metadata
if (item.isAttachment()) { if (item.isAttachment()) {
item.attachmentLinkMode = parseInt(xmlItem.@linkMode); item.attachmentLinkMode = parseInt(xmlItem.@linkMode);
item.attachmentMIMEType = xmlItem.@mimeType; item.attachmentMIMEType = xmlItem.@mimeType.toString();
item.attachmentCharset = parseInt(xmlItem.@charsetID); item.attachmentCharset = xmlItem.@charset.toString();
if (item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) { if (item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
item.attachmentPath = xmlItem.path.toString(); item.attachmentPath = xmlItem.path.toString();
} }

View file

@ -296,6 +296,31 @@ Zotero.Utilities.prototype.isInt = function(x) {
} }
/**
* Generate a random integer between min and max inclusive
*
* @param {Integer} min
* @param {Integer} max
* @return {Integer}
*/
Zotero.Utilities.prototype.rand = function (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* Return true according to a given probability
*
* @param {Integer} x Will return true every x times on average
* @return {Boolean} On average, TRUE every x times
* the function is called
*/
Zotero.Utilities.prototype.probability = function (x) {
return this.rand(1, x) == this.rand(1, x);
}
/** /**
* Determine the necessary data type for SQLite parameter binding * Determine the necessary data type for SQLite parameter binding
* *
@ -643,9 +668,9 @@ Zotero.Utilities.HTTP = new function() {
this.doGet = doGet; this.doGet = doGet;
this.doPost = doPost; this.doPost = doPost;
this.doHead = doHead; this.doHead = doHead;
this.doOptions = doOptions;
this.browserIsOffline = browserIsOffline; this.browserIsOffline = browserIsOffline;
this.WebDAV = {};
/** /**
* Send an HTTP GET request via XMLHTTPRequest * Send an HTTP GET request via XMLHTTPRequest
@ -665,8 +690,9 @@ Zotero.Utilities.HTTP = new function() {
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(); .createInstance();
// Prevent certificate/authentication dialogs from popping up
var test = xmlhttp.open('GET', url, true); xmlhttp.mozBackgroundRequest = true;
xmlhttp.open('GET', url, true);
xmlhttp.onreadystatechange = function(){ xmlhttp.onreadystatechange = function(){
_stateChange(xmlhttp, onDone, responseCharset); _stateChange(xmlhttp, onDone, responseCharset);
@ -716,7 +742,8 @@ Zotero.Utilities.HTTP = new function() {
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(); .createInstance();
// Prevent certificate/authentication dialogs from popping up
xmlhttp.mozBackgroundRequest = true;
xmlhttp.open('POST', url, true); xmlhttp.open('POST', url, true);
xmlhttp.setRequestHeader("Content-Type", (requestContentType ? requestContentType : "application/x-www-form-urlencoded" )); xmlhttp.setRequestHeader("Content-Type", (requestContentType ? requestContentType : "application/x-www-form-urlencoded" ));
@ -750,8 +777,9 @@ Zotero.Utilities.HTTP = new function() {
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(); .createInstance();
// Prevent certificate/authentication dialogs from popping up
var test = xmlhttp.open('HEAD', url, true); xmlhttp.mozBackgroundRequest = true;
xmlhttp.open('HEAD', url, true);
xmlhttp.onreadystatechange = function(){ xmlhttp.onreadystatechange = function(){
_stateChange(xmlhttp, onDone); _stateChange(xmlhttp, onDone);
@ -776,26 +804,30 @@ Zotero.Utilities.HTTP = new function() {
/** /**
* Send an HTTP OPTIONS request via XMLHTTPRequest * Send an HTTP OPTIONS request via XMLHTTPRequest
* *
* doOptions can be called as: * @param {nsIURI} url
* Zotero.Utilities.HTTP.doOptions(url, body, onDone) * @param {Function} onDone
* * @return {XMLHTTPRequest}
* Returns the XMLHTTPRequest object */
**/ this.doOptions = function (uri, callback) {
function doOptions(url, body, onDone) { // Don't display password in console
Zotero.debug("HTTP OPTIONS "+url); var disp = uri.clone();
if (this.browserIsOffline()){ disp.password = "********";
Zotero.debug("HTTP OPTIONS to " + disp.spec);
if (Zotero.Utilities.HTTP.browserIsOffline()){
return false; return false;
} }
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(); .createInstance();
// Prevent certificate/authentication dialogs from popping up
xmlhttp.mozBackgroundRequest = true;
xmlhttp.open('OPTIONS', uri.spec, true);
xmlhttp.open('OPTIONS', url, true); xmlhttp.onreadystatechange = function() {
_stateChange(xmlhttp, callback);
xmlhttp.onreadystatechange = function(){
_stateChange(xmlhttp, onDone);
}; };
// Temporarily set cookieBehavior to 0 for Firefox 3 // Temporarily set cookieBehavior to 0 for Firefox 3
@ -806,7 +838,7 @@ Zotero.Utilities.HTTP = new function() {
var cookieBehavior = prefService.getIntPref("network.cookie.cookieBehavior"); var cookieBehavior = prefService.getIntPref("network.cookie.cookieBehavior");
prefService.setIntPref("network.cookie.cookieBehavior", 0); prefService.setIntPref("network.cookie.cookieBehavior", 0);
xmlhttp.send(body); xmlhttp.send(null);
} }
finally { finally {
prefService.setIntPref("network.cookie.cookieBehavior", cookieBehavior); prefService.setIntPref("network.cookie.cookieBehavior", cookieBehavior);
@ -816,36 +848,231 @@ Zotero.Utilities.HTTP = new function() {
} }
//
// WebDAV methods
//
/**
* Send a WebDAV PROP* request via XMLHTTPRequest
*
* Returns false if browser is offline
*
* @param {String} method PROPFIND or PROPPATCH
* @param {nsIURI} uri
* @param {String} body XML string
* @param {Function} callback
* @param {Object} requestHeaders e.g. { Depth: 0 }
*/
this.WebDAV.doProp = function (method, uri, body, callback, requestHeaders) {
switch (method) {
case 'PROPFIND':
case 'PROPPATCH':
break;
default:
throw ("Invalid method '" + method
+ "' in Zotero.Utilities.HTTP.doProp");
}
if (requestHeaders && requestHeaders.depth != undefined) {
var depth = requestHeaders.depth;
}
// Don't display password in console
var disp = uri.clone();
disp.password = "********";
var bodyStart = body.substr(0, 1024);
Zotero.debug("HTTP " + method + " "
+ (depth != undefined ? "(depth " + depth + ") " : "")
+ (body.length > 1024 ?
bodyStart + "... (" + body.length + " chars)" : bodyStart)
+ " to " + disp.spec);
if (Zotero.Utilities.HTTP.browserIsOffline()) {
Zotero.debug("Browser is offline", 2);
return false;
}
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance();
// Prevent certificate/authentication dialogs from popping up
xmlhttp.mozBackgroundRequest = true;
xmlhttp.open(method, uri.spec, true);
if (requestHeaders) {
for (var header in requestHeaders) {
xmlhttp.setRequestHeader(header, requestHeaders[header]);
}
}
xmlhttp.setRequestHeader("Content-Type", 'text/xml; charset="utf-8"');
xmlhttp.onreadystatechange = function() {
_stateChange(xmlhttp, callback);
};
xmlhttp.send(body);
return xmlhttp;
}
/**
* Send a WebDAV MKCOL request via XMLHTTPRequest
*
* @param {nsIURI} url
* @param {Function} onDone
* @return {XMLHTTPRequest}
*/
this.WebDAV.doMkCol = function (uri, callback) {
// Don't display password in console
var disp = uri.clone();
disp.password = "********";
Zotero.debug("HTTP MKCOL to " + disp.spec);
if (Zotero.Utilities.HTTP.browserIsOffline()) {
return false;
}
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance();
// Prevent certificate/authentication dialogs from popping up
xmlhttp.mozBackgroundRequest = true;
xmlhttp.open('MKCOL', uri.spec, true);
xmlhttp.onreadystatechange = function() {
_stateChange(xmlhttp, callback);
};
xmlhttp.send(null);
return xmlhttp;
}
/**
* Send a WebDAV PUT request via XMLHTTPRequest
*
* @param {nsIURI} url
* @param {String} body String body to PUT
* @param {Function} onDone
* @return {XMLHTTPRequest}
*/
this.WebDAV.doPut = function (uri, body, callback) {
// Don't display password in console
var disp = uri.clone();
disp.password = "********";
var bodyStart = "'" + body.substr(0, 1024) + "'";
Zotero.debug("HTTP PUT "
+ (body.length > 1024 ?
bodyStart + "... (" + body.length + " chars)" : bodyStart)
+ " to " + disp.spec);
if (Zotero.Utilities.HTTP.browserIsOffline()) {
return false;
}
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance();
// Prevent certificate/authentication dialogs from popping up
xmlhttp.mozBackgroundRequest = true;
xmlhttp.open("PUT", uri.spec, true);
xmlhttp.onreadystatechange = function() {
_stateChange(xmlhttp, callback);
};
xmlhttp.send(body);
return xmlhttp;
}
/**
* Send a WebDAV PUT request via XMLHTTPRequest
*
* @param {nsIURI} url
* @param {Function} onDone
* @return {XMLHTTPRequest}
*/
this.WebDAV.doDelete = function (uri, callback) {
// Don't display password in console
var disp = uri.clone();
disp.password = "********";
Zotero.debug("WebDAV DELETE to " + disp.spec);
if (Zotero.Utilities.HTTP.browserIsOffline()) {
return false;
}
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance();
// Prevent certificate/authentication dialogs from popping up
xmlhttp.mozBackgroundRequest = true;
xmlhttp.open("DELETE", uri.spec, true);
xmlhttp.onreadystatechange = function() {
_stateChange(xmlhttp, callback);
};
xmlhttp.send(null);
return xmlhttp;
}
/**
* Get the Authorization header used by a channel
*
* As of Firefox 3.0.1 subsequent requests to higher-level directories
* seem not to authenticate properly and just return 401s, so this
* can be used to manually include the Authorization header in a request
*
* It can also be used to check whether a request was forced to
* use authentication
*
* @param {nsIChannel} channel
* @return {String|FALSE} Authorization header, or FALSE if none
*/
this.getChannelAuthorization = function (channel) {
try {
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
var authHeader = channel.getRequestHeader("Authorization");
return authHeader;
}
catch (e) {
Zotero.debug(e);
return false;
}
}
function browserIsOffline() { function browserIsOffline() {
return Components.classes["@mozilla.org/network/io-service;1"] return Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService).offline; .getService(Components.interfaces.nsIIOService).offline;
} }
function _stateChange(xmlhttp, onDone, responseCharset){ function _stateChange(xmlhttp, callback, responseCharset, data) {
switch (xmlhttp.readyState){ switch (xmlhttp.readyState){
// Request not yet made // Request not yet made
case 1: case 1:
break; break;
// Called multiple while downloading in progress case 2:
break;
// Called multiple times while downloading in progress
case 3: case 3:
break; break;
// Download complete // Download complete
case 4: case 4:
if(onDone){ if (callback) {
// Override the content charset // Override the content charset
if (responseCharset) { if (responseCharset) {
xmlhttp.channel.contentCharset = responseCharset; xmlhttp.channel.contentCharset = responseCharset;
} }
onDone(xmlhttp); callback(xmlhttp, data);
} }
break; break;
} }
} }
} }
// Downloads and processes documents with processor() // Downloads and processes documents with processor()
@ -952,4 +1179,143 @@ Zotero.Utilities.AutoComplete = new function(){
} }
return false; return false;
} }
} }
/**
* Base64 encode / decode
* From http://www.webtoolkit.info/
*/
Zotero.Utilities.Base64 = {
// private property
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
// public method for encoding
encode : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = this._utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
}
return output;
},
// public method for decoding
decode : function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
output = this._utf8_decode(output);
return output;
},
// private method for UTF-8 encoding
_utf8_encode : function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
},
// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = c1 = c2 = 0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
}
}

View file

@ -41,6 +41,7 @@ var Zotero = new function(){
this.getZoteroDirectory = getZoteroDirectory; this.getZoteroDirectory = getZoteroDirectory;
this.getStorageDirectory = getStorageDirectory; this.getStorageDirectory = getStorageDirectory;
this.getZoteroDatabase = getZoteroDatabase; this.getZoteroDatabase = getZoteroDatabase;
this.getTempDirectory = getTempDirectory;
this.chooseZoteroDirectory = chooseZoteroDirectory; this.chooseZoteroDirectory = chooseZoteroDirectory;
this.debug = debug; this.debug = debug;
this.log = log; this.log = log;
@ -249,6 +250,9 @@ var Zotero = new function(){
if (typeof e == 'string' && e.match('newer than SQL file')) { if (typeof e == 'string' && e.match('newer than SQL file')) {
_startupError = e; _startupError = e;
} }
else {
_startupError = "Database upgrade error";
}
Components.utils.reportError(_startupError); Components.utils.reportError(_startupError);
return false; return false;
} }
@ -265,7 +269,8 @@ var Zotero = new function(){
Zotero.Zeroconf.init(); Zotero.Zeroconf.init();
Zotero.Sync.init(); Zotero.Sync.init();
Zotero.Sync.Server.init(); Zotero.Sync.Runner.init();
Zotero.Sync.Storage.init();
this.initialized = true; this.initialized = true;
@ -357,6 +362,22 @@ var Zotero = new function(){
} }
/**
* @return {nsIFile}
*/
function getTempDirectory() {
var tmp = this.getZoteroDirectory();
tmp.append('tmp');
if (!tmp.exists() || !tmp.isDirectory()) {
if (tmp.exists() && !tmp.isDirectory()) {
tmp.remove(null);
}
tmp.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
}
return tmp;
}
function chooseZoteroDirectory(forceRestartNow, useProfileDir) { function chooseZoteroDirectory(forceRestartNow, useProfileDir) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator); .getService(Components.interfaces.nsIWindowMediator);
@ -1284,6 +1305,66 @@ Zotero.Date = new function(){
} }
/**
* Convert a JS Date object to an ISO 8601 UTC date/time
*
* @param {Date} date JS Date object
* @return {String} ISO 8601 UTC date/time
* e.g. 2008-08-15T20:00:00Z
*/
this.dateToISO = function (date) {
var year = date.getUTCFullYear();
var month = date.getUTCMonth();
var day = date.getUTCDate();
var hours = date.getUTCHours();
var minutes = date.getUTCMinutes();
var seconds = date.getUTCSeconds();
var utils = new Zotero.Utilities();
year = utils.lpad(year, '0', 4);
month = utils.lpad(month + 1, '0', 2);
day = utils.lpad(day, '0', 2);
hours = utils.lpad(hours, '0', 2);
minutes = utils.lpad(minutes, '0', 2);
seconds = utils.lpad(seconds, '0', 2);
return year + '-' + month + '-' + day + 'T'
+ hours + ':' + minutes + ':' + seconds + 'Z';
}
/**
* Convert an ISO 8601formatted UTC date/time to a JS Date
*
* Adapted from http://delete.me.uk/2005/03/iso8601.html (AFL-licensed)
*
* @param {String} isoDate ISO 8601 date
* @return {Date} JS Date
*/
this.isoToDate = function (isoDate) {
var re8601 = /([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?/;
var d = isoDate.match(re8601);
var offset = 0;
var date = new Date(d[1], 0, 1);
if (d[3]) { date.setMonth(d[3] - 1); }
if (d[5]) { date.setDate(d[5]); }
if (d[7]) { date.setHours(d[7]); }
if (d[8]) { date.setMinutes(d[8]); }
if (d[10]) { date.setSeconds(d[10]); }
if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= ((d[15] == '-') ? 1 : -1);
}
offset -= date.getTimezoneOffset();
var time = (Number(date) + (offset * 60 * 1000));
return new Date(time);
}
/* /*
* converts a string to an object containing: * converts a string to an object containing:
* day: integer form of the day * day: integer form of the day
@ -1494,7 +1575,7 @@ Zotero.Date = new function(){
return string; return string;
} }
function strToISO(str){ function strToISO(str) {
var date = Zotero.Date.strToDate(str); var date = Zotero.Date.strToDate(str);
if(date.year) { if(date.year) {

View file

@ -156,8 +156,12 @@
<!ENTITY zotero.integration.references.label "References in Bibliography"> <!ENTITY zotero.integration.references.label "References in Bibliography">
<!ENTITY zotero.sync.storage.progress "Progress:">
<!ENTITY zotero.sync.storage.downloads "Downloads:">
<!ENTITY zotero.sync.storage.uploads "Uploads:">
<!ENTITY zotero.proxy.recognized.title "Proxy Recognized"> <!ENTITY zotero.proxy.recognized.title "Proxy Recognized">
<!ENTITY zotero.proxy.recognized.warning "Only add proxies linked from your library, school, or corporate website"> <!ENTITY zotero.proxy.recognized.warning "Only add proxies linked from your library, school, or corporate website">
<!ENTITY zotero.proxy.recognized.warning.secondary "Adding other proxies allows malicious sites to masquerade as sites you trust."> <!ENTITY zotero.proxy.recognized.warning.secondary "Adding other proxies allows malicious sites to masquerade as sites you trust.">
<!ENTITY zotero.proxy.recognized.disable.label "Do not automatically redirect requests through previously recognized proxies"> <!ENTITY zotero.proxy.recognized.disable.label "Do not automatically redirect requests through previously recognized proxies">
<!ENTITY zotero.proxy.recognized.ignore.label "Ignore"> <!ENTITY zotero.proxy.recognized.ignore.label "Ignore">

View file

@ -501,6 +501,9 @@ styles.installed = The style "%S" was installed successfully.
styles.installError = %S does not appear to be a valid CSL file. styles.installError = %S does not appear to be a valid CSL file.
styles.deleteStyle = Are you sure you want to delete the style "%1$S"? styles.deleteStyle = Are you sure you want to delete the style "%1$S"?
sync.storage.kbRemaining = %SKB remaining
sync.storage.none = None
proxies.multiSite = Multi-Site proxies.multiSite = Multi-Site
proxies.error = Information Validation Error proxies.error = Information Validation Error
proxies.error.scheme.noHTTP = Valid proxy schemes must start with "http://" or "https://" proxies.error.scheme.noHTTP = Valid proxy schemes must start with "http://" or "https://"
@ -513,4 +516,4 @@ proxies.enableTransparentWarning.title = Warning
proxies.enableTransparentWarning.description = Please ensure that the proxies listed below belong to a library, school, or other institution with which you are affiliated. A malicious proxy could pose a security risk. proxies.enableTransparentWarning.description = Please ensure that the proxies listed below belong to a library, school, or other institution with which you are affiliated. A malicious proxy could pose a security risk.
recognizePDF.couldNotRecognize.title = Could Not Retrieve Metada recognizePDF.couldNotRecognize.title = Could Not Retrieve Metada
recognizePDF.couldNotRecognize.message = Zotero could not retrieve metadata for "%1$S". recognizePDF.couldNotRecognize.message = Zotero could not retrieve metadata for "%1$S".

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

View file

@ -191,11 +191,28 @@
list-style-image: url('chrome://zotero/skin/toolbar-advanced-search.png'); list-style-image: url('chrome://zotero/skin/toolbar-advanced-search.png');
} }
#zotero-tb-syncProgress
{
min-width: 50px;
width: 50px;
height: 10px;
}
#zotero-tb-syncProgress-tooltip row label:first-child
{
text-align: right;
font-weight: bold;
}
#zotero-tb-storage-sync
{
list-style-image: url(chrome://zotero/skin/drive_network.png);
}
#zotero-tb-sync { #zotero-tb-sync {
margin-top: -2px; list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png);
margin-left: -2px; margin-left: -2px;
margin-right: -2px; margin-right: -2px;
list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png);
} }
#zotero-tb-sync[status=animate] { #zotero-tb-sync[status=animate] {

View file

@ -3,14 +3,8 @@ prefwindow .chromeclass-toolbar
display: -moz-box !important; /* Ignore toolbar collapse button on OS X */ display: -moz-box !important; /* Ignore toolbar collapse button on OS X */
} }
/* Prevent bugs in automatic prefpane sizing in Firefox 2.0
From http://forums.mozillazine.org/viewtopic.php?p=2883233&sid=e1285f81ea9c824363802ea5ca96c9b2
*/
prefwindow { prefwindow {
width: 45em; min-width: 600px;
}
prefwindow > prefpane > vbox.content-box {
height: 42em;
} }
radio[pane] radio[pane]
@ -75,6 +69,50 @@ grid row hbox:first-child
} }
/*
* Sync pane
*/
#zotero-prefpane-sync row, #zotero-prefpane-sync row hbox
{
-moz-box-align: center;
}
#zotero-prefpane-sync row label:first-child
{
text-align: right;
}
#zotero-prefpane-sync row hbox
{
margin-left: 4px;
}
#zotero-prefpane-sync row hbox label:first-child
{
margin-left: 0;
margin-right: 0;
}
#zotero-prefpane-sync row hbox textbox
{
margin-left: 3px;
margin-right: 3px;
}
#zotero-prefpane-sync row hbox label:last-child
{
margin-left: 0;
margin-right: 10px;
}
#storage-settings
{
margin-left: 10px;
margin-right: 5px;
}
#storage-verify, #storage-abort, #storage-clean
{
margin-left: 0;
min-width: 8em;
}
/* /*
* Search pane * Search pane
*/ */

View file

@ -51,6 +51,7 @@ var xpcomFiles = [
'schema', 'schema',
'search', 'search',
'sync', 'sync',
'storage',
'timeline', 'timeline',
'translate', 'translate',
'utilities', 'utilities',

View file

@ -82,10 +82,17 @@ pref("extensions.zotero.zeroconf.server.enabled", false);
// Annotation settings // Annotation settings
pref("extensions.zotero.annotations.warnOnClose", true); pref("extensions.zotero.annotations.warnOnClose", true);
// Server // Sync
pref("extensions.zotero.sync.server.autoSync", true); pref("extensions.zotero.sync.autoSync", true);
pref("extensions.zotero.sync.server.username", ''); pref("extensions.zotero.sync.server.username", '');
pref("extensions.zotero.sync.server.compressData", true); pref("extensions.zotero.sync.server.compressData", true);
pref("extensions.zotero.sync.storage.enabled", false);
pref("extensions.zotero.sync.storage.verified", false);
pref("extensions.zotero.sync.storage.url", '');
pref("extensions.zotero.sync.storage.username", '');
pref("extensions.zotero.sync.storage.maxDownloads", 4);
pref("extensions.zotero.sync.storage.maxUploads", 4);
pref("extensions.zotero.sync.storage.deleteDelayDays", 30);
// Proxy // Proxy
pref("extensions.zotero.proxies.autoRecognize", true); pref("extensions.zotero.proxies.autoRecognize", true);

View file

@ -1,4 +1,4 @@
-- 39 -- 40
-- This file creates tables containing user-specific data -- any changes made -- This file creates tables containing user-specific data -- any changes made
-- here must be mirrored in transition steps in schema.js::_migrateSchema() -- here must be mirrored in transition steps in schema.js::_migrateSchema()
@ -62,11 +62,14 @@ CREATE TABLE itemAttachments (
charsetID INT, charsetID INT,
path TEXT, path TEXT,
originalPath TEXT, originalPath TEXT,
syncState INT DEFAULT 0,
storageModTime INT,
FOREIGN KEY (itemID) REFERENCES items(itemID), FOREIGN KEY (itemID) REFERENCES items(itemID),
FOREIGN KEY (sourceItemID) REFERENCES items(sourceItemID) FOREIGN KEY (sourceItemID) REFERENCES items(sourceItemID)
); );
CREATE INDEX itemAttachments_sourceItemID ON itemAttachments(sourceItemID); CREATE INDEX itemAttachments_sourceItemID ON itemAttachments(sourceItemID);
CREATE INDEX itemAttachments_mimeType ON itemAttachments(mimeType); CREATE INDEX itemAttachments_mimeType ON itemAttachments(mimeType);
CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState);
-- Individual entries for each tag -- Individual entries for each tag
CREATE TABLE tags ( CREATE TABLE tags (
@ -202,6 +205,12 @@ CREATE TABLE syncDeleteLog (
); );
CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp); CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp);
CREATE TABLE storageDeleteLog (
key TEXT PRIMARY KEY,
timestamp INT NOT NULL
);
CREATE INDEX storageDeleteLog_timestamp ON storageDeleteLog(timestamp);
CREATE TABLE translators ( CREATE TABLE translators (
translatorID TEXT PRIMARY KEY, translatorID TEXT PRIMARY KEY,
minVersion TEXT, minVersion TEXT,