19 Commits

Author SHA1 Message Date
Rob Weber
a4bb3f3feb completely changed this class, default no longer matters. Just restore settings that differ from current, ignore default flags closes #154 2019-09-11 10:02:45 -05:00
Rob Weber
1f6324b2d5 use path separators for os 2019-09-11 08:58:21 -05:00
Rob Weber
12b25f7cea version bump 2019-09-10 15:38:34 -05:00
Rob
5d9d8a1820 Guisettings fix (#156)
fix guisettings restore issues - thanks Bluerayx
2019-09-10 15:36:52 -05:00
Rob Weber
b34e538d6b probot not adhering to onlyLabels at the moment 2019-08-27 10:41:19 -05:00
Rob Weber
b5a7aada4c added probot to help with stale issues 2019-08-27 09:53:25 -05:00
Rob Weber
1a9c43b998 Merge branch 'master' of https://github.com/robweber/xbmcbackup 2019-08-27 09:42:23 -05:00
Rob Weber
b7f4b14fe2 delmit with comma, not ampersand (xml formatting) 2019-08-27 09:42:07 -05:00
Rob Weber
787b054bba Merge branch 'master' of https://github.com/robweber/xbmcbackup 2019-08-27 09:08:31 -05:00
Rob Weber
a7be48a341 forgot to add screenshots back in 2019-08-27 09:08:05 -05:00
Rob Weber
2fe76b7b52 Merge branch 'master' of https://github.com/robweber/xbmcbackup 2019-08-27 09:06:02 -05:00
Rob Weber
3aed105fd7 lowercase filename ext 2019-08-27 09:05:23 -05:00
Rob Weber
c9b4554eac allow folder/id mistmatch for this addon 2019-08-27 09:02:21 -05:00
Rob Weber
e736b964a5 added build status badge 2019-08-27 09:01:19 -05:00
Rob Weber
4c5f6774df added travis testing script (kodi addon checker) 2019-08-27 08:45:45 -05:00
Rob Weber
1f2e315208 updated screenshots 2019-08-27 08:30:26 -05:00
Rob Weber
138f910d07 updated breaking change dialog (onetime) 2019-08-26 15:43:56 -05:00
Rob Weber
1d3b2f58ab updated version - out of beta 2019-08-26 15:43:44 -05:00
Rob
865416977d 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
2019-08-26 15:40:15 -05:00
32 changed files with 1612 additions and 1112 deletions

18
.github/stale-dontuse.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 31
# Number of days of inactivity before a stale Issue or Pull Request is closed
daysUntilClose: 14
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels:
- waiting for info
- wontfix
# Label to use when marking as stale
staleLabel: inactive
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as inactive because it has not had
recent activity. It will be closed if no further activity occurs.

13
.travis.yml Normal file
View File

@@ -0,0 +1,13 @@
dist: xenial
language: python
python: 3.7
install:
- pip install kodi-addon-checker
before_script:
- git config core.quotepath false
# command to run our tests
script:
- kodi-addon-checker --branch=krypton --allow-folder-id-mismatch

View File

@@ -1,4 +1,7 @@
# Backup Addon # Backup Addon
[![Build Status](https://travis-ci.org/robweber/xbmcbackup.svg?branch=master)](https://travis-ci.org/robweber/xbmcbackup)
__Kodi Version Compatibility:__ Kodi 17.x (Krypton) and greater
## About ## About
@@ -17,6 +20,11 @@ For more specific information please check out the [wiki on Github](https://gith
* [FAQ](https://github.com/robweber/xbmcbackup/wiki/FAQ) * [FAQ](https://github.com/robweber/xbmcbackup/wiki/FAQ)
## Attributions
Icon files from Open Iconic — www.useiconic.com/open

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.xbmcbackup" <addon id="script.xbmcbackup"
name="Backup" version="1.1.3" provider-name="robweber"> name="Backup" version="1.5.1" provider-name="robweber">
<requires> <requires>
<!-- jarvis --> <!-- 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.httplib2" version="0.8.0" />
<import addon="script.module.oauth2client" version="4.1.2" /> <import addon="script.module.oauth2client" version="4.1.2" />
<import addon="script.module.uritemplate" version="0.6" /> <import addon="script.module.uritemplate" version="0.6" />
@@ -89,12 +89,14 @@
<source>https://github.com/robweber/xbmcbackup</source> <source>https://github.com/robweber/xbmcbackup</source>
<email></email> <email></email>
<assets> <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> </assets>
<news>Version 1.1.4 <news>Version 1.5.1
- added file chunk support for dropbox uploads - fix guisettings restores not working - thanks Bluerayx
- fixed settings duplicate ids, thanks aster-anto
- added scheduler delay to assist with time sync (rpi mostly)
</news> </news>
</extension> </extension>
</addon> </addon>

View File

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

View File

@@ -1,3 +1,11 @@
Version 1.5.1
fix guisettings restores not working - thanks Bluerayx
Version 1.5.0
Overhaul of file selection and restore procedures. Breaking Change with previous versions PR117
Version 1.1.3 Version 1.1.3
added file chunk support for dropbox uploads added file chunk support for dropbox uploads

View File

@@ -1,5 +1,5 @@
import urlparse import sys, urlparse
import xbmcgui import xbmc, xbmcgui
import resources.lib.utils as utils import resources.lib.utils as utils
from resources.lib.backup import XbmcBackup 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 wasn't passed in as arg, get from user
if(mode == -1): 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 #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 #check if program should be run
if(mode != -1): if(mode != -1):
@@ -39,7 +46,9 @@ if(mode != -1):
if(mode == 2): if(mode == 2):
#open the settings dialog #open the settings dialog
utils.openSettings() 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()): elif(backup.remoteConfigured()):
if(mode == backup.Restore): if(mode == backup.Restore):
@@ -70,7 +79,12 @@ if(mode != -1):
if(selectedRestore != -1): if(selectedRestore != -1):
backup.selectRestore(restorePoints[selectedRestore][0]) 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: else:
#can't go any further #can't go any further
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045)) xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045))

64
launcher.py Normal file
View 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()

View File

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

View 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
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -48,6 +48,14 @@ msgctxt "#30013"
msgid "Scheduling" msgid "Scheduling"
msgstr "Scheduling" msgstr "Scheduling"
msgctxt "#30014"
msgid "Simple"
msgstr "Simple"
msgctxt "#30015"
msgid "Advanced"
msgstr "Advanced"
msgctxt "#30016" msgctxt "#30016"
msgid "Backup" msgid "Backup"
msgstr "Backup" msgstr "Backup"
@@ -129,12 +137,12 @@ msgid "Config Files"
msgstr "Config Files" msgstr "Config Files"
msgctxt "#30036" msgctxt "#30036"
msgid "Custom Directory 1" msgid "Disclaimer"
msgstr "Custom Directory 1" msgstr "Disclaimer"
msgctxt "#30037" msgctxt "#30037"
msgid "Custom Directory 2" msgid "Canceling this menu will close and save changes"
msgstr "Custom Directory 2" msgstr "Canceling this menu will close and save changes"
msgctxt "#30038" msgctxt "#30038"
msgid "Advanced Settings Detected" msgid "Advanced Settings Detected"
@@ -420,3 +428,131 @@ msgstr "Visit https://console.developers.google.com/"
msgctxt "#30109" msgctxt "#30109"
msgid "Run on startup if missed" msgid "Run on startup if missed"
msgstr "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.5.0 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 ""

View File

@@ -129,14 +129,6 @@ msgctxt "#30035"
msgid "Config Files" msgid "Config Files"
msgstr "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" msgctxt "#30038"
msgid "Advanced Settings Detected" msgid "Advanced Settings Detected"
msgstr "Advanced Settings Detected" msgstr "Advanced Settings Detected"

View 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)

View File

@@ -3,9 +3,18 @@ import xbmcgui
import xbmcvfs import xbmcvfs
import resources.lib.tinyurl as tinyurl import resources.lib.tinyurl as tinyurl
import resources.lib.utils as utils import resources.lib.utils as utils
import dropbox
from resources.lib.pydrive.auth import GoogleAuth #don't die on import error yet, these might not even get used
from resources.lib.pydrive.drive import GoogleDrive 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: class DropboxAuthorizer:
APP_KEY = "" APP_KEY = ""

View File

@@ -4,6 +4,8 @@ import xbmcvfs
import utils as utils import utils as utils
import time import time
import json import json
import os
from datetime import datetime
from vfs import XBMCFileSystem,DropboxFileSystem,ZipFileSystem,GoogleDriveFilesystem from vfs import XBMCFileSystem,DropboxFileSystem,ZipFileSystem,GoogleDriveFilesystem
from progressbar import BackupProgressBar from progressbar import BackupProgressBar
from resources.lib.guisettings import GuiSettingsManager from resources.lib.guisettings import GuiSettingsManager
@@ -23,6 +25,9 @@ class XbmcBackup:
Backup = 0 Backup = 0
Restore = 1 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 #file systems
xbmc_vfs = None xbmc_vfs = None
remote_vfs = None remote_vfs = None
@@ -36,7 +41,6 @@ class XbmcBackup:
filesLeft = 0 filesLeft = 0
filesTotal = 1 filesTotal = 1
fileManager = None
restore_point = None restore_point = None
skip_advanced = False #if we should check for the existance of advancedsettings in the restore skip_advanced = False #if we should check for the existance of advancedsettings in the restore
@@ -69,7 +73,8 @@ class XbmcBackup:
return result return result
def listBackups(self): #reverse - should reverse the resulting, default is true - newest to oldest
def listBackups(self,reverse=True):
result = [] result = []
#get all the folders in the current root path #get all the folders in the current root path
@@ -78,12 +83,8 @@ class XbmcBackup:
for aDir in dirs: for aDir in dirs:
if(self.remote_vfs.exists(self.remote_base_path + aDir + "/xbmcbackup.val")): 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 #format the name according to regional settings
folderName = '' folderName = self._dateFormat(aDir)
if(len(aDir) > 8):
folderName = aDir[6:8] + '-' + aDir[4:6] + '-' + aDir[0:4] + " " + aDir[8:10] + ":" + aDir[10:12]
else:
folderName = aDir[6:8] + '-' + aDir[4:6] + '-' + aDir[0:4]
result.append((aDir,folderName)) result.append((aDir,folderName))
@@ -91,18 +92,15 @@ class XbmcBackup:
file_ext = aFile.split('.')[-1] file_ext = aFile.split('.')[-1]
folderName = utils.encode(aFile.split('.')[0]) 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 #format the name according to regional settings
if(len(aFile ) > 8): folderName = self._dateFormat(folderName)
folderName = aFile [6:8] + '-' + aFile [4:6] + '-' + aFile [0:4] + " " + aFile [8:10] + ":" + aFile [10:12]
else:
folderName = aFile [6:8] + '-' + aFile [4:6] + '-' + aFile [0:4]
result.append((aFile ,folderName)) result.append((aFile ,folderName))
result.sort(key=folderSort) result.sort(key=folderSort,reverse=reverse)
return result return result
@@ -112,46 +110,10 @@ class XbmcBackup:
def skipAdvanced(self): def skipAdvanced(self):
self.skip_advanced = True self.skip_advanced = True
def run(self,mode=-1,progressOverride=False): def backup(self,progressOverride=False):
#set windows setting to true shouldContinue = self._setupVFS(self.Backup,progressOverride)
window = xbmcgui.Window(10000)
window.setProperty(utils.__addon_id__ + ".running","true")
#append backup folder name if(shouldContinue):
progressBarTitle = utils.getString(30010) + " - "
if(mode == self.Backup and self.remote_vfs.root_path != ''):
if(utils.getSetting("compress_backups") == 'true'):
#delete old temp file
if(self.xbmc_vfs.exists(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
if(not self.xbmc_vfs.rmfile(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
#we had some kind of error deleting the old file
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30096),utils.getString(30097))
return
#save the remote file system and use the zip vfs
self.saved_remote_vfs = self.remote_vfs
self.remote_vfs = ZipFileSystem(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"),"w")
self.remote_vfs.set_root(self.remote_vfs.root_path + time.strftime("%Y%m%d%H%M") + "/")
progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30016)
elif(mode == self.Restore and self.restore_point != None and self.remote_vfs.root_path != ''):
if(self.restore_point.split('.')[-1] != 'zip'):
self.remote_vfs.set_root(self.remote_vfs.root_path + self.restore_point + "/")
progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30017)
else:
#kill the program here
self.remote_vfs = None
return
utils.log(utils.getString(30047) + ": " + self.xbmc_vfs.root_path)
utils.log(utils.getString(30048) + ": " + self.remote_vfs.root_path)
#setup the progress bar
self.progressBar = BackupProgressBar(progressOverride)
self.progressBar.create(progressBarTitle,utils.getString(30049) + "......")
if(mode == self.Backup):
utils.log(utils.getString(30023) + " - " + utils.getString(30016)) utils.log(utils.getString(30023) + " - " + utils.getString(30016))
#check if remote path exists #check if remote path exists
if(self.remote_vfs.exists(self.remote_vfs.root_path)): if(self.remote_vfs.exists(self.remote_vfs.root_path)):
@@ -161,8 +123,37 @@ class XbmcBackup:
#make the remote directory #make the remote directory
self.remote_vfs.mkdir(self.remote_vfs.root_path) 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 #create a validation file for backup rotation
writeCheck = self._createValidationFile() writeCheck = self._createValidationFile(allFiles)
if(not writeCheck): if(not writeCheck):
#we may not be able to write to this destination for some reason #we may not be able to write to this destination for some reason
@@ -171,90 +162,14 @@ class XbmcBackup:
if(not shouldContinue): if(not shouldContinue):
return 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 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 #backup all the files
self.filesLeft = self.filesTotal self.filesLeft = self.filesTotal
for fileGroup in allFiles: for fileGroup in allFiles:
self.xbmc_vfs.set_root(fileGroup['source']) self.xbmc_vfs.set_root(xbmc.translatePath(fileGroup['source']))
self.remote_vfs.set_root(fileGroup['dest']) self.remote_vfs.set_root(fileGroup['dest'] + fileGroup['name'])
filesCopied = self.backupFiles(fileGroup['files'],self.xbmc_vfs,self.remote_vfs) filesCopied = self._copyFiles(fileGroup['files'],self.xbmc_vfs,self.remote_vfs)
if(not filesCopied): if(not filesCopied):
utils.showNotification(utils.getString(30092)) utils.showNotification(utils.getString(30092))
@@ -265,6 +180,8 @@ class XbmcBackup:
self.remote_vfs.set_root(orig_base_path) self.remote_vfs.set_root(orig_base_path)
if(utils.getSetting("compress_backups") == 'true'): if(utils.getSetting("compress_backups") == 'true'):
fileManager = FileManager(self.xbmc_vfs)
#send the zip file to the real remote vfs #send the zip file to the real remote vfs
zip_name = self.remote_vfs.root_path[:-1] + ".zip" zip_name = self.remote_vfs.root_path[:-1] + ".zip"
self.remote_vfs.cleanup() self.remote_vfs.cleanup()
@@ -276,7 +193,7 @@ class XbmcBackup:
self.remote_vfs = self.saved_remote_vfs self.remote_vfs = self.saved_remote_vfs
self.progressBar.updateProgress(98, utils.getString(30088)) 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): if(not fileCopied):
#zip archive copy filed, inform the user #zip archive copy filed, inform the user
@@ -288,7 +205,13 @@ class XbmcBackup:
#remove old backups #remove old backups
self._rotateBackups() 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)) utils.log(utils.getString(30023) + " - " + utils.getString(30017))
#catch for if the restore point is actually a zip file #catch for if the restore point is actually a zip file
@@ -304,7 +227,7 @@ class XbmcBackup:
zipFile = [] zipFile = []
zipFile.append(self.remote_base_path + self.restore_point) 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: else:
utils.log("zip file exists already") 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) xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path)
return return
if(not self._checkValidationFile(self.remote_vfs.root_path)): valFile = self._checkValidationFile(self.remote_vfs.root_path)
if(valFile == None):
#don't continue #don't continue
return return
@@ -340,104 +264,55 @@ class XbmcBackup:
allFiles = [] allFiles = []
fileManager = FileManager(self.remote_vfs) 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 #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 #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)) restartXbmc = xbmcgui.Dialog().yesno(utils.getString(30038),utils.getString(30039),utils.getString(30040), utils.getString(30041))
if(restartXbmc): if(restartXbmc):
#add only this file to the file list #add only this file to the file list
fileManager.addFile(self.remote_vfs.root_path + "userdata/advancedsettings.xml") fileManager.addFile(self.remote_vfs.root_path + "config/advancedsettings.xml")
self.backupFiles(fileManager.getFiles(),self.remote_vfs,self.xbmc_vfs) self._copyFiles(fileManager.getFiles(),self.remote_vfs,self.xbmc_vfs)
#let the service know to resume this backup on startup #let the service know to resume this backup on startup
self._createResumeBackupFile() self._createResumeBackupFile()
#do not continue running #do not continue running
xbmcgui.Dialog().ok(utils.getString(30077),utils.getString(30078)) xbmcgui.Dialog().ok(utils.getString(30077),utils.getString(30078))
return return
fileManager.addFile('-' + self.remote_vfs.root_path + 'userdata/keymaps') #use a multiselect dialog to select sets to restore
fileManager.walkTree(self.remote_vfs.root_path + "userdata/keymaps") restoreSets = [n['name'] for n in valFile['directories']]
fileManager.addFile('-' + self.remote_vfs.root_path + "userdata/peripheral_data") #if passed in list, skip selection
fileManager.walkTree(self.remote_vfs.root_path + "userdata/peripheral_data") if(selectedSets == None):
selectedSets = xbmcgui.Dialog().multiselect(utils.getString(30131),restoreSets)
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()})
else: else:
utils.log("error path not found: " + self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path)) selectedSets = [restoreSets.index(n) for n in selectedSets if n in restoreSets] #if set name not found just skip it
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path + "custom_" + self._createCRC(utils.getSetting('backup_custom_dir_1')))
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')) #add this directory
if(self.remote_vfs.exists(self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path) + "/")): aDir = valFile['directories'][index]
#index files to restore
fileManager.walkTree(self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path)) 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() 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: else:
utils.log("error path not found: " + self.remote_vfs.root_path + "custom_" + self._createCRC(self.xbmc_vfs.root_path)) 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 + "custom_" + self._createCRC(utils.getSetting('backup_custom_dir_2'))) xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_vfs.root_path + aDir['name'])
#restore all the files #restore all the files
self.filesLeft = self.filesTotal self.filesLeft = self.filesTotal
for fileGroup in allFiles: for fileGroup in allFiles:
self.remote_vfs.set_root(fileGroup['source']) self.remote_vfs.set_root(fileGroup['source'])
self.xbmc_vfs.set_root(fileGroup['dest']) 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 .....") self.progressBar.updateProgress(99,"Clean up operations .....")
@@ -446,26 +321,75 @@ class XbmcBackup:
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point)) self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point))
self.xbmc_vfs.rmdir(self.remote_vfs.root_path) self.xbmc_vfs.rmdir(self.remote_vfs.root_path)
if(utils.getSetting("backup_config") == "true"):
#update the guisettings information (or what we can from it) #update the guisettings information (or what we can from it)
gui_settings = GuiSettingsManager('special://home/userdata/guisettings.xml') gui_settings = GuiSettingsManager()
gui_settings.run() gui_settings.run()
#call update addons to refresh everything #call update addons to refresh everything
xbmc.executebuiltin('UpdateLocalAddons') 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.xbmc_vfs.cleanup()
self.remote_vfs.cleanup() self.remote_vfs.cleanup()
self.progressBar.close() self.progressBar.close()
#reset the window setting #reset the window setting
window = xbmcgui.Window(10000)
window.setProperty(utils.__addon_id__ + ".running","") window.setProperty(utils.__addon_id__ + ".running","")
def backupFiles(self,fileList,source,dest): def _copyFiles(self,fileList,source,dest):
result = True result = True
utils.log("Writing files to: " + dest.root_path)
utils.log("Source: " + source.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: for aFile in fileList:
if(not self.progressBar.checkCancel()): if(not self.progressBar.checkCancel()):
utils.log('Writing file: ' + aFile,xbmc.LOGDEBUG) utils.log('Writing file: ' + aFile,xbmc.LOGDEBUG)
@@ -474,13 +398,15 @@ class XbmcBackup:
dest.mkdir(dest.root_path + aFile[len(source.root_path) + 1:]) dest.mkdir(dest.root_path + aFile[len(source.root_path) + 1:])
else: else:
self._updateProgress() self._updateProgress()
wroteFile = True wroteFile = True
destFile = dest.root_path + aFile[len(source.root_path):]
if(isinstance(source,DropboxFileSystem) or isinstance(source,GoogleDriveFilesystem)): if(isinstance(source,DropboxFileSystem) or isinstance(source,GoogleDriveFilesystem)):
#if copying from cloud storage we need the file handle, use get_file #if copying from cloud storage we need the file handle, use get_file
wroteFile = source.get_file(aFile,dest.root_path + aFile[len(source.root_path):]) wroteFile = source.get_file(aFile,destFile)
else: else:
#copy using normal method #copy using normal method
wroteFile = dest.put(aFile,dest.root_path + aFile[len(source.root_path):]) wroteFile = dest.put(aFile,destFile)
#if result is still true but this file failed #if result is still true but this file failed
if(not wroteFile and result): if(not wroteFile and result):
@@ -489,21 +415,29 @@ class XbmcBackup:
return result return result
def _createCRC(self,string): def _addBackupDir(self,folder_name,root_path,dirList):
#create hash from string utils.log('Backup set: ' + folder_name)
string = string.lower() fileManager = FileManager(self.xbmc_vfs)
bytes = bytearray(string.encode())
crc = 0xffffffff;
for b in bytes:
crc = crc ^ (b << 24)
for i in range(8):
if (crc & 0x80000000 ):
crc = (crc << 1) ^ 0x04C11DB7
else:
crc = crc << 1;
crc = crc & 0xFFFFFFFF
return '%08x' % crc 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): def _updateProgress(self,message=None):
self.filesLeft = self.filesLeft - 1 self.filesLeft = self.filesLeft - 1
@@ -514,7 +448,7 @@ class XbmcBackup:
if(total_backups > 0): if(total_backups > 0):
#get a list of valid backup folders #get a list of valid backup folders
dirs = self.listBackups() dirs = self.listBackups(reverse=False)
if(len(dirs) > total_backups): if(len(dirs) > total_backups):
#remove backups to equal total wanted #remove backups to equal total wanted
@@ -534,9 +468,16 @@ class XbmcBackup:
remove_num = remove_num + 1 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 = 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.write("")
vFile.close() vFile.close()
@@ -556,7 +497,7 @@ class XbmcBackup:
return success return success
def _checkValidationFile(self,path): def _checkValidationFile(self,path):
result = False result = None
#copy the file and open it #copy the file and open it
self.xbmc_vfs.put(path + "xbmcbackup.val",xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val")) self.xbmc_vfs.put(path + "xbmcbackup.val",xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
@@ -569,16 +510,17 @@ class XbmcBackup:
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val")) xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
try: try:
json_dict = json.loads(jsonString) result = json.loads(jsonString)
if(xbmc.getInfoLabel('System.BuildVersion') == json_dict['xbmc_version']): if(xbmc.getInfoLabel('System.BuildVersion') != result['xbmc_version']):
result = True shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30085),utils.getString(30086),utils.getString(30044))
else:
result = xbmcgui.Dialog().yesno(utils.getString(30085),utils.getString(30086),utils.getString(30044)) if(not shouldContinue):
result = None
except ValueError: except ValueError:
#may fail on older archives #may fail on older archives
result = True result = None
return result return result
@@ -587,28 +529,45 @@ class XbmcBackup:
rFile.write(self.restore_point) rFile.write(self.restore_point)
rFile.close() rFile.close()
def _readBackupConfig(self,aFile):
jFile = xbmcvfs.File(xbmc.translatePath(aFile),'r')
jsonString = jFile.read()
jFile.close()
return json.loads(jsonString)
class FileManager: class FileManager:
not_dir = ['.zip','.xsp','.rar'] not_dir = ['.zip','.xsp','.rar']
exclude_dir = [] exclude_dir = []
root_dirs = []
def __init__(self,vfs): def __init__(self,vfs):
self.vfs = vfs self.vfs = vfs
self.fileArray = [] 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] directory = directory[:-1]
if(self.vfs.exists(directory + "/")): if(self.vfs.exists(directory + os.path.sep)):
dirs,files = self.vfs.listdir(directory) dirs,files = self.vfs.listdir(directory)
if(recurse):
#create all the subdirs first #create all the subdirs first
for aDir in dirs: for aDir in dirs:
dirPath = xbmc.validatePath(xbmc.translatePath(directory + "/" + aDir)) dirPath = xbmc.validatePath(xbmc.translatePath(directory + os.path.sep + aDir))
file_ext = aDir.split('.')[-1] 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) self.addFile("-" + dirPath)
@@ -624,9 +583,15 @@ class FileManager:
#copy all the files #copy all the files
for aFile in files: for aFile in files:
filePath = xbmc.translatePath(directory + "/" + aFile) filePath = xbmc.translatePath(directory + os.path.sep + aFile)
self.addFile(filePath) 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): def addFile(self,filename):
try: try:
filename = filename.decode('UTF-8') filename = filename.decode('UTF-8')
@@ -643,13 +608,20 @@ class FileManager:
except UnicodeDecodeError: except UnicodeDecodeError:
filename = filename.decode('ISO-8859-2') 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 #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) self.exclude_dir.append(filename)
def getFiles(self): def getFiles(self):
result = self.fileArray result = self.fileArray
self.fileArray = [] self.fileArray = []
self.root_dirs = []
self.exclude_dir = []
return result return result
def size(self): def size(self):

View File

@@ -511,9 +511,5 @@ def _params_to_urlencoded(params):
else: else:
return str(o).encode('utf-8') return str(o).encode('utf-8')
#fix for python 2.6 utf8_params = {encode(k): encode(v) for k, v in six.iteritems(params)}
utf8_params = {}
for k,v in six.iteritems(params):
utf8_params[encode(k)] = encode(v)
return url_encode(utf8_params) return url_encode(utf8_params)

View File

@@ -237,12 +237,11 @@ class StoneToPythonPrimitiveSerializer(StoneSerializerBase):
def encode_map(self, validator, value): def encode_map(self, validator, value):
validated_value = validator.validate(value) validated_value = validator.validate(value)
#fix for python 2.6 return {
result = {} self.encode_sub(validator.key_validator, key):
for key, value in validated_value.items(): self.encode_sub(validator.value_validator, value) for
result[self.encode_sub(validator.key_validator,key)] = self.encode_sub(validator.value_validator, value) key, value in validated_value.items()
}
return result
def encode_nullable(self, validator, value): def encode_nullable(self, validator, value):
if value is None: if value is None:
@@ -831,12 +830,11 @@ def _decode_list(
if not isinstance(obj, list): if not isinstance(obj, list):
raise bv.ValidationError( raise bv.ValidationError(
'expected list, got %s' % bv.generic_type_name(obj)) 'expected list, got %s' % bv.generic_type_name(obj))
return [
result = [] _json_compat_obj_decode_helper(
for item in obj: data_type.item_validator, item, alias_validators, strict,
result.append(_json_compat_obj_decode_helper(data_type.item_validator, item, alias_validators, strict,old_style, for_msgpack)) old_style, for_msgpack)
for item in obj]
return result
def _decode_map( def _decode_map(
@@ -848,12 +846,15 @@ def _decode_map(
if not isinstance(obj, dict): if not isinstance(obj, dict):
raise bv.ValidationError( raise bv.ValidationError(
'expected dict, got %s' % bv.generic_type_name(obj)) 'expected dict, got %s' % bv.generic_type_name(obj))
return {
result = {} _json_compat_obj_decode_helper(
for key, value in obj.items(): data_type.key_validator, key, alias_validators, strict,
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) old_style, for_msgpack):
_json_compat_obj_decode_helper(
return result data_type.value_validator, value, alias_validators, strict,
old_style, for_msgpack)
for key, value in obj.items()
}
def _decode_nullable( def _decode_nullable(

View File

@@ -422,13 +422,10 @@ class Map(Composite):
def validate(self, val): def validate(self, val):
if not isinstance(val, dict): if not isinstance(val, dict):
raise ValidationError('%r is not a valid dict' % val) raise ValidationError('%r is not a valid dict' % val)
return {
#fix for python 2.6 self.key_validator.validate(key):
result = {} self.value_validator.validate(value) for key, value in val.items()
for key, value in val.items(): }
result[self.key_validator.validate(key)] = self.value_validator.validate(value)
return result
class Struct(Composite): class Struct(Composite):

View File

@@ -6,94 +6,68 @@ import xbmc,xbmcvfs
class GuiSettingsManager: class GuiSettingsManager:
settingsFile = None
doc = None doc = None
settings_allowed = list()
found_settings = list()
def __init__(self,settingsFile): def __init__(self):
self._readFile(xbmc.translatePath(settingsFile)) #first make a copy of the file
xbmcvfs.copy(xbmc.translatePath('special://home/userdata/guisettings.xml'), xbmc.translatePath("special://home/userdata/guisettings.xml.restored"))
#read in the copy
self._readFile(xbmc.translatePath('special://home/userdata/guisettings.xml.restored'))
def run(self): def run(self):
#get a list of all the settings we can manipulate via json #get a list of all the settings we can manipulate via json
json_response = json.loads(xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettings","params":{"level":"advanced"}}')) json_response = json.loads(xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettings","params":{"level":"advanced"}}'))
settings = json_response['result']['settings'] settings = json_response['result']['settings']
currentSettings = {}
for aSetting in settings: for aSetting in settings:
self.settings_allowed.append(aSetting['id']) if('value' in aSetting):
currentSettings[aSetting['id']] = aSetting['value']
#parse the existing xml file and get all the settings #parse the existing xml file and get all the settings we need to restore
root_nodes = self.__parseNodes(self.doc.documentElement) restoreSettings = self.__parseNodes(self.doc.getElementsByTagName('setting'))
for aNode in root_nodes: #get a list where the restore setting value != the current value
secondary_list = self.__parseNodes(self.doc.getElementsByTagName(aNode.name)[0]) updateSettings = {k: v for k, v in restoreSettings.items() if (k in currentSettings and currentSettings[k] != v)}
for secondNode in secondary_list:
#if the node does not have children and is not default
if(not secondNode.hasChildren and not secondNode.isDefault):
if(secondNode.json_name() in self.settings_allowed):
self.found_settings.append(secondNode)
#go through all the found settings and update them #go through all the found settings and update them
for aSetting in self.found_settings: jsonObj = {"jsonrpc":"2.0","id":1,"method":"Settings.SetSettingValue","params":{"setting":"","value":""}}
utils.log("updating: " + aSetting.json_name() + ", value: " + aSetting.value) for anId, aValue in updateSettings.items():
utils.log("updating: " + anId + ", value: " + str(aValue))
#check for boolean and numeric values jsonObj['params']['setting'] = anId
if(aSetting.value.isdigit() or (aSetting.value == 'true' or aSetting.value == 'false')): jsonObj['params']['value'] = aValue
xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"' + aSetting.json_name() + '","value":' + aSetting.value + '}}')
else:
xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"' + aSetting.json_name() + '","value":"' + utils.encode(aSetting.value) + '"}}')
#make a copy of the guisettings file to make user based restores easier xbmc.executeJSONRPC(json.dumps(jsonObj))
xbmcvfs.copy(self.settingsFile, xbmc.translatePath("special://home/userdata/guisettings.xml.restored"))
def __parseNodes(self,nodeList): def __parseNodes(self,nodeList):
result = [] result = {}
for node in nodeList.childNodes: for node in nodeList:
if(node.nodeType == self.doc.ELEMENT_NODE): nodeValue = ''
aSetting = SettingNode(node.nodeName) if(node.firstChild != None):
nodeValue = node.firstChild.nodeValue
#detect if there are any element nodes #check for numbers and booleans
if(len(node.childNodes) > 0): if(nodeValue.isdigit()):
for child_node in node.childNodes: nodeValue = int(nodeValue)
if(child_node.nodeType == self.doc.ELEMENT_NODE): elif(nodeValue == 'true'):
aSetting.hasChildren = True nodeValue = True
elif(nodeValue == 'false'):
nodeValue = False
if(not aSetting.hasChildren and len(node.childNodes) > 0): result[node.getAttribute('id')] = nodeValue
aSetting.value = node.firstChild.nodeValue
if('default' not in node.attributes.keys()):
aSetting.isDefault = False
aSetting.parent = node.parentNode.nodeName
result.append(aSetting)
return result return result
def _readFile(self,fileLoc): def _readFile(self,fileLoc):
if(xbmcvfs.exists(fileLoc)): if(xbmcvfs.exists(fileLoc)):
try: try:
self.doc = minidom.parse(fileLoc) self.doc = minidom.parse(fileLoc)
self.settingsFile = fileLoc
except ExpatError: except ExpatError:
utils.log("Can't read " + fileLoc) utils.log("Can't read " + fileLoc)
class SettingNode:
name = ''
value = ''
hasChildren = False
isDefault = True
parent = ''
def __init__(self,name):
self.name = name
def json_name(self):
return self.parent + "." + self.name

View File

@@ -14,11 +14,11 @@ def addon_dir():
def openSettings(): def openSettings():
__Addon.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) xbmc.log(encode(__addon_id__ + "-" + __Addon.getAddonInfo('version') + ": " + message),level=loglevel)
def showNotification(message): 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): def getSetting(name):
return __Addon.getSetting(name) return __Addon.getSetting(name)
@@ -29,6 +29,14 @@ def setSetting(name,value):
def getString(string_id): def getString(string_id):
return __Addon.getLocalizedString(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): def encode(string):
result = '' result = ''

View File

@@ -1,16 +1,12 @@
import utils as utils import utils as utils
import tinyurl as tinyurl
import xbmc import xbmc
import xbmcvfs import xbmcvfs
import xbmcgui import xbmcgui
import zipfile import zipfile
import zlib
import os
import os.path import os.path
import sys import sys
import dropbox import dropbox
from dropbox.files import WriteMode,CommitInfo,UploadSessionCursor from dropbox.files import WriteMode,CommitInfo,UploadSessionCursor
from pydrive.drive import GoogleDrive
from authorizers import DropboxAuthorizer,GoogleDriveAuthorizer from authorizers import DropboxAuthorizer,GoogleDriveAuthorizer
class Vfs: class Vfs:

View File

@@ -4,6 +4,7 @@
<setting id="compress_backups" type="bool" label="30087" default="false" /> <setting id="compress_backups" type="bool" label="30087" default="false" />
<setting id="backup_rotation" type="number" label="30026" default="0" /> <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="progress_mode" type="enum" label="30022" lvalues="30082|30083|30084" default="0" />
<setting id="upgrade_notes" type="number" label="upgrade_notes" visible="false" default="1" />
</category> </category>
<category id="backup_path" label="30048"> <category id="backup_path" label="30048">
<setting id="remote_selection" type="enum" lvalues="30018|30019|30027|30098" default="0" label="30025"/> <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="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_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="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_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/authorize_cloud.py,type=google_drive)" visible="eq(-8,3)"/> <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/remove_auth.py)" visible="gt(-9,1)"/> <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>
<category id="selection" label="30012"> <category id="selection" label="30012">
<setting id="backup_addons" type="bool" label="30030" default="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" /> <setting id="backup_addon_data" type="bool" label="30031" default="false" visible="eq(-1,0)"/>
<setting id="backup_database" type="bool" label="30032" default="true" /> <setting id="backup_config" type="bool" label="30035" default="true" visible="eq(-2,0)"/>
<setting id="backup_playlists" type="bool" label="30033" default="true" /> <setting id="backup_database" type="bool" label="30032" default="true" visible="eq(-3,0)"/>
<setting id="backup_profiles" type="bool" label="30080" default="false" /> <setting id="backup_game_saves" type="bool" label="30133" default="false" visible="eq(-4,0)" />
<setting id="backup_thumbnails" type="bool" label="30034" default="true" /> <setting id="backup_playlists" type="bool" label="30033" default="true" visible="eq(-5,0)"/>
<setting id="backup_config" type="bool" label="30035" default="true" /> <setting id="backup_profiles" type="bool" label="30080" default="false" visible="eq(-6,0)"/>
<setting id="custom_dir_1_enable" type="bool" label="30036" default="false" /> <setting id="backup_thumbnails" type="bool" label="30034" default="true" visible="eq(-7,0)"/>
<setting id="backup_custom_dir_1" type="folder" label="30018" default="" visible="eq(-1,true)"/> <setting id="backup_addons" type="bool" label="30030" default="true" visible="eq(-8,0)" />
<setting id="custom_dir_2_enable" type="bool" label="30037" default="false" /> <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="backup_custom_dir_2" type="folder" label="30018" default="" visible="eq(-1,true)"/> <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>
<category id="scheduling" label="30013"> <category id="scheduling" label="30013">
<setting id="enable_scheduler" type="bool" label="30060" default="false" /> <setting id="enable_scheduler" type="bool" label="30060" default="false" />

View File

@@ -1,12 +1,14 @@
import xbmc import xbmc
import xbmcvfs import xbmcvfs
import xbmcgui import xbmcgui
import datetime from datetime import datetime
import time import time
import resources.lib.utils as utils import resources.lib.utils as utils
from resources.lib.croniter import croniter from resources.lib.croniter import croniter
from resources.lib.backup import XbmcBackup from resources.lib.backup import XbmcBackup
UPGRADE_INT = 2 #to keep track of any upgrade notifications
class BackupScheduler: class BackupScheduler:
monitor = None monitor = None
enabled = "false" enabled = "false"
@@ -55,6 +57,11 @@ class BackupScheduler:
def start(self): 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 #check if a backup should be resumed
resumeRestore = self._resumeCheck() resumeRestore = self._resumeCheck()
@@ -63,7 +70,7 @@ class BackupScheduler:
restore.selectRestore(self.restore_point) restore.selectRestore(self.restore_point)
#skip the advanced settings check #skip the advanced settings check
restore.skipAdvanced() restore.skipAdvanced()
restore.run(XbmcBackup.Restore) restore.restore()
while(not self.monitor.abortRequested()): while(not self.monitor.abortRequested()):
@@ -98,9 +105,9 @@ class BackupScheduler:
if(backup.remoteConfigured()): if(backup.remoteConfigured()):
if(int(utils.getSetting('progress_mode')) in [0,1]): if(int(utils.getSetting('progress_mode')) in [0,1]):
backup.run(XbmcBackup.Backup,True) backup.backup(True)
else: else:
backup.run(XbmcBackup.Backup,False) backup.backup(False)
#check if this is a "one-off" #check if this is a "one-off"
if(int(utils.getSetting("schedule_interval")) == 0): if(int(utils.getSetting("schedule_interval")) == 0):
@@ -116,12 +123,12 @@ class BackupScheduler:
#find the cron expression and get the next run time #find the cron expression and get the next run time
cron_exp = self.parseSchedule() 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) new_run_time = cron_ob.get_next(float)
if(new_run_time != self.next_run): if(new_run_time != self.next_run):
self.next_run = new_run_time 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 #write the next time to a file
fh = xbmcvfs.File(self.next_run_path, 'w') fh = xbmcvfs.File(self.next_run_path, 'w')
@@ -130,7 +137,7 @@ class BackupScheduler:
#only show when not in silent mode #only show when not in silent mode
if(progress_mode != 2): 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): def settingsChanged(self):
current_enabled = utils.getSetting("enable_scheduler") current_enabled = utils.getSetting("enable_scheduler")