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:
Dan Stillman 2011-11-14 03:42:06 -05:00
parent 80530b9599
commit 758216638f
33 changed files with 4829 additions and 4170 deletions

View file

@ -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;

View file

@ -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;"/>

View file

@ -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>

View file

@ -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");
}
}

View file

@ -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, '&nbsp;&nbsp;&nbsp;&nbsp;')
.replace(/ /g, '&nbsp;&nbsp;')
+ '</p></div>';
+ '</p>' + Zotero.Notes.noteSuffix;
note = note.replace(/<p>\s*<\/p>/g, '<p>&nbsp;</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()) {

View file

@ -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

View file

@ -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;
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);
}
} else {
getField = function(row) row.getField(columnField, unformatted);
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;
}

View file

@ -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

View 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 = [];
}
};
}());

View 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);
}
}

View 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();
}
}
}

View 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
);
}
}
}
}

View 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();
}
}
}

View file

@ -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);
}
}

View 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

View file

@ -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

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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/>

View file

@ -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...">

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

View file

@ -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;

View file

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

View file

@ -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',

View file

@ -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);