Update for Leia (#117)
* updated addon.xml for Krypton * default log level is always debug now * added screenshots per krypton format * started new way of defining backup directories * reconfigured simple backup process * added an advanced backup editor and combined settings.xml scripts into a launcher * added strings for advanced editor * there was a function to do this * match excluded with regex * updated def for the addons set * directory has to end in slash to use exists() * added a backup set chooser on restore * added string for restore browser * utilize details to show root folder and icons * save non translated paths, better cross platform support * revert dropbox python 2.6 changes * start of #132 * can't have duplicate ids * updated strings * closes #132 * added a disclaimer for breaking changes * split backup and restore into separate functions * updated scripting to pass in list of sets to restore * beta version * added 2 min delay in startup - part of #147 * forgot to remove debug message * change to wait for abort in case someone tries to close Kodi * add retroplayer game saves to default file list * display restore points with most recent on top * remove length check, breaking change with this version means old archives are no longer compatible * format restore list according to regional settings * this function isn't used anymore, legacy of old file manager * use images folder as default * added note about compatibility * added utils function for regional date, use for scheduler notifications as well * add/remove include and exclude directories to a set * paths should have / at the end * show path relative to root * if in advanced mode allow jumping to editor from launch screen * check that path is within root folder of set * cannot have duplicate set names or rules regarding folders within a set * put strings in correct lang file * beta version bump * accidentally deleted string id * change exclude criteria. Regex was not matching in complex cases * make sure the dest folder (backup set root) exists before writing to it * modify select display to show recursive value for included folders * use a context menu here * added ability to toggle recursion of sub folders * beta 3 * added support doc * wrong branch * don't need this import anymore * don't need these imports * part of #133
@ -1,5 +1,7 @@
|
||||
# Backup Addon
|
||||
|
||||
__Kodi Version Compatibility:__ Kodi 17.x (Krypton) and greater
|
||||
|
||||
## About
|
||||
|
||||
I've had to recover my database, thumbnails, and source configuration enough times that I just wanted a quick easy way to back them up. That is what this addon is meant to do.
|
||||
@ -17,6 +19,11 @@ For more specific information please check out the [wiki on Github](https://gith
|
||||
* [FAQ](https://github.com/robweber/xbmcbackup/wiki/FAQ)
|
||||
|
||||
|
||||
## Attributions
|
||||
|
||||
Icon files from Open Iconic — www.useiconic.com/open
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
10
addon.xml
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="script.xbmcbackup"
|
||||
name="Backup" version="1.1.3" provider-name="robweber">
|
||||
name="Backup" version="1.5.0~beta3" provider-name="robweber">
|
||||
<requires>
|
||||
<!-- jarvis -->
|
||||
<import addon="xbmc.python" version="2.24.0"/>
|
||||
<import addon="xbmc.python" version="2.25.0"/>
|
||||
<import addon="script.module.httplib2" version="0.8.0" />
|
||||
<import addon="script.module.oauth2client" version="4.1.2" />
|
||||
<import addon="script.module.uritemplate" version="0.6" />
|
||||
@ -89,7 +89,11 @@
|
||||
<source>https://github.com/robweber/xbmcbackup</source>
|
||||
<email></email>
|
||||
<assets>
|
||||
<icon>resources/media/icon.png</icon>
|
||||
<icon>resources/images/icon.png</icon>
|
||||
<screenshot>resources/images/screenshot1.png</screenshot>
|
||||
<screenshot>resources/images/screenshot2.png</screenshot>
|
||||
<screenshot>resources/images/screenshot3.png</screenshot>
|
||||
<screenshot>resources/images/screenshot4.png</screenshot>
|
||||
</assets>
|
||||
<news>Version 1.1.4
|
||||
- added file chunk support for dropbox uploads
|
||||
|
@ -1,37 +0,0 @@
|
||||
import sys
|
||||
import urlparse
|
||||
import xbmcgui
|
||||
import resources.lib.utils as utils
|
||||
from resources.lib.authorizers import DropboxAuthorizer,GoogleDriveAuthorizer
|
||||
|
||||
def get_params():
|
||||
param = {}
|
||||
try:
|
||||
for i in sys.argv:
|
||||
args = i
|
||||
if(args.startswith('?')):
|
||||
args = args[1:]
|
||||
param.update(dict(urlparse.parse_qsl(args)))
|
||||
except:
|
||||
pass
|
||||
return param
|
||||
|
||||
params = get_params()
|
||||
|
||||
#drobpox
|
||||
if(params['type'] == 'dropbox'):
|
||||
authorizer = DropboxAuthorizer()
|
||||
|
||||
if(authorizer.authorize()):
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30027) + ' ' + utils.getString(30106))
|
||||
else:
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30107) + ' ' + utils.getString(30027))
|
||||
|
||||
#google drive
|
||||
elif(params['type'] == 'google_drive'):
|
||||
authorizer = GoogleDriveAuthorizer()
|
||||
|
||||
if(authorizer.authorize()):
|
||||
xbmcgui.Dialog().ok("Backup",utils.getString(30098) + ' ' + utils.getString(30106))
|
||||
else:
|
||||
xbmcgui.Dialog().ok("Backup",utils.getString(30107) + ' ' + utils.getString(30098))
|
24
default.py
@ -1,5 +1,5 @@
|
||||
import urlparse
|
||||
import xbmcgui
|
||||
import sys, urlparse
|
||||
import xbmc, xbmcgui
|
||||
import resources.lib.utils as utils
|
||||
from resources.lib.backup import XbmcBackup
|
||||
|
||||
@ -28,8 +28,15 @@ if("mode" in params):
|
||||
|
||||
#if mode wasn't passed in as arg, get from user
|
||||
if(mode == -1):
|
||||
#by default, Backup,Restore,Open Settings
|
||||
options = [utils.getString(30016),utils.getString(30017),utils.getString(30099)]
|
||||
|
||||
#find out if we're using the advanced editor
|
||||
if(int(utils.getSetting('backup_selection_type')) == 1):
|
||||
options.append(utils.getString(30125))
|
||||
|
||||
#figure out if this is a backup or a restore from the user
|
||||
mode = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30023),[utils.getString(30016),utils.getString(30017),utils.getString(30099)])
|
||||
mode = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30023),options)
|
||||
|
||||
#check if program should be run
|
||||
if(mode != -1):
|
||||
@ -39,7 +46,9 @@ if(mode != -1):
|
||||
if(mode == 2):
|
||||
#open the settings dialog
|
||||
utils.openSettings()
|
||||
|
||||
elif(mode == 3 and int(utils.getSetting('backup_selection_type')) == 1):
|
||||
#open the advanced editor
|
||||
xbmc.executebuiltin('RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_editor)')
|
||||
elif(backup.remoteConfigured()):
|
||||
|
||||
if(mode == backup.Restore):
|
||||
@ -70,7 +79,12 @@ if(mode != -1):
|
||||
if(selectedRestore != -1):
|
||||
backup.selectRestore(restorePoints[selectedRestore][0])
|
||||
|
||||
backup.run(mode)
|
||||
if('sets' in params):
|
||||
backup.restore(selectedSets=params['sets'].split('|'))
|
||||
else:
|
||||
backup.restore()
|
||||
else:
|
||||
backup.backup()
|
||||
else:
|
||||
#can't go any further
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045))
|
||||
|
64
launcher.py
Normal file
@ -0,0 +1,64 @@
|
||||
import sys
|
||||
import urlparse
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcvfs
|
||||
import resources.lib.utils as utils
|
||||
from resources.lib.authorizers import DropboxAuthorizer,GoogleDriveAuthorizer
|
||||
from resources.lib.advanced_editor import AdvancedBackupEditor
|
||||
|
||||
|
||||
#launcher for various helpful functions found in the settings.xml area
|
||||
|
||||
def authorize_cloud(cloudProvider):
|
||||
#drobpox
|
||||
if(cloudProvider == 'dropbox'):
|
||||
authorizer = DropboxAuthorizer()
|
||||
|
||||
if(authorizer.authorize()):
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30027) + ' ' + utils.getString(30106))
|
||||
else:
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30107) + ' ' + utils.getString(30027))
|
||||
|
||||
#google drive
|
||||
elif(cloudProvider == 'google_drive'):
|
||||
authorizer = GoogleDriveAuthorizer()
|
||||
|
||||
if(authorizer.authorize()):
|
||||
xbmcgui.Dialog().ok("Backup",utils.getString(30098) + ' ' + utils.getString(30106))
|
||||
else:
|
||||
xbmcgui.Dialog().ok("Backup",utils.getString(30107) + ' ' + utils.getString(30098))
|
||||
|
||||
def remove_auth():
|
||||
#triggered from settings.xml - asks if user wants to delete OAuth token information
|
||||
shouldDelete = xbmcgui.Dialog().yesno(utils.getString(30093),utils.getString(30094),utils.getString(30095),autoclose=7000)
|
||||
|
||||
if(shouldDelete):
|
||||
#delete any of the known token file types
|
||||
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "tokens.txt")) #dropbox
|
||||
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "google_drive.dat")) #google drive
|
||||
|
||||
def get_params():
|
||||
param = {}
|
||||
try:
|
||||
for i in sys.argv:
|
||||
args = i
|
||||
if(args.startswith('?')):
|
||||
args = args[1:]
|
||||
param.update(dict(urlparse.parse_qsl(args)))
|
||||
except:
|
||||
pass
|
||||
return param
|
||||
|
||||
params = get_params()
|
||||
|
||||
if(params['action'] == 'authorize_cloud'):
|
||||
authorize_cloud(params['provider'])
|
||||
elif(params['action'] == 'remove_auth'):
|
||||
remove_auth()
|
||||
elif(params['action'] == 'advanced_editor'):
|
||||
editor = AdvancedBackupEditor()
|
||||
editor.showMainScreen()
|
||||
elif(params['action'] == 'advanced_copy_config'):
|
||||
editor = AdvancedBackupEditor()
|
||||
editor.copySimpleConfig()
|
@ -1,13 +0,0 @@
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcvfs
|
||||
import resources.lib.utils as utils
|
||||
|
||||
#triggered from settings.xml - asks if user wants to delete OAuth token information
|
||||
shouldDelete = xbmcgui.Dialog().yesno(utils.getString(30093),utils.getString(30094),utils.getString(30095),autoclose=7000)
|
||||
|
||||
if(shouldDelete):
|
||||
#delete any of the known token file types
|
||||
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "tokens.txt")) #dropbox
|
||||
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "google_drive.dat")) #google drive
|
||||
|
105
resources/data/default_files.json
Normal file
@ -0,0 +1,105 @@
|
||||
{
|
||||
"addons":{
|
||||
"root":"special://home/addons/",
|
||||
"dirs":[
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/addons/",
|
||||
"recurse":true
|
||||
},
|
||||
{
|
||||
"type":"exclude",
|
||||
"path":"special://home/addons/packages/"
|
||||
},
|
||||
{
|
||||
"type":"exclude",
|
||||
"path":"special://home/addons/temp/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"addon_data":{
|
||||
"root":"special://home/userdata/addon_data/",
|
||||
"dirs":[
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/addon_data/",
|
||||
"recurse":true
|
||||
}
|
||||
]
|
||||
},
|
||||
"database":{
|
||||
"root":"special://home/userdata/Database/",
|
||||
"dirs":[
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/Database/",
|
||||
"recurse":true
|
||||
}
|
||||
]
|
||||
},
|
||||
"game_saves":{
|
||||
"root":"special://home/userdata/Savestates/",
|
||||
"dirs":[
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/Savestates/",
|
||||
"recurse":true
|
||||
}
|
||||
]
|
||||
},
|
||||
"playlists":{
|
||||
"root":"special://home/userdata/playlists/",
|
||||
"dirs":[
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/playlists/",
|
||||
"recurse":true
|
||||
}
|
||||
]
|
||||
},
|
||||
"profiles":{
|
||||
"root":"special://home/userdata/profiles/",
|
||||
"dirs":[
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/profiles/",
|
||||
"recurse":true
|
||||
}
|
||||
]
|
||||
},
|
||||
"thumbnails":{
|
||||
"root":"special://home/userdata/Thumbnails/",
|
||||
"dirs":[
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/Thumbnails/",
|
||||
"recurse":true
|
||||
}
|
||||
]
|
||||
},
|
||||
"config":{
|
||||
"root":"special://home/userdata/",
|
||||
"dirs":[
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/",
|
||||
"recurse":false
|
||||
},
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/keymaps/",
|
||||
"recurse":true
|
||||
},
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/peripheral_data/",
|
||||
"recurse":true
|
||||
},
|
||||
{
|
||||
"type":"include",
|
||||
"path":"special://home/userdata/library/",
|
||||
"recurse":true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
BIN
resources/images/folder-icon.png
Normal file
After Width: | Height: | Size: 226 B |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
BIN
resources/images/plus-icon.png
Normal file
After Width: | Height: | Size: 196 B |
BIN
resources/images/screenshot1.PNG
Normal file
After Width: | Height: | Size: 125 KiB |
BIN
resources/images/screenshot2.PNG
Normal file
After Width: | Height: | Size: 129 KiB |
BIN
resources/images/screenshot3.PNG
Normal file
After Width: | Height: | Size: 270 KiB |
BIN
resources/images/screenshot4.PNG
Normal file
After Width: | Height: | Size: 188 KiB |
@ -48,6 +48,14 @@ msgctxt "#30013"
|
||||
msgid "Scheduling"
|
||||
msgstr "Scheduling"
|
||||
|
||||
msgctxt "#30014"
|
||||
msgid "Simple"
|
||||
msgstr "Simple"
|
||||
|
||||
msgctxt "#30015"
|
||||
msgid "Advanced"
|
||||
msgstr "Advanced"
|
||||
|
||||
msgctxt "#30016"
|
||||
msgid "Backup"
|
||||
msgstr "Backup"
|
||||
@ -129,12 +137,12 @@ msgid "Config Files"
|
||||
msgstr "Config Files"
|
||||
|
||||
msgctxt "#30036"
|
||||
msgid "Custom Directory 1"
|
||||
msgstr "Custom Directory 1"
|
||||
msgid "Disclaimer"
|
||||
msgstr "Disclaimer"
|
||||
|
||||
msgctxt "#30037"
|
||||
msgid "Custom Directory 2"
|
||||
msgstr "Custom Directory 2"
|
||||
msgid "Canceling this menu will close and save changes"
|
||||
msgstr "Canceling this menu will close and save changes"
|
||||
|
||||
msgctxt "#30038"
|
||||
msgid "Advanced Settings Detected"
|
||||
@ -420,3 +428,131 @@ msgstr "Visit https://console.developers.google.com/"
|
||||
msgctxt "#30109"
|
||||
msgid "Run on startup if missed"
|
||||
msgstr "Run on startup if missed"
|
||||
|
||||
msgctxt "#30110"
|
||||
msgid "Set Name"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30111"
|
||||
msgid "Root folder selection"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Browse Folder"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30113"
|
||||
msgid "Enter Own"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30114"
|
||||
msgid "starts in Kodi home"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30115"
|
||||
msgid "enter path to start there"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30116"
|
||||
msgid "Enter root path"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30117"
|
||||
msgid "Path Error"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30118"
|
||||
msgid "Path does not exist"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30119"
|
||||
msgid "Select root"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30120"
|
||||
msgid "Add Exclude Folder"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30121"
|
||||
msgid "Root Folder"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30122"
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30123"
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30124"
|
||||
msgid "Choose Action"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30125"
|
||||
msgid "Advanced Editor"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30126"
|
||||
msgid "Add Set"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30127"
|
||||
msgid "Delete Set"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30128"
|
||||
msgid "Are you sure you want to delete?"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30129"
|
||||
msgid "Exclude"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30130"
|
||||
msgid "The root folder cannot be changed"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30131"
|
||||
msgid "Choose Sets to Restore"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30132"
|
||||
msgid "Version 1.1.4 requires you to setup your file selections again - this is a breaking change"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30133"
|
||||
msgid "Game Saves"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30134"
|
||||
msgid "Include"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30135"
|
||||
msgid "Add Include Folder"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30136"
|
||||
msgid "Path must be within root folder"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30137"
|
||||
msgid "This path is part of a rule already"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30138"
|
||||
msgid "Set Name exists already"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30139"
|
||||
msgid "Copy Simple Config"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30140"
|
||||
msgid "This will copy the default Simple file selection to the Advanced Editor"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30141"
|
||||
msgid "This will erase any current Advanced Editor settings"
|
||||
msgstr ""
|
@ -129,14 +129,6 @@ msgctxt "#30035"
|
||||
msgid "Config Files"
|
||||
msgstr "Config Files"
|
||||
|
||||
msgctxt "#30036"
|
||||
msgid "Custom Directory 1"
|
||||
msgstr "Custom Directory 1"
|
||||
|
||||
msgctxt "#30037"
|
||||
msgid "Custom Directory 2"
|
||||
msgstr "Custom Directory 2"
|
||||
|
||||
msgctxt "#30038"
|
||||
msgid "Advanced Settings Detected"
|
||||
msgstr "Advanced Settings Detected"
|
||||
|
229
resources/lib/advanced_editor.py
Normal file
@ -0,0 +1,229 @@
|
||||
import json
|
||||
import utils as utils
|
||||
import xbmcvfs
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
class BackupSetManager:
|
||||
jsonFile = xbmc.translatePath(utils.data_dir() + "custom_paths.json")
|
||||
paths = None
|
||||
|
||||
def __init__(self):
|
||||
self.paths = {}
|
||||
|
||||
#try and read in the custom file
|
||||
self._readFile()
|
||||
|
||||
def addSet(self,aSet):
|
||||
self.paths[aSet['name']] = {'root':aSet['root'],'dirs':[{"type":"include","path":aSet['root'],'recurse':True}]}
|
||||
|
||||
#save the file
|
||||
self._writeFile()
|
||||
|
||||
def updateSet(self,name,aSet):
|
||||
self.paths[name] = aSet
|
||||
|
||||
#save the file
|
||||
self._writeFile()
|
||||
|
||||
def deleteSet(self,index):
|
||||
#match the index to a key
|
||||
keys = self.getSets()
|
||||
|
||||
#delete this set
|
||||
del self.paths[keys[index]]
|
||||
|
||||
#save the file
|
||||
self._writeFile()
|
||||
|
||||
def getSets(self):
|
||||
#list all current sets by name
|
||||
keys = self.paths.keys()
|
||||
keys.sort()
|
||||
|
||||
return keys
|
||||
|
||||
def getSet(self,index):
|
||||
keys = self.getSets();
|
||||
|
||||
#return the set at this index
|
||||
return {'name':keys[index],'set':self.paths[keys[index]]}
|
||||
|
||||
def validateSetName(self,name):
|
||||
return (name not in self.getSets())
|
||||
|
||||
def _writeFile(self):
|
||||
#create the custom file
|
||||
aFile = xbmcvfs.File(self.jsonFile,'w')
|
||||
aFile.write(json.dumps(self.paths))
|
||||
aFile.close()
|
||||
|
||||
def _readFile(self):
|
||||
|
||||
if(xbmcvfs.exists(self.jsonFile)):
|
||||
|
||||
#read in the custom file
|
||||
aFile = xbmcvfs.File(self.jsonFile)
|
||||
|
||||
#load custom dirs
|
||||
self.paths = json.loads(aFile.read())
|
||||
aFile.close()
|
||||
else:
|
||||
#write a blank file
|
||||
self._writeFile()
|
||||
|
||||
class AdvancedBackupEditor:
|
||||
dialog = None
|
||||
|
||||
def __init__(self):
|
||||
self.dialog = xbmcgui.Dialog()
|
||||
|
||||
def _cleanPath(self,root,path):
|
||||
return path[len(root)-1:]
|
||||
|
||||
def _validatePath(self,root,path):
|
||||
return path.startswith(root)
|
||||
|
||||
def createSet(self):
|
||||
backupSet = None
|
||||
|
||||
name = self.dialog.input(utils.getString(30110),defaultt='Backup Set')
|
||||
|
||||
if(name != None):
|
||||
|
||||
#give a choice to start in home or enter a root path
|
||||
enterHome = self.dialog.yesno(utils.getString(30111),line1=utils.getString(30112) + " - " + utils.getString(30114),line2=utils.getString(30113) + " - " + utils.getString(30115),nolabel=utils.getString(30112),yeslabel=utils.getString(30113))
|
||||
|
||||
rootFolder = 'special://home'
|
||||
if(enterHome):
|
||||
rootFolder = self.dialog.input(utils.getString(30116),defaultt=rootFolder)
|
||||
|
||||
#direcotry has to end in slash
|
||||
if(rootFolder[:-1] != '/'):
|
||||
rootFolder = rootFolder + '/'
|
||||
|
||||
#check that this path even exists
|
||||
if(not xbmcvfs.exists(xbmc.translatePath(rootFolder))):
|
||||
self.dialog.ok(utils.getString(30117),utils.getString(30118),rootFolder)
|
||||
return None
|
||||
else:
|
||||
#select path to start set
|
||||
rootFolder = self.dialog.browse(type=0,heading=utils.getString(30119),shares='files',defaultt=rootFolder)
|
||||
|
||||
backupSet = {'name':name,'root':rootFolder}
|
||||
|
||||
return backupSet
|
||||
|
||||
def editSet(self,name,backupSet):
|
||||
optionSelected = ''
|
||||
rootPath = backupSet['root']
|
||||
utils.log(rootPath)
|
||||
while(optionSelected != -1):
|
||||
options = [xbmcgui.ListItem(utils.getString(30120),"Exclude a specific folder from this backup set"),xbmcgui.ListItem(utils.getString(30135),"Include a specific folder to this backup set"),xbmcgui.ListItem(rootPath,utils.getString(30121))]
|
||||
|
||||
for aDir in backupSet['dirs']:
|
||||
if(aDir['type'] == 'exclude'):
|
||||
options.append(xbmcgui.ListItem(self._cleanPath(rootPath,aDir['path']),"%s: %s" % ("Type",utils.getString(30129))))
|
||||
elif(aDir['type'] == 'include'):
|
||||
options.append(xbmcgui.ListItem(self._cleanPath(rootPath,aDir['path']),"%s: %s | %s: %s" % ("Type",utils.getString(30134),"Include Sub Folders",str(aDir['recurse']))))
|
||||
|
||||
optionSelected = self.dialog.select(utils.getString(30122) + ' ' + name,options,useDetails=True)
|
||||
|
||||
if(optionSelected == 0 or optionSelected == 1):
|
||||
#add a folder, will equal root if cancel is hit
|
||||
addFolder = self.dialog.browse(type=0,heading=utils.getString(30120),shares='files',defaultt=backupSet['root'])
|
||||
|
||||
if(addFolder.startswith(rootPath)):
|
||||
|
||||
if(not any(addFolder == aDir['path'] for aDir in backupSet['dirs'])):
|
||||
#cannot add root as an exclusion
|
||||
if(optionSelected == 0 and addFolder != backupSet['root']):
|
||||
backupSet['dirs'].append({"path":addFolder,"type":"exclude"})
|
||||
elif(optionSelected == 1):
|
||||
#can add root as inclusion
|
||||
backupSet['dirs'].append({"path":addFolder,"type":"include","recurse":True})
|
||||
else:
|
||||
#this path is already part of another include/exclude rule
|
||||
self.dialog.ok(utils.getString(30117),utils.getString(30137),addFolder)
|
||||
else:
|
||||
#folder must be under root folder
|
||||
self.dialog.ok(utils.getString(30117), utils.getString(30136),rootPath)
|
||||
elif(optionSelected == 2):
|
||||
self.dialog.ok(utils.getString(30121),utils.getString(30130),backupSet['root'])
|
||||
elif(optionSelected > 2):
|
||||
|
||||
cOptions = ['Delete']
|
||||
if(backupSet['dirs'][optionSelected - 3]['type'] == 'include'):
|
||||
cOptions.append('Toggle Sub Folders')
|
||||
|
||||
contextOption = self.dialog.contextmenu(cOptions)
|
||||
|
||||
if(contextOption == 0):
|
||||
if(self.dialog.yesno(heading=utils.getString(30123),line1=utils.getString(30128))):
|
||||
#remove folder
|
||||
del backupSet['dirs'][optionSelected - 3]
|
||||
elif(contextOption == 1 and backupSet['dirs'][optionSelected - 3]['type'] == 'include'):
|
||||
#toggle if this folder should be recursive
|
||||
backupSet['dirs'][optionSelected - 3]['recurse'] = not backupSet['dirs'][optionSelected - 3]['recurse']
|
||||
|
||||
return backupSet
|
||||
|
||||
|
||||
def showMainScreen(self):
|
||||
exitCondition = ""
|
||||
customPaths = BackupSetManager()
|
||||
|
||||
#show this every time
|
||||
self.dialog.ok(utils.getString(30036),utils.getString(30037))
|
||||
|
||||
while(exitCondition != -1):
|
||||
#load the custom paths
|
||||
options = [xbmcgui.ListItem(utils.getString(30126),'',utils.addon_dir() + '/resources/images/plus-icon.png')]
|
||||
|
||||
for index in range(0,len(customPaths.getSets())):
|
||||
aSet = customPaths.getSet(index)
|
||||
options.append(xbmcgui.ListItem(aSet['name'],utils.getString(30121) + ': ' + aSet['set']['root'],utils.addon_dir() + '/resources/images/folder-icon.png'))
|
||||
|
||||
#show the gui
|
||||
exitCondition = self.dialog.select(utils.getString(30125),options,useDetails=True)
|
||||
|
||||
if(exitCondition >= 0):
|
||||
if(exitCondition == 0):
|
||||
newSet = self.createSet()
|
||||
|
||||
#check that the name is unique
|
||||
if(customPaths.validateSetName(newSet['name'])):
|
||||
customPaths.addSet(newSet)
|
||||
else:
|
||||
self.dialog.ok(utils.getString(30117), utils.getString(30138),newSet['name'])
|
||||
else:
|
||||
#bring up a context menu
|
||||
menuOption = self.dialog.contextmenu([utils.getString(30122),utils.getString(30123)])
|
||||
|
||||
if(menuOption == 0):
|
||||
#get the set
|
||||
aSet = customPaths.getSet(exitCondition -1)
|
||||
|
||||
#edit the set
|
||||
updatedSet = self.editSet(aSet['name'],aSet['set'])
|
||||
|
||||
#save it
|
||||
customPaths.updateSet(aSet['name'],updatedSet)
|
||||
|
||||
elif(menuOption == 1):
|
||||
if(self.dialog.yesno(heading=utils.getString(30127),line1=utils.getString(30128))):
|
||||
#delete this path - subtract one because of "add" item
|
||||
customPaths.deleteSet(exitCondition -1)
|
||||
|
||||
def copySimpleConfig(self):
|
||||
#disclaimer in case the user hit this on accident
|
||||
shouldContinue = self.dialog.yesno(utils.getString(30139),utils.getString(30140),utils.getString(30141))
|
||||
|
||||
if(shouldContinue):
|
||||
source = xbmc.translatePath(utils.addon_dir() + "/resources/data/default_files.json")
|
||||
dest = xbmc.translatePath(utils.data_dir() + "/custom_paths.json")
|
||||
|
||||
xbmcvfs.copy(source,dest)
|
||||
|
||||
|
||||
|
@ -3,9 +3,18 @@ import xbmcgui
|
||||
import xbmcvfs
|
||||
import resources.lib.tinyurl as tinyurl
|
||||
import resources.lib.utils as utils
|
||||
import dropbox
|
||||
from resources.lib.pydrive.auth import GoogleAuth
|
||||
from resources.lib.pydrive.drive import GoogleDrive
|
||||
|
||||
#don't die on import error yet, these might not even get used
|
||||
try:
|
||||
import dropbox
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from resources.lib.pydrive.auth import GoogleAuth
|
||||
from resources.lib.pydrive.drive import GoogleDrive
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class DropboxAuthorizer:
|
||||
APP_KEY = ""
|
||||
|
@ -4,10 +4,12 @@ import xbmcvfs
|
||||
import utils as utils
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime
|
||||
from vfs import XBMCFileSystem,DropboxFileSystem,ZipFileSystem,GoogleDriveFilesystem
|
||||
from progressbar import BackupProgressBar
|
||||
from resources.lib.guisettings import GuiSettingsManager
|
||||
from resources.lib.extractor import ZipExtractor
|
||||
from __builtin__ import file
|
||||
|
||||
def folderSort(aKey):
|
||||
result = aKey[0]
|
||||
@ -23,6 +25,9 @@ class XbmcBackup:
|
||||
Backup = 0
|
||||
Restore = 1
|
||||
|
||||
#list of dirs for the "simple" file selection
|
||||
simple_directory_list = ['addons','addon_data','database','game_saves','playlists','profiles','thumbnails','config']
|
||||
|
||||
#file systems
|
||||
xbmc_vfs = None
|
||||
remote_vfs = None
|
||||
@ -36,7 +41,6 @@ class XbmcBackup:
|
||||
filesLeft = 0
|
||||
filesTotal = 1
|
||||
|
||||
fileManager = None
|
||||
restore_point = None
|
||||
skip_advanced = False #if we should check for the existance of advancedsettings in the restore
|
||||
|
||||
@ -69,7 +73,8 @@ class XbmcBackup:
|
||||
|
||||
return result
|
||||
|
||||
def listBackups(self):
|
||||
#reverse - should reverse the resulting, default is true - newest to oldest
|
||||
def listBackups(self,reverse=True):
|
||||
result = []
|
||||
|
||||
#get all the folders in the current root path
|
||||
@ -78,12 +83,8 @@ class XbmcBackup:
|
||||
for aDir in dirs:
|
||||
if(self.remote_vfs.exists(self.remote_base_path + aDir + "/xbmcbackup.val")):
|
||||
|
||||
#folder may or may not contain time, older versions didn't include this
|
||||
folderName = ''
|
||||
if(len(aDir) > 8):
|
||||
folderName = aDir[6:8] + '-' + aDir[4:6] + '-' + aDir[0:4] + " " + aDir[8:10] + ":" + aDir[10:12]
|
||||
else:
|
||||
folderName = aDir[6:8] + '-' + aDir[4:6] + '-' + aDir[0:4]
|
||||
#format the name according to regional settings
|
||||
folderName = self._dateFormat(aDir)
|
||||
|
||||
result.append((aDir,folderName))
|
||||
|
||||
@ -91,18 +92,15 @@ class XbmcBackup:
|
||||
file_ext = aFile.split('.')[-1]
|
||||
folderName = utils.encode(aFile.split('.')[0])
|
||||
|
||||
if(file_ext == 'zip' and (len(folderName) == 12 or len(folderName) == 8) and str.isdigit(folderName)):
|
||||
if(file_ext == 'zip' and len(folderName) == 12 and str.isdigit(folderName)):
|
||||
|
||||
#folder may or may not contain time, older versions didn't include this
|
||||
if(len(aFile ) > 8):
|
||||
folderName = aFile [6:8] + '-' + aFile [4:6] + '-' + aFile [0:4] + " " + aFile [8:10] + ":" + aFile [10:12]
|
||||
else:
|
||||
folderName = aFile [6:8] + '-' + aFile [4:6] + '-' + aFile [0:4]
|
||||
#format the name according to regional settings
|
||||
folderName = self._dateFormat(folderName)
|
||||
|
||||
result.append((aFile ,folderName))
|
||||
|
||||
|
||||
result.sort(key=folderSort)
|
||||
result.sort(key=folderSort,reverse=reverse)
|
||||
|
||||
return result
|
||||
|
||||
@ -112,46 +110,10 @@ class XbmcBackup:
|
||||
def skipAdvanced(self):
|
||||
self.skip_advanced = True
|
||||
|
||||
def run(self,mode=-1,progressOverride=False):
|
||||
#set windows setting to true
|
||||
window = xbmcgui.Window(10000)
|
||||
window.setProperty(utils.__addon_id__ + ".running","true")
|
||||
def backup(self,progressOverride=False):
|
||||
shouldContinue = self._setupVFS(self.Backup,progressOverride)
|
||||
|
||||
#append backup folder name
|
||||
progressBarTitle = utils.getString(30010) + " - "
|
||||
if(mode == self.Backup and self.remote_vfs.root_path != ''):
|
||||
if(utils.getSetting("compress_backups") == 'true'):
|
||||
#delete old temp file
|
||||
if(self.xbmc_vfs.exists(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
|
||||
if(not self.xbmc_vfs.rmfile(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
|
||||
#we had some kind of error deleting the old file
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30096),utils.getString(30097))
|
||||
return
|
||||
|
||||
#save the remote file system and use the zip vfs
|
||||
self.saved_remote_vfs = self.remote_vfs
|
||||
self.remote_vfs = ZipFileSystem(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"),"w")
|
||||
|
||||
self.remote_vfs.set_root(self.remote_vfs.root_path + time.strftime("%Y%m%d%H%M") + "/")
|
||||
progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30016)
|
||||
elif(mode == self.Restore and self.restore_point != None and self.remote_vfs.root_path != ''):
|
||||
if(self.restore_point.split('.')[-1] != 'zip'):
|
||||
self.remote_vfs.set_root(self.remote_vfs.root_path + self.restore_point + "/")
|
||||
progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30017)
|
||||
else:
|
||||
#kill the program here
|
||||
self.remote_vfs = None
|
||||
return
|
||||
|
||||
utils.log(utils.getString(30047) + ": " + self.xbmc_vfs.root_path)
|
||||
utils.log(utils.getString(30048) + ": " + self.remote_vfs.root_path)
|
||||
|
||||
|
||||
#setup the progress bar
|
||||
self.progressBar = BackupProgressBar(progressOverride)
|
||||
self.progressBar.create(progressBarTitle,utils.getString(30049) + "......")
|
||||
|
||||
if(mode == self.Backup):
|
||||
if(shouldContinue):
|
||||
utils.log(utils.getString(30023) + " - " + utils.getString(30016))
|
||||
#check if remote path exists
|
||||
if(self.remote_vfs.exists(self.remote_vfs.root_path)):
|
||||
@ -161,8 +123,37 @@ class XbmcBackup:
|
||||
#make the remote directory
|
||||
self.remote_vfs.mkdir(self.remote_vfs.root_path)
|
||||
|
||||
utils.log(utils.getString(30051))
|
||||
utils.log('File Selection Type: ' + str(utils.getSetting('backup_selection_type')))
|
||||
allFiles = []
|
||||
|
||||
if(int(utils.getSetting('backup_selection_type')) == 0):
|
||||
#read in a list of the directories to backup
|
||||
selectedDirs = self._readBackupConfig(utils.addon_dir() + "/resources/data/default_files.json")
|
||||
|
||||
#simple mode - get file listings for all enabled directories
|
||||
for aDir in self.simple_directory_list:
|
||||
#if this dir enabled
|
||||
if(utils.getSetting('backup_' + aDir) == 'true'):
|
||||
#get a file listing and append it to the allfiles array
|
||||
allFiles.append(self._addBackupDir(aDir,selectedDirs[aDir]['root'],selectedDirs[aDir]['dirs']))
|
||||
else:
|
||||
#advanced mode - load custom paths
|
||||
selectedDirs = self._readBackupConfig(utils.data_dir() + "/custom_paths.json")
|
||||
|
||||
#get the set names
|
||||
keys = selectedDirs.keys()
|
||||
|
||||
#go through the custom sets
|
||||
for aKey in keys:
|
||||
#get the set
|
||||
aSet = selectedDirs[aKey]
|
||||
|
||||
#get file listing and append
|
||||
allFiles.append(self._addBackupDir(aKey,aSet['root'],aSet['dirs']))
|
||||
|
||||
#create a validation file for backup rotation
|
||||
writeCheck = self._createValidationFile()
|
||||
writeCheck = self._createValidationFile(allFiles)
|
||||
|
||||
if(not writeCheck):
|
||||
#we may not be able to write to this destination for some reason
|
||||
@ -171,90 +162,14 @@ class XbmcBackup:
|
||||
if(not shouldContinue):
|
||||
return
|
||||
|
||||
utils.log(utils.getString(30051))
|
||||
allFiles = []
|
||||
fileManager = FileManager(self.xbmc_vfs)
|
||||
|
||||
#go through each of the user selected items and write them to the backup store
|
||||
if(utils.getSetting('backup_addons') == 'true'):
|
||||
fileManager.addFile("-" + xbmc.translatePath('special://home/addons'))
|
||||
fileManager.excludeFile(xbmc.translatePath('special://home/addons/packages'))
|
||||
fileManager.walkTree(xbmc.translatePath('special://home/addons'))
|
||||
|
||||
fileManager.addFile("-" + xbmc.translatePath('special://home/userdata'))
|
||||
|
||||
if(utils.getSetting('backup_addon_data') == 'true'):
|
||||
fileManager.addFile("-" + xbmc.translatePath('special://home/userdata/addon_data'))
|
||||
fileManager.walkTree(xbmc.translatePath('special://home/userdata/addon_data'))
|
||||
|
||||
if(utils.getSetting('backup_database') == 'true'):
|
||||
fileManager.addFile("-" + xbmc.translatePath('special://home/userdata/Database'))
|
||||
fileManager.walkTree(xbmc.translatePath('special://home/userdata/Database'))
|
||||
|
||||
if(utils.getSetting("backup_playlists") == 'true'):
|
||||
fileManager.addFile("-" + xbmc.translatePath('special://home/userdata/playlists'))
|
||||
fileManager.walkTree(xbmc.translatePath('special://home/userdata/playlists'))
|
||||
|
||||
if(utils.getSetting('backup_profiles') == 'true'):
|
||||
fileManager.addFile("-" + xbmc.translatePath('special://home/userdata/profiles'))
|
||||
fileManager.walkTree(xbmc.translatePath('special://home/userdata/profiles'))
|
||||
|
||||
if(utils.getSetting("backup_thumbnails") == "true"):
|
||||
fileManager.addFile("-" + xbmc.translatePath('special://home/userdata/Thumbnails'))
|
||||
fileManager.walkTree(xbmc.translatePath('special://home/userdata/Thumbnails'))
|
||||
|
||||
if(utils.getSetting("backup_config") == "true"):
|
||||
fileManager.addFile("-" + xbmc.translatePath('special://home/userdata/keymaps'))
|
||||
fileManager.walkTree(xbmc.translatePath('special://home/userdata/keymaps'))
|
||||
|
||||
fileManager.addFile("-" + xbmc.translatePath('special://home/userdata/peripheral_data'))
|
||||
fileManager.walkTree(xbmc.translatePath('special://home/userdata/peripheral_data'))
|
||||
|
||||
fileManager.addFile('-' + xbmc.translatePath('special://home/userdata/library'))
|
||||
fileManager.walkTree(xbmc.translatePath('special://home/userdata/library'))
|
||||
|
||||
#this part is an oddity
|
||||
dirs,configFiles = self.xbmc_vfs.listdir(xbmc.translatePath('special://home/userdata/'))
|
||||
for aFile in configFiles:
|
||||
if(aFile.endswith(".xml")):
|
||||
fileManager.addFile(xbmc.translatePath('special://home/userdata/') + aFile)
|
||||
|
||||
#add to array
|
||||
self.filesTotal = fileManager.size()
|
||||
allFiles.append({"source":self.xbmc_vfs.root_path,"dest":self.remote_vfs.root_path,"files":fileManager.getFiles()})
|
||||
|
||||
orig_base_path = self.remote_vfs.root_path
|
||||
|
||||
#check if there are custom directories
|
||||
if(utils.getSetting('custom_dir_1_enable') == 'true' and utils.getSetting('backup_custom_dir_1') != ''):
|
||||
|
||||
#create a special remote path with hash
|
||||
self.xbmc_vfs.set_root(utils.getSetting('backup_custom_dir_1'))
|
||||
fileManager.addFile("-custom_" + self._createCRC(self.xbmc_vfs.root_path))
|
||||
|
||||
#walk the directory
|
||||
fileManager.walkTree(self.xbmc_vfs.root_path)
|
||||
self.filesTotal = self.filesTotal + fileManager.size()
|
||||
allFiles.append({"source":self.xbmc_vfs.root_path,"dest":self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path),"files":fileManager.getFiles()})
|
||||
|
||||
if(utils.getSetting('custom_dir_2_enable') == 'true' and utils.getSetting('backup_custom_dir_2') != ''):
|
||||
|
||||
#create a special remote path with hash
|
||||
self.xbmc_vfs.set_root(utils.getSetting('backup_custom_dir_2'))
|
||||
fileManager.addFile("-custom_" + self._createCRC(self.xbmc_vfs.root_path))
|
||||
|
||||
#walk the directory
|
||||
fileManager.walkTree(self.xbmc_vfs.root_path)
|
||||
self.filesTotal = self.filesTotal + fileManager.size()
|
||||
allFiles.append({"source":self.xbmc_vfs.root_path,"dest":self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path),"files":fileManager.getFiles()})
|
||||
|
||||
|
||||
#backup all the files
|
||||
self.filesLeft = self.filesTotal
|
||||
for fileGroup in allFiles:
|
||||
self.xbmc_vfs.set_root(fileGroup['source'])
|
||||
self.remote_vfs.set_root(fileGroup['dest'])
|
||||
filesCopied = self.backupFiles(fileGroup['files'],self.xbmc_vfs,self.remote_vfs)
|
||||
self.xbmc_vfs.set_root(xbmc.translatePath(fileGroup['source']))
|
||||
self.remote_vfs.set_root(fileGroup['dest'] + fileGroup['name'])
|
||||
filesCopied = self._copyFiles(fileGroup['files'],self.xbmc_vfs,self.remote_vfs)
|
||||
|
||||
if(not filesCopied):
|
||||
utils.showNotification(utils.getString(30092))
|
||||
@ -265,6 +180,8 @@ class XbmcBackup:
|
||||
self.remote_vfs.set_root(orig_base_path)
|
||||
|
||||
if(utils.getSetting("compress_backups") == 'true'):
|
||||
fileManager = FileManager(self.xbmc_vfs)
|
||||
|
||||
#send the zip file to the real remote vfs
|
||||
zip_name = self.remote_vfs.root_path[:-1] + ".zip"
|
||||
self.remote_vfs.cleanup()
|
||||
@ -276,7 +193,7 @@ class XbmcBackup:
|
||||
|
||||
self.remote_vfs = self.saved_remote_vfs
|
||||
self.progressBar.updateProgress(98, utils.getString(30088))
|
||||
fileCopied = self.backupFiles(fileManager.getFiles(),self.xbmc_vfs, self.remote_vfs)
|
||||
fileCopied = self._copyFiles(fileManager.getFiles(),self.xbmc_vfs, self.remote_vfs)
|
||||
|
||||
if(not fileCopied):
|
||||
#zip archive copy filed, inform the user
|
||||
@ -288,7 +205,13 @@ class XbmcBackup:
|
||||
#remove old backups
|
||||
self._rotateBackups()
|
||||
|
||||
elif (mode == self.Restore):
|
||||
#close any files
|
||||
self._closeVFS()
|
||||
|
||||
def restore(self,progressOverride=False,selectedSets=None):
|
||||
shouldContinue = self._setupVFS(self.Restore, progressOverride)
|
||||
|
||||
if(shouldContinue):
|
||||
utils.log(utils.getString(30023) + " - " + utils.getString(30017))
|
||||
|
||||
#catch for if the restore point is actually a zip file
|
||||
@ -304,7 +227,7 @@ class XbmcBackup:
|
||||
zipFile = []
|
||||
zipFile.append(self.remote_base_path + self.restore_point)
|
||||
|
||||
self.backupFiles(zipFile,self.remote_vfs, self.xbmc_vfs)
|
||||
self._copyFiles(zipFile,self.remote_vfs, self.xbmc_vfs)
|
||||
else:
|
||||
utils.log("zip file exists already")
|
||||
|
||||
@ -332,7 +255,8 @@ class XbmcBackup:
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path)
|
||||
return
|
||||
|
||||
if(not self._checkValidationFile(self.remote_vfs.root_path)):
|
||||
valFile = self._checkValidationFile(self.remote_vfs.root_path)
|
||||
if(valFile == None):
|
||||
#don't continue
|
||||
return
|
||||
|
||||
@ -340,104 +264,55 @@ class XbmcBackup:
|
||||
allFiles = []
|
||||
fileManager = FileManager(self.remote_vfs)
|
||||
|
||||
#go through each of the user selected items and write them to the backup store
|
||||
|
||||
if(utils.getSetting("backup_config") == "true"):
|
||||
#check for the existance of an advancedsettings file
|
||||
if(self.remote_vfs.exists(self.remote_vfs.root_path + "userdata/advancedsettings.xml") and not self.skip_advanced):
|
||||
if(self.remote_vfs.exists(self.remote_vfs.root_path + "config/advancedsettings.xml") and not self.skip_advanced):
|
||||
#let the user know there is an advanced settings file present
|
||||
restartXbmc = xbmcgui.Dialog().yesno(utils.getString(30038),utils.getString(30039),utils.getString(30040), utils.getString(30041))
|
||||
|
||||
if(restartXbmc):
|
||||
#add only this file to the file list
|
||||
fileManager.addFile(self.remote_vfs.root_path + "userdata/advancedsettings.xml")
|
||||
self.backupFiles(fileManager.getFiles(),self.remote_vfs,self.xbmc_vfs)
|
||||
fileManager.addFile(self.remote_vfs.root_path + "config/advancedsettings.xml")
|
||||
self._copyFiles(fileManager.getFiles(),self.remote_vfs,self.xbmc_vfs)
|
||||
|
||||
#let the service know to resume this backup on startup
|
||||
self._createResumeBackupFile()
|
||||
|
||||
#do not continue running
|
||||
xbmcgui.Dialog().ok(utils.getString(30077),utils.getString(30078))
|
||||
|
||||
return
|
||||
|
||||
fileManager.addFile('-' + self.remote_vfs.root_path + 'userdata/keymaps')
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "userdata/keymaps")
|
||||
#use a multiselect dialog to select sets to restore
|
||||
restoreSets = [n['name'] for n in valFile['directories']]
|
||||
|
||||
fileManager.addFile('-' + self.remote_vfs.root_path + "userdata/peripheral_data")
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "userdata/peripheral_data")
|
||||
|
||||
fileManager.addFile('-' + self.remote_vfs.root_path + "userdata/library")
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "userdata/library")
|
||||
|
||||
#this part is an oddity
|
||||
dirs,configFiles = self.remote_vfs.listdir(self.remote_vfs.root_path + "userdata/")
|
||||
for aFile in configFiles:
|
||||
if(aFile.endswith(".xml")):
|
||||
fileManager.addFile(self.remote_vfs.root_path + "userdata/" + aFile)
|
||||
|
||||
if(utils.getSetting('backup_addons') == 'true'):
|
||||
fileManager.addFile('-' + self.remote_vfs.root_path + "addons")
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "addons")
|
||||
|
||||
self.xbmc_vfs.mkdir(xbmc.translatePath('special://home/userdata'))
|
||||
|
||||
if(utils.getSetting('backup_addon_data') == 'true'):
|
||||
fileManager.addFile('-' + self.remote_vfs.root_path + "userdata/addon_data")
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "userdata/addon_data")
|
||||
|
||||
if(utils.getSetting('backup_database') == 'true'):
|
||||
fileManager.addFile('-' + self.remote_vfs.root_path + "userdata/Database")
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "userdata/Database")
|
||||
|
||||
if(utils.getSetting("backup_playlists") == 'true'):
|
||||
fileManager.addFile('-' + self.remote_vfs.root_path + "userdata/playlists")
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "userdata/playlists")
|
||||
|
||||
if(utils.getSetting('backup_profiles') == 'true'):
|
||||
fileManager.addFile('-' + self.remote_vfs.root_path + "userdata/profiles")
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "userdata/profiles")
|
||||
|
||||
if(utils.getSetting("backup_thumbnails") == "true"):
|
||||
fileManager.addFile('-' + self.remote_vfs.root_path + "userdata/Thumbnails")
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "userdata/Thumbnails")
|
||||
|
||||
#add to array
|
||||
self.filesTotal = fileManager.size()
|
||||
allFiles.append({"source":self.remote_vfs.root_path,"dest":self.xbmc_vfs.root_path,"files":fileManager.getFiles()})
|
||||
|
||||
#check if there are custom directories
|
||||
if(utils.getSetting('custom_dir_1_enable') == 'true' and utils.getSetting('backup_custom_dir_1') != ''):
|
||||
|
||||
self.xbmc_vfs.set_root(utils.getSetting('backup_custom_dir_1'))
|
||||
if(self.remote_vfs.exists(self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path) + "/")):
|
||||
#index files to restore
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path))
|
||||
self.filesTotal = self.filesTotal + fileManager.size()
|
||||
allFiles.append({"source":self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path),"dest":self.xbmc_vfs.root_path,"files":fileManager.getFiles()})
|
||||
#if passed in list, skip selection
|
||||
if(selectedSets == None):
|
||||
selectedSets = xbmcgui.Dialog().multiselect(utils.getString(30131),restoreSets)
|
||||
else:
|
||||
utils.log("error path not found: " + self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path))
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path + "custom_" + self._createCRC(utils.getSetting('backup_custom_dir_1')))
|
||||
selectedSets = [restoreSets.index(n) for n in selectedSets if n in restoreSets] #if set name not found just skip it
|
||||
|
||||
if(utils.getSetting('custom_dir_2_enable') == 'true' and utils.getSetting('backup_custom_dir_2') != ''):
|
||||
if(selectedSets != None):
|
||||
#go through each of the directories in the backup and write them to the correct location
|
||||
for index in selectedSets:
|
||||
|
||||
self.xbmc_vfs.set_root(utils.getSetting('backup_custom_dir_2'))
|
||||
if(self.remote_vfs.exists(self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path) + "/")):
|
||||
#index files to restore
|
||||
fileManager.walkTree(self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path))
|
||||
#add this directory
|
||||
aDir = valFile['directories'][index]
|
||||
|
||||
self.xbmc_vfs.set_root(xbmc.translatePath(aDir['path']))
|
||||
if(self.remote_vfs.exists(self.remote_vfs.root_path + aDir['name'] + '/')):
|
||||
#walk the directory
|
||||
fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/')
|
||||
self.filesTotal = self.filesTotal + fileManager.size()
|
||||
allFiles.append({"source":self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path),"dest":self.xbmc_vfs.root_path,"files":fileManager.getFiles()})
|
||||
allFiles.append({"source":self.remote_vfs.root_path + aDir['name'],"dest":self.xbmc_vfs.root_path,"files":fileManager.getFiles()})
|
||||
else:
|
||||
utils.log("error path not found: " + self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path))
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path + "custom_" + self._createCRC(utils.getSetting('backup_custom_dir_2')))
|
||||
|
||||
utils.log("error path not found: " + self.remote_vfs.root_path + aDir['name'])
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path + aDir['name'])
|
||||
|
||||
#restore all the files
|
||||
self.filesLeft = self.filesTotal
|
||||
for fileGroup in allFiles:
|
||||
self.remote_vfs.set_root(fileGroup['source'])
|
||||
self.xbmc_vfs.set_root(fileGroup['dest'])
|
||||
self.backupFiles(fileGroup['files'],self.remote_vfs,self.xbmc_vfs)
|
||||
self._copyFiles(fileGroup['files'],self.remote_vfs,self.xbmc_vfs)
|
||||
|
||||
self.progressBar.updateProgress(99,"Clean up operations .....")
|
||||
|
||||
@ -454,18 +329,67 @@ class XbmcBackup:
|
||||
#call update addons to refresh everything
|
||||
xbmc.executebuiltin('UpdateLocalAddons')
|
||||
|
||||
def _setupVFS(self,mode=-1,progressOverride=False):
|
||||
#set windows setting to true
|
||||
window = xbmcgui.Window(10000)
|
||||
window.setProperty(utils.__addon_id__ + ".running","true")
|
||||
|
||||
#append backup folder name
|
||||
progressBarTitle = utils.getString(30010) + " - "
|
||||
if(mode == self.Backup and self.remote_vfs.root_path != ''):
|
||||
if(utils.getSetting("compress_backups") == 'true'):
|
||||
#delete old temp file
|
||||
if(self.xbmc_vfs.exists(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
|
||||
if(not self.xbmc_vfs.rmfile(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
|
||||
#we had some kind of error deleting the old file
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30096),utils.getString(30097))
|
||||
return False
|
||||
|
||||
#save the remote file system and use the zip vfs
|
||||
self.saved_remote_vfs = self.remote_vfs
|
||||
self.remote_vfs = ZipFileSystem(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"),"w")
|
||||
|
||||
self.remote_vfs.set_root(self.remote_vfs.root_path + time.strftime("%Y%m%d%H%M") + "/")
|
||||
progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30016)
|
||||
elif(mode == self.Restore and self.restore_point != None and self.remote_vfs.root_path != ''):
|
||||
if(self.restore_point.split('.')[-1] != 'zip'):
|
||||
self.remote_vfs.set_root(self.remote_vfs.root_path + self.restore_point + "/")
|
||||
progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30017)
|
||||
else:
|
||||
#kill the program here
|
||||
self.remote_vfs = None
|
||||
return False
|
||||
|
||||
utils.log(utils.getString(30047) + ": " + self.xbmc_vfs.root_path)
|
||||
utils.log(utils.getString(30048) + ": " + self.remote_vfs.root_path)
|
||||
|
||||
|
||||
#setup the progress bar
|
||||
self.progressBar = BackupProgressBar(progressOverride)
|
||||
self.progressBar.create(progressBarTitle,utils.getString(30049) + "......")
|
||||
|
||||
#if we made it this far we're good
|
||||
return True
|
||||
|
||||
def _closeVFS(self):
|
||||
self.xbmc_vfs.cleanup()
|
||||
self.remote_vfs.cleanup()
|
||||
self.progressBar.close()
|
||||
|
||||
#reset the window setting
|
||||
window = xbmcgui.Window(10000)
|
||||
window.setProperty(utils.__addon_id__ + ".running","")
|
||||
|
||||
def backupFiles(self,fileList,source,dest):
|
||||
def _copyFiles(self,fileList,source,dest):
|
||||
result = True
|
||||
|
||||
utils.log("Writing files to: " + dest.root_path)
|
||||
utils.log("Source: " + source.root_path)
|
||||
utils.log("Desintation: " + dest.root_path)
|
||||
|
||||
#make sure the dest folder exists - can cause write errors if the full path doesn't exist
|
||||
if(not dest.exists(dest.root_path)):
|
||||
dest.mkdir(dest.root_path)
|
||||
|
||||
for aFile in fileList:
|
||||
if(not self.progressBar.checkCancel()):
|
||||
utils.log('Writing file: ' + aFile,xbmc.LOGDEBUG)
|
||||
@ -489,21 +413,29 @@ class XbmcBackup:
|
||||
|
||||
return result
|
||||
|
||||
def _createCRC(self,string):
|
||||
#create hash from string
|
||||
string = string.lower()
|
||||
bytes = bytearray(string.encode())
|
||||
crc = 0xffffffff;
|
||||
for b in bytes:
|
||||
crc = crc ^ (b << 24)
|
||||
for i in range(8):
|
||||
if (crc & 0x80000000 ):
|
||||
crc = (crc << 1) ^ 0x04C11DB7
|
||||
else:
|
||||
crc = crc << 1;
|
||||
crc = crc & 0xFFFFFFFF
|
||||
def _addBackupDir(self,folder_name,root_path,dirList):
|
||||
utils.log('Backup set: ' + folder_name)
|
||||
fileManager = FileManager(self.xbmc_vfs)
|
||||
|
||||
return '%08x' % crc
|
||||
self.xbmc_vfs.set_root(xbmc.translatePath(root_path))
|
||||
for aDir in dirList:
|
||||
fileManager.addDir(aDir)
|
||||
|
||||
#walk all the root trees
|
||||
fileManager.walk()
|
||||
#update total files
|
||||
self.filesTotal = self.filesTotal + fileManager.size()
|
||||
|
||||
return {"name":folder_name,"source":root_path,"dest":self.remote_vfs.root_path,"files":fileManager.getFiles()}
|
||||
|
||||
def _dateFormat(self,dirName):
|
||||
#create date_time object from foldername YYYYMMDDHHmm
|
||||
date_time = datetime(int(dirName[0:4]),int(dirName[4:6]),int(dirName[6:8]),int(dirName[8:10]),int(dirName[10:12]))
|
||||
|
||||
#format the string based on region settings
|
||||
result = utils.getRegionalTimestamp(date_time, ['dateshort','time'])
|
||||
|
||||
return result
|
||||
|
||||
def _updateProgress(self,message=None):
|
||||
self.filesLeft = self.filesLeft - 1
|
||||
@ -514,7 +446,7 @@ class XbmcBackup:
|
||||
|
||||
if(total_backups > 0):
|
||||
#get a list of valid backup folders
|
||||
dirs = self.listBackups()
|
||||
dirs = self.listBackups(reverse=False)
|
||||
|
||||
if(len(dirs) > total_backups):
|
||||
#remove backups to equal total wanted
|
||||
@ -534,9 +466,16 @@ class XbmcBackup:
|
||||
|
||||
remove_num = remove_num + 1
|
||||
|
||||
def _createValidationFile(self):
|
||||
def _createValidationFile(self,dirList):
|
||||
valInfo = {"name":"XBMC Backup Validation File","xbmc_version":xbmc.getInfoLabel('System.BuildVersion'),"type":0}
|
||||
valDirs = []
|
||||
|
||||
for aDir in dirList:
|
||||
valDirs.append({"name":aDir['name'],"path":aDir['source']})
|
||||
valInfo['directories'] = valDirs
|
||||
|
||||
vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"),'w')
|
||||
vFile.write(json.dumps({"name":"XBMC Backup Validation File","xbmc_version":xbmc.getInfoLabel('System.BuildVersion')}))
|
||||
vFile.write(json.dumps(valInfo))
|
||||
vFile.write("")
|
||||
vFile.close()
|
||||
|
||||
@ -556,7 +495,7 @@ class XbmcBackup:
|
||||
return success
|
||||
|
||||
def _checkValidationFile(self,path):
|
||||
result = False
|
||||
result = None
|
||||
|
||||
#copy the file and open it
|
||||
self.xbmc_vfs.put(path + "xbmcbackup.val",xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
|
||||
@ -569,16 +508,17 @@ class XbmcBackup:
|
||||
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
|
||||
|
||||
try:
|
||||
json_dict = json.loads(jsonString)
|
||||
result = json.loads(jsonString)
|
||||
|
||||
if(xbmc.getInfoLabel('System.BuildVersion') == json_dict['xbmc_version']):
|
||||
result = True
|
||||
else:
|
||||
result = xbmcgui.Dialog().yesno(utils.getString(30085),utils.getString(30086),utils.getString(30044))
|
||||
if(xbmc.getInfoLabel('System.BuildVersion') != result['xbmc_version']):
|
||||
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30085),utils.getString(30086),utils.getString(30044))
|
||||
|
||||
if(not shouldContinue):
|
||||
result = None
|
||||
|
||||
except ValueError:
|
||||
#may fail on older archives
|
||||
result = True
|
||||
result = None
|
||||
|
||||
return result
|
||||
|
||||
@ -587,28 +527,45 @@ class XbmcBackup:
|
||||
rFile.write(self.restore_point)
|
||||
rFile.close()
|
||||
|
||||
def _readBackupConfig(self,aFile):
|
||||
jFile = xbmcvfs.File(xbmc.translatePath(aFile),'r')
|
||||
jsonString = jFile.read()
|
||||
jFile.close()
|
||||
return json.loads(jsonString)
|
||||
|
||||
class FileManager:
|
||||
not_dir = ['.zip','.xsp','.rar']
|
||||
exclude_dir = []
|
||||
root_dirs = []
|
||||
|
||||
def __init__(self,vfs):
|
||||
self.vfs = vfs
|
||||
self.fileArray = []
|
||||
self.exclude_dir = []
|
||||
self.root_dirs = []
|
||||
|
||||
def walkTree(self,directory):
|
||||
def walk(self):
|
||||
|
||||
if(directory[-1:] == '/'):
|
||||
for aDir in self.root_dirs:
|
||||
self.addFile('-' + xbmc.translatePath(aDir['path']))
|
||||
self.walkTree(xbmc.translatePath(aDir['path']),aDir['recurse'])
|
||||
|
||||
def walkTree(self,directory,recurse=True):
|
||||
utils.log('walking ' + directory + ', recurse: ' + str(recurse))
|
||||
if(directory[-1:] == '/' or directory[-1:] == '\\'):
|
||||
directory = directory[:-1]
|
||||
|
||||
if(self.vfs.exists(directory + "/")):
|
||||
dirs,files = self.vfs.listdir(directory)
|
||||
|
||||
if(recurse):
|
||||
#create all the subdirs first
|
||||
for aDir in dirs:
|
||||
dirPath = xbmc.validatePath(xbmc.translatePath(directory + "/" + aDir))
|
||||
file_ext = aDir.split('.')[-1]
|
||||
|
||||
if(not dirPath in self.exclude_dir):
|
||||
#check if directory is excluded
|
||||
if(not any(dirPath.startswith(exDir) for exDir in self.exclude_dir)):
|
||||
|
||||
self.addFile("-" + dirPath)
|
||||
|
||||
@ -627,6 +584,12 @@ class FileManager:
|
||||
filePath = xbmc.translatePath(directory + "/" + aFile)
|
||||
self.addFile(filePath)
|
||||
|
||||
def addDir(self,dirMeta):
|
||||
if(dirMeta['type'] == 'include'):
|
||||
self.root_dirs.append({'path':dirMeta['path'],'recurse':dirMeta['recurse']})
|
||||
else:
|
||||
self.excludeFile(xbmc.translatePath(dirMeta['path']))
|
||||
|
||||
def addFile(self,filename):
|
||||
try:
|
||||
filename = filename.decode('UTF-8')
|
||||
@ -643,13 +606,20 @@ class FileManager:
|
||||
except UnicodeDecodeError:
|
||||
filename = filename.decode('ISO-8859-2')
|
||||
|
||||
#remove trailing slash
|
||||
if(filename[-1] == '/' or filename[-1] == '\\'):
|
||||
filename = filename[:-1]
|
||||
|
||||
#write the full remote path name of this file
|
||||
utils.log("Exclude File: " + filename,xbmc.LOGDEBUG)
|
||||
utils.log("Exclude File: " + filename)
|
||||
self.exclude_dir.append(filename)
|
||||
|
||||
def getFiles(self):
|
||||
result = self.fileArray
|
||||
self.fileArray = []
|
||||
self.root_dirs = []
|
||||
self.exclude_dir = []
|
||||
|
||||
return result
|
||||
|
||||
def size(self):
|
||||
|
@ -511,9 +511,5 @@ def _params_to_urlencoded(params):
|
||||
else:
|
||||
return str(o).encode('utf-8')
|
||||
|
||||
#fix for python 2.6
|
||||
utf8_params = {}
|
||||
for k,v in six.iteritems(params):
|
||||
utf8_params[encode(k)] = encode(v)
|
||||
|
||||
utf8_params = {encode(k): encode(v) for k, v in six.iteritems(params)}
|
||||
return url_encode(utf8_params)
|
||||
|
@ -237,12 +237,11 @@ class StoneToPythonPrimitiveSerializer(StoneSerializerBase):
|
||||
def encode_map(self, validator, value):
|
||||
validated_value = validator.validate(value)
|
||||
|
||||
#fix for python 2.6
|
||||
result = {}
|
||||
for key, value in validated_value.items():
|
||||
result[self.encode_sub(validator.key_validator,key)] = self.encode_sub(validator.value_validator, value)
|
||||
|
||||
return result
|
||||
return {
|
||||
self.encode_sub(validator.key_validator, key):
|
||||
self.encode_sub(validator.value_validator, value) for
|
||||
key, value in validated_value.items()
|
||||
}
|
||||
|
||||
def encode_nullable(self, validator, value):
|
||||
if value is None:
|
||||
@ -831,12 +830,11 @@ def _decode_list(
|
||||
if not isinstance(obj, list):
|
||||
raise bv.ValidationError(
|
||||
'expected list, got %s' % bv.generic_type_name(obj))
|
||||
|
||||
result = []
|
||||
for item in obj:
|
||||
result.append(_json_compat_obj_decode_helper(data_type.item_validator, item, alias_validators, strict,old_style, for_msgpack))
|
||||
|
||||
return result
|
||||
return [
|
||||
_json_compat_obj_decode_helper(
|
||||
data_type.item_validator, item, alias_validators, strict,
|
||||
old_style, for_msgpack)
|
||||
for item in obj]
|
||||
|
||||
|
||||
def _decode_map(
|
||||
@ -848,12 +846,15 @@ def _decode_map(
|
||||
if not isinstance(obj, dict):
|
||||
raise bv.ValidationError(
|
||||
'expected dict, got %s' % bv.generic_type_name(obj))
|
||||
|
||||
result = {}
|
||||
for key, value in obj.items():
|
||||
result[_json_compat_obj_decode_helper(data_type.key_validator, key, alias_validators, strict,old_style, for_msgpack)] = _json_compat_obj_decode_helper(data_type.value_validator, value, alias_validators, strict,old_style, for_msgpack)
|
||||
|
||||
return result
|
||||
return {
|
||||
_json_compat_obj_decode_helper(
|
||||
data_type.key_validator, key, alias_validators, strict,
|
||||
old_style, for_msgpack):
|
||||
_json_compat_obj_decode_helper(
|
||||
data_type.value_validator, value, alias_validators, strict,
|
||||
old_style, for_msgpack)
|
||||
for key, value in obj.items()
|
||||
}
|
||||
|
||||
|
||||
def _decode_nullable(
|
||||
|
@ -422,13 +422,10 @@ class Map(Composite):
|
||||
def validate(self, val):
|
||||
if not isinstance(val, dict):
|
||||
raise ValidationError('%r is not a valid dict' % val)
|
||||
|
||||
#fix for python 2.6
|
||||
result = {}
|
||||
for key, value in val.items():
|
||||
result[self.key_validator.validate(key)] = self.value_validator.validate(value)
|
||||
|
||||
return result
|
||||
return {
|
||||
self.key_validator.validate(key):
|
||||
self.value_validator.validate(value) for key, value in val.items()
|
||||
}
|
||||
|
||||
|
||||
class Struct(Composite):
|
||||
|
@ -14,11 +14,11 @@ def addon_dir():
|
||||
def openSettings():
|
||||
__Addon.openSettings()
|
||||
|
||||
def log(message,loglevel=xbmc.LOGNOTICE):
|
||||
def log(message,loglevel=xbmc.LOGDEBUG):
|
||||
xbmc.log(encode(__addon_id__ + "-" + __Addon.getAddonInfo('version') + ": " + message),level=loglevel)
|
||||
|
||||
def showNotification(message):
|
||||
xbmcgui.Dialog().notification(encode(getString(30010)),encode(message),time=4000,icon=xbmc.translatePath(__Addon.getAddonInfo('path') + "/resources/media/icon.png"))
|
||||
xbmcgui.Dialog().notification(encode(getString(30010)),encode(message),time=4000,icon=xbmc.translatePath(__Addon.getAddonInfo('path') + "/resources/images/icon.png"))
|
||||
|
||||
def getSetting(name):
|
||||
return __Addon.getSetting(name)
|
||||
@ -29,6 +29,14 @@ def setSetting(name,value):
|
||||
def getString(string_id):
|
||||
return __Addon.getLocalizedString(string_id)
|
||||
|
||||
def getRegionalTimestamp(date_time,dateformat=['dateshort']):
|
||||
result = ''
|
||||
|
||||
for aFormat in dateformat:
|
||||
result = result + ("%s " % date_time.strftime(xbmc.getRegion(aFormat)))
|
||||
|
||||
return result.strip()
|
||||
|
||||
def encode(string):
|
||||
result = ''
|
||||
|
||||
|
@ -1,16 +1,12 @@
|
||||
import utils as utils
|
||||
import tinyurl as tinyurl
|
||||
import xbmc
|
||||
import xbmcvfs
|
||||
import xbmcgui
|
||||
import zipfile
|
||||
import zlib
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import dropbox
|
||||
from dropbox.files import WriteMode,CommitInfo,UploadSessionCursor
|
||||
from pydrive.drive import GoogleDrive
|
||||
from authorizers import DropboxAuthorizer,GoogleDriveAuthorizer
|
||||
|
||||
class Vfs:
|
||||
|
@ -4,6 +4,7 @@
|
||||
<setting id="compress_backups" type="bool" label="30087" default="false" />
|
||||
<setting id="backup_rotation" type="number" label="30026" default="0" />
|
||||
<setting id="progress_mode" type="enum" label="30022" lvalues="30082|30083|30084" default="0" />
|
||||
<setting id="upgrade_notes" type="number" label="upgrade_notes" visible="false" default="0" />
|
||||
</category>
|
||||
<category id="backup_path" label="30048">
|
||||
<setting id="remote_selection" type="enum" lvalues="30018|30019|30027|30098" default="0" label="30025"/>
|
||||
@ -13,22 +14,22 @@
|
||||
<setting id="dropbox_secret" type="text" label="30029" visible="eq(-4,2)" default="" />
|
||||
<setting id="google_drive_id" type="text" label="Client ID" visible="eq(-5,3)" default="" />
|
||||
<setting id="google_drive_secret" type="text" label="Client Secret" visible="eq(-6,3)" default="" />
|
||||
<setting id="auth_dropbox_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/authorize_cloud.py,type=dropbox)" visible="eq(-7,2)"/>
|
||||
<setting id="auth_google_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/authorize_cloud.py,type=google_drive)" visible="eq(-8,3)"/>
|
||||
<setting id="remove_auth_button" type="action" label="30093" action="RunScript(special://home/addons/script.xbmcbackup/remove_auth.py)" visible="gt(-9,1)"/>
|
||||
<setting id="auth_dropbox_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=authorize_cloud&provider=dropbox)" visible="eq(-7,2)"/>
|
||||
<setting id="auth_google_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=authorize_cloud&provider=google_drive)" visible="eq(-8,3)"/>
|
||||
<setting id="remove_auth_button" type="action" label="30093" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=remove_auth)" visible="gt(-9,1)"/>
|
||||
</category>
|
||||
<category id="selection" label="30012">
|
||||
<setting id="backup_addons" type="bool" label="30030" default="true" />
|
||||
<setting id="backup_addon_data" type="bool" label="30031" default="false" />
|
||||
<setting id="backup_database" type="bool" label="30032" default="true" />
|
||||
<setting id="backup_playlists" type="bool" label="30033" default="true" />
|
||||
<setting id="backup_profiles" type="bool" label="30080" default="false" />
|
||||
<setting id="backup_thumbnails" type="bool" label="30034" default="true" />
|
||||
<setting id="backup_config" type="bool" label="30035" default="true" />
|
||||
<setting id="custom_dir_1_enable" type="bool" label="30036" default="false" />
|
||||
<setting id="backup_custom_dir_1" type="folder" label="30018" default="" visible="eq(-1,true)"/>
|
||||
<setting id="custom_dir_2_enable" type="bool" label="30037" default="false" />
|
||||
<setting id="backup_custom_dir_2" type="folder" label="30018" default="" visible="eq(-1,true)"/>
|
||||
<setting id="backup_selection_type" type="enum" lvalues="30014|30015" default="0" label="30023" />
|
||||
<setting id="backup_addon_data" type="bool" label="30031" default="false" visible="eq(-1,0)"/>
|
||||
<setting id="backup_config" type="bool" label="30035" default="true" visible="eq(-2,0)"/>
|
||||
<setting id="backup_database" type="bool" label="30032" default="true" visible="eq(-3,0)"/>
|
||||
<setting id="backup_game_saves" type="bool" label="30133" default="false" visible="eq(-4,0)" />
|
||||
<setting id="backup_playlists" type="bool" label="30033" default="true" visible="eq(-5,0)"/>
|
||||
<setting id="backup_profiles" type="bool" label="30080" default="false" visible="eq(-6,0)"/>
|
||||
<setting id="backup_thumbnails" type="bool" label="30034" default="true" visible="eq(-7,0)"/>
|
||||
<setting id="backup_addons" type="bool" label="30030" default="true" visible="eq(-8,0)" />
|
||||
<setting id="advanced_button" type="action" label="30125" visible="eq(-9,1)" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_editor)" />
|
||||
<setting id="advanced_defaults" type="action" label="30139" visible="eq(-10,1)" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_copy_config)" />
|
||||
</category>
|
||||
<category id="scheduling" label="30013">
|
||||
<setting id="enable_scheduler" type="bool" label="30060" default="false" />
|
||||
|
21
scheduler.py
@ -1,12 +1,14 @@
|
||||
import xbmc
|
||||
import xbmcvfs
|
||||
import xbmcgui
|
||||
import datetime
|
||||
from datetime import datetime
|
||||
import time
|
||||
import resources.lib.utils as utils
|
||||
from resources.lib.croniter import croniter
|
||||
from resources.lib.backup import XbmcBackup
|
||||
|
||||
UPGRADE_INT = 1 #to keep track of any upgrade notifications
|
||||
|
||||
class BackupScheduler:
|
||||
monitor = None
|
||||
enabled = "false"
|
||||
@ -55,6 +57,11 @@ class BackupScheduler:
|
||||
|
||||
def start(self):
|
||||
|
||||
#display upgrade messages if they exist
|
||||
if(int(utils.getSetting('upgrade_notes')) < UPGRADE_INT):
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30132))
|
||||
utils.setSetting('upgrade_notes',str(UPGRADE_INT))
|
||||
|
||||
#check if a backup should be resumed
|
||||
resumeRestore = self._resumeCheck()
|
||||
|
||||
@ -63,7 +70,7 @@ class BackupScheduler:
|
||||
restore.selectRestore(self.restore_point)
|
||||
#skip the advanced settings check
|
||||
restore.skipAdvanced()
|
||||
restore.run(XbmcBackup.Restore)
|
||||
restore.restore()
|
||||
|
||||
while(not self.monitor.abortRequested()):
|
||||
|
||||
@ -98,9 +105,9 @@ class BackupScheduler:
|
||||
if(backup.remoteConfigured()):
|
||||
|
||||
if(int(utils.getSetting('progress_mode')) in [0,1]):
|
||||
backup.run(XbmcBackup.Backup,True)
|
||||
backup.backup(True)
|
||||
else:
|
||||
backup.run(XbmcBackup.Backup,False)
|
||||
backup.backup(False)
|
||||
|
||||
#check if this is a "one-off"
|
||||
if(int(utils.getSetting("schedule_interval")) == 0):
|
||||
@ -116,12 +123,12 @@ class BackupScheduler:
|
||||
#find the cron expression and get the next run time
|
||||
cron_exp = self.parseSchedule()
|
||||
|
||||
cron_ob = croniter(cron_exp,datetime.datetime.fromtimestamp(now))
|
||||
cron_ob = croniter(cron_exp,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'))
|
||||
utils.log("scheduler will run again on " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run),['dateshort','time']))
|
||||
|
||||
#write the next time to a file
|
||||
fh = xbmcvfs.File(self.next_run_path, 'w')
|
||||
@ -130,7 +137,7 @@ class BackupScheduler:
|
||||
|
||||
#only show when not in silent mode
|
||||
if(progress_mode != 2):
|
||||
utils.showNotification(utils.getString(30081) + " " + datetime.datetime.fromtimestamp(self.next_run).strftime('%m-%d-%Y %H:%M'))
|
||||
utils.showNotification(utils.getString(30081) + " " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run),['dateshort','time']))
|
||||
|
||||
def settingsChanged(self):
|
||||
current_enabled = utils.getSetting("enable_scheduler")
|
||||
|