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:
parent
227b4cbfcd
commit
a8bb8dae40
19 changed files with 3516 additions and 246 deletions
|
@ -133,6 +133,10 @@
|
|||
<menuitem label="Clear Server Data" oncommand="Zotero.Sync.Server.clear()"/>
|
||||
<menuitem label="Reset Server Lock" oncommand="Zotero.Sync.Server.resetServer()"/>
|
||||
<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"/>
|
||||
<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')"/>
|
||||
|
@ -305,16 +309,49 @@
|
|||
<splitter id="zotero-view-splitter" resizebefore="closest" resizeafter="closest"/>
|
||||
|
||||
<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"
|
||||
oncommand="Zotero.Sync.Server.sync()">
|
||||
oncommand="Zotero.Sync.Runner.sync()">
|
||||
<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')"
|
||||
noautohide="true"><!-- localize -->
|
||||
noautohide="true"><!-- TODO: localize -->
|
||||
<label value="Sync with Zotero Server"/>
|
||||
<label id="zotero-last-sync-time"/>
|
||||
</tooltip>
|
||||
</toolbarbutton>
|
||||
<!--
|
||||
<toolbarbutton id="zotero-tb-storage-sync"
|
||||
tooltiptext="Sync with Storage Server"
|
||||
oncommand="Zotero.Sync.Storage.sync()"/>
|
||||
-->
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="zotero-tb-fullscreen" tooltiptext="&zotero.toolbar.fullscreen.tooltip;" oncommand="ZoteroPane.fullScreen();"/>
|
||||
<toolbarbutton class="tabs-closebutton" oncommand="ZoteroPane.toggleDisplay()"/>
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -156,16 +156,23 @@ To add a new preference:
|
|||
<!-- localize -->
|
||||
<prefpane id="zotero-prefpane-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">
|
||||
<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>
|
||||
|
||||
<groupbox>
|
||||
<caption label="Zotero Sync Server"/>
|
||||
|
||||
<grid>
|
||||
<columns>
|
||||
<columns/>
|
||||
<columns/>
|
||||
<column/>
|
||||
<column/>
|
||||
</columns>
|
||||
|
||||
<rows>
|
||||
|
@ -179,8 +186,86 @@ To add a new preference:
|
|||
<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 && 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 && 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 && 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>
|
||||
|
||||
|
||||
|
@ -282,8 +367,12 @@ To add a new preference:
|
|||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.citationOptions.caption;"/>
|
||||
|
||||
<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>
|
||||
|
@ -511,8 +600,7 @@ To add a new preference:
|
|||
</groupbox>
|
||||
</prefpane>
|
||||
|
||||
|
||||
<!-- These mess up the prefwindow if they come before the prefpanes
|
||||
<!-- These mess up the prefwindow (more) if they come before the prefpanes
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=296418 -->
|
||||
<script src="chrome://zotero/content/include.js"/>
|
||||
<script src="preferences.js"/>
|
||||
|
|
|
@ -943,13 +943,62 @@ Zotero.Attachments = new function(){
|
|||
function getPath(file, linkMode) {
|
||||
if (linkMode == self.LINK_MODE_IMPORTED_URL ||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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){
|
||||
var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
|
||||
.createInstance(Components.interfaces.nsIURL);
|
||||
|
|
|
@ -87,8 +87,9 @@ Zotero.Item.prototype._init = function () {
|
|||
|
||||
this._attachmentLinkMode = null;
|
||||
this._attachmentMIMEType = null;
|
||||
this._attachmentCharset = null;
|
||||
this._attachmentCharset;
|
||||
this._attachmentPath = null;
|
||||
this._attachmentSyncState;
|
||||
|
||||
this._relatedItems = false;
|
||||
}
|
||||
|
@ -1254,22 +1255,13 @@ Zotero.Item.prototype.save = function() {
|
|||
// Attachment
|
||||
if (this.isAttachment()) {
|
||||
var sql = "INSERT INTO itemAttachments (itemID, sourceItemID, linkMode, "
|
||||
+ "mimeType, charsetID, path) VALUES (?,?,?,?,?,?)";
|
||||
+ "mimeType, charsetID, path, syncState) VALUES (?,?,?,?,?,?,?)";
|
||||
var parent = this.getSource();
|
||||
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 charsetID = this.attachmentCharset;
|
||||
var path = this.attachmentPath;
|
||||
var syncState = this.attachmentSyncState;
|
||||
|
||||
var bindParams = [
|
||||
itemID,
|
||||
|
@ -1277,7 +1269,8 @@ Zotero.Item.prototype.save = function() {
|
|||
{ int: linkMode },
|
||||
mimeType ? { string: mimeType } : null,
|
||||
charsetID ? { int: charsetID } : null,
|
||||
path ? { string: path } : null
|
||||
path ? { string: path } : null,
|
||||
syncState ? { int: syncState } : 0
|
||||
];
|
||||
Zotero.DB.query(sql, bindParams);
|
||||
}
|
||||
|
@ -1596,21 +1589,24 @@ Zotero.Item.prototype.save = function() {
|
|||
|
||||
// Attachment
|
||||
if (this._changedAttachmentData) {
|
||||
var sql = "REPLACE INTO itemAttachments (itemID, sourceItemID, linkMode, "
|
||||
+ "mimeType, charsetID, path) VALUES (?,?,?,?,?,?)";
|
||||
var sql = "UPDATE itemAttachments SET sourceItemID=?, "
|
||||
+ "linkMode=?, mimeType=?, charsetID=?, path=?, syncState=? "
|
||||
+ "WHERE itemID=?";
|
||||
var parent = this.getSource();
|
||||
var linkMode = this.attachmentLinkMode;
|
||||
var mimeType = this.attachmentMIMEType;
|
||||
var charsetID = this.attachmentCharset;
|
||||
var path = this.attachmentPath;
|
||||
var syncState = this.attachmentSyncState;
|
||||
|
||||
var bindParams = [
|
||||
this.id,
|
||||
parent ? parent : null,
|
||||
{ int: linkMode },
|
||||
mimeType ? { string: mimeType } : null,
|
||||
charsetID ? { int: charsetID } : null,
|
||||
path ? { string: path } : null
|
||||
path ? { string: path } : null,
|
||||
syncState ? { int: syncState } : 0,
|
||||
this.id
|
||||
];
|
||||
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
|
||||
* 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,
|
||||
* since they have no files -- use getField('url') instead
|
||||
|
@ -2120,12 +2116,10 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
|
|||
}
|
||||
|
||||
if (!row) {
|
||||
var sql = "SELECT linkMode, path FROM itemAttachments WHERE itemID=?"
|
||||
var row = Zotero.DB.rowQuery(sql, this.id);
|
||||
}
|
||||
|
||||
if (!row) {
|
||||
throw ('Attachment data not found for item ' + this.id + ' in getFile()');
|
||||
var row = {
|
||||
linkMode: this.attachmentLinkMode,
|
||||
path: this.attachmentPath
|
||||
};
|
||||
}
|
||||
|
||||
// No associated files for linked URLs
|
||||
|
@ -2144,7 +2138,7 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
|
|||
var path = row.path.substr(8);
|
||||
var file = Zotero.Attachments.getStorageDirectory(this.id);
|
||||
file.QueryInterface(Components.interfaces.nsILocalFile);
|
||||
file.append(path);
|
||||
file.setRelativeDescriptor(file, path);
|
||||
if (!file.exists()) {
|
||||
Zotero.debug("Attachment file '" + path + "' not found");
|
||||
throw ('File not found');
|
||||
|
@ -2321,7 +2315,8 @@ Zotero.Item.prototype.__defineSetter__('attachmentLinkMode', function (val) {
|
|||
break;
|
||||
|
||||
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) {
|
||||
|
@ -2402,18 +2397,18 @@ Zotero.Item.prototype.__defineGetter__('attachmentCharset', function () {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
if (this._attachmentCharset !== null) {
|
||||
if (this._attachmentCharset != undefined) {
|
||||
return this._attachmentCharset;
|
||||
}
|
||||
|
||||
if (!this.id) {
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
var sql = "SELECT charsetID FROM itemAttachments WHERE itemID=?";
|
||||
var charset = Zotero.DB.valueQuery(sql, this.id);
|
||||
if (!charset) {
|
||||
charset = '';
|
||||
charset = null;
|
||||
}
|
||||
this._attachmentCharset = charset;
|
||||
return charset;
|
||||
|
@ -2425,8 +2420,10 @@ Zotero.Item.prototype.__defineSetter__('attachmentCharset', function (val) {
|
|||
throw (".attachmentCharset can only be set for attachment items");
|
||||
}
|
||||
|
||||
val = Zotero.CharacterSets.getID(val);
|
||||
|
||||
if (!val) {
|
||||
val = '';
|
||||
val = null;
|
||||
}
|
||||
|
||||
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,
|
||||
* or FALSE if none
|
||||
|
@ -2579,18 +2660,28 @@ Zotero.Item.prototype.addTag = function(name, type) {
|
|||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var existingTypes = Zotero.Tags.getTypes(name);
|
||||
if (existingTypes) {
|
||||
// If existing automatic and adding identical user, remove automatic
|
||||
if (type == 0 && existingTypes.indexOf(1) != -1) {
|
||||
this.removeTag(Zotero.Tags.getID(name, 1));
|
||||
var matchingTags = Zotero.Tags.getIDs(name);
|
||||
if (matchingTags) {
|
||||
var itemTags = this.getTags();
|
||||
for each(var id in matchingTags) {
|
||||
if (itemTags.indexOf(id) != -1) {
|
||||
var tag = Zotero.Tags.get(id);
|
||||
// If existing automatic and adding identical user,
|
||||
// remove automatic
|
||||
if (type == 0 && tag.type == 1) {
|
||||
this.removeTag(id);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
Zotero.debug('Identical tag already exists -- not adding tag');
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tagID = Zotero.Tags.getID(name, type);
|
||||
if (!tagID) {
|
||||
|
@ -2601,9 +2692,9 @@ Zotero.Item.prototype.addTag = function(name, type) {
|
|||
}
|
||||
|
||||
try {
|
||||
this.addTagByID(tagID);
|
||||
var added = this.addTagByID(tagID);
|
||||
Zotero.DB.commitTransaction();
|
||||
return tagID;
|
||||
return added ? tagID : false;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
|
@ -2641,8 +2732,12 @@ Zotero.Item.prototype.addTagByID = function(tagID) {
|
|||
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();
|
||||
return true;
|
||||
}
|
||||
|
||||
Zotero.Item.prototype.hasTag = function(tagID) {
|
||||
|
|
|
@ -401,6 +401,11 @@ Zotero.Items = new function() {
|
|||
var sql = "DELETE FROM itemDataValues WHERE valueID NOT IN "
|
||||
+ "(SELECT valueID FROM itemData)";
|
||||
Zotero.DB.query(sql);
|
||||
|
||||
var ZU = new Zotero.Utilities;
|
||||
if (Zotero.Sync.Storage.active && ZU.probability(10)) {
|
||||
Zotero.Sync.Storage.purgeDeletedStorageFiles();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 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);
|
||||
|
|
2168
chrome/content/zotero/xpcom/storage.js
Normal file
2168
chrome/content/zotero/xpcom/storage.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
*/
|
||||
Zotero.Sync.EventListener = new function () {
|
||||
|
@ -313,13 +313,22 @@ Zotero.Sync.EventListener = new function () {
|
|||
return;
|
||||
}
|
||||
|
||||
var isItem = Zotero.Sync.getObjectTypeName(objectTypeID) == 'item';
|
||||
|
||||
var ZU = new Zotero.Utilities;
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
if (event == 'delete') {
|
||||
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();
|
||||
|
||||
|
@ -331,24 +340,51 @@ Zotero.Sync.EventListener = new function () {
|
|||
continue;
|
||||
}
|
||||
|
||||
var key = extraData[ids[i]].old.primary.key;
|
||||
var oldItem = extraData[ids[i]].old;
|
||||
var key = oldItem.primary.key;
|
||||
|
||||
statement.bindInt32Parameter(0, objectTypeID);
|
||||
statement.bindInt32Parameter(1, ids[i]);
|
||||
statement.bindStringParameter(2, key);
|
||||
statement.bindInt32Parameter(3, ts);
|
||||
if (!key) {
|
||||
throw("Key not provided in notifier object in "
|
||||
+ "Zotero.Sync.EventListener.notify()");
|
||||
}
|
||||
|
||||
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 {
|
||||
statement.execute();
|
||||
syncStatement.execute();
|
||||
if (storageBound) {
|
||||
storageStatement.execute();
|
||||
storageBound = false;
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
statement.reset();
|
||||
syncStatement.reset();
|
||||
if (storageEnabled) {
|
||||
storageStatement.reset();
|
||||
}
|
||||
Zotero.DB.rollbackTransaction();
|
||||
throw(Zotero.DB.getLastErrorString());
|
||||
}
|
||||
}
|
||||
|
||||
statement.reset();
|
||||
syncStatement.reset();
|
||||
if (storageEnabled) {
|
||||
storageStatement.reset();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
Zotero.Sync.Server = new function () {
|
||||
this.init = init;
|
||||
this.login = login;
|
||||
this.sync = sync;
|
||||
this.lock = lock;
|
||||
|
@ -388,14 +566,12 @@ Zotero.Sync.Server = new function () {
|
|||
this.resetServer = resetServer;
|
||||
this.resetClient = resetClient;
|
||||
this.logout = logout;
|
||||
this.setSyncTimeout = setSyncTimeout;
|
||||
this.clearSyncTimeout = clearSyncTimeout;
|
||||
this.setSyncIcon = setSyncIcon;
|
||||
|
||||
this.__defineGetter__('enabled', function () {
|
||||
// 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) {
|
||||
Components.utils.reportError("Build has expired -- syncing disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -469,6 +645,7 @@ Zotero.Sync.Server = new function () {
|
|||
}
|
||||
});
|
||||
|
||||
this.__defineGetter__("syncInProgress", function () _syncInProgress);
|
||||
this.__defineGetter__("sessionIDComponent", function () {
|
||||
return 'sessionid=' + _sessionID;
|
||||
});
|
||||
|
@ -484,12 +661,6 @@ Zotero.Sync.Server = new function () {
|
|||
this.__defineSetter__("lastLocalSyncTime", function (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.apiVersion = 2;
|
||||
|
@ -508,13 +679,6 @@ Zotero.Sync.Server = new function () {
|
|||
var _syncInProgress;
|
||||
var _sessionID;
|
||||
var _sessionLock;
|
||||
var _lastSyncError;
|
||||
var _autoSyncTimer;
|
||||
|
||||
|
||||
function init() {
|
||||
this.EventListener.init();
|
||||
}
|
||||
|
||||
|
||||
function login(callback) {
|
||||
|
@ -572,8 +736,7 @@ Zotero.Sync.Server = new function () {
|
|||
|
||||
|
||||
function sync() {
|
||||
Zotero.Sync.Server.clearSyncTimeout();
|
||||
Zotero.Sync.Server.setSyncIcon('animate');
|
||||
Zotero.Sync.Runner.setSyncIcon('animate');
|
||||
|
||||
if (_attempts < 0) {
|
||||
_error('Too many attempts in Zotero.Sync.Server.sync()');
|
||||
|
@ -594,6 +757,7 @@ Zotero.Sync.Server = new function () {
|
|||
_error("Sync operation already in progress");
|
||||
}
|
||||
|
||||
Zotero.debug("Beginning server sync");
|
||||
_syncInProgress = true;
|
||||
|
||||
// Get updated data
|
||||
|
@ -682,8 +846,10 @@ Zotero.Sync.Server = new function () {
|
|||
Zotero.Sync.Server.lastLocalSyncTime = nextLocalSyncTime;
|
||||
Zotero.Sync.Server.nextLocalSyncDate = false;
|
||||
Zotero.DB.commitTransaction();
|
||||
Zotero.Sync.Server.unlock();
|
||||
Zotero.Sync.Server.unlock(function () {
|
||||
_syncInProgress = false;
|
||||
Zotero.Sync.Runner.next();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -722,8 +888,10 @@ Zotero.Sync.Server = new function () {
|
|||
//throw('break2');
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
Zotero.Sync.Server.unlock();
|
||||
Zotero.Sync.Server.unlock(function () {
|
||||
_syncInProgress = false;
|
||||
Zotero.Sync.Runner.next();
|
||||
});
|
||||
}
|
||||
|
||||
var compress = Zotero.Prefs.get('sync.server.compressData');
|
||||
|
@ -894,12 +1062,6 @@ Zotero.Sync.Server = new function () {
|
|||
if (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("DELETE FROM syncDeleteLog");
|
||||
Zotero.DB.query("DELETE FROM storageDeleteLog");
|
||||
|
||||
sql = "INSERT INTO version VALUES ('syncdeletelog', ?)";
|
||||
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) {
|
||||
if (!xmlhttp.responseXML ||
|
||||
!xmlhttp.responseXML.childNodes[0] ||
|
||||
|
@ -1130,14 +1238,15 @@ Zotero.Sync.Server = new function () {
|
|||
Zotero.Sync.Server.unlock()
|
||||
}
|
||||
|
||||
Zotero.Sync.Server.setSyncIcon('error');
|
||||
|
||||
Zotero.Sync.Runner.setSyncIcon('error');
|
||||
if (e.name) {
|
||||
Zotero.Sync.Server.lastSyncError = e.name;
|
||||
Zotero.Sync.Runner.lastSyncError = e.name;
|
||||
}
|
||||
else {
|
||||
Zotero.Sync.Server.lastSyncError = e;
|
||||
Zotero.Sync.Runner.lastSyncError = e;
|
||||
}
|
||||
Zotero.debug(e, 1);
|
||||
Zotero.Sync.Runner.reset();
|
||||
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() {
|
||||
this.processUpdatedXML = processUpdatedXML;
|
||||
this.buildUploadXML = buildUploadXML;
|
||||
|
@ -1299,6 +1388,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
var remoteCreatorStore = {};
|
||||
var relatedItemsStore = {};
|
||||
var itemStorageModTimes = {};
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
|
@ -1527,10 +1617,10 @@ Zotero.Sync.Server.Data = new function() {
|
|||
// Create or overwrite locally
|
||||
obj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode, obj);
|
||||
|
||||
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
|
||||
if (isNewObject && type == 'tag') {
|
||||
var tagName = xmlNode.@name.toString();
|
||||
var tagType = xmlNode.@type.toString()
|
||||
? parseInt(xmlNode.@type) : 0;
|
||||
|
@ -1562,6 +1652,25 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
// Don't use assigned-but-unsaved ids for new ids
|
||||
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.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);
|
||||
|
@ -1888,10 +2010,18 @@ Zotero.Sync.Server.Data = new function() {
|
|||
if (item.primary.itemType == 'attachment') {
|
||||
xml.@linkMode = item.attachment.linkMode;
|
||||
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) {
|
||||
var mtime = Zotero.Sync.Storage.getSyncedModificationTime(item.primary.itemID);
|
||||
if (mtime) {
|
||||
xml.@storageModTime = mtime;
|
||||
}
|
||||
|
||||
var path = <path>{item.attachment.path}</path>;
|
||||
xml.path += path;
|
||||
}
|
||||
|
@ -2018,8 +2148,8 @@ Zotero.Sync.Server.Data = new function() {
|
|||
// Attachment metadata
|
||||
if (item.isAttachment()) {
|
||||
item.attachmentLinkMode = parseInt(xmlItem.@linkMode);
|
||||
item.attachmentMIMEType = xmlItem.@mimeType;
|
||||
item.attachmentCharset = parseInt(xmlItem.@charsetID);
|
||||
item.attachmentMIMEType = xmlItem.@mimeType.toString();
|
||||
item.attachmentCharset = xmlItem.@charset.toString();
|
||||
if (item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
item.attachmentPath = xmlItem.path.toString();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
@ -643,9 +668,9 @@ Zotero.Utilities.HTTP = new function() {
|
|||
this.doGet = doGet;
|
||||
this.doPost = doPost;
|
||||
this.doHead = doHead;
|
||||
this.doOptions = doOptions;
|
||||
this.browserIsOffline = browserIsOffline;
|
||||
|
||||
this.WebDAV = {};
|
||||
|
||||
/**
|
||||
* 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"]
|
||||
.createInstance();
|
||||
|
||||
var test = xmlhttp.open('GET', url, true);
|
||||
// Prevent certificate/authentication dialogs from popping up
|
||||
xmlhttp.mozBackgroundRequest = true;
|
||||
xmlhttp.open('GET', url, true);
|
||||
|
||||
xmlhttp.onreadystatechange = function(){
|
||||
_stateChange(xmlhttp, onDone, responseCharset);
|
||||
|
@ -716,7 +742,8 @@ Zotero.Utilities.HTTP = new function() {
|
|||
|
||||
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance();
|
||||
|
||||
// Prevent certificate/authentication dialogs from popping up
|
||||
xmlhttp.mozBackgroundRequest = true;
|
||||
xmlhttp.open('POST', url, true);
|
||||
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"]
|
||||
.createInstance();
|
||||
|
||||
var test = xmlhttp.open('HEAD', url, true);
|
||||
// Prevent certificate/authentication dialogs from popping up
|
||||
xmlhttp.mozBackgroundRequest = true;
|
||||
xmlhttp.open('HEAD', url, true);
|
||||
|
||||
xmlhttp.onreadystatechange = function(){
|
||||
_stateChange(xmlhttp, onDone);
|
||||
|
@ -778,24 +806,28 @@ Zotero.Utilities.HTTP = new function() {
|
|||
/**
|
||||
* Send an HTTP OPTIONS request via XMLHTTPRequest
|
||||
*
|
||||
* doOptions can be called as:
|
||||
* Zotero.Utilities.HTTP.doOptions(url, body, onDone)
|
||||
*
|
||||
* Returns the XMLHTTPRequest object
|
||||
**/
|
||||
function doOptions(url, body, onDone) {
|
||||
Zotero.debug("HTTP OPTIONS "+url);
|
||||
if (this.browserIsOffline()){
|
||||
* @param {nsIURI} url
|
||||
* @param {Function} onDone
|
||||
* @return {XMLHTTPRequest}
|
||||
*/
|
||||
this.doOptions = function (uri, callback) {
|
||||
// Don't display password in console
|
||||
var disp = uri.clone();
|
||||
disp.password = "********";
|
||||
Zotero.debug("HTTP OPTIONS to " + disp.spec);
|
||||
|
||||
if (Zotero.Utilities.HTTP.browserIsOffline()){
|
||||
return false;
|
||||
}
|
||||
|
||||
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance();
|
||||
|
||||
xmlhttp.open('OPTIONS', url, true);
|
||||
// Prevent certificate/authentication dialogs from popping up
|
||||
xmlhttp.mozBackgroundRequest = true;
|
||||
xmlhttp.open('OPTIONS', uri.spec, true);
|
||||
|
||||
xmlhttp.onreadystatechange = function() {
|
||||
_stateChange(xmlhttp, onDone);
|
||||
_stateChange(xmlhttp, callback);
|
||||
};
|
||||
|
||||
// Temporarily set cookieBehavior to 0 for Firefox 3
|
||||
|
@ -806,7 +838,7 @@ Zotero.Utilities.HTTP = new function() {
|
|||
var cookieBehavior = prefService.getIntPref("network.cookie.cookieBehavior");
|
||||
prefService.setIntPref("network.cookie.cookieBehavior", 0);
|
||||
|
||||
xmlhttp.send(body);
|
||||
xmlhttp.send(null);
|
||||
}
|
||||
finally {
|
||||
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() {
|
||||
return Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService).offline;
|
||||
}
|
||||
|
||||
|
||||
function _stateChange(xmlhttp, onDone, responseCharset){
|
||||
function _stateChange(xmlhttp, callback, responseCharset, data) {
|
||||
switch (xmlhttp.readyState){
|
||||
// Request not yet made
|
||||
case 1:
|
||||
break;
|
||||
|
||||
// Called multiple while downloading in progress
|
||||
case 2:
|
||||
break;
|
||||
|
||||
// Called multiple times while downloading in progress
|
||||
case 3:
|
||||
break;
|
||||
|
||||
// Download complete
|
||||
case 4:
|
||||
if(onDone){
|
||||
if (callback) {
|
||||
// Override the content charset
|
||||
if (responseCharset) {
|
||||
xmlhttp.channel.contentCharset = responseCharset;
|
||||
}
|
||||
onDone(xmlhttp);
|
||||
callback(xmlhttp, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Downloads and processes documents with processor()
|
||||
|
@ -953,3 +1180,142 @@ Zotero.Utilities.AutoComplete = new function(){
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -41,6 +41,7 @@ var Zotero = new function(){
|
|||
this.getZoteroDirectory = getZoteroDirectory;
|
||||
this.getStorageDirectory = getStorageDirectory;
|
||||
this.getZoteroDatabase = getZoteroDatabase;
|
||||
this.getTempDirectory = getTempDirectory;
|
||||
this.chooseZoteroDirectory = chooseZoteroDirectory;
|
||||
this.debug = debug;
|
||||
this.log = log;
|
||||
|
@ -249,6 +250,9 @@ var Zotero = new function(){
|
|||
if (typeof e == 'string' && e.match('newer than SQL file')) {
|
||||
_startupError = e;
|
||||
}
|
||||
else {
|
||||
_startupError = "Database upgrade error";
|
||||
}
|
||||
Components.utils.reportError(_startupError);
|
||||
return false;
|
||||
}
|
||||
|
@ -265,7 +269,8 @@ var Zotero = new function(){
|
|||
Zotero.Zeroconf.init();
|
||||
|
||||
Zotero.Sync.init();
|
||||
Zotero.Sync.Server.init();
|
||||
Zotero.Sync.Runner.init();
|
||||
Zotero.Sync.Storage.init();
|
||||
|
||||
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) {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.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 8601–formatted 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:
|
||||
* day: integer form of the day
|
||||
|
|
|
@ -156,6 +156,10 @@
|
|||
|
||||
<!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.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.">
|
||||
|
|
|
@ -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.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.error = Information Validation Error
|
||||
proxies.error.scheme.noHTTP = Valid proxy schemes must start with "http://" or "https://"
|
||||
|
|
BIN
chrome/skin/default/zotero/drive_network.png
Executable file
BIN
chrome/skin/default/zotero/drive_network.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 585 B |
|
@ -191,11 +191,28 @@
|
|||
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 {
|
||||
margin-top: -2px;
|
||||
list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png);
|
||||
margin-left: -2px;
|
||||
margin-right: -2px;
|
||||
list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png);
|
||||
}
|
||||
|
||||
#zotero-tb-sync[status=animate] {
|
||||
|
|
|
@ -3,14 +3,8 @@ prefwindow .chromeclass-toolbar
|
|||
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 {
|
||||
width: 45em;
|
||||
}
|
||||
prefwindow > prefpane > vbox.content-box {
|
||||
height: 42em;
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -51,6 +51,7 @@ var xpcomFiles = [
|
|||
'schema',
|
||||
'search',
|
||||
'sync',
|
||||
'storage',
|
||||
'timeline',
|
||||
'translate',
|
||||
'utilities',
|
||||
|
|
|
@ -82,10 +82,17 @@ pref("extensions.zotero.zeroconf.server.enabled", false);
|
|||
// Annotation settings
|
||||
pref("extensions.zotero.annotations.warnOnClose", true);
|
||||
|
||||
// Server
|
||||
pref("extensions.zotero.sync.server.autoSync", true);
|
||||
// Sync
|
||||
pref("extensions.zotero.sync.autoSync", true);
|
||||
pref("extensions.zotero.sync.server.username", '');
|
||||
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
|
||||
pref("extensions.zotero.proxies.autoRecognize", true);
|
||||
|
|
11
userdata.sql
11
userdata.sql
|
@ -1,4 +1,4 @@
|
|||
-- 39
|
||||
-- 40
|
||||
|
||||
-- This file creates tables containing user-specific data -- any changes made
|
||||
-- here must be mirrored in transition steps in schema.js::_migrateSchema()
|
||||
|
@ -62,11 +62,14 @@ CREATE TABLE itemAttachments (
|
|||
charsetID INT,
|
||||
path TEXT,
|
||||
originalPath TEXT,
|
||||
syncState INT DEFAULT 0,
|
||||
storageModTime INT,
|
||||
FOREIGN KEY (itemID) REFERENCES items(itemID),
|
||||
FOREIGN KEY (sourceItemID) REFERENCES items(sourceItemID)
|
||||
);
|
||||
CREATE INDEX itemAttachments_sourceItemID ON itemAttachments(sourceItemID);
|
||||
CREATE INDEX itemAttachments_mimeType ON itemAttachments(mimeType);
|
||||
CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState);
|
||||
|
||||
-- Individual entries for each tag
|
||||
CREATE TABLE tags (
|
||||
|
@ -202,6 +205,12 @@ CREATE TABLE syncDeleteLog (
|
|||
);
|
||||
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 (
|
||||
translatorID TEXT PRIMARY KEY,
|
||||
minVersion TEXT,
|
||||
|
|
Loading…
Add table
Reference in a new issue