From 83a01a48bf5944ac511e5b62b31a722e73c1b6d8 Mon Sep 17 00:00:00 2001 From: robweber Date: Mon, 29 Jul 2019 16:58:00 -0500 Subject: [PATCH] split backup and restore into separate functions --- default.py | 156 ++--- resources/lib/backup.py | 1242 ++++++++++++++++++++------------------- scheduler.py | 392 ++++++------ 3 files changed, 903 insertions(+), 887 deletions(-) diff --git a/default.py b/default.py index 7a3f43d..870dbff 100644 --- a/default.py +++ b/default.py @@ -1,77 +1,79 @@ -import urlparse -import xbmcgui -import resources.lib.utils as utils -from resources.lib.backup import XbmcBackup - -def get_params(): - param = {} - - if(len(sys.argv) > 1): - for i in sys.argv: - args = i - if(args.startswith('?')): - args = args[1:] - param.update(dict(urlparse.parse_qsl(args))) - - return param - -#the program mode -mode = -1 -params = get_params() - - -if("mode" in params): - if(params['mode'] == 'backup'): - mode = 0 - elif(params['mode'] == 'restore'): - mode = 1 - -#if mode wasn't passed in as arg, get from user -if(mode == -1): - #figure out if this is a backup or a restore from the user - mode = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30023),[utils.getString(30016),utils.getString(30017),utils.getString(30099)]) - -#check if program should be run -if(mode != -1): - #run the profile backup - backup = XbmcBackup() - - if(mode == 2): - #open the settings dialog - utils.openSettings() - - elif(backup.remoteConfigured()): - - if(mode == backup.Restore): - #get list of valid restore points - restorePoints = backup.listBackups() - pointNames = [] - folderNames = [] - - for aDir in restorePoints: - pointNames.append(aDir[1]) - folderNames.append(aDir[0]) - - selectedRestore = -1 - - if("archive" in params): - #check that the user give archive exists - if(params['archive'] in folderNames): - #set the index - selectedRestore = folderNames.index(params['archive']) - utils.log(str(selectedRestore) + " : " + params['archive']) - else: - utils.showNotification(utils.getString(30045)) - utils.log(params['archive'] + ' is not a valid restore point') - else: - #allow user to select the backup to restore from - selectedRestore = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30021),pointNames) - - if(selectedRestore != -1): - backup.selectRestore(restorePoints[selectedRestore][0]) - - backup.run(mode) - else: - #can't go any further - xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045)) - utils.openSettings() +import sys, urlparse +import xbmcgui +import resources.lib.utils as utils +from resources.lib.backup import XbmcBackup + +def get_params(): + param = {} + + if(len(sys.argv) > 1): + for i in sys.argv: + args = i + if(args.startswith('?')): + args = args[1:] + param.update(dict(urlparse.parse_qsl(args))) + + return param + +#the program mode +mode = -1 +params = get_params() + + +if("mode" in params): + if(params['mode'] == 'backup'): + mode = 0 + elif(params['mode'] == 'restore'): + mode = 1 + +#if mode wasn't passed in as arg, get from user +if(mode == -1): + #figure out if this is a backup or a restore from the user + mode = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30023),[utils.getString(30016),utils.getString(30017),utils.getString(30099)]) + +#check if program should be run +if(mode != -1): + #run the profile backup + backup = XbmcBackup() + + if(mode == 2): + #open the settings dialog + utils.openSettings() + + elif(backup.remoteConfigured()): + + if(mode == backup.Restore): + #get list of valid restore points + restorePoints = backup.listBackups() + pointNames = [] + folderNames = [] + + for aDir in restorePoints: + pointNames.append(aDir[1]) + folderNames.append(aDir[0]) + + selectedRestore = -1 + + if("archive" in params): + #check that the user give archive exists + if(params['archive'] in folderNames): + #set the index + selectedRestore = folderNames.index(params['archive']) + utils.log(str(selectedRestore) + " : " + params['archive']) + else: + utils.showNotification(utils.getString(30045)) + utils.log(params['archive'] + ' is not a valid restore point') + else: + #allow user to select the backup to restore from + selectedRestore = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30021),pointNames) + + if(selectedRestore != -1): + backup.selectRestore(restorePoints[selectedRestore][0]) + + backup.restore() + else: + backup.backup() + else: + #can't go any further + xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045)) + utils.openSettings() diff --git a/resources/lib/backup.py b/resources/lib/backup.py index c4c87c2..962e7cc 100644 --- a/resources/lib/backup.py +++ b/resources/lib/backup.py @@ -1,614 +1,628 @@ -import xbmc -import xbmcgui -import xbmcvfs -import utils as utils -import time -import json -import re -from vfs import XBMCFileSystem,DropboxFileSystem,ZipFileSystem,GoogleDriveFilesystem -from progressbar import BackupProgressBar -from resources.lib.guisettings import GuiSettingsManager -from resources.lib.extractor import ZipExtractor - -def folderSort(aKey): - result = aKey[0] - - if(len(result) < 8): - result = result + "0000" - - return result - - -class XbmcBackup: - #constants for initiating a back or restore - Backup = 0 - Restore = 1 - - #list of dirs for the "simple" file selection - simple_directory_list = ['addons','addon_data','database','playlists','profiles','thumbnails','config'] - - #file systems - xbmc_vfs = None - remote_vfs = None - saved_remote_vfs = None - - restoreFile = None - remote_base_path = None - - #for the progress bar - progressBar = None - filesLeft = 0 - filesTotal = 1 - - restore_point = None - skip_advanced = False #if we should check for the existance of advancedsettings in the restore - - def __init__(self): - self.xbmc_vfs = XBMCFileSystem(xbmc.translatePath('special://home')) - - self.configureRemote() - utils.log(utils.getString(30046)) - - def configureRemote(self): - if(utils.getSetting('remote_selection') == '1'): - self.remote_base_path = utils.getSetting('remote_path_2'); - self.remote_vfs = XBMCFileSystem(utils.getSetting('remote_path_2')) - utils.setSetting("remote_path","") - elif(utils.getSetting('remote_selection') == '0'): - self.remote_base_path = utils.getSetting('remote_path'); - self.remote_vfs = XBMCFileSystem(utils.getSetting("remote_path")) - elif(utils.getSetting('remote_selection') == '2'): - self.remote_base_path = "/" - self.remote_vfs = DropboxFileSystem("/") - elif(utils.getSetting('remote_selection') == '3'): - self.remote_base_path = '/Kodi Backup/' - self.remote_vfs = GoogleDriveFilesystem('/Kodi Backup/') - - def remoteConfigured(self): - result = True - - if(self.remote_base_path == ""): - result = False - - return result - - def listBackups(self): - result = [] - - #get all the folders in the current root path - dirs,files = self.remote_vfs.listdir(self.remote_base_path) - - for aDir in dirs: - if(self.remote_vfs.exists(self.remote_base_path + aDir + "/xbmcbackup.val")): - - #folder may or may not contain time, older versions didn't include this - folderName = '' - if(len(aDir) > 8): - folderName = aDir[6:8] + '-' + aDir[4:6] + '-' + aDir[0:4] + " " + aDir[8:10] + ":" + aDir[10:12] - else: - folderName = aDir[6:8] + '-' + aDir[4:6] + '-' + aDir[0:4] - - result.append((aDir,folderName)) - - for aFile in files: - file_ext = aFile.split('.')[-1] - folderName = utils.encode(aFile.split('.')[0]) - - if(file_ext == 'zip' and (len(folderName) == 12 or len(folderName) == 8) and str.isdigit(folderName)): - - #folder may or may not contain time, older versions didn't include this - if(len(aFile ) > 8): - folderName = aFile [6:8] + '-' + aFile [4:6] + '-' + aFile [0:4] + " " + aFile [8:10] + ":" + aFile [10:12] - else: - folderName = aFile [6:8] + '-' + aFile [4:6] + '-' + aFile [0:4] - - result.append((aFile ,folderName)) - - - result.sort(key=folderSort) - - return result - - def selectRestore(self,restore_point): - self.restore_point = restore_point - - def skipAdvanced(self): - self.skip_advanced = True - - def run(self,mode=-1,progressOverride=False): - #set windows setting to true - window = xbmcgui.Window(10000) - window.setProperty(utils.__addon_id__ + ".running","true") - - #append backup folder name - progressBarTitle = utils.getString(30010) + " - " - if(mode == self.Backup and self.remote_vfs.root_path != ''): - if(utils.getSetting("compress_backups") == 'true'): - #delete old temp file - if(self.xbmc_vfs.exists(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))): - if(not self.xbmc_vfs.rmfile(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))): - #we had some kind of error deleting the old file - xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30096),utils.getString(30097)) - return - - #save the remote file system and use the zip vfs - self.saved_remote_vfs = self.remote_vfs - self.remote_vfs = ZipFileSystem(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"),"w") - - self.remote_vfs.set_root(self.remote_vfs.root_path + time.strftime("%Y%m%d%H%M") + "/") - progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30016) - elif(mode == self.Restore and self.restore_point != None and self.remote_vfs.root_path != ''): - if(self.restore_point.split('.')[-1] != 'zip'): - self.remote_vfs.set_root(self.remote_vfs.root_path + self.restore_point + "/") - progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30017) - else: - #kill the program here - self.remote_vfs = None - return - - utils.log(utils.getString(30047) + ": " + self.xbmc_vfs.root_path) - utils.log(utils.getString(30048) + ": " + self.remote_vfs.root_path) - - - #setup the progress bar - self.progressBar = BackupProgressBar(progressOverride) - self.progressBar.create(progressBarTitle,utils.getString(30049) + "......") - - if(mode == self.Backup): - utils.log(utils.getString(30023) + " - " + utils.getString(30016)) - #check if remote path exists - if(self.remote_vfs.exists(self.remote_vfs.root_path)): - #may be data in here already - utils.log(utils.getString(30050)) - else: - #make the remote directory - self.remote_vfs.mkdir(self.remote_vfs.root_path) - - utils.log(utils.getString(30051)) - utils.log('File Selection Type: ' + str(utils.getSetting('backup_selection_type'))) - allFiles = [] - - if(int(utils.getSetting('backup_selection_type')) == 0): - #read in a list of the directories to backup - selectedDirs = self._readBackupConfig(utils.addon_dir() + "/resources/data/default_files.json") - - #simple mode - get file listings for all enabled directories - for aDir in self.simple_directory_list: - #if this dir enabled - if(utils.getSetting('backup_' + aDir) == 'true'): - #get a file listing and append it to the allfiles array - allFiles.append(self._addBackupDir(aDir,selectedDirs[aDir]['root'],selectedDirs[aDir]['dirs'])) - else: - #advanced mode - load custom paths - selectedDirs = self._readBackupConfig(utils.data_dir() + "/custom_paths.json") - - #get the set names - keys = selectedDirs.keys() - - #go through the custom sets - for aKey in keys: - #get the set - aSet = selectedDirs[aKey] - - #get file listing and append - allFiles.append(self._addBackupDir(aKey,aSet['root'],aSet['dirs'])) - - #create a validation file for backup rotation - writeCheck = self._createValidationFile(allFiles) - - if(not writeCheck): - #we may not be able to write to this destination for some reason - shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30089),utils.getString(30090), utils.getString(30044),autoclose=25000) - - if(not shouldContinue): - return - - orig_base_path = self.remote_vfs.root_path - - #backup all the files - self.filesLeft = self.filesTotal - for fileGroup in allFiles: - self.xbmc_vfs.set_root(xbmc.translatePath(fileGroup['source'])) - self.remote_vfs.set_root(fileGroup['dest'] + fileGroup['name']) - filesCopied = self.backupFiles(fileGroup['files'],self.xbmc_vfs,self.remote_vfs) - - if(not filesCopied): - utils.showNotification(utils.getString(30092)) - utils.log(utils.getString(30092)) - - #reset remote and xbmc vfs - self.xbmc_vfs.set_root("special://home/") - self.remote_vfs.set_root(orig_base_path) - - if(utils.getSetting("compress_backups") == 'true'): - fileManager = FileManager(self.xbmc_vfs) - - #send the zip file to the real remote vfs - zip_name = self.remote_vfs.root_path[:-1] + ".zip" - self.remote_vfs.cleanup() - self.xbmc_vfs.rename(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"), xbmc.translatePath("special://temp/" + zip_name)) - fileManager.addFile(xbmc.translatePath("special://temp/" + zip_name)) - - #set root to data dir home - self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/")) - - self.remote_vfs = self.saved_remote_vfs - self.progressBar.updateProgress(98, utils.getString(30088)) - fileCopied = self.backupFiles(fileManager.getFiles(),self.xbmc_vfs, self.remote_vfs) - - if(not fileCopied): - #zip archive copy filed, inform the user - shouldContinue = xbmcgui.Dialog().ok(utils.getString(30089),utils.getString(30090), utils.getString(30091)) - - #delete the temp zip file - self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + zip_name)) - - #remove old backups - self._rotateBackups() - - elif (mode == self.Restore): - utils.log(utils.getString(30023) + " - " + utils.getString(30017)) - - #catch for if the restore point is actually a zip file - if(self.restore_point.split('.')[-1] == 'zip'): - self.progressBar.updateProgress(2, utils.getString(30088)) - utils.log("copying zip file: " + self.restore_point) - - #set root to data dir home - self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/")) - - if(not self.xbmc_vfs.exists(xbmc.translatePath("special://temp/" + self.restore_point))): - #copy just this file from the remote vfs - zipFile = [] - zipFile.append(self.remote_base_path + self.restore_point) - - self.backupFiles(zipFile,self.remote_vfs, self.xbmc_vfs) - else: - utils.log("zip file exists already") - - #extract the zip file - zip_vfs = ZipFileSystem(xbmc.translatePath("special://temp/"+ self.restore_point),'r') - extractor = ZipExtractor() - - if(not extractor.extract(zip_vfs, xbmc.translatePath("special://temp/"), self.progressBar)): - #we had a problem extracting the archive, delete everything - zip_vfs.cleanup() - self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point)) - - xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30101)) - return - - zip_vfs.cleanup() - - self.progressBar.updateProgress(0,utils.getString(30049) + "......") - #set the new remote vfs and fix xbmc path - self.remote_vfs = XBMCFileSystem(xbmc.translatePath("special://temp/" + self.restore_point.split(".")[0] + "/")) - self.xbmc_vfs.set_root(xbmc.translatePath("special://home/")) - - #for restores remote path must exist - if(not self.remote_vfs.exists(self.remote_vfs.root_path)): - xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path) - return - - valFile = self._checkValidationFile(self.remote_vfs.root_path) - if(valFile == None): - #don't continue - return - - utils.log(utils.getString(30051)) - allFiles = [] - fileManager = FileManager(self.remote_vfs) - - #check for the existance of an advancedsettings file - if(self.remote_vfs.exists(self.remote_vfs.root_path + "config/advancedsettings.xml") and not self.skip_advanced): - #let the user know there is an advanced settings file present - restartXbmc = xbmcgui.Dialog().yesno(utils.getString(30038),utils.getString(30039),utils.getString(30040), utils.getString(30041)) - - if(restartXbmc): - #add only this file to the file list - fileManager.addFile(self.remote_vfs.root_path + "config/advancedsettings.xml") - self.backupFiles(fileManager.getFiles(),self.remote_vfs,self.xbmc_vfs) - - #let the service know to resume this backup on startup - self._createResumeBackupFile() - - #do not continue running - xbmcgui.Dialog().ok(utils.getString(30077),utils.getString(30078)) - return - - #use a multiselect dialog to select sets to restore - restoreSets = [n['name'] for n in valFile['directories']] - selectedSets = xbmcgui.Dialog().multiselect(utils.getString(30131),restoreSets) - - if(selectedSets != None): - #go through each of the directories in the backup and write them to the correct location - for index in selectedSets: - - #add this directory - aDir = valFile['directories'][index] - - self.xbmc_vfs.set_root(xbmc.translatePath(aDir['path'])) - if(self.remote_vfs.exists(self.remote_vfs.root_path + aDir['name'] + '/')): - #walk the directory - fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/') - self.filesTotal = self.filesTotal + fileManager.size() - allFiles.append({"source":self.remote_vfs.root_path + aDir['name'],"dest":self.xbmc_vfs.root_path,"files":fileManager.getFiles()}) - else: - utils.log("error path not found: " + self.remote_vfs.root_path + aDir['name']) - xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path + aDir['name']) - - #restore all the files - self.filesLeft = self.filesTotal - for fileGroup in allFiles: - self.remote_vfs.set_root(fileGroup['source']) - self.xbmc_vfs.set_root(fileGroup['dest']) - self.backupFiles(fileGroup['files'],self.remote_vfs,self.xbmc_vfs) - - self.progressBar.updateProgress(99,"Clean up operations .....") - - if(self.restore_point.split('.')[-1] == 'zip'): - #delete the zip file and the extracted directory - self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point)) - self.xbmc_vfs.rmdir(self.remote_vfs.root_path) - - if(utils.getSetting("backup_config") == "true"): - #update the guisettings information (or what we can from it) - gui_settings = GuiSettingsManager('special://home/userdata/guisettings.xml') - gui_settings.run() - - #call update addons to refresh everything - xbmc.executebuiltin('UpdateLocalAddons') - - self.xbmc_vfs.cleanup() - self.remote_vfs.cleanup() - self.progressBar.close() - - #reset the window setting - window.setProperty(utils.__addon_id__ + ".running","") - - def backupFiles(self,fileList,source,dest): - result = True - - utils.log("Source: " + source.root_path) - utils.log("Desintation: " + dest.root_path) - for aFile in fileList: - if(not self.progressBar.checkCancel()): - utils.log('Writing file: ' + aFile,xbmc.LOGDEBUG) - if(aFile.startswith("-")): - self._updateProgress(aFile[len(source.root_path) + 1:]) - dest.mkdir(dest.root_path + aFile[len(source.root_path) + 1:]) - else: - self._updateProgress() - wroteFile = True - if(isinstance(source,DropboxFileSystem) or isinstance(source,GoogleDriveFilesystem)): - #if copying from cloud storage we need the file handle, use get_file - wroteFile = source.get_file(aFile,dest.root_path + aFile[len(source.root_path):]) - else: - #copy using normal method - wroteFile = dest.put(aFile,dest.root_path + aFile[len(source.root_path):]) - - #if result is still true but this file failed - if(not wroteFile and result): - result = False - - - return result - - def _addBackupDir(self,folder_name,root_path,dirList): - utils.log('Backup set: ' + folder_name) - fileManager = FileManager(self.xbmc_vfs) - - self.xbmc_vfs.set_root(xbmc.translatePath(root_path)) - for aDir in dirList: - fileManager.addDir(aDir) - - #walk all the root trees - fileManager.walk() - #update total files - self.filesTotal = self.filesTotal + fileManager.size() - - return {"name":folder_name,"source":root_path,"dest":self.remote_vfs.root_path,"files":fileManager.getFiles()} - - - def _createCRC(self,string): - #create hash from string - string = string.lower() - bytes = bytearray(string.encode()) - crc = 0xffffffff; - for b in bytes: - crc = crc ^ (b << 24) - for i in range(8): - if (crc & 0x80000000 ): - crc = (crc << 1) ^ 0x04C11DB7 - else: - crc = crc << 1; - crc = crc & 0xFFFFFFFF - - return '%08x' % crc - - def _updateProgress(self,message=None): - self.filesLeft = self.filesLeft - 1 - self.progressBar.updateProgress(int((float(self.filesTotal - self.filesLeft)/float(self.filesTotal)) * 100),message) - - def _rotateBackups(self): - total_backups = int(utils.getSetting('backup_rotation')) - - if(total_backups > 0): - #get a list of valid backup folders - dirs = self.listBackups() - - if(len(dirs) > total_backups): - #remove backups to equal total wanted - remove_num = 0 - self.filesTotal = self.filesTotal + remove_num + 1 - - #update the progress bar if it is available - while(remove_num < (len(dirs) - total_backups) and not self.progressBar.checkCancel()): - self._updateProgress(utils.getString(30054) + " " + dirs[remove_num][1]) - utils.log("Removing backup " + dirs[remove_num][0]) - - if(dirs[remove_num][0].split('.')[-1] == 'zip'): - #this is a file, remove it that way - self.remote_vfs.rmfile(self.remote_base_path + dirs[remove_num][0]) - else: - self.remote_vfs.rmdir(self.remote_base_path + dirs[remove_num][0] + "/") - - remove_num = remove_num + 1 - - def _createValidationFile(self,dirList): - valInfo = {"name":"XBMC Backup Validation File","xbmc_version":xbmc.getInfoLabel('System.BuildVersion'),"type":0} - valDirs = [] - - for aDir in dirList: - valDirs.append({"name":aDir['name'],"path":aDir['source']}) - valInfo['directories'] = valDirs - - vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"),'w') - vFile.write(json.dumps(valInfo)) - vFile.write("") - vFile.close() - - success = self.remote_vfs.put(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"),self.remote_vfs.root_path + "xbmcbackup.val") - - #remove the validation file - xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val")) - - if(success): - #android requires a .nomedia file to not index the directory as media - if(not xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + ".nomedia"))): - nmFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + ".nomedia"),'w') - nmFile.close() - - success = self.remote_vfs.put(xbmc.translatePath(utils.data_dir() + ".nomedia"),self.remote_vfs.root_path + ".nomedia") - - return success - - def _checkValidationFile(self,path): - result = None - - #copy the file and open it - self.xbmc_vfs.put(path + "xbmcbackup.val",xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val")) - - vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"),'r') - jsonString = vFile.read() - vFile.close() - - #delete after checking - xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val")) - - try: - result = json.loads(jsonString) - - if(xbmc.getInfoLabel('System.BuildVersion') != result['xbmc_version']): - shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30085),utils.getString(30086),utils.getString(30044)) - - if(not shouldContinue): - result = None - - except ValueError: - #may fail on older archives - result = None - - return result - - def _createResumeBackupFile(self): - rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"),'w') - rFile.write(self.restore_point) - rFile.close() - - def _readBackupConfig(self,aFile): - jFile = xbmcvfs.File(xbmc.translatePath(aFile),'r') - jsonString = jFile.read() - jFile.close() - return json.loads(jsonString) - -class FileManager: - not_dir = ['.zip','.xsp','.rar'] - exclude_dir = [] - root_dirs = [] - - def __init__(self,vfs): - self.vfs = vfs - self.fileArray = [] - self.exclude_dir = [] - self.root_dirs = [] - - def walk(self): - - for aDir in self.root_dirs: - self.addFile('-' + xbmc.translatePath(aDir['path'])) - self.walkTree(xbmc.translatePath(aDir['path']),aDir['recurse']) - - def walkTree(self,directory,recurse=True): - utils.log('walking ' + directory + ', recurse: ' + str(recurse)) - if(directory[-1:] == '/' or directory[-1:] == '\\'): - directory = directory[:-1] - - if(self.vfs.exists(directory + "/")): - dirs,files = self.vfs.listdir(directory) - - if(recurse): - #create all the subdirs first - for aDir in dirs: - dirPath = xbmc.validatePath(xbmc.translatePath(directory + "/" + aDir)) - file_ext = aDir.split('.')[-1] - - #check if directory is excluded - regex = re.compile(".*(" + aDir + ").*") - excludedCheck = [m.group(0) for l in self.exclude_dir for m in [regex.search(l)] if m] - - if(len(excludedCheck) == 0): - - self.addFile("-" + dirPath) - - #catch for "non directory" type files - shouldWalk = True - - for s in file_ext: - if(s in self.not_dir): - shouldWalk = False - - if(shouldWalk): - self.walkTree(dirPath) - - #copy all the files - for aFile in files: - filePath = xbmc.translatePath(directory + "/" + aFile) - self.addFile(filePath) - - def addDir(self,dirMeta): - if(dirMeta['type'] == 'include'): - self.root_dirs.append({'path':dirMeta['path'],'recurse':dirMeta['recurse']}) - else: - self.excludeFile(xbmc.translatePath(dirMeta['path'])) - - def addFile(self,filename): - try: - filename = filename.decode('UTF-8') - except UnicodeDecodeError: - filename = filename.decode('ISO-8859-2') - - #write the full remote path name of this file - utils.log("Add File: " + filename,xbmc.LOGDEBUG) - self.fileArray.append(filename) - - def excludeFile(self,filename): - try: - filename = filename.decode('UTF-8') - except UnicodeDecodeError: - filename = filename.decode('ISO-8859-2') - - #write the full remote path name of this file - utils.log("Exclude File: " + filename) - self.exclude_dir.append(filename) - - def getFiles(self): - result = self.fileArray - self.fileArray = [] - self.root_dirs = [] - self.exclude_dir = [] - - return result - - def size(self): - return len(self.fileArray) +import xbmc +import xbmcgui +import xbmcvfs +import utils as utils +import time +import json +import re +from vfs import XBMCFileSystem,DropboxFileSystem,ZipFileSystem,GoogleDriveFilesystem +from progressbar import BackupProgressBar +from resources.lib.guisettings import GuiSettingsManager +from resources.lib.extractor import ZipExtractor + +def folderSort(aKey): + result = aKey[0] + + if(len(result) < 8): + result = result + "0000" + + return result + + +class XbmcBackup: + #constants for initiating a back or restore + Backup = 0 + Restore = 1 + + #list of dirs for the "simple" file selection + simple_directory_list = ['addons','addon_data','database','playlists','profiles','thumbnails','config'] + + #file systems + xbmc_vfs = None + remote_vfs = None + saved_remote_vfs = None + + restoreFile = None + remote_base_path = None + + #for the progress bar + progressBar = None + filesLeft = 0 + filesTotal = 1 + + restore_point = None + skip_advanced = False #if we should check for the existance of advancedsettings in the restore + + def __init__(self): + self.xbmc_vfs = XBMCFileSystem(xbmc.translatePath('special://home')) + + self.configureRemote() + utils.log(utils.getString(30046)) + + def configureRemote(self): + if(utils.getSetting('remote_selection') == '1'): + self.remote_base_path = utils.getSetting('remote_path_2'); + self.remote_vfs = XBMCFileSystem(utils.getSetting('remote_path_2')) + utils.setSetting("remote_path","") + elif(utils.getSetting('remote_selection') == '0'): + self.remote_base_path = utils.getSetting('remote_path'); + self.remote_vfs = XBMCFileSystem(utils.getSetting("remote_path")) + elif(utils.getSetting('remote_selection') == '2'): + self.remote_base_path = "/" + self.remote_vfs = DropboxFileSystem("/") + elif(utils.getSetting('remote_selection') == '3'): + self.remote_base_path = '/Kodi Backup/' + self.remote_vfs = GoogleDriveFilesystem('/Kodi Backup/') + + def remoteConfigured(self): + result = True + + if(self.remote_base_path == ""): + result = False + + return result + + def listBackups(self): + result = [] + + #get all the folders in the current root path + dirs,files = self.remote_vfs.listdir(self.remote_base_path) + + for aDir in dirs: + if(self.remote_vfs.exists(self.remote_base_path + aDir + "/xbmcbackup.val")): + + #folder may or may not contain time, older versions didn't include this + folderName = '' + if(len(aDir) > 8): + folderName = aDir[6:8] + '-' + aDir[4:6] + '-' + aDir[0:4] + " " + aDir[8:10] + ":" + aDir[10:12] + else: + folderName = aDir[6:8] + '-' + aDir[4:6] + '-' + aDir[0:4] + + result.append((aDir,folderName)) + + for aFile in files: + file_ext = aFile.split('.')[-1] + folderName = utils.encode(aFile.split('.')[0]) + + if(file_ext == 'zip' and (len(folderName) == 12 or len(folderName) == 8) and str.isdigit(folderName)): + + #folder may or may not contain time, older versions didn't include this + if(len(aFile ) > 8): + folderName = aFile [6:8] + '-' + aFile [4:6] + '-' + aFile [0:4] + " " + aFile [8:10] + ":" + aFile [10:12] + else: + folderName = aFile [6:8] + '-' + aFile [4:6] + '-' + aFile [0:4] + + result.append((aFile ,folderName)) + + + result.sort(key=folderSort) + + return result + + def selectRestore(self,restore_point): + self.restore_point = restore_point + + def skipAdvanced(self): + self.skip_advanced = True + + def backup(self,progressOverride=False): + shouldContinue = self._setupVFS(self.Backup,progressOverride) + + if(shouldContinue): + utils.log(utils.getString(30023) + " - " + utils.getString(30016)) + #check if remote path exists + if(self.remote_vfs.exists(self.remote_vfs.root_path)): + #may be data in here already + utils.log(utils.getString(30050)) + else: + #make the remote directory + self.remote_vfs.mkdir(self.remote_vfs.root_path) + + utils.log(utils.getString(30051)) + utils.log('File Selection Type: ' + str(utils.getSetting('backup_selection_type'))) + allFiles = [] + + if(int(utils.getSetting('backup_selection_type')) == 0): + #read in a list of the directories to backup + selectedDirs = self._readBackupConfig(utils.addon_dir() + "/resources/data/default_files.json") + + #simple mode - get file listings for all enabled directories + for aDir in self.simple_directory_list: + #if this dir enabled + if(utils.getSetting('backup_' + aDir) == 'true'): + #get a file listing and append it to the allfiles array + allFiles.append(self._addBackupDir(aDir,selectedDirs[aDir]['root'],selectedDirs[aDir]['dirs'])) + else: + #advanced mode - load custom paths + selectedDirs = self._readBackupConfig(utils.data_dir() + "/custom_paths.json") + + #get the set names + keys = selectedDirs.keys() + + #go through the custom sets + for aKey in keys: + #get the set + aSet = selectedDirs[aKey] + + #get file listing and append + allFiles.append(self._addBackupDir(aKey,aSet['root'],aSet['dirs'])) + + #create a validation file for backup rotation + writeCheck = self._createValidationFile(allFiles) + + if(not writeCheck): + #we may not be able to write to this destination for some reason + shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30089),utils.getString(30090), utils.getString(30044),autoclose=25000) + + if(not shouldContinue): + return + + orig_base_path = self.remote_vfs.root_path + + #backup all the files + self.filesLeft = self.filesTotal + for fileGroup in allFiles: + self.xbmc_vfs.set_root(xbmc.translatePath(fileGroup['source'])) + self.remote_vfs.set_root(fileGroup['dest'] + fileGroup['name']) + filesCopied = self._copyFiles(fileGroup['files'],self.xbmc_vfs,self.remote_vfs) + + if(not filesCopied): + utils.showNotification(utils.getString(30092)) + utils.log(utils.getString(30092)) + + #reset remote and xbmc vfs + self.xbmc_vfs.set_root("special://home/") + self.remote_vfs.set_root(orig_base_path) + + if(utils.getSetting("compress_backups") == 'true'): + fileManager = FileManager(self.xbmc_vfs) + + #send the zip file to the real remote vfs + zip_name = self.remote_vfs.root_path[:-1] + ".zip" + self.remote_vfs.cleanup() + self.xbmc_vfs.rename(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"), xbmc.translatePath("special://temp/" + zip_name)) + fileManager.addFile(xbmc.translatePath("special://temp/" + zip_name)) + + #set root to data dir home + self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/")) + + self.remote_vfs = self.saved_remote_vfs + self.progressBar.updateProgress(98, utils.getString(30088)) + fileCopied = self._copyFiles(fileManager.getFiles(),self.xbmc_vfs, self.remote_vfs) + + if(not fileCopied): + #zip archive copy filed, inform the user + shouldContinue = xbmcgui.Dialog().ok(utils.getString(30089),utils.getString(30090), utils.getString(30091)) + + #delete the temp zip file + self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + zip_name)) + + #remove old backups + self._rotateBackups() + + #close any files + self._closeVFS() + + def restore(self,progressOverride=False): + shouldContinue = self._setupVFS(self.Restore, progressOverride) + + if(shouldContinue): + utils.log(utils.getString(30023) + " - " + utils.getString(30017)) + + #catch for if the restore point is actually a zip file + if(self.restore_point.split('.')[-1] == 'zip'): + self.progressBar.updateProgress(2, utils.getString(30088)) + utils.log("copying zip file: " + self.restore_point) + + #set root to data dir home + self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/")) + + if(not self.xbmc_vfs.exists(xbmc.translatePath("special://temp/" + self.restore_point))): + #copy just this file from the remote vfs + zipFile = [] + zipFile.append(self.remote_base_path + self.restore_point) + + self._copyFiles(zipFile,self.remote_vfs, self.xbmc_vfs) + else: + utils.log("zip file exists already") + + #extract the zip file + zip_vfs = ZipFileSystem(xbmc.translatePath("special://temp/"+ self.restore_point),'r') + extractor = ZipExtractor() + + if(not extractor.extract(zip_vfs, xbmc.translatePath("special://temp/"), self.progressBar)): + #we had a problem extracting the archive, delete everything + zip_vfs.cleanup() + self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point)) + + xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30101)) + return + + zip_vfs.cleanup() + + self.progressBar.updateProgress(0,utils.getString(30049) + "......") + #set the new remote vfs and fix xbmc path + self.remote_vfs = XBMCFileSystem(xbmc.translatePath("special://temp/" + self.restore_point.split(".")[0] + "/")) + self.xbmc_vfs.set_root(xbmc.translatePath("special://home/")) + + #for restores remote path must exist + if(not self.remote_vfs.exists(self.remote_vfs.root_path)): + xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path) + return + + valFile = self._checkValidationFile(self.remote_vfs.root_path) + if(valFile == None): + #don't continue + return + + utils.log(utils.getString(30051)) + allFiles = [] + fileManager = FileManager(self.remote_vfs) + + #check for the existance of an advancedsettings file + if(self.remote_vfs.exists(self.remote_vfs.root_path + "config/advancedsettings.xml") and not self.skip_advanced): + #let the user know there is an advanced settings file present + restartXbmc = xbmcgui.Dialog().yesno(utils.getString(30038),utils.getString(30039),utils.getString(30040), utils.getString(30041)) + + if(restartXbmc): + #add only this file to the file list + fileManager.addFile(self.remote_vfs.root_path + "config/advancedsettings.xml") + self._copyFiles(fileManager.getFiles(),self.remote_vfs,self.xbmc_vfs) + + #let the service know to resume this backup on startup + self._createResumeBackupFile() + + #do not continue running + xbmcgui.Dialog().ok(utils.getString(30077),utils.getString(30078)) + return + + #use a multiselect dialog to select sets to restore + restoreSets = [n['name'] for n in valFile['directories']] + selectedSets = xbmcgui.Dialog().multiselect(utils.getString(30131),restoreSets) + + if(selectedSets != None): + #go through each of the directories in the backup and write them to the correct location + for index in selectedSets: + + #add this directory + aDir = valFile['directories'][index] + + self.xbmc_vfs.set_root(xbmc.translatePath(aDir['path'])) + if(self.remote_vfs.exists(self.remote_vfs.root_path + aDir['name'] + '/')): + #walk the directory + fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/') + self.filesTotal = self.filesTotal + fileManager.size() + allFiles.append({"source":self.remote_vfs.root_path + aDir['name'],"dest":self.xbmc_vfs.root_path,"files":fileManager.getFiles()}) + else: + utils.log("error path not found: " + self.remote_vfs.root_path + aDir['name']) + xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path + aDir['name']) + + #restore all the files + self.filesLeft = self.filesTotal + for fileGroup in allFiles: + self.remote_vfs.set_root(fileGroup['source']) + self.xbmc_vfs.set_root(fileGroup['dest']) + self._copyFiles(fileGroup['files'],self.remote_vfs,self.xbmc_vfs) + + self.progressBar.updateProgress(99,"Clean up operations .....") + + if(self.restore_point.split('.')[-1] == 'zip'): + #delete the zip file and the extracted directory + self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point)) + self.xbmc_vfs.rmdir(self.remote_vfs.root_path) + + if(utils.getSetting("backup_config") == "true"): + #update the guisettings information (or what we can from it) + gui_settings = GuiSettingsManager('special://home/userdata/guisettings.xml') + gui_settings.run() + + #call update addons to refresh everything + xbmc.executebuiltin('UpdateLocalAddons') + + def _setupVFS(self,mode=-1,progressOverride=False): + #set windows setting to true + window = xbmcgui.Window(10000) + window.setProperty(utils.__addon_id__ + ".running","true") + + #append backup folder name + progressBarTitle = utils.getString(30010) + " - " + if(mode == self.Backup and self.remote_vfs.root_path != ''): + if(utils.getSetting("compress_backups") == 'true'): + #delete old temp file + if(self.xbmc_vfs.exists(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))): + if(not self.xbmc_vfs.rmfile(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))): + #we had some kind of error deleting the old file + xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30096),utils.getString(30097)) + return False + + #save the remote file system and use the zip vfs + self.saved_remote_vfs = self.remote_vfs + self.remote_vfs = ZipFileSystem(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"),"w") + + self.remote_vfs.set_root(self.remote_vfs.root_path + time.strftime("%Y%m%d%H%M") + "/") + progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30016) + elif(mode == self.Restore and self.restore_point != None and self.remote_vfs.root_path != ''): + if(self.restore_point.split('.')[-1] != 'zip'): + self.remote_vfs.set_root(self.remote_vfs.root_path + self.restore_point + "/") + progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30017) + else: + #kill the program here + self.remote_vfs = None + return False + + utils.log(utils.getString(30047) + ": " + self.xbmc_vfs.root_path) + utils.log(utils.getString(30048) + ": " + self.remote_vfs.root_path) + + + #setup the progress bar + self.progressBar = BackupProgressBar(progressOverride) + self.progressBar.create(progressBarTitle,utils.getString(30049) + "......") + + #if we made it this far we're good + return True + + def _closeVFS(self): + self.xbmc_vfs.cleanup() + self.remote_vfs.cleanup() + self.progressBar.close() + + #reset the window setting + window = xbmcgui.Window(10000) + window.setProperty(utils.__addon_id__ + ".running","") + + def _copyFiles(self,fileList,source,dest): + result = True + + utils.log("Source: " + source.root_path) + utils.log("Desintation: " + dest.root_path) + for aFile in fileList: + if(not self.progressBar.checkCancel()): + utils.log('Writing file: ' + aFile,xbmc.LOGDEBUG) + if(aFile.startswith("-")): + self._updateProgress(aFile[len(source.root_path) + 1:]) + dest.mkdir(dest.root_path + aFile[len(source.root_path) + 1:]) + else: + self._updateProgress() + wroteFile = True + if(isinstance(source,DropboxFileSystem) or isinstance(source,GoogleDriveFilesystem)): + #if copying from cloud storage we need the file handle, use get_file + wroteFile = source.get_file(aFile,dest.root_path + aFile[len(source.root_path):]) + else: + #copy using normal method + wroteFile = dest.put(aFile,dest.root_path + aFile[len(source.root_path):]) + + #if result is still true but this file failed + if(not wroteFile and result): + result = False + + + return result + + def _addBackupDir(self,folder_name,root_path,dirList): + utils.log('Backup set: ' + folder_name) + fileManager = FileManager(self.xbmc_vfs) + + self.xbmc_vfs.set_root(xbmc.translatePath(root_path)) + for aDir in dirList: + fileManager.addDir(aDir) + + #walk all the root trees + fileManager.walk() + #update total files + self.filesTotal = self.filesTotal + fileManager.size() + + return {"name":folder_name,"source":root_path,"dest":self.remote_vfs.root_path,"files":fileManager.getFiles()} + + + def _createCRC(self,string): + #create hash from string + string = string.lower() + bytes = bytearray(string.encode()) + crc = 0xffffffff; + for b in bytes: + crc = crc ^ (b << 24) + for i in range(8): + if (crc & 0x80000000 ): + crc = (crc << 1) ^ 0x04C11DB7 + else: + crc = crc << 1; + crc = crc & 0xFFFFFFFF + + return '%08x' % crc + + def _updateProgress(self,message=None): + self.filesLeft = self.filesLeft - 1 + self.progressBar.updateProgress(int((float(self.filesTotal - self.filesLeft)/float(self.filesTotal)) * 100),message) + + def _rotateBackups(self): + total_backups = int(utils.getSetting('backup_rotation')) + + if(total_backups > 0): + #get a list of valid backup folders + dirs = self.listBackups() + + if(len(dirs) > total_backups): + #remove backups to equal total wanted + remove_num = 0 + self.filesTotal = self.filesTotal + remove_num + 1 + + #update the progress bar if it is available + while(remove_num < (len(dirs) - total_backups) and not self.progressBar.checkCancel()): + self._updateProgress(utils.getString(30054) + " " + dirs[remove_num][1]) + utils.log("Removing backup " + dirs[remove_num][0]) + + if(dirs[remove_num][0].split('.')[-1] == 'zip'): + #this is a file, remove it that way + self.remote_vfs.rmfile(self.remote_base_path + dirs[remove_num][0]) + else: + self.remote_vfs.rmdir(self.remote_base_path + dirs[remove_num][0] + "/") + + remove_num = remove_num + 1 + + def _createValidationFile(self,dirList): + valInfo = {"name":"XBMC Backup Validation File","xbmc_version":xbmc.getInfoLabel('System.BuildVersion'),"type":0} + valDirs = [] + + for aDir in dirList: + valDirs.append({"name":aDir['name'],"path":aDir['source']}) + valInfo['directories'] = valDirs + + vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"),'w') + vFile.write(json.dumps(valInfo)) + vFile.write("") + vFile.close() + + success = self.remote_vfs.put(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"),self.remote_vfs.root_path + "xbmcbackup.val") + + #remove the validation file + xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val")) + + if(success): + #android requires a .nomedia file to not index the directory as media + if(not xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + ".nomedia"))): + nmFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + ".nomedia"),'w') + nmFile.close() + + success = self.remote_vfs.put(xbmc.translatePath(utils.data_dir() + ".nomedia"),self.remote_vfs.root_path + ".nomedia") + + return success + + def _checkValidationFile(self,path): + result = None + + #copy the file and open it + self.xbmc_vfs.put(path + "xbmcbackup.val",xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val")) + + vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"),'r') + jsonString = vFile.read() + vFile.close() + + #delete after checking + xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val")) + + try: + result = json.loads(jsonString) + + if(xbmc.getInfoLabel('System.BuildVersion') != result['xbmc_version']): + shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30085),utils.getString(30086),utils.getString(30044)) + + if(not shouldContinue): + result = None + + except ValueError: + #may fail on older archives + result = None + + return result + + def _createResumeBackupFile(self): + rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"),'w') + rFile.write(self.restore_point) + rFile.close() + + def _readBackupConfig(self,aFile): + jFile = xbmcvfs.File(xbmc.translatePath(aFile),'r') + jsonString = jFile.read() + jFile.close() + return json.loads(jsonString) + +class FileManager: + not_dir = ['.zip','.xsp','.rar'] + exclude_dir = [] + root_dirs = [] + + def __init__(self,vfs): + self.vfs = vfs + self.fileArray = [] + self.exclude_dir = [] + self.root_dirs = [] + + def walk(self): + + for aDir in self.root_dirs: + self.addFile('-' + xbmc.translatePath(aDir['path'])) + self.walkTree(xbmc.translatePath(aDir['path']),aDir['recurse']) + + def walkTree(self,directory,recurse=True): + utils.log('walking ' + directory + ', recurse: ' + str(recurse)) + if(directory[-1:] == '/' or directory[-1:] == '\\'): + directory = directory[:-1] + + if(self.vfs.exists(directory + "/")): + dirs,files = self.vfs.listdir(directory) + + if(recurse): + #create all the subdirs first + for aDir in dirs: + dirPath = xbmc.validatePath(xbmc.translatePath(directory + "/" + aDir)) + file_ext = aDir.split('.')[-1] + + #check if directory is excluded + regex = re.compile(".*(" + aDir + ").*") + excludedCheck = [m.group(0) for l in self.exclude_dir for m in [regex.search(l)] if m] + + if(len(excludedCheck) == 0): + + self.addFile("-" + dirPath) + + #catch for "non directory" type files + shouldWalk = True + + for s in file_ext: + if(s in self.not_dir): + shouldWalk = False + + if(shouldWalk): + self.walkTree(dirPath) + + #copy all the files + for aFile in files: + filePath = xbmc.translatePath(directory + "/" + aFile) + self.addFile(filePath) + + def addDir(self,dirMeta): + if(dirMeta['type'] == 'include'): + self.root_dirs.append({'path':dirMeta['path'],'recurse':dirMeta['recurse']}) + else: + self.excludeFile(xbmc.translatePath(dirMeta['path'])) + + def addFile(self,filename): + try: + filename = filename.decode('UTF-8') + except UnicodeDecodeError: + filename = filename.decode('ISO-8859-2') + + #write the full remote path name of this file + utils.log("Add File: " + filename,xbmc.LOGDEBUG) + self.fileArray.append(filename) + + def excludeFile(self,filename): + try: + filename = filename.decode('UTF-8') + except UnicodeDecodeError: + filename = filename.decode('ISO-8859-2') + + #write the full remote path name of this file + utils.log("Exclude File: " + filename) + self.exclude_dir.append(filename) + + def getFiles(self): + result = self.fileArray + self.fileArray = [] + self.root_dirs = [] + self.exclude_dir = [] + + return result + + def size(self): + return len(self.fileArray) diff --git a/scheduler.py b/scheduler.py index d6573c1..fbe2642 100644 --- a/scheduler.py +++ b/scheduler.py @@ -1,196 +1,196 @@ -import xbmc -import xbmcvfs -import xbmcgui -import datetime -import time -import os -import resources.lib.utils as utils -from resources.lib.croniter import croniter -from resources.lib.backup import XbmcBackup - -UPGRADE_INT = 1 #to keep track of any upgrade notifications - -class BackupScheduler: - monitor = None - enabled = "false" - next_run = 0 - next_run_path = None - restore_point = None - - def __init__(self): - self.monitor = UpdateMonitor(update_method = self.settingsChanged) - self.enabled = utils.getSetting("enable_scheduler") - self.next_run_path = xbmc.translatePath(utils.data_dir()) + 'next_run.txt' - - if(self.enabled == "true"): - - nr = 0 - if(xbmcvfs.exists(self.next_run_path)): - - fh = xbmcvfs.File(self.next_run_path) - try: - #check if we saved a run time from the last run - nr = float(fh.read()) - except ValueError: - nr = 0 - - fh.close() - - #if we missed and the user wants to play catch-up - if(0 < nr <= time.time() and utils.getSetting('schedule_miss') == 'true'): - utils.log("scheduled backup was missed, doing it now...") - progress_mode = int(utils.getSetting('progress_mode')) - - if(progress_mode == 0): - progress_mode = 1 # Kodi just started, don't block it with a foreground progress bar - - self.doScheduledBackup(progress_mode) - - self.setup() - - def setup(self): - #scheduler was turned on, find next run time - utils.log("scheduler enabled, finding next run time") - self.findNextRun(time.time()) - - def start(self): - - #display upgrade messages if they exist - if(int(utils.getSetting('upgrade_notes')) < UPGRADE_INT): - xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30132)) - utils.setSetting('upgrade_notes',str(UPGRADE_INT)) - - #check if a backup should be resumed - resumeRestore = self._resumeCheck() - - if(resumeRestore): - restore = XbmcBackup() - restore.selectRestore(self.restore_point) - #skip the advanced settings check - restore.skipAdvanced() - restore.run(XbmcBackup.Restore) - - while(not xbmc.abortRequested): - - if(self.enabled == "true"): - #scheduler is still on - now = time.time() - - if(self.next_run <= now): - progress_mode = int(utils.getSetting('progress_mode')) - self.doScheduledBackup(progress_mode) - - #check if we should shut the computer down - if(utils.getSetting("cron_shutdown") == 'true'): - #wait 10 seconds to make sure all backup processes and files are completed - time.sleep(10) - xbmc.executebuiltin('ShutDown()') - else: - #find the next run time like normal - self.findNextRun(now) - - xbmc.sleep(500) - - #delete monitor to free up memory - del self.monitor - - def doScheduledBackup(self,progress_mode): - if(progress_mode != 2): - utils.showNotification(utils.getString(30053)) - - backup = XbmcBackup() - - if(backup.remoteConfigured()): - - if(int(utils.getSetting('progress_mode')) in [0,1]): - backup.run(XbmcBackup.Backup,True) - else: - backup.run(XbmcBackup.Backup,False) - - #check if this is a "one-off" - if(int(utils.getSetting("schedule_interval")) == 0): - #disable the scheduler after this run - self.enabled = "false" - utils.setSetting('enable_scheduler','false') - else: - utils.showNotification(utils.getString(30045)) - - def findNextRun(self,now): - progress_mode = int(utils.getSetting('progress_mode')) - - #find the cron expression and get the next run time - cron_exp = self.parseSchedule() - - cron_ob = croniter(cron_exp,datetime.datetime.fromtimestamp(now)) - new_run_time = cron_ob.get_next(float) - - if(new_run_time != self.next_run): - self.next_run = new_run_time - utils.log("scheduler will run again on " + datetime.datetime.fromtimestamp(self.next_run).strftime('%m-%d-%Y %H:%M')) - - #write the next time to a file - fh = xbmcvfs.File(self.next_run_path, 'w') - fh.write(str(self.next_run)) - fh.close() - - #only show when not in silent mode - if(progress_mode != 2): - utils.showNotification(utils.getString(30081) + " " + datetime.datetime.fromtimestamp(self.next_run).strftime('%m-%d-%Y %H:%M')) - - def settingsChanged(self): - current_enabled = utils.getSetting("enable_scheduler") - - if(current_enabled == "true" and self.enabled == "false"): - #scheduler was just turned on - self.enabled = current_enabled - self.setup() - elif (current_enabled == "false" and self.enabled == "true"): - #schedule was turn off - self.enabled = current_enabled - - if(self.enabled == "true"): - #always recheck the next run time after an update - self.findNextRun(time.time()) - - def parseSchedule(self): - schedule_type = int(utils.getSetting("schedule_interval")) - cron_exp = utils.getSetting("cron_schedule") - - hour_of_day = utils.getSetting("schedule_time") - hour_of_day = int(hour_of_day[0:2]) - if(schedule_type == 0 or schedule_type == 1): - #every day - cron_exp = "0 " + str(hour_of_day) + " * * *" - elif(schedule_type == 2): - #once a week - day_of_week = utils.getSetting("day_of_week") - cron_exp = "0 " + str(hour_of_day) + " * * " + day_of_week - elif(schedule_type == 3): - #first day of month - cron_exp = "0 " + str(hour_of_day) + " 1 * *" - - return cron_exp - - def _resumeCheck(self): - shouldContinue = False - if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "resume.txt"))): - rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"),'r') - self.restore_point = rFile.read() - rFile.close() - xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "resume.txt")) - shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042),utils.getString(30043),utils.getString(30044)) - - return shouldContinue - - -class UpdateMonitor(xbmc.Monitor): - update_method = None - - def __init__(self,*args, **kwargs): - xbmc.Monitor.__init__(self) - self.update_method = kwargs['update_method'] - - def onSettingsChanged(self): - self.update_method() - -BackupScheduler().start() +import xbmc +import xbmcvfs +import xbmcgui +import datetime +import time +import os +import resources.lib.utils as utils +from resources.lib.croniter import croniter +from resources.lib.backup import XbmcBackup + +UPGRADE_INT = 1 #to keep track of any upgrade notifications + +class BackupScheduler: + monitor = None + enabled = "false" + next_run = 0 + next_run_path = None + restore_point = None + + def __init__(self): + self.monitor = UpdateMonitor(update_method = self.settingsChanged) + self.enabled = utils.getSetting("enable_scheduler") + self.next_run_path = xbmc.translatePath(utils.data_dir()) + 'next_run.txt' + + if(self.enabled == "true"): + + nr = 0 + if(xbmcvfs.exists(self.next_run_path)): + + fh = xbmcvfs.File(self.next_run_path) + try: + #check if we saved a run time from the last run + nr = float(fh.read()) + except ValueError: + nr = 0 + + fh.close() + + #if we missed and the user wants to play catch-up + if(0 < nr <= time.time() and utils.getSetting('schedule_miss') == 'true'): + utils.log("scheduled backup was missed, doing it now...") + progress_mode = int(utils.getSetting('progress_mode')) + + if(progress_mode == 0): + progress_mode = 1 # Kodi just started, don't block it with a foreground progress bar + + self.doScheduledBackup(progress_mode) + + self.setup() + + def setup(self): + #scheduler was turned on, find next run time + utils.log("scheduler enabled, finding next run time") + self.findNextRun(time.time()) + + def start(self): + + #display upgrade messages if they exist + if(int(utils.getSetting('upgrade_notes')) < UPGRADE_INT): + xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30132)) + utils.setSetting('upgrade_notes',str(UPGRADE_INT)) + + #check if a backup should be resumed + resumeRestore = self._resumeCheck() + + if(resumeRestore): + restore = XbmcBackup() + restore.selectRestore(self.restore_point) + #skip the advanced settings check + restore.skipAdvanced() + restore.restore() + + while(not xbmc.abortRequested): + + if(self.enabled == "true"): + #scheduler is still on + now = time.time() + + if(self.next_run <= now): + progress_mode = int(utils.getSetting('progress_mode')) + self.doScheduledBackup(progress_mode) + + #check if we should shut the computer down + if(utils.getSetting("cron_shutdown") == 'true'): + #wait 10 seconds to make sure all backup processes and files are completed + time.sleep(10) + xbmc.executebuiltin('ShutDown()') + else: + #find the next run time like normal + self.findNextRun(now) + + xbmc.sleep(500) + + #delete monitor to free up memory + del self.monitor + + def doScheduledBackup(self,progress_mode): + if(progress_mode != 2): + utils.showNotification(utils.getString(30053)) + + backup = XbmcBackup() + + if(backup.remoteConfigured()): + + if(int(utils.getSetting('progress_mode')) in [0,1]): + backup.backup(True) + else: + backup.backup(False) + + #check if this is a "one-off" + if(int(utils.getSetting("schedule_interval")) == 0): + #disable the scheduler after this run + self.enabled = "false" + utils.setSetting('enable_scheduler','false') + else: + utils.showNotification(utils.getString(30045)) + + def findNextRun(self,now): + progress_mode = int(utils.getSetting('progress_mode')) + + #find the cron expression and get the next run time + cron_exp = self.parseSchedule() + + cron_ob = croniter(cron_exp,datetime.datetime.fromtimestamp(now)) + new_run_time = cron_ob.get_next(float) + + if(new_run_time != self.next_run): + self.next_run = new_run_time + utils.log("scheduler will run again on " + datetime.datetime.fromtimestamp(self.next_run).strftime('%m-%d-%Y %H:%M')) + + #write the next time to a file + fh = xbmcvfs.File(self.next_run_path, 'w') + fh.write(str(self.next_run)) + fh.close() + + #only show when not in silent mode + if(progress_mode != 2): + utils.showNotification(utils.getString(30081) + " " + datetime.datetime.fromtimestamp(self.next_run).strftime('%m-%d-%Y %H:%M')) + + def settingsChanged(self): + current_enabled = utils.getSetting("enable_scheduler") + + if(current_enabled == "true" and self.enabled == "false"): + #scheduler was just turned on + self.enabled = current_enabled + self.setup() + elif (current_enabled == "false" and self.enabled == "true"): + #schedule was turn off + self.enabled = current_enabled + + if(self.enabled == "true"): + #always recheck the next run time after an update + self.findNextRun(time.time()) + + def parseSchedule(self): + schedule_type = int(utils.getSetting("schedule_interval")) + cron_exp = utils.getSetting("cron_schedule") + + hour_of_day = utils.getSetting("schedule_time") + hour_of_day = int(hour_of_day[0:2]) + if(schedule_type == 0 or schedule_type == 1): + #every day + cron_exp = "0 " + str(hour_of_day) + " * * *" + elif(schedule_type == 2): + #once a week + day_of_week = utils.getSetting("day_of_week") + cron_exp = "0 " + str(hour_of_day) + " * * " + day_of_week + elif(schedule_type == 3): + #first day of month + cron_exp = "0 " + str(hour_of_day) + " 1 * *" + + return cron_exp + + def _resumeCheck(self): + shouldContinue = False + if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "resume.txt"))): + rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"),'r') + self.restore_point = rFile.read() + rFile.close() + xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "resume.txt")) + shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042),utils.getString(30043),utils.getString(30044)) + + return shouldContinue + + +class UpdateMonitor(xbmc.Monitor): + update_method = None + + def __init__(self,*args, **kwargs): + xbmc.Monitor.__init__(self) + self.update_method = kwargs['update_method'] + + def onSettingsChanged(self): + self.update_method() + +BackupScheduler().start()