From: <luc...@us...> - 2003-07-13 09:04:18
|
Update of /cvsroot/zxsync/zXSync/zxsync In directory sc8-pr-cvs1:/tmp/cvs-serv29461 Added Files: backup.py Log Message: - Added automatic backups --- NEW FILE: backup.py --- #!/usr/bin/env python # # zXSync backup management. # # Copyright (C) 2003 Andreas Junghans <aj...@lu...> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: backup.py,v 1.1 2003/07/13 09:04:15 lucidcake Exp $ # """ This module is tightly coupled with engine.py, and nothing from engine.py that is used here should be considered a stable API. """ from cPickle import dump from cPickle import load import os import os.path import re import string import sys import time # compile regular expressions for finding existing backup files preSyncRE = re.compile("\\A\\d{8}-\\d{6}-\\d{4}-pre-sync.data\\Z") postSyncRE = re.compile("\\A\\d{8}-\\d{6}-\\d{4}-post-sync.data\\Z") # determine the base path to use for the operating system basePath = "." # TODO: use a better default if sys.platform[:6] == "darwin": basePath = os.path.expanduser("~/Library/Application Support/zXSync/backup") def createBackup(pluginId, deviceId, app, postSync): """ Creates a backup for the specified application. The directory where backups are stored depends on the operating system. Currently, only Darwin/Mac OS X is supported with a directory of "~/Library/Application Support/zXSync/backup". The structure of the backup directory looks as follows: backup | +-- plugin id | +-- device id | +-- app id | +-- 20030629-135027-0000-pre-sync.data | +-- 20030629-135228-0000-post-sync.data As you can see, backups are created before and after each sync. The pre-sync backups are made whether or not the sync is successful. A post-sync backup with an identical number as an existing pre-sync backup is made after a successful sync. The "postSync" parameter determines whether this is a pre- or post-sync backup. If "postSync" is true, it's a post-sync backup. Only the last 10 pre and post .data files for an application are kept. Everything else is deleted when making a backup. In the future, this number should be made configurable. """ if postSync: prefix = "post" filterRE = postSyncRE else: prefix = "pre" filterRE = preSyncRE appId = app.getId() # determine the backup path for the application appPath = os.path.join(basePath, pluginId, deviceId, appId) if not os.path.exists(appPath): os.makedirs(appPath, 0700) # get a list of entries in that path and mask out everything # that's irrelevant for zXSync unFilteredEntries = os.listdir(appPath) backups = filter(lambda x: len(filterRE.findall(x)) == 1, unFilteredEntries) backups.sort() # delete old backups removeCount = len(backups) - 10 + 1 # +1 because we make a new backup later on for index in range(removeCount): os.remove(os.path.join(appPath, backups[0])) del backups[0] # determine the file name to use backupName = time.strftime("%Y%m%d-%H%M%S") backupNumber = 0 for index in range(len(backups)-1, -1, -1): check = backups[index][:15] if check < backupName: break # note: this code assumes that there may be backup names # for earlier and _later_ times (e.g. when changing the # system clock) if check == backupName: backupNumber = int(backups[index][16:20]) + 1 if backupNumber >= 0000: backupNumber = 0 # delete all previous backups to prevent # sorting problems - this is not really # supposed to happen (10000 backups per second!) for delIndex in range(index, -1, -1): check = backups[delIndex][:15] if check == backupName: os.remove(os.path.join(appPath, backups[delIndex])) del backups[delIndex] else: break break backupName += "-" + string.zfill(backupNumber, 4) + "-" + prefix + "-sync.data" # actually make the backup backupFile = open(os.path.join(appPath, backupName), "wb") try: dump(app, backupFile, 1) finally: backupFile.close() def createBackups(deviceInfos, appToSync, postSync): """ Creates backups for all deviceInfo objects in the specified sequence (but only for the specified application). """ if postSync: prefix = "post" filterRE = postSyncRE else: prefix = "pre" filterRE = preSyncRE for deviceInfo in deviceInfos: # determine the intermediate path components pluginId = deviceInfo.plugin.__name__ deviceId = deviceInfo.device.getId() # make backups of all apps for app in deviceInfo.data: if app.getId() == appToSync: createBackup(pluginId, deviceId, app, postSync) def loadBackup(pluginId, deviceId, appId, postSync): """ Loads the latest available backup for the specified plugin, device, and application. If no backup is found, None is returned. """ if postSync: prefix = "post" filterRE = postSyncRE else: prefix = "pre" filterRE = preSyncRE # determine the backup path for the application appPath = os.path.join(basePath, pluginId, deviceId, appId) if not os.path.exists(appPath): return None # determine the latest backup file unFilteredEntries = os.listdir(appPath) backups = filter(lambda x: len(filterRE.findall(x)) == 1, unFilteredEntries) backups.sort() if len(backups) == 0: return None # load the backup backupFile = open(os.path.join(appPath, backups[-1]), "rb") try: retVal = load(backupFile) finally: backupFile.close() return retVal def loadBackups(deviceInfos, appToSync, postSync): """ Loads the latest available backups for all applications in the specified list of device infos (but only for the specified application). The return value is a dictionary with a (plugin id, device id) tuple as keys and lists of Application instances as values. """ if postSync: prefix = "post" filterRE = postSyncRE else: prefix = "pre" filterRE = preSyncRE retVal = {} for deviceInfo in deviceInfos: # determine the intermediate path components pluginId = deviceInfo.plugin.__name__ deviceId = deviceInfo.device.getId() appList = [] # make backups of all apps for app in deviceInfo.data: appId = app.getId() if appId != appToSync: continue # try to load the backup backup = loadBackup(pluginId, deviceId, appId, postSync) if backup != None: appList.append(backup) # append the app list to the dictionary if len(appList) > 0: retVal[(pluginId, deviceId)] = appList return retVal |