On-demand download support
Can choose to download files "at sync time" or "as needed" On-demand defaults to on, but remains off for existing users To-do: - Handling of local and remote file changes on on-demand download (currently if a file exists it isn't downloaded, which means a remotely modified file won't be redownloaded in on-demand mode) - Additional control over file downloading and retention Other changes: - Overhauled entire file syncing architecture - Replaced numAttachments column with Note and Attachment columns with dynamic icons to indicate status - Double-clicking a parent with a missing best attachment and on-demand downloading off no longer loads the parent URL - Bugs
This commit is contained in:
parent
80530b9599
commit
758216638f
33 changed files with 4829 additions and 4170 deletions
|
@ -178,7 +178,7 @@ var ZoteroOverlay = new function()
|
|||
* Hides/displays the Zotero interface
|
||||
*/
|
||||
this.toggleDisplay = function(makeVisible)
|
||||
{
|
||||
{
|
||||
if(!Zotero || !Zotero.initialized) {
|
||||
ZoteroPane.displayStartupError();
|
||||
return;
|
||||
|
|
|
@ -198,6 +198,7 @@ function updateStorageSettings(enabled, protocol, skipWarnings) {
|
|||
protocol = oldProtocol;
|
||||
}
|
||||
|
||||
var storageSettings = document.getElementById('storage-settings');
|
||||
var protocolMenu = document.getElementById('storage-protocol');
|
||||
var settings = document.getElementById('storage-webdav-settings');
|
||||
var sep = document.getElementById('storage-separator');
|
||||
|
@ -211,7 +212,12 @@ function updateStorageSettings(enabled, protocol, skipWarnings) {
|
|||
sep.hidden = true;
|
||||
}
|
||||
|
||||
protocolMenu.disabled = !enabled;
|
||||
var menulists = storageSettings.getElementsByTagName('menulist');
|
||||
for each(var menulist in menulists) {
|
||||
if (menulist.className == 'storage-personal') {
|
||||
menulist.disabled = !enabled;
|
||||
}
|
||||
}
|
||||
|
||||
if (!skipWarnings) {
|
||||
// WARN if going between
|
||||
|
@ -270,6 +276,21 @@ function updateStorageSettings(enabled, protocol, skipWarnings) {
|
|||
}
|
||||
|
||||
|
||||
function updateStorageSettingsGroups(enabled) {
|
||||
var storageSettings = document.getElementById('storage-settings');
|
||||
var menulists = storageSettings.getElementsByTagName('menulist');
|
||||
for each(var menulist in menulists) {
|
||||
if (menulist.className == 'storage-groups') {
|
||||
menulist.disabled = !enabled;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
updateStorageTerms();
|
||||
}, 1)
|
||||
}
|
||||
|
||||
|
||||
function updateStorageTerms() {
|
||||
var terms = document.getElementById('storage-terms');
|
||||
|
||||
|
@ -297,7 +318,11 @@ function verifyStorageServer() {
|
|||
var usernameField = document.getElementById("storage-username");
|
||||
var passwordField = document.getElementById("storage-password");
|
||||
|
||||
var callback = function (uri, status, error) {
|
||||
verifyButton.hidden = true;
|
||||
abortButton.hidden = false;
|
||||
progressMeter.hidden = false;
|
||||
|
||||
var requestHolder = Zotero.Sync.Storage.checkServer('webdav', function (uri, status, callback) {
|
||||
verifyButton.hidden = false;
|
||||
abortButton.hidden = true;
|
||||
progressMeter.hidden = true;
|
||||
|
@ -322,13 +347,9 @@ function verifyStorageServer() {
|
|||
break;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.checkServerCallback(uri, status, window, false, error);
|
||||
}
|
||||
callback(uri, status, window);
|
||||
});
|
||||
|
||||
verifyButton.hidden = true;
|
||||
abortButton.hidden = false;
|
||||
progressMeter.hidden = false;
|
||||
var requestHolder = Zotero.Sync.Storage.checkServer('webdav', callback);
|
||||
abortButton.onclick = function () {
|
||||
if (requestHolder.request) {
|
||||
requestHolder.request.onreadystatechange = undefined;
|
||||
|
|
|
@ -165,13 +165,15 @@ To add a new preference:
|
|||
<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-storage-downloadMode-personal" name="extensions.zotero.sync.storage.downloadMode.personal" type="string"/>
|
||||
<preference id="pref-storage-downloadMode-groups" name="extensions.zotero.sync.storage.downloadMode.groups" type="string"/>
|
||||
<preference id="pref-group-storage-enabled" name="extensions.zotero.sync.storage.groups.enabled" type="bool"/>
|
||||
</preferences>
|
||||
|
||||
<tabbox>
|
||||
<tabs>
|
||||
<tab label="Settings"/>
|
||||
<tab label="Reset"/>
|
||||
<tab label="&zotero.preferences.settings;"/>
|
||||
<tab label="&zotero.preferences.sync.reset;"/>
|
||||
</tabs>
|
||||
|
||||
<tabpanels>
|
||||
|
@ -197,6 +199,10 @@ To add a new preference:
|
|||
<textbox id="sync-password" type="password"
|
||||
onchange="Zotero.Sync.Server.password = this.value"/>
|
||||
</row>
|
||||
<row>
|
||||
<box/>
|
||||
<checkbox label="&zotero.preferences.sync.syncAutomatically;" preference="pref-sync-autosync"/>
|
||||
</row>
|
||||
<!--
|
||||
<row>
|
||||
<box/>
|
||||
|
@ -212,29 +218,23 @@ To add a new preference:
|
|||
<hbox style="width:2em"/>
|
||||
|
||||
<vbox>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.about;" href="http://www.zotero.org/support/sync"/>
|
||||
<separator class="thin"/>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.createAccount;" href="http://zotero.org/user/register"/>
|
||||
<separator class="thin"/>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.lostPassword;" href="http://zotero.org/user/lostpassword"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<hbox>
|
||||
<checkbox label="&zotero.preferences.sync.syncAutomatically;" preference="pref-sync-autosync"/>
|
||||
</hbox>
|
||||
|
||||
<label class="zotero-text-link" style="margin-top:.7em; margin-left: 0" value="&zotero.preferences.sync.about;" href="http://www.zotero.org/support/sync"/>
|
||||
</groupbox>
|
||||
|
||||
|
||||
<groupbox>
|
||||
<groupbox id="storage-settings">
|
||||
<caption label="&zotero.preferences.sync.fileSyncing;"/>
|
||||
|
||||
<!-- My Library -->
|
||||
<hbox style="margin: 0">
|
||||
<hbox>
|
||||
<checkbox label="&zotero.preferences.sync.fileSyncing.myLibrary;" 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)">
|
||||
<menulist id="storage-protocol" class="storage-personal" style="margin-left: .5em" preference="pref-storage-protocol" oncommand="updateStorageSettings(null, this.value)">
|
||||
<menupopup>
|
||||
<menuitem label="Zotero" value="zotero"/>
|
||||
<menuitem label="WebDAV" value="webdav"/>
|
||||
|
@ -307,16 +307,35 @@ To add a new preference:
|
|||
|
||||
</stack>
|
||||
|
||||
<hbox class="storage-settings-download-options" align="center">
|
||||
<label value="&zotero.preferences.sync.fileSyncing.download;"/>
|
||||
<menulist class="storage-personal" preference="pref-storage-downloadMode-personal" style="margin-left: 0">
|
||||
<menupopup>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.onDemand;" value="on-demand"/>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.atSyncTime;" value="on-sync"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<separator id="storage-separator" class="thin"/>
|
||||
|
||||
<!-- Group Libraries -->
|
||||
<checkbox label="&zotero.preferences.sync.fileSyncing.groups;"
|
||||
preference="pref-group-storage-enabled" oncommand="setTimeout(function () { updateStorageTerms(); }, 1)"/>
|
||||
preference="pref-group-storage-enabled" oncommand="updateStorageSettingsGroups(this.checked)"/>
|
||||
|
||||
<hbox class="storage-settings-download-options" align="center">
|
||||
<label value="&zotero.preferences.sync.fileSyncing.download;"/>
|
||||
<menulist class="storage-groups" preference="pref-storage-downloadMode-groups" style="margin-left: 0">
|
||||
<menupopup>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.onDemand;" value="on-demand"/>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.atSyncTime;" value="on-sync"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<vbox>
|
||||
<label class="zotero-text-link" style="margin-top:.3em; margin-left: 0" value="&zotero.preferences.sync.fileSyncing.about;" href="http://zotero.org/support/file_sync"/>
|
||||
<hbox id="storage-terms" style="margin-top: .4em" align="center">
|
||||
<label>&zotero.preferences.sync.fileSyncing.tos1;</label>
|
||||
<label class="zotero-text-link" href="http://www.digitalscholar.org/z_terms" value="&zotero.preferences.sync.fileSyncing.tos2;"/>
|
||||
|
|
|
@ -147,9 +147,18 @@
|
|||
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol
|
||||
id="zotero-items-column-numChildren"
|
||||
label="&zotero.items.numChildren_column;"
|
||||
persist="width ordinal hidden sortActive sortDirection"/>
|
||||
id="zotero-items-column-hasAttachment"
|
||||
class="treecol-image"
|
||||
label="&zotero.tabs.attachments.label;"
|
||||
src="chrome://zotero/skin/attach-small.png"
|
||||
zotero-persist="width ordinal hidden sortActive sortDirection"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol
|
||||
id="zotero-items-column-hasNote"
|
||||
class="treecol-image"
|
||||
label="&zotero.tabs.notes.label;"
|
||||
src="chrome://zotero/skin/treeitem-note-small.png"
|
||||
zotero-persist="width ordinal hidden sortActive sortDirection"/>
|
||||
</treecols>
|
||||
<treechildren/>
|
||||
</tree>
|
||||
|
|
|
@ -366,16 +366,6 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
|
|||
|
||||
this.editCheck = function (obj) {
|
||||
if (!Zotero.Sync.Server.updatesInProgress && !Zotero.Sync.Storage.updatesInProgress && !this.isEditable(obj)) {
|
||||
if (Zotero.Sync.Storage.syncInProgress) {
|
||||
try {
|
||||
asfasf();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e);
|
||||
}
|
||||
Components.utils.reportError("Storage sync in progress but updatesInProgress not set -- fix?");
|
||||
return;
|
||||
}
|
||||
throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,9 @@ Zotero.Item.prototype._init = function () {
|
|||
this._dateModified = null;
|
||||
this._firstCreator = null;
|
||||
this._numNotes = null;
|
||||
this._numNotesTrashed = null;
|
||||
this._numNotesEmbedded = null;
|
||||
this._numNotesEmbeddedIncludingTrashed = null;
|
||||
this._numAttachments = null;
|
||||
|
||||
this._creators = [];
|
||||
|
@ -76,7 +79,11 @@ Zotero.Item.prototype._init = function () {
|
|||
this._skipModTimeUpdate = false;
|
||||
this._previousData = null;
|
||||
|
||||
this._bestAttachmentState = null;
|
||||
this._fileExists = null;
|
||||
|
||||
this._deleted = null;
|
||||
this._hasNote = null;
|
||||
this._noteTitle = null;
|
||||
this._noteText = null;
|
||||
this._noteAccessTime = null;
|
||||
|
@ -1804,6 +1811,13 @@ Zotero.Item.prototype.save = function() {
|
|||
sql = "DELETE FROM deletedItems WHERE itemID=?";
|
||||
}
|
||||
Zotero.DB.query(sql, this.id);
|
||||
|
||||
var parent = this.getSource();
|
||||
if (parent) {
|
||||
parent = Zotero.Items.get(parent);
|
||||
parent.updateNumNotes();
|
||||
parent.updateBestAttachmentState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1843,6 +1857,13 @@ Zotero.Item.prototype.save = function() {
|
|||
];
|
||||
}
|
||||
Zotero.DB.query(sql, bindParams);
|
||||
|
||||
if (this.isAttachment()) {
|
||||
var parent = this.getSource();
|
||||
if (parent) {
|
||||
Zotero.Items.get(parent).updateNumNotes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -2316,9 +2337,10 @@ Zotero.Item.prototype.updateNote = function(text) {
|
|||
* Returns number of child notes of item
|
||||
*
|
||||
* @param {Boolean} includeTrashed Include trashed child items in count
|
||||
* @param {Boolean} includeEmbedded Include notes embedded in attachments
|
||||
* @return {Integer}
|
||||
*/
|
||||
Zotero.Item.prototype.numNotes = function(includeTrashed) {
|
||||
Zotero.Item.prototype.numNotes = function(includeTrashed, includeEmbedded) {
|
||||
if (this.isNote()) {
|
||||
throw ("numNotes() cannot be called on items of type 'note'");
|
||||
}
|
||||
|
@ -2327,14 +2349,38 @@ Zotero.Item.prototype.numNotes = function(includeTrashed) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
var deleted = 0;
|
||||
if (includeTrashed) {
|
||||
if (includeTrashed && this._numNotesTrashed === null) {
|
||||
var sql = "SELECT COUNT(*) FROM itemNotes WHERE sourceItemID=? AND "
|
||||
+ "itemID IN (SELECT itemID FROM deletedItems)";
|
||||
deleted = parseInt(Zotero.DB.valueQuery(sql, this.id));
|
||||
this._numNotesTrashed = parseInt(Zotero.DB.valueQuery(sql, this.id));
|
||||
}
|
||||
var embedded = 0;
|
||||
if (includeEmbedded) {
|
||||
if ((includeTrashed ? this._numNotesEmbeddedIncludingTrashed : this._numNotesEmbedded) === null) {
|
||||
var sql = "SELECT COUNT(*) FROM itemAttachments IA JOIN itemNotes USING (itemID) "
|
||||
+ "WHERE IA.sourceItemID=? AND note!='' AND note!=?";
|
||||
if (!includeTrashed) {
|
||||
sql += " AND itemID NOT IN (SELECT itemID FROM deletedItems)";
|
||||
}
|
||||
var embedded = parseInt(Zotero.DB.valueQuery(sql, [this.id, Zotero.Notes.defaultNote]));
|
||||
if (includeTrashed) {
|
||||
this._numNotesEmbeddedIncludingTrashed = embedded;
|
||||
}
|
||||
else {
|
||||
this._numNotesEmbedded = embedded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._numNotes + deleted;
|
||||
return this._numNotes + this._numNotesTrashed +
|
||||
(includeTrashed ? this._numNotesEmbeddedIncludingTrashed : this._numNotesEmbedded);
|
||||
}
|
||||
|
||||
|
||||
Zotero.Item.prototype.updateNumNotes = function () {
|
||||
this._numNotesTrashed = null;
|
||||
this._numNotesEmbedded = null;
|
||||
this._numNotesEmbeddedIncludingTrashed = null;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2367,9 +2413,31 @@ Zotero.Item.prototype.getNoteTitle = function() {
|
|||
}
|
||||
|
||||
|
||||
Zotero.Item.prototype.hasNote = function () {
|
||||
if (!this.isNote() && !this.isAttachment()) {
|
||||
throw new Error("hasNote() can only be called on notes and attachments");
|
||||
}
|
||||
|
||||
if (this._hasNote !== null) {
|
||||
return this._hasNote;
|
||||
}
|
||||
|
||||
if (!this.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var sql = "SELECT COUNT(*) FROM itemNotes WHERE itemID=? "
|
||||
+ "AND note!='' AND note!=?";
|
||||
var hasNote = !!Zotero.DB.valueQuery(sql, [this.id, Zotero.Notes.defaultNote]);
|
||||
|
||||
this._hasNote = hasNote;
|
||||
return hasNote;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the text of an item note
|
||||
**/
|
||||
* Get the text of an item note
|
||||
**/
|
||||
Zotero.Item.prototype.getNote = function() {
|
||||
if (!this.isNote() && !this.isAttachment()) {
|
||||
throw ("getNote() can only be called on notes and attachments");
|
||||
|
@ -2393,11 +2461,11 @@ Zotero.Item.prototype.getNote = function() {
|
|||
if (note) {
|
||||
if (!note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)) {
|
||||
note = Zotero.Utilities.htmlSpecialChars(note);
|
||||
note = '<div class="zotero-note znv1"><p>'
|
||||
note = Zotero.Notes.notePrefix + '<p>'
|
||||
+ note.replace(/\n/g, '</p><p>')
|
||||
.replace(/\t/g, ' ')
|
||||
.replace(/ /g, ' ')
|
||||
+ '</p></div>';
|
||||
+ '</p>' + Zotero.Notes.noteSuffix;
|
||||
note = note.replace(/<p>\s*<\/p>/g, '<p> </p>');
|
||||
var sql = "UPDATE itemNotes SET note=? WHERE itemID=?";
|
||||
Zotero.DB.query(sql, [note, this.id]);
|
||||
|
@ -2410,7 +2478,6 @@ Zotero.Item.prototype.getNote = function() {
|
|||
}
|
||||
|
||||
this._noteText = note ? note : '';
|
||||
|
||||
return this._noteText;
|
||||
}
|
||||
|
||||
|
@ -2441,6 +2508,7 @@ Zotero.Item.prototype.setNote = function(text) {
|
|||
this._previousData = this.serialize();
|
||||
}
|
||||
|
||||
this._hasNote = text !== '';
|
||||
this._noteText = text;
|
||||
this._noteTitle = Zotero.Notes.noteToTitle(text);
|
||||
this._changedNote = true;
|
||||
|
@ -2547,6 +2615,14 @@ Zotero.Item.prototype.isWebAttachment = function() {
|
|||
}
|
||||
|
||||
|
||||
Zotero.Item.prototype.isFileAttachment = function() {
|
||||
if (!this.isAttachment()) {
|
||||
return false;
|
||||
}
|
||||
return this.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns number of child attachments of item
|
||||
*
|
||||
|
@ -2594,6 +2670,18 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
|
|||
};
|
||||
}
|
||||
|
||||
// Update file existence state of this item
|
||||
// and best attachment state of parent item
|
||||
var self = this;
|
||||
var updateAttachmentStates = function (exists) {
|
||||
self._fileExists = exists;
|
||||
|
||||
if (self.isTopLevelItem()) {
|
||||
return;
|
||||
}
|
||||
Zotero.Items.get(self.getSource()).updateBestAttachmentState();
|
||||
};
|
||||
|
||||
// No associated files for linked URLs
|
||||
if (row.linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
return false;
|
||||
|
@ -2601,6 +2689,7 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
|
|||
|
||||
if (!row.path) {
|
||||
Zotero.debug("Attachment path is empty", 2);
|
||||
updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2648,6 +2737,7 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
|
|||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Invalid persistent descriptor', 2);
|
||||
updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -2674,6 +2764,7 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
|
|||
}
|
||||
catch (e) {
|
||||
Zotero.debug('Invalid relative descriptor', 2);
|
||||
updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -2681,9 +2772,11 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
|
|||
|
||||
if (!skipExistsCheck && !file.exists()) {
|
||||
Zotero.debug("Attachment file '" + file.path + "' not found", 2);
|
||||
updateAttachmentStates(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
updateAttachmentStates(true);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
@ -2706,6 +2799,19 @@ Zotero.Item.prototype.getFilename = function () {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cached check for file existence, used for items view
|
||||
*
|
||||
* This is updated only initially and on subsequent getFile() calls.
|
||||
*/
|
||||
Zotero.Item.prototype.__defineGetter__('fileExists', function () {
|
||||
if (this._fileExists === null) {
|
||||
this.getFile();
|
||||
}
|
||||
return this._fileExists;
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Rename file associated with an attachment
|
||||
*
|
||||
|
@ -3304,7 +3410,7 @@ Zotero.Item.prototype.getBestSnapshot = function () {
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
* Looks for attachment in the following order: oldest PDF attachment matching parent URL,
|
||||
* oldest non-PDF attachment matching parent URL, oldest PDF attachment not matching URL,
|
||||
* old non-PDF attachment not matching URL
|
||||
|
@ -3315,15 +3421,36 @@ Zotero.Item.prototype.getBestAttachment = function() {
|
|||
if (!this.isRegularItem()) {
|
||||
throw ("getBestAttachment() can only be called on regular items");
|
||||
}
|
||||
return this.getBestAttachments()[0];
|
||||
var attachments = this.getBestAttachments();
|
||||
return attachments ? attachments[0] : false;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Returned cached state of best attachment for use in items view
|
||||
*
|
||||
* @return {Integer} 0 (none), 1 (present), -1 (missing)
|
||||
*/
|
||||
Zotero.Item.prototype.getBestAttachmentState = function () {
|
||||
if (this._bestAttachmentState !== null) {
|
||||
return this._bestAttachmentState;
|
||||
}
|
||||
var itemID = this.getBestAttachment();
|
||||
this._bestAttachmentState = itemID ? (Zotero.Items.get(itemID).fileExists ? 1 : -1) : 0;
|
||||
return this._bestAttachmentState;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Item.prototype.updateBestAttachmentState = function () {
|
||||
this._bestAttachmentState = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Looks for attachment in the following order: oldest PDF attachment matching parent URL,
|
||||
* oldest PDF attachment not matching parent URL, oldest non-PDF attachment matching parent URL,
|
||||
* old non-PDF attachment not matching parent URL
|
||||
*
|
||||
* @return {Array} itemIDs for attachments
|
||||
* @return {Array|FALSE} itemIDs for attachments, or FALSE if none
|
||||
*/
|
||||
Zotero.Item.prototype.getBestAttachments = function() {
|
||||
if (!this.isRegularItem()) {
|
||||
|
|
|
@ -28,6 +28,9 @@ Zotero.Notes = new function() {
|
|||
this.noteToTitle = noteToTitle;
|
||||
|
||||
this.__defineGetter__("MAX_TITLE_LENGTH", function() { return 80; });
|
||||
this.__defineGetter__("defaultNote", function () '<div class="zotero-note znv1"></div>');
|
||||
this.__defineGetter__("notePrefix", function () '<div class="zotero-note znv1">');
|
||||
this.__defineGetter__("noteSuffix", function () '</div>');
|
||||
|
||||
/**
|
||||
* Return first line (or first MAX_LENGTH characters) of note content
|
||||
|
|
|
@ -245,8 +245,14 @@ Zotero.ItemTreeView.prototype._refreshGenerator = function()
|
|||
|
||||
for (var i=0; i<visibleFields.length; i++) {
|
||||
var field = visibleFields[i];
|
||||
if (field == 'year') {
|
||||
field = 'date';
|
||||
switch (field) {
|
||||
case 'hasAttachment':
|
||||
case 'hasNote':
|
||||
continue;
|
||||
|
||||
case 'year':
|
||||
field = 'date';
|
||||
break;
|
||||
}
|
||||
if (cacheFields.indexOf(field) == -1) {
|
||||
cacheFields = cacheFields.concat(field);
|
||||
|
@ -724,13 +730,9 @@ Zotero.ItemTreeView.prototype.getCellText = function(row, column)
|
|||
|
||||
var val;
|
||||
|
||||
if(column.id == "zotero-items-column-numChildren")
|
||||
{
|
||||
var c = obj.numChildren(this._itemGroup.isTrash());
|
||||
// Don't display '0'
|
||||
if(c && parseInt(c) > 0) {
|
||||
val = c;
|
||||
}
|
||||
// Image only
|
||||
if (column.id === "zotero-items-column-hasAttachment" || column.id === "zotero-items-column-hasNote") {
|
||||
return;
|
||||
}
|
||||
else if(column.id == "zotero-items-column-type")
|
||||
{
|
||||
|
@ -791,6 +793,47 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
|
|||
{
|
||||
return this._getItemAtRow(row).ref.getImageSrc();
|
||||
}
|
||||
else if (col.id == 'zotero-items-column-hasAttachment') {
|
||||
if (this._itemGroup.isTrash()) return false;
|
||||
|
||||
var treerow = this._getItemAtRow(row);
|
||||
if (treerow.level === 0) {
|
||||
if (treerow.ref.isRegularItem()) {
|
||||
switch (treerow.ref.getBestAttachmentState()) {
|
||||
case 1:
|
||||
return "chrome://zotero/skin/bullet_blue.png";
|
||||
|
||||
case -1:
|
||||
return "chrome://zotero/skin/bullet_blue_empty.png";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (treerow.ref.isFileAttachment()) {
|
||||
if (treerow.ref.fileExists) {
|
||||
return "chrome://zotero/skin/bullet_blue.png";
|
||||
}
|
||||
else {
|
||||
return "chrome://zotero/skin/bullet_blue_empty.png";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (col.id == 'zotero-items-column-hasNote') {
|
||||
if (this._itemGroup.isTrash()) return false;
|
||||
|
||||
var treerow = this._getItemAtRow(row);
|
||||
if (treerow.level === 0 && treerow.ref.isRegularItem() && treerow.ref.numNotes(false, true)) {
|
||||
return "chrome://zotero/skin/bullet_yellow.png";
|
||||
}
|
||||
else if (treerow.ref.isAttachment()) {
|
||||
if (treerow.ref.hasNote()) {
|
||||
return "chrome://zotero/skin/bullet_yellow.png";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.ItemTreeView.prototype.isContainer = function(row)
|
||||
|
@ -805,13 +848,16 @@ Zotero.ItemTreeView.prototype.isContainerOpen = function(row)
|
|||
|
||||
Zotero.ItemTreeView.prototype.isContainerEmpty = function(row)
|
||||
{
|
||||
if(this._sourcesOnly) {
|
||||
if (this._sourcesOnly) {
|
||||
return true;
|
||||
} else {
|
||||
var includeTrashed = this._itemGroup.isTrash();
|
||||
return (this._getItemAtRow(row).numNotes(includeTrashed) == 0
|
||||
&& this._getItemAtRow(row).numAttachments(includeTrashed) == 0);
|
||||
}
|
||||
|
||||
var item = this._getItemAtRow(row).ref;
|
||||
if (!item.isRegularItem()) {
|
||||
return false;
|
||||
}
|
||||
var includeTrashed = this._itemGroup.isTrash();
|
||||
return item.numNotes(includeTrashed) === 0 && item.numAttachments(includeTrashed) == 0;
|
||||
}
|
||||
|
||||
Zotero.ItemTreeView.prototype.getLevel = function(row)
|
||||
|
@ -1010,25 +1056,51 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
|
|||
|
||||
// Get the display field for a row (which might be a placeholder title)
|
||||
var getField;
|
||||
if (columnField == 'title') {
|
||||
getField = function (row) {
|
||||
var field;
|
||||
var type = row.ref.itemTypeID;
|
||||
switch (type) {
|
||||
case 8: // letter
|
||||
case 10: // interview
|
||||
case 17: // case
|
||||
field = row.ref.getDisplayTitle();
|
||||
break;
|
||||
|
||||
default:
|
||||
field = row.getField(columnField, unformatted);
|
||||
}
|
||||
// Ignore some leading and trailing characters when sorting
|
||||
return Zotero.Items.getSortTitle(field);
|
||||
}
|
||||
} else {
|
||||
getField = function(row) row.getField(columnField, unformatted);
|
||||
switch (columnField) {
|
||||
case 'title':
|
||||
getField = function (row) {
|
||||
var field;
|
||||
var type = row.ref.itemTypeID;
|
||||
switch (type) {
|
||||
case 8: // letter
|
||||
case 10: // interview
|
||||
case 17: // case
|
||||
field = row.ref.getDisplayTitle();
|
||||
break;
|
||||
|
||||
default:
|
||||
field = row.getField(columnField, unformatted);
|
||||
}
|
||||
// Ignore some leading and trailing characters when sorting
|
||||
return Zotero.Items.getSortTitle(field);
|
||||
};
|
||||
break;
|
||||
|
||||
case 'hasAttachment':
|
||||
getField = function (row) {
|
||||
if (!row.ref.isRegularItem()) {
|
||||
return 0;
|
||||
}
|
||||
var state = row.ref.getBestAttachmentState();
|
||||
// Make sort order present, missing, empty when ascending
|
||||
if (state === -1) {
|
||||
state = 2;
|
||||
}
|
||||
return state * -1;
|
||||
};
|
||||
break;
|
||||
|
||||
case 'hasNote':
|
||||
getField = function (row) {
|
||||
if (!row.ref.isRegularItem()) {
|
||||
return 0;
|
||||
}
|
||||
return row.ref.numNotes(false, true) ? 1 : 0;
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
getField = function (row) row.getField(columnField, unformatted);
|
||||
}
|
||||
|
||||
var includeTrashed = this._itemGroup.isTrash();
|
||||
|
@ -1074,13 +1146,6 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
|
|||
}
|
||||
break;
|
||||
|
||||
case 'numChildren':
|
||||
cmp = b.numChildren(includeTrashed) - a.numChildren(includeTrashed);
|
||||
if (cmp) {
|
||||
return cmp;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (fieldA == undefined) {
|
||||
fieldA = getField(a);
|
||||
|
@ -2613,17 +2678,16 @@ Zotero.ItemTreeView.prototype.onDragExit = function (event) {
|
|||
|
||||
Zotero.ItemTreeView.prototype.isSeparator = function(row) { return false; }
|
||||
Zotero.ItemTreeView.prototype.getRowProperties = function(row, prop) {
|
||||
if (!this.selection.isSelected(row)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var itemID = this._getItemAtRow(row).ref.id;
|
||||
var treeRow = this._getItemAtRow(row);
|
||||
var itemID = treeRow.ref.id;
|
||||
|
||||
// Set background color for selected items with colored tags
|
||||
if (color = Zotero.Tags.getItemColor(itemID)) {
|
||||
var aServ = Components.classes["@mozilla.org/atom-service;1"].
|
||||
getService(Components.interfaces.nsIAtomService);
|
||||
prop.AppendElement(aServ.getAtom("color" + color.substr(1)));
|
||||
if (this.selection.isSelected(row)) {
|
||||
if (color = Zotero.Tags.getItemColor(itemID)) {
|
||||
var aServ = Components.classes["@mozilla.org/atom-service;1"].
|
||||
getService(Components.interfaces.nsIAtomService);
|
||||
prop.AppendElement(aServ.getAtom("color" + color.substr(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Zotero.ItemTreeView.prototype.getColumnProperties = function(col, prop) { }
|
||||
|
@ -2663,27 +2727,3 @@ Zotero.ItemTreeView.TreeRow.prototype.getField = function(field, unformatted)
|
|||
{
|
||||
return this.ref.getField(field, unformatted, true);
|
||||
}
|
||||
|
||||
Zotero.ItemTreeView.TreeRow.prototype.numChildren = function(includeTrashed)
|
||||
{
|
||||
if(this.ref.isRegularItem())
|
||||
return this.ref.numChildren(includeTrashed);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
Zotero.ItemTreeView.TreeRow.prototype.numNotes = function(includeTrashed)
|
||||
{
|
||||
if(this.ref.isRegularItem())
|
||||
return this.ref.numNotes(includeTrashed);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
Zotero.ItemTreeView.TreeRow.prototype.numAttachments = function(includeTrashed)
|
||||
{
|
||||
if(this.ref.isRegularItem())
|
||||
return this.ref.numAttachments(includeTrashed);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -225,6 +225,7 @@ Zotero.Schema = new function(){
|
|||
finally {
|
||||
Zotero.UnresponsiveScriptIndicator.enable();
|
||||
}
|
||||
|
||||
return up1 || up2 || up3 || up4;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
143
chrome/content/zotero/xpcom/storage/eventManager.js
Normal file
143
chrome/content/zotero/xpcom/storage/eventManager.js
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Sync.Storage.EventManager = (function () {
|
||||
var _observers = [];
|
||||
|
||||
function call(handler, data, clear) {
|
||||
Zotero.debug("Calling storage sync " + handler + " handlers");
|
||||
|
||||
var observers = _observers;
|
||||
var cont = true;
|
||||
var handled = false;
|
||||
|
||||
if (clear) {
|
||||
Zotero.Sync.Storage.EventManager.clear();
|
||||
}
|
||||
|
||||
// Process most recently assigned observers first
|
||||
for (var i = observers.length - 1; i >= 0; i--) {
|
||||
let observer = observers[i].observer;
|
||||
let j = i;
|
||||
if (observer[handler]) {
|
||||
handled = true;
|
||||
if (observers[i].async) {
|
||||
setTimeout(function () {
|
||||
Zotero.debug("Calling " + handler + " handler " + j);
|
||||
var cont = observer[handler](data);
|
||||
if (cont === false) {
|
||||
throw new Error("Cannot cancel events from async observer");
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
else {
|
||||
Zotero.debug("Calling " + handler + " handler " + j);
|
||||
var cont = observer[handler](data);
|
||||
// If handler returns explicit false, cancel further events
|
||||
if (cont === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled && data) {
|
||||
var msg = "Unhandled storage sync event: " + data;
|
||||
Zotero.debug(msg, 1);
|
||||
if (handler == 'onError') {
|
||||
throw new Error(msg);
|
||||
}
|
||||
else {
|
||||
Components.utils.reportError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Throw errors to stop execution
|
||||
if (handler == 'onError') {
|
||||
if (!data) {
|
||||
throw new Error("Data not provided for error");
|
||||
}
|
||||
|
||||
if (cont !== false) {
|
||||
throw (data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
registerObserver: function (observer, async, id) {
|
||||
var pos = -1;
|
||||
|
||||
if (id) {
|
||||
for (var i = 0, len = _observers.length; i < len; i++) {
|
||||
var o = _observers[i];
|
||||
if (o.id === id && o.async == async) {
|
||||
pos = o;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pos == -1) {
|
||||
Zotero.debug("Registering storage sync event observer '" + id + "'");
|
||||
_observers.push({
|
||||
observer: observer,
|
||||
async: !!async,
|
||||
id: id
|
||||
});
|
||||
}
|
||||
else {
|
||||
Zotero.debug("Replacing storage sync event observer '" + id + "'");
|
||||
_observers[pos] = {
|
||||
observer: observer,
|
||||
async: !!async,
|
||||
id: id
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
success: function () call('onSuccess', false, true),
|
||||
skip: function (clear) call('onSkip', false, true),
|
||||
stop: function () call('onStop', false, true),
|
||||
error: function (e) call('onError', e, true),
|
||||
|
||||
warning: function (e) call('onWarning', e),
|
||||
changesMade: function () call('onChangesMade'),
|
||||
|
||||
clear: function () {
|
||||
var queues = Zotero.Sync.Storage.QueueManager.getAll();
|
||||
for each(var queue in queues) {
|
||||
if (queue.isRunning()) {
|
||||
Zotero.debug(queue[0].toUpperCase() + queue.substr(1)
|
||||
+ " queue not empty -- not clearing storage sync event observers");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.debug("Clearing storage sync event observers");
|
||||
_observers = [];
|
||||
}
|
||||
};
|
||||
}());
|
198
chrome/content/zotero/xpcom/storage/module.js
Normal file
198
chrome/content/zotero/xpcom/storage/module.js
Normal file
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Module = function (moduleName) {
|
||||
switch (moduleName) {
|
||||
case 'ZFS':
|
||||
this._module = Zotero.Sync.Storage.Module.ZFS;
|
||||
break;
|
||||
|
||||
case 'WebDAV':
|
||||
this._module = Zotero.Sync.Storage.Module.WebDAV;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid storage session module '" + moduleName + "'");
|
||||
}
|
||||
};
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.__defineGetter__('name', function () this._module.name);
|
||||
Zotero.Sync.Storage.Module.prototype.__defineGetter__('includeUserFiles', function () this._module.includeUserFiles);
|
||||
Zotero.Sync.Storage.Module.prototype.__defineGetter__('includeGroupFiles', function () this._module.includeGroupFiles);
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.__defineGetter__('enabled', function () {
|
||||
try {
|
||||
return this._module.enabled;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.__defineGetter__('verified', function () {
|
||||
try {
|
||||
return this._module.verified;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.__defineGetter__('active', function () {
|
||||
try {
|
||||
return this._module.enabled && this._module.initFromPrefs() && this._module.verified;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.__defineGetter__('username', function () {
|
||||
try {
|
||||
return this._module.username;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.__defineGetter__('password', function () {
|
||||
try {
|
||||
return this._module.password;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.__defineSetter__('password', function (val) {
|
||||
try {
|
||||
this._module.password = val;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.init = function () {
|
||||
try {
|
||||
return this._module.init();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.initFromPrefs = function () {
|
||||
try {
|
||||
return this._module.initFromPrefs();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.downloadFile = function (request) {
|
||||
try {
|
||||
this._module.downloadFile(request);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.uploadFile = function (request) {
|
||||
try {
|
||||
this._module.uploadFile(request);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.getLastSyncTime = function (callback) {
|
||||
try {
|
||||
this._module.getLastSyncTime(callback);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.setLastSyncTime = function (callback, useLastSyncTime) {
|
||||
try {
|
||||
this._module.setLastSyncTime(callback, useLastSyncTime);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.checkServer = function (callback) {
|
||||
try {
|
||||
return this._module.checkServer(callback);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.checkServerCallback = function (uri, status, window, skipSuccessMessage) {
|
||||
try {
|
||||
return this._module.checkServerCallback(uri, status, authRequired, window, skipSuccessMessage);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.cacheCredentials = function (callback) {
|
||||
try {
|
||||
return this._module.cacheCredentials(callback);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.purgeDeletedStorageFiles = function (callback) {
|
||||
try {
|
||||
this._module.purgeDeletedStorageFiles(callback);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Module.prototype.purgeOrphanedStorageFiles = function (callback) {
|
||||
try {
|
||||
this._module.purgeOrphanedStorageFiles(callback);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
}
|
258
chrome/content/zotero/xpcom/storage/queue.js
Normal file
258
chrome/content/zotero/xpcom/storage/queue.js
Normal file
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* Queue for storage sync transfer requests
|
||||
*
|
||||
* @param {String} name Queue name (e.g., 'download' or 'upload')
|
||||
*/
|
||||
Zotero.Sync.Storage.Queue = function (name) {
|
||||
Zotero.debug("Initializing " + name + " queue");
|
||||
|
||||
// Public properties
|
||||
this.name = name;
|
||||
this.maxConcurrentRequests = 1;
|
||||
this.activeRequests = 0;
|
||||
this.totalRequests = 0;
|
||||
|
||||
// Private properties
|
||||
this._requests = {};
|
||||
this._highPriority = [];
|
||||
this._running = false;
|
||||
this._stopping = false;
|
||||
this._finishedReqs = 0;
|
||||
this._lastTotalRequests = 0;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('Name', function () {
|
||||
return this.name[0].toUpperCase() + this.name.substr(1);
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('running', function () this._running);
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('stopping', function () this._stopping);
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('unfinishedRequests', function () {
|
||||
return this.totalRequests - this.finishedRequests;
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('finishedRequests', function () {
|
||||
return this._finishedReqs;
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineSetter__('finishedRequests', function (val) {
|
||||
Zotero.debug("Finished requests: " + val);
|
||||
Zotero.debug("Total requests: " + this.totalRequests);
|
||||
|
||||
this._finishedReqs = val;
|
||||
|
||||
if (val == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Last request
|
||||
if (val == this.totalRequests) {
|
||||
Zotero.debug(this.Name + " queue is done");
|
||||
|
||||
// DEBUG info
|
||||
Zotero.debug("Active requests: " + this.activeRequests);
|
||||
|
||||
if (this.activeRequests) {
|
||||
throw new Error(this.Name + " queue can't be done if there are active requests");
|
||||
}
|
||||
|
||||
this._running = false;
|
||||
this._stopping = false;
|
||||
this._requests = {};
|
||||
this._highPriority = [];
|
||||
this._finishedReqs = 0;
|
||||
this._lastTotalRequests = this.totalRequests;
|
||||
this.totalRequests = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._stopping) {
|
||||
return;
|
||||
}
|
||||
this.advance();
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('lastTotalRequests', function () {
|
||||
return this._lastTotalRequests;
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('queuedRequests', function () {
|
||||
return this.unfinishedRequests - this.activeRequests;
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('remaining', function () {
|
||||
var remaining = 0;
|
||||
for each(var request in this._requests) {
|
||||
remaining += request.remaining;
|
||||
}
|
||||
return remaining;
|
||||
});
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('percentage', function () {
|
||||
if (this.totalRequests == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var completedRequests = 0;
|
||||
for each(var request in this._requests) {
|
||||
completedRequests += request.percentage / 100;
|
||||
}
|
||||
return Math.round((completedRequests / this.totalRequests) * 100);
|
||||
});
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.isRunning = function () {
|
||||
return this._running;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.isStopping = function () {
|
||||
return this._stopping;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a request to this queue
|
||||
*
|
||||
* @param {Zotero.Sync.Storage.Request} request
|
||||
* @param {Boolean} highPriority Add or move request to high priority queue
|
||||
*/
|
||||
Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority) {
|
||||
request.queue = this;
|
||||
var name = request.name;
|
||||
Zotero.debug("Queuing " + this.name + " request '" + name + "'");
|
||||
|
||||
if (this._requests[name]) {
|
||||
if (highPriority) {
|
||||
Zotero.debug("Moving " + name + " to high-priority queue");
|
||||
this._requests[name].importCallbacks(request);
|
||||
this._highPriority.push(name);
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.debug("Request '" + name + "' already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
this._requests[name] = request;
|
||||
this.totalRequests++;
|
||||
|
||||
if (highPriority) {
|
||||
this._highPriority.push(name);
|
||||
}
|
||||
|
||||
this.advance();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start another request in this queue if there's an available slot
|
||||
*/
|
||||
Zotero.Sync.Storage.Queue.prototype.advance = function () {
|
||||
this._running = true;
|
||||
|
||||
if (this._stopping) {
|
||||
Zotero.debug(this.Name + " queue is being stopped in "
|
||||
+ "Zotero.Sync.Storage.Queue.advance()", 2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.queuedRequests) {
|
||||
Zotero.debug("No remaining requests in " + this.name + " queue ("
|
||||
+ this.activeRequests + " active, "
|
||||
+ this.finishedRequests + " finished)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeRequests >= this.maxConcurrentRequests) {
|
||||
Zotero.debug(this.Name + " queue is busy ("
|
||||
+ this.activeRequests + "/" + this.maxConcurrentRequests + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the first unprocessed request
|
||||
|
||||
// Try the high-priority queue first
|
||||
var name, request;
|
||||
while (name = this._highPriority.shift()) {
|
||||
request = this._requests[name];
|
||||
if (!request.isRunning() && !request.isFinished()) {
|
||||
request.start();
|
||||
this.advance();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// And then others
|
||||
for each(request in this._requests) {
|
||||
if (!request.isRunning() && !request.isFinished()) {
|
||||
request.start();
|
||||
this.advance();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.updateProgress = function () {
|
||||
Zotero.Sync.Storage.QueueManager.updateProgress();
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.error = function (e) {
|
||||
Zotero.Sync.Storage.EventManager.error(e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops all requests in this queue
|
||||
*/
|
||||
Zotero.Sync.Storage.Queue.prototype.stop = function () {
|
||||
if (!this._running) {
|
||||
Zotero.debug(this.Name + " queue is not running");
|
||||
return;
|
||||
}
|
||||
if (this._stopping) {
|
||||
Zotero.debug("Already stopping " + this.name + " queue");
|
||||
return;
|
||||
}
|
||||
|
||||
// If no requests, finish manually
|
||||
/*if (this.activeRequests == 0) {
|
||||
this._finishedRequests = this._finishedRequests;
|
||||
return;
|
||||
}*/
|
||||
|
||||
this._stopping = true;
|
||||
for each(var request in this._requests) {
|
||||
if (!request.isFinished()) {
|
||||
request.stop();
|
||||
}
|
||||
}
|
||||
}
|
314
chrome/content/zotero/xpcom/storage/queueManager.js
Normal file
314
chrome/content/zotero/xpcom/storage/queueManager.js
Normal file
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
|
||||
Zotero.Sync.Storage.QueueManager = new function () {
|
||||
var _queues = {};
|
||||
var _conflicts = [];
|
||||
var _cancelled = false;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieving a queue, creating a new one if necessary
|
||||
*
|
||||
* @param {String} queueName
|
||||
*/
|
||||
this.get = function (queueName, noInit) {
|
||||
// Initialize the queue if it doesn't exist yet
|
||||
if (!_queues[queueName]) {
|
||||
if (noInit) {
|
||||
return false;
|
||||
}
|
||||
var queue = new Zotero.Sync.Storage.Queue(queueName);
|
||||
switch (queueName) {
|
||||
case 'download':
|
||||
queue.maxConcurrentRequests =
|
||||
Zotero.Prefs.get('sync.storage.maxDownloads')
|
||||
break;
|
||||
|
||||
case 'upload':
|
||||
queue.maxConcurrentRequests =
|
||||
Zotero.Prefs.get('sync.storage.maxUploads')
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid queue '" + queueName + "' in Zotero.Sync.Storage.QueueManager.get()");
|
||||
}
|
||||
_queues[queueName] = queue;
|
||||
}
|
||||
|
||||
return _queues[queueName];
|
||||
}
|
||||
|
||||
|
||||
this.getAll = function () {
|
||||
var queues = [];
|
||||
for each(var queue in _queues) {
|
||||
queues.push(queue);
|
||||
}
|
||||
return queues;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stop all queues
|
||||
*
|
||||
* @param {Boolean} [skipStorageFinish=false] Don't call Zotero.Sync.Storage.finish()
|
||||
* when done (used when we stopped because of
|
||||
* an error)
|
||||
*/
|
||||
this.cancel = function (skipStorageFinish) {
|
||||
Zotero.debug("Stopping all storage queues");
|
||||
_cancelled = true;
|
||||
for each(var queue in _queues) {
|
||||
if (queue.isRunning() && !queue.isStopping()) {
|
||||
queue.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.finish = function () {
|
||||
Zotero.debug("All storage queues are finished");
|
||||
|
||||
if (!_cancelled && _conflicts.length) {
|
||||
var data = _reconcileConflicts();
|
||||
if (data) {
|
||||
_processMergeData(data);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (_cancelled) {
|
||||
Zotero.Sync.Storage.EventManager.stop();
|
||||
}
|
||||
else {
|
||||
Zotero.Sync.Storage.EventManager.success();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
_cancelled = false;
|
||||
_conflicts = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the current progress values and trigger a display update
|
||||
*
|
||||
* Also detects when all queues have finished and ends sync progress
|
||||
*/
|
||||
this.updateProgress = function () {
|
||||
var activeRequests = 0;
|
||||
var allFinished = true;
|
||||
for each(var queue in _queues) {
|
||||
// Finished or never started
|
||||
if (!queue.isRunning() && !queue.isStopping()) {
|
||||
continue;
|
||||
}
|
||||
allFinished = false;
|
||||
activeRequests += queue.activeRequests;
|
||||
}
|
||||
if (activeRequests == 0) {
|
||||
this.updateProgressMeters(0);
|
||||
if (allFinished) {
|
||||
this.finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Percentage
|
||||
var percentageSum = 0;
|
||||
var numQueues = 0;
|
||||
for each(var queue in _queues) {
|
||||
percentageSum += queue.percentage;
|
||||
numQueues++;
|
||||
}
|
||||
var percentage = Math.round(percentageSum / numQueues);
|
||||
//Zotero.debug("Total percentage is " + percentage);
|
||||
|
||||
// Remaining KB
|
||||
var downloadStatus = _queues.download ?
|
||||
_getQueueStatus(_queues.download) : 0;
|
||||
var uploadStatus = _queues.upload ?
|
||||
_getQueueStatus(_queues.upload) : 0;
|
||||
|
||||
this.updateProgressMeters(
|
||||
activeRequests, percentage, downloadStatus, uploadStatus
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cycle through windows, updating progress meters with new values
|
||||
*/
|
||||
this.updateProgressMeters = function (activeRequests, percentage, downloadStatus, uploadStatus) {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var enumerator = wm.getEnumerator("navigator:browser");
|
||||
while (enumerator.hasMoreElements()) {
|
||||
var win = enumerator.getNext();
|
||||
if (!win.ZoteroPane) continue;
|
||||
var doc = win.ZoteroPane.document;
|
||||
|
||||
//
|
||||
// TODO: Move to overlay.js?
|
||||
//
|
||||
var box = doc.getElementById("zotero-tb-sync-progress-box");
|
||||
var meter = doc.getElementById("zotero-tb-sync-progress");
|
||||
|
||||
if (activeRequests == 0) {
|
||||
box.hidden = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
meter.setAttribute("value", percentage);
|
||||
box.hidden = false;
|
||||
|
||||
var tooltip = doc.
|
||||
getElementById("zotero-tb-sync-progress-tooltip-progress");
|
||||
tooltip.setAttribute("value", percentage + "%");
|
||||
|
||||
var tooltip = doc.
|
||||
getElementById("zotero-tb-sync-progress-tooltip-downloads");
|
||||
tooltip.setAttribute("value", downloadStatus);
|
||||
|
||||
var tooltip = doc.
|
||||
getElementById("zotero-tb-sync-progress-tooltip-uploads");
|
||||
tooltip.setAttribute("value", uploadStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.addConflict = function (requestName, localData, remoteData) {
|
||||
Zotero.debug('===========');
|
||||
Zotero.debug(localData);
|
||||
Zotero.debug(remoteData);
|
||||
|
||||
_conflicts.push({
|
||||
name: requestName,
|
||||
localData: localData,
|
||||
remoteData: remoteData
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a status string for a queue
|
||||
*
|
||||
* @param {Zotero.Sync.Storage.Queue} queue
|
||||
* @return {String}
|
||||
*/
|
||||
function _getQueueStatus(queue) {
|
||||
var remaining = queue.remaining;
|
||||
var unfinishedRequests = queue.unfinishedRequests;
|
||||
|
||||
if (!unfinishedRequests) {
|
||||
return Zotero.getString('sync.storage.none')
|
||||
}
|
||||
|
||||
var kbRemaining = Zotero.getString(
|
||||
'sync.storage.kbRemaining',
|
||||
Zotero.Utilities.numberFormat(remaining / 1024, 0)
|
||||
);
|
||||
var totalRequests = queue.totalRequests;
|
||||
var filesRemaining = Zotero.getString(
|
||||
'sync.storage.filesRemaining',
|
||||
[totalRequests - unfinishedRequests, totalRequests]
|
||||
);
|
||||
var status = Zotero.localeJoin([kbRemaining, '(' + filesRemaining + ')']);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
function _reconcileConflicts() {
|
||||
var objectPairs = [];
|
||||
for each(var conflict in _conflicts) {
|
||||
var item = Zotero.Sync.Storage.getItemFromRequestName(conflict.name);
|
||||
var item1 = item.clone(false, false, true);
|
||||
item1.setField('dateModified',
|
||||
Zotero.Date.dateToSQL(new Date(conflict.localData.modTime), true));
|
||||
var item2 = item.clone(false, false, true);
|
||||
item2.setField('dateModified',
|
||||
Zotero.Date.dateToSQL(new Date(conflict.remoteData.modTime), true));
|
||||
objectPairs.push([item1, item2]);
|
||||
}
|
||||
|
||||
var io = {
|
||||
dataIn: {
|
||||
type: 'storagefile',
|
||||
captions: [
|
||||
Zotero.getString('sync.storage.localFile'),
|
||||
Zotero.getString('sync.storage.remoteFile'),
|
||||
Zotero.getString('sync.storage.savedFile')
|
||||
],
|
||||
objects: objectPairs
|
||||
}
|
||||
};
|
||||
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||
lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io);
|
||||
|
||||
if (!io.dataOut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since we're only putting cloned items into the merge window,
|
||||
// we have to manually set the ids
|
||||
for (var i=0; i<_conflicts.length; i++) {
|
||||
io.dataOut[i].id = Zotero.Sync.Storage.getItemFromRequestName(_conflicts[i].name).id;
|
||||
}
|
||||
|
||||
return io.dataOut;
|
||||
}
|
||||
|
||||
|
||||
function _processMergeData(data) {
|
||||
if (!data.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.resyncOnFinish = true;
|
||||
|
||||
for each(var mergeItem in data) {
|
||||
var itemID = mergeItem.id;
|
||||
var dateModified = mergeItem.ref.getField('dateModified');
|
||||
// Local
|
||||
if (dateModified == mergeItem.left.getField('dateModified')) {
|
||||
Zotero.Sync.Storage.setSyncState(
|
||||
itemID, Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD
|
||||
);
|
||||
}
|
||||
// Remote
|
||||
else {
|
||||
Zotero.Sync.Storage.setSyncState(
|
||||
itemID, Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
288
chrome/content/zotero/xpcom/storage/request.js
Normal file
288
chrome/content/zotero/xpcom/storage/request.js
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Transfer request for storage sync
|
||||
*
|
||||
* @param {String} name Identifier for request (e.g., "[libraryID]/[key]")
|
||||
* @param {Function} onStart Callback to run when request starts
|
||||
* @param {Function} onStop Callback to run when request stops
|
||||
*/
|
||||
Zotero.Sync.Storage.Request = function (name, callbacks) {
|
||||
Zotero.debug("Initializing request '" + name + "'");
|
||||
|
||||
this.callbacks = ['onStart', 'onProgress', 'onStop'];
|
||||
|
||||
this.name = name;
|
||||
this.channel = null;
|
||||
this.queue = null;
|
||||
this.progress = 0;
|
||||
this.progressMax = 0;
|
||||
|
||||
this._running = false;
|
||||
this._percentage = 0;
|
||||
this._remaining = null;
|
||||
this._finished = false;
|
||||
|
||||
for (var func in callbacks) {
|
||||
if (this.callbacks.indexOf(func) !== -1) {
|
||||
// Stuff all single functions into arrays
|
||||
this['_' + func] = typeof callbacks[func] === 'function' ? [callbacks[func]] : callbacks[func];
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid handler '" + func + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add callbacks from another request to this request
|
||||
*/
|
||||
Zotero.Sync.Storage.Request.prototype.importCallbacks = function (request) {
|
||||
for each(var name in this.callbacks) {
|
||||
name = '_' + name;
|
||||
if (request[name]) {
|
||||
// If no handlers for this event, add them all
|
||||
if (!this[name]) {
|
||||
this[name] = request[name];
|
||||
continue;
|
||||
}
|
||||
// Otherwise add functions that don't already exist
|
||||
var add = true;
|
||||
for each(var newFunc in request[name]) {
|
||||
for each(var currentFunc in this[name]) {
|
||||
if (newFunc.toString() === currentFunc.toString()) {
|
||||
Zotero.debug("Callback already exists in request -- not importing");
|
||||
add = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (add) {
|
||||
this[name].push(newFunc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Request.prototype.__defineGetter__('percentage', function () {
|
||||
if (this.progressMax == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var percentage = Math.round((this.progress / this.progressMax) * 100);
|
||||
if (percentage < this._percentage) {
|
||||
Zotero.debug(percentage + " is less than last percentage of "
|
||||
+ this._percentage + " for request '" + this.name + "'", 2);
|
||||
Zotero.debug(this.progress);
|
||||
Zotero.debug(this.progressMax);
|
||||
percentage = this._percentage;
|
||||
}
|
||||
else if (percentage > 100) {
|
||||
Zotero.debug(percentage + " is greater than 100 for "
|
||||
+ this.name + " request", 2);
|
||||
Zotero.debug(this.progress);
|
||||
Zotero.debug(this.progressMax);
|
||||
percentage = 100;
|
||||
}
|
||||
else {
|
||||
this._percentage = percentage;
|
||||
}
|
||||
//Zotero.debug("Request '" + this.name + "' percentage is " + percentage);
|
||||
return percentage;
|
||||
});
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Request.prototype.__defineGetter__('remaining', function () {
|
||||
if (!this.progressMax) {
|
||||
//Zotero.debug("Remaining not yet available for request '" + this.name + "'");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var remaining = this.progressMax - this.progress;
|
||||
if (this._remaining === null) {
|
||||
this._remaining = remaining;
|
||||
}
|
||||
else if (remaining > this._remaining) {
|
||||
Zotero.debug(remaining + " is greater than the last remaining amount of "
|
||||
+ this._remaining + " for request " + this.name);
|
||||
remaining = this._remaining;
|
||||
}
|
||||
else if (remaining < 0) {
|
||||
Zotero.debug(remaining + " is less than 0 for request " + this.name);
|
||||
}
|
||||
else {
|
||||
this._remaining = remaining;
|
||||
}
|
||||
//Zotero.debug("Request '" + this.name + "' remaining is " + remaining);
|
||||
return remaining;
|
||||
});
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Request.prototype.setChannel = function (channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Request.prototype.start = function () {
|
||||
if (!this.queue) {
|
||||
throw ("Request '" + this.name + "' must be added to a queue before starting");
|
||||
}
|
||||
|
||||
if (this._running) {
|
||||
throw ("Request '" + this.name + "' already running in "
|
||||
+ "Zotero.Sync.Storage.Request.start()");
|
||||
}
|
||||
|
||||
Zotero.debug("Starting " + this.queue.name + " request '" + this.name + "'");
|
||||
this._running = true;
|
||||
this.queue.activeRequests++;
|
||||
if (this._onStart) {
|
||||
for each(var f in this._onStart) {
|
||||
f(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Request.prototype.isRunning = function () {
|
||||
return this._running;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Request.prototype.isFinished = function () {
|
||||
return this._finished;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update counters for given request
|
||||
*
|
||||
* Also updates progress meter
|
||||
*
|
||||
* @param {Integer} progress Progress so far
|
||||
* (usually bytes transferred)
|
||||
* @param {Integer} progressMax Max progress value for this request
|
||||
* (usually total bytes)
|
||||
*/
|
||||
Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress, progressMax) {
|
||||
if (!this._running) {
|
||||
Zotero.debug("Trying to update finished request " + this.name + " in "
|
||||
+ "Zotero.Sync.Storage.Request.onProgress() "
|
||||
+ "(" + progress + "/" + progressMax + ")", 2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
// Workaround for invalid progress values (possibly related to
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=451991 and fixed in 3.1)
|
||||
if (progress < this.progress) {
|
||||
Zotero.debug("Invalid progress for request '"
|
||||
+ this.name + "' (" + progress + " < " + this.progress + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
if (progressMax != this.progressMax) {
|
||||
Zotero.debug("progressMax has changed from " + this.progressMax
|
||||
+ " to " + progressMax + " for request '" + this.name + "'", 2);
|
||||
}
|
||||
|
||||
this.progress = progress;
|
||||
this.progressMax = progressMax;
|
||||
this.queue.updateProgress();
|
||||
|
||||
if (this.onProgress) {
|
||||
for each(var f in this._onProgress) {
|
||||
f(progress, progressMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Zotero.Sync.Storage.Request.prototype.error = function (e) {
|
||||
this.queue.error(e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stop the request's underlying network request, if there is one
|
||||
*/
|
||||
Zotero.Sync.Storage.Request.prototype.stop = function () {
|
||||
var finishNow = false;
|
||||
try {
|
||||
// If upload already finished, finish() will never be called otherwise
|
||||
if (this.channel) {
|
||||
this.channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
// Throws error if request not finished
|
||||
this.channel.requestSucceeded;
|
||||
Zotero.debug("Channel is no longer running for request " + this.name);
|
||||
Zotero.debug(this.channel.requestSucceeded);
|
||||
finishNow = true;
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
if (!this._running || !this.channel || finishNow) {
|
||||
this.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.debug("Stopping request '" + this.name + "'");
|
||||
this.channel.cancel(0x804b0002); // NS_BINDING_ABORTED
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark request as finished and notify queue that it's done
|
||||
*/
|
||||
Zotero.Sync.Storage.Request.prototype.finish = function () {
|
||||
if (this._finished) {
|
||||
throw ("Request '" + this.name + "' is already finished");
|
||||
}
|
||||
|
||||
Zotero.debug("Finishing " + this.queue.name + " request '" + this.name + "'");
|
||||
this._finished = true;
|
||||
var active = this._running;
|
||||
this._running = false;
|
||||
|
||||
if (active) {
|
||||
this.queue.activeRequests--;
|
||||
}
|
||||
// mechanism for failures?
|
||||
this.queue.finishedRequests++;
|
||||
this.queue.updateProgress();
|
||||
|
||||
if (this._onStop) {
|
||||
for each(var f in this._onStop) {
|
||||
f();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
|
||||
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.init = 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 {
|
||||
return this._session.checkServer(callback);
|
||||
}
|
||||
catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Session.prototype.checkServerCallback = function (uri, status, authRequired, window, skipSuccessMessage, error) {
|
||||
try {
|
||||
return this._session.checkServerCallback(uri, status, authRequired, window, skipSuccessMessage, error);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
235
chrome/content/zotero/xpcom/storage/streamListener.js
Normal file
235
chrome/content/zotero/xpcom/storage/streamListener.js
Normal file
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Stream listener that can handle both download and upload requests
|
||||
*
|
||||
* Possible properties of data object:
|
||||
* - onStart: f(request)
|
||||
* - onProgress: f(request, progress, progressMax)
|
||||
* - onStop: f(request, status, response, data)
|
||||
* - onCancel: f(request, status, data)
|
||||
* - streams: array of streams to close on completion
|
||||
* - Other values to pass to onStop()
|
||||
*/
|
||||
Zotero.Sync.Storage.StreamListener = function (data) {
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.StreamListener.prototype = {
|
||||
_channel: null,
|
||||
|
||||
// nsIProgressEventSink
|
||||
onProgress: function (request, context, progress, progressMax) {
|
||||
// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=451991
|
||||
// (fixed in Fx3.1)
|
||||
if (progress > progressMax) {
|
||||
progress = progressMax;
|
||||
}
|
||||
//Zotero.debug("onProgress with " + progress + "/" + progressMax);
|
||||
this._onProgress(request, progress, progressMax);
|
||||
},
|
||||
|
||||
onStatus: function (request, context, status, statusArg) {
|
||||
//Zotero.debug('onStatus');
|
||||
},
|
||||
|
||||
// nsIRequestObserver
|
||||
// Note: For uploads, this isn't called until data is done uploading
|
||||
onStartRequest: function (request, context) {
|
||||
Zotero.debug('onStartRequest');
|
||||
this._response = "";
|
||||
|
||||
this._onStart(request);
|
||||
},
|
||||
|
||||
onStopRequest: function (request, context, status) {
|
||||
Zotero.debug('onStopRequest');
|
||||
|
||||
switch (status) {
|
||||
case 0:
|
||||
case 0x804b0002: // NS_BINDING_ABORTED
|
||||
this._onStop(request, status);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Unexpected request status " + status
|
||||
+ " in Zotero.Sync.Storage.StreamListener.onStopRequest()");
|
||||
}
|
||||
},
|
||||
|
||||
// nsIWebProgressListener
|
||||
onProgressChange: function (wp, request, curSelfProgress,
|
||||
maxSelfProgress, curTotalProgress, maxTotalProgress) {
|
||||
//Zotero.debug("onProgressChange with " + curTotalProgress + "/" + maxTotalProgress);
|
||||
|
||||
// onProgress gets called too, so this isn't necessary
|
||||
//this._onProgress(request, curTotalProgress, maxTotalProgress);
|
||||
},
|
||||
|
||||
onStateChange: function (wp, request, stateFlags, status) {
|
||||
Zotero.debug("onStateChange");
|
||||
|
||||
if ((stateFlags & Components.interfaces.nsIWebProgressListener.STATE_START)
|
||||
&& (stateFlags & Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK)) {
|
||||
this._onStart(request);
|
||||
}
|
||||
else if ((stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
|
||||
&& (stateFlags & Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK)) {
|
||||
this._onStop(request, status);
|
||||
}
|
||||
},
|
||||
|
||||
onStatusChange: function (progress, request, status, message) {
|
||||
Zotero.debug("onStatusChange with '" + message + "'");
|
||||
},
|
||||
onLocationChange: function () {},
|
||||
onSecurityChange: function () {},
|
||||
|
||||
// nsIStreamListener
|
||||
onDataAvailable: function (request, context, stream, sourceOffset, length) {
|
||||
Zotero.debug('onDataAvailable');
|
||||
var scriptableInputStream =
|
||||
Components.classes["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
||||
scriptableInputStream.init(stream);
|
||||
|
||||
this._response += scriptableInputStream.read(length);
|
||||
},
|
||||
|
||||
// nsIChannelEventSink
|
||||
onChannelRedirect: function (oldChannel, newChannel, flags) {
|
||||
Zotero.debug('onChannelRedirect');
|
||||
|
||||
// if redirecting, store the new channel
|
||||
this._channel = newChannel;
|
||||
},
|
||||
|
||||
asyncOnChannelRedirect: function (oldChan, newChan, flags, redirectCallback) {
|
||||
Zotero.debug('asyncOnRedirect');
|
||||
|
||||
this.onChannelRedirect(oldChan, newChan, flags);
|
||||
redirectCallback.onRedirectVerifyCallback(0);
|
||||
},
|
||||
|
||||
// nsIHttpEventSink
|
||||
onRedirect: function (oldChannel, newChannel) {
|
||||
Zotero.debug('onRedirect');
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
_onStart: function (request) {
|
||||
//Zotero.debug('Starting request');
|
||||
if (this._data && this._data.onStart) {
|
||||
var data = this._getPassData();
|
||||
this._data.onStart(request, data);
|
||||
}
|
||||
},
|
||||
|
||||
_onProgress: function (request, progress, progressMax) {
|
||||
if (this._data && this._data.onProgress) {
|
||||
this._data.onProgress(request, progress, progressMax);
|
||||
}
|
||||
},
|
||||
|
||||
_onStop: function (request, status) {
|
||||
var cancelled = status == 0x804b0002; // NS_BINDING_ABORTED
|
||||
|
||||
if (!cancelled && request instanceof Components.interfaces.nsIHttpChannel) {
|
||||
request.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
status = request.responseStatus;
|
||||
request.QueryInterface(Components.interfaces.nsIRequest);
|
||||
}
|
||||
|
||||
if (this._data.streams) {
|
||||
for each(var stream in this._data.streams) {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
var data = this._getPassData();
|
||||
|
||||
if (cancelled) {
|
||||
if (this._data.onCancel) {
|
||||
this._data.onCancel(request, status, data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this._data.onStop) {
|
||||
this._data.onStop(request, status, this._response, data);
|
||||
}
|
||||
}
|
||||
|
||||
this._channel = null;
|
||||
},
|
||||
|
||||
_getPassData: function () {
|
||||
// Make copy of data without callbacks to pass along
|
||||
var passData = {};
|
||||
for (var i in this._data) {
|
||||
switch (i) {
|
||||
case "onStart":
|
||||
case "onProgress":
|
||||
case "onStop":
|
||||
case "onCancel":
|
||||
continue;
|
||||
}
|
||||
passData[i] = this._data[i];
|
||||
}
|
||||
return passData;
|
||||
},
|
||||
|
||||
// nsIInterfaceRequestor
|
||||
getInterface: function (iid) {
|
||||
try {
|
||||
return this.QueryInterface(iid);
|
||||
}
|
||||
catch (e) {
|
||||
throw Components.results.NS_NOINTERFACE;
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsISupports) ||
|
||||
iid.equals(Components.interfaces.nsIInterfaceRequestor) ||
|
||||
iid.equals(Components.interfaces.nsIChannelEventSink) ||
|
||||
iid.equals(Components.interfaces.nsIProgressEventSink) ||
|
||||
iid.equals(Components.interfaces.nsIHttpEventSink) ||
|
||||
iid.equals(Components.interfaces.nsIStreamListener) ||
|
||||
iid.equals(Components.interfaces.nsIWebProgressListener)) {
|
||||
return this;
|
||||
}
|
||||
throw Components.results.NS_NOINTERFACE;
|
||||
},
|
||||
|
||||
_safeSpec: function (uri) {
|
||||
return uri.scheme + '://' + uri.username + ':********@'
|
||||
+ uri.hostPort + uri.path
|
||||
},
|
||||
};
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -425,7 +425,7 @@ Zotero.Sync.EventListener = new function () {
|
|||
var sql = "REPLACE INTO syncDeleteLog VALUES (?, ?, ?, ?)";
|
||||
var syncStatement = Zotero.DB.getStatement(sql);
|
||||
|
||||
if (isItem && Zotero.Sync.Storage.isActive('webdav')) {
|
||||
if (isItem && Zotero.Sync.Storage.isActive('WebDAV')) {
|
||||
var storageEnabled = true;
|
||||
var sql = "INSERT INTO storageDeleteLog VALUES (?, ?, ?)";
|
||||
var storageStatement = Zotero.DB.getStatement(sql);
|
||||
|
@ -562,74 +562,59 @@ Zotero.Sync.Runner = new function () {
|
|||
|
||||
Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.syncingFiles'));
|
||||
|
||||
Zotero.Sync.Storage.sync(
|
||||
'webdav',
|
||||
|
||||
{
|
||||
// WebDAV success
|
||||
var zfsSync = function (skipSyncNeeded) {
|
||||
Zotero.Sync.Storage.sync('ZFS', {
|
||||
// ZFS success
|
||||
onSuccess: function () {
|
||||
syncNeeded = true;
|
||||
|
||||
Zotero.Sync.Storage.sync(
|
||||
'zfs',
|
||||
|
||||
{
|
||||
// ZFS success
|
||||
onSuccess: function () {
|
||||
Zotero.Sync.Server.sync(finalCallbacks);
|
||||
},
|
||||
|
||||
// ZFS skip
|
||||
onSkip: function () {
|
||||
if (syncNeeded) {
|
||||
Zotero.Sync.Server.sync(finalCallbacks);
|
||||
}
|
||||
},
|
||||
|
||||
// ZFS cancel
|
||||
onStop: Zotero.Sync.Runner.stop,
|
||||
|
||||
// ZFS failure
|
||||
onError: Zotero.Sync.Runner.error,
|
||||
|
||||
onWarning: Zotero.Sync.Runner.warning
|
||||
}
|
||||
)
|
||||
setTimeout(function () {
|
||||
Zotero.Sync.Server.sync(finalCallbacks);
|
||||
}, 0);
|
||||
},
|
||||
|
||||
// WebDAV skip
|
||||
// ZFS skip
|
||||
onSkip: function () {
|
||||
Zotero.Sync.Storage.sync(
|
||||
'zfs',
|
||||
|
||||
{
|
||||
// ZFS success
|
||||
onSuccess: function () {
|
||||
Zotero.Sync.Server.sync(finalCallbacks);
|
||||
},
|
||||
|
||||
// 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
|
||||
setTimeout(function () {
|
||||
if (skipSyncNeeded) {
|
||||
Zotero.Sync.Server.sync(finalCallbacks);
|
||||
}
|
||||
)
|
||||
else {
|
||||
Zotero.Sync.Runner.stop();
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
|
||||
// WebDAV cancel
|
||||
onStop: Zotero.Sync.Runner.stop,
|
||||
// ZFS cancel
|
||||
onStop: function () {
|
||||
setTimeout(function () {
|
||||
Zotero.Sync.Runner.stop();
|
||||
}, 0);
|
||||
},
|
||||
|
||||
// WebDAV failure
|
||||
onError: Zotero.Sync.Runner.error
|
||||
}
|
||||
)
|
||||
}
|
||||
// ZFS failure
|
||||
onError: Zotero.Sync.Runner.error,
|
||||
|
||||
onWarning: Zotero.Sync.Runner.warning
|
||||
})
|
||||
};
|
||||
|
||||
Zotero.Sync.Storage.sync('WebDAV', {
|
||||
// WebDAV success
|
||||
onSuccess: function () {
|
||||
zfsSync(true);
|
||||
},
|
||||
|
||||
// WebDAV skip
|
||||
onSkip: function () {
|
||||
zfsSync();
|
||||
},
|
||||
|
||||
// WebDAV cancel
|
||||
onStop: Zotero.Sync.Runner.stop,
|
||||
|
||||
// WebDAV failure
|
||||
onError: Zotero.Sync.Runner.error
|
||||
});
|
||||
};
|
||||
|
||||
Zotero.Sync.Server.sync({
|
||||
// Sync 1 success
|
||||
|
|
|
@ -1859,12 +1859,12 @@ const ZOTERO_CONFIG = {
|
|||
Zotero.Relations.purge();
|
||||
|
||||
if (!skipStoragePurge && Math.random() < 1/10) {
|
||||
Zotero.Sync.Storage.purgeDeletedStorageFiles('zfs');
|
||||
Zotero.Sync.Storage.purgeDeletedStorageFiles('webdav');
|
||||
Zotero.Sync.Storage.purgeDeletedStorageFiles('ZFS');
|
||||
Zotero.Sync.Storage.purgeDeletedStorageFiles('WebDAV');
|
||||
}
|
||||
|
||||
if (!skipStoragePurge) {
|
||||
Zotero.Sync.Storage.purgeOrphanedStorageFiles('webdav');
|
||||
Zotero.Sync.Storage.purgeOrphanedStorageFiles('WebDAV');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1911,6 +1911,32 @@ Zotero.Prefs = new function(){
|
|||
|
||||
// Register observer to handle pref changes
|
||||
this.register();
|
||||
|
||||
// Process pref version updates
|
||||
var fromVersion = this.get('prefVersion');
|
||||
if (!fromVersion) {
|
||||
fromVersion = 0;
|
||||
}
|
||||
var toVersion = 1;
|
||||
if (fromVersion < toVersion) {
|
||||
for (var i = fromVersion + 1; i <= toVersion; i++) {
|
||||
switch (i) {
|
||||
case 1:
|
||||
// If a sync username is entered and ZFS is enabled, turn
|
||||
// on-demand downloading off to maintain current behavior
|
||||
if (this.get('sync.server.username')) {
|
||||
if (this.get('sync.storage.enabled')
|
||||
&& this.get('sync.storage.protocol') == 'zotero') {
|
||||
this.set('sync.storage.downloadMode.personal', 'on-sync');
|
||||
}
|
||||
if (this.get('sync.storage.groups.enabled')) {
|
||||
this.set('sync.storage.downloadMode.groups', 'on-sync');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.set('prefVersion', toVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2590,14 +2590,8 @@ var ZoteroPane = new function()
|
|||
createInstance(Components.interfaces.nsIURI);
|
||||
var snapID = item.getBestAttachment();
|
||||
if (snapID) {
|
||||
spec = Zotero.Items.get(snapID).getLocalFileURL();
|
||||
if (spec) {
|
||||
uri.spec = spec;
|
||||
if (uri.scheme && uri.scheme == 'file') {
|
||||
ZoteroPane_Local.viewAttachment(snapID, event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ZoteroPane_Local.viewAttachment(snapID, event);
|
||||
return;
|
||||
}
|
||||
|
||||
var uri = item.getField('url');
|
||||
|
@ -3352,22 +3346,22 @@ var ZoteroPane = new function()
|
|||
}
|
||||
|
||||
for each(var itemID in itemIDs) {
|
||||
var attachment = Zotero.Items.get(itemID);
|
||||
if (!attachment.isAttachment()) {
|
||||
var item = Zotero.Items.get(itemID);
|
||||
if (!item.isAttachment()) {
|
||||
throw ("Item " + itemID + " is not an attachment in ZoteroPane_Local.viewAttachment()");
|
||||
}
|
||||
|
||||
if (attachment.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
this.loadURI(attachment.getField('url'), event);
|
||||
if (item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
this.loadURI(item.getField('url'), event);
|
||||
continue;
|
||||
}
|
||||
|
||||
var file = attachment.getFile();
|
||||
var file = item.getFile();
|
||||
if (file) {
|
||||
if(forceExternalViewer !== undefined) {
|
||||
var externalViewer = forceExternalViewer;
|
||||
} else {
|
||||
var mimeType = attachment.attachmentMIMEType;
|
||||
var mimeType = item.attachmentMIMEType;
|
||||
// If no MIME type specified, try to detect again (I guess in case
|
||||
// we've gotten smarter since the file was imported?)
|
||||
if (!mimeType) {
|
||||
|
@ -3393,12 +3387,43 @@ var ZoteroPane = new function()
|
|||
}
|
||||
catch (e) {
|
||||
Zotero.debug("launch() not supported -- passing file to loadURI()");
|
||||
var fileURL = attachment.getLocalFileURL();
|
||||
var fileURL = item.getLocalFileURL();
|
||||
this.loadURI(fileURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (item.isImportedAttachment() && Zotero.Sync.Storage.downloadAsNeeded(item.libraryID)) {
|
||||
let downloadedItem = item;
|
||||
var started = Zotero.Sync.Storage.downloadFile(item, {
|
||||
onStart: function (request) {
|
||||
if (!(request instanceof Zotero.Sync.Storage.Request)) {
|
||||
throw new Error("Invalid request object");
|
||||
}
|
||||
},
|
||||
|
||||
onProgress: function (progress, progressMax) {
|
||||
|
||||
},
|
||||
|
||||
onStop: function () {
|
||||
if (!downloadedItem.getFile()) {
|
||||
ZoteroPane_Local.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if unchanged?
|
||||
// maybe not necessary, since we'll get an error if there's an error
|
||||
|
||||
ZoteroPane_Local.viewAttachment(downloadedItem.id, event, false, forceExternalViewer);
|
||||
},
|
||||
});
|
||||
|
||||
if (started) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
this.showAttachmentNotFoundDialog(itemID, noLocateOnMissing);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script src="include.js"/>
|
||||
<script src="zoteroPane.js"/>
|
||||
<script src="zoteroPane.js" type="application/javascript;version=1.8"/>
|
||||
<script src="fileInterface.js"/>
|
||||
<script src="reportInterface.js"/>
|
||||
<script src="timelineInterface.js"/>
|
||||
|
@ -413,8 +413,17 @@
|
|||
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol
|
||||
id="zotero-items-column-numChildren"
|
||||
label="&zotero.items.numChildren_column;"
|
||||
id="zotero-items-column-hasAttachment"
|
||||
class="treecol-image"
|
||||
label="&zotero.tabs.attachments.label;"
|
||||
src="chrome://zotero/skin/attach-small.png"
|
||||
zotero-persist="width ordinal hidden sortActive sortDirection"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol
|
||||
id="zotero-items-column-hasNote"
|
||||
class="treecol-image"
|
||||
label="&zotero.tabs.notes.label;"
|
||||
src="chrome://zotero/skin/treeitem-note-small.png"
|
||||
zotero-persist="width ordinal hidden sortActive sortDirection"/>
|
||||
</treecols>
|
||||
<treechildren/>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<!ENTITY zotero.preferences.default "Default:">
|
||||
<!ENTITY zotero.preferences.items "items">
|
||||
<!ENTITY zotero.preferences.period ".">
|
||||
<!ENTITY zotero.preferences.settings "Settings">
|
||||
|
||||
<!ENTITY zotero.preferences.prefpane.general "General">
|
||||
|
||||
|
@ -58,7 +59,9 @@
|
|||
<!ENTITY zotero.preferences.sync.fileSyncing.url "URL:">
|
||||
<!ENTITY zotero.preferences.sync.fileSyncing.myLibrary "Sync attachment files in My Library using">
|
||||
<!ENTITY zotero.preferences.sync.fileSyncing.groups "Sync attachment files in group libraries using Zotero storage">
|
||||
<!ENTITY zotero.preferences.sync.fileSyncing.about "About File Syncing">
|
||||
<!ENTITY zotero.preferences.sync.fileSyncing.download "Download files">
|
||||
<!ENTITY zotero.preferences.sync.fileSyncing.download.atSyncTime "at sync time">
|
||||
<!ENTITY zotero.preferences.sync.fileSyncing.download.onDemand "as needed">
|
||||
<!ENTITY zotero.preferences.sync.fileSyncing.tos1 "By using Zotero storage, you agree to become bound by its">
|
||||
<!ENTITY zotero.preferences.sync.fileSyncing.tos2 "terms and conditions">
|
||||
<!ENTITY zotero.preferences.sync.reset.fullSync "Full Sync with Zotero Server">
|
||||
|
@ -69,6 +72,7 @@
|
|||
<!ENTITY zotero.preferences.sync.reset.restoreToServer.desc "Erase all server data and overwrite with local Zotero data.">
|
||||
<!ENTITY zotero.preferences.sync.reset.resetFileSyncHistory "Reset File Sync History">
|
||||
<!ENTITY zotero.preferences.sync.reset.resetFileSyncHistory.desc "Force checking of the storage server for all local attachment files.">
|
||||
<!ENTITY zotero.preferences.sync.reset "Reset">
|
||||
<!ENTITY zotero.preferences.sync.reset.button "Reset...">
|
||||
|
||||
|
||||
|
|
BIN
chrome/skin/default/zotero/attach-small.png
Normal file
BIN
chrome/skin/default/zotero/attach-small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
chrome/skin/default/zotero/bullet_blue.png
Executable file
BIN
chrome/skin/default/zotero/bullet_blue.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 289 B |
BIN
chrome/skin/default/zotero/bullet_blue_empty.png
Normal file
BIN
chrome/skin/default/zotero/bullet_blue_empty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 295 B |
BIN
chrome/skin/default/zotero/bullet_yellow.png
Executable file
BIN
chrome/skin/default/zotero/bullet_yellow.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 287 B |
|
@ -66,6 +66,11 @@
|
|||
background-image: none;
|
||||
}
|
||||
|
||||
#zotero-items-column-hasAttachment, #zotero-items-column-hasNote
|
||||
{
|
||||
min-width: 21px;
|
||||
}
|
||||
|
||||
#zotero-items-tree treechildren::-moz-tree-image
|
||||
{
|
||||
margin-right: 5px;
|
||||
|
|
|
@ -22,7 +22,7 @@ radio[pane]
|
|||
}
|
||||
|
||||
/* Links within messages */
|
||||
description label[class=text-link], label label[class=text-link]
|
||||
description label[class=zotero-text-link], label label[class=zotero-text-link]
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -114,10 +114,9 @@ grid row hbox:first-child
|
|||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#storage-settings
|
||||
.storage-settings-download-options
|
||||
{
|
||||
margin-left: 10px;
|
||||
margin-right: 5px;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
#storage-verify, #storage-abort, #storage-clean
|
||||
|
@ -137,7 +136,7 @@ grid row hbox:first-child
|
|||
margin-right: .25em;
|
||||
}
|
||||
|
||||
#storage-terms > label[class=text-link]
|
||||
#storage-terms > label[class=zotero-text-link]
|
||||
{
|
||||
margin-right: 0;
|
||||
}
|
||||
|
|
BIN
chrome/skin/default/zotero/treeitem-note-small.png
Normal file
BIN
chrome/skin/default/zotero/treeitem-note-small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 376 B |
|
@ -96,7 +96,12 @@ const xpcomFilesLocal = [
|
|||
'style',
|
||||
'sync',
|
||||
'storage',
|
||||
'storage/session',
|
||||
'storage/streamListener',
|
||||
'storage/eventManager',
|
||||
'storage/queueManager',
|
||||
'storage/queue',
|
||||
'storage/request',
|
||||
'storage/module',
|
||||
'storage/zfs',
|
||||
'storage/webdav',
|
||||
'timeline',
|
||||
|
|
|
@ -140,6 +140,8 @@ 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);
|
||||
pref("extensions.zotero.sync.storage.downloadMode.personal", "on-demand");
|
||||
pref("extensions.zotero.sync.storage.downloadMode.groups", "on-demand");
|
||||
|
||||
// Proxy
|
||||
pref("extensions.zotero.proxies.autoRecognize", true);
|
||||
|
|
Loading…
Reference in a new issue