- Saved search syncing, with automatic latest-wins conflict resolution
- Last sync time displayed in sync button tooltip - Various and sundry bug fixes DB must be re-upgraded from 1.0
This commit is contained in:
parent
cd93bf3927
commit
77133f465c
11 changed files with 611 additions and 230 deletions
|
@ -145,9 +145,8 @@ var Zotero_File_Interface = new function() {
|
|||
// find name
|
||||
var searchRef = ZoteroPane.getSelectedSavedSearch();
|
||||
if(searchRef) {
|
||||
var search = new Zotero.Search();
|
||||
search.load(searchRef['id']);
|
||||
exporter.name = search.getName();
|
||||
var search = new Zotero.Search(searchRef.id);
|
||||
exporter.name = search.name;
|
||||
}
|
||||
}
|
||||
exporter.save();
|
||||
|
@ -285,9 +284,8 @@ var Zotero_File_Interface = new function() {
|
|||
} else {
|
||||
var searchRef = ZoteroPane.getSelectedSavedSearch();
|
||||
if(searchRef) {
|
||||
var search = new Zotero.Search();
|
||||
search.load(searchRef['id']);
|
||||
name = search.getName();
|
||||
var search = new Zotero.Search(searchRef.id);
|
||||
name = search.name;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1133,8 +1133,7 @@ var ZoteroPane = new function()
|
|||
}
|
||||
}
|
||||
else {
|
||||
var s = new Zotero.Search();
|
||||
s.load(row.ref.id);
|
||||
var s = new Zotero.Search(row.ref.id);
|
||||
var io = {dataIn: {search: s, name: row.getName()}, dataOut: null};
|
||||
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
|
||||
if (io.dataOut) {
|
||||
|
|
|
@ -302,7 +302,18 @@
|
|||
|
||||
<vbox id="zotero-item-pane" persist="width">
|
||||
<toolbar align="right">
|
||||
<toolbarbutton tooltiptext="Sync with Zotero Server" image="chrome://zotero/skin/arrow_refresh.png" oncommand="Zotero.Sync.Server.sync()"/>
|
||||
<toolbarbutton
|
||||
id="zotero-tb-sync"
|
||||
image="chrome://zotero/skin/arrow_refresh.png"
|
||||
tooltip="_child"
|
||||
oncommand="Zotero.Sync.Server.sync()">
|
||||
<tooltip
|
||||
onpopupshowing="this.firstChild.nextSibling.value = 'Last sync: ' + (Zotero.Sync.Server.lastLocalSyncTime ? new Date(Zotero.Sync.Server.lastLocalSyncTime * 1000).toLocaleString() : 'Not yet synced')"
|
||||
noautohide="true"><!-- localize -->
|
||||
<label value="Sync with Zotero Server"/>
|
||||
<label id="zotero-last-sync-time"/>
|
||||
</tooltip>
|
||||
</toolbarbutton>
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="zotero-tb-fullscreen" tooltiptext="&zotero.toolbar.fullscreen.tooltip;" oncommand="ZoteroPane.fullScreen();"/>
|
||||
<toolbarbutton class="tabs-closebutton" oncommand="ZoteroPane.toggleDisplay()"/>
|
||||
|
|
|
@ -244,9 +244,8 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
|
|||
break;
|
||||
|
||||
case 'search':
|
||||
var search = Zotero.Searches.get(ids);
|
||||
this.reload();
|
||||
this.selection.select(this._searchRowMap[search.id]);
|
||||
this.selection.select(this._searchRowMap[ids]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -930,7 +929,7 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
|
|||
var includeScopeChildren = false;
|
||||
|
||||
// Create/load the inner search
|
||||
var s = new Zotero.Search();
|
||||
var s = new Zotero.Search(this.isSearch() ? this.ref.id : null);
|
||||
if (this.isLibrary()) {
|
||||
s.addCondition('noChildren', 'true');
|
||||
includeScopeChildren = true;
|
||||
|
@ -943,10 +942,7 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
|
|||
}
|
||||
includeScopeChildren = true;
|
||||
}
|
||||
else if (this.isSearch()){
|
||||
s.load(this.ref['id']);
|
||||
}
|
||||
else {
|
||||
else if (!this.isSearch()) {
|
||||
throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');
|
||||
}
|
||||
|
||||
|
|
|
@ -26,13 +26,17 @@ Zotero.Collection = function(collectionID) {
|
|||
this._init();
|
||||
}
|
||||
|
||||
Zotero.Collection.prototype._init = function (collectionID) {
|
||||
Zotero.Collection.prototype._init = function () {
|
||||
// Public members for access by public methods -- do not access directly
|
||||
this._name = null;
|
||||
this._parent = null;
|
||||
this._dateModified = null;
|
||||
this._key = null;
|
||||
|
||||
this._loaded = false;
|
||||
this._changed = false;
|
||||
this._previousData = false;
|
||||
|
||||
this._hasChildCollections = false;
|
||||
this._childCollections = [];
|
||||
this._childCollectionsLoaded = false;
|
||||
|
@ -40,8 +44,6 @@ Zotero.Collection.prototype._init = function (collectionID) {
|
|||
this._hasChildItems = false;
|
||||
this._childItems = [];
|
||||
this._childItemsLoaded = false;
|
||||
|
||||
this._previousData = false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -122,7 +124,6 @@ Zotero.Collection.prototype.load = function() {
|
|||
+ "(SELECT COUNT(*) FROM collectionItems WHERE "
|
||||
+ "collectionID=C.collectionID)!=0 AS hasChildItems "
|
||||
+ "FROM collections C WHERE collectionID=?";
|
||||
|
||||
var data = Zotero.DB.rowQuery(sql, this.id);
|
||||
|
||||
this._init();
|
||||
|
|
|
@ -32,6 +32,11 @@ Zotero.ID = new function () {
|
|||
* Gets an unused primary key id for a DB table
|
||||
*/
|
||||
function get(table, notNull, skip) {
|
||||
// Used in sync.js
|
||||
if (table == 'searches') {
|
||||
table = 'savedSearches';
|
||||
}
|
||||
|
||||
switch (table) {
|
||||
// Autoincrement tables
|
||||
//
|
||||
|
|
|
@ -20,62 +20,149 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Search = function(savedSearchID){
|
||||
Zotero.Search = function(searchID) {
|
||||
this._id = searchID ? searchID : null;
|
||||
this._init();
|
||||
}
|
||||
|
||||
|
||||
Zotero.Search.prototype._init = function () {
|
||||
// Public members for access by public methods -- do not access directly
|
||||
this._name = null;
|
||||
this._dateModified = null;
|
||||
this._key = null;
|
||||
|
||||
this._loaded = false;
|
||||
this._changed = false;
|
||||
this._previousData = false;
|
||||
|
||||
this._scope = null;
|
||||
this._scopeIncludeChildren = null;
|
||||
this._sql = null;
|
||||
this._sqlParams = null;
|
||||
this._maxSearchConditionID = 0;
|
||||
this._conditions = [];
|
||||
this._savedSearchID = null;
|
||||
this._savedSearchName = null;
|
||||
this._hasPrimaryConditions = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Zotero.Search.prototype.getID = function(){
|
||||
Zotero.debug('Zotero.Search.getName() is deprecated -- use Search.id');
|
||||
return this._id;
|
||||
}
|
||||
|
||||
Zotero.Search.prototype.getName = function() {
|
||||
Zotero.debug('Zotero.Search.getName() is deprecated -- use Search.name');
|
||||
return this.name;
|
||||
}
|
||||
|
||||
Zotero.Search.prototype.setName = function(val) {
|
||||
Zotero.debug('Zotero.Search.setName() is deprecated -- use Search.name');
|
||||
this.name = val;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Search.prototype.__defineGetter__('id', function () { return this._id; });
|
||||
|
||||
Zotero.Search.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
|
||||
Zotero.Search.prototype.__defineSetter__('searchID', function (val) { this._set('id', val); });
|
||||
Zotero.Search.prototype.__defineGetter__('name', function () { return this._get('name'); });
|
||||
Zotero.Search.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
|
||||
Zotero.Search.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
|
||||
Zotero.Search.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
|
||||
Zotero.Search.prototype.__defineGetter__('key', function () { return this._get('key'); });
|
||||
Zotero.Search.prototype.__defineSetter__('key', function (val) { this._set('key', val); });
|
||||
|
||||
Zotero.Search.prototype.__defineGetter__('conditions', function (arr) { this.getSearchConditions(); });
|
||||
|
||||
|
||||
Zotero.Search.prototype._get = function (field) {
|
||||
if (this.id && !this._loaded) {
|
||||
this.load();
|
||||
}
|
||||
return this['_' + field];
|
||||
}
|
||||
|
||||
|
||||
Zotero.Search.prototype._set = function (field, val) {
|
||||
switch (field) {
|
||||
//case 'id': // set using constructor
|
||||
case 'searchID':
|
||||
throw ("Invalid field '" + field + "' in Zotero.Search.set()");
|
||||
}
|
||||
|
||||
if (savedSearchID) {
|
||||
this.load(savedSearchID);
|
||||
if (this.id) {
|
||||
if (!this._loaded) {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._loaded = true;
|
||||
}
|
||||
|
||||
if (this['_' + field] != val) {
|
||||
this._prepFieldChange(field);
|
||||
|
||||
switch (field) {
|
||||
default:
|
||||
this['_' + field] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set the name for the saved search
|
||||
/**
|
||||
* Check if saved search exists in the database
|
||||
*
|
||||
* Must be called before save() for new searches
|
||||
* @return bool TRUE if the search exists, FALSE if not
|
||||
*/
|
||||
Zotero.Search.prototype.setName = function(name){
|
||||
if (!name){
|
||||
throw("Invalid saved search name '" + name + '"');
|
||||
Zotero.Search.prototype.exists = function() {
|
||||
if (!this.id) {
|
||||
throw ('searchID not set in Zotero.Search.exists()');
|
||||
}
|
||||
|
||||
this._savedSearchName = name;
|
||||
var sql = "SELECT COUNT(*) FROM savedSearches WHERE savedSearchID=?";
|
||||
return !!Zotero.DB.valueQuery(sql, this.id);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Load a saved search from the DB
|
||||
*/
|
||||
Zotero.Search.prototype.load = function(savedSearchID){
|
||||
var sql = "SELECT savedSearchName, MAX(searchConditionID) AS maxID "
|
||||
+ "FROM savedSearches LEFT JOIN savedSearchConditions "
|
||||
+ "USING (savedSearchID) WHERE savedSearchID=" + savedSearchID
|
||||
+ " GROUP BY savedSearchID";
|
||||
var row = Zotero.DB.rowQuery(sql);
|
||||
|
||||
if (!row){
|
||||
throw('Saved search ' + savedSearchID + ' does not exist');
|
||||
Zotero.Search.prototype.load = function() {
|
||||
// Changed in 1.5
|
||||
if (arguments[0]) {
|
||||
throw ('Parameter no longer allowed in Zotero.Search.load()');
|
||||
}
|
||||
|
||||
this._sql = null;
|
||||
this._sqlParams = null;
|
||||
this._maxSearchConditionID = row['maxID'];
|
||||
this._conditions = [];
|
||||
this._savedSearchID = savedSearchID;
|
||||
this._savedSearchName = row['savedSearchName'];
|
||||
var sql = "SELECT S.*, "
|
||||
+ "MAX(searchConditionID) AS maxID "
|
||||
+ "FROM savedSearches S LEFT JOIN savedSearchConditions "
|
||||
+ "USING (savedSearchID) WHERE savedSearchID=? "
|
||||
+ "GROUP BY savedSearchID";
|
||||
var data = Zotero.DB.rowQuery(sql, this.id);
|
||||
|
||||
var conditions = Zotero.DB.query("SELECT * FROM savedSearchConditions "
|
||||
+ "WHERE savedSearchID=" + savedSearchID + " ORDER BY searchConditionID");
|
||||
this._init();
|
||||
this._loaded = true;
|
||||
|
||||
for (var i in conditions){
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._changed = false;
|
||||
this._previousData = false;
|
||||
this._id = data.savedSearchID;
|
||||
this._name = data.savedSearchName;
|
||||
this._dateModified = data.dateModified;
|
||||
this._key = data.key;
|
||||
this._maxSearchConditionID = data.maxID;
|
||||
|
||||
var sql = "SELECT * FROM savedSearchConditions "
|
||||
+ "WHERE savedSearchID=? ORDER BY searchConditionID";
|
||||
var conditions = Zotero.DB.query(sql, this.id);
|
||||
|
||||
for (var i in conditions) {
|
||||
// Parse "condition[/mode]"
|
||||
var [condition, mode] =
|
||||
Zotero.SearchConditions.parseCondition(conditions[i]['condition']);
|
||||
|
@ -98,21 +185,6 @@ Zotero.Search.prototype.load = function(savedSearchID){
|
|||
}
|
||||
|
||||
|
||||
Zotero.Search.prototype.getID = function(){
|
||||
Zotero.debug('Zotero.Search.getName() is deprecated -- use Search.id');
|
||||
return this._savedSearchID;
|
||||
}
|
||||
|
||||
Zotero.Search.prototype.__defineGetter__('id', function () { return this._savedSearchID; });
|
||||
|
||||
|
||||
Zotero.Search.prototype.getName = function() {
|
||||
Zotero.debug('Zotero.Search.getName() is deprecated -- use Search.name');
|
||||
return this._savedSearchName;
|
||||
}
|
||||
|
||||
Zotero.Search.prototype.__defineGetter__('name', function () { return this._savedSearchName; });
|
||||
|
||||
/*
|
||||
* Save the search to the DB and return a savedSearchID
|
||||
*
|
||||
|
@ -120,75 +192,134 @@ Zotero.Search.prototype.__defineGetter__('name', function () { return this._save
|
|||
* and the caller must dispose of the search or reload the condition ids,
|
||||
* which may change after the save.
|
||||
*
|
||||
* For new searches, setName() must be called before saving
|
||||
* For new searches, name must be set called before saving
|
||||
*/
|
||||
Zotero.Search.prototype.save = function(fixGaps) {
|
||||
if (!this._savedSearchName){
|
||||
if (!this.name) {
|
||||
throw('Name not provided for saved search');
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
if (this._savedSearchID){
|
||||
var sql = "UPDATE savedSearches SET savedSearchName=? WHERE savedSearchID=?";
|
||||
Zotero.DB.query(sql, [this._savedSearchName, this._savedSearchID]);
|
||||
// ID change
|
||||
if (this._changed.id) {
|
||||
var oldID = this._previousData.primary.id;
|
||||
var params = [this.id, oldID];
|
||||
|
||||
Zotero.DB.query("DELETE FROM savedSearchConditions "
|
||||
+ "WHERE savedSearchID=" + this._savedSearchID);
|
||||
Zotero.debug("Changing search id " + oldID + " to " + this.id);
|
||||
|
||||
var row = Zotero.DB.rowQuery("SELECT * FROM savedSearches WHERE savedSearchID=?", oldID);
|
||||
// Add a new row so we can update the old rows despite FK checks
|
||||
// Use temp key due to UNIQUE constraint on key column
|
||||
Zotero.DB.query("INSERT INTO savedSearches VALUES (?, ?, ?, ?)",
|
||||
[this.id, row.savedSearchName, row.dateModified, 'TEMPKEY']);
|
||||
|
||||
Zotero.DB.query("UPDATE savedSearchConditions SET savedSearchID=? WHERE savedSearchID=?", params);
|
||||
|
||||
Zotero.DB.query("DELETE FROM savedSearches WHERE savedSearchID=?", oldID);
|
||||
Zotero.DB.query("UPDATE savedSearches SET key=? WHERE savedSearchID=?", [row.key, this.id]);
|
||||
|
||||
//Zotero.Searches.unload(oldID);
|
||||
Zotero.Notifier.trigger('id-change', 'search', oldID + '-' + this.id);
|
||||
|
||||
// update caches
|
||||
}
|
||||
|
||||
var isNew = !this.id || !this.exists();
|
||||
|
||||
try {
|
||||
var searchID = this.id ? this.id : Zotero.ID.get('savedSearches');
|
||||
|
||||
Zotero.debug("Saving " + (isNew ? 'new ' : '') + "search " + this.id);
|
||||
|
||||
var key = this.key ? this.key : this._generateKey();
|
||||
|
||||
var columns = [
|
||||
'savedSearchID', 'savedSearchName', 'dateModified', 'key'
|
||||
];
|
||||
var placeholders = ['?', '?', '?', '?'];
|
||||
var sqlValues = [
|
||||
searchID ? { int: searchID } : null,
|
||||
{ string: this.name },
|
||||
// If date modified hasn't changed, use current timestamp
|
||||
this._changed.dateModified ?
|
||||
this.dateModified : Zotero.DB.transactionDateTime,
|
||||
key
|
||||
];
|
||||
|
||||
var sql = "REPLACE INTO savedSearches (" + columns.join(', ') + ") VALUES ("
|
||||
+ placeholders.join(', ') + ")";
|
||||
var insertID = Zotero.DB.query(sql, sqlValues);
|
||||
if (!searchID) {
|
||||
searchID = insertID;
|
||||
}
|
||||
|
||||
if (!isNew) {
|
||||
var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
|
||||
Zotero.DB.query(sql, this.id);
|
||||
}
|
||||
|
||||
// Close gaps in savedSearchIDs
|
||||
var saveConditions = {};
|
||||
var i = 1;
|
||||
for (var id in this._conditions) {
|
||||
if (!fixGaps && id != i) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
throw ('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' + this._id);
|
||||
}
|
||||
saveConditions[i] = this._conditions[id];
|
||||
i++;
|
||||
}
|
||||
|
||||
this._conditions = saveConditions;
|
||||
|
||||
// TODO: use proper bound parameters once DB class is updated
|
||||
for (var i in this._conditions){
|
||||
var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
|
||||
+ "searchConditionID, condition, operator, value, required) "
|
||||
+ "VALUES (?,?,?,?,?,?)";
|
||||
|
||||
// Convert condition and mode to "condition[/mode]"
|
||||
var condition = this._conditions[i].mode ?
|
||||
this._conditions[i].condition + '/' + this._conditions[i].mode :
|
||||
this._conditions[i].condition
|
||||
|
||||
var sqlParams = [
|
||||
searchID, i, condition,
|
||||
this._conditions[i].operator
|
||||
? this._conditions[i].operator : null,
|
||||
this._conditions[i].value
|
||||
? this._conditions[i].value : null,
|
||||
this._conditions[i].required
|
||||
? 1 : null
|
||||
];
|
||||
Zotero.DB.query(sql, sqlParams);
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
throw (e);
|
||||
}
|
||||
|
||||
// If successful, set values in object
|
||||
if (!this.id) {
|
||||
this._id = searchID;
|
||||
}
|
||||
|
||||
if (!this.key) {
|
||||
this._key = key;
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
Zotero.Notifier.trigger('add', 'search', this.id);
|
||||
}
|
||||
else {
|
||||
var isNew = true;
|
||||
|
||||
this._savedSearchID = Zotero.ID.get('savedSearches');
|
||||
|
||||
var sql = "INSERT INTO savedSearches (savedSearchID, savedSearchName) "
|
||||
+ "VALUES (?,?)";
|
||||
Zotero.DB.query(sql,
|
||||
[this._savedSearchID, {string: this._savedSearchName}]);
|
||||
Zotero.Notifier.trigger('modify', 'search', this.id, this._previousData);
|
||||
}
|
||||
|
||||
// Close gaps in savedSearchIDs
|
||||
var saveConditions = {};
|
||||
var i = 1;
|
||||
for (var id in this._conditions) {
|
||||
if (!fixGaps && id != i) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
throw ('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' + this._savedSearchID);
|
||||
}
|
||||
saveConditions[i] = this._conditions[id];
|
||||
i++;
|
||||
}
|
||||
|
||||
this._conditions = saveConditions;
|
||||
|
||||
// TODO: use proper bound parameters once DB class is updated
|
||||
for (var i in this._conditions){
|
||||
var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
|
||||
+ "searchConditionID, condition, operator, value, required) "
|
||||
+ "VALUES (?,?,?,?,?,?)";
|
||||
|
||||
// Convert condition and mode to "condition[/mode]"
|
||||
var condition = this._conditions[i]['mode'] ?
|
||||
this._conditions[i]['condition'] + '/' + this._conditions[i]['mode'] :
|
||||
this._conditions[i]['condition']
|
||||
|
||||
var sqlParams = [
|
||||
this._savedSearchID, i, condition,
|
||||
this._conditions[i]['operator']
|
||||
? this._conditions[i]['operator'] : null,
|
||||
this._conditions[i]['value']
|
||||
? this._conditions[i]['value'] : null,
|
||||
this._conditions[i]['required']
|
||||
? 1 : null
|
||||
];
|
||||
Zotero.DB.query(sql, sqlParams);
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
Zotero.Notifier.trigger(
|
||||
(isNew ? 'add' : 'modify'), 'search', this._savedSearchID
|
||||
);
|
||||
return this._savedSearchID;
|
||||
return this._id;
|
||||
}
|
||||
|
||||
|
||||
|
@ -198,9 +329,9 @@ Zotero.Search.prototype.clone = function() {
|
|||
var conditions = this.getSearchConditions();
|
||||
|
||||
for each(var condition in conditions) {
|
||||
var name = condition['mode'] ?
|
||||
condition['condition'] + '/' + condition['mode'] :
|
||||
condition['condition']
|
||||
var name = condition.mode ?
|
||||
condition.condition + '/' + condition.mode :
|
||||
condition.condition
|
||||
|
||||
s.addCondition(name, condition.operator, condition.value,
|
||||
condition.required);
|
||||
|
@ -210,7 +341,11 @@ Zotero.Search.prototype.clone = function() {
|
|||
}
|
||||
|
||||
|
||||
Zotero.Search.prototype.addCondition = function(condition, operator, value, required){
|
||||
Zotero.Search.prototype.addCondition = function(condition, operator, value, required) {
|
||||
if (this.id && !this._loaded) {
|
||||
this.load();
|
||||
}
|
||||
|
||||
if (!Zotero.SearchConditions.hasOperator(condition, operator)){
|
||||
throw ("Invalid operator '" + operator + "' for condition " + condition);
|
||||
}
|
||||
|
@ -271,6 +406,10 @@ Zotero.Search.prototype.setScope = function (searchObj, includeChildren) {
|
|||
|
||||
|
||||
Zotero.Search.prototype.updateCondition = function(searchConditionID, condition, operator, value, required){
|
||||
if (this.id && !this._loaded) {
|
||||
this.load();
|
||||
}
|
||||
|
||||
if (typeof this._conditions[searchConditionID] == 'undefined'){
|
||||
throw ('Invalid searchConditionID ' + searchConditionID + ' in updateCondition()');
|
||||
}
|
||||
|
@ -282,7 +421,7 @@ Zotero.Search.prototype.updateCondition = function(searchConditionID, condition,
|
|||
var [condition, mode] = Zotero.SearchConditions.parseCondition(condition);
|
||||
|
||||
this._conditions[searchConditionID] = {
|
||||
id: searchConditionID,
|
||||
id: parseInt(searchConditionID),
|
||||
condition: condition,
|
||||
mode: mode,
|
||||
operator: operator,
|
||||
|
@ -296,6 +435,10 @@ Zotero.Search.prototype.updateCondition = function(searchConditionID, condition,
|
|||
|
||||
|
||||
Zotero.Search.prototype.removeCondition = function(searchConditionID){
|
||||
if (this.id && !this._loaded) {
|
||||
this.load();
|
||||
}
|
||||
|
||||
if (typeof this._conditions[searchConditionID] == 'undefined'){
|
||||
throw ('Invalid searchConditionID ' + searchConditionID + ' in removeCondition()');
|
||||
}
|
||||
|
@ -309,6 +452,9 @@ Zotero.Search.prototype.removeCondition = function(searchConditionID){
|
|||
* for the given searchConditionID
|
||||
*/
|
||||
Zotero.Search.prototype.getSearchCondition = function(searchConditionID){
|
||||
if (this.id && !this._loaded) {
|
||||
this.load();
|
||||
}
|
||||
return this._conditions[searchConditionID];
|
||||
}
|
||||
|
||||
|
@ -318,6 +464,9 @@ Zotero.Search.prototype.getSearchCondition = function(searchConditionID){
|
|||
* used in the search, indexed by searchConditionID
|
||||
*/
|
||||
Zotero.Search.prototype.getSearchConditions = function(){
|
||||
if (this.id && !this._loaded) {
|
||||
this.load();
|
||||
}
|
||||
var conditions = [];
|
||||
var i = 1;
|
||||
for each(var condition in this._conditions) {
|
||||
|
@ -336,6 +485,9 @@ Zotero.Search.prototype.getSearchConditions = function(){
|
|||
|
||||
|
||||
Zotero.Search.prototype.hasPostSearchFilter = function() {
|
||||
if (this.id && !this._loaded) {
|
||||
this.load();
|
||||
}
|
||||
for each(var i in this._conditions){
|
||||
if (i.condition == 'fulltextContent'){
|
||||
return true;
|
||||
|
@ -349,6 +501,10 @@ Zotero.Search.prototype.hasPostSearchFilter = function() {
|
|||
* Run the search and return an array of item ids for results
|
||||
*/
|
||||
Zotero.Search.prototype.search = function(asTempTable){
|
||||
if (this.id && !this._loaded) {
|
||||
this.load();
|
||||
}
|
||||
|
||||
if (!this._sql){
|
||||
this._buildQuery();
|
||||
}
|
||||
|
@ -651,6 +807,20 @@ Zotero.Search.prototype.search = function(asTempTable){
|
|||
}
|
||||
|
||||
|
||||
Zotero.Search.prototype.serialize = function() {
|
||||
var obj = {
|
||||
primary: {
|
||||
id: this.id,
|
||||
dateModified: this.dateModified,
|
||||
key: this.key
|
||||
},
|
||||
name: this.name,
|
||||
conditions: this.getSearchConditions()
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get the SQL string for the search
|
||||
*/
|
||||
|
@ -670,6 +840,20 @@ Zotero.Search.prototype.getSQLParams = function(){
|
|||
}
|
||||
|
||||
|
||||
Zotero.Search.prototype._prepFieldChange = function (field) {
|
||||
if (!this._changed) {
|
||||
this._changed = {};
|
||||
}
|
||||
this._changed[field] = true;
|
||||
|
||||
// Save a copy of the data before changing
|
||||
// TODO: only save previous data if search exists
|
||||
if (this.id && this.exists() && !this._previousData) {
|
||||
this._previousData = this.serialize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Batch insert
|
||||
*/
|
||||
|
@ -895,8 +1079,7 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
condSQL += "NOT ";
|
||||
}
|
||||
condSQL += "IN (";
|
||||
var search = new Zotero.Search();
|
||||
search.load(condition['value']);
|
||||
var search = new Zotero.Search(condition.value);
|
||||
|
||||
// Check if there are any post-search filters
|
||||
var hasFilter = search.hasPostSearchFilter();
|
||||
|
@ -1292,16 +1475,32 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
}
|
||||
|
||||
|
||||
Zotero.Search.prototype._generateKey = function () {
|
||||
return Zotero.ID.getKey();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Zotero.Searches = new function(){
|
||||
this.get = get;
|
||||
this.getAll = getAll;
|
||||
this.getUpdated = getUpdated;
|
||||
this.erase = erase;
|
||||
|
||||
|
||||
function get(id){
|
||||
var sql = "SELECT savedSearchID AS id, savedSearchName AS name "
|
||||
+ "FROM savedSearches WHERE savedSearchID=?";
|
||||
return Zotero.DB.rowQuery(sql, [id]);
|
||||
/**
|
||||
* Retrieve a saved search
|
||||
*
|
||||
* @param int id savedSearchID
|
||||
* @return object|bool Zotero.Search object,
|
||||
* or false if it doesn't exist
|
||||
*/
|
||||
function get(id) {
|
||||
var sql = "SELECT COUNT(*) FROM savedSearches WHERE savedSearchID=?";
|
||||
if (Zotero.DB.valueQuery(sql, id)) {
|
||||
return new Zotero.Search(id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1315,21 +1514,37 @@ Zotero.Searches = new function(){
|
|||
}
|
||||
|
||||
|
||||
function getUpdated(date) {
|
||||
var sql = "SELECT savedSearchID FROM savedSearches";
|
||||
if (date) {
|
||||
sql += " WHERE dateModified>?";
|
||||
return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
|
||||
}
|
||||
return Zotero.DB.columnQuery(sql);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Delete a given saved search from the DB
|
||||
*/
|
||||
function erase(savedSearchID){
|
||||
Zotero.DB.beginTransaction();
|
||||
var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID="
|
||||
+ savedSearchID;
|
||||
Zotero.DB.query(sql);
|
||||
function erase(ids) {
|
||||
ids = Zotero.flattenArguments(ids);
|
||||
var notifierData = {};
|
||||
|
||||
var sql = "DELETE FROM savedSearches WHERE savedSearchID="
|
||||
+ savedSearchID;
|
||||
Zotero.DB.query(sql);
|
||||
Zotero.DB.beginTransaction();
|
||||
for each(var id in ids) {
|
||||
var search = new Zotero.Search(id);
|
||||
notifierData[id] = { old: search.serialize() };
|
||||
|
||||
var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
|
||||
Zotero.DB.query(sql, id);
|
||||
|
||||
var sql = "DELETE FROM savedSearches WHERE savedSearchID=?";
|
||||
Zotero.DB.query(sql, id);
|
||||
}
|
||||
Zotero.DB.commitTransaction();
|
||||
|
||||
Zotero.Notifier.trigger('delete', 'search', savedSearchID);
|
||||
Zotero.Notifier.trigger('delete', 'search', ids, notifierData);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +9,26 @@ Zotero.Sync = new function() {
|
|||
this.purgeDeletedObjects = purgeDeletedObjects;
|
||||
this.removeFromDeleted = removeFromDeleted;
|
||||
|
||||
// Keep in sync with syncObjectTypes table
|
||||
this.__defineGetter__('syncObjects', function () {
|
||||
return ['Creator', 'Item', 'Collection'];
|
||||
return {
|
||||
creator: {
|
||||
singular: 'Creator',
|
||||
plural: 'Creators'
|
||||
},
|
||||
item: {
|
||||
singular: 'Item',
|
||||
plural: 'Items'
|
||||
},
|
||||
collection: {
|
||||
singular: 'Collection',
|
||||
plural: 'Collections'
|
||||
},
|
||||
search: {
|
||||
singular: 'Search',
|
||||
plural: 'Searches'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
default xml namespace = '';
|
||||
|
@ -47,7 +65,7 @@ Zotero.Sync = new function() {
|
|||
}
|
||||
|
||||
|
||||
function getObjectTypeName(typeID) {
|
||||
function getObjectTypeName(typeID, plural) {
|
||||
if (!_typesLoaded) {
|
||||
_loadObjectTypes();
|
||||
}
|
||||
|
@ -64,10 +82,8 @@ Zotero.Sync = new function() {
|
|||
uploadIDs.changed = {};
|
||||
uploadIDs.deleted = {};
|
||||
|
||||
for each(var Type in Zotero.Sync.syncObjects) {
|
||||
var Types = Type + 's'; // 'Items'
|
||||
var type = Type.toLowerCase(); // 'item'
|
||||
var types = type + 's'; // 'items'
|
||||
for each(var syncObject in Zotero.Sync.syncObjects) {
|
||||
var types = syncObject.plural.toLowerCase(); // 'items'
|
||||
|
||||
uploadIDs.updated[types] = [];
|
||||
uploadIDs.changed[types] = {};
|
||||
|
@ -89,10 +105,9 @@ Zotero.Sync = new function() {
|
|||
}
|
||||
|
||||
var updatedIDs = {};
|
||||
for each(var Type in this.syncObjects) {
|
||||
var Types = Type + 's'; // 'Items'
|
||||
var type = Type.toLowerCase(); // 'item'
|
||||
var types = type + 's'; // 'items'
|
||||
for each(var syncObject in this.syncObjects) {
|
||||
var Types = syncObject.plural; // 'Items'
|
||||
var types = syncObject.plural.toLowerCase(); // 'items'
|
||||
|
||||
Zotero.debug("Getting updated local " + types);
|
||||
|
||||
|
@ -156,12 +171,14 @@ Zotero.Sync = new function() {
|
|||
}
|
||||
|
||||
var deletedIDs = {};
|
||||
for each(var Type in this.syncObjects) {
|
||||
deletedIDs[Type.toLowerCase() + 's'] = [];
|
||||
for each(var syncObject in this.syncObjects) {
|
||||
deletedIDs[syncObject.plural.toLowerCase()] = [];
|
||||
}
|
||||
|
||||
for each(var row in rows) {
|
||||
deletedIDs[this.getObjectTypeName(row.syncObjectTypeID) + 's'].push({
|
||||
var type = this.getObjectTypeName(row.syncObjectTypeID);
|
||||
type = this.syncObjects[type].plural.toLowerCase()
|
||||
deletedIDs[type].push({
|
||||
id: row.objectID,
|
||||
key: row.key
|
||||
});
|
||||
|
@ -239,8 +256,7 @@ Zotero.Sync.EventListener = new function () {
|
|||
* Blacklist objects from going into the sync delete log
|
||||
*/
|
||||
function ignoreDeletions(type, ids) {
|
||||
var cap = type[0].toUpperCase() + type.substr(1);
|
||||
if (Zotero.Sync.syncObjects.indexOf(cap) == -1) {
|
||||
if (!Zotero.Sync.syncObjects[type]) {
|
||||
throw ("Invalid type '" + type +
|
||||
"' in Zotero.Sync.EventListener.ignoreDeletions()");
|
||||
}
|
||||
|
@ -260,8 +276,7 @@ Zotero.Sync.EventListener = new function () {
|
|||
* Remove objects blacklisted from the sync delete log
|
||||
*/
|
||||
function unignoreDeletions(type, ids) {
|
||||
var cap = type[0].toUpperCase() + type.substr(1);
|
||||
if (Zotero.Sync.syncObjects.indexOf(cap) == -1) {
|
||||
if (!Zotero.Sync.syncObjects[type]) {
|
||||
throw ("Invalid type '" + type +
|
||||
"' in Zotero.Sync.EventListener.ignoreDeletions()");
|
||||
}
|
||||
|
@ -521,9 +536,7 @@ Zotero.Sync.Server = new function () {
|
|||
}
|
||||
|
||||
if (_syncInProgress) {
|
||||
Zotero.log("Sync operation already in progress", 'error');
|
||||
return;
|
||||
|
||||
_error("Sync operation already in progress");
|
||||
}
|
||||
|
||||
_syncInProgress = true;
|
||||
|
@ -990,8 +1003,14 @@ Zotero.Sync.Server = new function () {
|
|||
|
||||
|
||||
function _error(e) {
|
||||
_syncInProgress = false;
|
||||
_resetAttempts();
|
||||
Zotero.DB.rollbackAllTransactions();
|
||||
|
||||
if (_sessionID && _sessionLock) {
|
||||
Zotero.Sync.Server.unlock()
|
||||
}
|
||||
|
||||
throw(e);
|
||||
}
|
||||
}
|
||||
|
@ -1047,6 +1066,10 @@ Zotero.Sync.Server.Data = new function() {
|
|||
this.xmlToCollection = xmlToCollection;
|
||||
this.creatorToXML = creatorToXML;
|
||||
this.xmlToCreator = xmlToCreator;
|
||||
this.searchToXML = searchToXML;
|
||||
this.xmlToSearch = xmlToSearch;
|
||||
|
||||
var _noMergeTypes = ['search'];
|
||||
|
||||
default xml namespace = '';
|
||||
|
||||
|
@ -1061,10 +1084,11 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
for each(var Type in Zotero.Sync.syncObjects) {
|
||||
var Types = Type + 's'; // 'Items'
|
||||
for each(var syncObject in Zotero.Sync.syncObjects) {
|
||||
var Type = syncObject.singular; // 'Item'
|
||||
var Types = syncObject.plural; // 'Items'
|
||||
var type = Type.toLowerCase(); // 'item'
|
||||
var types = type + 's'; // 'items'
|
||||
var types = Types.toLowerCase(); // 'items'
|
||||
|
||||
if (!xml[types]) {
|
||||
continue;
|
||||
|
@ -1092,48 +1116,61 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
// Local object has been modified since last sync
|
||||
if ((objDate > lastLocalSyncDate &&
|
||||
objDate < Zotero.Sync.Server.nextLocalSyncDate)
|
||||
// Check for object in updated array, since it might
|
||||
// have been modified during sync process, making its
|
||||
// date equal to Zotero.Sync.Server.nextLocalSyncDate
|
||||
// and therefore excluded above (example: an item
|
||||
// linked to a creator whose id changed)
|
||||
|| uploadIDs.updated[types].indexOf(obj.id) != -1) {
|
||||
objDate < Zotero.Sync.Server.nextLocalSyncDate)
|
||||
// Check for object in updated array, since it might
|
||||
// have been modified during sync process, making its
|
||||
// date equal to Zotero.Sync.Server.nextLocalSyncDate
|
||||
// and therefore excluded above (example: an item
|
||||
// linked to a creator whose id changed)
|
||||
|| uploadIDs.updated[types].indexOf(obj.id) != -1) {
|
||||
|
||||
var remoteObj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode);
|
||||
|
||||
/*
|
||||
// For now, show item conflicts even if only
|
||||
// dateModified changed, since we need to handle
|
||||
// creator conflicts there
|
||||
if (type != 'item') {
|
||||
// Skip if only dateModified changed
|
||||
var diff = obj.diff(remoteObj, false, true);
|
||||
if (!diff) {
|
||||
// Some types we don't bother to reconcile
|
||||
if (_noMergeTypes.indexOf(type) != -1) {
|
||||
if (obj.dateModified > remoteObj.dateModified) {
|
||||
Zotero.Sync.addToUpdated(uploadIDs.updated.items, obj.id);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
obj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode, obj);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Will be handled by item CR for now
|
||||
if (type == 'creator') {
|
||||
remoteCreatorStore[remoteObj.id] = remoteObj;
|
||||
// Mark other types for conflict resolution
|
||||
else {
|
||||
/*
|
||||
// For now, show item conflicts even if only
|
||||
// dateModified changed, since we need to handle
|
||||
// creator conflicts there
|
||||
if (type != 'item') {
|
||||
// Skip if only dateModified changed
|
||||
var diff = obj.diff(remoteObj, false, true);
|
||||
if (!diff) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Will be handled by item CR for now
|
||||
if (type == 'creator') {
|
||||
remoteCreatorStore[remoteObj.id] = remoteObj;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type != 'item') {
|
||||
alert('Reconciliation unimplemented for ' + types);
|
||||
throw ('Reconciliation unimplemented for ' + types);
|
||||
}
|
||||
|
||||
// TODO: order reconcile by parent/child?
|
||||
|
||||
toReconcile.push([
|
||||
obj,
|
||||
remoteObj
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type != 'item') {
|
||||
alert('Reconciliation unimplemented for ' + types);
|
||||
_error('Reconciliation unimplemented for ' + types);
|
||||
}
|
||||
|
||||
// TODO: order reconcile by parent/child?
|
||||
|
||||
toReconcile.push([
|
||||
obj,
|
||||
remoteObj
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Local object hasn't been modified -- overwrite
|
||||
else {
|
||||
|
@ -1216,12 +1253,15 @@ Zotero.Sync.Server.Data = new function() {
|
|||
continue;
|
||||
}
|
||||
|
||||
var remoteObj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode);
|
||||
// TODO: non-merged items
|
||||
|
||||
if (type != 'item') {
|
||||
alert('Reconciliation unimplemented for ' + types);
|
||||
_error('Reconciliation unimplemented for ' + types);
|
||||
alert('Delete reconciliation unimplemented for ' + types);
|
||||
_error('Delete reconciliation unimplemented for ' + types);
|
||||
}
|
||||
|
||||
var remoteObj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode);
|
||||
|
||||
// TODO: order reconcile by parent/child?
|
||||
|
||||
toReconcile.push([
|
||||
|
@ -1229,7 +1269,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
remoteObj
|
||||
]);
|
||||
|
||||
break typeloop;
|
||||
continue typeloop;
|
||||
}
|
||||
|
||||
// Create locally
|
||||
|
@ -1245,7 +1285,10 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Handle deleted objects
|
||||
//
|
||||
if (xml.deleted && xml.deleted[types]) {
|
||||
Zotero.debug("Processing remotely deleted " + types);
|
||||
|
||||
|
@ -1275,7 +1318,9 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Reconcile objects that have changed locally and remotely
|
||||
//
|
||||
if (toReconcile.length) {
|
||||
var io = {
|
||||
dataIn: {
|
||||
|
@ -1345,11 +1390,11 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
// Sort collections in order of parent collections,
|
||||
// so referenced parent collections always exist when saving
|
||||
if (type == 'collection') {
|
||||
var collections = [];
|
||||
|
||||
// Sort collections in order of parent collections,
|
||||
// so referenced parent collections always exist when saving
|
||||
var cmp = function (a, b) {
|
||||
var pA = a.parent;
|
||||
var pB = b.parent;
|
||||
|
@ -1421,8 +1466,10 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
/**
|
||||
* ids = {
|
||||
* items: [123, 234, 345, 456],
|
||||
* creators: [321, 432, 543, 654],
|
||||
* updated: {
|
||||
* items: [123, 234, 345, 456],
|
||||
* creators: [321, 432, 543, 654]
|
||||
* },
|
||||
* changed: {
|
||||
* items: {
|
||||
* oldID: { oldID: 1234, newID: 5678 }, ...
|
||||
|
@ -1449,10 +1496,11 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
|
||||
// Updates
|
||||
for each(var Type in Zotero.Sync.syncObjects) {
|
||||
var Types = Type + 's'; // 'Items'
|
||||
for each(var syncObject in Zotero.Sync.syncObjects) {
|
||||
var Type = syncObject.singular; // 'Item'
|
||||
var Types = syncObject.plural; // 'Items'
|
||||
var type = Type.toLowerCase(); // 'item'
|
||||
var types = type + 's'; // 'items'
|
||||
var types = Types.toLowerCase(); // 'items'
|
||||
|
||||
if (!ids.updated[types]) {
|
||||
continue;
|
||||
|
@ -1462,7 +1510,7 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
switch (type) {
|
||||
// Items.get() can take multiple ids,
|
||||
// so we handle it differently
|
||||
// so we handle them differently
|
||||
case 'item':
|
||||
var objs = Zotero[Types].get(ids.updated[types]);
|
||||
for each(var obj in objs) {
|
||||
|
@ -1481,10 +1529,11 @@ Zotero.Sync.Server.Data = new function() {
|
|||
// TODO: handle changed ids
|
||||
|
||||
// Deletions
|
||||
for each(var Type in Zotero.Sync.syncObjects) {
|
||||
var Types = Type + 's'; // 'Items'
|
||||
for each(var syncObject in Zotero.Sync.syncObjects) {
|
||||
var Type = syncObject.singular; // 'Item'
|
||||
var Types = syncObject.plural; // 'Items'
|
||||
var type = Type.toLowerCase(); // 'item'
|
||||
var types = type + 's'; // 'items'
|
||||
var types = Types.toLowerCase(); // 'items'
|
||||
|
||||
if (!ids.deleted[types]) {
|
||||
continue;
|
||||
|
@ -1849,4 +1898,106 @@ Zotero.Sync.Server.Data = new function() {
|
|||
|
||||
return creator;
|
||||
}
|
||||
|
||||
|
||||
function searchToXML(search) {
|
||||
var xml = <search/>;
|
||||
|
||||
xml.@id = search.id;
|
||||
xml.@name = search.name;
|
||||
xml.@dateModified = search.dateModified;
|
||||
xml.@key = search.key;
|
||||
|
||||
var conditions = search.getSearchConditions();
|
||||
if (conditions) {
|
||||
for each(var condition in conditions) {
|
||||
var conditionXML = <condition/>
|
||||
conditionXML.@id = condition.id;
|
||||
conditionXML.@condition = condition.condition;
|
||||
if (condition.mode) {
|
||||
conditionXML.@mode = condition.mode;
|
||||
}
|
||||
conditionXML.@operator = condition.operator;
|
||||
conditionXML.@value = condition.value;
|
||||
if (condition.required) {
|
||||
conditionXML.@required = 1;
|
||||
}
|
||||
xml.condition += conditionXML;
|
||||
}
|
||||
}
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert E4X <search> object into an unsaved Zotero.Search
|
||||
*
|
||||
* @param object xmlSearch E4X XML node with search data
|
||||
* @param object item (Optional) Existing Zotero.Search to update
|
||||
* @param bool newID (Optional) Ignore passed searchID and choose new one
|
||||
*/
|
||||
function xmlToSearch(xmlSearch, search, newID) {
|
||||
if (!search) {
|
||||
if (newID) {
|
||||
search = new Zotero.Search(null);
|
||||
}
|
||||
else {
|
||||
search = new Zotero.Search(parseInt(xmlSearch.@id));
|
||||
/*
|
||||
if (search.exists()) {
|
||||
throw ("Search specified in XML node already exists "
|
||||
+ "in Zotero.Sync.Server.Data.xmlToSearch()");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
else if (newID) {
|
||||
_error("Cannot use new id with existing search in "
|
||||
+ "Zotero.Sync.Server.Data.xmlToSearch()");
|
||||
}
|
||||
|
||||
search.name = xmlSearch.@name.toString();
|
||||
search.dateModified = xmlSearch.@dateModified.toString();
|
||||
search.key = xmlSearch.@key.toString();
|
||||
|
||||
var conditionID = -1;
|
||||
|
||||
// Search conditions
|
||||
for each(var condition in xmlSearch.condition) {
|
||||
conditionID = parseInt(condition.@id);
|
||||
var name = condition.@condition.toString();
|
||||
var mode = condition.@mode.toString();
|
||||
if (mode) {
|
||||
name = name + '/' + mode;
|
||||
}
|
||||
if (search.getSearchCondition(conditionID)) {
|
||||
search.updateCondition(
|
||||
conditionID,
|
||||
name,
|
||||
condition.@operator.toString(),
|
||||
condition.@value.toString(),
|
||||
!!condition.@required.toString()
|
||||
);
|
||||
}
|
||||
else {
|
||||
var newID = search.addCondition(
|
||||
name,
|
||||
condition.@operator.toString(),
|
||||
condition.@value.toString(),
|
||||
!!condition.@required.toString()
|
||||
);
|
||||
if (newID != conditionID) {
|
||||
throw ("Search condition ids not contiguous in Zotero.Sync.Server.xmlToSearch()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conditionID++;
|
||||
while (search.getSearchCondition(conditionID)) {
|
||||
search.removeCondition(conditionID);
|
||||
}
|
||||
|
||||
return search;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,6 +191,11 @@
|
|||
list-style-image: url('chrome://zotero/skin/toolbar-advanced-search.png');
|
||||
}
|
||||
|
||||
#zotero-tb-sync #zotero-last-sync-time
|
||||
{
|
||||
color: gray;
|
||||
}
|
||||
|
||||
#zotero-tb-fullscreen
|
||||
{
|
||||
list-style-image: url('chrome://zotero/skin/toolbar-fullscreen-bottom.png');
|
||||
|
|
|
@ -30,7 +30,7 @@ CREATE TABLE fields (
|
|||
fieldID INTEGER PRIMARY KEY,
|
||||
fieldName TEXT,
|
||||
fieldFormatID INT,
|
||||
FOREIGN KEY (fieldFormatID) REFERENCES fieldFormat(fieldFormatID)
|
||||
FOREIGN KEY (fieldFormatID) REFERENCES fieldFormats(fieldFormatID)
|
||||
);
|
||||
|
||||
-- Defines valid fields for each itemType, their display order, and their default visibility
|
||||
|
@ -1248,4 +1248,4 @@ INSERT INTO "charsets" VALUES(168, 'x0212');
|
|||
INSERT INTO "syncObjectTypes" VALUES(1, 'collection');
|
||||
INSERT INTO "syncObjectTypes" VALUES(2, 'creator');
|
||||
INSERT INTO "syncObjectTypes" VALUES(3, 'item');
|
||||
INSERT INTO "syncObjectTypes" VALUES(4, 'savedSearch');
|
||||
INSERT INTO "syncObjectTypes" VALUES(4, 'search');
|
||||
|
|
28
triggers.sql
28
triggers.sql
|
@ -608,29 +608,29 @@ CREATE TRIGGER fkd_itemTags_tagID_tags_tagID
|
|||
WHERE (SELECT COUNT(*) FROM itemTags WHERE tagID = OLD.tagID) > 0;
|
||||
END;
|
||||
|
||||
-- savedSearchConditions/searchConditionID
|
||||
DROP TRIGGER IF EXISTS fki_savedSearchConditions_searchConditionID_savedSearches_savedSearchID;
|
||||
CREATE TRIGGER fki_savedSearchConditions_searchConditionID_savedSearches_savedSearchID
|
||||
-- savedSearchConditions/savedSearchID
|
||||
DROP TRIGGER IF EXISTS fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID;
|
||||
CREATE TRIGGER fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID
|
||||
BEFORE INSERT ON savedSearchConditions
|
||||
FOR EACH ROW BEGIN
|
||||
SELECT RAISE(ABORT, 'insert on table "savedSearchConditions" violates foreign key constraint "fki_savedSearchConditions_searchConditionID_savedSearches_savedSearchID"')
|
||||
WHERE NEW.searchConditionID IS NOT NULL AND (SELECT COUNT(*) FROM savedSearches WHERE savedSearchID = NEW.searchConditionID) = 0;
|
||||
SELECT RAISE(ABORT, 'insert on table "savedSearchConditions" violates foreign key constraint "fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID"')
|
||||
WHERE (SELECT COUNT(*) FROM savedSearches WHERE savedSearchID = NEW.savedSearchID) = 0;
|
||||
END;
|
||||
|
||||
DROP TRIGGER IF EXISTS fku_savedSearchConditions_searchConditionID_savedSearches_savedSearchID;
|
||||
CREATE TRIGGER fku_savedSearchConditions_searchConditionID_savedSearches_savedSearchID
|
||||
BEFORE UPDATE OF searchConditionID ON savedSearchConditions
|
||||
DROP TRIGGER IF EXISTS fku_savedSearchConditions_savedSearchID_savedSearches_savedSearchID;
|
||||
CREATE TRIGGER fku_savedSearchConditions_savedSearchID_savedSearches_savedSearchID
|
||||
BEFORE UPDATE OF savedSearchID ON savedSearchConditions
|
||||
FOR EACH ROW BEGIN
|
||||
SELECT RAISE(ABORT, 'update on table "savedSearchConditions" violates foreign key constraint "fku_savedSearchConditions_searchConditionID_savedSearches_savedSearchID"')
|
||||
WHERE NEW.searchConditionID IS NOT NULL AND (SELECT COUNT(*) FROM savedSearches WHERE savedSearchID = NEW.searchConditionID) = 0;
|
||||
SELECT RAISE(ABORT, 'update on table "savedSearchConditions" violates foreign key constraint "fku_savedSearchConditions_savedSearchID_savedSearches_savedSearchID"')
|
||||
WHERE (SELECT COUNT(*) FROM savedSearches WHERE savedSearchID = NEW.savedSearchID) = 0;
|
||||
END;
|
||||
|
||||
DROP TRIGGER IF EXISTS fkd_savedSearchConditions_searchConditionID_savedSearches_savedSearchID;
|
||||
CREATE TRIGGER fkd_savedSearchConditions_searchConditionID_savedSearches_savedSearchID
|
||||
DROP TRIGGER IF EXISTS fkd_savedSearchConditions_savedSearchID_savedSearches_savedSearchID;
|
||||
CREATE TRIGGER fkd_savedSearchConditions_savedSearchID_savedSearches_savedSearchID
|
||||
BEFORE DELETE ON savedSearches
|
||||
FOR EACH ROW BEGIN
|
||||
SELECT RAISE(ABORT, 'delete on table "savedSearches" violates foreign key constraint "fkd_savedSearchConditions_searchConditionID_savedSearches_savedSearchID"')
|
||||
WHERE (SELECT COUNT(*) FROM savedSearchConditions WHERE searchConditionID = OLD.savedSearchID) > 0;
|
||||
SELECT RAISE(ABORT, 'delete on table "savedSearches" violates foreign key constraint "fkd_savedSearchConditions_savedSearchID_savedSearches_savedSearchID"')
|
||||
WHERE (SELECT COUNT(*) FROM savedSearchConditions WHERE savedSearchID = OLD.savedSearchID) > 0;
|
||||
END;
|
||||
|
||||
-- syncDeleteLog/syncObjectTypeID
|
||||
|
|
Loading…
Reference in a new issue