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
|
# Backup Addon
|
||||||
|
|
||||||
|
__Kodi Version Compatibility:__ Kodi 17.x (Krypton) and greater
|
||||||
|
|
||||||
## About
|
## 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.
|
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)
|
* [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"?>
|
<?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.0~beta3" 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,7 +89,11 @@
|
|||||||
<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.1.4
|
||||||
- added file chunk support for dropbox uploads
|
- 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 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
@ -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"
|
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.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"
|
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"
|
||||||
|
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 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 = ""
|
||||||
|
@ -4,10 +4,12 @@ import xbmcvfs
|
|||||||
import utils as utils
|
import utils as utils
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
|
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
|
||||||
from resources.lib.extractor import ZipExtractor
|
from resources.lib.extractor import ZipExtractor
|
||||||
|
from __builtin__ import file
|
||||||
|
|
||||||
def folderSort(aKey):
|
def folderSort(aKey):
|
||||||
result = aKey[0]
|
result = aKey[0]
|
||||||
@ -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 .....")
|
||||||
|
|
||||||
@ -454,18 +329,67 @@ class XbmcBackup:
|
|||||||
#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)
|
||||||
@ -489,21 +413,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 +446,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 +466,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 +495,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 +508,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 +527,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 + "/")):
|
||||||
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 + "/" + 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)
|
||||||
|
|
||||||
@ -627,6 +584,12 @@ class FileManager:
|
|||||||
filePath = xbmc.translatePath(directory + "/" + aFile)
|
filePath = xbmc.translatePath(directory + "/" + 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 +606,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):
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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):
|
||||||
|
@ -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 = ''
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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="0" />
|
||||||
</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" />
|
||||||
|
21
scheduler.py
@ -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 = 1 #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")
|
||||||
|