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