diff --git a/chrome/chromeFiles/content/scholar/xpcom/data_access.js b/chrome/chromeFiles/content/scholar/xpcom/data_access.js index 85ed93ed1..894e6fbbf 100644 --- a/chrome/chromeFiles/content/scholar/xpcom/data_access.js +++ b/chrome/chromeFiles/content/scholar/xpcom/data_access.js @@ -44,6 +44,7 @@ Scholar.Item.prototype.isPrimaryField = function(field){ Scholar.Item.primaryFields = Scholar.DB.getColumnHash('items'); Scholar.Item.primaryFields['firstCreator'] = true; Scholar.Item.primaryFields['numNotes'] = true; + Scholar.Item.primaryFields['numFiles'] = true; } return !!Scholar.Item.primaryFields[field]; @@ -70,7 +71,8 @@ Scholar.Item.prototype.loadFromID = function(id){ var sql = 'SELECT I.*, lastName || ' + 'CASE ((SELECT COUNT(*) FROM itemCreators WHERE itemID=' + id + ')>1) ' + "WHEN 0 THEN '' ELSE ' et al.' END AS firstCreator, " - + "(SELECT COUNT(*) FROM itemNotes WHERE sourceItemID=I.itemID) AS numNotes " + + "(SELECT COUNT(*) FROM itemNotes WHERE sourceItemID=I.itemID) AS numNotes, " + + "(SELECT COUNT(*) FROM itemFiles WHERE sourceItemID=I.itemID) AS numFiles " + 'FROM items I ' + 'LEFT JOIN itemCreators IC ON (I.itemID=IC.itemID) ' + 'LEFT JOIN creators C ON (IC.creatorID=C.creatorID) ' @@ -774,11 +776,13 @@ Scholar.Item.prototype.updateDateModified = function(){ } +//////////////////////////////////////////////////////// // -// Methods dealing with item notes +// Methods dealing with note items // // save() is not required for note functions // +//////////////////////////////////////////////////////// Scholar.Item.prototype.incrementNoteCount = function(){ this._data['numNotes']++; } @@ -827,13 +831,21 @@ Scholar.Item.prototype.updateNote = function(text){ } -Scholar.Item.prototype.setNoteSource = function(sourceItemID){ - if (!this.isNote()){ - throw ("updateNote() can only be called on items of type 'note'"); +Scholar.Item.prototype.setSource = function(sourceItemID){ + if (this.isNote()){ + var type = 'note'; + var Type = 'Note'; + } + else if (this.isFile()){ + var type = 'file'; + var Type = 'file'; + } + else { + throw ("setSource() can only be called on items of type 'note' or 'file'"); } if (!this.getID()){ - throw ("Cannot call setNoteSource() on unsaved note"); + throw ("Cannot call setSource() on unsaved " + type); } Scholar.DB.beginTransaction(); @@ -842,15 +854,15 @@ Scholar.Item.prototype.setNoteSource = function(sourceItemID){ // FK check if (sourceItemID && !newItem){ Scholar.DB.rollbackTransaction(); - throw ("Cannot set note source to invalid item " + sourceItemID); + throw ("Cannot set " + type + " source to invalid item " + sourceItemID); } // Get previous source item id - var sql = "SELECT sourceItemID FROM itemNotes WHERE item=" + this.getID(); + var sql = "SELECT sourceItemID FROM item" + Type + "s WHERE item=" + this.getID(); var oldSourceItemID = Scholar.DB.valueQuery(sql); if (oldSourceItemID==sourceItemID){ - Scholar.debug("Note source hasn't changed", 4); + Scholar.debug(Type + " source hasn't changed", 4); Scholar.DB.commitTransaction(); return false; } @@ -858,10 +870,10 @@ Scholar.Item.prototype.setNoteSource = function(sourceItemID){ var oldItem = Scholar.Items.get(oldSourceItemID); if (oldSourceItemID && !oldItem){ Scholar.debug("Old source item " + oldSourceItemID - + "didn't exist in setNoteSource()", 2); + + "didn't exist in setSource()", 2); } - var sql = "UPDATE itemNotes SET sourceItemID=? WHERE itemID=?"; + var sql = "UPDATE item" + Type + "s SET sourceItemID=? WHERE itemID=?"; var bindParams = [sourceItemID ? {int:sourceItemID} : null, this.getID()]; Scholar.DB.query(sql, bindParams); this.updateDateModified(); @@ -869,13 +881,27 @@ Scholar.Item.prototype.setNoteSource = function(sourceItemID){ Scholar.Notifier.trigger('modify', 'item', this.getID()); - // Update the note counts of the previous and new sources + // Update the counts of the previous and new sources if (oldItem){ - oldItem.decrementNoteCount(); + switch (type){ + case 'note': + oldItem.decrementNoteCount(); + break; + case 'file': + oldItem.decrementFileCount(); + break; + } Scholar.Notifier.trigger('modify', 'item', oldSourceItemID); } if (newItem){ - newItem.incrementNoteCount(); + switch (type){ + case 'note': + newItem.incrementNoteCount(); + break; + case 'file': + newItem.incrementFileCount(); + break; + } Scholar.Notifier.trigger('modify', 'item', sourceItemID); } @@ -914,14 +940,20 @@ Scholar.Item.prototype.getNote = function(){ /** -* Get the itemID of the source item for a note +* Get the itemID of the source item for a note or file **/ -Scholar.Item.prototype.getNoteSource = function(){ - if (!this.isNote()){ - throw ("getNoteSource() can only be called on items of type 'note'"); +Scholar.Item.prototype.getSource = function(){ + if (this.isNote()){ + var Type = 'Note'; + } + else if (this.isFile()){ + var Type = 'File'; + } + else { + throw ("getSource() can only be called on items of type 'note' or 'file'"); } - var sql = "SELECT sourceItemID FROM itemNotes WHERE itemID=" + this.getID(); + var sql = "SELECT sourceItemID FROM item" + Type + "s WHERE itemID=" + this.getID(); return Scholar.DB.valueQuery(sql); } @@ -945,6 +977,171 @@ Scholar.Item.prototype.getNotes = function(){ } + +//////////////////////////////////////////////////////// +// +// Methods dealing with file items +// +// save() is not required for file functions +// +/////////////////////////////////////////////////////// +Scholar.Item.prototype.incrementFileCount = function(){ + this._data['numFiles']++; +} + + +Scholar.Item.prototype.decrementFileCount = function(){ + this._data['numFiles']--; +} + + +/** +* Determine if an item is a file +**/ +Scholar.Item.prototype.isFile = function(){ + return Scholar.ItemTypes.getName(this.getType())=='file'; +} + + +/** +* Returns number of files in item +**/ +Scholar.Item.prototype.numFiles = function(){ + if (this.isFile()){ + throw ("numFiles() cannot be called on items of type 'file'"); + } + + if (!this.getID()){ + return 0; + } + + return this._data['numFiles']; +} + + +/** +* Get an nsILocalFile for the file item, or false if the associated file doesn't exist +* +* Note: Always returns false for items with LINK_MODE_LINKED_URL, +* since they have no files -- use getFileURL() instead +**/ +Scholar.Item.prototype.getFile = function(){ + if (!this.isFile()){ + throw ("getFile() can only be called on items of type 'file'"); + } + + var sql = "SELECT linkMode, path FROM itemFiles WHERE itemID=" + this.getID(); + var row = Scholar.DB.rowQuery(sql); + + if (!row){ + throw ('File data not found for item ' + this.getID() + ' in getFile()'); + } + + // No associated files for linked URLs + if (row['linkMode']==Scholar.Files.LINK_MODE_LINKED_URL){ + return false; + } + + var file = Components.classes["@mozilla.org/file/local;1"]. + createInstance(Components.interfaces.nsILocalFile); + + var refDir = (linkMode==this.LINK_MODE_LINKED_FILE) + ? Scholar.getScholarDirectory() : Scholar.getStorageDirectory(); + file.setRelativeDescriptor(refDir, row['path']); + + if (!file.exists()){ + return false; + } + + return file; +} + + +Scholar.Item.prototype.getFileURL = function(){ + if (!this.isFile()){ + throw ("getFileURL() can only be called on items of type 'file'"); + } + + var sql = "SELECT linkMode, path, originalPath FROM itemFiles " + + "WHERE itemID=" + this.getID(); + var row = Scholar.DB.rowQuery(sql); + + if (!row){ + throw ('File data not found for item ' + this.getID() + ' in getFileURL()'); + } + + switch (row['linkMode']){ + case Scholar.Files.LINK_MODE_LINKED_URL: + return row['path']; + case Scholar.Files.LINK_MODE_IMPORTED_URL: + return row['originalPath']; + default: + throw ('getFileURL() cannot be called on files without associated URLs'); + } +} + + +/** +* Get the link mode of a file item +* +* Possible return values specified as constants in Scholar.Files +* (e.g. Scholar.Files.LINK_MODE_LINKED_FILE) +**/ +Scholar.Item.prototype.getFileLinkMode = function(){ + if (!this.isFile()){ + throw ("getFileLinkMode() can only be called on items of type 'file'"); + } + + var sql = "SELECT linkMode FROM itemFiles WHERE itemID=" + this.getID(); + return Scholar.DB.valueQuery(sql); +} + + +/** +* Get the mime type of a file item (e.g. text/plain) +**/ +Scholar.Item.prototype.getFileMimeType = function(){ + if (!this.isFile()){ + throw ("getFileData() can only be called on items of type 'file'"); + } + + var sql = "SELECT mimeType FROM itemFiles WHERE itemID=" + this.getID(); + return Scholar.DB.valueQuery(sql); +} + + +/** +* Get the character set id of a file item +**/ +Scholar.Item.prototype.getFileCharset = function(){ + if (!this.isFile()){ + throw ("getFileCharset() can only be called on items of type 'file'"); + } + + var sql = "SELECT charsetID FROM itemFiles WHERE itemID=" + this.getID(); + return Scholar.DB.valueQuery(sql); +} + + +/** +* Returns an array of file itemIDs for this item +**/ +Scholar.Item.prototype.getFiles = function(){ + if (this.isFile()){ + throw ("getFiles() cannot be called on items of type 'file'"); + } + + if (!this.getID()){ + return []; + } + + var sql = "SELECT itemID FROM itemFiles NATURAL JOIN items " + + "WHERE sourceItemID=" + this.getID() + " ORDER BY dateAdded"; + return Scholar.DB.columnQuery(sql); +} + + + // // Methods dealing with item tags // @@ -1069,8 +1266,9 @@ Scholar.Item.prototype.erase = function(){ Scholar.Collections.get(parentCollectionIDs[i]).removeItem(this.getID()); } - // If note, remove item from source notes + // Note if (this.isNote()){ + // Decrement note count of source items var sql = "SELECT sourceItemID FROM itemNotes WHERE itemID=" + this.getID(); var sourceItemID = Scholar.DB.valueQuery(sql); if (sourceItemID){ @@ -1079,19 +1277,55 @@ Scholar.Item.prototype.erase = function(){ changedItems.push(sourceItemID); } } - // If not note, unassociate any notes for which this is a source + // File + else if (this.isFile()){ + // Decrement file count of source items + var sql = "SELECT sourceItemID FROM itemFiles WHERE itemID=" + this.getID(); + var sourceItemID = Scholar.DB.valueQuery(sql); + if (sourceItemID){ + var sourceItem = Scholar.Items.get(sourceItemID); + sourceItem.decrementFileCount(); + changedItems.push(sourceItemID); + } + + // Delete associated files + var linkMode = this.getFileLinkMode(); + switch (linkMode){ + case Scholar.Files.LINK_MODE_LINKED_FILE: + case Scholar.Files.LINK_MODE_LINKED_URL: + // Links only -- nothing to delete + break; + default: + var file = Scholar.getStorageDirectory(); + file.append(this.getID()); + if (file.exists()){ + file.remove(true); + } + } + } + // If regular item, unassociate any notes or files for which this is a source else { // TODO: option for deleting child notes instead of unlinking + // Notes var sql = "SELECT itemID FROM itemNotes WHERE sourceItemID=" + this.getID(); var childNotes = Scholar.DB.columnQuery(sql); if (childNotes){ changedItems.push(childNotes); } - var sql = "UPDATE itemNotes SET sourceItemID=NULL WHERE sourceItemID=" + this.getID(); Scholar.DB.query(sql); + + // Files + var sql = "SELECT itemID FROM itemFiles WHERE sourceItemID=" + this.getID(); + var childFiles = Scholar.DB.columnQuery(sql); + if (childFiles){ + changedItems.push(childFiles); + } + var sql = "UPDATE itemFiles SET sourceItemID=NULL WHERE sourceItemID=" + + this.getID(); + Scholar.DB.query(sql); } // Flag See Also links for notification @@ -1102,6 +1336,7 @@ Scholar.Item.prototype.erase = function(){ sql = 'DELETE FROM itemCreators WHERE itemID=' + this.getID() + ";\n"; sql += 'DELETE FROM itemNotes WHERE itemID=' + this.getID() + ";\n"; + sql += 'DELETE FROM itemFiles WHERE itemID=' + this.getID() + ";\n"; sql += 'DELETE FROM itemSeeAlso WHERE itemID=' + this.getID() + ";\n"; sql += 'DELETE FROM itemSeeAlso WHERE linkedItemID=' + this.getID() + ";\n"; sql += 'DELETE FROM itemTags WHERE itemID=' + this.getID() + ";\n"; @@ -1115,8 +1350,14 @@ Scholar.Item.prototype.erase = function(){ Scholar.DB.commitTransaction(); } catch (e){ + // On failure, reset count of source items if (sourceItem){ - sourceItem.incrementNoteCount(); + if (this.isNote()){ + sourceItem.incrementNoteCount(); + } + else if (this.isFile()){ + sourceItem.incrementFileCount(); + } } Scholar.DB.rollbackTransaction(); throw (e); @@ -1167,6 +1408,7 @@ Scholar.Item.prototype.toArray = function(){ // Skip certain fields case 'firstCreator': case 'numNotes': + case 'numFiles': continue; // For the rest, just copy over @@ -1180,7 +1422,7 @@ Scholar.Item.prototype.toArray = function(){ arr[Scholar.ItemFields.getName(i)] = this._itemData[i]; } - if (!this.isNote()){ + if (!this.isNote() && !this.isFile()){ // Creators arr['creators'] = []; var creators = this.getCreators(); @@ -1192,9 +1434,20 @@ Scholar.Item.prototype.toArray = function(){ arr['creators'][i]['creatorType'] = Scholar.CreatorTypes.getName(creators[i]['creatorTypeID']); } - - // Source notes - arr['notes'] = [] + } + + // Notes + if (this.isNote()){ + // Don't need title for notes + delete arr['title']; + arr['note'] = this.getNote(); + if (this.getSource()){ + arr['sourceItemID'] = this.getSource(); + } + } + // If not note, append attached notes + else { + arr['notes'] = []; var notes = this.getNotes(); for (var i in notes){ var note = Scholar.Items.get(notes[i]); @@ -1207,16 +1460,34 @@ Scholar.Item.prototype.toArray = function(){ } } - // Notes - else { - // Don't need title for notes + // Append source files + if (this.isFile()){ + arr['fileName'] = arr['title']; delete arr['title']; - arr['note'] = this.getNote(); - if (this.getNoteSource()){ - arr['sourceItemID'] = this.getNoteSource(); + + // TODO: file data + + if (this.getSource()){ + arr['sourceItemID'] = this.getSource(); } } + // If not file, append attached files + else { + arr['files'] = []; + var files = this.getFiles(); + for (var i in files){ + var file = Scholar.Items.get(files[i]); + arr['files'].push({ + itemID: file.getID(), + // TODO + tags: file.getTags(), + seeAlso: file.getSeeAlso() + }); + } + } + + arr['tags'] = this.getTags(); arr['seeAlso'] = this.getSeeAlso(); @@ -1473,7 +1744,8 @@ Scholar.Items = new function(){ var sql = 'SELECT I.*, lastName || ' + 'CASE ((SELECT COUNT(*) FROM itemCreators WHERE itemID=I.itemID)>1) ' + "WHEN 0 THEN '' ELSE ' et al.' END AS firstCreator, " - + "(SELECT COUNT(*) FROM itemNotes WHERE sourceItemID=I.itemID) AS numNotes " + + "(SELECT COUNT(*) FROM itemNotes WHERE sourceItemID=I.itemID) AS numNotes, " + + "(SELECT COUNT(*) FROM itemFiles WHERE sourceItemID=I.itemID) AS numFiles " + 'FROM items I ' + 'LEFT JOIN itemCreators IC ON (I.itemID=IC.itemID) ' + 'LEFT JOIN creators C ON (IC.creatorID=C.creatorID) ' @@ -1554,7 +1826,231 @@ Scholar.Notes = new function(){ - +Scholar.Files = new function(){ + this.LINK_MODE_IMPORTED_FILE = 0; + this.LINK_MODE_IMPORTED_URL = 1; + this.LINK_MODE_LINKED_FILE = 2; + this.LINK_MODE_LINKED_URL = 3; + + this.importFromFile = importFromFile; + this.linkFromFile = linkFromFile; + this.linkFromDocument = linkFromDocument; + this.importFromDocument = importFromDocument; + + + function importFromFile(file, sourceItemID){ + var title = file.leafName; + + Scholar.DB.beginTransaction(); + + // Create a new file item + var fileItem = Scholar.Items.getNewItemByType(Scholar.ItemTypes.getID('file')); + fileItem.setField('title', title); + fileItem.save(); + var itemID = fileItem.getID(); + + // Create directory for item files within storage directory + var destDir = Scholar.getStorageDirectory(); + destDir.append(itemID); + destDir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0644); + + try { + file.copyTo(destDir, null); + } + catch (e){ + // hmph + Scholar.DB.rollbackTransaction(); + destDir.remove(true); + throw (e); + } + + // Point to copied file + var newFile = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + newFile.initWithFile(destDir); + newFile.append(title); + + var mimeType = _getMIMETypeFromFile(newFile); + var charsetID = _getCharsetIDFromFile(newFile); + + _addToDB(newFile, null, null, this.LINK_MODE_IMPORTED_FILE, mimeType, charsetID, sourceItemID, itemID); + Scholar.DB.commitTransaction(); + return itemID; + } + + + function linkFromFile(file, sourceItemID){ + var title = file.leafName; + var mimeType = _getMIMETypeFromFile(file); + var charsetID = _getCharsetIDFromFile(file); + return _addToDB(file, null, title, this.LINK_MODE_LINKED_FILE, mimeType, charsetID, sourceItemID); + } + + + // TODO: what if called on file:// document? + function linkFromDocument(document, sourceItemID){ + var url = document.location; + var title = document.title; // TODO: don't use Mozilla-generated title for images, etc. + var mimeType = document.contentType; + var charsetID = Scholar.CharacterSets.getID(document.characterSet); + + return _addToDB(null, url, title, this.LINK_MODE_LINKED_URL, mimeType, charsetID, sourceItemID); + } + + + function importFromDocument(document, sourceItemID){ + var url = document.location; + var title = document.title; + var mimeType = document.contentType; + var charsetID = Scholar.CharacterSets.getID(document.characterSet); + + const nsIWBP = Components.interfaces.nsIWebBrowserPersist; + var wbp = Components + .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(nsIWBP); + //wbp.persistFlags = nsIWBP.PERSIST_FLAGS...; + var encodingFlags = false; + + Scholar.DB.beginTransaction(); + + // Create a new file item + var fileItem = Scholar.Items.getNewItemByType(Scholar.ItemTypes.getID('file')); + fileItem.setField('title', title); + fileItem.save(); + var itemID = fileItem.getID(); + + // Create a new folder for this item in the storage directory + var destDir = Scholar.getStorageDirectory(); + destDir.append(itemID); + destDir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0644); + + var file = Components.classes["@mozilla.org/file/local;1"]. + createInstance(Components.interfaces.nsILocalFile); + file.initWithFile(destDir); + file.append(_getFileNameFromURL(url, mimeType)); + + wbp.saveDocument(document, file, destDir, mimeType, encodingFlags, false); + + _addToDB(file, url, title, this.LINK_MODE_IMPORTED_URL, mimeType, charsetID, sourceItemID, itemID); + + Scholar.DB.commitTransaction(); + return itemID; + } + + + // TODO: currently only uses file extension + function _getMIMETypeFromFile(file){ + var ms = Components + .classes['@mozilla.org/uriloader/external-helper-app-service;1'] + .getService(Components.interfaces.nsIMIMEService); + return ms.getTypeFromFile(file); + } + + + function _getCharsetIDFromFile(file){ + // TODO: Not yet implemented + return null; + } + + + function _getFileNameFromURL(url, mimeType){ + var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"] + .createInstance(Components.interfaces.nsIURL); + nsIURL.spec = url; + + if (nsIURL.fileName){ + return nsIURL.fileName; + } + + if (mimeType){ + var ext = Components.classes["@mozilla.org/mime;1"] + .getService(Components.interfaces.nsIMIMEService) + .getPrimaryExtension(mimeType, nsIURL.fileExt ? nsIURL.fileExt : null); + } + + return nsIURL.host + (ext ? '.' + ext : ''); + } + + + /** + * Create a new item of type 'file' and add the file link to the itemFiles table + * + * Passing an itemID causes it to skip new item creation and use the specified + * item instead -- used when importing files (since we have to know + * the itemID before copying in a file and don't want to update the DB before + * the file is saved) + * + * Returns the itemID of the new file item + **/ + function _addToDB(file, url, title, linkMode, mimeType, charsetID, sourceItemID, itemID){ + if (url){ + var path = url; + } + + if (file){ + if (linkMode==this.LINK_MODE_IMPORTED_URL){ + var originalPath = path; + } + + // Path relative to Scholar directory for external files and relative + // to storage directory for imported files + var refDir = (linkMode==this.LINK_MODE_LINKED_FILE) + ? Scholar.getScholarDirectory() : Scholar.getStorageDirectory(); + var path = file.getRelativeDescriptor(refDir); + } + + Scholar.DB.beginTransaction(); + + if (sourceItemID){ + var sourceItem = Scholar.Items.get(sourceItemID); + if (!sourceItem){ + Scholar.DB.commitTransaction(); + throw ("Cannot set file source to invalid item " + sourceItemID); + } + if (sourceItem.isFile()){ + Scholar.DB.commitTransaction(); + throw ("Cannot set file source to another file (" + sourceItemID + ")"); + } + } + + // If an itemID is provided, use that + if (itemID){ + var fileItem = Scholar.Items.get(itemID); + if (!fileItem.isFile()){ + throw ("Item " + itemID + " is not a valid file item in _addToDB()"); + } + } + // Otherwise create a new file item + else { + var fileItem = Scholar.Items.getNewItemByType(Scholar.ItemTypes.getID('file')); + fileItem.setField('title', title); + fileItem.save(); + } + + var sql = "INSERT INTO itemFiles (itemID, sourceItemID, linkMode, " + + "mimeType, charsetID, path, originalPath) VALUES (?,?,?,?,?,?,?)"; + var bindParams = [ + fileItem.getID(), + (sourceItemID ? {int:sourceItemID} : null), + {int:linkMode}, + {string:mimeType}, + (charsetID ? {int:charsetID} : null), + {string:path}, + (originalPath ? {string:originalPath} : null) + ]; + Scholar.DB.query(sql, bindParams); + Scholar.DB.commitTransaction(); + + if (sourceItemID){ + sourceItem.incrementNoteCount(); + Scholar.Notifier.trigger('modify', 'item', sourceItemID); + } + + Scholar.Notifier.trigger('add', 'item', fileItem.getID()); + + return fileItem.getID(); + } +} /* * Constructor for Collection object @@ -2279,7 +2775,8 @@ Scholar.Tags = new function(){ /* - * Same structure as Scholar.ItemTypes -- make changes in both places if possible + * Same structure as Scholar.ItemTypes and FileTypes -- + * make changes in both places if possible */ Scholar.CreatorTypes = new function(){ var _types = new Array(); @@ -2344,7 +2841,8 @@ Scholar.CreatorTypes = new function(){ /* - * Same structure as Scholar.CreatorTypes -- make changes in both places if possible + * Same structure as Scholar.CreatorTypes and FileTypes -- + * make changes in both places if possible */ Scholar.ItemTypes = new function(){ var _types = new Array(); @@ -2408,6 +2906,162 @@ Scholar.ItemTypes = new function(){ + +/* + * Same structure as Scholar.ItemTypes and CreatorTypes -- + * make changes in both places if possible + */ +Scholar.FileTypes = new function(){ + var _types = new Array(); + var _typesLoaded; + var self = this; + + this.getName = getName; + this.getID = getID; + this.getIDFromMIMEType = getIDFromMIMEType; + this.getTypes = getTypes; + + + function getName(idOrName){ + if (!_typesLoaded){ + _load(); + } + + if (!_types[idOrName]){ + Scholar.debug('Invalid file type ' + idOrName, 1); + } + + return _types[idOrName]['name']; + } + + + function getID(idOrName){ + if (!_typesLoaded){ + _load(); + } + + if (!_types[idOrName]){ + Scholar.debug('Invalid file type ' + idOrName, 1); + } + + return _types[idOrName]['id']; + } + + + function getIDFromMIMEType(mimeType){ + // TODO + } + + + function getTypes(){ + return Scholar.DB.query('SELECT fileTypeID AS id, ' + + 'fileType AS name FROM fileTypes order BY fileType'); + } + + + function _load(){ + var types = self.getTypes(); + + for (i in types){ + // Store as both id and name for access by either + var typeData = { + id: types[i]['id'], + name: types[i]['name'] + } + _types[types[i]['id']] = typeData; + _types[types[i]['name']] = _types[types[i]['id']]; + } + + _typesLoaded = true; + } +} + + + +/* + * Same structure as Scholar.ItemTypes, CreatorTypes, etc. -- + * make changes in all versions if possible + */ +Scholar.CharacterSets = new function(){ + var _types = new Array(); + var _typesLoaded; + var self = this; + + var _typeDesc = 'character set'; + var _idCol = 'charsetID'; + var _nameCol = 'charset'; + var _table = 'charsets'; + var _ignoreCase = true; + + this.getName = getName; + this.getID = getID; + this.getTypes = getTypes; + this.getAll = getAll; + + function getName(idOrName){ + if (!_typesLoaded){ + _load(); + } + + if (_ignoreCase){ + idOrName = idOrName.toLowerCase(); + } + + if (!_types[idOrName]){ + Scholar.debug('Invalid ' + _typeDesc + ' ' + idOrName, 1); + } + + return _types[idOrName]['name']; + } + + + function getID(idOrName){ + if (!_typesLoaded){ + _load(); + } + + if (_ignoreCase){ + idOrName = idOrName.toLowerCase(); + } + + if (!_types[idOrName]){ + Scholar.debug('Invalid ' + _typeDesc + ' ' + idOrName, 1); + } + + return _types[idOrName]['id']; + } + + + function getTypes(){ + return Scholar.DB.query('SELECT ' + _idCol + ' AS id, ' + + _nameCol + ' AS name FROM ' + _table + ' order BY ' + _nameCol); + } + + + function getAll(){ + return this.getTypes(); + } + + + function _load(){ + var types = self.getTypes(); + + for (i in types){ + // Store as both id and name for access by either + var typeData = { + id: types[i]['id'], + name: _ignoreCase ? types[i]['name'].toLowerCase() : types[i]['name'] + } + _types[types[i]['id']] = typeData; + _types[types[i]['name']] = _types[types[i]['id']]; + } + + _typesLoaded = true; + } +} + + + Scholar.ItemFields = new function(){ // Private members var _fields = new Array(); diff --git a/chrome/chromeFiles/content/scholar/xpcom/schema.js b/chrome/chromeFiles/content/scholar/xpcom/schema.js index b3393d22f..aa074e49c 100644 --- a/chrome/chromeFiles/content/scholar/xpcom/schema.js +++ b/chrome/chromeFiles/content/scholar/xpcom/schema.js @@ -236,7 +236,8 @@ Scholar.Schema = new function(){ Scholar.DB.commitTransaction(); } catch(e){ - alert(e); + Scholar.debug(e, 1); + alert('Error initializing Scholar database'); // TODO: localize Scholar.DB.rollbackTransaction(); } } @@ -390,7 +391,7 @@ Scholar.Schema = new function(){ // // Change this value to match the schema version // - var toVersion = 29; + var toVersion = 30; if (toVersion != _getSchemaSQLVersion()){ throw('Schema version does not match version in _migrateSchema()'); @@ -405,10 +406,18 @@ Scholar.Schema = new function(){ // Each block performs the changes necessary to move from the // previous revision to that one. for (var i=fromVersion + 1; i<=toVersion; i++){ - if (i==29){ + if (i==30){ Scholar.DB.query("DROP TABLE IF EXISTS keywords"); Scholar.DB.query("DROP TABLE IF EXISTS itemKeywords"); Scholar.DB.query("DROP TABLE IF EXISTS scrapers"); + + // Remove old SQLite DB + var file = Scholar.getProfileDirectory(); + file.append('scholar.sqlite'); + if (file.exists()){ + file.remove(null); + } + _initializeSchema(); } } diff --git a/chrome/chromeFiles/locale/en-US/scholar/scholar.properties b/chrome/chromeFiles/locale/en-US/scholar/scholar.properties index 1a961943c..78ab9534d 100644 --- a/chrome/chromeFiles/locale/en-US/scholar/scholar.properties +++ b/chrome/chromeFiles/locale/en-US/scholar/scholar.properties @@ -64,6 +64,7 @@ itemTypes.interview = Interview itemTypes.film = Film itemTypes.artwork = Artwork itemTypes.website = Website +itemTypes.file = File creatorTypes.author = Author creatorTypes.contributor = Contributor diff --git a/schema.sql b/schema.sql index 9795f50b5..357f70758 100644 --- a/schema.sql +++ b/schema.sql @@ -1,4 +1,4 @@ --- 29 +-- 30 DROP TABLE IF EXISTS version; CREATE TABLE version ( @@ -66,11 +66,56 @@ sourceItemID INT, note TEXT, PRIMARY KEY (itemID), + FOREIGN KEY (itemID) REFERENCES items(itemID), FOREIGN KEY (sourceItemID) REFERENCES items(itemID) ); DROP INDEX IF EXISTS itemNotes_sourceItemID; CREATE INDEX itemNotes_sourceItemID ON itemNotes(sourceItemID); + DROP TABLE IF EXISTS charsets; + CREATE TABLE charsets ( + charsetID INTEGER PRIMARY KEY, + charset TEXT UNIQUE + ); + DROP INDEX IF EXISTS charsets_charset; + CREATE INDEX charsets_charset ON charsets(charset); + + DROP TABLE IF EXISTS fileTypes; + CREATE TABLE fileTypes ( + fileTypeID INTEGER PRIMARY KEY, + fileType TEXT UNIQUE + ); + DROP INDEX IF EXISTS fileTypes_fileType; + CREATE INDEX fileTypes_fileType ON fileTypes(fileType); + + DROP TABLE IF EXISTS fileTypeMimeTypes; + CREATE TABLE fileTypeMimeTypes ( + fileTypeID, + mimeType, + PRIMARY KEY (fileTypeID, mimeType), + FOREIGN KEY (fileTypeID) REFERENCES fileTypes(fileTypeID) + ); + DROP INDEX IF EXISTS fileTypeMimeTypes_mimeType; + CREATE INDEX fileTypeMimeTypes_mimeType ON fileTypeMimeTypes(mimeType); + + DROP TABLE IF EXISTS itemFiles; + CREATE TABLE itemFiles ( + itemID INT, + sourceItemID INT, + linkMode INT, + mimeType TEXT, + charsetID INT, + path TEXT, + originalPath TEXT, + PRIMARY KEY (itemID), + FOREIGN KEY (itemID) REFERENCES items(itemID), + FOREIGN KEY (sourceItemID) REFERENCES items(sourceItemID) + ); + DROP INDEX IF EXISTS itemFiles_sourceItemID; + CREATE INDEX itemFiles_sourceItemID ON itemFiles(sourceItemID); + DROP INDEX IF EXISTS itemFiles_mimeType; + CREATE INDEX itemFiles_mimeType ON itemFiles(mimeType); + DROP TABLE IF EXISTS tags; CREATE TABLE tags ( tagID INT, @@ -201,6 +246,7 @@ INSERT INTO itemTypes VALUES (11,'film'); INSERT INTO itemTypes VALUES (12,'artwork'); INSERT INTO itemTypes VALUES (13,'website'); + INSERT INTO itemTypes VALUES (14,'file'); INSERT INTO "fieldFormats" VALUES(1, '.*', 0); INSERT INTO "fieldFormats" VALUES(2, '[0-9]*', 1); @@ -342,6 +388,175 @@ INSERT INTO "itemTypeFields" VALUES(7, 22, 9); INSERT INTO "itemTypeFields" VALUES(13, 22, 5); + INSERT INTO "charsets" VALUES(1, 'utf-8'); + INSERT INTO "charsets" VALUES(2, 'ascii'); + INSERT INTO "charsets" VALUES(3, 'windows-1250'); + INSERT INTO "charsets" VALUES(4, 'windows-1251'); + INSERT INTO "charsets" VALUES(5, 'windows-1252'); + INSERT INTO "charsets" VALUES(6, 'windows-1253'); + INSERT INTO "charsets" VALUES(7, 'windows-1254'); + INSERT INTO "charsets" VALUES(8, 'windows-1257'); + INSERT INTO "charsets" VALUES(9, 'us'); + INSERT INTO "charsets" VALUES(10, 'us-ascii'); + INSERT INTO "charsets" VALUES(11, 'utf-7'); + INSERT INTO "charsets" VALUES(12, 'iso8859-1'); + INSERT INTO "charsets" VALUES(13, 'iso8859-15'); + INSERT INTO "charsets" VALUES(14, 'iso_646.irv:1991'); + INSERT INTO "charsets" VALUES(15, 'iso_8859-1'); + INSERT INTO "charsets" VALUES(16, 'iso_8859-1:1987'); + INSERT INTO "charsets" VALUES(17, 'iso_8859-2'); + INSERT INTO "charsets" VALUES(18, 'iso_8859-2:1987'); + INSERT INTO "charsets" VALUES(19, 'iso_8859-4'); + INSERT INTO "charsets" VALUES(20, 'iso_8859-4:1988'); + INSERT INTO "charsets" VALUES(21, 'iso_8859-5'); + INSERT INTO "charsets" VALUES(22, 'iso_8859-5:1988'); + INSERT INTO "charsets" VALUES(23, 'iso_8859-7'); + INSERT INTO "charsets" VALUES(24, 'iso_8859-7:1987'); + INSERT INTO "charsets" VALUES(25, 'iso-8859-1'); + INSERT INTO "charsets" VALUES(26, 'iso-8859-1-windows-3.0-latin-1'); + INSERT INTO "charsets" VALUES(27, 'iso-8859-1-windows-3.1-latin-1'); + INSERT INTO "charsets" VALUES(28, 'iso-8859-15'); + INSERT INTO "charsets" VALUES(29, 'iso-8859-2'); + INSERT INTO "charsets" VALUES(30, 'iso-8859-2-windows-latin-2'); + INSERT INTO "charsets" VALUES(31, 'iso-8859-3'); + INSERT INTO "charsets" VALUES(32, 'iso-8859-4'); + INSERT INTO "charsets" VALUES(33, 'iso-8859-5'); + INSERT INTO "charsets" VALUES(34, 'iso-8859-5-windows-latin-5'); + INSERT INTO "charsets" VALUES(35, 'iso-8859-6'); + INSERT INTO "charsets" VALUES(36, 'iso-8859-7'); + INSERT INTO "charsets" VALUES(37, 'iso-8859-8'); + INSERT INTO "charsets" VALUES(38, 'iso-8859-9'); + INSERT INTO "charsets" VALUES(39, 'l1'); + INSERT INTO "charsets" VALUES(40, 'l2'); + INSERT INTO "charsets" VALUES(41, 'l4'); + INSERT INTO "charsets" VALUES(42, 'latin1'); + INSERT INTO "charsets" VALUES(43, 'latin2'); + INSERT INTO "charsets" VALUES(44, 'latin4'); + INSERT INTO "charsets" VALUES(45, 'x-mac-ce'); + INSERT INTO "charsets" VALUES(46, 'x-mac-cyrillic'); + INSERT INTO "charsets" VALUES(47, 'x-mac-greek'); + INSERT INTO "charsets" VALUES(48, 'x-mac-roman'); + INSERT INTO "charsets" VALUES(49, 'x-mac-turkish'); + INSERT INTO "charsets" VALUES(50, 'adobe-symbol-encoding'); + INSERT INTO "charsets" VALUES(51, 'ansi_x3.4-1968'); + INSERT INTO "charsets" VALUES(52, 'ansi_x3.4-1986'); + INSERT INTO "charsets" VALUES(53, 'big5'); + INSERT INTO "charsets" VALUES(54, 'chinese'); + INSERT INTO "charsets" VALUES(55, 'cn-big5'); + INSERT INTO "charsets" VALUES(56, 'cn-gb'); + INSERT INTO "charsets" VALUES(57, 'cn-gb-isoir165'); + INSERT INTO "charsets" VALUES(58, 'cp367'); + INSERT INTO "charsets" VALUES(59, 'cp819'); + INSERT INTO "charsets" VALUES(60, 'cp850'); + INSERT INTO "charsets" VALUES(61, 'cp852'); + INSERT INTO "charsets" VALUES(62, 'cp855'); + INSERT INTO "charsets" VALUES(63, 'cp857'); + INSERT INTO "charsets" VALUES(64, 'cp862'); + INSERT INTO "charsets" VALUES(65, 'cp864'); + INSERT INTO "charsets" VALUES(66, 'cp866'); + INSERT INTO "charsets" VALUES(67, 'csascii'); + INSERT INTO "charsets" VALUES(68, 'csbig5'); + INSERT INTO "charsets" VALUES(69, 'cseuckr'); + INSERT INTO "charsets" VALUES(70, 'cseucpkdfmtjapanese'); + INSERT INTO "charsets" VALUES(71, 'csgb2312'); + INSERT INTO "charsets" VALUES(72, 'cshalfwidthkatakana'); + INSERT INTO "charsets" VALUES(73, 'cshppsmath'); + INSERT INTO "charsets" VALUES(74, 'csiso103t618bit'); + INSERT INTO "charsets" VALUES(75, 'csiso159jisx02121990'); + INSERT INTO "charsets" VALUES(76, 'csiso2022jp'); + INSERT INTO "charsets" VALUES(77, 'csiso2022jp2'); + INSERT INTO "charsets" VALUES(78, 'csiso2022kr'); + INSERT INTO "charsets" VALUES(79, 'csiso58gb231280'); + INSERT INTO "charsets" VALUES(80, 'csisolatin4'); + INSERT INTO "charsets" VALUES(81, 'csisolatincyrillic'); + INSERT INTO "charsets" VALUES(82, 'csisolatingreek'); + INSERT INTO "charsets" VALUES(83, 'cskoi8r'); + INSERT INTO "charsets" VALUES(84, 'csksc56011987'); + INSERT INTO "charsets" VALUES(85, 'csshiftjis'); + INSERT INTO "charsets" VALUES(86, 'csunicode11'); + INSERT INTO "charsets" VALUES(87, 'csunicode11utf7'); + INSERT INTO "charsets" VALUES(88, 'csunicodeascii'); + INSERT INTO "charsets" VALUES(89, 'csunicodelatin1'); + INSERT INTO "charsets" VALUES(90, 'cswindows31latin5'); + INSERT INTO "charsets" VALUES(91, 'cyrillic'); + INSERT INTO "charsets" VALUES(92, 'ecma-118'); + INSERT INTO "charsets" VALUES(93, 'elot_928'); + INSERT INTO "charsets" VALUES(94, 'euc-jp'); + INSERT INTO "charsets" VALUES(95, 'euc-kr'); + INSERT INTO "charsets" VALUES(96, 'extended_unix_code_packed_format_for_japanese'); + INSERT INTO "charsets" VALUES(97, 'gb2312'); + INSERT INTO "charsets" VALUES(98, 'gb_2312-80'); + INSERT INTO "charsets" VALUES(99, 'greek'); + INSERT INTO "charsets" VALUES(100, 'greek8'); + INSERT INTO "charsets" VALUES(101, 'hz-gb-2312'); + INSERT INTO "charsets" VALUES(102, 'ibm367'); + INSERT INTO "charsets" VALUES(103, 'ibm819'); + INSERT INTO "charsets" VALUES(104, 'ibm850'); + INSERT INTO "charsets" VALUES(105, 'ibm852'); + INSERT INTO "charsets" VALUES(106, 'ibm855'); + INSERT INTO "charsets" VALUES(107, 'ibm857'); + INSERT INTO "charsets" VALUES(108, 'ibm862'); + INSERT INTO "charsets" VALUES(109, 'ibm864'); + INSERT INTO "charsets" VALUES(110, 'ibm866'); + INSERT INTO "charsets" VALUES(111, 'iso-10646'); + INSERT INTO "charsets" VALUES(112, 'iso-10646-j-1'); + INSERT INTO "charsets" VALUES(113, 'iso-10646-ucs-2'); + INSERT INTO "charsets" VALUES(114, 'iso-10646-ucs-4'); + INSERT INTO "charsets" VALUES(115, 'iso-10646-ucs-basic'); + INSERT INTO "charsets" VALUES(116, 'iso-10646-unicode-latin1'); + INSERT INTO "charsets" VALUES(117, 'iso-2022-jp'); + INSERT INTO "charsets" VALUES(118, 'iso-2022-jp-2'); + INSERT INTO "charsets" VALUES(119, 'iso-2022-kr'); + INSERT INTO "charsets" VALUES(120, 'iso-ir-100'); + INSERT INTO "charsets" VALUES(121, 'iso-ir-101'); + INSERT INTO "charsets" VALUES(122, 'iso-ir-103'); + INSERT INTO "charsets" VALUES(123, 'iso-ir-110'); + INSERT INTO "charsets" VALUES(124, 'iso-ir-126'); + INSERT INTO "charsets" VALUES(125, 'iso-ir-144'); + INSERT INTO "charsets" VALUES(126, 'iso-ir-149'); + INSERT INTO "charsets" VALUES(127, 'iso-ir-159'); + INSERT INTO "charsets" VALUES(128, 'iso-ir-58'); + INSERT INTO "charsets" VALUES(129, 'iso-ir-6'); + INSERT INTO "charsets" VALUES(130, 'iso646-us'); + INSERT INTO "charsets" VALUES(131, 'jis_x0201'); + INSERT INTO "charsets" VALUES(132, 'jis_x0208-1983'); + INSERT INTO "charsets" VALUES(133, 'jis_x0212-1990'); + INSERT INTO "charsets" VALUES(134, 'koi8-r'); + INSERT INTO "charsets" VALUES(135, 'korean'); + INSERT INTO "charsets" VALUES(136, 'ks_c_5601'); + INSERT INTO "charsets" VALUES(137, 'ks_c_5601-1987'); + INSERT INTO "charsets" VALUES(138, 'ks_c_5601-1989'); + INSERT INTO "charsets" VALUES(139, 'ksc5601'); + INSERT INTO "charsets" VALUES(140, 'ksc_5601'); + INSERT INTO "charsets" VALUES(141, 'ms_kanji'); + INSERT INTO "charsets" VALUES(142, 'shift_jis'); + INSERT INTO "charsets" VALUES(143, 't.61'); + INSERT INTO "charsets" VALUES(144, 't.61-8bit'); + INSERT INTO "charsets" VALUES(145, 'unicode-1-1-utf-7'); + INSERT INTO "charsets" VALUES(146, 'unicode-1-1-utf-8'); + INSERT INTO "charsets" VALUES(147, 'unicode-2-0-utf-7'); + INSERT INTO "charsets" VALUES(148, 'windows-31j'); + INSERT INTO "charsets" VALUES(149, 'x-cns11643-1'); + INSERT INTO "charsets" VALUES(150, 'x-cns11643-1110'); + INSERT INTO "charsets" VALUES(151, 'x-cns11643-2'); + INSERT INTO "charsets" VALUES(152, 'x-cp1250'); + INSERT INTO "charsets" VALUES(153, 'x-cp1251'); + INSERT INTO "charsets" VALUES(154, 'x-cp1253'); + INSERT INTO "charsets" VALUES(155, 'x-dectech'); + INSERT INTO "charsets" VALUES(156, 'x-dingbats'); + INSERT INTO "charsets" VALUES(157, 'x-euc-jp'); + INSERT INTO "charsets" VALUES(158, 'x-euc-tw'); + INSERT INTO "charsets" VALUES(159, 'x-gb2312-11'); + INSERT INTO "charsets" VALUES(160, 'x-imap4-modified-utf7'); + INSERT INTO "charsets" VALUES(161, 'x-jisx0208-11'); + INSERT INTO "charsets" VALUES(162, 'x-ksc5601-11'); + INSERT INTO "charsets" VALUES(163, 'x-sjis'); + INSERT INTO "charsets" VALUES(164, 'x-tis620'); + INSERT INTO "charsets" VALUES(165, 'x-unicode-2-0-utf-7'); + INSERT INTO "charsets" VALUES(166, 'x-x-big5'); + INSERT INTO "charsets" VALUES(167, 'x0201'); + INSERT INTO "charsets" VALUES(168, 'x0212'); + -- Some sample data INSERT INTO "items" VALUES(1, 2, 'Online connections: Internet interpersonal relationships', '2006-03-12 05:24:40', '2006-03-12 05:24:40'); INSERT INTO "items" VALUES(2, 2, 'Computer-Mediated Communication: Human-to-Human Communication Across the Internet', '2006-03-12 05:25:50', '2006-03-12 05:25:50'); @@ -411,6 +626,15 @@ INSERT INTO "itemNotes" VALUES(16, 1, 'This item is note-worthy.'); INSERT INTO "itemNotes" VALUES(17, NULL, 'This is an independent note.'); + INSERT INTO "fileTypes" VALUES(1, 'link'); + INSERT INTO "fileTypes" VALUES(2, 'snapshot'); + INSERT INTO "fileTypes" VALUES(3, 'image'); + INSERT INTO "fileTypes" VALUES(4, 'pdf'); + INSERT INTO "fileTypes" VALUES(5, 'audio'); + INSERT INTO "fileTypes" VALUES(6, 'video'); + INSERT INTO "fileTypes" VALUES(7, 'document'); + INSERT INTO "fileTypes" VALUES(8, 'presentation'); + INSERT INTO collections VALUES (1241, 'Test Project', NULL); INSERT INTO collections VALUES (3262, 'Another Test Project', NULL); INSERT INTO collections VALUES (6856, 'Yet Another Project', NULL);