mirror of
https://github.com/robweber/xbmcbackup.git
synced 2026-05-26 01:01:22 +02:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a9f65318f | |||
| 6adceb3060 | |||
| eb52649eb8 | |||
| 1dcc302ddd | |||
| 1559df4d4c | |||
| dd2ddb50be | |||
| 4f00c1d983 | |||
| ca7f6e89c7 | |||
| 7e820c9ad7 | |||
| 324f3a46cf | |||
| d1907baf38 | |||
| a32e6f2656 | |||
| ca10bf7307 | |||
| dc88c2615a | |||
| 090842b03c | |||
| f6fae17c19 | |||
| 780f0f8401 |
@@ -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="Backup" version="1.7.0~beta2" provider-name="robweber">
|
name="Backup" version="1.7.2" provider-name="robweber">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="3.0.0"/>
|
<import addon="xbmc.python" version="3.0.0"/>
|
||||||
<import addon="script.module.dateutil" version="2.8.0" />
|
<import addon="script.module.dateutil" version="2.8.0" />
|
||||||
@@ -24,11 +24,9 @@
|
|||||||
<screenshot>resources/images/screenshot3.jpg</screenshot>
|
<screenshot>resources/images/screenshot3.jpg</screenshot>
|
||||||
<screenshot>resources/images/screenshot4.jpg</screenshot>
|
<screenshot>resources/images/screenshot4.jpg</screenshot>
|
||||||
</assets>
|
</assets>
|
||||||
<news>Version 1.7.0
|
<news>Version 1.7.2
|
||||||
Can add suffix to backup folder names
|
Fix bug with gui settings restore
|
||||||
translations sync
|
strptime Python bugfix
|
||||||
Minor UI fixes
|
|
||||||
Fixed Dropbox tokens expiring by using refresh tokens
|
|
||||||
</news>
|
</news>
|
||||||
<summary lang="ar_SA">إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع</summary>
|
<summary lang="ar_SA">إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع</summary>
|
||||||
<summary lang="bg_BG">Добавката може да създава резервни копия и възстановява базата данни и настройките на Kodi, в случай на срив или повреда на файловете.</summary>
|
<summary lang="bg_BG">Добавката може да създава резервни копия и възстановява базата данни и настройките на Kodi, в случай на срив или повреда на файловете.</summary>
|
||||||
|
|||||||
+25
-1
@@ -4,12 +4,35 @@ 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/)
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
## Unreleased
|
## [Version 1.7.2](https://github.com/robweber/xbmcbackup/compare/matrix-1.7.1...robweber:matrix-1.7.2)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- fixed a bug with the GUI settings restore, the settings key was not identified properly. Thanks @alexhass
|
||||||
|
- implemented [suggested fix](https://kodi.wiki/view/Python_Problems#datetime.strptime) for strptime Python bug
|
||||||
|
|
||||||
|
## [Version 1.7.1](https://github.com/robweber/xbmcbackup/compare/matrix-1.7.0...robweber:matrix-1.7.1)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `utils.getSettingStripped` to trim whitespace around setting strings
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Dropbox Secret and App Key are trimmed on loading - thanks @rjclark99
|
||||||
|
- added additional dialog information when gathering files for a Restore. This doesn't fix the speed which these happen since that is related to the platform but does provide more info that it's working.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- issue where Zip file restores were failing due to a missing `is_dir` attribute
|
||||||
|
|
||||||
|
## [Version 1.7.0](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.8...robweber:matrix-1.7.0)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- You can now append a suffix to the end of each backup name (folder or zip file). This is only available in the Advanced or Expert settings.
|
- You can now append a suffix to the end of each backup name (folder or zip file). This is only available in the Advanced or Expert settings.
|
||||||
- validation file now saves a list of all installed addons and versions
|
- validation file now saves a list of all installed addons and versions
|
||||||
|
- prompt to close Kodi at the end of successful restore
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@@ -17,6 +40,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|||||||
- modified GitHub issue template slightly
|
- modified GitHub issue template slightly
|
||||||
- translations sync
|
- translations sync
|
||||||
- token files are stored in a `.json` instead of a `.txt` file
|
- token files are stored in a `.json` instead of a `.txt` file
|
||||||
|
- file discovery process now flags directories with an `is_dir` metadata property instead of prefixing with a dash (-). This was done for legacy reasons and there is no reason for it.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ msgid "Restart Kodi"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30078"
|
msgctxt "#30078"
|
||||||
msgid "You should restart Kodi to continue"
|
msgid "A restart is recommended, select Yes to close Kodi"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30079"
|
msgctxt "#30079"
|
||||||
@@ -629,3 +629,11 @@ msgstr ""
|
|||||||
msgctxt "#30161"
|
msgctxt "#30161"
|
||||||
msgid "Amend a string to the end of each backup folder or ZIP file"
|
msgid "Amend a string to the end of each backup folder or ZIP file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30162"
|
||||||
|
msgid "this could take some time"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30163"
|
||||||
|
msgid "Current folder"
|
||||||
|
msgstr ""
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import xbmcgui
|
|||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
import json
|
import json
|
||||||
import pyqrcode
|
import pyqrcode
|
||||||
|
import time
|
||||||
import resources.lib.tinyurl as tinyurl
|
import resources.lib.tinyurl as tinyurl
|
||||||
import resources.lib.utils as utils
|
import resources.lib.utils as utils
|
||||||
from datetime import datetime
|
import datetime
|
||||||
|
|
||||||
# don't die on import error yet, these might not even get used
|
# don't die on import error yet, these might not even get used
|
||||||
try:
|
try:
|
||||||
@@ -13,6 +14,14 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# fix for datetime.strptime bug https://kodi.wiki/view/Python_Problems#datetime.strptime
|
||||||
|
class proxydt(datetime.datetime):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def strptime(cls, date_string, format):
|
||||||
|
return datetime.datetime(*(time.strptime(date_string, format)[:6]))
|
||||||
|
|
||||||
|
datetime.datetime = proxydt
|
||||||
|
|
||||||
class QRCode(xbmcgui.WindowXMLDialog):
|
class QRCode(xbmcgui.WindowXMLDialog):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -44,8 +53,8 @@ class DropboxAuthorizer:
|
|||||||
APP_SECRET = ""
|
APP_SECRET = ""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.APP_KEY = utils.getSetting('dropbox_key')
|
self.APP_KEY = utils.getSettingStringStripped('dropbox_key')
|
||||||
self.APP_SECRET = utils.getSetting('dropbox_secret')
|
self.APP_SECRET = utils.getSettingStringStripped('dropbox_secret')
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
result = True
|
result = True
|
||||||
@@ -145,7 +154,7 @@ class DropboxAuthorizer:
|
|||||||
if(token.strip() != ""):
|
if(token.strip() != ""):
|
||||||
result = json.loads(token)
|
result = json.loads(token)
|
||||||
# convert expiration back to a datetime object
|
# convert expiration back to a datetime object
|
||||||
result['expiration'] = datetime.strptime(result['expiration'], "%Y-%m-%d %H:%M:%S.%f")
|
result['expiration'] = datetime.datetime.strptime(result['expiration'], "%Y-%m-%d %H:%M:%S.%f")
|
||||||
|
|
||||||
token_file.close()
|
token_file.close()
|
||||||
|
|
||||||
|
|||||||
+17
-8
@@ -228,7 +228,7 @@ class XbmcBackup:
|
|||||||
# copy just this file from the remote vfs
|
# copy just this file from the remote vfs
|
||||||
self.transferSize = self.remote_vfs.fileSize(self.remote_base_path + self.restore_point)
|
self.transferSize = self.remote_vfs.fileSize(self.remote_base_path + self.restore_point)
|
||||||
zipFile = []
|
zipFile = []
|
||||||
zipFile.append({'file': self.remote_base_path + self.restore_point, 'size': self.transferSize})
|
zipFile.append({'file': self.remote_base_path + self.restore_point, 'size': self.transferSize, 'is_dir': False})
|
||||||
|
|
||||||
# set transfer size
|
# set transfer size
|
||||||
self.transferLeft = self.transferSize
|
self.transferLeft = self.transferSize
|
||||||
@@ -285,7 +285,9 @@ class XbmcBackup:
|
|||||||
self._createResumeBackupFile()
|
self._createResumeBackupFile()
|
||||||
|
|
||||||
# do not continue running
|
# do not continue running
|
||||||
xbmcgui.Dialog().ok(utils.getString(30077), utils.getString(30078))
|
if(xbmcgui.Dialog().yesno(utils.getString(30077), utils.getString(30078), autoclose=15000)):
|
||||||
|
xbmc.executebuiltin('Quit')
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# check if settings should be restored from this backup
|
# check if settings should be restored from this backup
|
||||||
@@ -314,6 +316,7 @@ class XbmcBackup:
|
|||||||
self.xbmc_vfs.set_root(xbmcvfs.translatePath(aDir['path']))
|
self.xbmc_vfs.set_root(xbmcvfs.translatePath(aDir['path']))
|
||||||
if(self.remote_vfs.exists(self.remote_vfs.root_path + aDir['name'] + '/')):
|
if(self.remote_vfs.exists(self.remote_vfs.root_path + aDir['name'] + '/')):
|
||||||
# walk the directory
|
# walk the directory
|
||||||
|
self.progressBar.updateProgress(0, f"{utils.getString(30049)}....{utils.getString(30162)}\n{utils.getString(30163)}: {aDir['name']}")
|
||||||
fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/')
|
fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/')
|
||||||
self.transferSize = self.transferSize + fileManager.fileSize()
|
self.transferSize = self.transferSize + fileManager.fileSize()
|
||||||
|
|
||||||
@@ -347,6 +350,11 @@ class XbmcBackup:
|
|||||||
# call update addons to refresh everything
|
# call update addons to refresh everything
|
||||||
xbmc.executebuiltin('UpdateLocalAddons')
|
xbmc.executebuiltin('UpdateLocalAddons')
|
||||||
|
|
||||||
|
# notify user that restart is recommended
|
||||||
|
if(xbmcgui.Dialog().yesno(utils.getString(30077), utils.getString(30078), autoclose=15000)):
|
||||||
|
xbmc.executebuiltin('Quit')
|
||||||
|
|
||||||
|
|
||||||
def _setupVFS(self, mode=-1, progressOverride=False):
|
def _setupVFS(self, mode=-1, progressOverride=False):
|
||||||
# set windows setting to true
|
# set windows setting to true
|
||||||
window = xbmcgui.Window(10000)
|
window = xbmcgui.Window(10000)
|
||||||
@@ -414,9 +422,9 @@ class XbmcBackup:
|
|||||||
if(utils.getSettingBool('verbose_logging')):
|
if(utils.getSettingBool('verbose_logging')):
|
||||||
utils.log('Writing file: ' + aFile['file'])
|
utils.log('Writing file: ' + aFile['file'])
|
||||||
|
|
||||||
if(aFile['file'].startswith("-")):
|
if(aFile['is_dir']):
|
||||||
self._updateProgress('%s remaining\nwriting %s' % (utils.diskString(self.transferLeft), os.path.basename(aFile['file'][len(source.root_path):]) + "/"))
|
self._updateProgress('%s remaining\nwriting %s' % (utils.diskString(self.transferLeft), os.path.basename(aFile['file'][len(source.root_path):]) + "/"))
|
||||||
dest.mkdir(dest.root_path + aFile['file'][len(source.root_path) + 1:])
|
dest.mkdir(dest.root_path + aFile['file'][len(source.root_path):])
|
||||||
else:
|
else:
|
||||||
self._updateProgress('%s remaining\nwriting %s' % (utils.diskString(self.transferLeft), os.path.basename(aFile['file'][len(source.root_path):])))
|
self._updateProgress('%s remaining\nwriting %s' % (utils.diskString(self.transferLeft), os.path.basename(aFile['file'][len(source.root_path):])))
|
||||||
self.transferLeft = self.transferLeft - aFile['size']
|
self.transferLeft = self.transferLeft - aFile['size']
|
||||||
@@ -426,6 +434,7 @@ class XbmcBackup:
|
|||||||
|
|
||||||
# if result is still true but this file failed
|
# if result is still true but this file failed
|
||||||
if(not wroteFile and result):
|
if(not wroteFile and result):
|
||||||
|
utils.log("Failed to write " + aFile['file'])
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -583,7 +592,7 @@ class FileManager:
|
|||||||
def walk(self):
|
def walk(self):
|
||||||
|
|
||||||
for aDir in self.root_dirs:
|
for aDir in self.root_dirs:
|
||||||
self.addFile('-' + xbmcvfs.translatePath(aDir['path']))
|
self.addFile(xbmcvfs.translatePath(aDir['path']), True)
|
||||||
self.walkTree(xbmcvfs.translatePath(aDir['path']), aDir['recurse'])
|
self.walkTree(xbmcvfs.translatePath(aDir['path']), aDir['recurse'])
|
||||||
|
|
||||||
def walkTree(self, directory, recurse=True):
|
def walkTree(self, directory, recurse=True):
|
||||||
@@ -605,7 +614,7 @@ class FileManager:
|
|||||||
# check if directory is excluded
|
# check if directory is excluded
|
||||||
if(not any(dirPath.startswith(exDir) for exDir in self.exclude_dir)):
|
if(not any(dirPath.startswith(exDir) for exDir in self.exclude_dir)):
|
||||||
|
|
||||||
self.addFile("-" + dirPath)
|
self.addFile(dirPath, True)
|
||||||
|
|
||||||
# catch for "non directory" type files
|
# catch for "non directory" type files
|
||||||
shouldWalk = True
|
shouldWalk = True
|
||||||
@@ -628,7 +637,7 @@ class FileManager:
|
|||||||
else:
|
else:
|
||||||
self.excludeFile(xbmcvfs.translatePath(dirMeta['path']))
|
self.excludeFile(xbmcvfs.translatePath(dirMeta['path']))
|
||||||
|
|
||||||
def addFile(self, filename):
|
def addFile(self, filename, is_dir = False):
|
||||||
# write the full remote path name of this file
|
# write the full remote path name of this file
|
||||||
if(utils.getSettingBool('verbose_logging')):
|
if(utils.getSettingBool('verbose_logging')):
|
||||||
utils.log("Add File: " + filename)
|
utils.log("Add File: " + filename)
|
||||||
@@ -637,7 +646,7 @@ class FileManager:
|
|||||||
fSize = self.vfs.fileSize(filename)
|
fSize = self.vfs.fileSize(filename)
|
||||||
self.totalSize = self.totalSize + fSize
|
self.totalSize = self.totalSize + fSize
|
||||||
|
|
||||||
self.fileArray.append({'file': filename, 'size': fSize})
|
self.fileArray.append({'file': filename, 'size': fSize, 'is_dir': is_dir})
|
||||||
|
|
||||||
def excludeFile(self, filename):
|
def excludeFile(self, filename):
|
||||||
# remove trailing slash
|
# remove trailing slash
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class GuiSettingsManager:
|
|||||||
restoreCount = 0
|
restoreCount = 0
|
||||||
for aSetting in restoreSettings:
|
for aSetting in restoreSettings:
|
||||||
# Ensure key exists before referencing
|
# Ensure key exists before referencing
|
||||||
if(aSetting['id'] in settingsDict.values()):
|
if(aSetting['id'] in settingsDict.keys()):
|
||||||
# only update a setting if its different than the current (action types have no value)
|
# 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(aSetting['type'] != 'action' and settingsDict[aSetting['id']] != aSetting['value']):
|
||||||
if(utils.getSettingBool('verbose_logging')):
|
if(utils.getSettingBool('verbose_logging')):
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ def showNotification(message):
|
|||||||
def getSetting(name):
|
def getSetting(name):
|
||||||
return __Addon.getSetting(name)
|
return __Addon.getSetting(name)
|
||||||
|
|
||||||
|
def getSettingStringStripped(name):
|
||||||
|
return __Addon.getSettingString(name).strip()
|
||||||
|
|
||||||
def getSettingBool(name):
|
def getSettingBool(name):
|
||||||
return bool(__Addon.getSettingBool(name))
|
return bool(__Addon.getSettingBool(name))
|
||||||
|
|||||||
Reference in New Issue
Block a user