rendered paste body// Bookmark Backup 0.4.1
// By Pike
// Contributor(s):
// Nickolay Ponomarev (Multiple file backup code)
function BackupService()
{
this._init();
}
BackupService.prototype = {
_STRINGS_URL: 'chrome://bookmarkbackup/locale/bookmarkbackup.properties',
_CREATE_PERMISSIONS: 0700,
performBackup: function()
{
// Create logger
this._logger = new BackupServiceLogger();
this._logger.log('Backup begun');
try
{
// Init vars
var profileDir = this._dirService.get('ProfD', Components.interfaces.nsILocalFile);
var stringBundleService = Components.classes['@mozilla.org/intl/stringbundle;1'].getService(Components.interfaces.nsIStringBundleService);
var localeStrings = stringBundleService.createBundle(this._STRINGS_URL);
// Find bookmarks file and backups dir
var bookmarksFile = this._getBookmarksFile();
var backupsDirPath = localeStrings.GetStringFromName('backupsFolderName');
var dayNameDirPath = localeStrings.GetStringFromName('dayOfWeek.' + (new Date()).getDay());
var backupsDir = this._getBackupsDir(backupsDirPath, dayNameDirPath, bookmarksFile);
if (backupsDir)
{
// Get names of additional files to backup
var backupFileNames = [];
try
{
var backupList = this._prefBranch.getComplexValue('bookmarkbackup.backupList', Components.interfaces.nsISupportsString).data;
if (backupList)
{
backupFileNames = backupList.split('|');
}
}
catch (err) {}
// Backup bookmarks file
if (bookmarksFile)
{
this._copyFile(bookmarksFile.parent, backupsDir.clone(), [bookmarksFile.leafName]);
}
// Backup additional files
for (var i = 0; i < backupFileNames.length; i++)
{
if (backupFileNames[i])
{
var fileSections = backupFileNames[i].split('/');
this._copyFile(profileDir.clone(), backupsDir.clone(), fileSections);
}
}
this._logger.log('Backup completed');
}
}
catch (err)
{
if (err instanceof BackupFolderNotCreated)
{
this._logger.log('Error: Today\'s backup dir could not be created at: ' + err.path);
}
else
{
this._logger.log('Error: Backup aborted due to:\n' + err);
}
}
this._logger.addBlankLine();
this._logger = null;
},
_getBookmarksFile: function()
{
var bookmarksFile;
// Check if the bookmarks file is in a custom location (the BMarks directory service key
// does *not* take this pref into account)
if (this._prefBranch.prefHasUserValue('browser.bookmarks.file'))
{
try
{
bookmarksFile = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
bookmarksFile.initWithPath(this._prefBranch.getCharPref('browser.bookmarks.file'));
}
catch (err)
{
this._logger.log('Warning: Custom bookmarks location (browser.bookmarks.file) is invalid');
bookmarksFile = null;
}
}
if (!bookmarksFile)
{
try
{
bookmarksFile = this._dirService.get('BMarks', Components.interfaces.nsILocalFile);
}
catch (err) {}
}
return bookmarksFile;
},
_getStandardBackupsDir: function(backupsDirName, dayNameDirPath, bookmarksFile)
{
var standardBackupsDir;
if (bookmarksFile) // Use directory in same place as bookmarks.html
{
standardBackupsDir = bookmarksFile.parent;
}
else // Use directory in profile
{
standardBackupsDir = this._dirService.get('ProfD', Components.interfaces.nsILocalFile);
}
standardBackupsDir.append(backupsDirName);
standardBackupsDir.append(dayNameDirPath);
return standardBackupsDir;
},
_getCustomBackupsDir: function(dayNameDirPath)
{
var customBackupsDir;
try
{
customBackupsDir = this._prefBranch.getComplexValue('bookmarkbackup.location', Components.interfaces.nsILocalFile);
customBackupsDir.append(dayNameDirPath);
}
catch (err)
{
customBackupsDir = null;
}
return customBackupsDir;
},
_getBackupsDir: function(backupsDirName, dayNameDirPath, bookmarksFile)
{
var backupsDir;
// Read pref that determines whether to delete any existing dir
var deletePrevious = false;
try
{
deletePrevious = this._prefBranch.getBoolPref('bookmarkbackup.deletePrevious');
}
catch (err) {}
// If a custom location was set, try to get it
if (this._prefBranch.prefHasUserValue('bookmarkbackup.location'))
{
backupsDir = this._getCustomBackupsDir(dayNameDirPath);
// Create backup dir if it doesn't exist yet
if (backupsDir)
{
if (deletePrevious && backupsDir.exists())
{
backupsDir.remove(true);
this._logger.log('Today\'s backup dir deleted at: ' + backupsDir.path);
}
if (!backupsDir.exists())
{
try
{
backupsDir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, this._CREATE_PERMISSIONS);
this._logger.log('Today\'s backup dir created at: ' + backupsDir.path);
}
catch (err)
{
backupsDir = null;
}
}
else
{
this._logger.log('Today\'s backup dir found at: ' + backupsDir.path);
}
}
// Custom dir could not be used, reset to standard location instead
if (!backupsDir)
{
this._logger.log('Warning: Custom backup location was invalid, resetting to default location (usually the user profile)');
this._prefBranch.clearUserPref('bookmarkbackup.location');
}
}
// If no custom location was set, or custom location was invalid, get standard location
if (!backupsDir)
{
backupsDir = this._getStandardBackupsDir(backupsDirName, dayNameDirPath, bookmarksFile);
// Create backup dir if it doesn't exist yet
if (backupsDir)
{
if (deletePrevious && backupsDir.exists())
{
backupsDir.remove(true);
this._logger.log('Today\'s backup dir deleted at: ' + backupsDir.path);
}
if (!backupsDir.exists())
{
try
{
backupsDir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, this._CREATE_PERMISSIONS);
this._logger.log('Today\'s backup dir created at: ' + backupsDir.path);
}
catch (err)
{
throw new BackupFolderNotCreated(backupsDir.path);
}
}
else
{
this._logger.log('Today\'s backup dir found at: ' + backupsDir.path);
}
}
}
return backupsDir;
},
_copyFile: function(fromFileOrDir, toFileOrDir, relPath)
// Perform recursive backup. An alternative would be using nsIFile.remove(true) on the existing
// backup, but this function is safer as (in theory) it should only remove files that are about
// to be overwritten, parameter types are: (nsIFile, nsIFile, string array)
{
if (fromFileOrDir.exists())
{
if (relPath.length > 0)
{
var pathSection = relPath.shift();
var wildcard = /^\*\.(\w*)$/i.exec(pathSection);
if (wildcard)
{
this._copyChildren(fromFileOrDir, toFileOrDir, relPath, wildcard[1]);
}
else
{
fromFileOrDir.append(pathSection);
toFileOrDir.append(pathSection);
this._copyFile(fromFileOrDir, toFileOrDir, relPath);
}
}
else if (fromFileOrDir.isDirectory())
{
this._copyChildren(fromFileOrDir, toFileOrDir, relPath, null);
}
else
{
try
{
if (toFileOrDir.exists())
{
toFileOrDir.remove(false);
}
fromFileOrDir.copyTo(toFileOrDir.parent, toFileOrDir.leafName);
this._logger.log(toFileOrDir.leafName + ' successfully copied');
}
catch (err)
{
this._logger.log('Warning: ' + fromFileOrDir.path + ' could not be copied to ' + toFileOrDir.path);
}
}
}
else
{
this._logger.log('Warning: ' + fromFileOrDir.path + ' could not be found');
}
},
_copyChildren: function(fromDir, toDir, relPath, fileExt)
{
// Check fromDir exists and is a directory, DO NOT check toDir since it may not exist yet
if (fromDir.exists() && fromDir.isDirectory())
{
var pattern = null;
if (fileExt)
{
pattern = new RegExp('\\.' + fileExt + '$');
}
var dirEnum = fromDir.directoryEntries;
while (dirEnum.hasMoreElements())
{
var fromChild = dirEnum.getNext().QueryInterface(Components.interfaces.nsIFile);
if (!pattern || fromChild.leafName.match(pattern))
{
var toChild = toDir.clone();
toChild.append(fromChild.leafName);
this._copyFile(fromChild, toChild, relPath);
}
}
}
},
_init: function()
{
// Setup object properties
this._observerService = Components.classes['@mozilla.org/observer-service;1'].getService(Components.interfaces.nsIObserverService);
this._dirService = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties);
this._prefBranch = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefBranch);
this._timerHandle = Components.classes['@mozilla.org/timer;1'].createInstance(Components.interfaces.nsITimer);
// Add observers
this._observerService.addObserver(this, 'profile-after-change', false);
this._observerService.addObserver(this, 'profile-before-change', false);
this._observerService.addObserver(this, 'xpcom-shutdown', false);
},
_destruct: function()
{
// Remove observers
this._observerService.removeObserver(this, 'profile-after-change');
this._observerService.removeObserver(this, 'profile-before-change');
this._observerService.removeObserver(this, 'xpcom-shutdown');
// Clear object properties
this._observerService = null;
this._dirService = null;
this._prefBranch = null;
this._timerHandle = null;
},
_onLoad: function()
{
// Perform initial backup
var startupBackup = false;
try
{
startupBackup = this._prefBranch.getBoolPref('bookmarkbackup.startupBackup');
}
catch (err) {}
if (startupBackup)
{
this.performBackup();
}
// Start cyclic backup
var backupInterval = 0;
try
{
backupInterval = this._prefBranch.getIntPref('bookmarkbackup.interval');
}
catch (err) {}
if (backupInterval > 0) // Setting the pref to 0 disables the cyclic backup
{
backupInterval *= 60000; // Convert from minutes to milliseconds
this._timerHandle.init(this, backupInterval, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
}
},
_onUnload: function()
{
// Stop cyclic backup
this._timerHandle.cancel();
// Perform final backup
var shutdownBackup = true;
try
{
shutdownBackup = this._prefBranch.getBoolPref('bookmarkbackup.shutdownBackup');
}
catch (err) {}
if (shutdownBackup)
{
this.performBackup();
}
},
observe: function(subject, topic, data)
{
if (topic == 'profile-after-change')
{
this._onLoad();
}
else if (topic == 'profile-before-change')
{
this._onUnload();
}
else if (topic == 'xpcom-shutdown')
{
this._destruct();
}
else if (topic == 'timer-callback' && subject == this._timerHandle)
{
this.performBackup();
}
},
QueryInterface: function(iid)
{
if (iid.equals(Components.interfaces.iBackupService) ||
iid.equals(Components.interfaces.nsIObserver) ||
iid.equals(Components.interfaces.nsISupports))
{
return this;
}
else
{
throw Components.results.NS_ERROR_NO_INTERFACE;
}
}
};
function BackupFolderNotCreated(path)
{
this.path = path;
}
function BackupServiceLogger()
{
this._init();
}
BackupServiceLogger.prototype = {
_CREATE_PERMISSIONS: 0600,
_FILE_FLAGS: 0x02 | 0x08 | 0x10, // WRONLY | CREATE_FILE | APPEND
log: function(message)
{
this._writeOutput((new Date().toLocaleString()) + ': ' + message + '\n');
},
addBlankLine: function()
{
this._writeOutput('\n');
},
_writeOutput: function(output)
{
if (this._logFile)
{
try
{
var fileOutputStream = Components.classes['@mozilla.org/network/file-output-stream;1'].createInstance(Components.interfaces.nsIFileOutputStream);
fileOutputStream.init(this._logFile, this._FILE_FLAGS, this._CREATE_PERMISSIONS, 0);
fileOutputStream.write(output, output.length);
fileOutputStream.close();
}
catch (err) {}
}
},
_init: function()
{
var prefBranch = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefBranch);
try
{
if (prefBranch.getBoolPref('bookmarkbackup.loggingEnabled'))
{
var dirService = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties);
this._logFile = dirService.get('ProfD', Components.interfaces.nsILocalFile);
this._logFile.append('bookmarkbackup.log');
}
}
catch (err) {}
}
};
var BackupServiceFactory = {
createInstance: function(outer, iid)
{
if (outer)
{
throw Components.results.NS_ERROR_NO_AGGREGATION;
}
return (new BackupService()).QueryInterface(iid);
}
};
var BackupServiceModule = {
_NAME: 'BackupService@pikey.me.uk',
_CID: Components.ID('{451b1443-ed41-11d9-920f-00055d74c2e4}'),
_CONTRACT_ID: '@pikey.me.uk/backup-service;1',
registerSelf: function(compMgr, fileSpec, location, type)
{
compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
compMgr.registerFactoryLocation(this._CID, this._NAME, this._CONTRACT_ID, fileSpec, location, type);
var categoryManager = Components.classes['@mozilla.org/categorymanager;1'].getService(Components.interfaces.nsICategoryManager);
categoryManager.addCategoryEntry('app-startup', this._NAME, 'service,' + this._CONTRACT_ID, true, true);
},
unregisterSelf: function(compMgr, fileSpec, location)
{
compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
compMgr.unregisterFactoryLocation(this._CID, fileSpec);
var categoryManager = Components.classes['@mozilla.org/categorymanager;1'].getService(Components.interfaces.nsICategoryManager);
categoryManager.deleteCategoryEntry('app-startup', this._NAME, true);
},
getClassObject: function(compMgr, cid, iid)
{
if (!iid.equals(Components.interfaces.nsIFactory))
{
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
if (cid.equals(this._CID))
{
return BackupServiceFactory;
}
else
{
throw Components.results.NS_ERROR_NO_INTERFACE;
}
},
canUnload: function(compMgr)
{
return true;
}
};
function NSGetModule(compMgr, fileSpec)
{
return BackupServiceModule;
}