70 Commits

Author SHA1 Message Date
Rob Weber
b7587c6170 version bump 2021-03-15 13:39:07 -05:00
robweber
79cddb422c part of #186 2021-03-14 21:18:28 -05:00
robweber
8415ec12ba updated changelog.md 2021-03-07 14:30:31 -06:00
robweber
a284451640 part of #186 2021-03-07 14:30:17 -06:00
robweber
dc8d334352 fix addon.xml version 2021-03-06 19:20:54 -06:00
robweber
c17a185639 version bump 2021-03-06 19:20:31 -06:00
robweber
aff124af1f more of #183 2021-03-06 19:11:26 -06:00
robweber
350d81caf4 updated changelog.md 2021-02-21 21:27:04 -06:00
robweber
46d7d22523 part of #183 2021-02-21 21:25:56 -06:00
Rob Weber
11c644cb15 added help strings 2021-01-20 14:35:52 -06:00
Rob Weber
941b593751 updated change log 2021-01-20 14:27:34 -06:00
Rob Weber
e622a0455f fixes #83 2021-01-20 14:25:48 -06:00
Rob Weber
9c1ecc254f path to this folder was incorrect 2021-01-18 13:48:17 -06:00
Rob Weber
44fdf7a20a rmdir function only accepts one arg now 2021-01-18 13:10:50 -06:00
Rob Weber
ec214c074f simplified path expansions for special://temp zip file location 2021-01-18 09:01:19 -06:00
Rob
02d852a7e9 Matrix Settings (#179)
* added settings levels
2021-01-17 14:54:10 -06:00
Rob Weber
d7e4946d9a fix travis link 2020-12-23 15:52:05 -06:00
Rob Weber
6fdaa4f253 fix syntax error 2020-12-23 15:51:38 -06:00
Rob Weber
5f1f9fef38 version bump 2020-12-23 15:44:32 -06:00
Rob Weber
4098cd18cb updated changelog.md 2020-12-18 09:32:19 -06:00
Rob Weber
8119a09449 fix oauth import 2020-12-18 09:31:52 -06:00
Rob Weber
a0ccd85d9e part of #174, fixes ok() method def change 2020-12-18 09:28:17 -06:00
Rob
71c8d9ae54 ui settings restore upgrade
* added ability to export/save settings as json using GetSettings

* added generic copyFile method instead of duplicating

* copy and load settings file after file restore (right now only reads)

* set settings values from backup when differ than current

* store settings as part of validation file

* prompt for settings restore or set always via toggle

* unused import

* added new strings for settings restore

* updated changelog

* fix pep8 syntax

* swap setting to always prompt instead of always restore (invert)
2020-12-03 14:08:25 -06:00
Rob Weber
b470412b4f removed unused imports 2020-11-18 14:28:09 -06:00
Rob Weber
7a5886cd26 beta version, updated news 2020-11-18 14:10:22 -06:00
Rob Weber
f20887b6e7 update travis CI badge 2020-11-18 14:09:22 -06:00
Rob Weber
a0eb28a5f6 convert xbmc.translatePath to xbmcvfs.translatePath 2020-11-18 14:07:06 -06:00
Rob Weber
a198c9448a update future module 2020-11-17 19:56:15 -06:00
Rob Weber
fa3a30eb55 added flake8 pollyfill dep 2020-11-17 12:55:05 -06:00
Rob Weber
201d04afeb updated version number 2020-11-17 09:54:14 -06:00
Rob Weber
2dabb23c2d Merge branch 'matrix'
# Conflicts:
#	README.md
2020-11-17 09:52:39 -06:00
Rob Weber
2f19ec2b75 added download stats to README.md 2020-06-18 15:37:12 -05:00
Rob Weber
db215873cf updated badge path 2020-06-17 13:51:49 -05:00
Rob Weber
bd963719d4 updated badge path 2020-06-17 13:51:00 -05:00
Rob Weber
18b7f338c7 added attributes file to export-ignore 2020-06-16 15:07:16 -05:00
Rob Weber
92a9245bdc added export ignore items 2020-06-16 15:02:21 -05:00
Rob Weber
568c3758a4 add export-ignore files 2020-06-16 14:54:15 -05:00
robweber
af999f7d04 wrong rep 2020-06-15 20:56:11 -05:00
robweber
1264ab86b2 space 2020-06-15 20:48:15 -05:00
robweber
90c458d4fc updated changelog.md 2020-06-15 20:46:39 -05:00
robweber
0e6f5acfb5 added deploy script using kodi-addon-submitter 2020-06-15 20:44:41 -05:00
Wuff
c9dd381037 Version 1.6.3 - fix validatePath error (issue 166) (#167) 2020-05-20 13:01:43 -05:00
Rob Weber
55b2ac83d4 version bump 2020-04-09 14:37:09 -05:00
Rob Weber
0d14dd17c6 replace png path with jpg #165 2020-04-09 13:53:29 -05:00
Rob Weber
9fa354b467 replace PNG screenshots with JPG #165 2020-04-09 13:50:34 -05:00
Oldřich Jedlička
006485b19e Remove upper-case PNGs (#164)
Signed-off-by: Oldřich Jedlička <oldium.pro@gmail.com>
2020-02-11 15:23:52 -06:00
Rob Weber
3c2f512ecf update changelog 2019-12-31 11:03:18 -06:00
Rob Weber
190b4fd86f one more context lib 2019-12-31 11:03:11 -06:00
Rob Weber
9ecf706d63 use contextlib 2019-12-31 10:49:16 -06:00
Rob Weber
05c53b7ed8 localize advanced editor strings 2019-12-31 10:41:15 -06:00
Rob Weber
8bc73f2832 don't need this file 2019-12-31 10:33:13 -06:00
Rob Weber
edc4a7b20f updated to release version v1.6.1 2019-12-30 10:21:01 -06:00
Rob Weber
90b4aeeebe pep8 fixes 2019-12-30 10:17:58 -06:00
Rob Weber
7ce9123e1f updated changelog 2019-12-30 10:14:35 -06:00
Rob Weber
8bfef6692f Merge branch 'master' into matrix 2019-12-30 10:13:23 -06:00
Rob Weber
e63560f0c4 added a clean path function and applied it to rotate backups 2019-12-30 10:09:01 -06:00
Wuff
51f2ef3973 Fix deleting old backups on remote drives + log error (#163) 2019-12-30 09:59:03 -06:00
Rob Weber
04bac77690 moved scheduler to resources/lib and created non-complex entry point 2019-12-17 15:02:07 -06:00
Rob Weber
b1f6d36d73 updated news and version (beta2) 2019-12-10 15:24:56 -06:00
Rob
5d398836ba Show File Transfer Size (#160)
adds file transfer size to progress bar - closes #157
2019-12-10 15:16:54 -06:00
Rob Weber
23a14d67c4 Merge branch '1.6.0_fixes' into matrix 2019-12-06 13:56:06 -06:00
Rob Weber
47fcb119f3 pull build from matrix branch 2019-12-04 11:53:51 -06:00
Rob Weber
f9f49e3fe6 Merge branch 'master' into matrix 2019-12-04 11:53:12 -06:00
Rob Weber
7c23c17e33 Merge branch '1.6.0_fixes' 2019-12-04 11:49:35 -06:00
Rob Weber
8d66fa6a9f part of #159 - this will get rid of the most significant logging and keep the essentials 2019-11-27 14:24:35 -06:00
Rob Weber
5ee610a586 update getSetting calls to get ints and bools where needed 2019-11-27 14:19:41 -06:00
Rob Weber
8c4465f552 add methods for getting bools and int values directly 2019-11-27 14:19:25 -06:00
Rob Weber
3849a902ea working 16.1 beta 2019-11-27 14:05:08 -06:00
Rob Weber
4492ab593e update news 2019-11-27 13:14:42 -06:00
Rob Weber
0cc0684263 Use the dropbox module instead of bundling code - module still needs to be updated in Kodi repo 2019-11-26 12:58:35 -06:00
35 changed files with 1037 additions and 479 deletions

7
.gitattributes vendored Normal file
View File

@@ -0,0 +1,7 @@
.github/ export-ignore
.settings/ export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.travis.yml export-ignore
README.md export-ignore
changelog.md export-ignore

View File

@@ -3,7 +3,7 @@ language: python
python: 3.7 python: 3.7
install: install:
- pip install flake8 kodi-addon-checker - pip install flake8 flake8_polyfill kodi-addon-checker git+https://github.com/romanvm/kodi-addon-submitter.git
before_script: before_script:
- git config core.quotepath false - git config core.quotepath false
@@ -11,4 +11,10 @@ before_script:
# command to run our tests # command to run our tests
script: script:
- flake8 ./ --statistics --show-source --builtins=sys --ignore=E501,E722 --exclude=croniter.py # check python structure against flake8 tests, ignore long lines - flake8 ./ --statistics --show-source --builtins=sys --ignore=E501,E722 --exclude=croniter.py # check python structure against flake8 tests, ignore long lines
- kodi-addon-checker --branch=matrix --allow-folder-id-mismatch - kodi-addon-checker --branch=matrix --allow-folder-id-mismatch
deploy:
- provider: script
script: submit-addon -r repo-scripts -b matrix --push-branch script.xbmcbackup
on:
tags: true

View File

@@ -1,5 +1,5 @@
# Backup Addon # Backup Addon
![Kodi Version](https://img.shields.io/endpoint?url=https%3A%2F%2Fweberjr.com%2Fkodi-shield%2Frobweber%2Fxbmcbackup%2Fmaster%2Ftrue%2Ftrue) [![Build Status](https://img.shields.io/travis/robweber/xbmcbackup/master)](https://travis-ci.org/robweber/xbmcbackup) [![License](https://img.shields.io/github/license/robweber/xbmcbackup)](https://github.com/robweber/xbmcbackup/blob/master/LICENSE.txt) [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/) ![Kodi Version](https://img.shields.io/endpoint?url=https%3A%2F%2Fweberjr.com%2Fkodi-shield%2Fversion%2Frobweber%2Fxbmcbackup%2Fmatrix%2Ftrue%2Ftrue) ![Total Downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fweberjr.com%2Fkodi-shield%2Fdownloads%2Fmatrix%2Fscript.xbmcbackup%2F1.6.4) [![Build Status](https://img.shields.io/travis/com/robweber/xbmcbackup/matrix)](https://travis-ci.com/robweber/xbmcbackup) [![License](https://img.shields.io/github/license/robweber/xbmcbackup)](https://github.com/robweber/xbmcbackup/blob/master/LICENSE.txt) [![PEP8](https://img.shields.io/badge/code%20style-pep8-orange.svg)](https://www.python.org/dev/peps/pep-0008/)
## About ## About

View File

@@ -1,16 +1,16 @@
<?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.6.0" provider-name="robweber"> name="Backup" version="1.6.6" provider-name="robweber">
<requires> <requires>
<import addon="xbmc.python" version="3.0.0"/> <import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.dateutil" version="2.8.0" /> <import addon="script.module.dateutil" version="2.8.0" />
<import addon="script.module.future" version="0.16.0.4"/> <import addon="script.module.future" version="0.18.2+matrix.1" />
<import addon="script.module.dropbox" version="9.4.0"/> <import addon="script.module.dropbox" version="9.4.0" />
</requires> </requires>
<extension point="xbmc.python.script" library="default.py"> <extension point="xbmc.python.script" library="default.py">
<provides>executable</provides> <provides>executable</provides>
</extension> </extension>
<extension point="xbmc.service" library="scheduler.py" /> <extension point="xbmc.service" library="service.py" />
<extension point="xbmc.addon.metadata"> <extension point="xbmc.addon.metadata">
<summary lang="ar_SA">إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع</summary> <summary lang="ar_SA">إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع</summary>
<summary lang="be_BY">Backup and restore your Kodi database and configuration files in the event of a crash or file corruption.</summary> <summary lang="be_BY">Backup and restore your Kodi database and configuration files in the event of a crash or file corruption.</summary>
@@ -84,16 +84,13 @@
<source>https://github.com/robweber/xbmcbackup</source> <source>https://github.com/robweber/xbmcbackup</source>
<assets> <assets>
<icon>resources/images/icon.png</icon> <icon>resources/images/icon.png</icon>
<screenshot>resources/images/screenshot1.png</screenshot> <screenshot>resources/images/screenshot1.jpg</screenshot>
<screenshot>resources/images/screenshot2.png</screenshot> <screenshot>resources/images/screenshot2.jpg</screenshot>
<screenshot>resources/images/screenshot3.png</screenshot> <screenshot>resources/images/screenshot3.jpg</screenshot>
<screenshot>resources/images/screenshot4.png</screenshot> <screenshot>resources/images/screenshot4.jpg</screenshot>
</assets> </assets>
<news>Version 1.6.0 <news>Version 1.6.6
- Backups/Restores now use the concept of a Set to define groups of related files. Restores can restore one set or all sets within a backup archive (no more all or nothing restores) - fixed issue with backup rotations not working properly
- Added a new Advanced Editor script for more dynamic included/excluded directories based on a JSON formatted file </news>
- Fixed guisettings restores
- Removed GoogleDrive support - Python 3 compatibility was an issue
</news>
</extension> </extension>
</addon> </addon>

View File

@@ -4,6 +4,82 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## [Version 1.6.6](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.5...robweber:matrix-1.6.6)
### Fixed
- error when typing the remote path, ```listBackups()``` function was not working if final slash not included in typed directory path name.
- added ```force=True``` flag to the ```rmdir()``` function. Fixes issue with directories being removed when not empty
## [Version 1.6.5](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.4...robweber:matrix-1.6.5) - 2021-03-06
### Added
- added Expert setting to change location of zip file temp location as it's being built or extracted
### Changed
- updated ```settings.xml``` to match new [Kodi settings syntax](https://kodi.wiki/view/Add-on_settings_conversion), including visibility levels
### Fixed
- when restoring from a zip file the command to delete the extracted directory was incorrect
- ```Dialog().yesno()``` no longer takes line1 arg, changed to message
## [Version 1.6.4](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.3...robweber:matrix-1.6.4) - 2020-12-23
### Added
- merged duplicate copy code into ```_copyFile``` method
- added method to backup/restore Kodi settings via the GetSettings/SetSettingValue JSON methods in the validation file
- added setting to always restore settings or prompt at the time of backup
### Changed
- updated script.module.future version to current
- swapped xbmc.translatePath for xbmcvfs.translatePath, deprecated
### Fixed
- fixed calls to ```xbmcgui.Dialog().ok()```, method definition changed to only allow one message arg with Kodi 19
- fixed import of dropbox Oauth package in authorizer flow
### Removed
- removed old xml GuiSettings parsing for settings restore
## [Version 1.6.3](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.2...robweber:matrix-1.6.3) - 2020-06-15
### Changed
- fixed validatePath error (issue #166) thanks (thanks @AnonTester)
## [Version 1.6.2](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.1...robweber:matrix-1.6.2) - 2019-04-09
### Changed
- changed PNG screenshots to JPG (per [#165](https://github.com/robweber/xbmcbackup/issues/165))
## [Version 1.6.1](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.0...robweber:matrix-1.6.1) - 2019-12-30
### Added
- added method to get size of a file from the VFS
- added total transfer size information to progress bar with appropriate precision (KB, MB, etc)
- show file size of zip files in the restore selection dialog
- added getSettingInt and getSettingBool to utils.py class
- added verbose logging setting and tied it to logging related to file paths added/written, this will significantly reduce the debug log size (thanks CastagnaIT)
- localize advanced editor strings instead of hard coding English
### Changed
- display every file transfered in progress bar, not just directory
- base progress bar percent on transfer size, not total files
- changed getSettings where needed to getSettingBool and getSettingInt
- use service.py to start scheduler, moving scheduler to resources/lib/scheduler.py Kodi doesn't cache files in the root directory
- fixed issues with rotating backups where trailing slash was missing (thanks @AnonTester)
- read/write files using contextlib
## [Version 1.6.0](https://github.com/robweber/xbmcbackup/compare/krypton-1.5.2...robweber:matrix-1.6.0) - 2019-11-26 ## [Version 1.6.0](https://github.com/robweber/xbmcbackup/compare/krypton-1.5.2...robweber:matrix-1.6.0) - 2019-11-26
### Added ### Added
@@ -16,7 +92,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- addon.xml updated to use Leia specific syntax and library imports - addon.xml updated to use Leia specific syntax and library imports
- removed specific encode() calls per Python2/3 compatibility - removed specific encode() calls per Python2/3 compatibility
- call isdigit() method on the string directly instead of str.isdigit() (results in unicode error) - call isdigit() method on the string directly instead of str.isdigit() (results in unicode error)
- added flake8 testing to travis-ci - added flake8 testing to travis-ci
- updated code to make python3 compatible - updated code to make python3 compatible
- updated code for pep9 styling - updated code for pep9 styling
- use setArt() to set ListItem icons as the icon= constructor is deprecated - use setArt() to set ListItem icons as the icon= constructor is deprecated
@@ -34,16 +110,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Updated Changelog format to the one suggested by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Updated Changelog format to the one suggested by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Added script.module.dropbox import as a dependency for Dropbox filesystem - Added script.module.dropbox import as a dependency for Dropbox filesystem
### Changed ### Changed
- Fixed issue getting xbmcbackup.val file from non-zipped remote directories. Was being copied as though it was a local file so it was failing. - Fixed issue getting xbmcbackup.val file from non-zipped remote directories. Was being copied as though it was a local file so it was failing.
- Use linux path separator (/) all the time, Kodi will interpret this correctly on windows. Was causing issues with remote file systems since os.path.sep - Use linux path separator (/) all the time, Kodi will interpret this correctly on windows. Was causing issues with remote file systems since os.path.sep
- Fixed minor python code style changes based on kodi-addon-checker output - Fixed minor python code style changes based on kodi-addon-checker output
### Removed ### Removed
- files releated to dropbox library, using script.module.dropbox import now - files releated to dropbox library, using script.module.dropbox import now
## Version 1.5.1 - 2019-09-10 ## Version 1.5.1 - 2019-09-10
@@ -53,7 +129,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## Version 1.5.0 - 2019-08-26 ## Version 1.5.0 - 2019-08-26
### Added ### Added
- Added new Advanced file editor and file selection based on a .json - Added new Advanced file editor and file selection based on a .json
### Removed ### Removed
- File backups and restores will not work with old version - breaking change with previous versions PR117 - File backups and restores will not work with old version - breaking change with previous versions PR117
@@ -63,7 +139,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Added ### Added
- added file chunk support for Dropbox uploads - added file chunk support for Dropbox uploads
- added scheduler delay to assist with time sync (rpi mostly), will delay startup by 2 min - added scheduler delay to assist with time sync (rpi mostly), will delay startup by 2 min
### Changed ### Changed
- fixed settings duplicate ids, thanks aster-anto - fixed settings duplicate ids, thanks aster-anto
@@ -75,7 +151,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## Version 1.1.1 ## Version 1.1.1
### Added ### Added
- added ability to "catchup" on missed scheduled backup - added ability to "catchup" on missed scheduled backup
### Changed ### Changed
- fixed error on authorizers (missing secret/key) - fixed error on authorizers (missing secret/key)
@@ -86,10 +162,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Added ### Added
- added tinyurl generation for oauth urls - added tinyurl generation for oauth urls
### Changed ### Changed
- moved authorize to settings area for cloud storage - moved authorize to settings area for cloud storage
## Version 1.0.9 ## Version 1.0.9
### Changed ### Changed
@@ -110,7 +186,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Added ### Added
- added progress for zip extraction - hopefully helps with extract errors - added progress for zip extraction - hopefully helps with extract errors
### Changed ### Changed
- fix for custom directories not working recursively - fix for custom directories not working recursively
@@ -250,7 +326,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## Version 0.3.9 ## Version 0.3.9
- added "just once" scheduler for one-off type backups - added "just once" scheduler for one-off type backups
- show notification on scheduler - show notification on scheduler
- update updated language files from Transifex - update updated language files from Transifex

View File

@@ -38,7 +38,7 @@ if(mode == -1):
options = [utils.getString(30016), utils.getString(30017), utils.getString(30099)] options = [utils.getString(30016), utils.getString(30017), utils.getString(30099)]
# find out if we're using the advanced editor # find out if we're using the advanced editor
if(int(utils.getSetting('backup_selection_type')) == 1): if(utils.getSettingInt('backup_selection_type') == 1):
options.append(utils.getString(30125)) 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
@@ -52,7 +52,7 @@ 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): elif(mode == 3 and utils.getSettingInt('backup_selection_type') == 1):
# open the advanced editor # open the advanced editor
xbmc.executebuiltin('RunScript(special://home/addons/script.xbmcbackup/launcher.py, action=advanced_editor)') xbmc.executebuiltin('RunScript(special://home/addons/script.xbmcbackup/launcher.py, action=advanced_editor)')
elif(backup.remoteConfigured()): elif(backup.remoteConfigured()):

View File

@@ -1,6 +1,5 @@
# launcher for various helpful functions found in the settings.xml area # launcher for various helpful functions found in the settings.xml area
import sys import sys
import xbmc
import xbmcgui import xbmcgui
import xbmcvfs import xbmcvfs
import resources.lib.utils as utils import resources.lib.utils as utils
@@ -14,9 +13,9 @@ def authorize_cloud(cloudProvider):
authorizer = DropboxAuthorizer() authorizer = DropboxAuthorizer()
if(authorizer.authorize()): if(authorizer.authorize()):
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30027) + ' ' + utils.getString(30106)) xbmcgui.Dialog().ok(utils.getString(30010), '%s %s' % (utils.getString(30027), utils.getString(30106)))
else: else:
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30107) + ' ' + utils.getString(30027)) xbmcgui.Dialog().ok(utils.getString(30010), '%s %s' % (utils.getString(30107), utils.getString(30027)))
def remove_auth(): def remove_auth():
@@ -25,8 +24,8 @@ def remove_auth():
if(shouldDelete): if(shouldDelete):
# delete any of the known token file types # delete any of the known token file types
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "tokens.txt")) # dropbox xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt")) # dropbox
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "google_drive.dat")) # google drive xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "google_drive.dat")) # google drive
def get_params(): def get_params():

View File

@@ -1,2 +0,0 @@
*.pyo

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -173,8 +173,8 @@ msgid "Would you like to continue?"
msgstr "Would you like to continue?" msgstr "Would you like to continue?"
msgctxt "#30045" msgctxt "#30045"
msgid "Error: Remote path doesn't exist" msgid "Error: Remote or zip file path doesn't exist"
msgstr "Error: Remote path doesn't exist" msgstr "Error: Remote or zip file path doesn't exist"
msgctxt "#30046" msgctxt "#30046"
msgid "Starting" msgid "Starting"
@@ -555,4 +555,72 @@ msgstr ""
msgctxt "#30141" msgctxt "#30141"
msgid "This will erase any current Advanced Editor settings" msgid "This will erase any current Advanced Editor settings"
msgstr "" msgstr ""
msgctxt "#30142"
msgid "Enable Verbose Logging"
msgstr ""
msgctxt "#30143"
msgid "Exclude a specific folder from this backup set"
msgstr ""
msgctxt "#30144"
msgid "Include a specific folder to this backup set"
msgstr ""
msgctxt "#30145"
msgid "Type"
msgstr ""
msgctxt "#30146"
msgid "Include Sub Folders"
msgstr ""
msgctxt "#30147"
msgid "Toggle Sub Folders"
msgstr ""
msgctxt "#30148"
msgid "Ask before restoring Kodi UI settings"
msgstr ""
msgctxt "#30149"
msgid "Restore Kodi UI Settings"
msgstr ""
msgctxt "#30150"
msgid "Restore saved Kodi system settings from backup?"
msgstr ""
msgctxt "#30151"
msgid "Enable Verbose Logging"
msgstr ""
msgctxt "#30152"
msgid "Set Zip File Location"
msgstr ""
msgctxt "#30153"
msgid "Full path to where the zip file will be staged during backup or restore - must be local to this device"
msgstr ""
msgctxt "#30154"
msgid "Always prompt if Kodi settings should be restored - no by default"
msgstr ""
msgctxt "#30155"
msgid "Adds additional information to the log file"
msgstr ""
msgctxt "#30156"
msgid "Must save key/secret first, then return to settings to authorize"
msgstr ""
msgctxt "#30157"
msgid "Simple uses pre-defined folder locations, use Advanced Editor to define custom paths"
msgstr ""
msgctxt "#30158"
msgid "Run backup on daily, weekly, monthly, or custom schedule"
msgstr ""

View File

@@ -1,5 +1,4 @@
import json import json
import xbmc
import xbmcgui import xbmcgui
import xbmcvfs import xbmcvfs
import os.path import os.path
@@ -7,7 +6,7 @@ from . import utils as utils
class BackupSetManager: class BackupSetManager:
jsonFile = xbmc.translatePath(utils.data_dir() + "custom_paths.json") jsonFile = xbmcvfs.translatePath(utils.data_dir() + "custom_paths.json")
paths = None paths = None
def __init__(self): def __init__(self):
@@ -95,7 +94,7 @@ class AdvancedBackupEditor:
if(name is not None): if(name is not None):
# give a choice to start in home or enter a root path # 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)) enterHome = self.dialog.yesno(utils.getString(30111), message=utils.getString(30112) + " - " + utils.getString(30114) + "\n" + utils.getString(30113) + " - " + utils.getString(30115), nolabel=utils.getString(30112), yeslabel=utils.getString(30113))
rootFolder = 'special://home' rootFolder = 'special://home'
if(enterHome): if(enterHome):
@@ -106,7 +105,7 @@ class AdvancedBackupEditor:
rootFolder = rootFolder + '/' rootFolder = rootFolder + '/'
# check that this path even exists # check that this path even exists
if(not xbmcvfs.exists(xbmc.translatePath(rootFolder))): if(not xbmcvfs.exists(xbmcvfs.translatePath(rootFolder))):
self.dialog.ok(utils.getString(30117), utils.getString(30118), rootFolder) self.dialog.ok(utils.getString(30117), utils.getString(30118), rootFolder)
return None return None
else: else:
@@ -122,13 +121,13 @@ class AdvancedBackupEditor:
rootPath = backupSet['root'] rootPath = backupSet['root']
while(optionSelected != -1): 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))] options = [xbmcgui.ListItem(utils.getString(30120), utils.getString(30143)), xbmcgui.ListItem(utils.getString(30135), utils.getString(30144)), xbmcgui.ListItem(rootPath, utils.getString(30121))]
for aDir in backupSet['dirs']: for aDir in backupSet['dirs']:
if(aDir['type'] == 'exclude'): if(aDir['type'] == 'exclude'):
options.append(xbmcgui.ListItem(self._cleanPath(rootPath, aDir['path']), "%s: %s" % ("Type", utils.getString(30129)))) options.append(xbmcgui.ListItem(self._cleanPath(rootPath, aDir['path']), "%s: %s" % (utils.getString(30145), utils.getString(30129))))
elif(aDir['type'] == 'include'): 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'])))) options.append(xbmcgui.ListItem(self._cleanPath(rootPath, aDir['path']), "%s: %s | %s: %s" % (utils.getString(30145), utils.getString(30134), utils.getString(30146), str(aDir['recurse']))))
optionSelected = self.dialog.select(utils.getString(30122) + ' ' + name, options, useDetails=True) optionSelected = self.dialog.select(utils.getString(30122) + ' ' + name, options, useDetails=True)
@@ -157,12 +156,12 @@ class AdvancedBackupEditor:
cOptions = ['Delete'] cOptions = ['Delete']
if(backupSet['dirs'][optionSelected - 3]['type'] == 'include'): if(backupSet['dirs'][optionSelected - 3]['type'] == 'include'):
cOptions.append('Toggle Sub Folders') cOptions.append(utils.getString(30147))
contextOption = self.dialog.contextmenu(cOptions) contextOption = self.dialog.contextmenu(cOptions)
if(contextOption == 0): if(contextOption == 0):
if(self.dialog.yesno(heading=utils.getString(30123), line1=utils.getString(30128))): if(self.dialog.yesno(heading=utils.getString(30123), message=utils.getString(30128))):
# remove folder # remove folder
del backupSet['dirs'][optionSelected - 3] del backupSet['dirs'][optionSelected - 3]
elif(contextOption == 1 and backupSet['dirs'][optionSelected - 3]['type'] == 'include'): elif(contextOption == 1 and backupSet['dirs'][optionSelected - 3]['type'] == 'include'):
@@ -218,7 +217,7 @@ class AdvancedBackupEditor:
customPaths.updateSet(aSet['name'], updatedSet) customPaths.updateSet(aSet['name'], updatedSet)
elif(menuOption == 1): elif(menuOption == 1):
if(self.dialog.yesno(heading=utils.getString(30127), line1=utils.getString(30128))): if(self.dialog.yesno(heading=utils.getString(30127), message=utils.getString(30128))):
# delete this path - subtract one because of "add" item # delete this path - subtract one because of "add" item
customPaths.deleteSet(exitCondition - 1) customPaths.deleteSet(exitCondition - 1)
@@ -227,7 +226,7 @@ class AdvancedBackupEditor:
shouldContinue = self.dialog.yesno(utils.getString(30139), utils.getString(30140), utils.getString(30141)) shouldContinue = self.dialog.yesno(utils.getString(30139), utils.getString(30140), utils.getString(30141))
if(shouldContinue): if(shouldContinue):
source = xbmc.translatePath(os.path.join(utils.addon_dir(), 'resources', 'data', 'default_files.json')) source = xbmcvfs.translatePath(os.path.join(utils.addon_dir(), 'resources', 'data', 'default_files.json'))
dest = xbmc.translatePath(os.path.join(utils.data_dir(), 'custom_paths.json')) dest = xbmcvfs.translatePath(os.path.join(utils.data_dir(), 'custom_paths.json'))
xbmcvfs.copy(source, dest) xbmcvfs.copy(source, dest)

View File

@@ -1,4 +1,3 @@
import xbmc
import xbmcgui import xbmcgui
import xbmcvfs import xbmcvfs
import resources.lib.tinyurl as tinyurl import resources.lib.tinyurl as tinyurl
@@ -7,6 +6,7 @@ import resources.lib.utils as utils
# don't die on import error yet, these might not even get used # don't die on import error yet, these might not even get used
try: try:
from dropbox import dropbox from dropbox import dropbox
from dropbox import oauth
except ImportError: except ImportError:
pass pass
@@ -24,7 +24,7 @@ class DropboxAuthorizer:
if(self.APP_KEY == '' and self.APP_SECRET == ''): if(self.APP_KEY == '' and self.APP_SECRET == ''):
# we can't go any farther, need these for sure # we can't go any farther, need these for sure
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30027) + ' ' + utils.getString(30058), utils.getString(30059)) xbmcgui.Dialog().ok(utils.getString(30010), '%s %s\n%s' % (utils.getString(30027), utils.getString(30058), utils.getString(30059)))
result = False result = False
@@ -46,13 +46,13 @@ class DropboxAuthorizer:
self._deleteToken() self._deleteToken()
# copied flow from http://dropbox-sdk-python.readthedocs.io/en/latest/moduledoc.html#dropbox.oauth.DropboxOAuth2FlowNoRedirect # copied flow from http://dropbox-sdk-python.readthedocs.io/en/latest/moduledoc.html#dropbox.oauth.DropboxOAuth2FlowNoRedirect
flow = dropbox.oauth.DropboxOAuth2FlowNoRedirect(self.APP_KEY, self.APP_SECRET) flow = oauth.DropboxOAuth2FlowNoRedirect(self.APP_KEY, self.APP_SECRET)
url = flow.start() url = flow.start()
# print url in log # print url in log
utils.log("Authorize URL: " + url) utils.log("Authorize URL: " + url)
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30056), utils.getString(30057), tinyurl.shorten(url)) xbmcgui.Dialog().ok(utils.getString(30010), '%s\n%s\n%s' % (utils.getString(30056), utils.getString(30057), str(tinyurl.shorten(url), 'utf-8')))
# get the auth code # get the auth code
code = xbmcgui.Dialog().input(utils.getString(30027) + ' ' + utils.getString(30103)) code = xbmcgui.Dialog().input(utils.getString(30027) + ' ' + utils.getString(30103))
@@ -89,14 +89,14 @@ class DropboxAuthorizer:
def _setToken(self, token): def _setToken(self, token):
# write the token files # write the token files
token_file = open(xbmc.translatePath(utils.data_dir() + "tokens.txt"), 'w') token_file = open(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt"), 'w')
token_file.write(token) token_file.write(token)
token_file.close() token_file.close()
def _getToken(self): def _getToken(self):
# get token, if it exists # get token, if it exists
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "tokens.txt"))): if(xbmcvfs.exists(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt"))):
token_file = open(xbmc.translatePath(utils.data_dir() + "tokens.txt")) token_file = open(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt"))
token = token_file.read() token = token_file.read()
token_file.close() token_file.close()
@@ -105,5 +105,5 @@ class DropboxAuthorizer:
return "" return ""
def _deleteToken(self): def _deleteToken(self):
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "tokens.txt"))): if(xbmcvfs.exists(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt"))):
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "tokens.txt")) xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt"))

View File

@@ -4,6 +4,7 @@ import json
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcvfs import xbmcvfs
import os.path
from . import utils as utils from . import utils as utils
from datetime import datetime from datetime import datetime
from . vfs import XBMCFileSystem, DropboxFileSystem, ZipFileSystem from . vfs import XBMCFileSystem, DropboxFileSystem, ZipFileSystem
@@ -26,6 +27,8 @@ class XbmcBackup:
Backup = 0 Backup = 0
Restore = 1 Restore = 1
ZIP_TEMP_PATH = None
# list of dirs for the "simple" file selection # list of dirs for the "simple" file selection
simple_directory_list = ['addons', 'addon_data', 'database', 'game_saves', 'playlists', 'profiles', 'thumbnails', 'config'] simple_directory_list = ['addons', 'addon_data', 'database', 'game_saves', 'playlists', 'profiles', 'thumbnails', 'config']
@@ -39,34 +42,34 @@ class XbmcBackup:
# for the progress bar # for the progress bar
progressBar = None progressBar = None
filesLeft = 0 transferSize = 0
filesTotal = 1 transferLeft = 0
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
def __init__(self): def __init__(self):
self.xbmc_vfs = XBMCFileSystem(xbmc.translatePath('special://home')) self.xbmc_vfs = XBMCFileSystem(xbmcvfs.translatePath('special://home'))
self.ZIP_TEMP_PATH = xbmcvfs.translatePath(utils.getSetting('zip_temp_path'))
self.configureRemote() self.configureRemote()
utils.log(utils.getString(30046)) utils.log(utils.getString(30046))
def configureRemote(self): def configureRemote(self):
if(utils.getSetting('remote_selection') == '1'): if(utils.getSetting('remote_selection') == '1'):
self.remote_base_path = utils.getSetting('remote_path_2')
self.remote_vfs = XBMCFileSystem(utils.getSetting('remote_path_2')) self.remote_vfs = XBMCFileSystem(utils.getSetting('remote_path_2'))
utils.setSetting("remote_path", "") utils.setSetting("remote_path", "")
elif(utils.getSetting('remote_selection') == '0'): elif(utils.getSetting('remote_selection') == '0'):
self.remote_base_path = utils.getSetting('remote_path')
self.remote_vfs = XBMCFileSystem(utils.getSetting("remote_path")) self.remote_vfs = XBMCFileSystem(utils.getSetting("remote_path"))
elif(utils.getSetting('remote_selection') == '2'): elif(utils.getSetting('remote_selection') == '2'):
self.remote_base_path = "/"
self.remote_vfs = DropboxFileSystem("/") self.remote_vfs = DropboxFileSystem("/")
self.remote_base_path = self.remote_vfs.root_path
def remoteConfigured(self): def remoteConfigured(self):
result = True result = True
if(self.remote_base_path == ""): if(self.remote_base_path == "" or not xbmcvfs.exists(self.ZIP_TEMP_PATH)):
result = False result = False
return result return result
@@ -92,8 +95,8 @@ class XbmcBackup:
if(file_ext == 'zip' and len(folderName) == 12 and folderName.isdigit()): if(file_ext == 'zip' and len(folderName) == 12 and folderName.isdigit()):
# format the name according to regional settings # format the name according to regional settings and display the file size
folderName = self._dateFormat(folderName) folderName = "%s - %s" % (self._dateFormat(folderName), utils.diskString(self.remote_vfs.fileSize(self.remote_base_path + aFile)))
result.append((aFile, folderName)) result.append((aFile, folderName))
@@ -124,14 +127,14 @@ class XbmcBackup:
utils.log('File Selection Type: ' + str(utils.getSetting('backup_selection_type'))) utils.log('File Selection Type: ' + str(utils.getSetting('backup_selection_type')))
allFiles = [] allFiles = []
if(int(utils.getSetting('backup_selection_type')) == 0): if(utils.getSettingInt('backup_selection_type') == 0):
# read in a list of the directories to backup # read in a list of the directories to backup
selectedDirs = self._readBackupConfig(utils.addon_dir() + "/resources/data/default_files.json") selectedDirs = self._readBackupConfig(utils.addon_dir() + "/resources/data/default_files.json")
# simple mode - get file listings for all enabled directories # simple mode - get file listings for all enabled directories
for aDir in self.simple_directory_list: for aDir in self.simple_directory_list:
# if this dir enabled # if this dir enabled
if(utils.getSetting('backup_' + aDir) == 'true'): if(utils.getSettingBool('backup_' + aDir)):
# get a file listing and append it to the allfiles array # get a file listing and append it to the allfiles array
allFiles.append(self._addBackupDir(aDir, selectedDirs[aDir]['root'], selectedDirs[aDir]['dirs'])) allFiles.append(self._addBackupDir(aDir, selectedDirs[aDir]['root'], selectedDirs[aDir]['dirs']))
else: else:
@@ -154,7 +157,7 @@ class XbmcBackup:
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
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30089), utils.getString(30090), utils.getString(30044), autoclose=25000) shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30089), "%s\n%s" % (utils.getString(30090), utils.getString(30044)), autoclose=25000)
if(not shouldContinue): if(not shouldContinue):
return return
@@ -162,9 +165,9 @@ class XbmcBackup:
orig_base_path = self.remote_vfs.root_path orig_base_path = self.remote_vfs.root_path
# backup all the files # backup all the files
self.filesLeft = self.filesTotal self.transferLeft = self.transferSize
for fileGroup in allFiles: for fileGroup in allFiles:
self.xbmc_vfs.set_root(xbmc.translatePath(fileGroup['source'])) self.xbmc_vfs.set_root(xbmcvfs.translatePath(fileGroup['source']))
self.remote_vfs.set_root(fileGroup['dest'] + fileGroup['name']) self.remote_vfs.set_root(fileGroup['dest'] + fileGroup['name'])
filesCopied = self._copyFiles(fileGroup['files'], self.xbmc_vfs, self.remote_vfs) filesCopied = self._copyFiles(fileGroup['files'], self.xbmc_vfs, self.remote_vfs)
@@ -176,28 +179,30 @@ class XbmcBackup:
self.xbmc_vfs.set_root("special://home/") self.xbmc_vfs.set_root("special://home/")
self.remote_vfs.set_root(orig_base_path) self.remote_vfs.set_root(orig_base_path)
if(utils.getSetting("compress_backups") == 'true'): if(utils.getSettingBool("compress_backups")):
fileManager = FileManager(self.xbmc_vfs) 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 = os.path.join(self.ZIP_TEMP_PATH, self.remote_vfs.root_path[:-1] + ".zip")
self.remote_vfs.cleanup() self.remote_vfs.cleanup()
self.xbmc_vfs.rename(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"), xbmc.translatePath("special://temp/" + zip_name)) self.xbmc_vfs.rename(os.path.join(self.ZIP_TEMP_PATH, "xbmc_backup_temp.zip"), zip_name)
fileManager.addFile(xbmc.translatePath("special://temp/" + zip_name)) fileManager.addFile(zip_name)
# set root to data dir home
self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/"))
# set root to data dir home and reset remote
self.xbmc_vfs.set_root(self.ZIP_TEMP_PATH)
self.remote_vfs = self.saved_remote_vfs self.remote_vfs = self.saved_remote_vfs
self.progressBar.updateProgress(98, utils.getString(30088))
# update the amount to transfer
self.transferSize = fileManager.fileSize()
self.transferLeft = self.transferSize
fileCopied = self._copyFiles(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
shouldContinue = xbmcgui.Dialog().ok(utils.getString(30089), utils.getString(30090), utils.getString(30091)) shouldContinue = xbmcgui.Dialog().ok(utils.getString(30089), '%s\n%s' % (utils.getString(30090), utils.getString(30091)))
# delete the temp zip file # delete the temp zip file
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + zip_name)) self.xbmc_vfs.rmfile(zip_name)
# remove old backups # remove old backups
self._rotateBackups() self._rotateBackups()
@@ -217,25 +222,28 @@ class XbmcBackup:
utils.log("copying zip file: " + self.restore_point) utils.log("copying zip file: " + self.restore_point)
# set root to data dir home # set root to data dir home
self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/")) self.xbmc_vfs.set_root(self.ZIP_TEMP_PATH)
restore_path = os.path.join(self.ZIP_TEMP_PATH, self.restore_point)
if(not self.xbmc_vfs.exists(xbmc.translatePath("special://temp/" + self.restore_point))): if(not self.xbmc_vfs.exists(restore_path)):
# copy just this file from the remote vfs # copy just this file from the remote vfs
self.transferSize = self.remote_vfs.fileSize(self.remote_base_path + self.restore_point)
zipFile = [] zipFile = []
zipFile.append(self.remote_base_path + self.restore_point) zipFile.append({'file': self.remote_base_path + self.restore_point, 'size': self.transferSize})
# set transfer size
self.transferLeft = self.transferSize
self._copyFiles(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")
# extract the zip file # extract the zip file
zip_vfs = ZipFileSystem(xbmc.translatePath("special://temp/" + self.restore_point), 'r') zip_vfs = ZipFileSystem(restore_path, 'r')
extractor = ZipExtractor() extractor = ZipExtractor()
if(not extractor.extract(zip_vfs, xbmc.translatePath("special://temp/"), self.progressBar)): if(not extractor.extract(zip_vfs, self.ZIP_TEMP_PATH, self.progressBar)):
# we had a problem extracting the archive, delete everything # we had a problem extracting the archive, delete everything
zip_vfs.cleanup() zip_vfs.cleanup()
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point)) self.xbmc_vfs.rmfile(restore_path)
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30101)) xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30101))
return return
@@ -244,12 +252,12 @@ class XbmcBackup:
self.progressBar.updateProgress(0, utils.getString(30049) + "......") self.progressBar.updateProgress(0, utils.getString(30049) + "......")
# set the new remote vfs and fix xbmc path # set the new remote vfs and fix xbmc path
self.remote_vfs = XBMCFileSystem(xbmc.translatePath("special://temp/" + self.restore_point.split(".")[0] + "/")) self.remote_vfs = XBMCFileSystem(os.path.join(self.ZIP_TEMP_PATH, self.restore_point.split(".")[0]))
self.xbmc_vfs.set_root(xbmc.translatePath("special://home/")) self.xbmc_vfs.set_root(xbmcvfs.translatePath("special://home/"))
# for restores remote path must exist # for restores remote path must exist
if(not self.remote_vfs.exists(self.remote_vfs.root_path)): if(not self.remote_vfs.exists(self.remote_vfs.root_path)):
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30045), self.remote_vfs.root_path) xbmcgui.Dialog().ok(utils.getString(30010), '%s\n%s' % (utils.getString(30045), self.remote_vfs.root_path))
return return
valFile = self._checkValidationFile(self.remote_vfs.root_path) valFile = self._checkValidationFile(self.remote_vfs.root_path)
@@ -264,7 +272,7 @@ class XbmcBackup:
# 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 + "config/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), "%s\n%s" % (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
@@ -278,6 +286,12 @@ class XbmcBackup:
xbmcgui.Dialog().ok(utils.getString(30077), utils.getString(30078)) xbmcgui.Dialog().ok(utils.getString(30077), utils.getString(30078))
return return
# check if settings should be restored from this backup
restoreSettings = not utils.getSettingBool('always_prompt_restore_settings')
if(not restoreSettings and 'system_settings' in valFile):
# prompt the user to restore settings yes/no
restoreSettings = xbmcgui.Dialog().yesno(utils.getString(30149), utils.getString(30150))
# use a multiselect dialog to select sets to restore # use a multiselect dialog to select sets to restore
restoreSets = [n['name'] for n in valFile['directories']] restoreSets = [n['name'] for n in valFile['directories']]
@@ -288,39 +302,45 @@ class XbmcBackup:
selectedSets = [restoreSets.index(n) for n in selectedSets if n in restoreSets] # if set name not found just skip it selectedSets = [restoreSets.index(n) for n in selectedSets if n in restoreSets] # if set name not found just skip it
if(selectedSets is not None): if(selectedSets is not None):
# go through each of the directories in the backup and write them to the correct location # go through each of the directories in the backup and write them to the correct location
for index in selectedSets: for index in selectedSets:
# add this directory # add this directory
aDir = valFile['directories'][index] aDir = valFile['directories'][index]
self.xbmc_vfs.set_root(xbmc.translatePath(aDir['path'])) self.xbmc_vfs.set_root(xbmcvfs.translatePath(aDir['path']))
if(self.remote_vfs.exists(self.remote_vfs.root_path + aDir['name'] + '/')): if(self.remote_vfs.exists(self.remote_vfs.root_path + aDir['name'] + '/')):
# walk the directory # walk the directory
fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/') fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/')
self.filesTotal = self.filesTotal + fileManager.size() self.transferSize = self.transferSize + fileManager.fileSize()
allFiles.append({"source": self.remote_vfs.root_path + aDir['name'], "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 + aDir['name']) utils.log("error path not found: " + self.remote_vfs.root_path + aDir['name'])
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30045), self.remote_vfs.root_path + aDir['name']) xbmcgui.Dialog().ok(utils.getString(30010), '%s\n%s' % (utils.getString(30045), self.remote_vfs.root_path + aDir['name']))
# restore all the files # restore all the files
self.filesLeft = self.filesTotal self.transferLeft = self.transferSize
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._copyFiles(fileGroup['files'], self.remote_vfs, self.xbmc_vfs) self._copyFiles(fileGroup['files'], self.remote_vfs, self.xbmc_vfs)
# update the Kodi settings - if we can
if('system_settings' in valFile and restoreSettings):
self.progressBar.updateProgress(98, "Restoring Kodi settings")
gui_settings = GuiSettingsManager()
gui_settings.restore(valFile['system_settings'])
self.progressBar.updateProgress(99, "Clean up operations .....") self.progressBar.updateProgress(99, "Clean up operations .....")
if(self.restore_point.split('.')[-1] == 'zip'): if(self.restore_point.split('.')[-1] == 'zip'):
# delete the zip file and the extracted directory # delete the zip file and the extracted directory
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point)) self.xbmc_vfs.rmfile(os.path.join(self.ZIP_TEMP_PATH, self.restore_point))
self.xbmc_vfs.rmdir(self.remote_vfs.root_path) xbmc.sleep(1000)
self.xbmc_vfs.rmdir(self.remote_vfs.clean_path(os.path.join(self.ZIP_TEMP_PATH, self.restore_point.split(".")[0])))
# update the guisettings information (or what we can from it) xbmc.sleep(1000)
gui_settings = GuiSettingsManager()
gui_settings.run()
# call update addons to refresh everything # call update addons to refresh everything
xbmc.executebuiltin('UpdateLocalAddons') xbmc.executebuiltin('UpdateLocalAddons')
@@ -333,17 +353,18 @@ class XbmcBackup:
# append backup folder name # append backup folder name
progressBarTitle = utils.getString(30010) + " - " progressBarTitle = utils.getString(30010) + " - "
if(mode == self.Backup and self.remote_vfs.root_path != ''): if(mode == self.Backup and self.remote_vfs.root_path != ''):
if(utils.getSetting("compress_backups") == 'true'): if(utils.getSettingBool("compress_backups")):
# delete old temp file # delete old temp file
if(self.xbmc_vfs.exists(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))): zip_path = os.path.join(self.ZIP_TEMP_PATH, 'xbmc_backup_temp.zip')
if(not self.xbmc_vfs.rmfile(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))): if(self.xbmc_vfs.exists(zip_path)):
if(not self.xbmc_vfs.rmfile(zip_path)):
# we had some kind of error deleting the old file # we had some kind of error deleting the old file
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30096), utils.getString(30097)) xbmcgui.Dialog().ok(utils.getString(30010), '%s\n%s' % (utils.getString(30096), utils.getString(30097)))
return False return False
# save the remote file system and use the zip vfs # save the remote file system and use the zip vfs
self.saved_remote_vfs = self.remote_vfs self.saved_remote_vfs = self.remote_vfs
self.remote_vfs = ZipFileSystem(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"), "w") self.remote_vfs = ZipFileSystem(zip_path, "w")
self.remote_vfs.set_root(self.remote_vfs.root_path + time.strftime("%Y%m%d%H%M") + "/") 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) progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30016)
@@ -358,6 +379,7 @@ class XbmcBackup:
utils.log(utils.getString(30047) + ": " + self.xbmc_vfs.root_path) utils.log(utils.getString(30047) + ": " + self.xbmc_vfs.root_path)
utils.log(utils.getString(30048) + ": " + self.remote_vfs.root_path) utils.log(utils.getString(30048) + ": " + self.remote_vfs.root_path)
utils.log(utils.getString(30152) + ": " + utils.getSetting('zip_temp_path'))
# setup the progress bar # setup the progress bar
self.progressBar = BackupProgressBar(progressOverride) self.progressBar = BackupProgressBar(progressOverride)
@@ -379,7 +401,7 @@ class XbmcBackup:
result = True result = True
utils.log("Source: " + source.root_path) utils.log("Source: " + source.root_path)
utils.log("Desintation: " + dest.root_path) utils.log("Destination: " + dest.root_path)
# make sure the dest folder exists - can cause write errors if the full path doesn't exist # make sure the dest folder exists - can cause write errors if the full path doesn't exist
if(not dest.exists(dest.root_path)): if(not dest.exists(dest.root_path)):
@@ -387,21 +409,18 @@ class XbmcBackup:
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) if(utils.getSettingBool('verbose_logging')):
if(aFile.startswith("-")): utils.log('Writing file: ' + aFile['file'])
self._updateProgress(aFile[len(source.root_path) + 1:])
dest.mkdir(dest.root_path + aFile[len(source.root_path) + 1:])
else:
self._updateProgress()
wroteFile = True if(aFile['file'].startswith("-")):
destFile = dest.root_path + aFile[len(source.root_path):] self._updateProgress('%s remaining, writing %s' % (utils.diskString(self.transferLeft), os.path.basename(aFile['file'][len(source.root_path):]) + "/"))
if(isinstance(source, DropboxFileSystem)): dest.mkdir(dest.root_path + aFile['file'][len(source.root_path) + 1:])
# if copying from cloud storage we need the file handle, use get_file else:
wroteFile = source.get_file(aFile, destFile) self._updateProgress('%s remaining, writing %s' % (utils.diskString(self.transferLeft), os.path.basename(aFile['file'][len(source.root_path):])))
else: self.transferLeft = self.transferLeft - aFile['size']
# copy using normal method
wroteFile = dest.put(aFile, destFile) # copy the file
wroteFile = self._copyFile(source, dest, aFile['file'], dest.root_path + aFile['file'][len(source.root_path):])
# 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):
@@ -409,18 +428,31 @@ class XbmcBackup:
return result return result
def _copyFile(self, source, dest, sourceFile, destFile):
result = True
if(isinstance(source, DropboxFileSystem)):
# if copying from cloud storage we need the file handle, use get_file
result = source.get_file(sourceFile, destFile)
else:
# copy using normal method
result = dest.put(sourceFile, destFile)
return result
def _addBackupDir(self, folder_name, root_path, dirList): def _addBackupDir(self, folder_name, root_path, dirList):
utils.log('Backup set: ' + folder_name) utils.log('Backup set: ' + folder_name)
fileManager = FileManager(self.xbmc_vfs) fileManager = FileManager(self.xbmc_vfs)
self.xbmc_vfs.set_root(xbmc.translatePath(root_path)) self.xbmc_vfs.set_root(xbmcvfs.translatePath(root_path))
for aDir in dirList: for aDir in dirList:
fileManager.addDir(aDir) fileManager.addDir(aDir)
# walk all the root trees # walk all the root trees
fileManager.walk() fileManager.walk()
# update total files
self.filesTotal = self.filesTotal + fileManager.size() # update total size
self.transferSize = self.transferSize + fileManager.fileSize()
return {"name": folder_name, "source": root_path, "dest": self.remote_vfs.root_path, "files": fileManager.getFiles()} return {"name": folder_name, "source": root_path, "dest": self.remote_vfs.root_path, "files": fileManager.getFiles()}
@@ -434,11 +466,10 @@ class XbmcBackup:
return result return result
def _updateProgress(self, message=None): def _updateProgress(self, message=None):
self.filesLeft = self.filesLeft - 1 self.progressBar.updateProgress(int((float(self.transferSize - self.transferLeft) / float(self.transferSize)) * 100), message)
self.progressBar.updateProgress(int((float(self.filesTotal - self.filesLeft) / float(self.filesTotal)) * 100), message)
def _rotateBackups(self): def _rotateBackups(self):
total_backups = int(utils.getSetting('backup_rotation')) total_backups = utils.getSettingInt('backup_rotation')
if(total_backups > 0): if(total_backups > 0):
# get a list of valid backup folders # get a list of valid backup folders
@@ -447,7 +478,6 @@ class XbmcBackup:
if(len(dirs) > total_backups): if(len(dirs) > total_backups):
# remove backups to equal total wanted # remove backups to equal total wanted
remove_num = 0 remove_num = 0
self.filesTotal = self.filesTotal + remove_num + 1
# update the progress bar if it is available # update the progress bar if it is available
while(remove_num < (len(dirs) - total_backups) and not self.progressBar.checkCancel()): while(remove_num < (len(dirs) - total_backups) and not self.progressBar.checkCancel()):
@@ -456,37 +486,42 @@ class XbmcBackup:
if(dirs[remove_num][0].split('.')[-1] == 'zip'): if(dirs[remove_num][0].split('.')[-1] == 'zip'):
# this is a file, remove it that way # this is a file, remove it that way
self.remote_vfs.rmfile(self.remote_base_path + dirs[remove_num][0]) self.remote_vfs.rmfile(self.remote_vfs.clean_path(self.remote_base_path) + dirs[remove_num][0])
else: else:
self.remote_vfs.rmdir(self.remote_base_path + dirs[remove_num][0] + "/") self.remote_vfs.rmdir(self.remote_vfs.clean_path(self.remote_base_path) + dirs[remove_num][0] + "/")
remove_num = remove_num + 1 remove_num = remove_num + 1
def _createValidationFile(self, dirList): def _createValidationFile(self, dirList):
valInfo = {"name": "XBMC Backup Validation File", "xbmc_version": xbmc.getInfoLabel('System.BuildVersion'), "type": 0} valInfo = {"name": "XBMC Backup Validation File", "xbmc_version": xbmc.getInfoLabel('System.BuildVersion'), "type": 0, "system_settings": []}
valDirs = [] valDirs = []
# save list of file sets
for aDir in dirList: for aDir in dirList:
valDirs.append({"name": aDir['name'], "path": aDir['source']}) valDirs.append({"name": aDir['name'], "path": aDir['source']})
valInfo['directories'] = valDirs valInfo['directories'] = valDirs
vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"), 'w') # dump all current Kodi settings
gui_settings = GuiSettingsManager()
valInfo['system_settings'] = gui_settings.backup()
vFile = xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup.val"), 'w')
vFile.write(json.dumps(valInfo)) vFile.write(json.dumps(valInfo))
vFile.write("") vFile.write("")
vFile.close() vFile.close()
success = self.remote_vfs.put(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"), self.remote_vfs.root_path + "xbmcbackup.val") success = self._copyFile(self.xbmc_vfs, self.remote_vfs, xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup.val"), self.remote_vfs.root_path + "xbmcbackup.val")
# remove the validation file # remove the validation file
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val")) xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup.val"))
if(success): if(success):
# android requires a .nomedia file to not index the directory as media # android requires a .nomedia file to not index the directory as media
if(not xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + ".nomedia"))): if(not xbmcvfs.exists(xbmcvfs.translatePath(utils.data_dir() + ".nomedia"))):
nmFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + ".nomedia"), 'w') nmFile = xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + ".nomedia"), 'w')
nmFile.close() nmFile.close()
success = self.remote_vfs.put(xbmc.translatePath(utils.data_dir() + ".nomedia"), self.remote_vfs.root_path + ".nomedia") success = self._copyFile(self.xbmc_vfs, self.remote_vfs, xbmcvfs.translatePath(utils.data_dir() + ".nomedia"), self.remote_vfs.root_path + ".nomedia")
return success return success
@@ -494,23 +529,19 @@ class XbmcBackup:
result = None result = None
# copy the file and open it # copy the file and open it
if(isinstance(self.remote_vfs, DropboxFileSystem)): self._copyFile(self.remote_vfs, self.xbmc_vfs, path + "xbmcbackup.val", xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
self.remote_vfs.get_file(path + "xbmcbackup.val", xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
else:
self.xbmc_vfs.put(path + "xbmcbackup.val", xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"), 'r') with xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup_restore.val"), 'r') as vFile:
jsonString = vFile.read() jsonString = vFile.read()
vFile.close()
# delete after checking # delete after checking
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val")) xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
try: try:
result = json.loads(jsonString) result = json.loads(jsonString)
if(xbmc.getInfoLabel('System.BuildVersion') != result['xbmc_version']): if(xbmc.getInfoLabel('System.BuildVersion') != result['xbmc_version']):
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30085), utils.getString(30086), utils.getString(30044)) shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30085), "%s\n%s" % (utils.getString(30086), utils.getString(30044)))
if(not shouldContinue): if(not shouldContinue):
result = None result = None
@@ -522,11 +553,11 @@ class XbmcBackup:
return result return result
def _createResumeBackupFile(self): def _createResumeBackupFile(self):
with xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"), 'w') as f: with xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + "resume.txt"), 'w') as f:
f.write(self.restore_point) f.write(self.restore_point)
def _readBackupConfig(self, aFile): def _readBackupConfig(self, aFile):
with xbmcvfs.File(xbmc.translatePath(aFile), 'r') as f: with xbmcvfs.File(xbmcvfs.translatePath(aFile), 'r') as f:
jsonString = f.read() jsonString = f.read()
return json.loads(jsonString) return json.loads(jsonString)
@@ -536,6 +567,7 @@ class FileManager:
exclude_dir = [] exclude_dir = []
root_dirs = [] root_dirs = []
pathSep = '/' pathSep = '/'
totalSize = 0
def __init__(self, vfs): def __init__(self, vfs):
self.vfs = vfs self.vfs = vfs
@@ -546,11 +578,13 @@ class FileManager:
def walk(self): def walk(self):
for aDir in self.root_dirs: for aDir in self.root_dirs:
self.addFile('-' + xbmc.translatePath(aDir['path'])) self.addFile('-' + xbmcvfs.translatePath(aDir['path']))
self.walkTree(xbmc.translatePath(aDir['path']), aDir['recurse']) self.walkTree(xbmcvfs.translatePath(aDir['path']), aDir['recurse'])
def walkTree(self, directory, recurse=True): def walkTree(self, directory, recurse=True):
utils.log('walking ' + directory + ', recurse: ' + str(recurse)) if(utils.getSettingBool('verbose_logging')):
utils.log('walking ' + directory + ', recurse: ' + str(recurse))
if(directory[-1:] == '/' or directory[-1:] == '\\'): if(directory[-1:] == '/' or directory[-1:] == '\\'):
directory = directory[:-1] directory = directory[:-1]
@@ -560,7 +594,7 @@ class FileManager:
if(recurse): 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 + self.pathSep + aDir)) dirPath = xbmcvfs.validatePath(xbmcvfs.translatePath(directory + self.pathSep + aDir))
file_ext = aDir.split('.')[-1] file_ext = aDir.split('.')[-1]
# check if directory is excluded # check if directory is excluded
@@ -580,22 +614,27 @@ class FileManager:
# copy all the files # copy all the files
for aFile in files: for aFile in files:
filePath = xbmc.translatePath(directory + self.pathSep + aFile) filePath = xbmcvfs.translatePath(directory + self.pathSep + aFile)
self.addFile(filePath) self.addFile(filePath)
def addDir(self, dirMeta): def addDir(self, dirMeta):
if(dirMeta['type'] == 'include'): if(dirMeta['type'] == 'include'):
self.root_dirs.append({'path': dirMeta['path'], 'recurse': dirMeta['recurse']}) self.root_dirs.append({'path': dirMeta['path'], 'recurse': dirMeta['recurse']})
else: else:
self.excludeFile(xbmc.translatePath(dirMeta['path'])) self.excludeFile(xbmcvfs.translatePath(dirMeta['path']))
def addFile(self, filename): def addFile(self, filename):
# write the full remote path name of this file # write the full remote path name of this file
utils.log("Add File: " + filename) if(utils.getSettingBool('verbose_logging')):
self.fileArray.append(filename) utils.log("Add File: " + filename)
# get the file size
fSize = self.vfs.fileSize(filename)
self.totalSize = self.totalSize + fSize
self.fileArray.append({'file': filename, 'size': fSize})
def excludeFile(self, filename): def excludeFile(self, filename):
# remove trailing slash # remove trailing slash
if(filename[-1] == '/' or filename[-1] == '\\'): if(filename[-1] == '/' or filename[-1] == '\\'):
filename = filename[:-1] filename = filename[:-1]
@@ -609,8 +648,12 @@ class FileManager:
self.fileArray = [] self.fileArray = []
self.root_dirs = [] self.root_dirs = []
self.exclude_dir = [] self.exclude_dir = []
self.totalSize = 0
return result return result
def size(self): def totalFiles(self):
return len(self.fileArray) return len(self.fileArray)
def fileSize(self):
return self.totalSize

View File

@@ -1,72 +1,47 @@
import json import json
import xbmc import xbmc
import xbmcvfs
from . import utils as utils from . import utils as utils
from xml.dom import minidom
from xml.parsers.expat import ExpatError
class GuiSettingsManager: class GuiSettingsManager:
doc = None filename = 'kodi_settings.json'
systemSettings = None
def __init__(self): def __init__(self):
# first make a copy of the file # get all of the current Kodi settings
xbmcvfs.copy(xbmc.translatePath('special://home/userdata/guisettings.xml'), xbmc.translatePath("special://home/userdata/guisettings.xml.restored")) json_response = json.loads(xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettings","params":{"level":"expert"}}'))
# read in the copy self.systemSettings = json_response['result']['settings']
self._readFile(xbmc.translatePath('special://home/userdata/guisettings.xml.restored'))
def run(self): def backup(self):
# get a list of all the settings we can manipulate via json utils.log('Backing up Kodi settings')
json_response = json.loads(xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettings","params":{"level":"advanced"}}'))
settings = json_response['result']['settings'] # return all current settings
currentSettings = {} return self.systemSettings
for aSetting in settings: def restore(self, restoreSettings):
if('value' in aSetting): utils.log('Restoring Kodi settings')
currentSettings[aSetting['id']] = aSetting['value']
# parse the existing xml file and get all the settings we need to restore updateJson = {"jsonrpc": "2.0", "id": 1, "method": "Settings.SetSettingValue", "params": {"setting": "", "value": ""}}
restoreSettings = self.__parseNodes(self.doc.getElementsByTagName('setting'))
# get a list where the restore setting value != the current value # create a setting=value dict of the current settings
updateSettings = {k: v for k, v in list(restoreSettings.items()) if (k in currentSettings and currentSettings[k] != v)} settingsDict = {}
for aSetting in self.systemSettings:
# ignore action types, no value
if(aSetting['type'] != 'action'):
settingsDict[aSetting['id']] = aSetting['value']
# go through all the found settings and update them restoreCount = 0
jsonObj = {"jsonrpc": "2.0", "id": 1, "method": "Settings.SetSettingValue", "params": {"setting": "", "value": ""}} for aSetting in restoreSettings:
for anId, aValue in list(updateSettings.items()): # only update a setting if its different than the current (action types have no value)
utils.log("updating: " + anId + ", value: " + str(aValue)) if(aSetting['type'] != 'action' and settingsDict[aSetting['id']] != aSetting['value']):
if(utils.getSettingBool('verbose_logging')):
utils.log('%s different than current: %s' % (aSetting['id'], str(aSetting['value'])))
jsonObj['params']['setting'] = anId updateJson['params']['setting'] = aSetting['id']
jsonObj['params']['value'] = aValue updateJson['params']['value'] = aSetting['value']
xbmc.executeJSONRPC(json.dumps(jsonObj)) xbmc.executeJSONRPC(json.dumps(updateJson))
restoreCount = restoreCount + 1
def __parseNodes(self, nodeList): utils.log('Update %d settings' % restoreCount)
result = {}
for node in nodeList:
nodeValue = ''
if(node.firstChild is not None):
nodeValue = node.firstChild.nodeValue
# check for numbers and booleans
if(nodeValue.isdigit()):
nodeValue = int(nodeValue)
elif(nodeValue == 'true'):
nodeValue = True
elif(nodeValue == 'false'):
nodeValue = False
result[node.getAttribute('id')] = nodeValue
return result
def _readFile(self, fileLoc):
if(xbmcvfs.exists(fileLoc)):
try:
self.doc = minidom.parse(fileLoc)
except ExpatError:
utils.log("Can't read " + fileLoc)

View File

@@ -15,9 +15,9 @@ class BackupProgressBar:
self.override = progressOverride self.override = progressOverride
# check if we should use the progress bar # check if we should use the progress bar
if(int(utils.getSetting('progress_mode')) != 2): if(utils.getSettingInt('progress_mode') != 2):
# check if background or normal # check if background or normal
if(int(utils.getSetting('progress_mode')) == 0 and not self.override): if(utils.getSettingInt('progress_mode') == 0 and not self.override):
self.mode = self.DIALOG self.mode = self.DIALOG
self.progressBar = xbmcgui.DialogProgress() self.progressBar = xbmcgui.DialogProgress()
else: else:

View File

@@ -1,200 +1,194 @@
import time import time
from datetime import datetime from datetime import datetime
import xbmc import xbmc
import xbmcvfs import xbmcvfs
import xbmcgui import xbmcgui
import resources.lib.utils as utils from . import 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 UPGRADE_INT = 2 # to keep track of any upgrade notifications
class BackupScheduler: class BackupScheduler:
monitor = None monitor = None
enabled = "false" enabled = False
next_run = 0 next_run = 0
next_run_path = None next_run_path = None
restore_point = None restore_point = None
def __init__(self): def __init__(self):
self.monitor = UpdateMonitor(update_method=self.settingsChanged) self.monitor = UpdateMonitor(update_method=self.settingsChanged)
self.enabled = utils.getSetting("enable_scheduler") self.enabled = utils.getSettingBool("enable_scheduler")
self.next_run_path = xbmc.translatePath(utils.data_dir()) + 'next_run.txt' self.next_run_path = xbmcvfs.translatePath(utils.data_dir()) + 'next_run.txt'
if(self.enabled == "true"): if(self.enabled):
# sleep for 2 minutes so Kodi can start and time can update correctly # sleep for 2 minutes so Kodi can start and time can update correctly
xbmc.Monitor().waitForAbort(120) xbmc.Monitor().waitForAbort(120)
nr = 0 nr = 0
if(xbmcvfs.exists(self.next_run_path)): if(xbmcvfs.exists(self.next_run_path)):
fh = xbmcvfs.File(self.next_run_path) with xbmcvfs.File(self.next_run_path) as fh:
try: try:
# check if we saved a run time from the last run # check if we saved a run time from the last run
nr = float(fh.read()) nr = float(fh.read())
except ValueError: except ValueError:
nr = 0 nr = 0
fh.close() # if we missed and the user wants to play catch-up
if(0 < nr <= time.time() and utils.getSettingBool('schedule_miss')):
# if we missed and the user wants to play catch-up utils.log("scheduled backup was missed, doing it now...")
if(0 < nr <= time.time() and utils.getSetting('schedule_miss') == 'true'): progress_mode = utils.getSettingInt('progress_mode')
utils.log("scheduled backup was missed, doing it now...")
progress_mode = int(utils.getSetting('progress_mode')) if(progress_mode == 0):
progress_mode = 1 # Kodi just started, don't block it with a foreground progress bar
if(progress_mode == 0):
progress_mode = 1 # Kodi just started, don't block it with a foreground progress bar self.doScheduledBackup(progress_mode)
self.doScheduledBackup(progress_mode) self.setup()
self.setup() def setup(self):
# scheduler was turned on, find next run time
def setup(self): utils.log("scheduler enabled, finding next run time")
# scheduler was turned on, find next run time self.findNextRun(time.time())
utils.log("scheduler enabled, finding next run time")
self.findNextRun(time.time()) def start(self):
def start(self): # display upgrade messages if they exist
if(utils.getSettingInt('upgrade_notes') < UPGRADE_INT):
# display upgrade messages if they exist xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30132))
if(int(utils.getSetting('upgrade_notes')) < UPGRADE_INT): utils.setSetting('upgrade_notes', str(UPGRADE_INT))
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30132))
utils.setSetting('upgrade_notes', str(UPGRADE_INT)) # check if a backup should be resumed
resumeRestore = self._resumeCheck()
# check if a backup should be resumed
resumeRestore = self._resumeCheck() if(resumeRestore):
restore = XbmcBackup()
if(resumeRestore): restore.selectRestore(self.restore_point)
restore = XbmcBackup() # skip the advanced settings check
restore.selectRestore(self.restore_point) restore.skipAdvanced()
# skip the advanced settings check restore.restore()
restore.skipAdvanced()
restore.restore() while(not self.monitor.abortRequested()):
while(not self.monitor.abortRequested()): if(self.enabled):
# scheduler is still on
if(self.enabled == "true"): now = time.time()
# scheduler is still on
now = time.time() if(self.next_run <= now):
progress_mode = utils.getSettingInt('progress_mode')
if(self.next_run <= now): self.doScheduledBackup(progress_mode)
progress_mode = int(utils.getSetting('progress_mode'))
self.doScheduledBackup(progress_mode) # check if we should shut the computer down
if(utils.getSettingBool("cron_shutdown")):
# check if we should shut the computer down # wait 10 seconds to make sure all backup processes and files are completed
if(utils.getSetting("cron_shutdown") == 'true'): time.sleep(10)
# wait 10 seconds to make sure all backup processes and files are completed xbmc.executebuiltin('ShutDown()')
time.sleep(10) else:
xbmc.executebuiltin('ShutDown()') # find the next run time like normal
else: self.findNextRun(now)
# find the next run time like normal
self.findNextRun(now) xbmc.sleep(500)
xbmc.sleep(500) # delete monitor to free up memory
del self.monitor
# delete monitor to free up memory
del self.monitor def doScheduledBackup(self, progress_mode):
if(progress_mode != 2):
def doScheduledBackup(self, progress_mode): utils.showNotification(utils.getString(30053))
if(progress_mode != 2):
utils.showNotification(utils.getString(30053)) backup = XbmcBackup()
backup = XbmcBackup() if(backup.remoteConfigured()):
if(backup.remoteConfigured()): if(utils.getSettingInt('progress_mode') in [0, 1]):
backup.backup(True)
if(int(utils.getSetting('progress_mode')) in [0, 1]): else:
backup.backup(True) backup.backup(False)
else:
backup.backup(False) # check if this is a "one-off"
if(utils.getSettingInt("schedule_interval") == 0):
# check if this is a "one-off" # disable the scheduler after this run
if(int(utils.getSetting("schedule_interval")) == 0): self.enabled = False
# disable the scheduler after this run utils.setSetting('enable_scheduler', 'false')
self.enabled = "false" else:
utils.setSetting('enable_scheduler', 'false') utils.showNotification(utils.getString(30045))
else:
utils.showNotification(utils.getString(30045)) def findNextRun(self, now):
progress_mode = utils.getSettingInt('progress_mode')
def findNextRun(self, now):
progress_mode = int(utils.getSetting('progress_mode')) # find the cron expression and get the next run time
cron_exp = self.parseSchedule()
# find the cron expression and get the next run time
cron_exp = self.parseSchedule() cron_ob = croniter(cron_exp, datetime.fromtimestamp(now))
new_run_time = cron_ob.get_next(float)
cron_ob = croniter(cron_exp, datetime.fromtimestamp(now))
new_run_time = cron_ob.get_next(float) if(new_run_time != self.next_run):
self.next_run = new_run_time
if(new_run_time != self.next_run): utils.log("scheduler will run again on " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run), ['dateshort', 'time']))
self.next_run = new_run_time
utils.log("scheduler will run again on " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run), ['dateshort', 'time'])) # write the next time to a file
with xbmcvfs.File(self.next_run_path, 'w') as fh:
# write the next time to a file fh.write(str(self.next_run))
fh = xbmcvfs.File(self.next_run_path, 'w')
fh.write(str(self.next_run)) # only show when not in silent mode
fh.close() if(progress_mode != 2):
utils.showNotification(utils.getString(30081) + " " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run), ['dateshort', 'time']))
# only show when not in silent mode
if(progress_mode != 2): def settingsChanged(self):
utils.showNotification(utils.getString(30081) + " " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run), ['dateshort', 'time'])) current_enabled = utils.getSettingBool("enable_scheduler")
def settingsChanged(self): if(current_enabled and not self.enabled):
current_enabled = utils.getSetting("enable_scheduler") # scheduler was just turned on
self.enabled = current_enabled
if(current_enabled == "true" and self.enabled == "false"): self.setup()
# scheduler was just turned on elif (not current_enabled and self.enabled):
self.enabled = current_enabled # schedule was turn off
self.setup() self.enabled = current_enabled
elif (current_enabled == "false" and self.enabled == "true"):
# schedule was turn off if(self.enabled):
self.enabled = current_enabled # always recheck the next run time after an update
self.findNextRun(time.time())
if(self.enabled == "true"):
# always recheck the next run time after an update def parseSchedule(self):
self.findNextRun(time.time()) schedule_type = utils.getSettingInt("schedule_interval")
cron_exp = utils.getSetting("cron_schedule")
def parseSchedule(self):
schedule_type = int(utils.getSetting("schedule_interval")) hour_of_day = utils.getSetting("schedule_time")
cron_exp = utils.getSetting("cron_schedule") hour_of_day = int(hour_of_day[0:2])
if(schedule_type == 0 or schedule_type == 1):
hour_of_day = utils.getSetting("schedule_time") # every day
hour_of_day = int(hour_of_day[0:2]) cron_exp = "0 " + str(hour_of_day) + " * * *"
if(schedule_type == 0 or schedule_type == 1): elif(schedule_type == 2):
# every day # once a week
cron_exp = "0 " + str(hour_of_day) + " * * *" day_of_week = utils.getSetting("day_of_week")
elif(schedule_type == 2): cron_exp = "0 " + str(hour_of_day) + " * * " + day_of_week
# once a week elif(schedule_type == 3):
day_of_week = utils.getSetting("day_of_week") # first day of month
cron_exp = "0 " + str(hour_of_day) + " * * " + day_of_week cron_exp = "0 " + str(hour_of_day) + " 1 * *"
elif(schedule_type == 3):
# first day of month return cron_exp
cron_exp = "0 " + str(hour_of_day) + " 1 * *"
def _resumeCheck(self):
return cron_exp shouldContinue = False
if(xbmcvfs.exists(xbmcvfs.translatePath(utils.data_dir() + "resume.txt"))):
def _resumeCheck(self): rFile = xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + "resume.txt"), 'r')
shouldContinue = False self.restore_point = rFile.read()
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "resume.txt"))): rFile.close()
rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"), 'r') xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "resume.txt"))
self.restore_point = rFile.read() shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042), utils.getString(30043), utils.getString(30044))
rFile.close()
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "resume.txt")) return shouldContinue
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042), utils.getString(30043), utils.getString(30044))
return shouldContinue class UpdateMonitor(xbmc.Monitor):
update_method = None
class UpdateMonitor(xbmc.Monitor): def __init__(self, *args, **kwargs):
update_method = None xbmc.Monitor.__init__(self)
self.update_method = kwargs['update_method']
def __init__(self, *args, **kwargs):
xbmc.Monitor.__init__(self) def onSettingsChanged(self):
self.update_method = kwargs['update_method'] self.update_method()
def onSettingsChanged(self):
self.update_method()
BackupScheduler().start()

View File

@@ -1,6 +1,7 @@
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcaddon import xbmcaddon
import xbmcvfs
__addon_id__ = 'script.xbmcbackup' __addon_id__ = 'script.xbmcbackup'
__Addon = xbmcaddon.Addon(__addon_id__) __Addon = xbmcaddon.Addon(__addon_id__)
@@ -23,13 +24,21 @@ def log(message, loglevel=xbmc.LOGDEBUG):
def showNotification(message): def showNotification(message):
xbmcgui.Dialog().notification(getString(30010), message, time=4000, icon=xbmc.translatePath(__Addon.getAddonInfo('path') + "/resources/images/icon.png")) xbmcgui.Dialog().notification(getString(30010), message, time=4000, icon=xbmcvfs.translatePath(__Addon.getAddonInfo('path') + "/resources/images/icon.png"))
def getSetting(name): def getSetting(name):
return __Addon.getSetting(name) return __Addon.getSetting(name)
def getSettingBool(name):
return bool(__Addon.getSettingBool(name))
def getSettingInt(name):
return __Addon.getSettingInt(name)
def setSetting(name, value): def setSetting(name, value):
__Addon.setSetting(name, value) __Addon.setSetting(name, value)
@@ -45,3 +54,16 @@ def getRegionalTimestamp(date_time, dateformat=['dateshort']):
result = result + ("%s " % date_time.strftime(xbmc.getRegion(aFormat))) result = result + ("%s " % date_time.strftime(xbmc.getRegion(aFormat)))
return result.strip() return result.strip()
def diskString(fSize):
# convert a size in kilobytes to the best possible match and return as a string
fSize = float(fSize)
i = 0
sizeNames = ['KB', 'MB', 'GB', 'TB']
while(fSize > 1024):
fSize = fSize / 1024
i = i + 1
return "%0.2f%s" % (fSize, sizeNames[i])

View File

@@ -2,7 +2,6 @@ from __future__ import unicode_literals
import zipfile import zipfile
import os.path import os.path
import sys import sys
import xbmc
import xbmcvfs import xbmcvfs
import xbmcgui import xbmcgui
from dropbox import dropbox from dropbox import dropbox
@@ -17,16 +16,19 @@ class Vfs:
def __init__(self, rootString): def __init__(self, rootString):
self.set_root(rootString) self.set_root(rootString)
def set_root(self, rootString): def clean_path(self, path):
old_root = self.root_path
self.root_path = rootString
# fix slashes # fix slashes
self.root_path = self.root_path.replace("\\", "/") path = path.replace("\\", "/")
# check if trailing slash is included # check if trailing slash is included
if(self.root_path[-1:] != "/"): if(path[-1:] != '/'):
self.root_path = self.root_path + "/" path = path + '/'
return path
def set_root(self, rootString):
old_root = self.root_path
self.root_path = self.clean_path(rootString)
# return the old root # return the old root
return old_root return old_root
@@ -55,6 +57,9 @@ class Vfs:
def cleanup(self): def cleanup(self):
return True return True
def fileSize(self, filename):
return 0 # result should be in KB
class XBMCFileSystem(Vfs): class XBMCFileSystem(Vfs):
@@ -62,13 +67,13 @@ class XBMCFileSystem(Vfs):
return xbmcvfs.listdir(directory) return xbmcvfs.listdir(directory)
def mkdir(self, directory): def mkdir(self, directory):
return xbmcvfs.mkdir(xbmc.translatePath(directory)) return xbmcvfs.mkdir(xbmcvfs.translatePath(directory))
def put(self, source, dest): def put(self, source, dest):
return xbmcvfs.copy(xbmc.translatePath(source), xbmc.translatePath(dest)) return xbmcvfs.copy(xbmcvfs.translatePath(source), xbmcvfs.translatePath(dest))
def rmdir(self, directory): def rmdir(self, directory):
return xbmcvfs.rmdir(directory, True) return xbmcvfs.rmdir(directory, force=True) # use force=True to make sure it works recursively
def rmfile(self, aFile): def rmfile(self, aFile):
return xbmcvfs.delete(aFile) return xbmcvfs.delete(aFile)
@@ -79,6 +84,12 @@ class XBMCFileSystem(Vfs):
def exists(self, aFile): def exists(self, aFile):
return xbmcvfs.exists(aFile) return xbmcvfs.exists(aFile)
def fileSize(self, filename):
with xbmcvfs.File(filename) as f:
result = f.size() / 1024 # bytes to kilobytes
return result
class ZipFileSystem(Vfs): class ZipFileSystem(Vfs):
zip = None zip = None
@@ -96,7 +107,7 @@ class ZipFileSystem(Vfs):
def put(self, source, dest): def put(self, source, dest):
aFile = xbmcvfs.File(xbmc.translatePath(source), 'r') aFile = xbmcvfs.File(xbmcvfs.translatePath(source), 'r')
self.zip.writestr(dest, aFile.readBytes()) self.zip.writestr(dest, aFile.readBytes())
@@ -246,6 +257,16 @@ class DropboxFileSystem(Vfs):
else: else:
return False return False
def fileSize(self, filename):
result = 0
aFile = self._fix_slashes(filename)
if(self.client is not None):
metadata = self.client.files_get_metadata(aFile)
result = metadata.size / 1024 # bytes to KB
return result
def get_file(self, source, dest): def get_file(self, source, dest):
if(self.client is not None): if(self.client is not None):
# write the file locally # write the file locally

View File

@@ -1,43 +1,392 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0"?>
<settings> <settings version="1">
<category id="general" label="30011"> <section id="service.xbmcbackup">
<setting id="compress_backups" type="bool" label="30087" default="false" /> <category id="general" label="30011" help="">
<setting id="backup_rotation" type="number" label="30026" default="0" /> <group id="1" label="">
<setting id="progress_mode" type="enum" label="30022" lvalues="30082|30083|30084" default="0" /> <!-- compress backups -->
<setting id="upgrade_notes" type="number" label="upgrade_notes" visible="false" default="1" /> <setting id="compress_backups" type="boolean" label="30087" help="">
</category> <level>0</level>
<category id="backup_path" label="30048"> <default>false</default>
<setting id="remote_selection" type="enum" lvalues="30018|30019|30027" default="0" label="30025"/> <control type="toggle" />
<setting id="remote_path_2" type="text" label="30024" default="" visible="eq(-1,1)" /> </setting>
<setting id="remote_path" type="folder" label="30020" visible="eq(-2,0)" /> <!-- zip folder staging path -->
<setting id="dropbox_key" type="text" label="30028" visible="eq(-3,2)" default="" /> <setting id="zip_temp_path" type="string" label="30152" help="30153">
<setting id="dropbox_secret" type="text" label="30029" visible="eq(-4,2)" default="" /> <level>3</level>
<setting id="google_drive_id" type="text" label="Client ID" visible="eq(-5,3)" default="" /> <default>special://temp</default>
<setting id="google_drive_secret" type="text" label="Client Secret" visible="eq(-6,3)" default="" /> <constraints>
<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)"/> <allowempty>true</allowempty>
<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)"/> </constraints>
<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)"/> <dependencies>
</category> <dependency type="visible" setting="compress_backups">true</dependency>
<category id="selection" label="30012"> </dependencies>
<setting id="backup_selection_type" type="enum" lvalues="30014|30015" default="0" label="30023" /> <control type="edit" format="string">
<setting id="backup_addon_data" type="bool" label="30031" default="false" visible="eq(-1,0)"/> <heading>30152</heading>
<setting id="backup_config" type="bool" label="30035" default="true" visible="eq(-2,0)"/> </control>
<setting id="backup_database" type="bool" label="30032" default="true" visible="eq(-3,0)"/> </setting>
<setting id="backup_game_saves" type="bool" label="30133" default="false" visible="eq(-4,0)" /> <!-- backup rotation -->
<setting id="backup_playlists" type="bool" label="30033" default="true" visible="eq(-5,0)"/> <setting id="backup_rotation" type="integer" label="30026" help="">
<setting id="backup_profiles" type="bool" label="30080" default="false" visible="eq(-6,0)"/> <level>0</level>
<setting id="backup_thumbnails" type="bool" label="30034" default="true" visible="eq(-7,0)"/> <default>0</default>
<setting id="backup_addons" type="bool" label="30030" default="true" visible="eq(-8,0)" /> <control type="edit" format="integer">
<setting id="advanced_button" type="action" label="30125" visible="eq(-9,1)" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_editor)" /> <heading>30026</heading>
<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)" /> </control>
</category> </setting>
<category id="scheduling" label="30013"> <!-- prompt to restore settings -->
<setting id="enable_scheduler" type="bool" label="30060" default="false" /> <setting id="always_prompt_restore_settings" type="boolean" label="30148" help="30154">
<setting id="schedule_interval" type="enum" label="30061" lvalues="30079|30072|30073|30074|30075" default="1" enable="eq(-1,true)"/> <level>2</level>
<setting id="schedule_time" type="labelenum" label="30062" values="00:00|01:00|02:00|03:00|04:00|05:00|06:00|07:00|08:00|09:00|10:00|11:00|12:00|13:00|14:00|15:00|16:00|17:00|18:00|19:00|20:00|21:00|22:00|23:00" default="00:00" visible="!eq(-1,4)" enable="eq(-2,true)"/> <default>false</default>
<setting id="day_of_week" type="enum" label="30063" lvalues="30065|30066|30067|30068|30069|30070|30071" default="0" visible="eq(-2,2)" enable="eq(-3,true)"/> <control type="toggle" />
<setting id="cron_schedule" type="text" label="30064" default="0 0 * * *" visible="eq(-3,4)" enable="eq(-4,true)"/> </setting>
<setting id="schedule_miss" type="bool" label="30109" default="false" enable="eq(-5,true)" /> <!-- progress mode -->
<setting id="cron_shutdown" type="bool" label="30076" default="false" enable="eq(-6,true)" /> <setting id="progress_mode" type="integer" label="30022" help="">
</category> <level>1</level>
<default>0</default>
<constraints>
<options>
<option label="30082">0</option>
<option label="30083">1</option>
<option label="30084">2</option>
</options>
</constraints>
<control type="spinner" format="string" />
</setting>
</group>
<group id="2" label="">
<!-- verbose logging -->
<setting id="verbose_logging" type="boolean" label="30151" help="30155">
<level>3</level>
<default>false</default>
<control type="toggle" />
</setting>
<!-- upgrade notes not visible to users -->
<setting id="upgrade_notes" type="integer" label="upgrade_notes" help="">
<level>4</level>
<default>1</default>
<visible>false</visible>
<control type="edit" format="integer">
<heading>upgrade_notes</heading>
</control>
</setting>
</group>
</category>
<category id="backup_path" label="30048" help="">
<group id="1" label="">
<!-- backup repo type -->
<setting id="remote_selection" type="integer" label="30025" help="">
<level>0</level>
<default>0</default>
<constraints>
<options>
<option label="30018">0</option>
<option label="30019">1</option>
<option label="30027">2</option>
</options>
</constraints>
<control type="spinner" format="string" />
</setting>
<!-- folder select path -->
<setting id="remote_path" type="path" label="30020" help="">
<level>0</level>
<default/>
<constraints>
<allowempty>true</allowempty>
</constraints>
<dependencies>
<dependency type="visible" setting="remote_selection">0</dependency>
</dependencies>
<control type="button" format="path">
<heading>30020</heading>
</control>
</setting>
<!-- type remote path -->
<setting id="remote_path_2" type="string" label="30024" help="">
<level>0</level>
<default></default>
<constraints>
<allowempty>true</allowempty>
</constraints>
<dependencies>
<dependency type="visible" setting="remote_selection">1</dependency>
</dependencies>
<control type="edit" format="string">
<heading>30024</heading>
</control>
</setting>
<!-- dropbox key and secret -->
<setting id="dropbox_key" type="string" label="30028" help="30156">
<level>0</level>
<default></default>
<constraints>
<allowempty>true</allowempty>
</constraints>
<dependencies>
<dependency type="visible" setting="remote_selection">2</dependency>
</dependencies>
<control type="edit" format="string">
<heading>30028</heading>
</control>
</setting>
<setting id="dropbox_secret" type="string" label="30029" help="30156">
<level>0</level>
<default></default>
<constraints>
<allowempty>true</allowempty>
</constraints>
<dependencies>
<dependency type="visible" setting="remote_selection">2</dependency>
</dependencies>
<control type="edit" format="string">
<heading>30029</heading>
</control>
</setting>
<!-- authorize dropbox -->
<setting id="auth_dropbox_button" type="action" label="30104" help="">
<level>0</level>
<default />
<dependencies>
<dependency type="visible" setting="remote_selection">2</dependency>
</dependencies>
<control type="button" format="action">
<data>RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=authorize_cloud,provider=dropbox)</data>
</control>
</setting>
</group>
<group id="2" label="">
<!-- remove auth button -->
<setting id="remove_auth_button" type="action" label="30093" help="">
<level>2</level>
<default />
<dependencies>
<dependency type="visible" setting="remote_selection">2</dependency>
</dependencies>
<control type="button" format="action">
<data>RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=remove_auth)</data>
</control>
</setting>
</group>
</category>
<category id="selection" label="30012">
<group id="1" label="">
<!-- selection type (simple/advanced) -->
<setting id="backup_selection_type" type="integer" label="30023" help="30157">
<level>2</level>
<default>0</default>
<constraints>
<options>
<option label="30014">0</option>
<option label="30015">1</option>
</options>
</constraints>
<control type="spinner" format="string" />
</setting>
<!-- simple selection settings -->
<setting id="backup_addon_data" type="boolean" label="30031" help="">
<level>0</level>
<default>false</default>
<dependencies>
<dependency type="visible" setting="backup_selection_type">0</dependency>
</dependencies>
<control type="toggle" />
</setting>
<setting id="backup_config" type="boolean" label="30035" help="">
<level>0</level>
<default>true</default>
<dependencies>
<dependency type="visible" setting="backup_selection_type">0</dependency>
</dependencies>
<control type="toggle" />
</setting>
<setting id="backup_database" type="boolean" label="30032" help="">
<level>0</level>
<default>true</default>
<dependencies>
<dependency type="visible" setting="backup_selection_type">0</dependency>
</dependencies>
<control type="toggle" />
</setting>
<setting id="backup_game_saves" type="boolean" label="30133" help="">
<level>0</level>
<default>false</default>
<dependencies>
<dependency type="visible" setting="backup_selection_type">0</dependency>
</dependencies>
<control type="toggle" />
</setting>
<setting id="backup_playlists" type="boolean" label="30033" help="">
<level>0</level>
<default>true</default>
<dependencies>
<dependency type="visible" setting="backup_selection_type">0</dependency>
</dependencies>
<control type="toggle" />
</setting>
<setting id="backup_profiles" type="boolean" label="30080" help="">
<level>0</level>
<default>false</default>
<dependencies>
<dependency type="visible" setting="backup_selection_type">0</dependency>
</dependencies>
<control type="toggle" />
</setting>
<setting id="backup_thumbnails" type="boolean" label="30034" help="">
<level>0</level>
<default>true</default>
<dependencies>
<dependency type="visible" setting="backup_selection_type">0</dependency>
</dependencies>
<control type="toggle" />
</setting>
<setting id="backup_addons" type="boolean" label="30030" help="">
<level>0</level>
<default>true</default>
<dependencies>
<dependency type="visible" setting="backup_selection_type">0</dependency>
</dependencies>
<control type="toggle" />
</setting>
<!-- advanced editor options -->
<setting id="advanced_button" type="action" label="30125" help="">
<level>2</level>
<default />
<dependencies>
<dependency type="visible" setting="backup_selection_type">1</dependency>
</dependencies>
<control type="button" format="action">
<data>RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_editor)</data>
</control>
</setting>
<setting id="advanced_defaults" type="action" label="30139" help="">
<level>2</level>
<default />
<dependencies>
<dependency type="visible" setting="backup_selection_type">1</dependency>
</dependencies>
<control type="button" format="action">
<data>RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_copy_config)</data>
</control>
</setting>
</group>
</category>
<category id="scheduling" label="30013">
<group id="1" label="">
<!-- enable scheduler -->
<setting id="enable_scheduler" type="boolean" label="30060" help="30158">
<level>0</level>
<default>false</default>
<control type="toggle" />
</setting>
<!-- schedule interval -->
<setting id="schedule_interval" type="integer" label="30061" help="">
<level>0</level>
<default>1</default>
<constraints>
<options>
<option label="30079">0</option>
<option label="30072">1</option>
<option label="30073">2</option>
<option label="30074">3</option>
<option label="30075">4</option>
</options>
</constraints>
<dependencies>
<dependency type="enable" setting="enable_scheduler">true</dependency>
</dependencies>
<control type="spinner" format="string"/>
</setting>
<!-- hour of the day -->
<setting id="schedule_time" type="string" label="30062" help="">
<level>0</level>
<default>00:00</default>
<constraints>
<options sort="ascending">
<option>00:00</option>
<option>01:00</option>
<option>02:00</option>
<option>03:00</option>
<option>04:00</option>
<option>05:00</option>
<option>06:00</option>
<option>07:00</option>
<option>08:00</option>
<option>09:00</option>
<option>10:00</option>
<option>11:00</option>
<option>12:00</option>
<option>13:00</option>
<option>14:00</option>
<option>15:00</option>
<option>16:00</option>
<option>17:00</option>
<option>18:00</option>
<option>19:00</option>
<option>20:00</option>
<option>21:00</option>
<option>22:00</option>
<option>23:00</option>
</options>
<allowempty>false</allowempty>
</constraints>
<dependencies>
<dependency type="visible" setting="schedule_interval" operator="!is">4</dependency>
<dependency type="enable" setting="enable_scheduler">true</dependency>
</dependencies>
<control type="spinner" format="string"/>
</setting>
<!-- day of the week -->
<setting id="day_of_week" type="integer" label="30063" help="">
<level>0</level>
<default>0</default>
<constraints>
<options>
<option label="30065">0</option>
<option label="30066">1</option>
<option label="30067">2</option>
<option label="30068">3</option>
<option label="30069">4</option>
<option label="30070">5</option>
<option label="30071">6</option>
</options>
</constraints>
<dependencies>
<dependency type="visible" setting="schedule_interval">2</dependency>
<dependency type="enable" setting="enable_scheduler">true</dependency>
</dependencies>
<control type="spinner" format="string"/>
</setting>
<!-- cron schedule -->
<setting id="cron_schedule" type="string" label="30064" help="">
<level>0</level>
<default>0 0 * * *</default>
<constraints>
<allowempty>false</allowempty>
</constraints>
<dependencies>
<dependency type="visible" setting="schedule_interval">4</dependency>
<dependency type="enable" setting="enable_scheduler">true</dependency>
</dependencies>
<control type="edit" format="string">
<heading>30064</heading>
</control>
</setting>
<!-- run if schedule missed -->
<setting id="schedule_miss" type="boolean" label="30109" help="">
<level>1</level>
<default>false</default>
<dependencies>
<dependency type="enable" setting="enable_scheduler">true</dependency>
</dependencies>
<control type="toggle" />
</setting>
<!-- shutdown on complete -->
<setting id="cron_shutdown" type="boolean" label="30076" help="">
<level>1</level>
<default>false</default>
<dependencies>
<dependency type="enable" setting="enable_scheduler">true</dependency>
</dependencies>
<control type="toggle" />
</setting>
</group>
</category>
</section>
</settings> </settings>

4
service.py Normal file
View File

@@ -0,0 +1,4 @@
from resources.lib.scheduler import BackupScheduler
# start the backup scheduler
BackupScheduler().start()