Zotero File Storage megacommit
- Group file sync via Zotero File Storage - Split file syncing into separate modules for ZFS and WebDAV - Dragging items between libraries copies child notes, snapshots/files, and links based on checkboxes for each (enabled by default) in the Zotero preferences - Sync errors now trigger an exclamation/error icon separate from the sync icon, with a popup window displaying the error and an option to report it - Various errors that could cause perpetual sync icon spinning now stop the sync properly - Zotero.Utilities.md5(str) is now md5(strOrFile, base64) - doPost(), doHead(), and retrieveSource() now takes a headers parameter instead of requestContentType - doHead() can now accept an nsIURI (with login credentials), is a background request, and isn't cached - When library access or file writing access is denied during sync, display a warning and then reset local group to server version - Perform additional steps (e.g., removing local groups) when switching sync users to prevent errors - Compare hash as well as mod time when checking for modified local files - Don't trigger notifications when removing groups from the client - Clear relation links to items in removed groups - Zotero.Item.attachmentHash property to get file MD5 - importFromFile() now takes libraryID as a third parameter - Zotero.Attachments.getNumFiles() returns the number of files in the attachment directory - Zotero.Attachments.copyAttachmentToLibrary() copies an attachment item, including files, to another library - Removed Zotero.File.getFileHash() in favor of updated Zotero.Utilities.md5() - Zotero.File.copyDirectory(dir, newDir) copies all files from dir into newDir - Preferences shuffling: OpenURL to Advanced, import/export character set options to Export, "Include URLs of paper articles in references" to Styles - Other stuff I don't remember Suffice it to say, this could use testing.
This commit is contained in:
parent
a611706f86
commit
884e5474fe
31 changed files with 4261 additions and 1997 deletions
|
@ -177,11 +177,25 @@ var ZoteroPane = new function()
|
|||
);
|
||||
|
||||
if (index == 0) {
|
||||
Zotero.Sync.Server.sync(function () {
|
||||
Zotero.Sync.Server.sync({
|
||||
onSuccess: function () {
|
||||
Zotero.Sync.Runner.setSyncIcon();
|
||||
|
||||
pr.alert(
|
||||
"Restore Completed",
|
||||
"The local Zotero database has been successfully restored."
|
||||
);
|
||||
},
|
||||
|
||||
onError: function (msg) {
|
||||
pr.alert(
|
||||
"Restore Failed",
|
||||
"An error occurred while restoring from the server:\n\n"
|
||||
+ msg
|
||||
);
|
||||
|
||||
Zotero.Sync.Runner.error(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
@ -898,7 +912,7 @@ var ZoteroPane = new function()
|
|||
if (!tagSelector.getAttribute('collapsed') ||
|
||||
tagSelector.getAttribute('collapsed') == 'false') {
|
||||
Zotero.debug('Updating tag selector with current tags');
|
||||
if (itemGroup.isEditable()) {
|
||||
if (itemGroup.editable) {
|
||||
tagSelector.mode = 'edit';
|
||||
}
|
||||
else {
|
||||
|
@ -2281,7 +2295,7 @@ var ZoteroPane = new function()
|
|||
var disabled = Zotero.locked;
|
||||
if (!disabled && self.collectionsView.selection && self.collectionsView.selection.count) {
|
||||
var itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex);
|
||||
disabled = !itemGroup.isEditable()
|
||||
disabled = !itemGroup.editable;
|
||||
}
|
||||
for each(var menuitem in menu.firstChild.childNodes) {
|
||||
menuitem.disabled = disabled;
|
||||
|
@ -2837,7 +2851,26 @@ var ZoteroPane = new function()
|
|||
}
|
||||
|
||||
var itemGroup = this.collectionsView._getItemAtRow(row);
|
||||
return itemGroup.isEditable();
|
||||
return itemGroup.editable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the user can edit the currently selected library/collection,
|
||||
* and display an error if not
|
||||
*
|
||||
* @param {Integer} [row]
|
||||
*
|
||||
* @return {Boolean} TRUE if user can edit, FALSE if not
|
||||
*/
|
||||
this.canEditFiles = function (row) {
|
||||
// Currently selected row
|
||||
if (row === undefined) {
|
||||
row = this.collectionsView.selection.currentIndex;
|
||||
}
|
||||
|
||||
var itemGroup = this.collectionsView._getItemAtRow(row);
|
||||
return itemGroup.filesEditable;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2999,12 +3032,6 @@ var ZoteroPane = new function()
|
|||
this.setLastSyncStatus = function (tooltip) {
|
||||
var label = tooltip.firstChild.nextSibling;
|
||||
|
||||
var msg = Zotero.Sync.Runner.lastSyncError;
|
||||
if (msg) {
|
||||
label.value = 'Last error: ' + msg; // TODO: localize
|
||||
return;
|
||||
}
|
||||
|
||||
var lastSyncTime = Zotero.Sync.Server.lastLocalSyncTime;
|
||||
// TODO: localize
|
||||
msg = 'Last sync: ';
|
||||
|
|
|
@ -164,8 +164,8 @@
|
|||
<menuitem hidden="true" label=" Reset Client" oncommand="Zotero.Sync.Server.resetClient()"/>
|
||||
<menuitem label="Storage Debugging" disabled="true"/>
|
||||
<menuitem hidden="true" 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); })"/>
|
||||
<menuitem label=" Purge Deleted Storage Files" oncommand="Zotero.Sync.Storage.purgeDeletedStorageFiles('webdav', function(results) { Zotero.debug(results); })"/>
|
||||
<menuitem label=" Purge Orphaned Storage Files" oncommand="Zotero.Sync.Storage.purgeOrphanedStorageFiles('webdav', function(results) { Zotero.debug(results); })"/>
|
||||
<menuseparator id="zotero-tb-actions-separator"/>
|
||||
<menuitem id="zotero-tb-actions-prefs" label="&zotero.toolbar.preferences.label;"
|
||||
oncommand="ZoteroPane.openPreferences()"/>
|
||||
|
@ -375,6 +375,7 @@
|
|||
</grid>
|
||||
</tooltip>
|
||||
</hbox>
|
||||
<toolbarbutton id="zotero-tb-sync-warning" hidden="true"/>
|
||||
<toolbarbutton id="zotero-tb-sync" class="zotero-tb-button" tooltip="_child"
|
||||
oncommand="Zotero.Sync.Server.canAutoResetClient = true; Zotero.Sync.Runner.sync()">
|
||||
<tooltip
|
||||
|
|
|
@ -42,7 +42,7 @@ function init()
|
|||
rows[i].firstChild.nextSibling.value = Zotero.isMac ? 'Cmd+Shift+' : 'Ctrl+Alt+';
|
||||
}
|
||||
|
||||
updateStorageSettings();
|
||||
updateStorageSettings(null, null, true);
|
||||
refreshStylesList();
|
||||
refreshProxyList();
|
||||
populateQuickCopyList();
|
||||
|
@ -171,43 +171,85 @@ function populateOpenURLResolvers() {
|
|||
//
|
||||
// Sync
|
||||
//
|
||||
/*
|
||||
function updateSyncStatus() {
|
||||
var disabled = !Zotero.Sync.Server.enabled;
|
||||
function updateStorageSettings(enabled, protocol, skipWarnings) {
|
||||
if (enabled === null) {
|
||||
enabled = document.getElementById('pref-storage-enabled').value;
|
||||
}
|
||||
|
||||
var radioGroup = document.getElementById('zotero-reset').firstChild;
|
||||
radioGroup.disabled = disabled;
|
||||
var labels = radioGroup.getElementsByTagName('label');
|
||||
for each(var label in labels) {
|
||||
label.disabled = disabled;
|
||||
var oldProtocol = document.getElementById('pref-storage-protocol').value;
|
||||
if (protocol === null) {
|
||||
protocol = oldProtocol;
|
||||
}
|
||||
var labels = radioGroup.getElementsByTagName('description');
|
||||
for each(var label in labels) {
|
||||
label.disabled = disabled;
|
||||
}
|
||||
document.getElementById('zotero-reset-button').disabled = disabled;
|
||||
}
|
||||
*/
|
||||
|
||||
function updateStorageSettings(value) {
|
||||
if (!value) {
|
||||
value = document.getElementById('pref-storage-protocol').value;
|
||||
}
|
||||
var prefix = document.getElementById('storage-url-prefix');
|
||||
switch (value) {
|
||||
case 'webdav':
|
||||
prefix.value = 'http://';
|
||||
break;
|
||||
var protocolMenu = document.getElementById('storage-protocol');
|
||||
var settings = document.getElementById('storage-webdav-settings');
|
||||
var sep = document.getElementById('storage-separator');
|
||||
|
||||
case 'webdavs':
|
||||
prefix.value = 'https://';
|
||||
break;
|
||||
if (!enabled || protocol == 'zotero') {
|
||||
settings.hidden = true;
|
||||
sep.hidden = false;
|
||||
}
|
||||
else {
|
||||
settings.hidden = false;
|
||||
sep.hidden = true;
|
||||
}
|
||||
|
||||
protocolMenu.disabled = !enabled;
|
||||
|
||||
if (!skipWarnings) {
|
||||
// WARN if going between
|
||||
}
|
||||
|
||||
if (oldProtocol == 'zotero' && protocol == 'webdav') {
|
||||
var sql = "SELECT COUNT(*) FROM version WHERE schema='storage_zfs'";
|
||||
if (Zotero.DB.valueQuery(sql)) {
|
||||
var pr = Components.classes["@mozilla.org/network/default-prompt;1"]
|
||||
.getService(Components.interfaces.nsIPrompt);
|
||||
var buttonFlags = (pr.BUTTON_POS_0) * (pr.BUTTON_TITLE_IS_STRING)
|
||||
+ (pr.BUTTON_POS_1) * (pr.BUTTON_TITLE_IS_STRING)
|
||||
+ pr.BUTTON_DELAY_ENABLE;
|
||||
var account = Zotero.Sync.Server.username;
|
||||
var index = pr.confirmEx(
|
||||
// TODO: localize
|
||||
"Purge Attachment Files on Zotero Servers?",
|
||||
|
||||
"If you plan to use WebDAV for file syncing and you previously synced attachment files in My Library "
|
||||
+ "to the Zotero servers, you can purge those files from the Zotero servers to give you more "
|
||||
+ "storage space for groups.\n\n"
|
||||
+ "You can purge files at any time from your account settings on zotero.org.",
|
||||
buttonFlags,
|
||||
"Purge Files Now",
|
||||
"Do Not Purge", null, null, {}
|
||||
);
|
||||
|
||||
if (index == 0) {
|
||||
var sql = "INSERT OR IGNORE INTO settings VALUES (?,?,?)";
|
||||
Zotero.DB.query(sql, ['storage', 'zfsPurge', 'user']);
|
||||
|
||||
Zotero.Sync.Storage.purgeDeletedStorageFiles('zfs', function (success) {
|
||||
if (success) {
|
||||
pr.alert(
|
||||
Zotero.getString("general.success"),
|
||||
"Attachment files from your personal library have been removed from the Zotero servers."
|
||||
);
|
||||
}
|
||||
else {
|
||||
pr.alert(
|
||||
Zotero.getString("general.error"),
|
||||
"An error occurred. Please try again later."
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function unverifyStorageServer() {
|
||||
Zotero.Sync.Storage.clearSettingsCache();
|
||||
Zotero.Prefs.set('sync.storage.verified', false);
|
||||
Zotero.Sync.Storage.resetAllSyncStates(null, true, false);
|
||||
}
|
||||
|
||||
function verifyStorageServer() {
|
||||
|
@ -220,7 +262,7 @@ function verifyStorageServer() {
|
|||
var usernameField = document.getElementById("storage-username");
|
||||
var passwordField = document.getElementById("storage-password");
|
||||
|
||||
var callback = function (uri, status, authRequired) {
|
||||
var callback = function (uri, status) {
|
||||
verifyButton.hidden = false;
|
||||
abortButton.hidden = true;
|
||||
progressMeter.hidden = true;
|
||||
|
@ -245,13 +287,13 @@ function verifyStorageServer() {
|
|||
break;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.checkServerCallback(uri, status, authRequired, window);
|
||||
Zotero.Sync.Storage.checkServerCallback(uri, status, window);
|
||||
}
|
||||
|
||||
verifyButton.hidden = true;
|
||||
abortButton.hidden = false;
|
||||
progressMeter.hidden = false;
|
||||
var requestHolder = Zotero.Sync.Storage.checkServer(callback);
|
||||
var requestHolder = Zotero.Sync.Storage.checkServer('webdav', callback);
|
||||
abortButton.onclick = function () {
|
||||
if (requestHolder.request) {
|
||||
requestHolder.request.onreadystatechange = undefined;
|
||||
|
|
|
@ -38,7 +38,8 @@ To add a new preference:
|
|||
|
||||
-->
|
||||
<prefwindow id="zotero-prefs" title="&zotero.preferences.title;" onload="moveToAlertPosition(); init()" onunload="Zotero_Preferences.onUnload()"
|
||||
windowtype="zotero:pref" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
windowtype="zotero:pref" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
style="min-height: 600px">
|
||||
|
||||
<prefpane id="zotero-prefpane-general"
|
||||
label="&zotero.preferences.prefpane.general;"
|
||||
|
@ -54,8 +55,10 @@ To add a new preference:
|
|||
<preference id="pref-automaticSnapshots" name="extensions.zotero.automaticSnapshots" type="bool"/>
|
||||
<preference id="pref-downloadAssociatedFiles" name="extensions.zotero.downloadAssociatedFiles" type="bool"/>
|
||||
<preference id="pref-automaticTags" name="extensions.zotero.automaticTags" type="bool"/>
|
||||
<preference id="pref-openURL-resolver" name="extensions.zotero.openURL.resolver" type="string"/>
|
||||
<preference id="pref-openURL-version" name="extensions.zotero.openURL.version" type="string"/>
|
||||
|
||||
<preference id="pref-groups-copyChildNotes" name="extensions.zotero.groups.copyChildNotes" type="bool"/>
|
||||
<preference id="pref-groups-copyChildFileAttachments" name="extensions.zotero.groups.copyChildFileAttachments" type="bool"/>
|
||||
<preference id="pref-groups-copyChildLinks" name="extensions.zotero.groups.copyChildLinks" type="bool"/>
|
||||
</preferences>
|
||||
|
||||
<groupbox>
|
||||
|
@ -138,31 +141,18 @@ To add a new preference:
|
|||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.openurl.caption;"/>
|
||||
<caption label="Groups"/>
|
||||
|
||||
<button id="openURLSearchButton" label="&zotero.preferences.openurl.search;" oncommand="populateOpenURLResolvers()"/>
|
||||
<menulist id="openURLMenu" oncommand="onOpenURLSelected();">
|
||||
<menupopup>
|
||||
<menuseparator/>
|
||||
<menuitem label="&zotero.preferences.openurl.custom;" value="custom" selected="true"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.openurl.server;"/>
|
||||
<textbox id="openURLServerField" flex="1" oninput="onOpenURLCustomized();" preference="pref-openURL-resolver"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.openurl.version;" control="openURLVersionMenu"/>
|
||||
<menulist id="openURLVersionMenu" oncommand="onOpenURLCustomized();" preference="pref-openURL-version">
|
||||
<menupopup>
|
||||
<menuitem label="0.1" value="0.1"/>
|
||||
<menuitem label="1.0" value="1.0"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
<!-- TODO: localize -->
|
||||
<label value="When copying items between libraries, include:"/>
|
||||
<vbox style="margin-left: 2em">
|
||||
<checkbox label="child notes" preference="pref-groups-copyChildNotes"/>
|
||||
<checkbox label="child snapshots and imported files" preference="pref-groups-copyChildFileAttachments"/>
|
||||
<checkbox label="child links" preference="pref-groups-copyChildLinks"/>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
|
||||
<separator/>
|
||||
</prefpane>
|
||||
|
||||
|
||||
|
@ -174,10 +164,12 @@ To add a new preference:
|
|||
<preferences>
|
||||
<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-protocol" name="extensions.zotero.sync.storage.protocol" type="string"/>
|
||||
<preference id="pref-storage-enabled" name="extensions.zotero.sync.storage.enabled" type="bool"/>
|
||||
<preference id="pref-storage-protocol" name="extensions.zotero.sync.storage.protocol" type="string" onchange="unverifyStorageServer()"/>
|
||||
<preference id="pref-storage-scheme" name="extensions.zotero.sync.storage.scheme" type="string"/>
|
||||
<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"/>
|
||||
<preference id="pref-group-storage-enabled" name="extensions.zotero.sync.storage.groups.enabled" type="bool"/>
|
||||
</preferences>
|
||||
|
||||
<!-- This doesn't wrap without an explicit width, for some reason -->
|
||||
|
@ -250,50 +242,44 @@ To add a new preference:
|
|||
</groupbox>
|
||||
|
||||
|
||||
<!-- TODO: localize -->
|
||||
<groupbox>
|
||||
<caption label="Storage Server"/>
|
||||
<caption label="File Syncing"/>
|
||||
|
||||
<hbox>
|
||||
<checkbox label="Enable file syncing" preference="pref-storage-enabled"/>
|
||||
<!-- My Library -->
|
||||
<hbox style="margin: 0">
|
||||
<checkbox label="Sync attachment files in My Library using" preference="pref-storage-enabled" oncommand="updateStorageSettings(this.checked, null)"/>
|
||||
<menulist id="storage-protocol" style="margin-left: .5em" preference="pref-storage-protocol" oncommand="updateStorageSettings(null, this.value)">
|
||||
<menupopup>
|
||||
<menuitem label="Zotero" value="zotero"/>
|
||||
<menuitem label="WebDAV" value="webdav"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
<hbox align="center">
|
||||
<label value="A" style="margin-right: 0"/>
|
||||
<label value="WebDAV server" class="text-link" href="http://zotero.org/support/sync#file_syncing" style="margin-left: .25em; margin-right: .25em"/>
|
||||
<label value="is currently required to sync attachment files." style="margin-left: 0"/>
|
||||
</hbox>
|
||||
<stack id="storage-webdav-settings" style="margin-top: .5em; margin-bottom: .8em; border: 1px gray solid; -moz-border-radius: 3px">
|
||||
<!-- Background shading -->
|
||||
<box style="background: black; opacity:.03"/>
|
||||
|
||||
<separator class="thin"/>
|
||||
<label value="Syncing of attachment files in group libraries is not currently supported."/>
|
||||
|
||||
<separator/>
|
||||
|
||||
<grid id="storage-settings">
|
||||
<grid style="padding: .7em .4em .7em 0">
|
||||
<columns>
|
||||
<column/>
|
||||
<column flex="1"/>
|
||||
</columns>
|
||||
|
||||
<rows>
|
||||
<row>
|
||||
<label value="Protocol:"/>
|
||||
<hbox>
|
||||
<menulist id="storage-url-protocol"
|
||||
preference="pref-storage-protocol"
|
||||
onsynctopreference="updateStorageSettings(this.value); unverifyStorageServer();">
|
||||
<menupopup>
|
||||
<menuitem label="WebDAV" value="webdav"/>
|
||||
<!-- TODO: localize -->
|
||||
<menuitem label="WebDAV (Secure)" value="webdavs"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
</row>
|
||||
<row>
|
||||
<label value="URL:"/>
|
||||
<hbox>
|
||||
<label id="storage-url-prefix"/>
|
||||
<menulist id="storage-url-prefix"
|
||||
preference="pref-storage-scheme"
|
||||
onsynctopreference="unverifyStorageServer()" style="padding: 0; width: 7em">
|
||||
<menupopup>
|
||||
<menuitem label="http" value="http" style="padding: 0"/>
|
||||
<menuitem label="https" value="https" style="padding: 0"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
<label value="://"/>
|
||||
<textbox id="storage-url" flex="1"
|
||||
preference="pref-storage-url"
|
||||
onkeypress="if (Zotero.isMac && event.keyCode == 13) { this.blur(); verifyStorageServer(); }"
|
||||
|
@ -309,7 +295,7 @@ To add a new preference:
|
|||
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; }"/>
|
||||
onchange="var pass = document.getElementById('storage-password'); if (pass.value) { Zotero.Sync.Storage.Session.WebDAV.prototype.password = pass.value; }"/>
|
||||
</hbox>
|
||||
</row>
|
||||
<row>
|
||||
|
@ -318,7 +304,7 @@ To add a new preference:
|
|||
<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"/>
|
||||
onchange="Zotero.Sync.Storage.Session.WebDAV.prototype.password = this.value"/>
|
||||
</hbox>
|
||||
</row>
|
||||
<row>
|
||||
|
@ -333,6 +319,20 @@ To add a new preference:
|
|||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
|
||||
</stack>
|
||||
|
||||
<separator id="storage-separator" class="thin"/>
|
||||
|
||||
<!-- Group Libraries -->
|
||||
<checkbox label="Sync attachment files in group libraries using Zotero File Storage"
|
||||
preference="pref-group-storage-enabled"/>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<hbox style="margin-top:.3em">
|
||||
<label class="text-link" style="margin-left: 0" value="About File Syncing" href="http://zotero.org/support/file_sync"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
</tabpanel>
|
||||
|
||||
|
@ -377,7 +377,7 @@ To add a new preference:
|
|||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="Storage Server"/>
|
||||
<caption label="File Syncing"/>
|
||||
|
||||
<grid>
|
||||
<columns>
|
||||
|
@ -404,6 +404,8 @@ To add a new preference:
|
|||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
|
||||
<separator/>
|
||||
</prefpane>
|
||||
|
||||
|
||||
|
@ -492,6 +494,8 @@ To add a new preference:
|
|||
</rows>
|
||||
</grid>
|
||||
</groupbox>
|
||||
|
||||
<separator/>
|
||||
</prefpane>
|
||||
|
||||
|
||||
|
@ -499,21 +503,10 @@ To add a new preference:
|
|||
label="&zotero.preferences.prefpane.export;"
|
||||
image="chrome://zotero/skin/prefs-export.png">
|
||||
<preferences>
|
||||
<preference id="pref-export-citePaperJournalArticleURL" name="extensions.zotero.export.citePaperJournalArticleURL" type="bool"/>
|
||||
<preference id="pref-quickCopy-setting" name="extensions.zotero.export.quickCopy.setting" type="string"/>
|
||||
<preference id="pref-quickCopy-dragLimit" name="extensions.zotero.export.quickCopy.dragLimit" type="int"/>
|
||||
</preferences>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.citationOptions.caption;"/>
|
||||
|
||||
<checkbox label="&zotero.preferences.export.citePaperJournalArticleURL;" preference="pref-export-citePaperJournalArticleURL"/>
|
||||
<!-- This doesn't wrap without an explicit width, for some reason -->
|
||||
<label id="export-citePaperJournalArticleURL" width="45em">
|
||||
&zotero.preferences.export.citePaperJournalArticleURL.description;
|
||||
</label>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.quickCopy.caption;"/>
|
||||
|
||||
|
@ -548,23 +541,38 @@ To add a new preference:
|
|||
<button label="+" onclick="showQuickCopySiteEditor()"/>
|
||||
</hbox>
|
||||
|
||||
<separator/>
|
||||
|
||||
<!-- TODO: localize -->
|
||||
<hbox align="center">
|
||||
<label value="Disable Quick Copy when dragging more than"/>
|
||||
<textbox preference="pref-quickCopy-dragLimit" size="3"/>
|
||||
<label value="items" flex="1"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.charset;"/>
|
||||
|
||||
<checkbox id="zotero-export-displayCharsetOption" label="&zotero.preferences.charset.displayExportOption;"
|
||||
preference="pref-export-displayCharsetOption"/>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.charset.importCharset;:" control="zotero-import-charsetMenu"/>
|
||||
<menulist id="zotero-import-charsetMenu" preference="pref-import-charset"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<separator/>
|
||||
</groupbox>
|
||||
</prefpane>
|
||||
|
||||
|
||||
<prefpane id="zotero-prefpane-styles"
|
||||
label="&zotero.preferences.prefpane.styles;"
|
||||
image="chrome://zotero/skin/prefs-styles.png">
|
||||
|
||||
<preferences>
|
||||
<preference id="pref-styles-citePaperJournalArticleURL" name="extensions.zotero.export.citePaperJournalArticleURL" type="bool"/>
|
||||
</preferences>
|
||||
|
||||
<groupbox flex="1">
|
||||
<caption label="&zotero.preferences.styles.styleManager;"/>
|
||||
|
||||
|
@ -579,13 +587,24 @@ To add a new preference:
|
|||
<treechildren id="styleManager-rows"/>
|
||||
</tree>
|
||||
<separator class="thin"/>
|
||||
<hbox pack="end">
|
||||
<hbox align="center" flex="1">
|
||||
<label class="text-link" href="http://www.zotero.org/styles/" value="&zotero.preferences.export.getAdditionalStyles;" flex="1"/>
|
||||
<button disabled="true" id="styleManager-delete" label="-" onclick="deleteStyle()"/>
|
||||
<button label="+" onclick="addStyle()"/>
|
||||
</hbox>
|
||||
<separator/>
|
||||
<label class="text-link" href="http://www.zotero.org/styles/" value="&zotero.preferences.export.getAdditionalStyles;"/>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.citationOptions.caption;"/>
|
||||
|
||||
<checkbox label="&zotero.preferences.export.citePaperJournalArticleURL;" preference="pref-styles-citePaperJournalArticleURL"/>
|
||||
<!-- This doesn't wrap without an explicit width, for some reason -->
|
||||
<label id="export-citePaperJournalArticleURL" width="45em">
|
||||
&zotero.preferences.export.citePaperJournalArticleURL.description;
|
||||
</label>
|
||||
</groupbox>
|
||||
|
||||
<separator/>
|
||||
</prefpane>
|
||||
|
||||
|
||||
|
@ -621,6 +640,8 @@ To add a new preference:
|
|||
<button label="+" onclick="showProxyEditor()"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<separator/>
|
||||
</prefpane>
|
||||
|
||||
|
||||
|
@ -714,6 +735,8 @@ To add a new preference:
|
|||
<checkbox label="&zotero.preferences.keys.overrideGlobal;" preference="pref-keys-overrideGlobal"/>
|
||||
|
||||
<label class="statusLine" value="&zotero.preferences.keys.changesTakeEffect;"/>
|
||||
|
||||
<separator/>
|
||||
</prefpane>
|
||||
|
||||
|
||||
|
@ -726,6 +749,8 @@ To add a new preference:
|
|||
<preference id="pref-export-displayCharsetOption" name="extensions.zotero.export.displayCharsetOption" type="bool"/>
|
||||
<preference id="pref-debug-output-enableAfterRestart" name="extensions.zotero.debug.store" type="bool"/>
|
||||
<preference id="pref-import-charset" name="extensions.zotero.import.charset" type="string"/>
|
||||
<preference id="pref-openURL-resolver" name="extensions.zotero.openURL.resolver" type="string"/>
|
||||
<preference id="pref-openURL-version" name="extensions.zotero.openURL.version" type="string"/>
|
||||
</preferences>
|
||||
|
||||
<groupbox>
|
||||
|
@ -750,9 +775,6 @@ To add a new preference:
|
|||
|
||||
<hbox>
|
||||
<button label="&zotero.preferences.dbMaintenance.integrityCheck;" oncommand="runIntegrityCheck()"/>
|
||||
</hbox>
|
||||
|
||||
<hbox>
|
||||
<button label="&zotero.preferences.dbMaintenance.resetTranslatorsAndStyles;" oncommand="resetTranslatorsAndStyles()"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
@ -782,16 +804,38 @@ To add a new preference:
|
|||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.charset;"/>
|
||||
|
||||
<checkbox id="zotero-export-displayCharsetOption" label="&zotero.preferences.charset.displayExportOption;"
|
||||
preference="pref-export-displayCharsetOption"/>
|
||||
<caption label="&zotero.preferences.openurl.caption;"/>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.charset.importCharset;:" control="zotero-import-charsetMenu"/>
|
||||
<menulist id="zotero-import-charsetMenu" preference="pref-import-charset"/>
|
||||
<!-- vbox prevents some weird vertical stretching of the menulist -->
|
||||
<vbox flex="1">
|
||||
<menulist id="openURLMenu" oncommand="onOpenURLSelected();">
|
||||
<menupopup>
|
||||
<menuseparator/>
|
||||
<menuitem label="&zotero.preferences.openurl.custom;" value="custom" selected="true"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</vbox>
|
||||
<button id="openURLSearchButton" label="&zotero.preferences.openurl.search;" oncommand="populateOpenURLResolvers()"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.openurl.server;"/>
|
||||
<textbox id="openURLServerField" flex="1" oninput="onOpenURLCustomized();" preference="pref-openURL-resolver"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.openurl.version;" control="openURLVersionMenu"/>
|
||||
<menulist id="openURLVersionMenu" oncommand="onOpenURLCustomized();" preference="pref-openURL-version">
|
||||
<menupopup>
|
||||
<menuitem label="0.1" value="0.1"/>
|
||||
<menuitem label="1.0" value="1.0"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<separator/>
|
||||
</prefpane>
|
||||
|
||||
<!-- These mess up the prefwindow (more) if they come before the prefpanes
|
||||
|
|
|
@ -43,7 +43,7 @@ Zotero.Attachments = new function(){
|
|||
var self = this;
|
||||
|
||||
|
||||
function importFromFile(file, sourceItemID){
|
||||
function importFromFile(file, sourceItemID, libraryID) {
|
||||
Zotero.debug('Importing attachment from file');
|
||||
|
||||
var title = file.leafName;
|
||||
|
@ -61,6 +61,9 @@ Zotero.Attachments = new function(){
|
|||
var parentItem = Zotero.Items.get(sourceItemID);
|
||||
attachmentItem.libraryID = parentItem.libraryID;
|
||||
}
|
||||
else if (libraryID) {
|
||||
attachmentItem.libraryID = libraryID;
|
||||
}
|
||||
attachmentItem.setField('title', title);
|
||||
attachmentItem.setSource(sourceItemID);
|
||||
attachmentItem.attachmentLinkMode = this.LINK_MODE_IMPORTED_FILE;
|
||||
|
@ -979,6 +982,54 @@ Zotero.Attachments = new function(){
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of files in the attachment directory
|
||||
*
|
||||
* Only counts if MIME type is text/html
|
||||
*
|
||||
* @param {Zotero.Item} item Attachment item
|
||||
*/
|
||||
this.getNumFiles = function (item) {
|
||||
var funcName = "Zotero.Attachments.getNumFiles()";
|
||||
|
||||
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:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid attachment link mode in " + funcName);
|
||||
}
|
||||
|
||||
if (item.attachmentMIMEType != 'text/html') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var file = item.getFile();
|
||||
if (!file) {
|
||||
throw ("File not found in " + funcName);
|
||||
}
|
||||
|
||||
var numFiles = 0;
|
||||
var parentDir = file.parent;
|
||||
var files = parentDir.directoryEntries;
|
||||
while (files.hasMoreElements()) {
|
||||
file = files.getNext();
|
||||
file.QueryInterface(Components.interfaces.nsIFile);
|
||||
if (file.leafName.indexOf('.') == 0) {
|
||||
continue;
|
||||
}
|
||||
numFiles++;
|
||||
}
|
||||
return numFiles;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Zotero.Item} item
|
||||
* @param {Boolean} [skipHidden=FALSE] Don't count hidden files
|
||||
|
@ -1026,6 +1077,45 @@ Zotero.Attachments = new function(){
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy attachment item, including files, to another library
|
||||
*/
|
||||
this.copyAttachmentToLibrary = function (attachment, libraryID, sourceItemID) {
|
||||
var linkMode = attachment.attachmentLinkMode;
|
||||
|
||||
if (attachment.libraryID == libraryID) {
|
||||
throw ("Attachment is already in library " + libraryID);
|
||||
}
|
||||
|
||||
var newAttachment = new Zotero.Item('attachment');
|
||||
newAttachment.libraryID = libraryID;
|
||||
// Link mode needs to be set when saving new attachment
|
||||
newAttachment.attachmentLinkMode = linkMode;
|
||||
if (attachment.isImportedAttachment()) {
|
||||
// Attachment path isn't copied over by clone() if libraryID is different
|
||||
newAttachment.attachmentPath = attachment.attachmentPath;
|
||||
}
|
||||
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
|
||||
var id = newAttachment.save();
|
||||
newAttachment = Zotero.Items.get(id);
|
||||
attachment.clone(false, newAttachment);
|
||||
if (sourceItemID) {
|
||||
newAttachment.setSource(sourceItemID);
|
||||
}
|
||||
newAttachment.save();
|
||||
|
||||
// Copy over files
|
||||
if (newAttachment.isImportedAttachment()) {
|
||||
var dir = Zotero.Attachments.getStorageDirectory(attachment.id);
|
||||
var newDir = Zotero.Attachments.createDirectoryForItem(newAttachment.id);
|
||||
Zotero.File.copyDirectory(dir, newDir);
|
||||
}
|
||||
|
||||
attachment.addLinkedItem(newAttachment);
|
||||
return newAttachment.id;
|
||||
}
|
||||
|
||||
|
||||
function _getFileNameFromURL(url, mimeType){
|
||||
var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
|
||||
.createInstance(Components.interfaces.nsIURL);
|
||||
|
|
|
@ -326,9 +326,14 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
|
|||
break;
|
||||
|
||||
case 'group':
|
||||
if (this._groupRowMap[ids[i]] != null) {
|
||||
rows.push(this._groupRowMap[ids[i]]);
|
||||
}
|
||||
//if (this._groupRowMap[ids[i]] != null) {
|
||||
// rows.push(this._groupRowMap[ids[i]]);
|
||||
//}
|
||||
|
||||
// For now, just reload if a group is removed, since otherwise
|
||||
// we'd have to remove collections too
|
||||
this.reload();
|
||||
this.rememberSelection(savedSelection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -635,7 +640,7 @@ Zotero.CollectionTreeView.prototype.isSelectable = function (row, col) {
|
|||
|
||||
|
||||
Zotero.CollectionTreeView.prototype.__defineGetter__('editable', function () {
|
||||
return this._getItemAtRow(this.selection.currentIndex).isEditable();
|
||||
return this._getItemAtRow(this.selection.currentIndex).editable;
|
||||
});
|
||||
|
||||
|
||||
|
@ -1029,12 +1034,11 @@ Zotero.CollectionTreeView.prototype.canDrop = function(row, orient, dragData)
|
|||
{
|
||||
var itemGroup = this._getItemAtRow(row); //the collection we are dragging over
|
||||
|
||||
if (!itemGroup.isEditable()) {
|
||||
if (!itemGroup.editable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dataType == 'zotero/item') {
|
||||
|
||||
if(itemGroup.isBucket()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1048,11 +1052,14 @@ Zotero.CollectionTreeView.prototype.canDrop = function(row, orient, dragData)
|
|||
return false
|
||||
}
|
||||
|
||||
// TODO: for now, only allow regular items to be dragged to groups
|
||||
if (itemGroup.isWithinGroup() && itemGroup.ref.libraryID != item.libraryID
|
||||
&& !item.isRegularItem()) {
|
||||
if (itemGroup.isWithinGroup() && item.isAttachment()) {
|
||||
// Linked files can't be added to groups
|
||||
if (item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
return false;
|
||||
}
|
||||
skip = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: for now, skip items that are already linked
|
||||
if (itemGroup.isWithinGroup() && itemGroup.ref.libraryID != item.libraryID) {
|
||||
|
@ -1070,7 +1077,7 @@ Zotero.CollectionTreeView.prototype.canDrop = function(row, orient, dragData)
|
|||
continue;
|
||||
}
|
||||
|
||||
// Allow drag of group items to library
|
||||
// Allow drag of group items to personal library
|
||||
if (item.libraryID && (itemGroup.isLibrary()
|
||||
|| itemGroup.isCollection() && !itemGroup.isWithinGroup())) {
|
||||
// TODO: for now, skip items that are already linked
|
||||
|
@ -1106,10 +1113,17 @@ Zotero.CollectionTreeView.prototype.canDrop = function(row, orient, dragData)
|
|||
if (itemGroup.isSearch()) {
|
||||
return false;
|
||||
}
|
||||
if (dataType == 'application/x-moz-file') {
|
||||
// Don't allow folder drag
|
||||
if (dataType == 'application/x-moz-file' && data[0].isDirectory()) {
|
||||
if (data[0].isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
// Don't allow drop if no permissions
|
||||
if (!itemGroup.filesEditable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (dataType == 'zotero/collection') {
|
||||
|
@ -1263,12 +1277,19 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
|
|||
*/
|
||||
}
|
||||
|
||||
// Standalone attachment
|
||||
if (item.isAttachment()) {
|
||||
var id = Zotero.Attachments.copyAttachmentToLibrary(item, targetLibraryID);
|
||||
newIDs.push(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create new unsaved clone item in target library
|
||||
var newItem = new Zotero.Item(item.itemTypeID);
|
||||
newItem.libraryID = targetLibraryID;
|
||||
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
|
||||
var id = newItem.save();
|
||||
var newItem = Zotero.Items.get(id);
|
||||
newItem = Zotero.Items.get(id);
|
||||
item.clone(false, newItem);
|
||||
newItem.save();
|
||||
//var id = newItem.save();
|
||||
|
@ -1277,6 +1298,62 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
|
|||
// Record link
|
||||
item.addLinkedItem(newItem);
|
||||
newIDs.push(id);
|
||||
|
||||
if (item.isNote()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// For regular items, add child items if prefs and permissions allow
|
||||
|
||||
// Child notes
|
||||
if (Zotero.Prefs.get('groups.copyChildNotes')) {
|
||||
var noteIDs = item.getNotes();
|
||||
var notes = Zotero.Items.get(noteIDs);
|
||||
for each(var note in notes) {
|
||||
var newNote = new Zotero.Item('note');
|
||||
newNote.libraryID = targetLibraryID;
|
||||
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
|
||||
var id = newNote.save();
|
||||
newNote = Zotero.Items.get(id);
|
||||
note.clone(false, newNote);
|
||||
newNote.setSource(newItem.id);
|
||||
newNote.save();
|
||||
|
||||
note.addLinkedItem(newNote);
|
||||
}
|
||||
}
|
||||
|
||||
// Child attachments
|
||||
var copyChildLinks = Zotero.Prefs.get('groups.copyChildLinks');
|
||||
var copyChildFileAttachments = Zotero.Prefs.get('groups.copyChildFileAttachments');
|
||||
if (copyChildLinks || copyChildFileAttachments) {
|
||||
var attachmentIDs = item.getAttachments();
|
||||
var attachments = Zotero.Items.get(attachmentIDs);
|
||||
for each(var attachment in attachments) {
|
||||
var linkMode = attachment.attachmentLinkMode;
|
||||
|
||||
// Skip linked files
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip imported files if we don't have pref and permissions
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
if (!copyChildLinks) {
|
||||
Zotero.debug("Skipping child link attachment on drag");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!copyChildFileAttachments || !itemGroup.filesEditable) {
|
||||
Zotero.debug("Skipping child file attachment on drag");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var id = Zotero.Attachments.copyAttachmentToLibrary(attachment, targetLibraryID, newItem.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toReconcile.length) {
|
||||
|
@ -1342,12 +1419,11 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
|
|||
return;
|
||||
}
|
||||
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
|
||||
// FIXME: temporarily disable dragging in of files
|
||||
if (dataType == 'application/x-moz-file' && itemGroup.isWithinGroup()) {
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
ps.alert(null, "", "Files cannot currently be added to group libraries.");
|
||||
return;
|
||||
if (itemGroup.isWithinGroup()) {
|
||||
var targetLibraryID = itemGroup.ref.libraryID;
|
||||
}
|
||||
else {
|
||||
var targetLibraryID = null;
|
||||
}
|
||||
|
||||
if (itemGroup.isCollection()) {
|
||||
|
@ -1398,7 +1474,7 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient)
|
|||
|
||||
try {
|
||||
Zotero.DB.beginTransaction();
|
||||
var itemID = Zotero.Attachments.importFromFile(file, false);
|
||||
var itemID = Zotero.Attachments.importFromFile(file, false, targetLibraryID);
|
||||
if (parentCollectionID) {
|
||||
var col = Zotero.Collections.get(parentCollectionID);
|
||||
if (col) {
|
||||
|
@ -1545,11 +1621,10 @@ Zotero.ItemGroup.prototype.isWithinGroup = function () {
|
|||
return this.ref && !!this.ref.libraryID;
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isEditable = function () {
|
||||
Zotero.ItemGroup.prototype.__defineGetter__('editable', function () {
|
||||
if (this.isTrash() || this.isShare()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.isWithinGroup()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1564,12 +1639,33 @@ Zotero.ItemGroup.prototype.isEditable = function () {
|
|||
var group = Zotero.Groups.get(groupID);
|
||||
return group.editable;
|
||||
}
|
||||
else {
|
||||
throw ("Unknown library type '" + type + "' in Zotero.ItemGroup.isEditable()");
|
||||
}
|
||||
}
|
||||
throw ("Unknown library type '" + type + "' in Zotero.ItemGroup.editable");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
Zotero.ItemGroup.prototype.__defineGetter__('filesEditable', function () {
|
||||
if (this.isTrash() || this.isShare()) {
|
||||
return false;
|
||||
}
|
||||
if (!this.isWithinGroup()) {
|
||||
return true;
|
||||
}
|
||||
var libraryID = this.ref.libraryID;
|
||||
if (this.isGroup()) {
|
||||
return this.ref.filesEditable;
|
||||
}
|
||||
if (this.isCollection()) {
|
||||
var type = Zotero.Libraries.getType(libraryID);
|
||||
if (type == 'group') {
|
||||
var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
|
||||
var group = Zotero.Groups.get(groupID);
|
||||
return group.filesEditable;
|
||||
}
|
||||
throw ("Unknown library type '" + type + "' in Zotero.ItemGroup.filesEditable");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
Zotero.ItemGroup.prototype.getName = function()
|
||||
{
|
||||
|
|
|
@ -327,7 +327,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
|
|||
|
||||
|
||||
this.editCheck = function (obj) {
|
||||
if (!Zotero.Sync.Server.syncInProgress && !this.isEditable(obj)) {
|
||||
if (!Zotero.Sync.Server.syncInProgress && !Zotero.Sync.Storage.syncInProgress && !this.isEditable(obj)) {
|
||||
throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -286,6 +286,10 @@ Zotero.Group.prototype.save = function () {
|
|||
* Deletes group and all descendant objects
|
||||
**/
|
||||
Zotero.Group.prototype.erase = function() {
|
||||
// Don't send notifications for items and other groups objects that are deleted,
|
||||
// since we're really only removing the group from the client
|
||||
var notifierDisabled = Zotero.Notifier.disable();
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var sql, ids, obj;
|
||||
|
@ -331,6 +335,9 @@ Zotero.Group.prototype.erase = function() {
|
|||
sql = "DELETE FROM syncDeleteLog WHERE libraryID=?";
|
||||
Zotero.DB.query(sql, this.libraryID);
|
||||
|
||||
var prefix = "groups/" + this.id;
|
||||
Zotero.Relations.eraseByPathPrefix(prefix);
|
||||
|
||||
// Delete group
|
||||
sql = "DELETE FROM groups WHERE groupID=?";
|
||||
ids = Zotero.DB.query(sql, this.id)
|
||||
|
@ -342,6 +349,10 @@ Zotero.Group.prototype.erase = function() {
|
|||
|
||||
Zotero.DB.commitTransaction();
|
||||
|
||||
if (notifierDisabled) {
|
||||
Zotero.Notifier.enable();
|
||||
}
|
||||
|
||||
Zotero.Notifier.trigger('delete', 'group', this.id, notifierData);
|
||||
}
|
||||
|
||||
|
|
|
@ -2540,28 +2540,16 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
|
|||
/**
|
||||
* _row_ is optional itemAttachments row if available to skip queries
|
||||
*/
|
||||
Zotero.Item.prototype.getFilename = function (row) {
|
||||
Zotero.Item.prototype.getFilename = function () {
|
||||
if (!this.isAttachment()) {
|
||||
throw ("getFileName() can only be called on attachment items in Zotero.Item.getFilename()");
|
||||
}
|
||||
|
||||
if (!row) {
|
||||
var row = {
|
||||
linkMode: this.attachmentLinkMode,
|
||||
path: this.attachmentPath
|
||||
};
|
||||
}
|
||||
|
||||
if (row.linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
throw ("getFilename() cannot be called on link attachments in Zotero.Item.getFilename()");
|
||||
}
|
||||
|
||||
if (this.isImportedAttachment()) {
|
||||
var matches = row.path.match("^storage:(.+)$");
|
||||
return matches[1];
|
||||
}
|
||||
|
||||
var file = this.getFile();
|
||||
var file = this.getFile(null, true);
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2602,10 +2590,12 @@ Zotero.Item.prototype.renameAttachmentFile = function(newName, overwrite) {
|
|||
}
|
||||
|
||||
file.moveTo(null, newName);
|
||||
// Update mod time so the file syncs
|
||||
// Update mod time and clear hash so the file syncs
|
||||
dest.lastModifiedTime = new Date();
|
||||
this.relinkAttachmentFile(dest);
|
||||
|
||||
Zotero.Sync.Storage.setSyncedHash(this.id, null, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
|
@ -2947,6 +2937,32 @@ Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', function ()
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* MD5 hash of an attachment file
|
||||
*
|
||||
* Note: This is the hash of the file itself, not the last-known hash
|
||||
* of the file on the storage server as stored in the database
|
||||
*
|
||||
* @return {String} MD5 hash of file as hex string
|
||||
*/
|
||||
Zotero.Item.prototype.__defineGetter__('attachmentHash', function () {
|
||||
if (!this.isAttachment()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this.id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var file = this.getFile();
|
||||
if (!file) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Zotero.Utilities.prototype.md5(file);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Return plain text of attachment content
|
||||
*
|
||||
|
|
|
@ -421,7 +421,6 @@ Zotero.Items = new function() {
|
|||
var item = this.get(id);
|
||||
if (!item) {
|
||||
Zotero.debug('Item ' + id + ' does not exist in Items.erase()!', 1);
|
||||
Zotero.Notifier.trigger('delete', 'item', id);
|
||||
continue;
|
||||
}
|
||||
item.erase(eraseChildren); // calls unload()
|
||||
|
|
|
@ -6,6 +6,7 @@ Zotero.Relations = new function () {
|
|||
owl: 'http://www.w3.org/2002/07/owl#'
|
||||
};
|
||||
|
||||
var _prefix = "http://zotero.org/";
|
||||
|
||||
this.get = function (id) {
|
||||
if (typeof id != 'number') {
|
||||
|
@ -76,6 +77,37 @@ Zotero.Relations = new function () {
|
|||
}
|
||||
|
||||
|
||||
this.updateUser = function (fromUserID, fromLibraryID, toUserID, toLibraryID) {
|
||||
if (!fromUserID) {
|
||||
throw ("Invalid source userID " + fromUserID + " in Zotero.Relations.updateUserID");
|
||||
}
|
||||
if (!fromLibraryID) {
|
||||
throw ("Invalid source libraryID " + fromLibraryID + " in Zotero.Relations.updateUserID");
|
||||
}
|
||||
if (!toUserID) {
|
||||
throw ("Invalid target userID " + toUserID + " in Zotero.Relations.updateUserID");
|
||||
}
|
||||
if (!toLibraryID) {
|
||||
throw ("Invalid target libraryID " + toLibraryID + " in Zotero.Relations.updateUserID");
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var sql = "UPDATE relations SET libraryID=? WHERE libraryID=?";
|
||||
Zotero.DB.query(sql, [fromLibraryID, toLibraryID]);
|
||||
|
||||
sql = "UPDATE relations SET "
|
||||
+ "subject=REPLACE(subject, 'zotero.org/users/" + fromUserID + "', "
|
||||
+ "'zotero.org/users/" + toUserID + "'), "
|
||||
+ "object=REPLACE(object, 'zotero.org/users/" + fromUserID + "', "
|
||||
+ "'zotero.org/users/" + toUserID + "') "
|
||||
+ "WHERE predicate='owl:sameAs'";
|
||||
Zotero.DB.query(sql);
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
||||
|
||||
this.add = function (libraryID, subject, predicate, object) {
|
||||
predicate = _getPrefixAndValue(predicate).join(':');
|
||||
|
||||
|
@ -109,6 +141,13 @@ Zotero.Relations = new function () {
|
|||
}
|
||||
|
||||
|
||||
this.eraseByPathPrefix = function (prefix) {
|
||||
prefix = _prefix + prefix + '%';
|
||||
sql = "DELETE FROM relations WHERE subject LIKE ? OR object LIKE ?";
|
||||
Zotero.DB.query(sql, [prefix, prefix]);
|
||||
}
|
||||
|
||||
|
||||
this.xmlToRelation = function (xml) {
|
||||
var relation = new Zotero.Relation;
|
||||
var libraryID = xml.@libraryID.toString();
|
||||
|
|
|
@ -421,7 +421,9 @@ Zotero.DBConnection.prototype.getLastErrorString = function () {
|
|||
Zotero.DBConnection.prototype.beginTransaction = function () {
|
||||
// TODO: limit to Zotero.DB, not all Zotero.DBConnections?
|
||||
if (Zotero.waiting) {
|
||||
throw ("Cannot access database layer during active Zotero.wait()");
|
||||
var msg = "Cannot access database layer during active Zotero.wait()";
|
||||
Zotero.debug(msg, 2);
|
||||
throw (msg);
|
||||
}
|
||||
|
||||
var db = this._getDBConnection();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
Zotero.Error = function (message, error) {
|
||||
Zotero.Error = function (message, error, data) {
|
||||
this.name = "ZOTERO_ERROR";
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
if (parseInt(error) == error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
@ -14,6 +15,11 @@ Zotero.Error.ERROR_MISSING_OBJECT = 1;
|
|||
Zotero.Error.ERROR_FULL_SYNC_REQUIRED = 2;
|
||||
Zotero.Error.ERROR_SYNC_USERNAME_NOT_SET = 3;
|
||||
Zotero.Error.ERROR_INVALID_SYNC_LOGIN = 4;
|
||||
Zotero.Error.ERROR_ZFS_OVER_QUOTA = 5;
|
||||
Zotero.Error.ERROR_ZFS_UPLOAD_QUEUE_LIMIT = 6;
|
||||
Zotero.Error.ERROR_ZFS_FILE_EDITING_DENIED = 7;
|
||||
//Zotero.Error.ERROR_SYNC_EMPTY_RESPONSE_FROM_SERVER = 6;
|
||||
//Zotero.Error.ERROR_SYNC_INVALID_RESPONSE_FROM_SERVER = 7;
|
||||
|
||||
Zotero.Error.prototype.toString = function () {
|
||||
return this.message;
|
||||
|
|
|
@ -148,25 +148,6 @@ Zotero.File = new function(){
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {nsIFile} file
|
||||
* @return {String} Base-64 representation of MD5 hash
|
||||
*/
|
||||
this.getFileHash = function (file) {
|
||||
var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
fis.init(file, -1, -1, false);
|
||||
|
||||
var hash = Components.classes["@mozilla.org/security/hash;1"].
|
||||
createInstance(Components.interfaces.nsICryptoHash);
|
||||
hash.init(Components.interfaces.nsICryptoHash.MD5);
|
||||
hash.updateFromStream(fis, 4294967295); // PR_UINT32_MAX
|
||||
hash = hash.finish(true);
|
||||
fis.close();
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write string to a file, overwriting existing file if necessary
|
||||
*/
|
||||
|
@ -199,6 +180,19 @@ Zotero.File = new function(){
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies all files from dir into newDir
|
||||
*/
|
||||
this.copyDirectory = function (dir, newDir) {
|
||||
var otherFiles = dir.directoryEntries;
|
||||
while (otherFiles.hasMoreElements()) {
|
||||
var file = otherFiles.getNext();
|
||||
file.QueryInterface(Components.interfaces.nsIFile);
|
||||
file.copyTo(newDir, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.createDirectoryIfMissing = function (dir) {
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
if (dir.exists() && !dir.isDirectory()) {
|
||||
|
|
|
@ -2218,16 +2218,8 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
|
|||
}
|
||||
}
|
||||
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
|
||||
// FIXME: temporarily disable dragging in of files
|
||||
if (dataType == 'application/x-moz-file' && itemGroup.isWithinGroup()) {
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
ps.alert(null, "", "Files cannot currently be added to group libraries.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Disallow drop into read-only libraries
|
||||
if (!itemGroup.isEditable()) {
|
||||
if (!itemGroup.editable) {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
|
@ -2235,6 +2227,13 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
|
|||
return;
|
||||
}
|
||||
|
||||
if (itemGroup.isWithinGroup()) {
|
||||
var targetLibraryID = itemGroup.ref.libraryID;
|
||||
}
|
||||
else {
|
||||
var targetLibraryID = null;
|
||||
}
|
||||
|
||||
var sourceItemID = false;
|
||||
var parentCollectionID = false;
|
||||
|
||||
|
@ -2245,9 +2244,6 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
|
|||
else if (itemGroup.isCollection()) {
|
||||
var parentCollectionID = itemGroup.ref.id;
|
||||
}
|
||||
else if (itemGroup.isLibrary(true)) {
|
||||
var libraryID = itemGroup.ref.libraryID;
|
||||
}
|
||||
|
||||
var unlock = Zotero.Notifier.begin(true);
|
||||
try {
|
||||
|
@ -2278,15 +2274,10 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
|
|||
|
||||
// Still string, so remote URL
|
||||
if (typeof file == 'string') {
|
||||
if (sourceItemID) {
|
||||
Zotero.Attachments.importFromURL(url, sourceItemID);
|
||||
}
|
||||
else {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
win.ZoteroPane.addItemFromURL(url, 'temporaryPDFHack'); // TODO: don't do this
|
||||
}
|
||||
win.ZoteroPane.addItemFromURL(url, 'temporaryPDFHack', row); // TODO: don't do this
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -2295,7 +2286,7 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient)
|
|||
|
||||
try {
|
||||
Zotero.DB.beginTransaction();
|
||||
var itemID = Zotero.Attachments.importFromFile(file, sourceItemID);
|
||||
var itemID = Zotero.Attachments.importFromFile(file, sourceItemID, targetLibraryID);
|
||||
if (parentCollectionID) {
|
||||
var col = Zotero.Collections.get(parentCollectionID);
|
||||
if (col) {
|
||||
|
|
|
@ -2451,6 +2451,21 @@ Zotero.Schema = new function(){
|
|||
Zotero.DB.query("CREATE INDEX IF NOT EXISTS itemData_fieldID ON itemData(fieldID)");
|
||||
}
|
||||
|
||||
if (i==63) {
|
||||
Zotero.DB.query("ALTER TABLE itemAttachments ADD COLUMN storageHash TEXT");
|
||||
|
||||
var protocol = Zotero.Prefs.get('sync.storage.protocol');
|
||||
if (protocol == 'webdav') {
|
||||
Zotero.Prefs.set('sync.storage.scheme', 'http');
|
||||
}
|
||||
else {
|
||||
Zotero.Prefs.set('sync.storage.protocol', 'webdav');
|
||||
Zotero.Prefs.set('sync.storage.scheme', 'https');
|
||||
}
|
||||
|
||||
Zotero.DB.query("UPDATE version SET schema='storage_webdav' WHERE schema='storage'");
|
||||
}
|
||||
|
||||
Zotero.wait();
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
166
chrome/content/zotero/xpcom/storage/session.js
Normal file
166
chrome/content/zotero/xpcom/storage/session.js
Normal file
|
@ -0,0 +1,166 @@
|
|||
Zotero.Sync.Storage.Session = function (module, callbacks) {
|
||||
switch (module) {
|
||||
case 'webdav':
|
||||
this._session = new Zotero.Sync.Storage.Session.WebDAV(callbacks);
|
||||
break;
|
||||
|
||||
case 'zfs':
|
||||
this._session = new Zotero.Sync.Storage.Session.ZFS(callbacks);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid storage session module '" + module + "'");
|
||||
}
|
||||
|
||||
this.module = module;
|
||||
this.onError = callbacks.onError;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.__defineGetter__('name', function () this._session.name);
|
||||
Zotero.Sync.Storage.Session.prototype.__defineGetter__('includeUserFiles', function () this._session.includeUserFiles);
|
||||
Zotero.Sync.Storage.Session.prototype.__defineGetter__('includeGroupFiles', function () this._session.includeGroupFiles);
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.__defineGetter__('enabled', function () {
|
||||
try {
|
||||
return this._session.enabled;
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.__defineGetter__('verified', function () {
|
||||
try {
|
||||
return this._session.verified;
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.__defineGetter__('active', function () {
|
||||
try {
|
||||
return this._session.active;
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.__defineGetter__('username', function () {
|
||||
try {
|
||||
return this._session.username;
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.__defineGetter__('password', function () {
|
||||
try {
|
||||
return this._session.password;
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.__defineSetter__('password', function (val) {
|
||||
try {
|
||||
this._session.password = val;
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.initFromPrefs = function () {
|
||||
try {
|
||||
return this._session.init();
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.initFromPrefs = function () {
|
||||
try {
|
||||
return this._session.initFromPrefs();
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.downloadFile = function (request) {
|
||||
try {
|
||||
this._session.downloadFile(request);
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.uploadFile = function (request) {
|
||||
try {
|
||||
this._session.uploadFile(request);
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.getLastSyncTime = function (callback) {
|
||||
try {
|
||||
this._session.getLastSyncTime(callback);
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.setLastSyncTime = function (callback, useLastSyncTime) {
|
||||
try {
|
||||
this._session.setLastSyncTime(callback, useLastSyncTime);
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.checkServer = function (callback) {
|
||||
try {
|
||||
this._session.checkServer(callback);
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.checkServerCallback = function (uri, status, authRequired, window, skipSuccessMessage) {
|
||||
try {
|
||||
return this._session.checkServerCallback(uri, status, authRequired, window, skipSuccessMessage);
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.purgeDeletedStorageFiles = function (callback) {
|
||||
try {
|
||||
this._session.purgeDeletedStorageFiles(callback);
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.purgeOrphanedStorageFiles = function (callback) {
|
||||
try {
|
||||
this._session.purgeOrphanedStorageFiles(callback);
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
1401
chrome/content/zotero/xpcom/storage/webdav.js
Normal file
1401
chrome/content/zotero/xpcom/storage/webdav.js
Normal file
File diff suppressed because it is too large
Load diff
852
chrome/content/zotero/xpcom/storage/zfs.js
Normal file
852
chrome/content/zotero/xpcom/storage/zfs.js
Normal file
|
@ -0,0 +1,852 @@
|
|||
Zotero.Sync.Storage.Session.ZFS = function (callbacks) {
|
||||
this.onChangesMade = callbacks.onChangesMade ? callbacks.onChangesMade : function () {};
|
||||
this.onError = callbacks.onError ? callbacks.onError : function () {};
|
||||
|
||||
this._rootURI;
|
||||
this._userURI;
|
||||
this._cachedCredentials = false;
|
||||
this._lastSyncTime = null;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.name = "ZFS";
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.__defineGetter__('includeUserFiles', function () {
|
||||
return Zotero.Prefs.get("sync.storage.enabled") && Zotero.Prefs.get("sync.storage.protocol") == 'zotero';
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.__defineGetter__('includeGroupFiles', function () {
|
||||
return Zotero.Prefs.get("sync.storage.groups.enabled");
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.__defineGetter__('enabled', function () {
|
||||
return this.includeUserFiles || this.includeGroupFiles;
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.__defineGetter__('active', function () {
|
||||
return this.enabled;
|
||||
});
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.__defineGetter__('rootURI', function () {
|
||||
if (!this._rootURI) {
|
||||
throw ("Root URI not initialized in Zotero.Sync.Storage.Session.ZFS.rootURI");
|
||||
}
|
||||
return this._rootURI.clone();
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.__defineGetter__('userURI', function () {
|
||||
if (!this._userURI) {
|
||||
throw ("User URI not initialized in Zotero.Sync.Storage.Session.ZFS.userURI");
|
||||
}
|
||||
return this._userURI.clone();
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.init = function (url, username, password) {
|
||||
var ios = Components.classes["@mozilla.org/network/io-service;1"].
|
||||
getService(Components.interfaces.nsIIOService);
|
||||
try {
|
||||
var uri = ios.newURI(url, null, null);
|
||||
if (username) {
|
||||
uri.username = username;
|
||||
uri.password = password;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e);
|
||||
Components.utils.reportError(e);
|
||||
return false;
|
||||
}
|
||||
this._rootURI = uri;
|
||||
|
||||
uri = uri.clone();
|
||||
uri.spec += 'users/' + Zotero.userID + '/';
|
||||
this._userURI = uri;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.initFromPrefs = function () {
|
||||
var url = ZOTERO_CONFIG.API_URL;
|
||||
var username = Zotero.Sync.Server.username;
|
||||
var password = Zotero.Sync.Server.password;
|
||||
return this.init(url, username, password);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get file metadata on storage server
|
||||
*
|
||||
* @param {Zotero.Item} item
|
||||
* @param {Function} callback Callback f(item, etag)
|
||||
*/
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype._getStorageFileInfo = function (item, callback) {
|
||||
var uri = this._getItemURI(item);
|
||||
|
||||
var self = this;
|
||||
|
||||
Zotero.Utilities.HTTP.doHead(uri, function (req) {
|
||||
var funcName = "Zotero.Sync.Storage.Session.ZFS._getStorageFileInfo()";
|
||||
|
||||
if (req.status == 404) {
|
||||
callback(item, false);
|
||||
return;
|
||||
}
|
||||
else if (req.status != 200) {
|
||||
Zotero.debug(req.responseText);
|
||||
self.onError("Unexpected status code " + req.status + " in " + funcName);
|
||||
return;
|
||||
}
|
||||
|
||||
var info = {};
|
||||
info.hash = req.getResponseHeader('ETag');
|
||||
info.filename = req.getResponseHeader('X-Zotero-Filename');
|
||||
info.mtime = req.getResponseHeader('X-Zotero-Modification-Time');
|
||||
info.compressed = req.getResponseHeader('X-Zotero-Compressed') == 'Yes';
|
||||
Zotero.debug(info);
|
||||
|
||||
if (!info) {
|
||||
callback(item, false);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(item, info);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begin download process for individual file
|
||||
*
|
||||
* @param {Zotero.Sync.Storage.Request} [request]
|
||||
*/
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.downloadFile = function (request) {
|
||||
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
|
||||
if (!item) {
|
||||
throw ("Item '" + request.name + "' not found in Zotero.Sync.Storage.Session.ZFS.downloadFile()");
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
// Retrieve file info from server to store locally afterwards
|
||||
this._getStorageFileInfo(item, function (item, info) {
|
||||
if (!request.isRunning()) {
|
||||
Zotero.debug("Download request '" + request.name
|
||||
+ "' is no longer running after getting remote file info");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!info) {
|
||||
Zotero.debug("Remote file not found for item " + item.libraryID + "/" + item.key);
|
||||
request.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var syncModTime = info.mtime;
|
||||
var syncHash = info.hash;
|
||||
|
||||
var file = item.getFile();
|
||||
// Skip download if local file exists and matches mod time
|
||||
if (file && file.exists()) {
|
||||
if (syncModTime == Math.round(file.lastModifiedTime / 1000)) {
|
||||
Zotero.debug("File mod time matches remote file -- skipping download");
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
var syncState = Zotero.Sync.Storage.getSyncState(item.id);
|
||||
//var updateItem = syncState != 1;
|
||||
var updateItem = false;
|
||||
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
|
||||
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
||||
Zotero.DB.commitTransaction();
|
||||
self.onChangesMade();
|
||||
request.finish();
|
||||
return;
|
||||
}
|
||||
// If not compressed, check hash, in case only timestamp changed
|
||||
else if (!info.compressed && Zotero.Utilities.prototype.md5(file) == syncHash) {
|
||||
Zotero.debug("File hash matches remote file -- skipping download");
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
var syncState = Zotero.Sync.Storage.getSyncState(item.id);
|
||||
//var updateItem = syncState != 1;
|
||||
var updateItem = false;
|
||||
if (!info.compressed) {
|
||||
Zotero.Sync.Storage.setSyncedHash(item.id, syncHash, false);
|
||||
}
|
||||
Zotero.Sync.Storage.setSyncedModificationTime(item.id, syncModTime, updateItem);
|
||||
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
||||
Zotero.DB.commitTransaction();
|
||||
self.onChangesMade();
|
||||
request.finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var destFile = Zotero.getTempDirectory();
|
||||
if (info.compressed) {
|
||||
destFile.append(item.key + '.zip.tmp');
|
||||
}
|
||||
else {
|
||||
destFile.append(item.key + '.tmp');
|
||||
if (destFile.exists()) {
|
||||
destFile.remove(false);
|
||||
}
|
||||
}
|
||||
if (destFile.exists()) {
|
||||
destFile.remove(false);
|
||||
}
|
||||
|
||||
var listener = new Zotero.Sync.Storage.StreamListener(
|
||||
{
|
||||
onStart: function (request, data) {
|
||||
if (data.request.isFinished()) {
|
||||
Zotero.debug("Download request " + data.request.name
|
||||
+ " stopped before download started -- closing channel");
|
||||
request.cancel(0x804b0002); // NS_BINDING_ABORTED
|
||||
return;
|
||||
}
|
||||
},
|
||||
onProgress: function (a, b, c) {
|
||||
request.onProgress(a, b, c)
|
||||
},
|
||||
onStop: function (request, status, response, data) {
|
||||
if (status != 200) {
|
||||
self.onError("Unexpected status code " + status
|
||||
+ " for request " + data.request.name + " in Zotero.Sync.Storage.Session.ZFS.downloadFile()");
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't try to process if the request has been cancelled
|
||||
if (data.request.isFinished()) {
|
||||
Zotero.debug("Download request " + data.request.name
|
||||
+ " is no longer running after file download");
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.debug("Finished download of " + destFile.path);
|
||||
|
||||
try {
|
||||
Zotero.Sync.Storage.processDownload(data);
|
||||
data.request.finish();
|
||||
}
|
||||
catch (e) {
|
||||
self.onError(e);
|
||||
}
|
||||
},
|
||||
request: request,
|
||||
item: item,
|
||||
compressed: info.compressed,
|
||||
syncModTime: syncModTime,
|
||||
syncHash: syncHash
|
||||
}
|
||||
);
|
||||
|
||||
var uri = self._getItemURI(item);
|
||||
|
||||
// Don't display password in console
|
||||
var disp = uri.clone();
|
||||
if (disp.password) {
|
||||
disp.password = "********";
|
||||
}
|
||||
|
||||
Zotero.debug('Saving ' + disp.spec + ' with saveURI()');
|
||||
const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
|
||||
var wbp = Components
|
||||
.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
|
||||
.createInstance(nsIWBP);
|
||||
wbp.persistFlags = nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
|
||||
|
||||
wbp.progressListener = listener;
|
||||
wbp.saveURI(uri, null, null, null, null, destFile);
|
||||
}
|
||||
catch (e) {
|
||||
self.onError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.uploadFile = function (request) {
|
||||
var self = this;
|
||||
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
|
||||
if (Zotero.Attachments.getNumFiles(item) > 1) {
|
||||
Zotero.Sync.Storage.createUploadFile(request, function (data) { self._processUploadFile(data); });
|
||||
}
|
||||
else {
|
||||
this._processUploadFile({ request: request });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upload the file to the server
|
||||
*
|
||||
* @param {Object} Object with 'request' property
|
||||
* @return {void}
|
||||
*/
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype._processUploadFile = function (data) {
|
||||
/*
|
||||
_updateSizeMultiplier(
|
||||
(100 - Zotero.Sync.Storage.compressionTracker.ratio) / 100
|
||||
);
|
||||
*/
|
||||
|
||||
var request = data.request;
|
||||
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
|
||||
|
||||
var self = this;
|
||||
|
||||
this._getStorageFileInfo(item, function (item, info) {
|
||||
if (request.isFinished()) {
|
||||
Zotero.debug("Upload request '" + request.name
|
||||
+ "' is no longer running after getting file info");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check for conflict
|
||||
if (Zotero.Sync.Storage.getSyncState(item.id)
|
||||
!= Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD) {
|
||||
if (info) {
|
||||
// Remote mod time
|
||||
var mtime = info.mtime;
|
||||
// Local file time
|
||||
var fmtime = item.attachmentModificationTime;
|
||||
|
||||
if (fmtime == mtime) {
|
||||
Zotero.debug("File mod time matches remote file -- skipping upload");
|
||||
|
||||
Zotero.debug(Zotero.Sync.Storage.getSyncedModificationTime(item.id));
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
var syncState = Zotero.Sync.Storage.getSyncState(item.id);
|
||||
//Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime, true);
|
||||
Zotero.Sync.Storage.setSyncedModificationTime(item.id, fmtime);
|
||||
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
||||
Zotero.DB.commitTransaction();
|
||||
self.onChangesMade();
|
||||
request.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var smtime = Zotero.Sync.Storage.getSyncedModificationTime(item.id);
|
||||
if (smtime != mtime) {
|
||||
var localData = { modTime: fmtime };
|
||||
var remoteData = { modTime: mtime };
|
||||
Zotero.Sync.Storage.QueueManager.addConflict(
|
||||
request.name, localData, remoteData
|
||||
);
|
||||
Zotero.debug("Conflict -- last synced file mod time "
|
||||
+ "does not match time on storage server"
|
||||
+ " (" + smtime + " != " + mtime + ")");
|
||||
request.finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Zotero.debug("Remote file not found for item " + item.id);
|
||||
}
|
||||
}
|
||||
|
||||
self._getFileUploadParameters(
|
||||
item,
|
||||
function (item, target, uploadKey, params) {
|
||||
try {
|
||||
self._postFile(request, item, target, uploadKey, params);
|
||||
}
|
||||
catch (e) {
|
||||
self.onError(e);
|
||||
}
|
||||
},
|
||||
function () {
|
||||
self._updateItemFileInfo(item);
|
||||
request.finish();
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
self.onError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get mod time of file on storage server
|
||||
*
|
||||
* @param {Zotero.Item} item
|
||||
* @param {Function} uploadCallback Callback f(request, item, target, params)
|
||||
* @param {Function} existsCallback Callback f() to call when file already exists
|
||||
* on server and uploading isn't necessary
|
||||
*/
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype._getFileUploadParameters = function (item, uploadCallback, existsCallback) {
|
||||
var uri = this._getItemURI(item);
|
||||
|
||||
if (Zotero.Attachments.getNumFiles(item) > 1) {
|
||||
var file = Zotero.getTempDirectory();
|
||||
var filename = item.key + '.zip';
|
||||
file.append(filename);
|
||||
uri.spec = uri.spec;
|
||||
var zip = true;
|
||||
}
|
||||
else {
|
||||
var file = item.getFile();
|
||||
var filename = file.leafName;
|
||||
var zip = false;
|
||||
}
|
||||
|
||||
var mtime = item.attachmentModificationTime;
|
||||
var hash = Zotero.Utilities.prototype.md5(file);
|
||||
|
||||
var body = "md5=" + hash + "&filename=" + encodeURIComponent(filename)
|
||||
+ "&filesize=" + file.fileSize + "&mtime=" + mtime;
|
||||
if (zip) {
|
||||
body += "&zip=1";
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
Zotero.Utilities.HTTP.doPost(uri, body, function (req) {
|
||||
var funcName = "Zotero.Sync.Storage.Session.ZFS._getFileUploadParameters()";
|
||||
|
||||
if (req.status == 413) {
|
||||
var retry = req.getResponseHeader('Retry-After');
|
||||
if (retry) {
|
||||
var minutes = Math.round(retry / 60);
|
||||
var e = new Zotero.Error("You have too many queued uploads. Please try again in " + minutes + " minutes.", "ZFS_UPLOAD_QUEUE_LIMIT");
|
||||
self.onError(e);
|
||||
}
|
||||
else {
|
||||
Zotero.debug(req.responseText);
|
||||
var e = new Zotero.Error("File would exceed Zotero File Storage quota", "ZFS_OVER_QUOTA");
|
||||
self.onError(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (req.status == 403) {
|
||||
Zotero.debug(req.responseText);
|
||||
|
||||
var groupID = Zotero.Groups.getGroupIDFromLibraryID(item.libraryID);
|
||||
var e = new Zotero.Error("File editing denied for group", "ZFS_FILE_EDITING_DENIED", { groupID: groupID });
|
||||
self.onError(e);
|
||||
return;
|
||||
}
|
||||
else if (req.status != 200) {
|
||||
Zotero.debug(req.responseText);
|
||||
self.onError("Unexpected status code " + req.status + " in " + funcName);
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.debug(req.responseText);
|
||||
|
||||
try {
|
||||
// Strip XML declaration and convert to E4X
|
||||
var xml = new XML(req.responseText.replace(/<\?xml.*\?>/, ''));
|
||||
}
|
||||
catch (e) {
|
||||
self.onError("Invalid response retrieving file upload parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
if (xml.name() != 'upload' && xml.name() != 'exists') {
|
||||
self.onError("Invalid response retrieving file upload parameters");
|
||||
return;
|
||||
}
|
||||
// File was already available, so uploading isn't required
|
||||
if (xml.name() == 'exists') {
|
||||
existsCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
var url = xml.url.toString();
|
||||
var uploadKey = xml.key.toString();
|
||||
var params = {}, p = '';
|
||||
for each(var param in xml.params.children()) {
|
||||
params[param.name()] = param.toString();
|
||||
}
|
||||
Zotero.debug(params);
|
||||
uploadCallback(item, url, uploadKey, params);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype._postFile = function (request, item, url, uploadKey, params) {
|
||||
if (request.isFinished()) {
|
||||
Zotero.debug("Upload request " + request.name + " is no longer running after getting upload parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
var file = this._getUploadFile(item);
|
||||
|
||||
// TODO: make sure this doesn't appear in file
|
||||
var boundary = "---------------------------" + Math.random().toString().substr(2);
|
||||
|
||||
var mis = Components.classes["@mozilla.org/io/multiplex-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIMultiplexInputStream);
|
||||
|
||||
// Add parameters
|
||||
for (var key in params) {
|
||||
var storage = Components.classes["@mozilla.org/storagestream;1"]
|
||||
.createInstance(Components.interfaces.nsIStorageStream);
|
||||
storage.init(4096, 4294967295, null); // PR_UINT32_MAX
|
||||
var out = storage.getOutputStream(0);
|
||||
|
||||
var conv = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterOutputStream);
|
||||
conv.init(out, null, 4096, "?");
|
||||
|
||||
var str = "--" + boundary + '\r\nContent-Disposition: form-data; name="' + key + '"'
|
||||
+ '\r\n\r\n' + params[key] + '\r\n';
|
||||
conv.writeString(str);
|
||||
conv.close();
|
||||
|
||||
var instr = storage.newInputStream(0);
|
||||
mis.appendStream(instr);
|
||||
}
|
||||
|
||||
// Add file
|
||||
var sis = Components.classes["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIStringInputStream);
|
||||
var str = "--" + boundary + '\r\nContent-Disposition: form-data; name="file"\r\n\r\n';
|
||||
sis.setData(str, -1);
|
||||
mis.appendStream(sis);
|
||||
|
||||
var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
fis.init(file, 0x01, 0, Components.interfaces.nsIFileInputStream.CLOSE_ON_EOF);
|
||||
|
||||
var bis = Components.classes["@mozilla.org/network/buffered-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIBufferedInputStream)
|
||||
bis.init(fis, 64 * 1024);
|
||||
mis.appendStream(bis);
|
||||
|
||||
// End request
|
||||
var sis = Components.classes["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIStringInputStream);
|
||||
var str = "\r\n--" + boundary + "--";
|
||||
sis.setData(str, -1);
|
||||
mis.appendStream(sis);
|
||||
|
||||
|
||||
/* var cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
|
||||
createInstance(Components.interfaces.nsIConverterInputStream);
|
||||
cstream.init(mis, "UTF-8", 0, 0); // you can use another encoding here if you wish
|
||||
|
||||
let (str = {}) {
|
||||
cstream.readString(-1, str); // read the whole file and put it in str.value
|
||||
data = str.value;
|
||||
}
|
||||
cstream.close(); // this closes fstream
|
||||
alert(data);
|
||||
*/
|
||||
|
||||
var ios = Components.classes["@mozilla.org/network/io-service;1"].
|
||||
getService(Components.interfaces.nsIIOService);
|
||||
var uri = ios.newURI(url, null, null);
|
||||
var channel = ios.newChannelFromURI(uri);
|
||||
|
||||
channel.QueryInterface(Components.interfaces.nsIUploadChannel);
|
||||
channel.setUploadStream(mis, "multipart/form-data", -1);
|
||||
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
channel.requestMethod = 'POST';
|
||||
channel.allowPipelining = false;
|
||||
channel.setRequestHeader('Keep-Alive', '', false);
|
||||
channel.setRequestHeader('Connection', '', false);
|
||||
channel.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary, false);
|
||||
//channel.setRequestHeader('Date', date, false);
|
||||
|
||||
var self = this;
|
||||
|
||||
request.setChannel(channel);
|
||||
|
||||
var listener = new Zotero.Sync.Storage.StreamListener(
|
||||
{
|
||||
onProgress: function (a, b, c) {
|
||||
request.onProgress(a, b, c);
|
||||
},
|
||||
onStop: function (httpRequest, status, response, data) { self._onUploadComplete(httpRequest, status, response, data); },
|
||||
onCancel: function (httpRequest, status, data) { self._onUploadCancel(httpRequest, status, data); },
|
||||
request: request,
|
||||
item: item,
|
||||
uploadKey: uploadKey,
|
||||
streams: [mis]
|
||||
}
|
||||
);
|
||||
channel.notificationCallbacks = listener;
|
||||
|
||||
var dispURI = uri.clone();
|
||||
if (dispURI.password) {
|
||||
dispURI.password = '********';
|
||||
}
|
||||
Zotero.debug("HTTP POST of " + file.leafName + " to " + dispURI.spec);
|
||||
|
||||
channel.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype._onUploadComplete = function (httpRequest, status, response, data) {
|
||||
var request = data.request;
|
||||
var item = data.item;
|
||||
var uploadKey = data.uploadKey;
|
||||
|
||||
Zotero.debug("Upload of attachment " + item.key
|
||||
+ " finished with status code " + status);
|
||||
|
||||
Zotero.debug(response);
|
||||
|
||||
switch (status) {
|
||||
case 201:
|
||||
break;
|
||||
|
||||
default:
|
||||
this.onError("Unexpected file upload status " + status
|
||||
+ " in Zotero.Sync.Storage._onUploadComplete()");
|
||||
return;
|
||||
}
|
||||
|
||||
var uri = this._getItemURI(item);
|
||||
var body = "update=" + uploadKey + "&mtime=" + item.attachmentModificationTime;
|
||||
|
||||
var self = this;
|
||||
|
||||
// Register upload on server
|
||||
Zotero.Utilities.HTTP.doPost(uri, body, function (req) {
|
||||
Zotero.debug(req.responseText);
|
||||
|
||||
if (req.status != 204) {
|
||||
self.onError("Unexpected file registration status " + status
|
||||
+ " in Zotero.Sync.Storage._onUploadComplete()");
|
||||
return;
|
||||
}
|
||||
|
||||
self._updateItemFileInfo(item);
|
||||
request.finish();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype._updateItemFileInfo = function (item) {
|
||||
// Mark as changed locally
|
||||
Zotero.DB.beginTransaction();
|
||||
Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC);
|
||||
|
||||
// Store file mod time
|
||||
var mtime = item.attachmentModificationTime;
|
||||
Zotero.Sync.Storage.setSyncedModificationTime(item.id, mtime, false);
|
||||
|
||||
// Store file hash of individual files
|
||||
if (Zotero.Attachments.getNumFiles(item) == 1) {
|
||||
var hash = item.attachmentHash;
|
||||
Zotero.Sync.Storage.setSyncedHash(item.id, hash, true);
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
|
||||
try {
|
||||
if (Zotero.Attachments.getNumFiles(item) > 1) {
|
||||
var file = Zotero.getTempDirectory();
|
||||
file.append(item.key + '.zip');
|
||||
file.remove(false);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError(e);
|
||||
}
|
||||
|
||||
this.onChangesMade();
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype._onUploadCancel = function (httpRequest, status, data) {
|
||||
var request = data.request;
|
||||
var item = data.item;
|
||||
|
||||
Zotero.debug("Upload of attachment " + item.key + " cancelled with status code " + status);
|
||||
|
||||
try {
|
||||
if (Zotero.Attachments.getNumFiles(item) > 1) {
|
||||
var file = Zotero.getTempDirectory();
|
||||
file.append(item.key + '.zip');
|
||||
file.remove(false);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError(e);
|
||||
}
|
||||
|
||||
request.finish();
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.getLastSyncTime = function (callback) {
|
||||
var uri = this.userURI;
|
||||
var successFileURI = uri.clone();
|
||||
successFileURI.spec += "laststoragesync?auth=1";
|
||||
|
||||
var self = this;
|
||||
|
||||
// Cache the credentials
|
||||
if (!this._cachedCredentials) {
|
||||
var uri = this.rootURI;
|
||||
// TODO: move to root uri
|
||||
uri.spec += "?auth=1";
|
||||
Zotero.Utilities.HTTP.doHead(uri, function (req) {
|
||||
if (req.status != 200) {
|
||||
self.onError("Unexpected status code " + req.status + " caching "
|
||||
+ "authentication credentials in Zotero.Sync.Storage.Session.ZFS.getLastSyncTime()");
|
||||
return;
|
||||
}
|
||||
self._cachedCredentials = true;
|
||||
self.getLastSyncTime(callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.Utilities.HTTP.doGet(successFileURI, function (req) {
|
||||
if (req.responseText) {
|
||||
Zotero.debug(req.responseText);
|
||||
}
|
||||
Zotero.debug(req.status);
|
||||
|
||||
if (req.status != 200 && req.status != 404) {
|
||||
self.onError("Unexpected status code " + req.status + " getting "
|
||||
+ "last file sync time");
|
||||
return;
|
||||
}
|
||||
|
||||
var ts = req.responseText;
|
||||
var date = new Date(req.responseText * 1000);
|
||||
Zotero.debug("Last successful storage sync was " + date);
|
||||
self._lastSyncTime = ts;
|
||||
callback(ts);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.setLastSyncTime = function (callback, useLastSyncTime) {
|
||||
if (useLastSyncTime) {
|
||||
var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)";
|
||||
Zotero.DB.query(sql, { int: this._lastSyncTime });
|
||||
|
||||
this._lastSyncTime = null;
|
||||
this._cachedCredentials = false;
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this._lastSyncTime = null;
|
||||
|
||||
var uri = this.userURI;
|
||||
var successFileURI = uri.clone();
|
||||
successFileURI.spec += "laststoragesync?auth=1";
|
||||
|
||||
var self = this;
|
||||
|
||||
Zotero.Utilities.HTTP.doPost(successFileURI, "", function (req) {
|
||||
Zotero.debug(req.responseText);
|
||||
Zotero.debug(req.status);
|
||||
|
||||
if (req.status != 200) {
|
||||
self.onError("Unexpected status code " + req.status + " setting "
|
||||
+ "last file sync time");
|
||||
return;
|
||||
}
|
||||
|
||||
var ts = req.responseText;
|
||||
|
||||
var sql = "REPLACE INTO version VALUES ('storage_zfs', ?)";
|
||||
Zotero.DB.query(sql, { int: ts });
|
||||
|
||||
self._cachedCredentials = false;
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype.purgeDeletedStorageFiles = function (callback) {
|
||||
// If we don't have a user id we've never synced and don't need to bother
|
||||
if (!Zotero.userID) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sql = "SELECT value FROM settings WHERE setting=? AND key=?";
|
||||
var values = Zotero.DB.columnQuery(sql, ['storage', 'zfsPurge']);
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.debug("Unlinking synced files on ZFS");
|
||||
|
||||
var uri = this.userURI;
|
||||
uri.spec += "removestoragefiles?";
|
||||
for each(var value in values) {
|
||||
switch (value) {
|
||||
case 'user':
|
||||
uri.spec += "user=1&";
|
||||
break;
|
||||
|
||||
case 'group':
|
||||
uri.spec += "group=1&";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid zfsPurge value '" + value + "' in ZFS purgeDeletedStorageFiles()");
|
||||
}
|
||||
}
|
||||
uri.spec = uri.spec.substr(0, uri.spec.length - 1);
|
||||
|
||||
var self = this;
|
||||
|
||||
Zotero.Utilities.HTTP.doPost(uri, "", function (xmlhttp) {
|
||||
if (xmlhttp.status != 204) {
|
||||
if (callback) {
|
||||
callback(false);
|
||||
}
|
||||
self.onError("Unexpected status code " + xmlhttp.status + " purging ZFS files");
|
||||
}
|
||||
|
||||
var sql = "DELETE FROM settings WHERE setting=? AND key=?";
|
||||
Zotero.DB.query(sql, ['storage', 'zfsPurge']);
|
||||
|
||||
if (callback) {
|
||||
callback(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get the storage URI for an item
|
||||
*
|
||||
* @inner
|
||||
* @param {Zotero.Item}
|
||||
* @return {nsIURI} URI of file on storage server
|
||||
*/
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype._getItemURI = function (item) {
|
||||
var uri = this.rootURI;
|
||||
uri.spec += Zotero.URI.getItemPath(item) + '/file?auth=1';
|
||||
return uri;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Session.ZFS.prototype._getUploadFile = function (item) {
|
||||
if (Zotero.Attachments.getNumFiles(item) > 1) {
|
||||
var file = Zotero.getTempDirectory();
|
||||
var filename = item.key + '.zip';
|
||||
file.append(filename);
|
||||
}
|
||||
else {
|
||||
var file = item.getFile();
|
||||
}
|
||||
return file;
|
||||
}
|
|
@ -443,30 +443,28 @@ Zotero.Sync.EventListener = new function () {
|
|||
|
||||
|
||||
Zotero.Sync.Runner = new function () {
|
||||
this.__defineGetter__("lastSyncError", function () {
|
||||
return _lastSyncError;
|
||||
});
|
||||
|
||||
this.__defineGetter__("background", function () {
|
||||
return _background;
|
||||
});
|
||||
|
||||
var _lastSyncError;
|
||||
var _autoSyncTimer;
|
||||
var _queue;
|
||||
var _running;
|
||||
var _background;
|
||||
|
||||
var _warning = null;
|
||||
|
||||
this.init = function () {
|
||||
this.EventListener.init();
|
||||
this.IdleListener.init();
|
||||
}
|
||||
|
||||
this.sync = function (background) {
|
||||
_warning = null;
|
||||
|
||||
if (Zotero.Utilities.HTTP.browserIsOffline()){
|
||||
_lastSyncError = "Browser is offline"; // TODO: localize
|
||||
this.clearSyncTimeout(); // DEBUG: necessary?
|
||||
this.setSyncIcon('error');
|
||||
this.setSyncIcon('error', "Browser is offline");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -478,40 +476,135 @@ Zotero.Sync.Runner = new function () {
|
|||
Zotero.purgeDataObjects(true);
|
||||
|
||||
_background = !!background;
|
||||
|
||||
_queue = [
|
||||
Zotero.Sync.Server.sync,
|
||||
Zotero.Sync.Storage.sync,
|
||||
Zotero.Sync.Server.sync,
|
||||
Zotero.Sync.Storage.sync
|
||||
];
|
||||
_running = true;
|
||||
_lastSyncError = '';
|
||||
this.setSyncIcon('animate');
|
||||
this.next();
|
||||
|
||||
var storageSync = function () {
|
||||
var syncNeeded = false;
|
||||
|
||||
Zotero.Sync.Storage.sync(
|
||||
'webdav',
|
||||
|
||||
{
|
||||
// WebDAV success
|
||||
onSuccess: function () {
|
||||
syncNeeded = true;
|
||||
|
||||
Zotero.Sync.Storage.sync(
|
||||
'zfs',
|
||||
|
||||
{
|
||||
// ZFS success
|
||||
onSuccess: function () {
|
||||
Zotero.Sync.Server.sync(
|
||||
Zotero.Sync.Runner.stop,
|
||||
Zotero.Sync.Runner.stop,
|
||||
Zotero.Sync.Runner.error
|
||||
)
|
||||
},
|
||||
|
||||
// ZFS skip
|
||||
onSkip: function () {
|
||||
if (syncNeeded) {
|
||||
Zotero.Sync.Server.sync(
|
||||
Zotero.Sync.Runner.stop,
|
||||
Zotero.Sync.Runner.stop,
|
||||
Zotero.Sync.Runner.error
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
// ZFS cancel
|
||||
onStop: Zotero.Sync.Runner.stop,
|
||||
|
||||
// ZFS failure
|
||||
onError: Zotero.Sync.Runner.error,
|
||||
|
||||
onWarning: Zotero.Sync.Runner.warning
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
// WebDAV skip
|
||||
onSkip: function () {
|
||||
Zotero.Sync.Storage.sync(
|
||||
'zfs',
|
||||
|
||||
{
|
||||
// ZFS success
|
||||
onSuccess: function () {
|
||||
Zotero.Sync.Server.sync({
|
||||
onSuccess: Zotero.Sync.Runner.stop,
|
||||
onSkip: Zotero.Sync.Runner.stop,
|
||||
onStop: Zotero.Sync.Runner.stop,
|
||||
onError: Zotero.Sync.Runner.error
|
||||
})
|
||||
},
|
||||
|
||||
// ZFS skip
|
||||
onSkip: Zotero.Sync.Runner.stop,
|
||||
|
||||
// ZFS cancel
|
||||
onStop: Zotero.Sync.Runner.stop,
|
||||
|
||||
// ZFS failure
|
||||
onError: Zotero.Sync.Runner.error,
|
||||
|
||||
onWarning: Zotero.Sync.Runner.warning
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
// WebDAV cancel
|
||||
onStop: Zotero.Sync.Runner.stop,
|
||||
|
||||
// WebDAV failure
|
||||
onError: Zotero.Sync.Runner.error
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Zotero.Sync.Server.sync({
|
||||
// Sync 1 success
|
||||
onSuccess: storageSync,
|
||||
|
||||
// Sync 1 skip
|
||||
onSkip: storageSync,
|
||||
|
||||
// Sync 1 stop
|
||||
onStop: Zotero.Sync.Runner.stop,
|
||||
|
||||
// Sync 1 error
|
||||
onError: Zotero.Sync.Runner.error
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.next = function () {
|
||||
if (!_queue.length) {
|
||||
this.setSyncIcon();
|
||||
this.stop = function () {
|
||||
if (_warning) {
|
||||
Zotero.Sync.Runner.setSyncIcon('warning', _warning);
|
||||
_warning = null;
|
||||
}
|
||||
else {
|
||||
Zotero.Sync.Runner.setSyncIcon();
|
||||
}
|
||||
_running = false;
|
||||
return;
|
||||
}
|
||||
var func = _queue.shift();
|
||||
func();
|
||||
}
|
||||
|
||||
|
||||
this.setError = function (msg) {
|
||||
this.setSyncIcon('error');
|
||||
_lastSyncError = msg;
|
||||
/**
|
||||
* Log a warning, but don't throw an error
|
||||
*/
|
||||
this.warning = function (e) {
|
||||
Components.utils.reportError(e);
|
||||
_warning = e;
|
||||
}
|
||||
|
||||
|
||||
this.reset = function () {
|
||||
_queue = [];
|
||||
this.error = function (e) {
|
||||
Zotero.Sync.Runner.setSyncIcon('error', e);
|
||||
_running = false;
|
||||
throw (e);
|
||||
}
|
||||
|
||||
|
||||
|
@ -593,12 +686,13 @@ Zotero.Sync.Runner = new function () {
|
|||
}
|
||||
|
||||
|
||||
this.setSyncIcon = function (status) {
|
||||
this.setSyncIcon = function (status, e) {
|
||||
status = status ? status : '';
|
||||
|
||||
switch (status) {
|
||||
case '':
|
||||
case 'animate':
|
||||
case 'warning':
|
||||
case 'error':
|
||||
break;
|
||||
|
||||
|
@ -610,18 +704,114 @@ Zotero.Sync.Runner = new function () {
|
|||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow('navigator:browser');
|
||||
|
||||
var warning = win.document.getElementById('zotero-tb-sync-warning');
|
||||
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);
|
||||
var message;
|
||||
var buttonText;
|
||||
var buttonCallback;
|
||||
|
||||
if (e) {
|
||||
if (e.error == Zotero.Error.ERROR_ZFS_OVER_QUOTA) {
|
||||
// TODO: localize
|
||||
message = "You have reached your Zotero File Storage quota. Some files were not synced.\n\n"
|
||||
+ "See your zotero.org account settings for additional storage options.";
|
||||
|
||||
buttonText = "Open Account Settings";
|
||||
buttonCallback = function () {
|
||||
var url = "https://www.zotero.org/settings/storage";
|
||||
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
var browser = win.getBrowser();
|
||||
browser.selectedTab = browser.addTab(url);
|
||||
}
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
message = e.message ? e.message : e;
|
||||
}
|
||||
}
|
||||
|
||||
if (status == 'warning' || status == 'error') {
|
||||
icon.setAttribute('status', '');
|
||||
warning.hidden = false;
|
||||
warning.setAttribute('mode', status);
|
||||
warning.tooltipText = "A sync error occurred. Click to view details.";
|
||||
warning.onclick = function () {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
var pr = Components.classes["@mozilla.org/network/default-prompt;1"]
|
||||
.createInstance(Components.interfaces.nsIPrompt);
|
||||
// Warning
|
||||
if (status == 'warning') {
|
||||
var title = Zotero.getString('general.warning');
|
||||
|
||||
// If secondary button not specified, just use an alert
|
||||
if (!buttonText) {
|
||||
prompt.alert(title, message);
|
||||
return;
|
||||
}
|
||||
|
||||
var buttonFlags = pr.BUTTON_POS_0 * pr.BUTTON_TITLE_OK
|
||||
+ pr.BUTTON_POS_1 * pr.BUTTON_TITLE_IS_STRING;
|
||||
Zotero.debug(buttonFlags);
|
||||
var index = pr.confirmEx(
|
||||
title,
|
||||
message,
|
||||
buttonFlags,
|
||||
"",
|
||||
buttonText,
|
||||
"", null, {}
|
||||
);
|
||||
|
||||
if (index == 1) {
|
||||
setTimeout(buttonCallback, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Error
|
||||
else if (status == 'error') {
|
||||
var errorsLogged = Zotero.getErrors().length > 0;
|
||||
// Probably not necessary, but let's be sure
|
||||
if (!errorsLogged) {
|
||||
Components.utils.reportError(message);
|
||||
}
|
||||
|
||||
var buttonFlags = pr.BUTTON_POS_0 * pr.BUTTON_TITLE_OK
|
||||
+ pr.BUTTON_POS_1 * pr.BUTTON_TITLE_IS_STRING;
|
||||
var index = pr.confirmEx(
|
||||
Zotero.getString('general.error'),
|
||||
message,
|
||||
buttonFlags,
|
||||
"",
|
||||
// TODO: localize
|
||||
"Report Error...",
|
||||
"", null, {}
|
||||
);
|
||||
|
||||
if (index == 1) {
|
||||
win.setTimeout(function () {
|
||||
win.ZoteroPane.reportErrors();
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
icon.setAttribute('status', status);
|
||||
warning.hidden = true;
|
||||
warning.onclick = null;
|
||||
}
|
||||
|
||||
// Disable button while spinning
|
||||
icon.disabled = status == 'animate';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -852,7 +1042,22 @@ Zotero.Sync.Server = new function () {
|
|||
var _throttleTimeout;
|
||||
var _canAutoResetClient = true;
|
||||
|
||||
function login(callback, callbackCallback) {
|
||||
var _callbacks = {
|
||||
onSuccess: function () {
|
||||
Zotero.Sync.Runner.setSyncIcon();
|
||||
},
|
||||
onSkip: function () {
|
||||
Zotero.Sync.Runner.setSyncIcon();
|
||||
},
|
||||
onStop: function () {
|
||||
Zotero.Sync.Runner.setSyncIcon();
|
||||
},
|
||||
onError: function (msg) {
|
||||
Zotero.Sync.Runner.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
function login(callback) {
|
||||
var url = _serverURL + "login";
|
||||
|
||||
var username = Zotero.Sync.Server.username;
|
||||
|
@ -906,18 +1111,26 @@ Zotero.Sync.Server = new function () {
|
|||
//Zotero.debug('Got session ID ' + _sessionID + ' from server');
|
||||
|
||||
if (callback) {
|
||||
callback(callbackCallback);
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function sync(callback) {
|
||||
function sync(callbacks) {
|
||||
for (var func in callbacks) {
|
||||
_callbacks[func] = callbacks[func];
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
Zotero.Sync.Runner.setSyncIcon('animate');
|
||||
|
||||
if (!_sessionID) {
|
||||
Zotero.debug("Session ID not available -- logging in");
|
||||
Zotero.Sync.Server.login(Zotero.Sync.Server.sync, callback);
|
||||
Zotero.Sync.Server.login(function () {
|
||||
Zotero.Sync.Server.sync(_callbacks);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -955,7 +1168,9 @@ Zotero.Sync.Server = new function () {
|
|||
Zotero.debug("Invalid session ID -- logging in");
|
||||
_sessionID = false;
|
||||
_syncInProgress = false;
|
||||
Zotero.Sync.Server.login(Zotero.Sync.Server.sync, callback);
|
||||
Zotero.Sync.Server.login(function () {
|
||||
Zotero.Sync.Server.sync(_callbacks);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -976,32 +1191,36 @@ Zotero.Sync.Server = new function () {
|
|||
// Strip XML declaration and convert to E4X
|
||||
var xml = new XML(xmlhttp.responseText.replace(/<\?xml.*\?>/, ''));
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
try {
|
||||
// If no earliest date is provided by the server, the server
|
||||
// account is empty
|
||||
var earliestRemoteDate = parseInt(xml.@earliest) ?
|
||||
new Date((xml.@earliest + 43200) * 1000) : false;
|
||||
var noServerData = !!earliestRemoteDate;
|
||||
|
||||
// Check to see if we're syncing with a different user
|
||||
var userID = parseInt(xml.@userID);
|
||||
var libraryID = parseInt(xml.@defaultLibraryID);
|
||||
if (!_checkSyncUser(userID, libraryID)) {
|
||||
var c = _checkSyncUser(userID, libraryID, noServerData);
|
||||
if (c == 0) {
|
||||
// Groups were deleted, so restart sync
|
||||
Zotero.debug("Restarting sync");
|
||||
_syncInProgress = false;
|
||||
Zotero.Sync.Server.sync(_callbacks);
|
||||
return;
|
||||
}
|
||||
else if (c == -1) {
|
||||
Zotero.debug("Sync cancelled");
|
||||
Zotero.DB.rollbackTransaction();
|
||||
Zotero.Sync.Server.unlock(function () {
|
||||
if (callback) {
|
||||
Zotero.Sync.Runner.setSyncIcon();
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
Zotero.Sync.Runner.reset();
|
||||
Zotero.Sync.Runner.next();
|
||||
}
|
||||
_callbacks.onStop();
|
||||
});
|
||||
_syncInProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.UnresponsiveScriptIndicator.disable();
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var earliestRemoteDate = parseInt(xml.@earliest) ?
|
||||
new Date((xml.@earliest + 43200) * 1000) : false;
|
||||
Zotero.UnresponsiveScriptIndicator.disable();
|
||||
|
||||
var lastLocalSyncTime = Zotero.Sync.Server.lastLocalSyncTime;
|
||||
var lastLocalSyncDate = lastLocalSyncTime ?
|
||||
|
@ -1039,14 +1258,7 @@ Zotero.Sync.Server = new function () {
|
|||
Zotero.debug("Sync cancelled");
|
||||
Zotero.DB.rollbackTransaction();
|
||||
Zotero.Sync.Server.unlock(function () {
|
||||
if (callback) {
|
||||
Zotero.Sync.Runner.setSyncIcon();
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
Zotero.Sync.Runner.reset();
|
||||
Zotero.Sync.Runner.next();
|
||||
}
|
||||
_callbacks.onSkip();
|
||||
});
|
||||
Zotero.reloadDataObjects();
|
||||
Zotero.Sync.EventListener.resetIgnored();
|
||||
|
@ -1067,13 +1279,7 @@ Zotero.Sync.Server = new function () {
|
|||
Zotero.DB.commitTransaction();
|
||||
Zotero.Sync.Server.unlock(function () {
|
||||
_syncInProgress = false;
|
||||
if (callback) {
|
||||
Zotero.Sync.Runner.setSyncIcon();
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
Zotero.Sync.Runner.next();
|
||||
}
|
||||
_callbacks.onSuccess();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -1114,13 +1320,7 @@ Zotero.Sync.Server = new function () {
|
|||
Zotero.DB.commitTransaction();
|
||||
Zotero.Sync.Server.unlock(function () {
|
||||
_syncInProgress = false;
|
||||
if (callback) {
|
||||
Zotero.Sync.Runner.setSyncIcon();
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
Zotero.Sync.Runner.next();
|
||||
}
|
||||
_callbacks.onSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1203,7 +1403,7 @@ Zotero.Sync.Server = new function () {
|
|||
}
|
||||
|
||||
|
||||
function lock(callback, callbackCallback) {
|
||||
function lock(callback) {
|
||||
Zotero.debug("Getting session lock");
|
||||
|
||||
if (!_sessionID) {
|
||||
|
@ -1234,11 +1434,7 @@ Zotero.Sync.Server = new function () {
|
|||
|
||||
if (response.firstChild.tagName == 'error') {
|
||||
if (_checkServerSessionLock(response.firstChild)) {
|
||||
Zotero.Sync.Server.lock(function () {
|
||||
if (callback) {
|
||||
callback(callbackCallback);
|
||||
}
|
||||
});
|
||||
Zotero.Sync.Server.lock(callback ? function () { callback(); } : null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1252,7 +1448,7 @@ Zotero.Sync.Server = new function () {
|
|||
_sessionLock = true;
|
||||
|
||||
if (callback) {
|
||||
callback(callbackCallback);
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1300,7 +1496,9 @@ Zotero.Sync.Server = new function () {
|
|||
function clear(callback) {
|
||||
if (!_sessionID) {
|
||||
Zotero.debug("Session ID not available -- logging in");
|
||||
Zotero.Sync.Server.login(Zotero.Sync.Server.clear, callback);
|
||||
Zotero.Sync.Server.login(function () {
|
||||
Zotero.Sync.Server.clear();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1312,7 +1510,9 @@ Zotero.Sync.Server = new function () {
|
|||
if (_invalidSession(xmlhttp)) {
|
||||
Zotero.debug("Invalid session ID -- logging in");
|
||||
_sessionID = false;
|
||||
Zotero.Sync.Server.login(Zotero.Sync.Server.clear, callback);
|
||||
Zotero.Sync.Server.login(function () {
|
||||
Zotero.Sync.Server.clear(callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1343,7 +1543,9 @@ Zotero.Sync.Server = new function () {
|
|||
function resetServer(callback) {
|
||||
if (!_sessionID) {
|
||||
Zotero.debug("Session ID not available -- logging in");
|
||||
Zotero.Sync.Server.login(Zotero.Sync.Server.resetServer, callback);
|
||||
Zotero.Sync.Server.login(function () {
|
||||
Zotero.Sync.Server.resetServer(callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1355,7 +1557,9 @@ Zotero.Sync.Server = new function () {
|
|||
if (_invalidSession(xmlhttp)) {
|
||||
Zotero.debug("Invalid session ID -- logging in");
|
||||
_sessionID = false;
|
||||
Zotero.Sync.Server.login(Zotero.Sync.Server.reset, callback);
|
||||
Zotero.Sync.Server.login(function () {
|
||||
Zotero.Sync.Server.reset();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1391,11 +1595,12 @@ Zotero.Sync.Server = new function () {
|
|||
+ "('lastlocalsync', 'lastremotesync', 'syncdeletelog')";
|
||||
Zotero.DB.query(sql);
|
||||
|
||||
var sql = "DELETE FROM version WHERE schema IN "
|
||||
+ "('lastlocalsync', 'lastremotesync', 'syncdeletelog')";
|
||||
Zotero.DB.query(sql);
|
||||
|
||||
Zotero.DB.query("DELETE FROM syncDeleteLog");
|
||||
Zotero.DB.query("DELETE FROM storageDeleteLog");
|
||||
sql = "DELETE FROM settings WHERE setting='account' AND "
|
||||
+ "key IN ('userID', 'username')";
|
||||
Zotero.DB.query(sql);
|
||||
|
||||
sql = "INSERT INTO version VALUES ('syncdeletelog', ?)";
|
||||
Zotero.DB.query(sql, Zotero.Date.getUnixTimestamp());
|
||||
|
@ -1545,6 +1750,41 @@ Zotero.Sync.Server = new function () {
|
|||
}, 1);
|
||||
break;
|
||||
|
||||
case 'LIBRARY_ACCESS_DENIED':
|
||||
var background = Zotero.Sync.Runner.background;
|
||||
setTimeout(function () {
|
||||
var libraryID = parseInt(firstChild.getAttribute('libraryID'));
|
||||
var group = Zotero.Groups.getByLibraryID(libraryID);
|
||||
|
||||
var pr = Components.classes["@mozilla.org/network/default-prompt;1"]
|
||||
.createInstance(Components.interfaces.nsIPrompt);
|
||||
var buttonFlags = (pr.BUTTON_POS_0) * (pr.BUTTON_TITLE_IS_STRING)
|
||||
+ (pr.BUTTON_POS_1) * (pr.BUTTON_TITLE_CANCEL)
|
||||
+ pr.BUTTON_DELAY_ENABLE;
|
||||
var index = pr.confirmEx(
|
||||
Zotero.getString('general.warning'),
|
||||
// TODO: localize
|
||||
"You no longer have write access to the Zotero group '" + group.name + "', "
|
||||
+ "and changes you've made cannot be synced to the server.\n\n"
|
||||
+ "If you continue, your copy of the group will be reset to its state "
|
||||
+ "on the server, and your local modifications will be lost.\n\n"
|
||||
+ "If you would like a chance to copy your changes elsewhere or to request "
|
||||
+ "write access from a group administrator, cancel the sync now.",
|
||||
buttonFlags,
|
||||
"Reset Group and Sync",
|
||||
null, null, null, {}
|
||||
);
|
||||
|
||||
if (index == 0) {
|
||||
group.erase();
|
||||
Zotero.Sync.Server.resetClient();
|
||||
Zotero.Sync.Storage.resetAllSyncStates();
|
||||
Zotero.Sync.Runner.sync();
|
||||
return;
|
||||
}
|
||||
}, 1);
|
||||
break;
|
||||
|
||||
case 'TAG_TOO_LONG':
|
||||
if (!Zotero.Sync.Runner.background) {
|
||||
var tag = xmlhttp.responseXML.firstChild.getElementsByTagName('tag');
|
||||
|
@ -1626,9 +1866,19 @@ Zotero.Sync.Server = new function () {
|
|||
/**
|
||||
* Make sure we're syncing with the same account we used last time
|
||||
*
|
||||
* @return TRUE if sync should continue, FALSE if cancelled
|
||||
* @param {Integer} userID New userID
|
||||
* @param {Integer} libraryID New libraryID
|
||||
* @param {Integer} noServerData The server account is empty — this is
|
||||
* the account after a server clear
|
||||
* @return 1 if sync should continue, 0 if sync should restart, -1 if sync should cancel
|
||||
*/
|
||||
function _checkSyncUser(userID, libraryID) {
|
||||
function _checkSyncUser(userID, libraryID, noServerData) {
|
||||
if (Zotero.DB.transactionInProgress()) {
|
||||
throw ("Transaction in progress in Zotero.Sync.Server._checkSyncUser");
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var sql = "SELECT value FROM settings WHERE "
|
||||
+ "setting='account' AND key='username'";
|
||||
var lastUsername = Zotero.DB.valueQuery(sql);
|
||||
|
@ -1636,7 +1886,11 @@ Zotero.Sync.Server = new function () {
|
|||
var lastUserID = Zotero.userID;
|
||||
var lastLibraryID = Zotero.libraryID;
|
||||
|
||||
var restartSync = false;
|
||||
|
||||
if (lastUserID && lastUserID != userID) {
|
||||
var groups = Zotero.Groups.getAll();
|
||||
|
||||
var pr = Components.classes["@mozilla.org/network/default-prompt;1"]
|
||||
.createInstance(Components.interfaces.nsIPrompt);
|
||||
var buttonFlags = (pr.BUTTON_POS_0) * (pr.BUTTON_TITLE_IS_STRING)
|
||||
|
@ -1644,20 +1898,52 @@ Zotero.Sync.Server = new function () {
|
|||
+ (pr.BUTTON_POS_2) * (pr.BUTTON_TITLE_IS_STRING)
|
||||
+ pr.BUTTON_POS_1_DEFAULT
|
||||
+ pr.BUTTON_DELAY_ENABLE;
|
||||
|
||||
var msg = "This Zotero database was last synced with a different "
|
||||
+ "zotero.org account ('" + lastUsername + "') from the "
|
||||
+ "current one ('" + username + "'). ";
|
||||
|
||||
if (!noServerData) {
|
||||
// TODO: localize
|
||||
msg += "If you continue, local Zotero data will be "
|
||||
+ "combined with data from the '" + username + "' account "
|
||||
+ "stored on the server.";
|
||||
// If there are local groups belonging to the previous user,
|
||||
// we need to remove them
|
||||
if (groups.length) {
|
||||
msg += "Local groups, including any with changed items, will also "
|
||||
+ "be removed.";
|
||||
}
|
||||
msg += "\n\n"
|
||||
+ "To avoid combining or losing data, revert to the '"
|
||||
+ lastUsername + "' account or use the Reset options "
|
||||
+ "in the Sync pane of the Zotero preferences.";
|
||||
|
||||
var syncButtonText = "Sync";
|
||||
}
|
||||
else if (groups.length) {
|
||||
msg += "If you continue, local groups, including any with changed items, "
|
||||
+ "will be removed and replaced with groups linked to the '"
|
||||
+ username + "' account."
|
||||
+ "\n\n"
|
||||
+ "To avoid losing local changes to groups, be sure you "
|
||||
+ "have synced with the '" + lastUsername + "' account before "
|
||||
+ "syncing with the '" + username + "' account.";
|
||||
|
||||
var syncButtonText = "Remove Groups and Sync";
|
||||
}
|
||||
// If there are no local groups and the server is empty,
|
||||
// don't bother prompting
|
||||
else {
|
||||
var noPrompt = true;
|
||||
}
|
||||
|
||||
if (!noPrompt) {
|
||||
var index = pr.confirmEx(
|
||||
Zotero.getString('general.warning'),
|
||||
// TODO: localize
|
||||
"This Zotero database was last synced with a different "
|
||||
+ "zotero.org account ('" + lastUsername + "') from the "
|
||||
+ "current one ('" + username + "'). "
|
||||
+ "If you continue, local Zotero data will be "
|
||||
+ "combined with data from the '" + username + "' account "
|
||||
+ "stored on the server.\n\n"
|
||||
+ "To avoid combining data, revert to the '"
|
||||
+ lastUsername + "' account or use the Reset options "
|
||||
+ "in the Sync pane of the Zotero preferences.",
|
||||
msg,
|
||||
buttonFlags,
|
||||
"Sync",
|
||||
syncButtonText,
|
||||
null,
|
||||
"Open Sync Preferences",
|
||||
null, {}
|
||||
|
@ -1674,24 +1960,44 @@ Zotero.Sync.Server = new function () {
|
|||
lastWin.ZoteroPane.openPreferences('zotero-prefpane-sync');
|
||||
}
|
||||
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Delete all local groups
|
||||
for each(var group in groups) {
|
||||
group.erase();
|
||||
}
|
||||
|
||||
restartSync = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastUserID != userID || lastLibraryID != libraryID) {
|
||||
Zotero.userID = userID;
|
||||
Zotero.libraryID = libraryID;
|
||||
|
||||
// Update userID in relations
|
||||
if (lastUserID && lastLibraryID) {
|
||||
Zotero.Relations.updateUser(lastUserID, lastLibraryID, userID, libraryID);
|
||||
}
|
||||
|
||||
Zotero.Sync.Server.resetClient();
|
||||
Zotero.Sync.Storage.resetAllSyncStates();
|
||||
}
|
||||
|
||||
if (lastUsername != username) {
|
||||
var sql = "REPLACE INTO settings VALUES ('account', 'username', ?)";
|
||||
Zotero.DB.query(sql, username);
|
||||
Zotero.username = username;
|
||||
}
|
||||
|
||||
return true;
|
||||
Zotero.DB.commitTransaction();
|
||||
|
||||
return restartSync ? 0 : 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function _invalidSession(xmlhttp) {
|
||||
if (xmlhttp.responseXML.childNodes[0].firstChild.tagName != 'error') {
|
||||
return false;
|
||||
|
@ -1783,8 +2089,7 @@ Zotero.Sync.Server = new function () {
|
|||
Zotero.Sync.Server.unlock()
|
||||
}
|
||||
|
||||
Zotero.Sync.Runner.setError(e.message ? e.message : e);
|
||||
Zotero.Sync.Runner.reset();
|
||||
_callbacks.onError(e);
|
||||
|
||||
throw (e);
|
||||
}
|
||||
|
@ -1970,6 +2275,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
var itemStorageModTimes = {};
|
||||
var childItemStore = [];
|
||||
|
||||
// Remotely changed groups
|
||||
if (xml.groups.length()) {
|
||||
Zotero.debug("Processing remotely changed groups");
|
||||
for each(var xmlNode in xml.groups.group) {
|
||||
|
@ -1978,6 +2284,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
// Remotely deleted groups
|
||||
if (xml.deleted.groups.toString()) {
|
||||
Zotero.debug("Processing remotely deleted groups");
|
||||
var groupIDs = xml.deleted.groups.toString().split(' ');
|
||||
|
@ -1988,21 +2295,14 @@ Zotero.Sync.Server.Data = new function() {
|
|||
if (!group) {
|
||||
continue;
|
||||
}
|
||||
// TODO: prompt to save
|
||||
|
||||
Zotero.Notifier.disable();
|
||||
// TODO: prompt to save data to local library?
|
||||
|
||||
// TODO: figure out a better way to do this
|
||||
var notifierData = {};
|
||||
notifierData[groupID] = group.serialize();
|
||||
group.erase();
|
||||
|
||||
Zotero.Notifier.enable();
|
||||
|
||||
Zotero.Notifier.trigger('delete', 'group', groupID, notifierData);
|
||||
}
|
||||
}
|
||||
|
||||
// Other objects
|
||||
for each(var syncObject in Zotero.Sync.syncObjects) {
|
||||
var Type = syncObject.singular; // 'Item'
|
||||
var Types = syncObject.plural; // 'Items'
|
||||
|
@ -2271,11 +2571,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
syncSession.removeFromDeleted(creator.ref);
|
||||
}
|
||||
}
|
||||
else if (obj.isAttachment() &&
|
||||
(obj.attachmentLinkMode ==
|
||||
Zotero.Attachments.LINK_MODE_IMPORTED_FILE ||
|
||||
obj.attachmentLinkMode ==
|
||||
Zotero.Attachments.LINK_MODE_IMPORTED_URL)) {
|
||||
else if (obj.isImportedAttachment()) {
|
||||
// Mark new attachments for download
|
||||
if (isNewObject) {
|
||||
obj.attachmentSyncState =
|
||||
|
@ -2285,7 +2581,8 @@ Zotero.Sync.Server.Data = new function() {
|
|||
else {
|
||||
var mtime = xmlNode.@storageModTime.toString();
|
||||
if (mtime) {
|
||||
itemStorageModTimes[obj.key] = parseInt(mtime);
|
||||
var lk = Zotero.Items.getLibraryKeyHash(obj)
|
||||
itemStorageModTimes[lk] = parseInt(mtime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2504,15 +2801,16 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
// Check mod times of updated items against stored time to see
|
||||
// Check mod times and hashes of updated items against stored values to see
|
||||
// if they've been updated elsewhere and mark for download if so
|
||||
if (type == 'item') {
|
||||
var ids = [];
|
||||
var modTimes = {};
|
||||
for (var key in itemStorageModTimes) {
|
||||
var item = Zotero.Items.getByLibraryAndKey(null, key);
|
||||
for (var libraryKeyHash in itemStorageModTimes) {
|
||||
var lk = Zotero.Items.parseLibraryKeyHash(libraryKeyHash);
|
||||
var item = Zotero.Items.getByLibraryAndKey(lk.libraryID, lk.key);
|
||||
ids.push(item.id);
|
||||
modTimes[item.id] = itemStorageModTimes[key];
|
||||
modTimes[item.id] = itemStorageModTimes[libraryKeyHash];
|
||||
}
|
||||
if (ids.length > 0) {
|
||||
Zotero.Sync.Storage.checkForUpdatedFiles(ids, modTimes);
|
||||
|
@ -3003,15 +3301,23 @@ Zotero.Sync.Server.Data = new function() {
|
|||
xml.@charset = charset;
|
||||
}
|
||||
|
||||
// Include storage sync time and paths for non-links
|
||||
if (item.attachment.linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
// Include paths for non-links
|
||||
var path = <path>{item.attachment.path}</path>;
|
||||
xml.path += path;
|
||||
|
||||
// Include storage sync time and hash for imported files
|
||||
if (item.attachment.linkMode != Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
var mtime = Zotero.Sync.Storage.getSyncedModificationTime(item.primary.itemID);
|
||||
if (mtime) {
|
||||
xml.@storageModTime = mtime;
|
||||
}
|
||||
|
||||
var path = <path>{item.attachment.path}</path>;
|
||||
xml.path += path;
|
||||
var hash = Zotero.Sync.Storage.getSyncedHash(item.primary.itemID);
|
||||
if (hash) {
|
||||
xml.@storageHash = hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.note) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
Zotero.URI = new function () {
|
||||
var _baseURI = ZOTERO_CONFIG.BASE_URI;
|
||||
var _apiURI = ZOTERO_CONFIG.API_URI;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -22,8 +23,11 @@ Zotero.URI = new function () {
|
|||
*
|
||||
* @return {String}
|
||||
*/
|
||||
this.getCurrentUserURI = function () {
|
||||
this.getCurrentUserURI = function (noLocal) {
|
||||
var userID = Zotero.userID;
|
||||
if (!userID && noLocal) {
|
||||
throw new Exception("Local userID not available and noLocal set in Zotero.URI.getCurrentUserURI()");
|
||||
}
|
||||
if (userID) {
|
||||
return _baseURI + "users/" + userID;
|
||||
}
|
||||
|
@ -42,22 +46,44 @@ Zotero.URI = new function () {
|
|||
|
||||
|
||||
this.getLibraryURI = function (libraryID) {
|
||||
var path = this.getLibraryPath(libraryID);
|
||||
return _baseURI + path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get path portion of library URI (e.g., users/6 or groups/1)
|
||||
*/
|
||||
this.getLibraryPath = function (libraryID) {
|
||||
if (libraryID) {
|
||||
var libraryType = Zotero.Libraries.getType(libraryID);
|
||||
}
|
||||
else {
|
||||
libraryType = 'user';
|
||||
}
|
||||
switch (libraryType) {
|
||||
case 'user':
|
||||
var id = Zotero.userID;
|
||||
if (!id) {
|
||||
throw new Exception("User id not available in Zotero.URI.getLibraryPath()");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'group':
|
||||
var id = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
|
||||
break;
|
||||
|
||||
case 'user':
|
||||
throw ("User library ids are not supported in Zotero.URI.getLibraryURI");
|
||||
|
||||
default:
|
||||
throw ("Unsupported library type '" + libraryType + "' in Zotero.URI.getLibraryURI()");
|
||||
throw ("Unsupported library type '" + libraryType + "' in Zotero.URI.getLibraryPath()");
|
||||
}
|
||||
return _baseURI + libraryType + "s/" + id;
|
||||
|
||||
return libraryType + "s/" + id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return URI of item, which might be a local URI if user hasn't synced
|
||||
*/
|
||||
this.getItemURI = function (item) {
|
||||
if (item.libraryID) {
|
||||
var baseURI = this.getLibraryURI(item.libraryID);
|
||||
|
@ -69,6 +95,14 @@ Zotero.URI = new function () {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get path portion of item URI (e.g., users/6/items/ABCD1234 or groups/1/items/ABCD1234)
|
||||
*/
|
||||
this.getItemPath = function (item) {
|
||||
return this.getLibraryPath(item.libraryID) + "/items/" + item.key;
|
||||
}
|
||||
|
||||
|
||||
this.getGroupsURL = function () {
|
||||
return ZOTERO_CONFIG.WWW_BASE_URL + "groups";
|
||||
}
|
||||
|
|
|
@ -396,19 +396,51 @@ Zotero.Utilities.prototype.getSQLDataType = function(value) {
|
|||
|
||||
|
||||
/*
|
||||
* From http://developer.mozilla.org/en/docs/nsICryptoHash#Computing_the_Hash_of_a_String
|
||||
* Adapted from http://developer.mozilla.org/en/docs/nsICryptoHash
|
||||
*
|
||||
* @param {String|nsIFile} strOrFile
|
||||
* @param {Boolean} [base64=false] Return as base-64-encoded string rather than hex string
|
||||
* @return {String}
|
||||
*/
|
||||
Zotero.Utilities.prototype.md5 = function(str) {
|
||||
Zotero.Utilities.prototype.md5 = function(strOrFile, base64) {
|
||||
if (typeof strOrFile == 'string') {
|
||||
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
var result = {};
|
||||
var data = converter.convertToByteArray(str, result);
|
||||
var data = converter.convertToByteArray(strOrFile, result);
|
||||
var ch = Components.classes["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Components.interfaces.nsICryptoHash);
|
||||
ch.init(ch.MD5);
|
||||
ch.update(data, data.length);
|
||||
var hash = ch.finish(false);
|
||||
}
|
||||
else if (strOrFile instanceof Components.interfaces.nsIFile) {
|
||||
var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
// open for reading
|
||||
istream.init(strOrFile, 0x01, 0444, 0);
|
||||
var ch = Components.classes["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Components.interfaces.nsICryptoHash);
|
||||
// we want to use the MD5 algorithm
|
||||
ch.init(ch.MD5);
|
||||
// this tells updateFromStream to read the entire file
|
||||
const PR_UINT32_MAX = 0xffffffff;
|
||||
ch.updateFromStream(istream, PR_UINT32_MAX);
|
||||
}
|
||||
|
||||
// pass false here to get binary data back
|
||||
var hash = ch.finish(base64);
|
||||
|
||||
if (istream) {
|
||||
istream.close();
|
||||
}
|
||||
|
||||
if (base64) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/*
|
||||
// This created 36-character hashes
|
||||
|
||||
// return the two-digit hexadecimal code for a byte
|
||||
function toHexString(charCode) {
|
||||
|
@ -417,6 +449,17 @@ Zotero.Utilities.prototype.md5 = function(str) {
|
|||
|
||||
// convert the binary hash data to a hex string.
|
||||
return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
|
||||
*/
|
||||
|
||||
// From http://rcrowley.org/2007/11/15/md5-in-xulrunner-or-firefox-extensions/
|
||||
var ascii = []; ii = hash.length;
|
||||
for (var i = 0; i < ii; ++i) {
|
||||
var c = hash.charCodeAt(i);
|
||||
var ones = c % 16;
|
||||
var tens = c >> 4;
|
||||
ascii.push(String.fromCharCode(tens + (tens > 9 ? 87 : 48)) + String.fromCharCode(ones + (ones > 9 ? 87 : 48)));
|
||||
}
|
||||
return ascii.join('');
|
||||
}
|
||||
|
||||
|
||||
|
@ -795,17 +838,18 @@ Zotero.Utilities.Translate.prototype.retrieveDocument = function(url) {
|
|||
* @param {String} url URL to load
|
||||
* @param {String} [body=null] Request body to POST to the URL; a GET request is
|
||||
* executed if no body is present
|
||||
* @param {String} [requestContentType=application/x-www-form-urlencoded]
|
||||
* Request content type for POST; ignored if no body
|
||||
* @param {Object} [headers] HTTP headers to include in request;
|
||||
* Content-Type defaults to application/x-www-form-urlencoded
|
||||
* for POST; ignored if no body
|
||||
* @param {String} [responseCharset] Character set to force on the response
|
||||
* @return {String} Request body
|
||||
*/
|
||||
Zotero.Utilities.Translate.prototype.retrieveSource = function(url, body, requestContentType, responseCharset) {
|
||||
Zotero.Utilities.Translate.prototype.retrieveSource = function(url, body, headers, responseCharset) {
|
||||
/* Apparently, a synchronous XMLHttpRequest would have the behavior of this routine in FF3, but
|
||||
* in FF3.5, synchronous XHR blocks all JavaScript on the thread. See
|
||||
* http://hacks.mozilla.org/2009/07/synchronous-xhr/. */
|
||||
if(this.translate.locationIsProxied) url = this._convertURL(url);
|
||||
if(!requestContentType) requestContentType = null;
|
||||
if(!headers) headers = null;
|
||||
if(!responseCharset) responseCharset = null;
|
||||
|
||||
var mainThread = Zotero.mainThread;
|
||||
|
@ -813,7 +857,7 @@ Zotero.Utilities.Translate.prototype.retrieveSource = function(url, body, reques
|
|||
var listener = function(aXmlhttp) { xmlhttp = aXmlhttp };
|
||||
|
||||
if(body) {
|
||||
Zotero.Utilities.HTTP.doPost(url, body, listener, requestContentType, responseCharset);
|
||||
Zotero.Utilities.HTTP.doPost(url, body, listener, headers, responseCharset);
|
||||
} else {
|
||||
Zotero.Utilities.HTTP.doGet(url, listener, responseCharset);
|
||||
}
|
||||
|
@ -870,7 +914,7 @@ Zotero.Utilities.Translate.prototype.doGet = function(urls, processor, done, res
|
|||
* Already documented in Zotero.Utilities.HTTP
|
||||
* @ignore
|
||||
*/
|
||||
Zotero.Utilities.Translate.prototype.doPost = function(url, body, onDone, requestContentType, responseCharset) {
|
||||
Zotero.Utilities.Translate.prototype.doPost = function(url, body, onDone, headers, responseCharset) {
|
||||
url = this._convertURL(url);
|
||||
|
||||
var translate = this.translate;
|
||||
|
@ -880,7 +924,7 @@ Zotero.Utilities.Translate.prototype.doPost = function(url, body, onDone, reques
|
|||
} catch(e) {
|
||||
translate.error(false, e);
|
||||
}
|
||||
}, requestContentType, responseCharset);
|
||||
}, headers, responseCharset);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -936,7 +980,6 @@ Zotero.Utilities.HTTP = new function() {
|
|||
}
|
||||
else {
|
||||
Zotero.debug("HTTP GET " + url);
|
||||
|
||||
}
|
||||
if (this.browserIsOffline()){
|
||||
return false;
|
||||
|
@ -981,12 +1024,20 @@ Zotero.Utilities.HTTP = new function() {
|
|||
* @param {String} url URL to request
|
||||
* @param {String} body Request body
|
||||
* @param {Function} onDone Callback to be executed upon request completion
|
||||
* @param {String} requestContentType Request content type (usually
|
||||
* application/x-www-form-urlencoded)
|
||||
* @param {String} headers Request HTTP headers
|
||||
* @param {String} responseCharset Character set to force on the response
|
||||
* @return {Boolean} True if the request was sent, or false if the browser is offline
|
||||
*/
|
||||
this.doPost = function(url, body, onDone, requestContentType, responseCharset) {
|
||||
this.doPost = function(url, body, onDone, headers, responseCharset) {
|
||||
if (url instanceof Components.interfaces.nsIURI) {
|
||||
// Don't display password in console
|
||||
var disp = url.clone();
|
||||
if (disp.password) {
|
||||
disp.password = "********";
|
||||
}
|
||||
url = url.spec;
|
||||
}
|
||||
|
||||
var bodyStart = body.substr(0, 1024);
|
||||
// Don't display sync password or session id in console
|
||||
bodyStart = bodyStart.replace(/password=[^&]+/, 'password=********');
|
||||
|
@ -995,7 +1046,7 @@ Zotero.Utilities.HTTP = new function() {
|
|||
Zotero.debug("HTTP POST "
|
||||
+ (body.length > 1024 ?
|
||||
bodyStart + '... (' + body.length + ' chars)' : bodyStart)
|
||||
+ " to " + url);
|
||||
+ " to " + (disp ? disp.spec : url));
|
||||
|
||||
|
||||
if (this.browserIsOffline()){
|
||||
|
@ -1025,7 +1076,27 @@ Zotero.Utilities.HTTP = new function() {
|
|||
xmlhttp.channel.loadGroup = ds.getInterface(Ci.nsILoadGroup);
|
||||
xmlhttp.channel.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI;
|
||||
|
||||
xmlhttp.setRequestHeader("Content-Type", (requestContentType ? requestContentType : "application/x-www-form-urlencoded" ));
|
||||
if (headers) {
|
||||
if (typeof headers == 'string') {
|
||||
var msg = "doPost() now takes a headers object rather than a requestContentType -- update your code";
|
||||
Zotero.debug(msg, 2);
|
||||
Components.utils.reportError(msg);
|
||||
headers = {
|
||||
"Content-Type": headers
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
headers = {};
|
||||
}
|
||||
|
||||
if (!headers["Content-Type"]) {
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||
}
|
||||
|
||||
for (var header in headers) {
|
||||
xmlhttp.setRequestHeader(header, headers[header]);
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
xmlhttp.onreadystatechange = function(){
|
||||
|
@ -1042,10 +1113,24 @@ Zotero.Utilities.HTTP = new function() {
|
|||
*
|
||||
* @param {String} url URL to request
|
||||
* @param {Function} onDone Callback to be executed upon request completion
|
||||
* @param {Object} requestHeaders HTTP headers to include with request
|
||||
* @return {Boolean} True if the request was sent, or false if the browser is offline
|
||||
*/
|
||||
this.doHead = function(url, onDone) {
|
||||
this.doHead = function(url, onDone, requestHeaders) {
|
||||
if (url instanceof Components.interfaces.nsIURI) {
|
||||
// Don't display password in console
|
||||
var disp = url.clone();
|
||||
if (disp.password) {
|
||||
disp.password = "********";
|
||||
}
|
||||
Zotero.debug("HTTP HEAD " + disp.spec);
|
||||
url = url.spec;
|
||||
}
|
||||
else {
|
||||
Zotero.debug("HTTP HEAD " + url);
|
||||
|
||||
}
|
||||
|
||||
if (this.browserIsOffline()){
|
||||
return false;
|
||||
}
|
||||
|
@ -1068,10 +1153,20 @@ Zotero.Utilities.HTTP = new function() {
|
|||
ds.itemType = Ci.nsIDocShellTreeItem.typeContent;
|
||||
var xmlhttp = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
||||
createInstance(Ci.nsIXMLHttpRequest);
|
||||
// Prevent certificate/authentication dialogs from popping up
|
||||
xmlhttp.mozBackgroundRequest = true;
|
||||
xmlhttp.open("HEAD", url, true);
|
||||
if (requestHeaders) {
|
||||
for (var header in requestHeaders) {
|
||||
xmlhttp.setRequestHeader(header, requestHeaders[header]);
|
||||
}
|
||||
}
|
||||
xmlhttp.channel.loadGroup = ds.getInterface(Ci.nsILoadGroup);
|
||||
xmlhttp.channel.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI;
|
||||
|
||||
// Don't cache HEAD requests
|
||||
xmlhttp.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
|
||||
|
||||
/** @ignore */
|
||||
xmlhttp.onreadystatechange = function(){
|
||||
_stateChange(xmlhttp, onDone);
|
||||
|
|
|
@ -28,7 +28,8 @@ const ZOTERO_CONFIG = {
|
|||
REPOSITORY_RETRY_INTERVAL: 3600, // 1 hour
|
||||
BASE_URI: 'http://zotero.org/',
|
||||
WWW_BASE_URL: 'http://www.zotero.org/',
|
||||
SYNC_URL: 'https://sync.zotero.org/'
|
||||
SYNC_URL: 'https://sync.zotero.org/',
|
||||
API_URL: 'https://api.zotero.org/'
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -100,6 +101,17 @@ var Zotero = new function(){
|
|||
Zotero.DB.query(sql, parseInt(val));
|
||||
});
|
||||
|
||||
this.__defineGetter__('username', function () {
|
||||
var sql = "SELECT value FROM settings WHERE "
|
||||
+ "setting='account' AND key='username'";
|
||||
return Zotero.DB.valueQuery(sql);
|
||||
});
|
||||
|
||||
this.__defineSetter__('username', function (val) {
|
||||
var sql = "REPLACE INTO settings VALUES ('account', 'username', ?)";
|
||||
Zotero.DB.query(sql, val);
|
||||
});
|
||||
|
||||
this.getLocalUserKey = function (generate) {
|
||||
if (_localUserKey) {
|
||||
return _localUserKey;
|
||||
|
@ -1160,8 +1172,9 @@ var Zotero = new function(){
|
|||
Zotero.Fulltext.purgeUnusedWords();
|
||||
Zotero.Items.purge();
|
||||
|
||||
if (!skipStoragePurge && Zotero.Sync.Storage.active && Zotero.Utilities.prototype.probability(10)) {
|
||||
Zotero.Sync.Storage.purgeDeletedStorageFiles();
|
||||
if (!skipStoragePurge && Zotero.Utilities.prototype.probability(10)) {
|
||||
Zotero.Sync.Storage.purgeDeletedStorageFiles('zfs');
|
||||
Zotero.Sync.Storage.purgeDeletedStorageFiles('webdav');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
extensions.zotero@chnm.gmu.edu.description = The Next-Generation Research Tool
|
||||
|
||||
general.success = Success
|
||||
general.error = Error
|
||||
general.warning = Warning
|
||||
general.dontShowWarningAgain = Don't show this warning again.
|
||||
|
|
BIN
chrome/skin/default/zotero/error.png
Executable file
BIN
chrome/skin/default/zotero/error.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 666 B |
BIN
chrome/skin/default/zotero/exclamation.png
Executable file
BIN
chrome/skin/default/zotero/exclamation.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 701 B |
|
@ -205,9 +205,15 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
#zotero-tb-storage-sync
|
||||
#zotero-tb-sync-warning
|
||||
{
|
||||
list-style-image: url(chrome://zotero/skin/drive_network.png);
|
||||
list-style-image: url(chrome://zotero/skin/error.png);
|
||||
margin-right: -5px;
|
||||
}
|
||||
|
||||
#zotero-tb-sync-warning[error=true]
|
||||
{
|
||||
list-style-image: url(chrome://zotero/skin/exclamation.png);
|
||||
}
|
||||
|
||||
#zotero-tb-sync {
|
||||
|
@ -220,10 +226,6 @@
|
|||
list-style-image: url(chrome://zotero/skin/arrow_rotate_animated.png);
|
||||
}
|
||||
|
||||
#zotero-tb-sync[status=error] {
|
||||
list-style-image: url(chrome://zotero/skin/arrow_rotate_error.png);
|
||||
}
|
||||
|
||||
#zotero-tb-sync #zotero-last-sync-time
|
||||
{
|
||||
color: gray;
|
||||
|
|
|
@ -64,6 +64,9 @@ var xpcomFiles = [
|
|||
'style',
|
||||
'sync',
|
||||
'storage',
|
||||
'storage/session',
|
||||
'storage/zfs',
|
||||
'storage/webdav',
|
||||
'timeline',
|
||||
'translate',
|
||||
'uri',
|
||||
|
|
|
@ -37,6 +37,10 @@ pref("extensions.zotero.sortAttachmentsChronologically", false);
|
|||
pref("extensions.zotero.showTrashWhenEmpty", true);
|
||||
pref("extensions.zotero.viewOnDoubleClick", true);
|
||||
|
||||
pref("extensions.zotero.groups.copyChildLinks", true);
|
||||
pref("extensions.zotero.groups.copyChildFileAttachments", true);
|
||||
pref("extensions.zotero.groups.copyChildNotes", true);
|
||||
|
||||
pref("extensions.zotero.backup.numBackups", 2);
|
||||
pref("extensions.zotero.backup.interval", 1440);
|
||||
|
||||
|
@ -113,14 +117,16 @@ pref("extensions.zotero.annotations.warnOnClose", true);
|
|||
pref("extensions.zotero.sync.autoSync", true);
|
||||
pref("extensions.zotero.sync.server.username", '');
|
||||
pref("extensions.zotero.sync.server.compressData", true);
|
||||
pref("extensions.zotero.sync.storage.protocol", "webdavs");
|
||||
pref("extensions.zotero.sync.storage.enabled", false);
|
||||
pref("extensions.zotero.sync.storage.enabled", true);
|
||||
pref("extensions.zotero.sync.storage.protocol", "zotero");
|
||||
pref("extensions.zotero.sync.storage.verified", false);
|
||||
pref("extensions.zotero.sync.storage.scheme", 'https');
|
||||
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);
|
||||
pref("extensions.zotero.sync.storage.groups.enabled", true);
|
||||
|
||||
// Proxy
|
||||
pref("extensions.zotero.proxies.transparent", true);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- 62
|
||||
-- 63
|
||||
|
||||
-- This file creates tables containing user-specific data for new users --
|
||||
-- any changes made here must be mirrored in transition steps in schema.js::_migrateSchema()
|
||||
|
@ -70,6 +70,7 @@ CREATE TABLE itemAttachments (
|
|||
originalPath TEXT,
|
||||
syncState INT DEFAULT 0,
|
||||
storageModTime INT,
|
||||
storageHash TEXT,
|
||||
FOREIGN KEY (itemID) REFERENCES items(itemID),
|
||||
FOREIGN KEY (sourceItemID) REFERENCES items(itemID)
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue