mirror of
https://github.com/robweber/xbmcbackup.git
synced 2024-11-14 20:35:48 +01:00
Merge branch 'master' into google_drive
Conflicts: resources/language/English/strings.xml resources/lib/backup.py
This commit is contained in:
commit
8473542aaf
13
README.txt
13
README.txt
@ -14,6 +14,17 @@ On the Backup Selection page you can select which items from your user profile f
|
|||||||
|
|
||||||
You can also define non-XBMC directories on your device. See "Custom Directories" for more information on how these are handled.
|
You can also define non-XBMC directories on your device. See "Custom Directories" for more information on how these are handled.
|
||||||
|
|
||||||
|
Restores:
|
||||||
|
|
||||||
|
During the restore process there are a few checks and post-run procedures to know about.
|
||||||
|
|
||||||
|
The first is a version check. If you are restoring to a different version of XBMC than the one used to create the backup archive you'll get a warning. In most cases it is OK to proceed, just know that some specific items like addons and database files may not work correctly.
|
||||||
|
|
||||||
|
The next check is for an advancedsettings.xml file. If you've created this file and it exists in your restore archive you'll be asked to reboot XBMC. This is so that the file can be loaded and used for any special settings, mainly path substitutions, you may have had that would affect the rest of the restore. XBMC Backup will prompt you to continue the restore process when you reboot the program.
|
||||||
|
|
||||||
|
The last bit of post-processing is done after all the backup files have been restored. If you have restored your configuration files the addon will attempt to restore any system specific settings that it can from the guisettings.xml file. This is done by comparing the restored file with settings via the JSONPRC Settings.SetSettingValue method. Only system specific settings can be restored so you will get any custom views or skin specific settings back. See the FAQ for how to restore these.
|
||||||
|
|
||||||
|
|
||||||
Scheduling:
|
Scheduling:
|
||||||
|
|
||||||
You can schedule backups to be completed on a set interval via the scheduling area. When it is time for the backup to run it will be executed in the background.
|
You can schedule backups to be completed on a set interval via the scheduling area. When it is time for the backup to run it will be executed in the background.
|
||||||
@ -84,7 +95,7 @@ If you've created restore points with an older version of the addon (pre 0.3.6)
|
|||||||
|
|
||||||
Several settings aren't being restored, this includes views, weather, etc. How do I get these back?
|
Several settings aren't being restored, this includes views, weather, etc. How do I get these back?
|
||||||
|
|
||||||
GUISETTINGS.xml is a configuration file used heavily by XBMC for remembering GUI specific settings. Due to the fact that XBMC reads this file on startup, and writes from memory to this file on shutdown; it is not possible to restore this file while XBMC is running. You must manually move this file from your backup archives if you wish to restore it. User SouthMark has posted the following steps for restoring in the OpenELEC system where this is more difficult:
|
GUISETTINGS.xml is a configuration file used heavily by XBMC for remembering GUI specific settings. Due to the fact that XBMC reads this file on startup, and writes from memory to this file on shutdown; it is not possible to restore this file while XBMC is running. This addon attempts to restore what settings it can via the JSONRPC interface, however you will still most likely be missing your specific skin settings and view settings. To get these back you must manually move this file from your backup archives if you wish to restore it. User SouthMark has posted the following steps for restoring in the OpenELEC system where this is more difficult:
|
||||||
|
|
||||||
1. Run the restore of your backup
|
1. Run the restore of your backup
|
||||||
2. SSH using putty to the IP Address of your media centre username: root Password openelec
|
2. SSH using putty to the IP Address of your media centre username: root Password openelec
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="script.xbmcbackup"
|
<addon id="script.xbmcbackup"
|
||||||
name="XBMC Backup" version="0.5.8.5" provider-name="robweber">
|
name="XBMC Backup" version="0.5.8.7" provider-name="robweber">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.14.0"/>
|
<import addon="xbmc.python" version="2.14.0"/>
|
||||||
<import addon="script.module.googleapi" version="0.0.1" />
|
<import addon="script.module.googleapi" version="0.0.1" />
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
Version 0.5.8.7
|
||||||
|
|
||||||
|
allow limited updating of guisettings file through json
|
||||||
|
|
||||||
|
Version 0.5.8.6
|
||||||
|
|
||||||
|
show notification if some files failed
|
||||||
|
check if destination is writeable - thanks war59312
|
||||||
|
|
||||||
Version 0.5.8.5
|
Version 0.5.8.5
|
||||||
|
|
||||||
added custom library nodes to config backup options - thanks Ned Scott
|
added custom library nodes to config backup options - thanks Ned Scott
|
||||||
|
@ -80,5 +80,9 @@
|
|||||||
<string id="30086">This version of XBMC is different than the one used to create the archive</string>
|
<string id="30086">This version of XBMC is different than the one used to create the archive</string>
|
||||||
<string id="30087">Compress Archives</string>
|
<string id="30087">Compress Archives</string>
|
||||||
<string id="30088">Copying Zip Archive</string>
|
<string id="30088">Copying Zip Archive</string>
|
||||||
<string id="30089">Google Drive</string>
|
<string id="30089">Write Error Detected</string>
|
||||||
|
<string id="30090">The destination may not be writeable</string>
|
||||||
|
<string id="30091">Zip archive could not be copied</string>
|
||||||
|
<string id="30092">Not all files were copied</string>
|
||||||
|
<string id="30093">Google Drive</string>
|
||||||
</strings>
|
</strings>
|
||||||
|
@ -5,6 +5,7 @@ import utils as utils
|
|||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
from vfs import XBMCFileSystem,DropboxFileSystem,ZipFileSystem,GoogleDriveFilesystem
|
from vfs import XBMCFileSystem,DropboxFileSystem,ZipFileSystem,GoogleDriveFilesystem
|
||||||
|
from resources.lib.guisettings import GuiSettingsManager
|
||||||
|
|
||||||
def folderSort(aKey):
|
def folderSort(aKey):
|
||||||
result = aKey[0]
|
result = aKey[0]
|
||||||
@ -156,7 +157,14 @@ class XbmcBackup:
|
|||||||
self.remote_vfs.mkdir(self.remote_vfs.root_path)
|
self.remote_vfs.mkdir(self.remote_vfs.root_path)
|
||||||
|
|
||||||
#create a validation file for backup rotation
|
#create a validation file for backup rotation
|
||||||
self._createValidationFile()
|
writeCheck = self._createValidationFile()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if(not shouldContinue):
|
||||||
|
return
|
||||||
|
|
||||||
utils.log(utils.getString(30051))
|
utils.log(utils.getString(30051))
|
||||||
allFiles = []
|
allFiles = []
|
||||||
@ -240,7 +248,11 @@ class XbmcBackup:
|
|||||||
for fileGroup in allFiles:
|
for fileGroup in allFiles:
|
||||||
self.xbmc_vfs.set_root(fileGroup['source'])
|
self.xbmc_vfs.set_root(fileGroup['source'])
|
||||||
self.remote_vfs.set_root(fileGroup['dest'])
|
self.remote_vfs.set_root(fileGroup['dest'])
|
||||||
self.backupFiles(fileGroup['files'],self.xbmc_vfs,self.remote_vfs)
|
filesCopied = self.backupFiles(fileGroup['files'],self.xbmc_vfs,self.remote_vfs)
|
||||||
|
|
||||||
|
if(not filesCopied):
|
||||||
|
utils.showNotification(utils.getString(30092))
|
||||||
|
utils.log(utils.getString(30092))
|
||||||
|
|
||||||
#reset remote and xbmc vfs
|
#reset remote and xbmc vfs
|
||||||
self.xbmc_vfs.set_root("special://home/")
|
self.xbmc_vfs.set_root("special://home/")
|
||||||
@ -258,8 +270,12 @@ class XbmcBackup:
|
|||||||
|
|
||||||
self.remote_vfs = self.saved_remote_vfs
|
self.remote_vfs = self.saved_remote_vfs
|
||||||
self.progressBar.updateProgress(98, utils.getString(30088))
|
self.progressBar.updateProgress(98, utils.getString(30088))
|
||||||
self.backupFiles(fileManager.getFiles(),self.xbmc_vfs, self.remote_vfs)
|
fileCopied = self.backupFiles(fileManager.getFiles(),self.xbmc_vfs, self.remote_vfs)
|
||||||
|
|
||||||
|
if(not fileCopied):
|
||||||
|
#zip archive copy filed, inform the user
|
||||||
|
shouldContinue = xbmcgui.Dialog().ok(utils.getString(30089),utils.getString(30090), utils.getString(30091))
|
||||||
|
|
||||||
#delete the temp zip file
|
#delete the temp zip file
|
||||||
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + zip_name))
|
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + zip_name))
|
||||||
|
|
||||||
@ -411,6 +427,11 @@ class XbmcBackup:
|
|||||||
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point))
|
self.xbmc_vfs.rmfile(xbmc.translatePath("special://temp/" + self.restore_point))
|
||||||
self.xbmc_vfs.rmdir(self.remote_vfs.root_path)
|
self.xbmc_vfs.rmdir(self.remote_vfs.root_path)
|
||||||
|
|
||||||
|
if(utils.getSetting("backup_config") == "true"):
|
||||||
|
#update the guisettings information (or what we can from it)
|
||||||
|
gui_settings = GuiSettingsManager('special://home/userdata/guisettings.xml')
|
||||||
|
gui_settings.run()
|
||||||
|
|
||||||
#call update addons to refresh everything
|
#call update addons to refresh everything
|
||||||
xbmc.executebuiltin('UpdateLocalAddons')
|
xbmc.executebuiltin('UpdateLocalAddons')
|
||||||
|
|
||||||
@ -422,6 +443,8 @@ class XbmcBackup:
|
|||||||
window.setProperty(utils.__addon_id__ + ".running","")
|
window.setProperty(utils.__addon_id__ + ".running","")
|
||||||
|
|
||||||
def backupFiles(self,fileList,source,dest):
|
def backupFiles(self,fileList,source,dest):
|
||||||
|
result = True
|
||||||
|
|
||||||
utils.log("Writing files to: " + dest.root_path)
|
utils.log("Writing files to: " + dest.root_path)
|
||||||
utils.log("Source: " + source.root_path)
|
utils.log("Source: " + source.root_path)
|
||||||
for aFile in fileList:
|
for aFile in fileList:
|
||||||
@ -432,12 +455,20 @@ class XbmcBackup:
|
|||||||
dest.mkdir(dest.root_path + aFile[len(source.root_path) + 1:])
|
dest.mkdir(dest.root_path + aFile[len(source.root_path) + 1:])
|
||||||
else:
|
else:
|
||||||
self._updateProgress()
|
self._updateProgress()
|
||||||
if(isinstance(source,DropboxFileSystem) or isinstance(source,GoogleDriveFilesystem)):
|
wroteFile = True
|
||||||
#if copying from cloud storage we need the file handle, use get_file
|
if(isinstance(source,DropboxFileSystem)):
|
||||||
source.get_file(aFile,dest.root_path + aFile[len(source.root_path):])
|
#if copying from dropbox we need the file handle, use get_file
|
||||||
|
wroteFile = source.get_file(aFile,dest.root_path + aFile[len(source.root_path):])
|
||||||
else:
|
else:
|
||||||
#copy using normal method
|
#copy using normal method
|
||||||
dest.put(aFile,dest.root_path + aFile[len(source.root_path):])
|
wroteFile = dest.put(aFile,dest.root_path + aFile[len(source.root_path):])
|
||||||
|
|
||||||
|
#if result is still true but this file failed
|
||||||
|
if(not wroteFile and result):
|
||||||
|
result = False
|
||||||
|
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def _createCRC(self,string):
|
def _createCRC(self,string):
|
||||||
#create hash from string
|
#create hash from string
|
||||||
@ -490,7 +521,9 @@ class XbmcBackup:
|
|||||||
vFile.write("")
|
vFile.write("")
|
||||||
vFile.close()
|
vFile.close()
|
||||||
|
|
||||||
self.remote_vfs.put(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"),self.remote_vfs.root_path + "xbmcbackup.val")
|
success = self.remote_vfs.put(xbmc.translatePath(utils.data_dir() + "xbmcbackup.val"),self.remote_vfs.root_path + "xbmcbackup.val")
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
def _checkValidationFile(self,path):
|
def _checkValidationFile(self,path):
|
||||||
result = False
|
result = False
|
||||||
|
96
resources/lib/guisettings.py
Normal file
96
resources/lib/guisettings.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import utils as utils
|
||||||
|
from xml.dom import minidom
|
||||||
|
from xml.parsers.expat import ExpatError
|
||||||
|
import json
|
||||||
|
import xbmc,xbmcvfs
|
||||||
|
|
||||||
|
|
||||||
|
class GuiSettingsManager:
|
||||||
|
doc = None
|
||||||
|
settings_allowed = list()
|
||||||
|
found_settings = list()
|
||||||
|
|
||||||
|
def __init__(self,settingsFile):
|
||||||
|
self._readFile(xbmc.translatePath(settingsFile))
|
||||||
|
|
||||||
|
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"}}'))
|
||||||
|
|
||||||
|
settings = json_response['result']['settings']
|
||||||
|
|
||||||
|
for aSetting in settings:
|
||||||
|
self.settings_allowed.append(aSetting['id'])
|
||||||
|
|
||||||
|
#parse the existing xml file and get all the settings
|
||||||
|
root_nodes = self.__parseNodes(self.doc.documentElement)
|
||||||
|
|
||||||
|
for aNode in root_nodes:
|
||||||
|
secondary_list = self.__parseNodes(self.doc.getElementsByTagName(aNode.name)[0])
|
||||||
|
|
||||||
|
for secondNode in secondary_list:
|
||||||
|
#if the node does not have children and is not default
|
||||||
|
if(not secondNode.hasChildren and not secondNode.isDefault):
|
||||||
|
|
||||||
|
if(secondNode.json_name() in self.settings_allowed):
|
||||||
|
self.found_settings.append(secondNode)
|
||||||
|
|
||||||
|
#go through all the found settings and update them
|
||||||
|
for aSetting in self.found_settings:
|
||||||
|
utils.log("updating: " + aSetting.json_name() + ", value: " + aSetting.value)
|
||||||
|
|
||||||
|
#check for boolean and numeric values
|
||||||
|
if(aSetting.value.isdigit() or (aSetting.value == 'true' or aSetting.value == 'false')):
|
||||||
|
xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"' + aSetting.json_name() + '","value":' + aSetting.value + '}}')
|
||||||
|
else:
|
||||||
|
xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"' + aSetting.json_name() + '","value":"' + aSetting.value + '"}}')
|
||||||
|
|
||||||
|
def __parseNodes(self,nodeList):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for node in nodeList.childNodes:
|
||||||
|
if(node.nodeType == self.doc.ELEMENT_NODE):
|
||||||
|
aSetting = SettingNode(node.nodeName)
|
||||||
|
|
||||||
|
#detect if there are any element nodes
|
||||||
|
if(len(node.childNodes) > 0):
|
||||||
|
for child_node in node.childNodes:
|
||||||
|
if(child_node.nodeType == self.doc.ELEMENT_NODE):
|
||||||
|
aSetting.hasChildren = True
|
||||||
|
|
||||||
|
if(not aSetting.hasChildren and len(node.childNodes) > 0):
|
||||||
|
aSetting.value = node.firstChild.nodeValue
|
||||||
|
|
||||||
|
if('default' not in node.attributes.keys()):
|
||||||
|
aSetting.isDefault = False
|
||||||
|
|
||||||
|
aSetting.parent = node.parentNode.nodeName
|
||||||
|
|
||||||
|
result.append(aSetting)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _readFile(self,fileLoc):
|
||||||
|
|
||||||
|
if(xbmcvfs.exists(fileLoc)):
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
self.doc = minidom.parse(fileLoc)
|
||||||
|
except ExpatError:
|
||||||
|
utils.log("Can't read " + fileLoc)
|
||||||
|
|
||||||
|
class SettingNode:
|
||||||
|
name = ''
|
||||||
|
value = ''
|
||||||
|
hasChildren = False
|
||||||
|
isDefault = True
|
||||||
|
parent = ''
|
||||||
|
|
||||||
|
def __init__(self,name):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def json_name(self):
|
||||||
|
return self.parent + "." + self.name
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user