42 Commits

Author SHA1 Message Date
Rob Weber
40b6260521 version bump 2021-04-16 09:40:31 -05:00
Rob Weber
5aad014dbc update changelog 2021-04-16 09:36:26 -05:00
Rob Weber
ef3b820ca5 fix some gui dialog prompts 2021-04-16 09:34:43 -05:00
Rob Weber
76e8e0efeb check for restores right away 2021-04-16 09:30:22 -05:00
Rob Weber
f7e77fd739 fixes #190 - need to set file number to 1 2021-04-16 09:30:11 -05:00
Rob Weber
382dbce4ac added qrcode for dropbox setup 2021-04-08 15:43:26 -05:00
Rob
4b066432be Launcher (#189)
* move launcher code to default.py, modify RunScript in settings
2021-04-01 15:33:38 -05:00
Rob Weber
d71c923e78 forgot to add release date 2021-03-15 13:44:46 -05:00
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
20 changed files with 853 additions and 319 deletions

View File

@@ -3,7 +3,7 @@ language: python
python: 3.7
install:
- pip install flake8 kodi-addon-checker git+https://github.com/romanvm/kodi-addon-submitter.git
- pip install flake8 flake8_polyfill kodi-addon-checker git+https://github.com/romanvm/kodi-addon-submitter.git
before_script:
- git config core.quotepath false

View File

@@ -1,13 +1,13 @@
# 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/matrix)](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.7) [![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
I've had to recover my database, thumbnails, and source configuration enough times that I just wanted a quick easy way to back them up. That is what this addon is meant to do.
I've had to recover my database, thumbnails, and source configuration enough times that I just wanted a quick easy way to back them up. That is what this addon is meant to do.
## Running the Program
Running the program will allow you to select Backup or Restore as a running mode. Selecting Backup will push files to your remote store using the addon settings you defined. Selecting Restore will give you a list of restore points currently in your remote destination. Selecting one will pull the files matching your selection criteria from the restore point to your local Kodi folders.
Running the program will allow you to select Backup or Restore as a running mode. Selecting Backup will push files to your remote store using the addon settings you defined. Selecting Restore will give you a list of restore points currently in your remote destination. Selecting one will pull the files matching your selection criteria from the restore point to your local Kodi folders.
For more specific information please check out the [wiki on Github](https://github.com/robweber/xbmcbackup/wiki) for this project. Advanced descriptions for the following are all there:
@@ -15,17 +15,9 @@ For more specific information please check out the [wiki on Github](https://gith
* [Cloud Storage](https://github.com/robweber/xbmcbackup/wiki/Cloud-Storage)
* [Scheduling](https://github.com/robweber/xbmcbackup/wiki/Scheduling)
* [Scripting](https://github.com/robweber/xbmcbackup/wiki/Scripting)
* [FAQ](https://github.com/robweber/xbmcbackup/wiki/FAQ)
* [FAQ](https://github.com/robweber/xbmcbackup/wiki/FAQ)
## Attributions
Icon files from Open Iconic — www.useiconic.com/open

View File

@@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.xbmcbackup"
name="Backup" version="1.6.3" provider-name="robweber">
name="Backup" version="1.6.7" provider-name="robweber">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.dateutil" version="2.8.0" />
<import addon="script.module.future" version="0.16.0.4"/>
<import addon="script.module.dropbox" version="9.4.0"/>
<import addon="script.module.future" version="0.18.2+matrix.1" />
<import addon="script.module.dropbox" version="9.4.0" />
<import addon="script.module.pyqrcode" version="1.2.1+matrix.1" />
</requires>
<extension point="xbmc.python.script" library="default.py">
<provides>executable</provides>
</extension>
<extension point="xbmc.python.script" library="default.py">
<provides>executable</provides>
</extension>
<extension point="xbmc.service" library="service.py" />
<extension point="xbmc.addon.metadata">
<summary lang="ar_SA">إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع</summary>
@@ -89,14 +90,10 @@
<screenshot>resources/images/screenshot3.jpg</screenshot>
<screenshot>resources/images/screenshot4.jpg</screenshot>
</assets>
<news>Version 1.6.3
- fixed validatePath error (issue #166)
Version 1.6.2
- replaced PNG screenshots with JPG
Version 1.6.1
- added file transfer size to progress bar
- progress bar now based on transfer size, not total file count
- fixed rotate backups error - thanks @AnonTester
<news>Version 1.6.7
- fixed issue with RunScript not launching Advanced Editor in some cases
- added qr code for Dropbox setup
- fixed error on advanced settings restore
</news>
</extension>
</addon>

View File

@@ -4,6 +4,62 @@ 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/)
## [Version 1.6.7](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.5...robweber:matrix-1.6.7) - 2021-04-16
### Added
- added QRcode when setting up Dropbox, uses pyqrcode
### Fixed
- fixed issue when using ```RunScript()``` within settings to launch Advanced Editor
- error on advanced settings restore prior to reboot
- minor gui dialog fixes
## [Version 1.6.6](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.5...robweber:matrix-1.6.6) - 2021-03-15
### 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
@@ -48,7 +104,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
- removed specific encode() calls per Python2/3 compatibility
- 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 for pep9 styling
- use setArt() to set ListItem icons as the icon= constructor is deprecated
@@ -66,16 +122,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/)
- Added script.module.dropbox import as a dependency for Dropbox filesystem
### 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
- 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
- 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
@@ -85,7 +141,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## Version 1.5.0 - 2019-08-26
### 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
- File backups and restores will not work with old version - breaking change with previous versions PR117
@@ -95,7 +151,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Added
- added file chunk support for Dropbox uploads
- added scheduler delay to assist with time sync (rpi mostly), will delay startup by 2 min
### Changed
- fixed settings duplicate ids, thanks aster-anto
@@ -107,7 +163,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## Version 1.1.1
### Added
- added ability to "catchup" on missed scheduled backup
- added ability to "catchup" on missed scheduled backup
### Changed
- fixed error on authorizers (missing secret/key)
@@ -118,10 +174,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Added
- added tinyurl generation for oauth urls
### Changed
- moved authorize to settings area for cloud storage
## Version 1.0.9
### Changed
@@ -142,7 +198,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Added
- added progress for zip extraction - hopefully helps with extract errors
### Changed
- fix for custom directories not working recursively
@@ -282,7 +338,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## 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
- update updated language files from Transifex

View File

@@ -1,7 +1,37 @@
import xbmc
import xbmcgui
import xbmcvfs
import resources.lib.utils as utils
from resources.lib.backup import XbmcBackup
from resources.lib.authorizers import DropboxAuthorizer
from resources.lib.advanced_editor import AdvancedBackupEditor
# mode constants
BACKUP = 0
RESTORE = 1
SETTINGS = 2
ADVANCED_EDITOR = 3
LAUNCHER = 4
def authorize_cloud(cloudProvider):
# drobpox
if(cloudProvider == 'dropbox'):
authorizer = DropboxAuthorizer()
if(authorizer.authorize()):
xbmcgui.Dialog().ok(utils.getString(30010), '%s %s' % (utils.getString(30027), utils.getString(30106)))
else:
xbmcgui.Dialog().ok(utils.getString(30010), '%s %s' % (utils.getString(30107), utils.getString(30027)))
def remove_auth():
# triggered from settings.xml - asks if user wants to delete OAuth token information
shouldDelete = xbmcgui.Dialog().yesno(utils.getString(30093), utils.getString(30094), utils.getString(30095), autoclose=7000)
if(shouldDelete):
# delete any of the known token file types
xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt")) # dropbox
xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "google_drive.dat")) # google drive
def get_params():
@@ -13,6 +43,7 @@ def get_params():
if(args.startswith('?')):
args = args[1:] # legacy in case of url params
splitString = args.split('=')
utils.log(splitString[1])
param[splitString[0]] = splitString[1]
except:
pass
@@ -24,13 +55,13 @@ def get_params():
mode = -1
params = get_params()
if("mode" in params):
if(params['mode'] == 'backup'):
mode = 0
mode = BACKUP
elif(params['mode'] == 'restore'):
mode = 1
mode = RESTORE
elif(params['mode'] == 'launcher'):
mode = LAUNCHER
# if mode wasn't passed in as arg, get from user
if(mode == -1):
@@ -49,15 +80,30 @@ if(mode != -1):
# run the profile backup
backup = XbmcBackup()
if(mode == 2):
if(mode == SETTINGS):
# open the settings dialog
utils.openSettings()
elif(mode == 3 and utils.getSettingInt('backup_selection_type') == 1):
# open the advanced editor
xbmc.executebuiltin('RunScript(special://home/addons/script.xbmcbackup/launcher.py, action=advanced_editor)')
elif(mode == ADVANCED_EDITOR and utils.getSettingInt('backup_selection_type') == 1):
# open the advanced editor but only if in advanced mode
editor = AdvancedBackupEditor()
editor.showMainScreen()
elif(mode == LAUNCHER):
# copied from old launcher.py
if(params['action'] == 'authorize_cloud'):
authorize_cloud(params['provider'])
elif(params['action'] == 'remove_auth'):
remove_auth()
elif(params['action'] == 'advanced_editor'):
editor = AdvancedBackupEditor()
editor.showMainScreen()
elif(params['action'] == 'advanced_copy_config'):
editor = AdvancedBackupEditor()
editor.copySimpleConfig()
elif(backup.remoteConfigured()):
if(mode == backup.Restore):
# if mode was RESTORE
if(mode == RESTORE):
# get list of valid restore points
restorePoints = backup.listBackups()
pointNames = []
@@ -90,6 +136,7 @@ if(mode != -1):
else:
backup.restore()
else:
# mode was BACKUP
backup.backup()
else:
# can't go any further

View File

@@ -1,59 +0,0 @@
# launcher for various helpful functions found in the settings.xml area
import sys
import xbmc
import xbmcgui
import xbmcvfs
import resources.lib.utils as utils
from resources.lib.authorizers import DropboxAuthorizer
from resources.lib.advanced_editor import AdvancedBackupEditor
def authorize_cloud(cloudProvider):
# drobpox
if(cloudProvider == 'dropbox'):
authorizer = DropboxAuthorizer()
if(authorizer.authorize()):
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30027) + ' ' + utils.getString(30106))
else:
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30107) + ' ' + utils.getString(30027))
def remove_auth():
# triggered from settings.xml - asks if user wants to delete OAuth token information
shouldDelete = xbmcgui.Dialog().yesno(utils.getString(30093), utils.getString(30094), utils.getString(30095), autoclose=7000)
if(shouldDelete):
# delete any of the known token file types
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "tokens.txt")) # dropbox
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "google_drive.dat")) # google drive
def get_params():
param = {}
try:
for i in sys.argv:
args = i
if('=' in args):
if(args.startswith('?')):
args = args[1:] # legacy in case of url params
splitString = args.split('=')
param[splitString[0]] = splitString[1]
except:
pass
return param
params = get_params()
if(params['action'] == 'authorize_cloud'):
authorize_cloud(params['provider'])
elif(params['action'] == 'remove_auth'):
remove_auth()
elif(params['action'] == 'advanced_editor'):
editor = AdvancedBackupEditor()
editor.showMainScreen()
elif(params['action'] == 'advanced_copy_config'):
editor = AdvancedBackupEditor()
editor.copySimpleConfig()

View File

@@ -173,8 +173,8 @@ msgid "Would you like to continue?"
msgstr "Would you like to continue?"
msgctxt "#30045"
msgid "Error: Remote path doesn't exist"
msgstr "Error: Remote path doesn't exist"
msgid "Error: Remote or zip file path doesn't exist"
msgstr "Error: Remote or zip file path doesn't exist"
msgctxt "#30046"
msgid "Starting"
@@ -213,8 +213,8 @@ msgid "Removing backup"
msgstr "Removing backup"
msgctxt "#30056"
msgid "Go to this URL to authorize"
msgstr "Go to this URL to authorize"
msgid "Scan or click this URL to authorize, click OK AFTER completion"
msgstr "Scan or click this URL to authorize, click OK AFTER completion"
msgctxt "#30057"
msgid "Click OK AFTER completion"
@@ -581,3 +581,46 @@ 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 xbmc
import xbmcgui
import xbmcvfs
import os.path
@@ -7,7 +6,7 @@ from . import utils as utils
class BackupSetManager:
jsonFile = xbmc.translatePath(utils.data_dir() + "custom_paths.json")
jsonFile = xbmcvfs.translatePath(utils.data_dir() + "custom_paths.json")
paths = None
def __init__(self):
@@ -95,7 +94,7 @@ class AdvancedBackupEditor:
if(name is not None):
# give a choice to start in home or enter a root path
enterHome = self.dialog.yesno(utils.getString(30111), line1=utils.getString(30112) + " - " + utils.getString(30114), line2=utils.getString(30113) + " - " + utils.getString(30115), nolabel=utils.getString(30112), yeslabel=utils.getString(30113))
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'
if(enterHome):
@@ -106,7 +105,7 @@ class AdvancedBackupEditor:
rootFolder = rootFolder + '/'
# 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)
return None
else:
@@ -162,7 +161,7 @@ class AdvancedBackupEditor:
contextOption = self.dialog.contextmenu(cOptions)
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
del backupSet['dirs'][optionSelected - 3]
elif(contextOption == 1 and backupSet['dirs'][optionSelected - 3]['type'] == 'include'):
@@ -218,7 +217,7 @@ class AdvancedBackupEditor:
customPaths.updateSet(aSet['name'], updatedSet)
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
customPaths.deleteSet(exitCondition - 1)
@@ -227,7 +226,7 @@ class AdvancedBackupEditor:
shouldContinue = self.dialog.yesno(utils.getString(30139), utils.getString(30140), utils.getString(30141))
if(shouldContinue):
source = xbmc.translatePath(os.path.join(utils.addon_dir(), 'resources', 'data', 'default_files.json'))
dest = xbmc.translatePath(os.path.join(utils.data_dir(), 'custom_paths.json'))
source = xbmcvfs.translatePath(os.path.join(utils.addon_dir(), 'resources', 'data', 'default_files.json'))
dest = xbmcvfs.translatePath(os.path.join(utils.data_dir(), 'custom_paths.json'))
xbmcvfs.copy(source, dest)

View File

@@ -1,16 +1,41 @@
import xbmc
import xbmcgui
import xbmcvfs
import pyqrcode
import resources.lib.tinyurl as tinyurl
import resources.lib.utils as utils
# don't die on import error yet, these might not even get used
try:
from dropbox import dropbox
from dropbox import oauth
except ImportError:
pass
class QRCode(xbmcgui.WindowXMLDialog):
def __init__(self, *args, **kwargs):
self.image = kwargs["image"]
self.text = kwargs["text"]
self.url = kwargs['url']
def onInit(self):
self.imagecontrol = 501
self.textbox1 = 502
self.textbox2 = 504
self.okbutton = 503
self.showdialog()
def showdialog(self):
self.getControl(self.imagecontrol).setImage(self.image)
self.getControl(self.textbox1).setText(self.text)
self.getControl(self.textbox2).setText(self.url)
self.setFocus(self.getControl(self.okbutton))
def onClick(self, controlId):
if (controlId == self.okbutton):
self.close()
class DropboxAuthorizer:
APP_KEY = ""
APP_SECRET = ""
@@ -24,7 +49,7 @@ class DropboxAuthorizer:
if(self.APP_KEY == '' and self.APP_SECRET == ''):
# 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
@@ -46,13 +71,26 @@ class DropboxAuthorizer:
self._deleteToken()
# 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()
# print url in log
utils.log("Authorize URL: " + url)
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30056), utils.getString(30057), tinyurl.shorten(url))
# create a QR Code
shortUrl = str(tinyurl.shorten(url), 'utf-8')
imageFile = xbmcvfs.translatePath(utils.data_dir() + '/qrcode.png')
qrIMG = pyqrcode.create(shortUrl)
qrIMG.png(imageFile, scale=10)
# show the dialog prompt to authorize
qr = QRCode("script-backup-qrcode.xml", utils.addon_dir(), "default", image=imageFile, text=utils.getString(30056), url=shortUrl)
qr.doModal()
# cleanup
del qr
xbmcvfs.delete(imageFile)
# get the auth code
code = xbmcgui.Dialog().input(utils.getString(30027) + ' ' + utils.getString(30103))
@@ -89,14 +127,14 @@ class DropboxAuthorizer:
def _setToken(self, token):
# 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.close()
def _getToken(self):
# get token, if it exists
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "tokens.txt"))):
token_file = open(xbmc.translatePath(utils.data_dir() + "tokens.txt"))
if(xbmcvfs.exists(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt"))):
token_file = open(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt"))
token = token_file.read()
token_file.close()
@@ -105,5 +143,5 @@ class DropboxAuthorizer:
return ""
def _deleteToken(self):
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "tokens.txt"))):
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "tokens.txt"))
if(xbmcvfs.exists(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt"))):
xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "tokens.txt"))

View File

@@ -27,6 +27,8 @@ class XbmcBackup:
Backup = 0
Restore = 1
ZIP_TEMP_PATH = None
# list of dirs for the "simple" file selection
simple_directory_list = ['addons', 'addon_data', 'database', 'game_saves', 'playlists', 'profiles', 'thumbnails', 'config']
@@ -47,27 +49,27 @@ class XbmcBackup:
skip_advanced = False # if we should check for the existance of advancedsettings in the restore
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()
utils.log(utils.getString(30046))
def configureRemote(self):
if(utils.getSetting('remote_selection') == '1'):
self.remote_base_path = utils.getSetting('remote_path_2')
self.remote_vfs = XBMCFileSystem(utils.getSetting('remote_path_2'))
utils.setSetting("remote_path", "")
elif(utils.getSetting('remote_selection') == '0'):
self.remote_base_path = utils.getSetting('remote_path')
self.remote_vfs = XBMCFileSystem(utils.getSetting("remote_path"))
elif(utils.getSetting('remote_selection') == '2'):
self.remote_base_path = "/"
self.remote_vfs = DropboxFileSystem("/")
self.remote_base_path = self.remote_vfs.root_path
def remoteConfigured(self):
result = True
if(self.remote_base_path == ""):
if(self.remote_base_path == "" or not xbmcvfs.exists(self.ZIP_TEMP_PATH)):
result = False
return result
@@ -155,7 +157,7 @@ class XbmcBackup:
if(not writeCheck):
# 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):
return
@@ -165,7 +167,7 @@ class XbmcBackup:
# backup all the files
self.transferLeft = self.transferSize
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'])
filesCopied = self._copyFiles(fileGroup['files'], self.xbmc_vfs, self.remote_vfs)
@@ -181,13 +183,13 @@ class XbmcBackup:
fileManager = FileManager(self.xbmc_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.xbmc_vfs.rename(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"), xbmc.translatePath("special://temp/" + zip_name))
fileManager.addFile(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(zip_name)
# set root to data dir home and reset remote
self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/"))
self.xbmc_vfs.set_root(self.ZIP_TEMP_PATH)
self.remote_vfs = self.saved_remote_vfs
# update the amount to transfer
@@ -197,10 +199,10 @@ class XbmcBackup:
if(not fileCopied):
# 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
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + zip_name))
self.xbmc_vfs.rmfile(zip_name)
# remove old backups
self._rotateBackups()
@@ -220,9 +222,9 @@ class XbmcBackup:
utils.log("copying zip file: " + self.restore_point)
# set root to data dir home
self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/"))
if(not self.xbmc_vfs.exists(xbmc.translatePath("special://temp/" + self.restore_point))):
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(restore_path)):
# copy just this file from the remote vfs
self.transferSize = self.remote_vfs.fileSize(self.remote_base_path + self.restore_point)
zipFile = []
@@ -235,13 +237,13 @@ class XbmcBackup:
utils.log("zip file exists already")
# extract the zip file
zip_vfs = ZipFileSystem(xbmc.translatePath("special://temp/" + self.restore_point), 'r')
zip_vfs = ZipFileSystem(restore_path, 'r')
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
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))
return
@@ -250,12 +252,12 @@ class XbmcBackup:
self.progressBar.updateProgress(0, utils.getString(30049) + "......")
# set the new remote vfs and fix xbmc path
self.remote_vfs = XBMCFileSystem(xbmc.translatePath("special://temp/" + self.restore_point.split(".")[0] + "/"))
self.xbmc_vfs.set_root(xbmc.translatePath("special://home/"))
self.remote_vfs = XBMCFileSystem(os.path.join(self.ZIP_TEMP_PATH, self.restore_point.split(".")[0]))
self.xbmc_vfs.set_root(xbmcvfs.translatePath("special://home/"))
# for restores remote path must exist
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
valFile = self._checkValidationFile(self.remote_vfs.root_path)
@@ -270,10 +272,12 @@ class XbmcBackup:
# 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):
# 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\n%s" % (utils.getString(30039), utils.getString(30040), utils.getString(30041)))
if(restartXbmc):
# add only this file to the file list
self.transferSize = 1
self.transferLeft = 1
fileManager.addFile(self.remote_vfs.root_path + "config/advancedsettings.xml")
self._copyFiles(fileManager.getFiles(), self.remote_vfs, self.xbmc_vfs)
@@ -284,6 +288,12 @@ class XbmcBackup:
xbmcgui.Dialog().ok(utils.getString(30077), utils.getString(30078))
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
restoreSets = [n['name'] for n in valFile['directories']]
@@ -294,13 +304,14 @@ class XbmcBackup:
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):
# go through each of the directories in the backup and write them to the correct location
for index in selectedSets:
# add this directory
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'] + '/')):
# walk the directory
fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/')
@@ -309,7 +320,7 @@ class XbmcBackup:
allFiles.append({"source": self.remote_vfs.root_path + aDir['name'], "dest": self.xbmc_vfs.root_path, "files": fileManager.getFiles()})
else:
utils.log("error path not found: " + self.remote_vfs.root_path + 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
self.transferLeft = self.transferSize
@@ -318,16 +329,20 @@ class XbmcBackup:
self.xbmc_vfs.set_root(fileGroup['dest'])
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 .....")
if(self.restore_point.split('.')[-1] == 'zip'):
# delete the zip file and the extracted directory
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point))
self.xbmc_vfs.rmdir(self.remote_vfs.root_path)
# update the guisettings information (or what we can from it)
gui_settings = GuiSettingsManager()
gui_settings.run()
self.xbmc_vfs.rmfile(os.path.join(self.ZIP_TEMP_PATH, self.restore_point))
xbmc.sleep(1000)
self.xbmc_vfs.rmdir(self.remote_vfs.clean_path(os.path.join(self.ZIP_TEMP_PATH, self.restore_point.split(".")[0])))
xbmc.sleep(1000)
# call update addons to refresh everything
xbmc.executebuiltin('UpdateLocalAddons')
@@ -342,15 +357,16 @@ class XbmcBackup:
if(mode == self.Backup and self.remote_vfs.root_path != ''):
if(utils.getSettingBool("compress_backups")):
# delete old temp file
if(self.xbmc_vfs.exists(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
if(not self.xbmc_vfs.rmfile(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
zip_path = os.path.join(self.ZIP_TEMP_PATH, '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
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
# save the remote file system and use the zip vfs
self.saved_remote_vfs = self.remote_vfs
self.remote_vfs = ZipFileSystem(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"), "w")
self.remote_vfs = ZipFileSystem(zip_path, "w")
self.remote_vfs.set_root(self.remote_vfs.root_path + time.strftime("%Y%m%d%H%M") + "/")
progressBarTitle = progressBarTitle + utils.getString(30023) + ": " + utils.getString(30016)
@@ -365,6 +381,7 @@ class XbmcBackup:
utils.log(utils.getString(30047) + ": " + self.xbmc_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
self.progressBar = BackupProgressBar(progressOverride)
@@ -404,14 +421,8 @@ class XbmcBackup:
self._updateProgress('%s remaining, writing %s' % (utils.diskString(self.transferLeft), os.path.basename(aFile['file'][len(source.root_path):])))
self.transferLeft = self.transferLeft - aFile['size']
wroteFile = True
destFile = dest.root_path + aFile['file'][len(source.root_path):]
if(isinstance(source, DropboxFileSystem)):
# if copying from cloud storage we need the file handle, use get_file
wroteFile = source.get_file(aFile['file'], destFile)
else:
# copy using normal method
wroteFile = dest.put(aFile['file'], 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(not wroteFile and result):
@@ -419,11 +430,23 @@ class XbmcBackup:
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):
utils.log('Backup set: ' + folder_name)
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:
fileManager.addDir(aDir)
@@ -472,30 +495,35 @@ class XbmcBackup:
remove_num = remove_num + 1
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 = []
# save list of file sets
for aDir in dirList:
valDirs.append({"name": aDir['name'], "path": aDir['source']})
valInfo['directories'] = valDirs
vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"), 'w')
# 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("")
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
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"))
xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup.val"))
if(success):
# android requires a .nomedia file to not index the directory as media
if(not xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + ".nomedia"))):
nmFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + ".nomedia"), 'w')
if(not xbmcvfs.exists(xbmcvfs.translatePath(utils.data_dir() + ".nomedia"))):
nmFile = xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + ".nomedia"), 'w')
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
@@ -503,22 +531,19 @@ class XbmcBackup:
result = None
# copy the file and open it
if(isinstance(self.remote_vfs, DropboxFileSystem)):
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"))
self._copyFile(self.remote_vfs, self.xbmc_vfs, path + "xbmcbackup.val", xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
with xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"), 'r') as vFile:
with xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup_restore.val"), 'r') as vFile:
jsonString = vFile.read()
# delete after checking
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
try:
result = json.loads(jsonString)
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):
result = None
@@ -530,11 +555,11 @@ class XbmcBackup:
return result
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)
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()
return json.loads(jsonString)
@@ -555,8 +580,8 @@ class FileManager:
def walk(self):
for aDir in self.root_dirs:
self.addFile('-' + xbmc.translatePath(aDir['path']))
self.walkTree(xbmc.translatePath(aDir['path']), aDir['recurse'])
self.addFile('-' + xbmcvfs.translatePath(aDir['path']))
self.walkTree(xbmcvfs.translatePath(aDir['path']), aDir['recurse'])
def walkTree(self, directory, recurse=True):
if(utils.getSettingBool('verbose_logging')):
@@ -571,7 +596,7 @@ class FileManager:
if(recurse):
# create all the subdirs first
for aDir in dirs:
dirPath = xbmcvfs.validatePath(xbmc.translatePath(directory + self.pathSep + aDir))
dirPath = xbmcvfs.validatePath(xbmcvfs.translatePath(directory + self.pathSep + aDir))
file_ext = aDir.split('.')[-1]
# check if directory is excluded
@@ -591,14 +616,14 @@ class FileManager:
# copy all the files
for aFile in files:
filePath = xbmc.translatePath(directory + self.pathSep + aFile)
filePath = xbmcvfs.translatePath(directory + self.pathSep + aFile)
self.addFile(filePath)
def addDir(self, dirMeta):
if(dirMeta['type'] == 'include'):
self.root_dirs.append({'path': dirMeta['path'], 'recurse': dirMeta['recurse']})
else:
self.excludeFile(xbmc.translatePath(dirMeta['path']))
self.excludeFile(xbmcvfs.translatePath(dirMeta['path']))
def addFile(self, filename):
# write the full remote path name of this file

View File

@@ -1,72 +1,47 @@
import json
import xbmc
import xbmcvfs
from . import utils as utils
from xml.dom import minidom
from xml.parsers.expat import ExpatError
class GuiSettingsManager:
doc = None
filename = 'kodi_settings.json'
systemSettings = None
def __init__(self):
# first make a copy of the file
xbmcvfs.copy(xbmc.translatePath('special://home/userdata/guisettings.xml'), xbmc.translatePath("special://home/userdata/guisettings.xml.restored"))
# get all of the current Kodi settings
json_response = json.loads(xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettings","params":{"level":"expert"}}'))
# read in the copy
self._readFile(xbmc.translatePath('special://home/userdata/guisettings.xml.restored'))
self.systemSettings = json_response['result']['settings']
def run(self):
# get a list of all the settings we can manipulate via json
json_response = json.loads(xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettings","params":{"level":"advanced"}}'))
def backup(self):
utils.log('Backing up Kodi settings')
settings = json_response['result']['settings']
currentSettings = {}
# return all current settings
return self.systemSettings
for aSetting in settings:
if('value' in aSetting):
currentSettings[aSetting['id']] = aSetting['value']
def restore(self, restoreSettings):
utils.log('Restoring Kodi settings')
# parse the existing xml file and get all the settings we need to restore
restoreSettings = self.__parseNodes(self.doc.getElementsByTagName('setting'))
updateJson = {"jsonrpc": "2.0", "id": 1, "method": "Settings.SetSettingValue", "params": {"setting": "", "value": ""}}
# get a list where the restore setting value != the current value
updateSettings = {k: v for k, v in list(restoreSettings.items()) if (k in currentSettings and currentSettings[k] != v)}
# create a setting=value dict of the current settings
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
jsonObj = {"jsonrpc": "2.0", "id": 1, "method": "Settings.SetSettingValue", "params": {"setting": "", "value": ""}}
for anId, aValue in list(updateSettings.items()):
utils.log("updating: " + anId + ", value: " + str(aValue))
restoreCount = 0
for aSetting in restoreSettings:
# only update a setting if its different than the current (action types have no value)
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
jsonObj['params']['value'] = aValue
updateJson['params']['setting'] = aSetting['id']
updateJson['params']['value'] = aSetting['value']
xbmc.executeJSONRPC(json.dumps(jsonObj))
xbmc.executeJSONRPC(json.dumps(updateJson))
restoreCount = restoreCount + 1
def __parseNodes(self, nodeList):
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)
utils.log('Update %d settings' % restoreCount)

View File

@@ -20,7 +20,22 @@ class BackupScheduler:
def __init__(self):
self.monitor = UpdateMonitor(update_method=self.settingsChanged)
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'
# display upgrade messages if they exist
if(utils.getSettingInt('upgrade_notes') < UPGRADE_INT):
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30132))
utils.setSetting('upgrade_notes', str(UPGRADE_INT))
# check if a backup should be resumed
resumeRestore = self._resumeCheck()
if(resumeRestore):
restore = XbmcBackup()
restore.selectRestore(self.restore_point)
# skip the advanced settings check
restore.skipAdvanced()
restore.restore()
if(self.enabled):
@@ -56,21 +71,6 @@ class BackupScheduler:
def start(self):
# display upgrade messages if they exist
if(utils.getSettingInt('upgrade_notes') < UPGRADE_INT):
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30132))
utils.setSetting('upgrade_notes', str(UPGRADE_INT))
# check if a backup should be resumed
resumeRestore = self._resumeCheck()
if(resumeRestore):
restore = XbmcBackup()
restore.selectRestore(self.restore_point)
# skip the advanced settings check
restore.skipAdvanced()
restore.restore()
while(not self.monitor.abortRequested()):
if(self.enabled):
@@ -173,12 +173,12 @@ class BackupScheduler:
def _resumeCheck(self):
shouldContinue = False
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "resume.txt"))):
rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"), 'r')
if(xbmcvfs.exists(xbmcvfs.translatePath(utils.data_dir() + "resume.txt"))):
rFile = xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + "resume.txt"), 'r')
self.restore_point = rFile.read()
rFile.close()
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "resume.txt"))
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042), utils.getString(30043), utils.getString(30044))
xbmcvfs.delete(xbmcvfs.translatePath(utils.data_dir() + "resume.txt"))
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042), "%s\n%s" % (utils.getString(30043), utils.getString(30044)))
return shouldContinue

View File

@@ -1,6 +1,7 @@
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
__addon_id__ = 'script.xbmcbackup'
__Addon = xbmcaddon.Addon(__addon_id__)
@@ -23,7 +24,7 @@ def log(message, loglevel=xbmc.LOGDEBUG):
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):

View File

@@ -2,7 +2,6 @@ from __future__ import unicode_literals
import zipfile
import os.path
import sys
import xbmc
import xbmcvfs
import xbmcgui
from dropbox import dropbox
@@ -68,13 +67,13 @@ class XBMCFileSystem(Vfs):
return xbmcvfs.listdir(directory)
def mkdir(self, directory):
return xbmcvfs.mkdir(xbmc.translatePath(directory))
return xbmcvfs.mkdir(xbmcvfs.translatePath(directory))
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):
return xbmcvfs.rmdir(directory, True)
return xbmcvfs.rmdir(directory, force=True) # use force=True to make sure it works recursively
def rmfile(self, aFile):
return xbmcvfs.delete(aFile)
@@ -108,7 +107,7 @@ class ZipFileSystem(Vfs):
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())

View File

@@ -1,45 +1,392 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<settings>
<category id="general" label="30011" level="expert">
<setting id="compress_backups" type="bool" label="30087" default="false" />
<setting id="backup_rotation" type="number" label="30026" default="0" />
<setting id="progress_mode" type="enum" label="30022" lvalues="30082|30083|30084" default="0" />
<setting type="sep" />
<setting id="verbose_logging" type="bool" label="Enable Verbose Logging" default="false" />
<setting id="upgrade_notes" type="number" label="upgrade_notes" visible="false" default="1" />
</category>
<category id="backup_path" label="30048">
<setting id="remote_selection" type="enum" lvalues="30018|30019|30027" default="0" label="30025"/>
<setting id="remote_path_2" type="text" label="30024" default="" visible="eq(-1,1)" />
<setting id="remote_path" type="folder" label="30020" visible="eq(-2,0)" />
<setting id="dropbox_key" type="text" label="30028" visible="eq(-3,2)" default="" />
<setting id="dropbox_secret" type="text" label="30029" visible="eq(-4,2)" default="" />
<setting id="google_drive_id" type="text" label="Client ID" visible="eq(-5,3)" default="" />
<setting id="google_drive_secret" type="text" label="Client Secret" visible="eq(-6,3)" default="" />
<setting id="auth_dropbox_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=authorize_cloud,provider=dropbox)" visible="eq(-7,2)"/>
<setting id="auth_google_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=authorize_cloud,provider=google_drive)" visible="eq(-8,3)"/>
<setting id="remove_auth_button" type="action" label="30093" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=remove_auth)" visible="gt(-9,1)"/>
</category>
<category id="selection" label="30012">
<setting id="backup_selection_type" type="enum" lvalues="30014|30015" default="0" label="30023" />
<setting id="backup_addon_data" type="bool" label="30031" default="false" visible="eq(-1,0)"/>
<setting id="backup_config" type="bool" label="30035" default="true" visible="eq(-2,0)"/>
<setting id="backup_database" type="bool" label="30032" default="true" visible="eq(-3,0)"/>
<setting id="backup_game_saves" type="bool" label="30133" default="false" visible="eq(-4,0)" />
<setting id="backup_playlists" type="bool" label="30033" default="true" visible="eq(-5,0)"/>
<setting id="backup_profiles" type="bool" label="30080" default="false" visible="eq(-6,0)"/>
<setting id="backup_thumbnails" type="bool" label="30034" default="true" visible="eq(-7,0)"/>
<setting id="backup_addons" type="bool" label="30030" default="true" visible="eq(-8,0)" />
<setting id="advanced_button" type="action" label="30125" visible="eq(-9,1)" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_editor)" />
<setting id="advanced_defaults" type="action" label="30139" visible="eq(-10,1)" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_copy_config)" />
</category>
<category id="scheduling" label="30013">
<setting id="enable_scheduler" type="bool" label="30060" default="false" />
<setting id="schedule_interval" type="enum" label="30061" lvalues="30079|30072|30073|30074|30075" default="1" enable="eq(-1,true)"/>
<setting id="schedule_time" type="labelenum" label="30062" values="00:00|01:00|02:00|03:00|04:00|05:00|06:00|07:00|08:00|09:00|10:00|11:00|12:00|13:00|14:00|15:00|16:00|17:00|18:00|19:00|20:00|21:00|22:00|23:00" default="00:00" visible="!eq(-1,4)" enable="eq(-2,true)"/>
<setting id="day_of_week" type="enum" label="30063" lvalues="30065|30066|30067|30068|30069|30070|30071" default="0" visible="eq(-2,2)" enable="eq(-3,true)"/>
<setting id="cron_schedule" type="text" label="30064" default="0 0 * * *" visible="eq(-3,4)" enable="eq(-4,true)"/>
<setting id="schedule_miss" type="bool" label="30109" default="false" enable="eq(-5,true)" />
<setting id="cron_shutdown" type="bool" label="30076" default="false" enable="eq(-6,true)" />
</category>
<?xml version="1.0"?>
<settings version="1">
<section id="service.xbmcbackup">
<category id="general" label="30011" help="">
<group id="1" label="">
<!-- compress backups -->
<setting id="compress_backups" type="boolean" label="30087" help="">
<level>0</level>
<default>false</default>
<control type="toggle" />
</setting>
<!-- zip folder staging path -->
<setting id="zip_temp_path" type="string" label="30152" help="30153">
<level>3</level>
<default>special://temp</default>
<constraints>
<allowempty>true</allowempty>
</constraints>
<dependencies>
<dependency type="visible" setting="compress_backups">true</dependency>
</dependencies>
<control type="edit" format="string">
<heading>30152</heading>
</control>
</setting>
<!-- backup rotation -->
<setting id="backup_rotation" type="integer" label="30026" help="">
<level>0</level>
<default>0</default>
<control type="edit" format="integer">
<heading>30026</heading>
</control>
</setting>
<!-- prompt to restore settings -->
<setting id="always_prompt_restore_settings" type="boolean" label="30148" help="30154">
<level>2</level>
<default>false</default>
<control type="toggle" />
</setting>
<!-- progress mode -->
<setting id="progress_mode" type="integer" label="30022" help="">
<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(script.xbmcbackup,mode=launcher,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(script.xbmcbackup,mode=launcher,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(script.xbmcbackup,mode=launcher,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(script.xbmcbackup,mode=launcher,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>

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<window>
<coordinates>
<left>502</left>
<top>345</top>
</coordinates>
<controls>
<animation type="WindowOpen" reversible="false">
<effect type="zoom" start="80" end="100" center="960,540" delay="160" tween="back" time="240" />
<effect type="fade" delay="160" end="100" time="240" />
</animation>
<animation type="WindowClose" reversible="false">
<effect type="zoom" start="100" end="80" center="960,540" easing="in" tween="back" time="240" />
<effect type="fade" start="100" end="0" time="240" />
</animation>
<control type="image">
<left>-1920</left>
<top>-1080</top>
<width>5760</width>
<height>3240</height>
<animation effect="fade" start="0" end="100" time="300">WindowOpen</animation>
<animation effect="fade" start="100" end="0" time="200">WindowClose</animation>
<texture colordiffuse="C2FFFFFF">background-black.png</texture>
</control>
<control type="image">
<left>0</left>
<top>0</top>
<width>915</width>
<height>450</height>
<texture border="2">dialog-bg.png</texture>
</control>
<control type="textbox" id="502">
<left>0</left>
<top>20</top>
<width>915</width>
<height>300</height>
<font>font12_title</font>
<align>center</align>
<aligny>top</aligny>
<shadowcolor>FF000000</shadowcolor>
</control>
<control type="textbox" id="504">
<left>0</left>
<top>60</top>
<width>915</width>
<height>300</height>
<font>font12_title</font>
<align>center</align>
<aligny>top</aligny>
<shadowcolor>FF000000</shadowcolor>
</control>
<control type="image" id="501">
<left>300</left>
<top>80</top>
<width>300</width>
<height>300</height>
<aspectratio aligny="center" align="center">keep</aspectratio>
</control>
<control type="button" id="503">
<left>302</left>
<top>350</top>
<width>300</width>
<height>90</height>
<font>font12_title</font>
<textcolor>FFF0F0F0</textcolor>
<textoffsetx>20</textoffsetx>
<label>OK</label>
<align>center</align>
<aligny>center</aligny>
<texturefocus border="40" colordiffuse="FF12B2E7">dialogbutton-fo.png</texturefocus>
<texturenofocus border="40">dialogbutton-nofo.png</texturenofocus>
</control>
</controls>
</window>

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB