mirror of
https://github.com/robweber/xbmcbackup.git
synced 2024-12-22 14:05:23 +01:00
Merge remote-tracking branch 'remotes/origin/frodo-dev'
Conflicts: addon.xml changelog.txt
This commit is contained in:
commit
738033897f
@ -5,7 +5,7 @@ I've had to recover my database, thumbnails, and source configuration enough tim
|
||||
|
||||
Usage:
|
||||
|
||||
In the addon settings you can define a remote path for the destination of your xbmc files. Each backup will create a folder named in a month, day, year format so you can create multiple backups.
|
||||
In the addon settings you can define a remote path for the destination of your xbmc files. Each backup will create a folder named in a month, day, year format so you can create multiple backups. You can keep a set number of backups by setting the integer value of the Backups to Keep setting greater than 0.
|
||||
|
||||
On the Backup Selection page you can select which items from your user profile folder will be sent to the backup location. By default all are turned on except the Addon Data directory.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="script.xbmcbackup"
|
||||
name="XBMC Backup" version="0.1.7" provider-name="robweber">
|
||||
name="XBMC Backup" version="0.2.2" provider-name="robweber">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.0"/>
|
||||
</requires>
|
||||
@ -31,6 +31,7 @@
|
||||
<description lang="sk">Už ste niekedy poškodili konfiguráciu XBMC a priali si mať zálohu? Teraz môžete - na jeden klik. Môžete exportovať Vašu databázu, playlist, náhľady, doplnky a konfigurácie na ktorýkoľvek zdroj zapisovateľný XBMC. Zálohy môžu byť púšťané na požiadanie alebo plánovačom. </description>
|
||||
<description lang="sv">Har du någonsin tappat bort din XBMC konfiguration och önskat att du hade en backup? Nu kan du enkelt med ett klick. Du kan exportera din databas, spellista, minityrer, tillägg och andra konfigurationsdetaljer till valfri källa som är skrivbar för XBMC. Backupper kan köras på begäran eller via scheman.</description>
|
||||
<platform>all</platform>
|
||||
<language></language>
|
||||
</extension>
|
||||
</addon>
|
||||
|
||||
|
@ -1,3 +1,17 @@
|
||||
[b]Version 0.2.2[/b]
|
||||
|
||||
fix for backup rotation sort
|
||||
|
||||
[b]Version 0.2.1[/b]
|
||||
|
||||
added ability to rotate backups, keeping a set number of days
|
||||
|
||||
[b]Version 0.2.0[/b]
|
||||
|
||||
removed the vfs.py helper library
|
||||
|
||||
default.py file now uses xbmcvfs python library exclusively for listing directories and copy operations
|
||||
|
||||
[b]Version 0.1.7[/b]
|
||||
|
||||
minor bug fixes and translations updates
|
||||
@ -76,9 +90,3 @@ Added progress bar and "silent" option for running on startup or as a script
|
||||
|
||||
First version, should backup directories as needed
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
2
resources/.gitignore
vendored
Normal file
2
resources/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
*.pyo
|
@ -15,6 +15,7 @@
|
||||
<string id="30023">Mode</string>
|
||||
<string id="30024">Type Remote Path</string>
|
||||
<string id="30025">Remote Path Type</string>
|
||||
<string id="30026">Backups to keep (0 for all)</string>
|
||||
|
||||
<string id="30030">User Addons</string>
|
||||
<string id="30031">Addon Data</string>
|
||||
@ -32,6 +33,7 @@
|
||||
<string id="30051">Creating Files List</string>
|
||||
<string id="30052">Writing file</string>
|
||||
<string id="30053">Starting scheduled backup</string>
|
||||
<string id="30054">Removing backup</string>
|
||||
|
||||
<string id="30060">Enable Scheduler</string>
|
||||
<string id="30061">Schedule</string>
|
||||
|
2
resources/lib/.gitignore
vendored
Normal file
2
resources/lib/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
*.pyo
|
@ -1,6 +1,6 @@
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import vfs as vfs
|
||||
import xbmcvfs
|
||||
import utils as utils
|
||||
import os
|
||||
import time
|
||||
@ -9,6 +9,7 @@ class FileManager:
|
||||
walk_path = ''
|
||||
fileArray = None
|
||||
verbose_log = False
|
||||
not_dir = ['.zip','.xsp','.rar']
|
||||
|
||||
def __init__(self,path):
|
||||
self.walk_path = path
|
||||
@ -48,21 +49,28 @@ class FileManager:
|
||||
self.walkTree(self.walk_path + "userdata/peripheral_data")
|
||||
|
||||
#this part is an oddity
|
||||
configFiles = vfs.listdir(self.walk_path + "userdata/",extra_metadata=True)
|
||||
dirs,configFiles = xbmcvfs.listdir(self.walk_path + "userdata/")
|
||||
for aFile in configFiles:
|
||||
if(aFile['file'].endswith(".xml")):
|
||||
self.addFile(aFile['file'][len(self.walk_path):])
|
||||
if(aFile.endswith(".xml")):
|
||||
self.addFile("userdata/" + aFile)
|
||||
|
||||
def walkTree(self,directory):
|
||||
for (path, dirs, files) in vfs.walk(directory):
|
||||
dirs,files = xbmcvfs.listdir(directory)
|
||||
|
||||
#create all the subdirs first
|
||||
for aDir in dirs:
|
||||
dirPath = xbmc.translatePath(directory + "/" + aDir)
|
||||
file_ext = aDir.split('.')[-1]
|
||||
|
||||
self.addFile("-" + dirPath[len(self.walk_path):].decode("UTF-8"))
|
||||
#catch for "non directory" type files
|
||||
if (not any(file_ext in s for s in self.not_dir)):
|
||||
self.walkTree(dirPath)
|
||||
|
||||
#create all the subdirs first
|
||||
for aDir in dirs:
|
||||
self.addFile("-" + aDir[len(self.walk_path):])
|
||||
#copy all the files
|
||||
for aFile in files:
|
||||
filePath = aFile[len(self.walk_path):]
|
||||
self.addFile(filePath)
|
||||
#copy all the files
|
||||
for aFile in files:
|
||||
filePath = xbmc.translatePath(directory + "/" + aFile)
|
||||
self.addFile(filePath[len(self.walk_path):].decode("UTF-8"))
|
||||
|
||||
def addFile(self,filename):
|
||||
#write the full remote path name of this file
|
||||
@ -78,6 +86,7 @@ class XbmcBackup:
|
||||
Restore = 1
|
||||
|
||||
local_path = ''
|
||||
remote_root = ''
|
||||
remote_path = ''
|
||||
restoreFile = None
|
||||
|
||||
@ -92,17 +101,17 @@ class XbmcBackup:
|
||||
self.local_path = xbmc.makeLegalFilename(xbmc.translatePath("special://home"),False);
|
||||
|
||||
if(utils.getSetting('remote_selection') == '1'):
|
||||
self.remote_path = utils.getSetting('remote_path_2')
|
||||
self.remote_root = utils.getSetting('remote_path_2')
|
||||
utils.setSetting("remote_path","")
|
||||
elif(utils.getSetting('remote_selection') == '0'):
|
||||
self.remote_path = utils.getSetting("remote_path")
|
||||
self.remote_root = utils.getSetting("remote_path")
|
||||
|
||||
#fix slashes
|
||||
self.remote_path = self.remote_path.replace("\\","/")
|
||||
self.remote_root = self.remote_root.replace("\\","/")
|
||||
|
||||
#check if trailing slash is included
|
||||
if(self.remote_path[-1:] != "/"):
|
||||
self.remote_path = self.remote_path + "/"
|
||||
if(self.remote_root[-1:] != "/"):
|
||||
self.remote_root = self.remote_root + "/"
|
||||
|
||||
utils.log(utils.getString(30046))
|
||||
|
||||
@ -117,10 +126,10 @@ class XbmcBackup:
|
||||
mode = int(utils.getSetting('addon_mode'))
|
||||
|
||||
#append backup folder name
|
||||
if(mode == self.Backup and self.remote_path != ''):
|
||||
self.remote_path = self.remote_path + time.strftime("%Y%m%d") + "/"
|
||||
elif(mode == self.Restore and utils.getSetting("backup_name") != '' and self.remote_path != ''):
|
||||
self.remote_path = self.remote_path + utils.getSetting("backup_name") + "/"
|
||||
if(mode == self.Backup and self.remote_root != ''):
|
||||
self.remote_path = self.remote_root + time.strftime("%Y%m%d") + "/"
|
||||
elif(mode == self.Restore and utils.getSetting("backup_name") != '' and self.remote_root != ''):
|
||||
self.remote_path = self.remote_root + utils.getSetting("backup_name") + "/"
|
||||
else:
|
||||
self.remote_path = ""
|
||||
|
||||
@ -133,17 +142,37 @@ class XbmcBackup:
|
||||
self.fileManager = FileManager(self.local_path)
|
||||
|
||||
#for backups check if remote path exists
|
||||
if(vfs.exists(self.remote_path)):
|
||||
if(xbmcvfs.exists(self.remote_path)):
|
||||
#this will fail - need a disclaimer here
|
||||
utils.log(utils.getString(30050))
|
||||
|
||||
self.syncFiles()
|
||||
|
||||
#remove old backups
|
||||
total_backups = int(utils.getSetting('backup_rotation'))
|
||||
if(total_backups > 0):
|
||||
|
||||
dirs,files = xbmcvfs.listdir(self.remote_root)
|
||||
if(len(dirs) > total_backups):
|
||||
#remove backups to equal total wanted
|
||||
dirs.sort()
|
||||
remove_num = len(dirs) - total_backups - 1
|
||||
self.filesTotal = self.filesTotal + remove_num + 1
|
||||
|
||||
#update the progress bar if it is available
|
||||
while(remove_num >= 0 and not self.checkCancel()):
|
||||
self.updateProgress(utils.getString(30054) + " " + dirs[remove_num])
|
||||
utils.log("Removing backup " + dirs[remove_num])
|
||||
xbmcvfs.rmdir(self.remote_root + dirs[remove_num] + "/",True)
|
||||
remove_num = remove_num - 1
|
||||
|
||||
|
||||
else:
|
||||
utils.log(utils.getString(30023) + " - " + utils.getString(30017))
|
||||
self.fileManager = FileManager(self.remote_path)
|
||||
|
||||
#for restores remote path must exist
|
||||
if(vfs.exists(self.remote_path)):
|
||||
if(xbmcvfs.exists(self.remote_path)):
|
||||
self.restoreFiles()
|
||||
else:
|
||||
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045),self.remote_path)
|
||||
@ -154,7 +183,7 @@ class XbmcBackup:
|
||||
def syncFiles(self):
|
||||
|
||||
#make the remote directory
|
||||
vfs.mkdir(self.remote_path)
|
||||
xbmcvfs.mkdir(self.remote_path)
|
||||
|
||||
utils.log(utils.getString(30051))
|
||||
self.fileManager.createFileList()
|
||||
@ -187,9 +216,9 @@ class XbmcBackup:
|
||||
utils.log('Writing file: ' + source + aFile,xbmc.LOGDEBUG)
|
||||
self.updateProgress(aFile)
|
||||
if (aFile.startswith("-")):
|
||||
vfs.mkdir(xbmc.makeLegalFilename(dest + aFile[1:],False))
|
||||
xbmcvfs.mkdir(xbmc.makeLegalFilename(dest + aFile[1:],False))
|
||||
else:
|
||||
vfs.copy(xbmc.makeLegalFilename(source + aFile),xbmc.makeLegalFilename(dest + aFile,False))
|
||||
xbmcvfs.copy(xbmc.makeLegalFilename(source + aFile),xbmc.makeLegalFilename(dest + aFile,False))
|
||||
|
||||
def updateProgress(self,message=''):
|
||||
self.filesLeft = self.filesLeft - 1
|
||||
@ -207,4 +236,4 @@ class XbmcBackup:
|
||||
return result
|
||||
|
||||
def isReady(self):
|
||||
return True if self.remote_path != '' else False
|
||||
return True if self.remote_root != '' else False
|
||||
|
@ -1,256 +0,0 @@
|
||||
'''
|
||||
Convenience wrappers and extensions for some commonly used VFS functions
|
||||
in XBMC addons. This module exposes all the functionality of xbmcvfs plus
|
||||
some extra functions.
|
||||
|
||||
Copyright (C) 2012 Patrick Carey
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import json
|
||||
import os
|
||||
import xbmc
|
||||
import xbmcvfs
|
||||
import urllib
|
||||
|
||||
def walk(path):
|
||||
|
||||
'''
|
||||
Reimplementation of os.walk using XBMC's jsonrpc API.
|
||||
|
||||
This has the nice added benefits of being able to walk remote
|
||||
directories and inside compressed files such as rars/zips.
|
||||
'''
|
||||
|
||||
dir_tree = [[path]]
|
||||
|
||||
current_depth = 0
|
||||
|
||||
not_dir = ['.zip','.xsp','.rar']
|
||||
|
||||
while True:
|
||||
|
||||
if current_depth > -1:
|
||||
|
||||
try:
|
||||
|
||||
current_path = dir_tree[current_depth].pop(0)
|
||||
current_dirs, current_files = [], []
|
||||
|
||||
for x in listdir(current_path, extra_metadata=True):
|
||||
|
||||
file_ext = x['file'].split('.')[-1]
|
||||
|
||||
if x['filetype'] == 'directory' and not any(file_ext in s for s in not_dir):
|
||||
|
||||
current_dirs.append(urllib.unquote(x['file']))
|
||||
|
||||
else:
|
||||
|
||||
current_files.append(urllib.unquote(x['file']))
|
||||
|
||||
except IndexError:
|
||||
|
||||
current_depth -= 1
|
||||
|
||||
dir_tree.pop()
|
||||
|
||||
else:
|
||||
|
||||
yield (current_path, current_dirs, current_files)
|
||||
|
||||
if current_dirs:
|
||||
|
||||
current_depth += 1
|
||||
|
||||
dir_tree.append(current_dirs)
|
||||
|
||||
else:
|
||||
|
||||
break
|
||||
|
||||
|
||||
def listdir(path, extra_metadata=False):
|
||||
|
||||
'''
|
||||
Reimplementation of os.listdir using XBMC's jsonrpc API.
|
||||
|
||||
Returns a list of file/directory names from the specified path
|
||||
|
||||
Accepts an optional boolean 'extra_metadata' as the second argument
|
||||
which will cause the function to instead return a list of dictionaries
|
||||
containing all of the metadata about each file that was retrieved from
|
||||
XBMC.
|
||||
'''
|
||||
|
||||
fileList = []
|
||||
|
||||
json_response = xbmc.executeJSONRPC('{ "jsonrpc" : "2.0" , "method" : "Files.GetDirectory" , "params" : { "directory" : "%s" , "sort" : { "method" : "file" } } , "id" : 1 }' % path.encode('utf-8').replace('\\', '\\\\'))
|
||||
|
||||
jsonobject = json.loads(json_response)
|
||||
|
||||
if jsonobject.has_key('result') and jsonobject['result']['files']:
|
||||
|
||||
for item in jsonobject['result']['files']:
|
||||
|
||||
if extra_metadata:
|
||||
|
||||
fileList.append(item)
|
||||
|
||||
else:
|
||||
|
||||
fileList.append(item['file'])
|
||||
|
||||
return fileList
|
||||
|
||||
|
||||
def copy(source, destination):
|
||||
|
||||
"""
|
||||
copy(source, destination) -- Copy file to destination, returns true/false.
|
||||
|
||||
source : file to copy.
|
||||
destination : destination file
|
||||
|
||||
example:
|
||||
- success = vfs.copy(source, destination)
|
||||
"""
|
||||
|
||||
return xbmcvfs.copy(source, destination)
|
||||
|
||||
|
||||
def delete(path):
|
||||
|
||||
"""
|
||||
delete(file) -- Delete file
|
||||
|
||||
file : file to delete
|
||||
|
||||
example:
|
||||
- vfs.delete(file)
|
||||
"""
|
||||
|
||||
return xbmcvfs.delete(path)
|
||||
|
||||
|
||||
def exists(path):
|
||||
|
||||
"""
|
||||
exists(path) -- Check if file exists, returns true/false.
|
||||
|
||||
path : file or folder
|
||||
|
||||
example:
|
||||
- success = vfs.exists(path)
|
||||
"""
|
||||
|
||||
return xbmcvfs.exists(path)
|
||||
|
||||
|
||||
def mkdir(path):
|
||||
|
||||
"""
|
||||
mkdir(path) -- Create a folder.
|
||||
|
||||
path : folder
|
||||
|
||||
example:
|
||||
- success = vfs.mkdir(path)
|
||||
"""
|
||||
|
||||
return xbmcvfs.mkdir(path)
|
||||
|
||||
|
||||
def rename(source, target):
|
||||
|
||||
"""
|
||||
rename(file, newFileName) -- Rename file, returns true/false.
|
||||
|
||||
file : file to reaname
|
||||
newFileName : new filename, including the full path
|
||||
|
||||
example:
|
||||
- success = vfs.rename(file, newFileName)
|
||||
"""
|
||||
|
||||
return xbmcvfs.rename(source, target)
|
||||
|
||||
|
||||
def rmdir(path):
|
||||
|
||||
"""
|
||||
rmdir(path) -- Remove a folder.
|
||||
|
||||
path : folder
|
||||
|
||||
example:
|
||||
- success = vfs.rmdir(path)
|
||||
"""
|
||||
|
||||
return xbmcvfs.rmdir(path)
|
||||
|
||||
|
||||
def comparepathlists(list1, list2, fullpath=False):
|
||||
|
||||
"""
|
||||
comparepathlists(list1, list2) -- Compare two lists of paths
|
||||
|
||||
list1 : list, contains paths (local or remote, absolute or relative)
|
||||
list2 : list, contains paths (local or remote, absolute or relative)
|
||||
fullpath : boolean, set True to compare perform straight comparison of lists
|
||||
set False (default) to compare on filename portions of each list
|
||||
|
||||
returns: dictionary:
|
||||
common_items: list, contains paths of items common to both lists
|
||||
list1_items: list, contains paths of items found only in list1
|
||||
list2_items: list, contains paths of items found only in list2
|
||||
|
||||
example:
|
||||
- compare = comparepathlists(list1, list2)
|
||||
"""
|
||||
|
||||
# initialise dict to store results and temp data
|
||||
results = {}
|
||||
temp_data = {}
|
||||
|
||||
if fullpath:
|
||||
|
||||
temp_path = lambda x: x
|
||||
|
||||
else:
|
||||
|
||||
temp_path = lambda x: os.path.split(x)[1]
|
||||
|
||||
for path in list1:
|
||||
|
||||
temp_data['list1'].append(temp_path(path))
|
||||
|
||||
for path in list2:
|
||||
|
||||
temp_data['list2'].append(temp_path(path))
|
||||
|
||||
# get items not in list 2
|
||||
results['list1_items'] = []
|
||||
gen = (i for i, x in enumerate(temp_data['list1']) if not x in temp_data['list2'])
|
||||
for i in gen:
|
||||
results['list1_items'].append(list1[i])
|
||||
|
||||
# get items not in list 1
|
||||
results['list2_items'] = []
|
||||
gen = (i for i, x in enumerate(temp_data['list2']) if not x in temp_data['list1'])
|
||||
for i in gen:
|
||||
results['list2_items'].append(list2[i])
|
||||
|
||||
return results
|
@ -6,6 +6,7 @@
|
||||
<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="backup_name" type="text" label="30021" default="backup_date" visible="eq(-4,1)"/>
|
||||
<setting id="backup_rotation" type="number" label="30026" default="0" />
|
||||
<setting id="run_silent" type="bool" label="30022" default="false" />
|
||||
</category>
|
||||
<category id="selection" label="30012">
|
||||
|
Loading…
Reference in New Issue
Block a user