From 71c8d9ae544eed4702715e517e06d140d7968b6f Mon Sep 17 00:00:00 2001
From: Rob <1572423+robweber@users.noreply.github.com>
Date: Thu, 3 Dec 2020 14:08:25 -0600
Subject: [PATCH] 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)
---
addon.xml | 7 +-
changelog.md | 36 +++++---
.../resource.language.en_gb/strings.po | 11 +++
resources/lib/backup.py | 55 +++++++-----
resources/lib/guisettings.py | 83 +++++++------------
resources/settings.xml | 1 +
6 files changed, 104 insertions(+), 89 deletions(-)
diff --git a/addon.xml b/addon.xml
index 96ec715..60581e2 100644
--- a/addon.xml
+++ b/addon.xml
@@ -7,9 +7,9 @@
-
- executable
-
+
+ executable
+
إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع
@@ -91,6 +91,7 @@
Version 1.6.4
- updated deprecated Kodi python methods
+ - added better system settings/restore functionality (enabled by default)
diff --git a/changelog.md b/changelog.md
index a6be2a3..b2a1900 100644
--- a/changelog.md
+++ b/changelog.md
@@ -6,11 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## [Unreleased](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.3...matrix)
+### 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
+### 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
@@ -55,7 +65,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
@@ -73,16 +83,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
@@ -92,7 +102,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
@@ -102,7 +112,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
@@ -114,7 +124,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)
@@ -125,10 +135,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
@@ -149,7 +159,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
@@ -289,7 +299,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
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index 9cc97da..9f1fa0b 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -581,3 +581,14 @@ 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 ""
diff --git a/resources/lib/backup.py b/resources/lib/backup.py
index fec25fd..bc45ed2 100644
--- a/resources/lib/backup.py
+++ b/resources/lib/backup.py
@@ -284,6 +284,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,6 +300,7 @@ 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:
@@ -318,6 +325,12 @@ 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'):
@@ -325,10 +338,6 @@ class XbmcBackup:
self.xbmc_vfs.rmfile(xbmcvfs.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()
-
# call update addons to refresh everything
xbmc.executebuiltin('UpdateLocalAddons')
@@ -404,14 +413,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,6 +422,18 @@ 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)
@@ -472,19 +487,24 @@ 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
+ # 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(xbmcvfs.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(xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup.val"))
@@ -495,7 +515,7 @@ class XbmcBackup:
nmFile = xbmcvfs.File(xbmcvfs.translatePath(utils.data_dir() + ".nomedia"), 'w')
nmFile.close()
- success = self.remote_vfs.put(xbmcvfs.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,10 +523,7 @@ class XbmcBackup:
result = None
# copy the file and open it
- if(isinstance(self.remote_vfs, DropboxFileSystem)):
- self.remote_vfs.get_file(path + "xbmcbackup.val", xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
- else:
- self.xbmc_vfs.put(path + "xbmcbackup.val", xbmcvfs.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(xbmcvfs.translatePath(utils.data_dir() + "xbmcbackup_restore.val"), 'r') as vFile:
jsonString = vFile.read()
diff --git a/resources/lib/guisettings.py b/resources/lib/guisettings.py
index 3af795e..50a4666 100644
--- a/resources/lib/guisettings.py
+++ b/resources/lib/guisettings.py
@@ -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(xbmcvfs.translatePath('special://home/userdata/guisettings.xml'), xbmcvfs.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(xbmcvfs.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)
diff --git a/resources/settings.xml b/resources/settings.xml
index 7e19f0c..18ba87d 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -3,6 +3,7 @@
+