All pastes #124616 Raw Edit

Someone

public text v1 · immutable
·published 2006-08-10 04:21 UTC
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;
}