Merge branch 'master' into frodo-dev

Conflicts:
	addon.xml
	changelog.txt
	default.py
	resources/lib/backup.py
This commit is contained in:
robweber 2012-09-12 15:30:54 -05:00
commit 719a189de6
11 changed files with 950 additions and 294 deletions

View File

@ -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. 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: 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. 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.

View File

@ -7,13 +7,14 @@
<extension point="xbmc.python.script" library="default.py"> <extension point="xbmc.python.script" library="default.py">
<provides>executable</provides> <provides>executable</provides>
</extension> </extension>
<extension point="xbmc.service" library="scheduler.py" start="startup" />
<extension point="xbmc.addon.metadata"> <extension point="xbmc.addon.metadata">
<summary lang="fr">Sauvegarder et restaurer vos bases de données XBMC et vos fichiers de configuration en cas de crash ou de fichiers corrompus.</summary> <summary lang="fr">Sauvegarder et restaurer vos bases de données XBMC et vos fichiers de configuration en cas de crash ou de fichiers corrompus.</summary>
<summary lang="de">Die XBMC Datenbank sichern und bei Dateiverlust oder Beschädigung wiederherstellen.</summary> <summary lang="de">Die XBMC Datenbank sichern und bei Dateiverlust oder Beschädigung wiederherstellen.</summary>
<summary lang="en">Backup and restore your XBMC database and configuration files in the event of a crash or file corruption.</summary> <summary lang="en">Backup and restore your XBMC database and configuration files in the event of a crash or file corruption.</summary>
<description lang="fr">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.</description> <description lang="fr">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.</description>
<description lang="de">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.</description> <description lang="de">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.</description>
<description lang="en">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.</description> <description lang="en">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. </description>
<platform>all</platform> <platform>all</platform>
<language></language> <language></language>
</extension> </extension>

View File

@ -4,8 +4,22 @@ removed the vfs.py helper library
default.py file now uses xbmcvfs python library exclusively for listing directories and copy operations default.py file now uses xbmcvfs python library exclusively for listing directories and copy operations
[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
[b]Version 0.1.4[/b]
added more verbose error message for incorrect paths
[b]Version 0.1.3[/b] [b]Version 0.1.3[/b]
backup folder format - thanks zeroram
added German translations - thanks dersphere added German translations - thanks dersphere
removed need for separate verbose logging setting removed need for separate verbose logging setting
@ -63,3 +77,4 @@ Added progress bar and "silent" option for running on startup or as a script
[b]Version 0.0.2[/b] [b]Version 0.0.2[/b]
First version, should backup directories as needed First version, should backup directories as needed

View File

@ -1,235 +1,7 @@
import xbmc from resources.lib.backup import XbmcBackup
import xbmcaddon
import xbmcgui
import xbmcvfs
import os
import time
__addon_id__ = 'script.xbmcbackup'
__Addon = xbmcaddon.Addon(__addon_id__)
class FileManager:
walk_path = ''
addonDir = ''
fileArray = None
verbose_log = False
not_dir = ['.zip','.xsp','.rar']
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
dirs,configFiles = xbmcvfs.listdir(self.walk_path + "userdata/")
for aFile in configFiles:
if(aFile.endswith(".xml")):
self.addFile("userdata/" + aFile)
def walkTree(self,directory):
dirs,files = xbmcvfs.listdir(directory)
#create all the subdirs first
for aDir in dirs:
dirPath = xbmc.translatePath(directory + "/" + aDir)
file_ext = aDir.split('.')[-1]
self.addFile("-" + dirPath[len(self.walk_path):].decode("UTF-8"))
#catch for "non directory" type files
if (not any(file_ext in s for s in self.not_dir)):
self.walkTree(dirPath)
#copy all the files
for aFile in files:
filePath = xbmc.translatePath(directory + "/" + aFile)
self.addFile(filePath[len(self.walk_path):].decode("UTF-8"))
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:
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 + "/"
#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 = ""
self.local_path = self.local_path.decode("UTF-8")
self.local_path = self.local_path.decode("UTF-8")
log(self.addon.getLocalizedString(30046))
log(self.addon.getLocalizedString(30047) + ": " + self.local_path)
log(self.addon.getLocalizedString(30048) + ": " + self.remote_path)
def run(self):
#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):
self.fileManager = FileManager(self.local_path,self.addon.getAddonInfo('profile'))
#for backups check if remote path exists
if(xbmcvfs.exists(self.remote_path)):
#this will fail - need a disclaimer here
log(self.addon.getLocalizedString(30050))
self.syncFiles()
else:
self.fileManager = FileManager(self.remote_path,self.addon.getAddonInfo('profile'))
#for restores remote path must exist
if(xbmcvfs.exists(self.remote_path)):
self.restoreFiles()
else:
xbmcgui.Dialog().ok(self.addon.getLocalizedString(30010),self.addon.getLocalizedString(30045))
def syncFiles(self):
#make the remote directory
xbmcvfs.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("-")):
xbmcvfs.mkdir(xbmc.makeLegalFilename(dest + aFile[1:],False))
else:
xbmcvfs.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):
logM = encode(__Addon.getLocalizedString(30010) + ": " + message)
xbmc.log(logM,level=loglevel)
def encode(string):
return string.encode('UTF-8','replace')
#run the profile backup #run the profile backup
backup = XbmcBackup(__Addon) backup = XbmcBackup()
if(backup.isReady()): if(backup.isReady()):
backup.run() backup.run()
else:
xbmcgui.Dialog().ok(__Addon.getLocalizedString(30010),__Addon.getLocalizedString(30045))

View File

@ -3,6 +3,7 @@
<string id="30010">XBMC Backup</string> <string id="30010">XBMC Backup</string>
<string id="30011">General</string> <string id="30011">General</string>
<string id="30012">File Selection</string> <string id="30012">File Selection</string>
<string id="30013">Scheduling</string>
<string id="30016">Backup</string> <string id="30016">Backup</string>
<string id="30017">Restore</string> <string id="30017">Restore</string>
@ -30,5 +31,22 @@
<string id="30050">Remote Path exists - may have old files in it!</string> <string id="30050">Remote Path exists - may have old files in it!</string>
<string id="30051">Creating Files List</string> <string id="30051">Creating Files List</string>
<string id="30052">Writing file</string> <string id="30052">Writing file</string>
<string id="30053">Starting scheduled backup</string>
<string id="30060">Enable Scheduler</string>
<string id="30061">Schedule</string>
<string id="30062">Hour of Day</string>
<string id="30063">Day of Week</string>
<string id="30064">Cron Schedule</string>
<string id="30065">Sunday</string>
<string id="30066">Monday</string>
<string id="30067">Tuesday</string>
<string id="30068">Wednesday</string>
<string id="30069">Thursday</string>
<string id="30070">Friday</string>
<string id="30071">Saturday</string>
<string id="30072">Every Day</string>
<string id="30073">Every Week</string>
<string id="30074">First Day of Month</string>
<string id="30075">Custom Schedule</string>
</strings> </strings>

View File

@ -1,53 +1,46 @@
import xbmc import xbmc
import xbmcgui import xbmcgui
import vfs as vfs import xbmcvfs
import utils as utils
import os import os
import time import time
class FileManager: class FileManager:
walk_path = '' walk_path = ''
addonDir = ''
fileArray = None fileArray = None
verbose_log = False verbose_log = False
not_dir = ['.zip','.xsp','.rar'] not_dir = ['.zip','.xsp','.rar']
def __init__(self,path,addon_dir): def __init__(self,path):
self.walk_path = path self.walk_path = path
self.addonDir = addon_dir
#create the addon folder if it doesn't exist def createFileList(self):
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.fileArray = []
self.verbose_log = Addon.getSetting("verbose_log") == 'true' self.verbose_log = utils.getSetting("verbose_log") == 'true'
#figure out which syncing options to run #figure out which syncing options to run
if(Addon.getSetting('backup_addons') == 'true'): if(utils.getSetting('backup_addons') == 'true'):
self.addFile("-addons") self.addFile("-addons")
self.walkTree(self.walk_path + "addons") self.walkTree(self.walk_path + "addons/")
self.addFile("-userdata") self.addFile("-userdata")
if(Addon.getSetting('backup_addon_data') == 'true'): if(utils.getSetting('backup_addon_data') == 'true'):
self.addFile("-userdata/addon_data") self.addFile("-userdata/addon_data")
self.walkTree(self.walk_path + "userdata/addon_data") self.walkTree(self.walk_path + "userdata/addon_data/")
if(Addon.getSetting('backup_database') == 'true'): if(utils.getSetting('backup_database') == 'true'):
self.addFile("-userdata/Database") self.addFile("-userdata/Database")
self.walkTree(self.walk_path + "userdata/Database") self.walkTree(self.walk_path + "userdata/Database")
if(Addon.getSetting("backup_playlists") == 'true'): if(utils.getSetting("backup_playlists") == 'true'):
self.addFile("-userdata/playlists") self.addFile("-userdata/playlists")
self.walkTree(self.walk_path + "userdata/playlists") self.walkTree(self.walk_path + "userdata/playlists")
if(Addon.getSetting("backup_thumbnails") == "true"): if(utils.getSetting("backup_thumbnails") == "true"):
self.addFile("-userdata/Thumbnails") self.addFile("-userdata/Thumbnails")
self.walkTree(self.walk_path + "userdata/Thumbnails") self.walkTree(self.walk_path + "userdata/Thumbnails")
if(Addon.getSetting("backup_config") == "true"): if(utils.getSetting("backup_config") == "true"):
self.addFile("-userdata/keymaps") self.addFile("-userdata/keymaps")
self.walkTree(self.walk_path + "userdata/keymaps") self.walkTree(self.walk_path + "userdata/keymaps")
@ -80,14 +73,17 @@ class FileManager:
def addFile(self,filename): def addFile(self,filename):
#write the full remote path name of this file #write the full remote path name of this file
log("Add File: " + filename,xbmc.LOGDEBUG) utils.log("Add File: " + filename,xbmc.LOGDEBUG)
self.fileArray.append(filename) self.fileArray.append(filename)
def getFileList(self): def getFileList(self):
return self.fileArray return self.fileArray
class XbmcBackup: class XbmcBackup:
addon = None #constants for initiating a back or restore
Backup = 0
Restore = 1
local_path = '' local_path = ''
remote_path = '' remote_path = ''
restoreFile = None restoreFile = None
@ -99,68 +95,76 @@ class XbmcBackup:
fileManager = None fileManager = None
def __init__(self,__Addon): def __init__(self):
self.addon = __Addon
self.local_path = xbmc.makeLegalFilename(xbmc.translatePath("special://home"),False); self.local_path = xbmc.makeLegalFilename(xbmc.translatePath("special://home"),False);
if(self.addon.getSetting('remote_selection') == '1'): if(utils.getSetting('remote_selection') == '1'):
self.remote_path = self.addon.getSetting('remote_path_2') self.remote_path = utils.getSetting('remote_path_2')
self.addon.setSetting("remote_path","") utils.setSetting("remote_path","")
elif(self.addon.getSetting('remote_selection') == '0'): elif(utils.getSetting('remote_selection') == '0'):
self.remote_path = self.addon.getSetting("remote_path") self.remote_path = utils.getSetting("remote_path")
#fix slashes
self.remote_path = self.remote_path.replace("\\","/")
#check if trailing slash is included #check if trailing slash is included
if(self.remote_path[-1:] != "/"): if(self.remote_path[-1:] != "/"):
self.remote_path = self.remote_path + "/" self.remote_path = self.remote_path + "/"
utils.log(utils.getString(30046))
def run(self,mode=-1,runSilent=False):
#check if we should use the progress bar
if(utils.getSetting('run_silent') == 'false' and not runSilent):
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 #append backup folder name
if(int(self.addon.getSetting('addon_mode')) == 0 and self.remote_path != ''): if(mode == self.Backup and self.remote_path != ''):
date_today = time.localtime(time.time()) self.remote_path = self.remote_path + time.strftime("%Y%m%d") + "/"
self.remote_path = self.remote_path + str(date_today[1]) + str(date_today[2]) + str(date_today[0]) + "/" elif(mode == self.Restore and utils.getSetting("backup_name") != '' and self.remote_path != ''):
elif(int(self.addon.getSetting('addon_mode')) == 1 and self.addon.getSetting("backup_name") != '' and self.remote_path != ''): self.remote_path = self.remote_path + utils.getSetting("backup_name") + "/"
self.remote_path = self.remote_path + self.addon.getSetting("backup_name") + "/"
else: else:
self.remote_path = "" self.remote_path = ""
self.local_path = self.local_path.decode("UTF-8") utils.log(utils.getString(30047) + ": " + self.local_path)
self.local_path = self.local_path.decode("UTF-8") utils.log(utils.getString(30048) + ": " + self.remote_path)
log(self.addon.getLocalizedString(30046)) #run the correct mode
log(self.addon.getLocalizedString(30047) + ": " + self.local_path) if(mode == self.Backup):
log(self.addon.getLocalizedString(30048) + ": " + self.remote_path) utils.log(utils.getString(30023) + " - " + utils.getString(30016))
self.fileManager = FileManager(self.local_path)
def run(self):
#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):
self.fileManager = FileManager(self.local_path,self.addon.getAddonInfo('profile'))
#for backups check if remote path exists #for backups check if remote path exists
if(xbmcvfs.exists(self.remote_path)): if(xbmcvfs.exists(self.remote_path)):
#this will fail - need a disclaimer here #this will fail - need a disclaimer here
log(self.addon.getLocalizedString(30050)) utils.log(utils.getString(30050))
self.syncFiles() self.syncFiles()
else: else:
self.fileManager = FileManager(self.remote_path,self.addon.getAddonInfo('profile')) utils.log(utils.getString(30023) + " - " + utils.getString(30017))
self.fileManager = FileManager(self.remote_path)
#for restores remote path must exist #for restores remote path must exist
if(xbmcvfs.exists(self.remote_path)): if(xbmcvfs.exists(self.remote_path)):
self.restoreFiles() self.restoreFiles()
else: else:
xbmcgui.Dialog().ok(self.addon.getLocalizedString(30010),self.addon.getLocalizedString(30045)) 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): def syncFiles(self):
#make the remote directory #make the remote directory
xbmcvfs.mkdir(self.remote_path) xbmcvfs.mkdir(self.remote_path)
log(self.addon.getLocalizedString(30051)) utils.log(utils.getString(30051))
self.fileManager.createFileList(self.addon) self.fileManager.createFileList()
allFiles = self.fileManager.getFileList() allFiles = self.fileManager.getFileList()
@ -168,9 +172,9 @@ class XbmcBackup:
self.writeFiles(allFiles,self.local_path,self.remote_path) self.writeFiles(allFiles,self.local_path,self.remote_path)
def restoreFiles(self): def restoreFiles(self):
self.fileManager.createFileList(self.addon) self.fileManager.createFileList()
log(self.addon.getLocalizedString(30051)) utils.log(utils.getString(30051))
allFiles = self.fileManager.getFileList() allFiles = self.fileManager.getFileList()
#write list from remote to local #write list from remote to local
@ -180,23 +184,20 @@ class XbmcBackup:
xbmc.executebuiltin('UpdateLocalAddons') xbmc.executebuiltin('UpdateLocalAddons')
def writeFiles(self,fileList,source,dest): def writeFiles(self,fileList,source,dest):
log("Writing files to: " + dest) utils.log("Writing files to: " + dest)
self.filesTotal = len(fileList) self.filesTotal = len(fileList)
self.filesLeft = self.filesTotal self.filesLeft = self.filesTotal
#write each file from source to destination #write each file from source to destination
for aFile in fileList: for aFile in fileList:
if(not self.checkCancel()): if(not self.checkCancel()):
log('Writing file: ' + source + aFile,xbmc.LOGDEBUG) utils.log('Writing file: ' + source + aFile,xbmc.LOGDEBUG)
self.updateProgress(aFile) self.updateProgress(aFile)
if (aFile.startswith("-")): if (aFile.startswith("-")):
xbmcvfs.mkdir(xbmc.makeLegalFilename(dest + aFile[1:],False)) xbmcvfs.mkdir(xbmc.makeLegalFilename(dest + aFile[1:],False))
else: else:
xbmcvfs.copy(xbmc.makeLegalFilename(source + aFile),xbmc.makeLegalFilename(dest + aFile,False)) xbmcvfs.copy(xbmc.makeLegalFilename(source + aFile),xbmc.makeLegalFilename(dest + aFile,False))
if(self.addon.getSetting('run_silent') == 'false'):
self.progressBar.close()
def updateProgress(self,message=''): def updateProgress(self,message=''):
self.filesLeft = self.filesLeft - 1 self.filesLeft = self.filesLeft - 1

308
resources/lib/croniter.py Normal file
View File

@ -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

View File

@ -0,0 +1,430 @@
"""
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
This module offers extensions to the standard python 2.3+
datetime module.
"""
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
__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))

24
resources/lib/utils.py Normal file
View File

@ -0,0 +1,24 @@
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 showNotification(message):
xbmc.executebuiltin("Notification(" + getString(30010) + "," + message + ",4000," + xbmc.translatePath(__Addon.getAddonInfo('path') + "/icon.png") + ")")
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')

View File

@ -16,4 +16,11 @@
<setting id="backup_thumbnails" type="bool" label="30034" default="true" /> <setting id="backup_thumbnails" type="bool" label="30034" default="true" />
<setting id="backup_config" type="bool" label="30035" default="true" /> <setting id="backup_config" type="bool" label="30035" default="true" />
</category> </category>
<category id="scheduling" label="30013">
<setting id="enable_scheduler" type="bool" label="30060" default="false" />
<setting id="schedule_interval" type="enum" label="30061" lvalues="30072|30073|30074|30075" default="0" enable="eq(-1,true)"/>
<setting id="schedule_time" type="labelenum" label="30062" values="00:00|01:00|02:00|03:00|04:00|05:00|06:00|07:00|08:00|09:00|10:00|11:00|12:00|13:00|14:00|15:00|16:00|17:00|18:00|19:00|20:00|21:00|22:00|23:00" default="00:00" visible="!eq(-1,3)" enable="eq(-2,true)"/>
<setting id="day_of_week" type="enum" label="30063" lvalues="30065|30066|30067|30068|30069|30070|30071" default="0" visible="eq(-2,1)" enable="eq(-3,true)"/>
<setting id="cron_schedule" type="text" label="30064" default="0 0 * * *" visible="eq(-3,3)" enable="eq(-4,true)"/>
</category>
</settings> </settings>

76
scheduler.py Normal file
View File

@ -0,0 +1,76 @@
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"):
now = time.time()
if(self.next_run <= now):
if(utils.getSetting('run_silent') == 'false'):
utils.showNotification(utils.getString(30053))
#run the job in backup mode, hiding the dialog box
backup = XbmcBackup()
backup.run(XbmcBackup.Backup,True)
self.findNextRun(now)
else:
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")
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()