mirror of
https://github.com/robweber/xbmcbackup.git
synced 2024-12-22 14:05:23 +01:00
commit
b64088660d
@ -1,12 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="script.xbmcbackup"
|
||||
name="XBMC Backup" version="0.1.3" provider-name="robweber">
|
||||
name="XBMC Backup" version="0.1.5" provider-name="robweber">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.0"/>
|
||||
</requires>
|
||||
<extension point="xbmc.python.script" library="default.py">
|
||||
<provides>executable</provides>
|
||||
</extension>
|
||||
<extension point="xbmc.service" library="scheduler.py" start="startup" />
|
||||
<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="de">Die XBMC Datenbank sichern und bei Dateiverlust oder Beschädigung wiederherstellen.</summary>
|
||||
|
@ -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
|
||||
|
220
default.py
220
default.py
@ -1,223 +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:
|
||||
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 = ""
|
||||
|
||||
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(vfs.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(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))
|
||||
|
@ -3,7 +3,8 @@
|
||||
<string id="30010">XBMC Backup</string>
|
||||
<string id="30011">General</string>
|
||||
<string id="30012">File Selection</string>
|
||||
|
||||
<string id="30013">Scheduling</string>
|
||||
|
||||
<string id="30016">Backup</string>
|
||||
<string id="30017">Restore</string>
|
||||
<string id="30018">Browse Path</string>
|
||||
@ -30,5 +31,22 @@
|
||||
<string id="30050">Remote Path exists - may have old files in it!</string>
|
||||
<string id="30051">Creating Files List</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>
|
||||
|
210
resources/lib/backup.py
Normal file
210
resources/lib/backup.py
Normal file
@ -0,0 +1,210 @@
|
||||
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")
|
||||
|
||||
#fix slashes
|
||||
self.remote_path = self.remote_path.replace("\\","/")
|
||||
|
||||
#check if trailing slash is included
|
||||
if(self.remote_path[-1:] != "/"):
|
||||
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
|
||||
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)
|
||||
|
||||
if(utils.getSetting('run_silent') == 'false' and not runSilent):
|
||||
self.progressBar.close()
|
||||
|
||||
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))
|
||||
|
||||
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
|
308
resources/lib/croniter.py
Normal file
308
resources/lib/croniter.py
Normal 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
|
430
resources/lib/relativedelta.py
Normal file
430
resources/lib/relativedelta.py
Normal 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
24
resources/lib/utils.py
Normal 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')
|
@ -16,4 +16,11 @@
|
||||
<setting id="backup_thumbnails" type="bool" label="30034" default="true" />
|
||||
<setting id="backup_config" type="bool" label="30035" default="true" />
|
||||
</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>
|
||||
|
76
scheduler.py
Normal file
76
scheduler.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user