From 1c79ba6e23735a40126575ebb3382bfb2bcf9be8 Mon Sep 17 00:00:00 2001 From: robweber Date: Wed, 29 Aug 2012 13:00:42 -0500 Subject: [PATCH 1/9] updated changelog.txt --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index 5e77a2e..4c1fd67 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,7 @@ [b]Version 0.1.3[/b] +backup folder format - thanks zeroram + added German translations - thanks dersphere removed need for separate verbose logging setting From 144b089673a027847985b728ec9d0110bcb7e672 Mon Sep 17 00:00:00 2001 From: robweber Date: Tue, 4 Sep 2012 09:18:32 -0500 Subject: [PATCH 2/9] fixed #13 --- changelog.txt | 4 ++++ default.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 4c1fd67..a6cee4b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +[b]Version 0.1.4[/b] + +added more verbose error message for incorrect paths + [b]Version 0.1.3[/b] backup folder format - thanks zeroram diff --git a/default.py b/default.py index 7c82791..7470474 100644 --- a/default.py +++ b/default.py @@ -143,7 +143,7 @@ class XbmcBackup: if(vfs.exists(self.remote_path)): self.restoreFiles() else: - xbmcgui.Dialog().ok(self.addon.getLocalizedString(30010),self.addon.getLocalizedString(30045)) + xbmcgui.Dialog().ok(self.addon.getLocalizedString(30010),self.addon.getLocalizedString(30045),self.remote_path) def syncFiles(self): From cb115e42fba31cf6702191917ea50406b496a486 Mon Sep 17 00:00:00 2001 From: robweber Date: Tue, 4 Sep 2012 10:42:17 -0500 Subject: [PATCH 3/9] created stub for new addon settings --- resources/language/English/strings.xml | 5 ++++- resources/settings.xml | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index b88f105..6ea2d39 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -3,7 +3,8 @@ XBMC Backup General File Selection - + Scheduling + Backup Restore Browse Path @@ -31,4 +32,6 @@ Creating Files List Writing file + Enable Scheduler + diff --git a/resources/settings.xml b/resources/settings.xml index d6cad5a..b115e9d 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -16,4 +16,7 @@ + + + From 5b51a9653166b63ca92047d4290d2e99e03d7d2b Mon Sep 17 00:00:00 2001 From: robweber Date: Tue, 4 Sep 2012 11:08:14 -0500 Subject: [PATCH 4/9] added additional parameters to run() method, can now override user preference if needed added "mode" logging message --- default.py | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/default.py b/default.py index 7470474..712ca1c 100644 --- a/default.py +++ b/default.py @@ -82,6 +82,10 @@ class FileManager: return self.fileArray class XbmcBackup: + #constants for initiating a back or restore + Backup = 0 + Restore = 1 + addon = None local_path = '' remote_path = '' @@ -107,27 +111,33 @@ class XbmcBackup: #check if trailing slash is included if(self.remote_path[-1:] != "/"): self.remote_path = self.remote_path + "/" - - #append backup folder name - if(int(self.addon.getSetting('addon_mode')) == 0 and self.remote_path != ''): - self.remote_path = self.remote_path + time.strftime("%Y%m%d") + "/" - elif(int(self.addon.getSetting('addon_mode')) == 1 and self.addon.getSetting("backup_name") != '' and self.remote_path != ''): - self.remote_path = self.remote_path + self.addon.getSetting("backup_name") + "/" - else: - self.remote_path = "" log(self.addon.getLocalizedString(30046)) - log(self.addon.getLocalizedString(30047) + ": " + self.local_path) - log(self.addon.getLocalizedString(30048) + ": " + self.remote_path) - def run(self): + def run(self,mode=-1): #check if we should use the progress bar if(self.addon.getSetting('run_silent') == 'false'): self.progressBar = xbmcgui.DialogProgress() self.progressBar.create(self.addon.getLocalizedString(30010),self.addon.getLocalizedString(30049) + "......") - - #check what mode were are in - if(int(self.addon.getSetting('addon_mode')) == 0): + + #determine backup mode + if(mode == -1): + mode = int(self.addon.getSetting('addon_mode')) + + #append backup folder name + if(mode == self.Backup and self.remote_path != ''): + self.remote_path = self.remote_path + time.strftime("%Y%m%d") + "/" + elif(mode == self.Restore and self.addon.getSetting("backup_name") != '' and self.remote_path != ''): + self.remote_path = self.remote_path + self.addon.getSetting("backup_name") + "/" + else: + self.remote_path = "" + + log(self.addon.getLocalizedString(30047) + ": " + self.local_path) + log(self.addon.getLocalizedString(30048) + ": " + self.remote_path) + + #run the correct mode + if(mode == self.Backup): + log(self.addon.getLocalizedString(30023) + " - " + self.addon.getLocalizedString(30016)) self.fileManager = FileManager(self.local_path,self.addon.getAddonInfo('profile')) #for backups check if remote path exists @@ -137,6 +147,7 @@ class XbmcBackup: self.syncFiles() else: + log(self.addon.getLocalizedString(30023) + " - " + self.addon.getLocalizedString(30017)) self.fileManager = FileManager(self.remote_path,self.addon.getAddonInfo('profile')) #for restores remote path must exist From 0150e6a277ffe25788ee04846784d004f35bd421 Mon Sep 17 00:00:00 2001 From: robweber Date: Wed, 5 Sep 2012 14:28:43 -0500 Subject: [PATCH 5/9] created utils library to avoid duplicate functions pulled XbmcBackup class into separate library --- default.py | 231 +--------------------------------------- resources/lib/backup.py | 207 +++++++++++++++++++++++++++++++++++ resources/lib/utils.py | 21 ++++ 3 files changed, 230 insertions(+), 229 deletions(-) create mode 100644 resources/lib/backup.py create mode 100644 resources/lib/utils.py diff --git a/default.py b/default.py index 712ca1c..8676e8b 100644 --- a/default.py +++ b/default.py @@ -1,234 +1,7 @@ -import xbmc -import xbmcaddon -import xbmcgui -import resources.lib.vfs as vfs -import os -import time - -__addon_id__ = 'script.xbmcbackup' -__Addon = xbmcaddon.Addon(__addon_id__) - -class FileManager: - walk_path = '' - addonDir = '' - fileArray = None - verbose_log = False - - def __init__(self,path,addon_dir): - self.walk_path = path - self.addonDir = addon_dir - - #create the addon folder if it doesn't exist - if(not os.path.exists(unicode(xbmc.translatePath(self.addonDir),'utf-8'))): - os.makedirs(unicode(xbmc.translatePath(self.addonDir),'utf-8')) - - def createFileList(self,Addon): - self.fileArray = [] - self.verbose_log = Addon.getSetting("verbose_log") == 'true' - - #figure out which syncing options to run - if(Addon.getSetting('backup_addons') == 'true'): - self.addFile("-addons") - self.walkTree(self.walk_path + "addons/") - - self.addFile("-userdata") - - if(Addon.getSetting('backup_addon_data') == 'true'): - self.addFile("-userdata/addon_data") - self.walkTree(self.walk_path + "userdata/addon_data/") - - if(Addon.getSetting('backup_database') == 'true'): - self.addFile("-userdata/Database") - self.walkTree(self.walk_path + "userdata/Database") - - if(Addon.getSetting("backup_playlists") == 'true'): - self.addFile("-userdata/playlists") - self.walkTree(self.walk_path + "userdata/playlists") - - if(Addon.getSetting("backup_thumbnails") == "true"): - self.addFile("-userdata/Thumbnails") - self.walkTree(self.walk_path + "userdata/Thumbnails") - - if(Addon.getSetting("backup_config") == "true"): - self.addFile("-userdata/keymaps") - self.walkTree(self.walk_path + "userdata/keymaps") - - self.addFile("-userdata/peripheral_data") - self.walkTree(self.walk_path + "userdata/peripheral_data") - - #this part is an oddity - configFiles = vfs.listdir(self.walk_path + "userdata/",extra_metadata=True) - for aFile in configFiles: - if(aFile['file'].endswith(".xml")): - self.addFile(aFile['file'][len(self.walk_path):]) - - def walkTree(self,directory): - for (path, dirs, files) in vfs.walk(directory): - - #create all the subdirs first - for aDir in dirs: - self.addFile("-" + aDir[len(self.walk_path):]) - #copy all the files - for aFile in files: - filePath = aFile[len(self.walk_path):] - self.addFile(filePath) - - def addFile(self,filename): - #write the full remote path name of this file - log("Add File: " + filename,xbmc.LOGDEBUG) - self.fileArray.append(filename) - - def getFileList(self): - return self.fileArray - -class XbmcBackup: - #constants for initiating a back or restore - Backup = 0 - Restore = 1 - - addon = None - local_path = '' - remote_path = '' - restoreFile = None - - #for the progress bar - progressBar = None - filesLeft = 0 - filesTotal = 1 - - fileManager = None - - def __init__(self,__Addon): - self.addon = __Addon - self.local_path = xbmc.makeLegalFilename(xbmc.translatePath("special://home"),False); - - if(self.addon.getSetting('remote_selection') == '1'): - self.remote_path = self.addon.getSetting('remote_path_2') - self.addon.setSetting("remote_path","") - elif(self.addon.getSetting('remote_selection') == '0'): - self.remote_path = self.addon.getSetting("remote_path") - - #check if trailing slash is included - if(self.remote_path[-1:] != "/"): - self.remote_path = self.remote_path + "/" - - log(self.addon.getLocalizedString(30046)) - - def run(self,mode=-1): - #check if we should use the progress bar - if(self.addon.getSetting('run_silent') == 'false'): - self.progressBar = xbmcgui.DialogProgress() - self.progressBar.create(self.addon.getLocalizedString(30010),self.addon.getLocalizedString(30049) + "......") - - #determine backup mode - if(mode == -1): - mode = int(self.addon.getSetting('addon_mode')) - - #append backup folder name - if(mode == self.Backup and self.remote_path != ''): - self.remote_path = self.remote_path + time.strftime("%Y%m%d") + "/" - elif(mode == self.Restore and self.addon.getSetting("backup_name") != '' and self.remote_path != ''): - self.remote_path = self.remote_path + self.addon.getSetting("backup_name") + "/" - else: - self.remote_path = "" - - log(self.addon.getLocalizedString(30047) + ": " + self.local_path) - log(self.addon.getLocalizedString(30048) + ": " + self.remote_path) - - #run the correct mode - if(mode == self.Backup): - log(self.addon.getLocalizedString(30023) + " - " + self.addon.getLocalizedString(30016)) - self.fileManager = FileManager(self.local_path,self.addon.getAddonInfo('profile')) - - #for backups check if remote path exists - if(vfs.exists(self.remote_path)): - #this will fail - need a disclaimer here - log(self.addon.getLocalizedString(30050)) - - self.syncFiles() - else: - log(self.addon.getLocalizedString(30023) + " - " + self.addon.getLocalizedString(30017)) - self.fileManager = FileManager(self.remote_path,self.addon.getAddonInfo('profile')) - - #for restores remote path must exist - if(vfs.exists(self.remote_path)): - self.restoreFiles() - else: - xbmcgui.Dialog().ok(self.addon.getLocalizedString(30010),self.addon.getLocalizedString(30045),self.remote_path) - - def syncFiles(self): - - #make the remote directory - vfs.mkdir(self.remote_path) - - log(self.addon.getLocalizedString(30051)) - self.fileManager.createFileList(self.addon) - - allFiles = self.fileManager.getFileList() - - #write list from local to remote - self.writeFiles(allFiles,self.local_path,self.remote_path) - - def restoreFiles(self): - self.fileManager.createFileList(self.addon) - - log(self.addon.getLocalizedString(30051)) - allFiles = self.fileManager.getFileList() - - #write list from remote to local - self.writeFiles(allFiles,self.remote_path,self.local_path) - - #call update addons to refresh everything - xbmc.executebuiltin('UpdateLocalAddons') - - def writeFiles(self,fileList,source,dest): - log("Writing files to: " + dest) - self.filesTotal = len(fileList) - self.filesLeft = self.filesTotal - - #write each file from source to destination - for aFile in fileList: - if(not self.checkCancel()): - log('Writing file: ' + source + aFile,xbmc.LOGDEBUG) - self.updateProgress(aFile) - if (aFile.startswith("-")): - vfs.mkdir(xbmc.makeLegalFilename(dest + aFile[1:],False)) - else: - vfs.copy(xbmc.makeLegalFilename(source + aFile),xbmc.makeLegalFilename(dest + aFile,False)) - - if(self.addon.getSetting('run_silent') == 'false'): - self.progressBar.close() - - def updateProgress(self,message=''): - self.filesLeft = self.filesLeft - 1 - - #update the progress bar - if(self.progressBar != None): - self.progressBar.update(int((float(self.filesTotal - self.filesLeft)/float(self.filesTotal)) * 100),message) - - def checkCancel(self): - result = False - - if(self.progressBar != None): - result = self.progressBar.iscanceled() - - return result - - def isReady(self): - return True if self.remote_path != '' else False - -#global functions for logging and encoding -def log(message,loglevel=xbmc.LOGNOTICE): - xbmc.log(encode(__Addon.getLocalizedString(30010) + ": " + message),level=loglevel) - -def encode(string): - return string.encode('UTF-8','replace') - +from resources.lib.backup import XbmcBackup #run the profile backup -backup = XbmcBackup(__Addon) +backup = XbmcBackup() if(backup.isReady()): backup.run() -else: - xbmcgui.Dialog().ok(__Addon.getLocalizedString(30010),__Addon.getLocalizedString(30045)) diff --git a/resources/lib/backup.py b/resources/lib/backup.py new file mode 100644 index 0000000..38be904 --- /dev/null +++ b/resources/lib/backup.py @@ -0,0 +1,207 @@ +import xbmc +import xbmcgui +import vfs as vfs +import utils as utils +import os +import time + +class FileManager: + walk_path = '' + fileArray = None + verbose_log = False + + def __init__(self,path): + self.walk_path = path + + def createFileList(self): + self.fileArray = [] + self.verbose_log = utils.getSetting("verbose_log") == 'true' + + #figure out which syncing options to run + if(utils.getSetting('backup_addons') == 'true'): + self.addFile("-addons") + self.walkTree(self.walk_path + "addons/") + + self.addFile("-userdata") + + if(utils.getSetting('backup_addon_data') == 'true'): + self.addFile("-userdata/addon_data") + self.walkTree(self.walk_path + "userdata/addon_data/") + + if(utils.getSetting('backup_database') == 'true'): + self.addFile("-userdata/Database") + self.walkTree(self.walk_path + "userdata/Database") + + if(utils.getSetting("backup_playlists") == 'true'): + self.addFile("-userdata/playlists") + self.walkTree(self.walk_path + "userdata/playlists") + + if(utils.getSetting("backup_thumbnails") == "true"): + self.addFile("-userdata/Thumbnails") + self.walkTree(self.walk_path + "userdata/Thumbnails") + + if(utils.getSetting("backup_config") == "true"): + self.addFile("-userdata/keymaps") + self.walkTree(self.walk_path + "userdata/keymaps") + + self.addFile("-userdata/peripheral_data") + self.walkTree(self.walk_path + "userdata/peripheral_data") + + #this part is an oddity + configFiles = vfs.listdir(self.walk_path + "userdata/",extra_metadata=True) + for aFile in configFiles: + if(aFile['file'].endswith(".xml")): + self.addFile(aFile['file'][len(self.walk_path):]) + + def walkTree(self,directory): + for (path, dirs, files) in vfs.walk(directory): + + #create all the subdirs first + for aDir in dirs: + self.addFile("-" + aDir[len(self.walk_path):]) + #copy all the files + for aFile in files: + filePath = aFile[len(self.walk_path):] + self.addFile(filePath) + + def addFile(self,filename): + #write the full remote path name of this file + utils.log("Add File: " + filename,xbmc.LOGDEBUG) + self.fileArray.append(filename) + + def getFileList(self): + return self.fileArray + +class XbmcBackup: + #constants for initiating a back or restore + Backup = 0 + Restore = 1 + + local_path = '' + remote_path = '' + restoreFile = None + + #for the progress bar + progressBar = None + filesLeft = 0 + filesTotal = 1 + + fileManager = None + + def __init__(self): + self.local_path = xbmc.makeLegalFilename(xbmc.translatePath("special://home"),False); + + if(utils.getSetting('remote_selection') == '1'): + self.remote_path = utils.getSetting('remote_path_2') + utils.setSetting("remote_path","") + elif(utils.getSetting('remote_selection') == '0'): + self.remote_path = utils.getSetting("remote_path") + + #check if trailing slash is included + if(self.remote_path[-1:] != "/" and self.remote_path[-1:] != "\\"): + self.remote_path = self.remote_path + "/" + + utils.log(utils.getString(30046)) + + def run(self,mode=-1): + #check if we should use the progress bar + if(utils.getSetting('run_silent') == 'false'): + self.progressBar = xbmcgui.DialogProgress() + self.progressBar.create(utils.getString(30010),utils.getString(30049) + "......") + + #determine backup mode + if(mode == -1): + mode = int(utils.getSetting('addon_mode')) + + #append backup folder name + if(mode == self.Backup and self.remote_path != ''): + self.remote_path = self.remote_path + time.strftime("%Y%m%d") + "/" + elif(mode == self.Restore and utils.getSetting("backup_name") != '' and self.remote_path != ''): + self.remote_path = self.remote_path + utils.getSetting("backup_name") + "/" + else: + self.remote_path = "" + + utils.log(utils.getString(30047) + ": " + self.local_path) + utils.log(utils.getString(30048) + ": " + self.remote_path) + + #run the correct mode + if(mode == self.Backup): + utils.log(utils.getString(30023) + " - " + utils.getString(30016)) + self.fileManager = FileManager(self.local_path) + + #for backups check if remote path exists + if(vfs.exists(self.remote_path)): + #this will fail - need a disclaimer here + utils.log(utils.getString(30050)) + + self.syncFiles() + else: + utils.log(utils.getString(30023) + " - " + utils.getString(30017)) + self.fileManager = FileManager(self.remote_path) + + #for restores remote path must exist + if(vfs.exists(self.remote_path)): + self.restoreFiles() + else: + xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_path) + + def syncFiles(self): + + #make the remote directory + vfs.mkdir(self.remote_path) + + utils.log(utils.getString(30051)) + self.fileManager.createFileList() + + allFiles = self.fileManager.getFileList() + + #write list from local to remote + self.writeFiles(allFiles,self.local_path,self.remote_path) + + def restoreFiles(self): + self.fileManager.createFileList() + + utils.log(utils.getString(30051)) + allFiles = self.fileManager.getFileList() + + #write list from remote to local + self.writeFiles(allFiles,self.remote_path,self.local_path) + + #call update addons to refresh everything + xbmc.executebuiltin('UpdateLocalAddons') + + def writeFiles(self,fileList,source,dest): + utils.log("Writing files to: " + dest) + self.filesTotal = len(fileList) + self.filesLeft = self.filesTotal + + #write each file from source to destination + for aFile in fileList: + if(not self.checkCancel()): + utils.log('Writing file: ' + source + aFile,xbmc.LOGDEBUG) + self.updateProgress(aFile) + if (aFile.startswith("-")): + vfs.mkdir(xbmc.makeLegalFilename(dest + aFile[1:],False)) + else: + vfs.copy(xbmc.makeLegalFilename(source + aFile),xbmc.makeLegalFilename(dest + aFile,False)) + + if(utils.getSetting('run_silent') == 'false'): + self.progressBar.close() + + def updateProgress(self,message=''): + self.filesLeft = self.filesLeft - 1 + + #update the progress bar + if(self.progressBar != None): + self.progressBar.update(int((float(self.filesTotal - self.filesLeft)/float(self.filesTotal)) * 100),message) + + def checkCancel(self): + result = False + + if(self.progressBar != None): + result = self.progressBar.iscanceled() + + return result + + def isReady(self): + return True if self.remote_path != '' else False diff --git a/resources/lib/utils.py b/resources/lib/utils.py new file mode 100644 index 0000000..eace7d5 --- /dev/null +++ b/resources/lib/utils.py @@ -0,0 +1,21 @@ +import xbmc +import xbmcaddon + +__addon_id__= 'script.xbmcbackup' +__Addon = xbmcaddon.Addon(__addon_id__) + +#global functions for logging and encoding +def log(message,loglevel=xbmc.LOGNOTICE): + xbmc.log(encode(__addon_id__ + ": " + message),level=loglevel) + +def getSetting(name): + return __Addon.getSetting(name) + +def setSetting(name,value): + __Addon.setSetting(name,value) + +def getString(string_id): + return __Addon.getLocalizedString(string_id) + +def encode(string): + return string.encode('UTF-8','replace') From 0bb915ae3a613ff7e305bfa74ce57c0e22eaf04f Mon Sep 17 00:00:00 2001 From: robweber Date: Wed, 5 Sep 2012 15:50:25 -0500 Subject: [PATCH 6/9] added initial resources for scheduler.py to function starte settings.xml modifications for timer intervals --- addon.xml | 3 +- changelog.txt | 4 + resources/lib/backup.py | 5 +- resources/lib/croniter.py | 308 +++++++++++++++++++++++ resources/lib/relativedelta.py | 430 +++++++++++++++++++++++++++++++++ resources/settings.xml | 4 + scheduler.py | 43 ++++ 7 files changed, 795 insertions(+), 2 deletions(-) create mode 100644 resources/lib/croniter.py create mode 100644 resources/lib/relativedelta.py create mode 100644 scheduler.py diff --git a/addon.xml b/addon.xml index 752b4d6..fc33fe0 100644 --- a/addon.xml +++ b/addon.xml @@ -1,12 +1,13 @@  + name="XBMC Backup" version="0.1.5" provider-name="robweber"> executable + Sauvegarder et restaurer vos bases de données XBMC et vos fichiers de configuration en cas de crash ou de fichiers corrompus. Die XBMC Datenbank sichern und bei Dateiverlust oder Beschädigung wiederherstellen. diff --git a/changelog.txt b/changelog.txt index a6cee4b..d48be4d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +[b]Version 0.1.5[/b] + +pulled xbmcbackup class into separate library + [b]Version 0.1.4[/b] added more verbose error message for incorrect paths diff --git a/resources/lib/backup.py b/resources/lib/backup.py index 38be904..2e583af 100644 --- a/resources/lib/backup.py +++ b/resources/lib/backup.py @@ -97,8 +97,11 @@ class XbmcBackup: elif(utils.getSetting('remote_selection') == '0'): self.remote_path = utils.getSetting("remote_path") + #fix slashes + self.remote_path = self.remote_path.replace("\\","/") + #check if trailing slash is included - if(self.remote_path[-1:] != "/" and self.remote_path[-1:] != "\\"): + if(self.remote_path[-1:] != "/"): self.remote_path = self.remote_path + "/" utils.log(utils.getString(30046)) diff --git a/resources/lib/croniter.py b/resources/lib/croniter.py new file mode 100644 index 0000000..dca0441 --- /dev/null +++ b/resources/lib/croniter.py @@ -0,0 +1,308 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import re +from time import time, mktime +from datetime import datetime, date +from relativedelta import relativedelta + +search_re = re.compile(r'^([^-]+)-([^-/]+)(/(.*))?$') +only_int_re = re.compile(r'^\d+$') +any_int_re = re.compile(r'^\d+') +star_or_int_re = re.compile(r'^(\d+|\*)$') + +__all__ = ('croniter',) + + +class croniter(object): + RANGES = ( + (0, 59), + (0, 23), + (1, 31), + (1, 12), + (0, 6), + (0, 59) + ) + DAYS = ( + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + ) + + ALPHACONV = ( + { }, + { }, + { }, + { 'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, + 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 }, + { 'sun':0, 'mon':1, 'tue':2, 'wed':3, 'thu':4, 'fri':5, 'sat':6 }, + { } + ) + + LOWMAP = ( + {}, + {}, + {0: 1}, + {0: 1}, + {7: 0}, + {}, + ) + + bad_length = 'Exactly 5 or 6 columns has to be specified for iterator' \ + 'expression.' + + def __init__(self, expr_format, start_time=time()): + if isinstance(start_time, datetime): + start_time = mktime(start_time.timetuple()) + + self.cur = start_time + self.exprs = expr_format.split() + + if len(self.exprs) != 5 and len(self.exprs) != 6: + raise ValueError(self.bad_length) + + expanded = [] + + for i, expr in enumerate(self.exprs): + e_list = expr.split(',') + res = [] + + while len(e_list) > 0: + e = e_list.pop() + t = re.sub(r'^\*(/.+)$', r'%d-%d\1' % (self.RANGES[i][0], + self.RANGES[i][1]), + str(e)) + m = search_re.search(t) + + if m: + (low, high, step) = m.group(1), m.group(2), m.group(4) or 1 + + if not any_int_re.search(low): + low = self.ALPHACONV[i][low.lower()] + + if not any_int_re.search(high): + high = self.ALPHACONV[i][high.lower()] + + if (not low or not high or int(low) > int(high) + or not only_int_re.search(str(step))): + raise ValueError("[%s] is not acceptable" %expr_format) + + for j in xrange(int(low), int(high)+1): + if j % int(step) == 0: + e_list.append(j) + else: + if not star_or_int_re.search(t): + t = self.ALPHACONV[i][t.lower()] + + try: + t = int(t) + except: + pass + + if t in self.LOWMAP[i]: + t = self.LOWMAP[i][t] + + if t != '*' and (int(t) < self.RANGES[i][0] or + int(t) > self.RANGES[i][1]): + raise ValueError("[%s] is not acceptable, out of range" % expr_format) + + res.append(t) + + res.sort() + expanded.append(['*'] if (len(res) == 1 and res[0] == '*') else res) + self.expanded = expanded + + def get_next(self, ret_type=float): + return self._get_next(ret_type, is_prev=False) + + def get_prev(self, ret_type=float): + return self._get_next(ret_type, is_prev=True) + + def _get_next(self, ret_type=float, is_prev=False): + expanded = self.expanded[:] + + if ret_type not in (float, datetime): + raise TypeError("Invalid ret_type, only 'float' or 'datetime' " \ + "is acceptable.") + + if expanded[2][0] != '*' and expanded[4][0] != '*': + bak = expanded[4] + expanded[4] = ['*'] + t1 = self._calc(self.cur, expanded, is_prev) + expanded[4] = bak + expanded[2] = ['*'] + + t2 = self._calc(self.cur, expanded, is_prev) + if not is_prev: + result = t1 if t1 < t2 else t2 + else: + result = t1 if t1 > t2 else t2 + else: + result = self._calc(self.cur, expanded, is_prev) + self.cur = result + + if ret_type == datetime: + result = datetime.fromtimestamp(result) + return result + + def _calc(self, now, expanded, is_prev): + if is_prev: + nearest_method = self._get_prev_nearest + nearest_diff_method = self._get_prev_nearest_diff + sign = -1 + else: + nearest_method = self._get_next_nearest + nearest_diff_method = self._get_next_nearest_diff + sign = 1 + + offset = len(expanded) == 6 and 1 or 60 + dst = now = datetime.fromtimestamp(now + sign * offset) + + day, month, year = dst.day, dst.month, dst.year + current_year = now.year + DAYS = self.DAYS + + def proc_month(d): + if expanded[3][0] != '*': + diff_month = nearest_diff_method(month, expanded[3], 12) + days = DAYS[month - 1] + if month == 2 and self.is_leap(year) == True: + days += 1 + + reset_day = days if is_prev else 1 + + if diff_month != None and diff_month != 0: + if is_prev: + d += relativedelta(months=diff_month) + else: + d += relativedelta(months=diff_month, day=reset_day, + hour=0, minute=0, second=0) + return True, d + return False, d + + def proc_day_of_month(d): + if expanded[2][0] != '*': + days = DAYS[month - 1] + if month == 2 and self.is_leap(year) == True: + days += 1 + + diff_day = nearest_diff_method(d.day, expanded[2], days) + + if diff_day != None and diff_day != 0: + if is_prev: + d += relativedelta(days=diff_day) + else: + d += relativedelta(days=diff_day, hour=0, minute=0, second=0) + return True, d + return False, d + + def proc_day_of_week(d): + if expanded[4][0] != '*': + diff_day_of_week = nearest_diff_method(d.isoweekday() % 7, expanded[4], 7) + if diff_day_of_week != None and diff_day_of_week != 0: + if is_prev: + d += relativedelta(days=diff_day_of_week) + else: + d += relativedelta(days=diff_day_of_week, hour=0, minute=0, second=0) + return True, d + return False, d + + def proc_hour(d): + if expanded[1][0] != '*': + diff_hour = nearest_diff_method(d.hour, expanded[1], 24) + if diff_hour != None and diff_hour != 0: + if is_prev: + d += relativedelta(hours = diff_hour) + else: + d += relativedelta(hours = diff_hour, minute=0, second=0) + return True, d + return False, d + + def proc_minute(d): + if expanded[0][0] != '*': + diff_min = nearest_diff_method(d.minute, expanded[0], 60) + if diff_min != None and diff_min != 0: + if is_prev: + d += relativedelta(minutes = diff_min) + else: + d += relativedelta(minutes = diff_min, second=0) + return True, d + return False, d + + def proc_second(d): + if len(expanded) == 6: + if expanded[5][0] != '*': + diff_sec = nearest_diff_method(d.second, expanded[5], 60) + if diff_sec != None and diff_sec != 0: + dst += relativedelta(seconds = diff_sec) + return True, d + else: + d += relativedelta(second = 0) + return False, d + + if is_prev: + procs = [proc_second, + proc_minute, + proc_hour, + proc_day_of_week, + proc_day_of_month, + proc_month] + else: + procs = [proc_month, + proc_day_of_month, + proc_day_of_week, + proc_hour, + proc_minute, + proc_second] + + while abs(year - current_year) <= 1: + next = False + for proc in procs: + (changed, dst) = proc(dst) + if changed: + next = True + break + if next: + continue + return mktime(dst.timetuple()) + + raise "failed to find prev date" + + def _get_next_nearest(self, x, to_check): + small = [item for item in to_check if item < x] + large = [item for item in to_check if item >= x] + large.extend(small) + return large[0] + + def _get_prev_nearest(self, x, to_check): + small = [item for item in to_check if item <= x] + large = [item for item in to_check if item > x] + small.reverse() + large.reverse() + small.extend(large) + return small[0] + + def _get_next_nearest_diff(self, x, to_check, range_val): + for i, d in enumerate(to_check): + if d >= x: + return d - x + return to_check[0] - x + range_val + + def _get_prev_nearest_diff(self, x, to_check, range_val): + candidates = to_check[:] + candidates.reverse() + for d in candidates: + if d <= x: + return d - x + return (candidates[0]) - x - range_val + + def is_leap(self, year): + if year % 400 == 0 or (year % 4 == 0 and year % 100 != 0): + return True + else: + return False + +if __name__ == '__main__': + + base = datetime(2010, 1, 25) + itr = croniter('0 0 1 * *', base) + n1 = itr.get_next(datetime) + print n1 diff --git a/resources/lib/relativedelta.py b/resources/lib/relativedelta.py new file mode 100644 index 0000000..c5e8e5d --- /dev/null +++ b/resources/lib/relativedelta.py @@ -0,0 +1,430 @@ +""" +Copyright (c) 2003-2010 Gustavo Niemeyer + +This module offers extensions to the standard python 2.3+ +datetime module. +""" +__author__ = "Gustavo Niemeyer " +__license__ = "PSF License" + +import datetime +import calendar + +__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + +class weekday(object): + __slots__ = ["weekday", "n"] + + def __init__(self, weekday, n=None): + self.weekday = weekday + self.n = n + + def __call__(self, n): + if n == self.n: + return self + else: + return self.__class__(self.weekday, n) + + def __eq__(self, other): + try: + if self.weekday != other.weekday or self.n != other.n: + return False + except AttributeError: + return False + return True + + def __repr__(self): + s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] + if not self.n: + return s + else: + return "%s(%+d)" % (s, self.n) + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) + +class relativedelta: + """ +The relativedelta type is based on the specification of the excelent +work done by M.-A. Lemburg in his mx.DateTime extension. However, +notice that this type does *NOT* implement the same algorithm as +his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. + +There's two different ways to build a relativedelta instance. The +first one is passing it two date/datetime classes: + + relativedelta(datetime1, datetime2) + +And the other way is to use the following keyword arguments: + + year, month, day, hour, minute, second, microsecond: + Absolute information. + + years, months, weeks, days, hours, minutes, seconds, microseconds: + Relative information, may be negative. + + weekday: + One of the weekday instances (MO, TU, etc). These instances may + receive a parameter N, specifying the Nth weekday, which could + be positive or negative (like MO(+1) or MO(-2). Not specifying + it is the same as specifying +1. You can also use an integer, + where 0=MO. + + leapdays: + Will add given days to the date found, if year is a leap + year, and the date found is post 28 of february. + + yearday, nlyearday: + Set the yearday or the non-leap year day (jump leap days). + These are converted to day/month/leapdays information. + +Here is the behavior of operations with relativedelta: + +1) Calculate the absolute year, using the 'year' argument, or the + original datetime year, if the argument is not present. + +2) Add the relative 'years' argument to the absolute year. + +3) Do steps 1 and 2 for month/months. + +4) Calculate the absolute day, using the 'day' argument, or the + original datetime day, if the argument is not present. Then, + subtract from the day until it fits in the year and month + found after their operations. + +5) Add the relative 'days' argument to the absolute day. Notice + that the 'weeks' argument is multiplied by 7 and added to + 'days'. + +6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, + microsecond/microseconds. + +7) If the 'weekday' argument is present, calculate the weekday, + with the given (wday, nth) tuple. wday is the index of the + weekday (0-6, 0=Mon), and nth is the number of weeks to add + forward or backward, depending on its signal. Notice that if + the calculated date is already Monday, for example, using + (0, 1) or (0, -1) won't change the day. + """ + + def __init__(self, dt1=None, dt2=None, + years=0, months=0, days=0, leapdays=0, weeks=0, + hours=0, minutes=0, seconds=0, microseconds=0, + year=None, month=None, day=None, weekday=None, + yearday=None, nlyearday=None, + hour=None, minute=None, second=None, microsecond=None): + if dt1 and dt2: + if not isinstance(dt1, datetime.date) or \ + not isinstance(dt2, datetime.date): + raise TypeError, "relativedelta only diffs datetime/date" + if type(dt1) is not type(dt2): + if not isinstance(dt1, datetime.datetime): + dt1 = datetime.datetime.fromordinal(dt1.toordinal()) + elif not isinstance(dt2, datetime.datetime): + dt2 = datetime.datetime.fromordinal(dt2.toordinal()) + self.years = 0 + self.months = 0 + self.days = 0 + self.leapdays = 0 + self.hours = 0 + self.minutes = 0 + self.seconds = 0 + self.microseconds = 0 + self.year = None + self.month = None + self.day = None + self.weekday = None + self.hour = None + self.minute = None + self.second = None + self.microsecond = None + self._has_time = 0 + + months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month) + self._set_months(months) + dtm = self.__radd__(dt2) + if dt1 < dt2: + while dt1 > dtm: + months += 1 + self._set_months(months) + dtm = self.__radd__(dt2) + else: + while dt1 < dtm: + months -= 1 + self._set_months(months) + dtm = self.__radd__(dt2) + delta = dt1 - dtm + self.seconds = delta.seconds+delta.days*86400 + self.microseconds = delta.microseconds + else: + self.years = years + self.months = months + self.days = days+weeks*7 + self.leapdays = leapdays + self.hours = hours + self.minutes = minutes + self.seconds = seconds + self.microseconds = microseconds + self.year = year + self.month = month + self.day = day + self.hour = hour + self.minute = minute + self.second = second + self.microsecond = microsecond + + if type(weekday) is int: + self.weekday = weekdays[weekday] + else: + self.weekday = weekday + + yday = 0 + if nlyearday: + yday = nlyearday + elif yearday: + yday = yearday + if yearday > 59: + self.leapdays = -1 + if yday: + ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366] + for idx, ydays in enumerate(ydayidx): + if yday <= ydays: + self.month = idx+1 + if idx == 0: + self.day = yday + else: + self.day = yday-ydayidx[idx-1] + break + else: + raise ValueError, "invalid year day (%d)" % yday + + self._fix() + + def _fix(self): + if abs(self.microseconds) > 999999: + s = self.microseconds//abs(self.microseconds) + div, mod = divmod(self.microseconds*s, 1000000) + self.microseconds = mod*s + self.seconds += div*s + if abs(self.seconds) > 59: + s = self.seconds//abs(self.seconds) + div, mod = divmod(self.seconds*s, 60) + self.seconds = mod*s + self.minutes += div*s + if abs(self.minutes) > 59: + s = self.minutes//abs(self.minutes) + div, mod = divmod(self.minutes*s, 60) + self.minutes = mod*s + self.hours += div*s + if abs(self.hours) > 23: + s = self.hours//abs(self.hours) + div, mod = divmod(self.hours*s, 24) + self.hours = mod*s + self.days += div*s + if abs(self.months) > 11: + s = self.months//abs(self.months) + div, mod = divmod(self.months*s, 12) + self.months = mod*s + self.years += div*s + if (self.hours or self.minutes or self.seconds or self.microseconds or + self.hour is not None or self.minute is not None or + self.second is not None or self.microsecond is not None): + self._has_time = 1 + else: + self._has_time = 0 + + def _set_months(self, months): + self.months = months + if abs(self.months) > 11: + s = self.months//abs(self.months) + div, mod = divmod(self.months*s, 12) + self.months = mod*s + self.years = div*s + else: + self.years = 0 + + def __radd__(self, other): + if not isinstance(other, datetime.date): + raise TypeError, "unsupported type for add operation" + elif self._has_time and not isinstance(other, datetime.datetime): + other = datetime.datetime.fromordinal(other.toordinal()) + year = (self.year or other.year)+self.years + month = self.month or other.month + if self.months: + assert 1 <= abs(self.months) <= 12 + month += self.months + if month > 12: + year += 1 + month -= 12 + elif month < 1: + year -= 1 + month += 12 + day = min(calendar.monthrange(year, month)[1], + self.day or other.day) + repl = {"year": year, "month": month, "day": day} + for attr in ["hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + repl[attr] = value + days = self.days + if self.leapdays and month > 2 and calendar.isleap(year): + days += self.leapdays + ret = (other.replace(**repl) + + datetime.timedelta(days=days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds, + microseconds=self.microseconds)) + if self.weekday: + weekday, nth = self.weekday.weekday, self.weekday.n or 1 + jumpdays = (abs(nth)-1)*7 + if nth > 0: + jumpdays += (7-ret.weekday()+weekday)%7 + else: + jumpdays += (ret.weekday()-weekday)%7 + jumpdays *= -1 + ret += datetime.timedelta(days=jumpdays) + return ret + + def __rsub__(self, other): + return self.__neg__().__radd__(other) + + def __add__(self, other): + if not isinstance(other, relativedelta): + raise TypeError, "unsupported type for add operation" + return relativedelta(years=other.years+self.years, + months=other.months+self.months, + days=other.days+self.days, + hours=other.hours+self.hours, + minutes=other.minutes+self.minutes, + seconds=other.seconds+self.seconds, + microseconds=other.microseconds+self.microseconds, + leapdays=other.leapdays or self.leapdays, + year=other.year or self.year, + month=other.month or self.month, + day=other.day or self.day, + weekday=other.weekday or self.weekday, + hour=other.hour or self.hour, + minute=other.minute or self.minute, + second=other.second or self.second, + microsecond=other.second or self.microsecond) + + def __sub__(self, other): + if not isinstance(other, relativedelta): + raise TypeError, "unsupported type for sub operation" + return relativedelta(years=other.years-self.years, + months=other.months-self.months, + days=other.days-self.days, + hours=other.hours-self.hours, + minutes=other.minutes-self.minutes, + seconds=other.seconds-self.seconds, + microseconds=other.microseconds-self.microseconds, + leapdays=other.leapdays or self.leapdays, + year=other.year or self.year, + month=other.month or self.month, + day=other.day or self.day, + weekday=other.weekday or self.weekday, + hour=other.hour or self.hour, + minute=other.minute or self.minute, + second=other.second or self.second, + microsecond=other.second or self.microsecond) + + def __neg__(self): + return relativedelta(years=-self.years, + months=-self.months, + days=-self.days, + hours=-self.hours, + minutes=-self.minutes, + seconds=-self.seconds, + microseconds=-self.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __nonzero__(self): + return not (not self.years and + not self.months and + not self.days and + not self.hours and + not self.minutes and + not self.seconds and + not self.microseconds and + not self.leapdays and + self.year is None and + self.month is None and + self.day is None and + self.weekday is None and + self.hour is None and + self.minute is None and + self.second is None and + self.microsecond is None) + + def __mul__(self, other): + f = float(other) + return relativedelta(years=self.years*f, + months=self.months*f, + days=self.days*f, + hours=self.hours*f, + minutes=self.minutes*f, + seconds=self.seconds*f, + microseconds=self.microseconds*f, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __eq__(self, other): + if not isinstance(other, relativedelta): + return False + if self.weekday or other.weekday: + if not self.weekday or not other.weekday: + return False + if self.weekday.weekday != other.weekday.weekday: + return False + n1, n2 = self.weekday.n, other.weekday.n + if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): + return False + return (self.years == other.years and + self.months == other.months and + self.days == other.days and + self.hours == other.hours and + self.minutes == other.minutes and + self.seconds == other.seconds and + self.leapdays == other.leapdays and + self.year == other.year and + self.month == other.month and + self.day == other.day and + self.hour == other.hour and + self.minute == other.minute and + self.second == other.second and + self.microsecond == other.microsecond) + + def __ne__(self, other): + return not self.__eq__(other) + + def __div__(self, other): + return self.__mul__(1/float(other)) + + def __repr__(self): + l = [] + for attr in ["years", "months", "days", "leapdays", + "hours", "minutes", "seconds", "microseconds"]: + value = getattr(self, attr) + if value: + l.append("%s=%+d" % (attr, value)) + for attr in ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, `value`)) + return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) diff --git a/resources/settings.xml b/resources/settings.xml index b115e9d..2ed67ef 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -18,5 +18,9 @@ + + + + diff --git a/scheduler.py b/scheduler.py new file mode 100644 index 0000000..96b727d --- /dev/null +++ b/scheduler.py @@ -0,0 +1,43 @@ +import xbmc +import time +import resources.lib.utils as utils +from resources.lib.backup import XbmcBackup + +class BackupScheduler: + enabled = "false" + + def __init__(self): + self.enabled = utils.getSetting("enable_scheduler") + + def start(self): + while(not xbmc.abortRequested): + if(self.enabled == "true"): + cron_exp = self.parseSchedule() + utils.log(cron_exp) + else: + utils.log("backup not enabled") + self.enabled = utils.getSetting("enable_scheduler") + + time.sleep(10) + + 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): + #every day + + cron_exp = "0 " + str(hour_of_day) + " * * *" + elif(schedule_type == 1): + #once a week + day_of_week = utils.getSetting("day_of_week") + cron_exp = "0 " + str(hour_of_day) + " * * " + day_of_week + elif(schedule_type == 2): + #first day of month + cron_exp = "0 " + str(hour_of_day) + " 1 * *" + + return cron_exp + +BackupScheduler().start() From 9bda2055b33dd27a5a121a8e68ac1013da9daa55 Mon Sep 17 00:00:00 2001 From: robweber Date: Thu, 6 Sep 2012 15:28:41 -0500 Subject: [PATCH 7/9] added call to XbmcBackup.run() in service.py file added showNotification() method so that a notification can display when backup starts via scheduler --- resources/lib/backup.py | 10 +++++----- resources/lib/utils.py | 3 +++ scheduler.py | 41 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/resources/lib/backup.py b/resources/lib/backup.py index 2e583af..149904d 100644 --- a/resources/lib/backup.py +++ b/resources/lib/backup.py @@ -106,9 +106,9 @@ class XbmcBackup: utils.log(utils.getString(30046)) - def run(self,mode=-1): + def run(self,mode=-1,runSilent=False): #check if we should use the progress bar - if(utils.getSetting('run_silent') == 'false'): + if(utils.getSetting('run_silent') == 'false' and not runSilent): self.progressBar = xbmcgui.DialogProgress() self.progressBar.create(utils.getString(30010),utils.getString(30049) + "......") @@ -147,6 +147,9 @@ class XbmcBackup: self.restoreFiles() else: xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_path) + + if(utils.getSetting('run_silent') == 'false' and not runSilent): + self.progressBar.close() def syncFiles(self): @@ -188,9 +191,6 @@ class XbmcBackup: else: vfs.copy(xbmc.makeLegalFilename(source + aFile),xbmc.makeLegalFilename(dest + aFile,False)) - if(utils.getSetting('run_silent') == 'false'): - self.progressBar.close() - def updateProgress(self,message=''): self.filesLeft = self.filesLeft - 1 diff --git a/resources/lib/utils.py b/resources/lib/utils.py index eace7d5..aa94723 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -8,6 +8,9 @@ __Addon = xbmcaddon.Addon(__addon_id__) def log(message,loglevel=xbmc.LOGNOTICE): xbmc.log(encode(__addon_id__ + ": " + message),level=loglevel) +def showNotification(message): + xbmc.executebuiltin("Notification(" + getString(30010) + "," + message + ",4000," + xbmc.translatePath(__Addon.getAddonInfo('path') + "/icon.png") + ")") + def getSetting(name): return __Addon.getSetting(name) diff --git a/scheduler.py b/scheduler.py index 96b727d..ecd0e76 100644 --- a/scheduler.py +++ b/scheduler.py @@ -1,25 +1,58 @@ import xbmc +import datetime import time import resources.lib.utils as utils +from resources.lib.croniter import croniter from resources.lib.backup import XbmcBackup class BackupScheduler: enabled = "false" - + next_run = 0 + def __init__(self): self.enabled = utils.getSetting("enable_scheduler") + + if(self.enabled == "true"): + 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()) + utils.log("scheduler will run again on " + datetime.datetime.fromtimestamp(self.next_run).strftime('%m-%d-%Y %H:%M')) def start(self): while(not xbmc.abortRequested): if(self.enabled == "true"): - cron_exp = self.parseSchedule() - utils.log(cron_exp) + now = time.time() + + if(self.next_run <= now): + if(utils.getSetting('run_silent') == 'false'): + utils.showNotification("Starting scheduled backup") + #run the job in backup mode, hiding the dialog box + backup = XbmcBackup() + backup.run(XbmcBackup.Backup,True) + + self.findNextRun(now) else: - utils.log("backup not enabled") self.enabled = utils.getSetting("enable_scheduler") + + if(self.enabled == "true"): + self.setup() time.sleep(10) + def findNextRun(self,now): + #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')) + def parseSchedule(self): schedule_type = int(utils.getSetting("schedule_interval")) cron_exp = utils.getSetting("cron_schedule") From 17a4e8ad3d8fe5d469918c103bf0afad759286c1 Mon Sep 17 00:00:00 2001 From: robweber Date: Wed, 12 Sep 2012 10:02:33 -0500 Subject: [PATCH 8/9] refactored code to use strings.xml --- resources/language/English/strings.xml | 19 +++++++++++++++++-- resources/settings.xml | 8 ++++---- scheduler.py | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 6ea2d39..5f31fab 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -31,7 +31,22 @@ Remote Path exists - may have old files in it! Creating Files List Writing file - + Starting scheduled backup + Enable Scheduler - + Schedule + Hour of Day + Day of Week + Cron Schedule + Sunday + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday + Every Day + Every Week + First Day of Month + Custom Schedule diff --git a/resources/settings.xml b/resources/settings.xml index 2ed67ef..d32a629 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -18,9 +18,9 @@ - - - - + + + + diff --git a/scheduler.py b/scheduler.py index ecd0e76..d453328 100644 --- a/scheduler.py +++ b/scheduler.py @@ -28,7 +28,7 @@ class BackupScheduler: if(self.next_run <= now): if(utils.getSetting('run_silent') == 'false'): - utils.showNotification("Starting scheduled backup") + utils.showNotification(utils.getString(30053)) #run the job in backup mode, hiding the dialog box backup = XbmcBackup() backup.run(XbmcBackup.Backup,True) From a066491757537b023ccf6b4cfa900ffd29436a6e Mon Sep 17 00:00:00 2001 From: robweber Date: Wed, 12 Sep 2012 10:18:18 -0500 Subject: [PATCH 9/9] update to changelog and version bump --- README.txt | 4 ++++ addon.xml | 4 ++-- changelog.txt | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index 750f69b..63faa3f 100644 --- a/README.txt +++ b/README.txt @@ -11,6 +11,10 @@ On the Backup Selection page you can select which items from your user profile f To restore your data simply switch the Mode from "backup" to "restore" and type the date of the backup you wish to restore from . The files will be copied from your remote directory to the local path. The file selection criteria will be used for the restore as well. +Scheduling: + +You can also schedule backups to be completed on a set interval via the scheduling area. When it is time for the backup to run it will be executed in the background. + What this Addon Will Not Do: This is not meant as an XBMC file sync solution. If you have multiple frontends you want to keep in sync this addon may work in a "poor man's" sort of way but it is not intended for that. diff --git a/addon.xml b/addon.xml index fc33fe0..338d24a 100644 --- a/addon.xml +++ b/addon.xml @@ -1,6 +1,6 @@  + name="XBMC Backup" version="0.1.6" provider-name="robweber"> @@ -14,7 +14,7 @@ Backup and restore your XBMC database and configuration files in the event of a crash or file corruption. Avez-vous déjà perdu votre configuration XBMC et espéré avoir fait une sauvegarde ? Maintenant, vous pouvez le faire en un simple click. Vous pouvez exporter vos bases de données, playlists, miniatures, addons et autres fichiers de configuration vers n'importe quel endroit accessible depuis XBMC. Jemals deine XBMC Konfiguration zerschossen und dir dann gewünscht, dass ein Backup existiert? Jetzt kannst du eine Sicherung mit nur einem Klick erzeugen. Du kannst deine Datenbanen, Playlisten, Thumbnails, Addons und andere Details zu einem Ort deiner Wahl sichern. - Ever hosed your XBMC configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by XBMC. + Ever hosed your XBMC configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by XBMC. Backups can be run on demand or via a scheduler. all diff --git a/changelog.txt b/changelog.txt index d48be4d..3d9005b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +[b]Version 0.1.6[/b] + +merged scheduler branch with master, can now schedule backups on an interval + [b]Version 0.1.5[/b] pulled xbmcbackup class into separate library