Mercurial > mozilla > hg > ProfileManager
view profilemanager/manager.py @ 55:3b53d584195f
dont print None
author | Jeff Hammel <jhammel@mozilla.com> |
---|---|
date | Fri, 07 May 2010 11:35:40 -0700 |
parents | f3812255ce6e |
children | 2dde7dcc899e |
line wrap: on
line source
""" manage Mozilla/Firefox profiles """ import os import shutil import string import time import ConfigParser from random import Random from utils import format_tabular from ConfigParser import SafeConfigParser as ConfigParser # Changes to profiles.ini: # - add a ``backups`` field for each profile: # Backups = /path/to/backup:1273104310 /path/to/other/backup:1273104355 class ProfileNotFound(Exception): """ exception when a profile is specified but is not present in a given .ini file """ def __init__(self, profile, config): self.profile = profile self.config = config Exception.__init__('Profile %s not found in %s' % (profile, config)) class ProfileManager(object): backups_dir = 'backups' # directory for backups relative to profile_dir def __init__(self, profiles): """ - profiles: filesystem path to profiles.ini file """ self.profiles = profiles self.profile_dir = os.path.abspath(os.path.dirname(profiles)) ### (public) API def new(self, name, directory=None, hash=True): """ generate a new clean profile - name: name of the profile to generate - directory: where to create the profile [DEFAULT: relative to profiles.ini] - hash: whether to generate a a random hash tag """ # path to the profile directory dirname = name relative = False if hash: dirname = '%s.%s' % (self.hash(), dirname) if not directory: directory = self.profile_dir relative = True path = os.path.join(directory, dirname) # create directory # TODO: (optionally) pre-populate the directory a la Firefox os.mkdir(path) # update profiles.ini self.add(name, relative and dirname or path, relative) # return the directory name return path def remove(self, name, delete=True): """ remove a profile from profiles.ini - delete: delete the profile directory as well """ parser = self.parser() section = self.section(name, parser) if section is None: raise ProfileNotFound(profile, self.profiles) if delete: # remove the profile from disk shutil.rmtree(self.path(name)) parser.remove_section(section) self.write(parser) def list(self, directories=False): """ lists the profiles available in the config file - directories : display the directories """ profiles = self.profiles_dict() if not directories: return sorted(profiles.keys()) return dict([(name, self.path(name)) for name in profiles.keys()]) def clone(self, source, dest, add=True): """ clones the profile `source` and output to `dest` - add: add the profile to the profiles.ini file """ # filesystem path of the `from` profile source_path = self.path(source) # dest: fs path to back up to relative = False if os.path.sep in dest: if not os.path.isabs(dest): dest = os.path.abspath(dest) dirname = dest name = os.path.basename(dest) else: name = dest relative = True if add: dirname = '%s.%s' % (self.hash(), name) dest = os.path.join(self.profile_dir, dirname) # update profiles.ini if add: self.add(name, dirname, relative) # copy the files shutil.copytree(source_path, dest, symlinks=False) return dest def backup(self, profile, dest=None): """ backup the profile - profile: name of the profile to be backed up - dest: name of the destination (optional) """ # get the profile section parser = self.parser() section = self.section(profile) if profile is None: raise ProfileNotFound(profile, self.profiles) # determine destination directory if dest is None: dest = '%s.%d.bak' % (profile, int(time.time())) name = dest backups_dir = os.path.join(self.profile_dir, self.backups_dir) if not os.path.exists(backups_dir): os.mkdir(backups_dir) dest = os.path.join(backups_dir, dest) else: if not os.path.isabs(dest): dest = os.path.abspath(dest) name = dest # copy the files self.clone(profile, dest, add=False) # add backup entry to profiles.ini (colon separated): # `Backup=$(profile)s.$(datestamp)s.bak` if parser.has_option(section, 'Backups'): backups = '%s:%s' % (parser.get(section, 'Backups'), name) else: backups = name parser.set(section, 'Backups', backups) self.write(parser) def backups(self, profile=None, datestamp=None): """ list backups for a given profile, or all profiles if the profile is not given; returns a list of backups if profile is given or a dictionary of lists otherwise - datestamp: format of date; otherwise, seconds since epoch """ if profile is None: # all profiles # XXX this should probably be recursive retval = {} profiles_dict = self.profiles_dict() for profile in profiles_dict: if 'Backups' in profiles_dict[profile]: retval[profile] = profiles_dict[profile].split(':') return retval # single profile profile_dict = self.profile_dict(profile) if 'Backups' not in profile_dict: return [] backups = profile_dict['Backups'].split(':') backup_dirs = [] for backup in backups: if os.path.isabs(backup): backup_dir = backup else: backup_dir = os.path.join(self.profile_dir, self.backups_dir, backup) backups_dirs.append((backup, int(os.path.getmtime(backup_dir)))) # TODO: check if the getmtime == the datestamp for relative backups # sort by reverse datestamp backups_dirs.sort(key=lambda x: x[1], reverse=True) # format to datestamps, if applicable if datestamp: backup_dirs = [ (i[0], time.strftime(datestamp, time.localtime(int(i[1])))) for i in backup_dirs ] return backups_dirs def restore(self, profile, date=None, delete=False): """ restore the profile from a backup the most recent backup is used unless `date` is given - date : date to restore from - delete : delete the backup after restoration """ # get the possible backups backups = self.backups(profile) # TODO: check to see if these all exist (print warnings if not) # restore the backup over ``profile`` if delete: # delete the backup # delete the directory # delete the entry from ``profiles.ini`` # if there are no backups, delete the ``backups`` line pass #TODO def temporary(self): """make a temporary profile""" raise NotImplementedError def merge(self, output, *profiles): """merge a set of profiles (not trivial!)""" raise NotImplementedError ### internal functions def add(self, profile, path, relative=True): """ add a profile entry to profiles.ini """ # ensure name is not already present assert profile not in self.profiles_dict(), 'Profile "%s" already in %s' % (name, self.profiles) parser = self.parser() # find and add the section ctr = 0 section = 'Profile%d' % ctr # unsure of this naming convention while section in parser.sections(): ctr += 1 section = 'Profile%d' % ctr parser.add_section(section) # add metadata parser.set(section, 'Name', profile) parser.set(section, 'IsRelative', '%d' % int(relative)) parser.set(section, 'Path', path) if not ctr: parser.set(section, 'Default', '1') # write the file self.write(parser) def path(self, profile): """returns the path to the profile""" profile = self.profile_dict(profile) if profile.get('isrelative', None) == '1': return os.path.join(self.profile_dir, profile['path']) return profile['path'] def parser(self): """ return a ConfigParser instance appropriate to profiles.ini """ parser = ConfigParser() parser.read(self.profiles) return parser def write(self, parser): f = file(self.profiles, 'w') parser.write(f) f.close() def section(self, profile, parser=None): """ returns the name of the section that a profile is in or None if not found """ if parser is None: parser = self.parser() for section in parser.sections(): if not parser.has_option(section, 'name'): continue # not a profile if parser.get(section, 'name') == profile: return section def profile_dict(self, profile): """ return option dictionary for a single profile """ parser = self.parser() section = self.section(profile, parser) if section is None: raise ProfileNotFound(profile, self.profiles) return dict(parser.items(section)) def profiles_dict(self): """ return nested dict of all profiles """ # assumes profiles have unique names parser = self.parser() retval = {} for section in parser.sections(): if section == 'General': continue try: name = parser.get(section, 'name') except ConfigParser.NoOptionError: continue retval[name] = self.profile_dict(name) return retval def hash(self): """ generate a random hash for a new profile """ population = string.lowercase + string.digits return ''.join(Random().sample(population, 8))