Compare commits
36 Commits
1.6.0_fixe
...
matrix-1.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18b7f338c7 | ||
|
|
92a9245bdc | ||
|
|
568c3758a4 | ||
|
|
af999f7d04 | ||
|
|
1264ab86b2 | ||
|
|
90c458d4fc | ||
|
|
0e6f5acfb5 | ||
|
|
c9dd381037 | ||
|
|
55b2ac83d4 | ||
|
|
0d14dd17c6 | ||
|
|
9fa354b467 | ||
|
|
006485b19e | ||
|
|
3c2f512ecf | ||
|
|
190b4fd86f | ||
|
|
9ecf706d63 | ||
|
|
05c53b7ed8 | ||
|
|
8bc73f2832 | ||
|
|
edc4a7b20f | ||
|
|
90b4aeeebe | ||
|
|
7ce9123e1f | ||
|
|
8bfef6692f | ||
|
|
e63560f0c4 | ||
|
|
51f2ef3973 | ||
|
|
04bac77690 | ||
|
|
b1f6d36d73 | ||
|
|
5d398836ba | ||
|
|
23a14d67c4 | ||
|
|
47fcb119f3 | ||
|
|
f9f49e3fe6 | ||
|
|
7c23c17e33 | ||
|
|
8d66fa6a9f | ||
|
|
5ee610a586 | ||
|
|
8c4465f552 | ||
|
|
3849a902ea | ||
|
|
4492ab593e | ||
|
|
0cc0684263 |
7
.gitattributes
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.github/ export-ignore
|
||||||
|
.settings/ export-ignore
|
||||||
|
.gitattributes export-ignore
|
||||||
|
.gitignore export-ignore
|
||||||
|
.travis.yml export-ignore
|
||||||
|
README.md export-ignore
|
||||||
|
changelog.md export-ignore
|
||||||
10
.travis.yml
@@ -3,7 +3,7 @@ language: python
|
|||||||
python: 3.7
|
python: 3.7
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install flake8 kodi-addon-checker
|
- pip install flake8 kodi-addon-checker git+https://github.com/romanvm/kodi-addon-submitter.git
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- git config core.quotepath false
|
- git config core.quotepath false
|
||||||
@@ -11,4 +11,10 @@ before_script:
|
|||||||
# command to run our tests
|
# command to run our tests
|
||||||
script:
|
script:
|
||||||
- flake8 ./ --statistics --show-source --builtins=sys --ignore=E501,E722 --exclude=croniter.py # check python structure against flake8 tests, ignore long lines
|
- flake8 ./ --statistics --show-source --builtins=sys --ignore=E501,E722 --exclude=croniter.py # check python structure against flake8 tests, ignore long lines
|
||||||
- kodi-addon-checker --branch=matrix --allow-folder-id-mismatch
|
- kodi-addon-checker --branch=matrix --allow-folder-id-mismatch
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
- provider: script
|
||||||
|
script: submit-addon -r repo-scripts -b matrix --push-branch script.xbmcbackup
|
||||||
|
on:
|
||||||
|
tags: true
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Backup Addon
|
# Backup Addon
|
||||||
 [](https://travis-ci.org/robweber/xbmcbackup) [](https://github.com/robweber/xbmcbackup/blob/master/LICENSE.txt) [](https://www.python.org/dev/peps/pep-0008/)
|
 [](https://travis-ci.org/robweber/xbmcbackup) [](https://github.com/robweber/xbmcbackup/blob/master/LICENSE.txt) [](https://www.python.org/dev/peps/pep-0008/)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
|
|||||||
27
addon.xml
@@ -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.6.0" provider-name="robweber">
|
name="Backup" version="1.6.3" 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" />
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<extension point="xbmc.python.script" library="default.py">
|
<extension point="xbmc.python.script" library="default.py">
|
||||||
<provides>executable</provides>
|
<provides>executable</provides>
|
||||||
</extension>
|
</extension>
|
||||||
<extension point="xbmc.service" library="scheduler.py" />
|
<extension point="xbmc.service" library="service.py" />
|
||||||
<extension point="xbmc.addon.metadata">
|
<extension point="xbmc.addon.metadata">
|
||||||
<summary lang="ar_SA">إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع</summary>
|
<summary lang="ar_SA">إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع</summary>
|
||||||
<summary lang="be_BY">Backup and restore your Kodi database and configuration files in the event of a crash or file corruption.</summary>
|
<summary lang="be_BY">Backup and restore your Kodi database and configuration files in the event of a crash or file corruption.</summary>
|
||||||
@@ -84,16 +84,19 @@
|
|||||||
<source>https://github.com/robweber/xbmcbackup</source>
|
<source>https://github.com/robweber/xbmcbackup</source>
|
||||||
<assets>
|
<assets>
|
||||||
<icon>resources/images/icon.png</icon>
|
<icon>resources/images/icon.png</icon>
|
||||||
<screenshot>resources/images/screenshot1.png</screenshot>
|
<screenshot>resources/images/screenshot1.jpg</screenshot>
|
||||||
<screenshot>resources/images/screenshot2.png</screenshot>
|
<screenshot>resources/images/screenshot2.jpg</screenshot>
|
||||||
<screenshot>resources/images/screenshot3.png</screenshot>
|
<screenshot>resources/images/screenshot3.jpg</screenshot>
|
||||||
<screenshot>resources/images/screenshot4.png</screenshot>
|
<screenshot>resources/images/screenshot4.jpg</screenshot>
|
||||||
</assets>
|
</assets>
|
||||||
<news>Version 1.6.0
|
<news>Version 1.6.3
|
||||||
- Backups/Restores now use the concept of a Set to define groups of related files. Restores can restore one set or all sets within a backup archive (no more all or nothing restores)
|
- fixed validatePath error (issue #166)
|
||||||
- Added a new Advanced Editor script for more dynamic included/excluded directories based on a JSON formatted file
|
Version 1.6.2
|
||||||
- Fixed guisettings restores
|
- replaced PNG screenshots with JPG
|
||||||
- Removed GoogleDrive support - Python 3 compatibility was an issue
|
Version 1.6.1
|
||||||
</news>
|
- added file transfer size to progress bar
|
||||||
|
- progress bar now based on transfer size, not total file count
|
||||||
|
- fixed rotate backups error - thanks @AnonTester
|
||||||
|
</news>
|
||||||
</extension>
|
</extension>
|
||||||
</addon>
|
</addon>
|
||||||
|
|||||||
32
changelog.md
@@ -4,6 +4,38 @@ 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/)
|
||||||
|
|
||||||
|
## [Version 1.6.3](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.2...robweber:matrix-1.6.3) - 2020-06-15
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- fixed validatePath error (issue #166) thanks (thanks @AnonTester)
|
||||||
|
|
||||||
|
## [Version 1.6.2](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.1...robweber:matrix-1.6.2) - 2019-04-09
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- changed PNG screenshots to JPG (per [#165](https://github.com/robweber/xbmcbackup/issues/165))
|
||||||
|
|
||||||
|
## [Version 1.6.1](https://github.com/robweber/xbmcbackup/compare/matrix-1.6.0...robweber:matrix-1.6.1) - 2019-12-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- added method to get size of a file from the VFS
|
||||||
|
- added total transfer size information to progress bar with appropriate precision (KB, MB, etc)
|
||||||
|
- show file size of zip files in the restore selection dialog
|
||||||
|
- added getSettingInt and getSettingBool to utils.py class
|
||||||
|
- added verbose logging setting and tied it to logging related to file paths added/written, this will significantly reduce the debug log size (thanks CastagnaIT)
|
||||||
|
- localize advanced editor strings instead of hard coding English
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- display every file transfered in progress bar, not just directory
|
||||||
|
- base progress bar percent on transfer size, not total files
|
||||||
|
- changed getSettings where needed to getSettingBool and getSettingInt
|
||||||
|
- use service.py to start scheduler, moving scheduler to resources/lib/scheduler.py Kodi doesn't cache files in the root directory
|
||||||
|
- fixed issues with rotating backups where trailing slash was missing (thanks @AnonTester)
|
||||||
|
- read/write files using contextlib
|
||||||
|
|
||||||
## [Version 1.6.0](https://github.com/robweber/xbmcbackup/compare/krypton-1.5.2...robweber:matrix-1.6.0) - 2019-11-26
|
## [Version 1.6.0](https://github.com/robweber/xbmcbackup/compare/krypton-1.5.2...robweber:matrix-1.6.0) - 2019-11-26
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ if(mode == -1):
|
|||||||
options = [utils.getString(30016), utils.getString(30017), utils.getString(30099)]
|
options = [utils.getString(30016), utils.getString(30017), utils.getString(30099)]
|
||||||
|
|
||||||
# find out if we're using the advanced editor
|
# find out if we're using the advanced editor
|
||||||
if(int(utils.getSetting('backup_selection_type')) == 1):
|
if(utils.getSettingInt('backup_selection_type') == 1):
|
||||||
options.append(utils.getString(30125))
|
options.append(utils.getString(30125))
|
||||||
|
|
||||||
# figure out if this is a backup or a restore from the user
|
# figure out if this is a backup or a restore from the user
|
||||||
@@ -52,7 +52,7 @@ if(mode != -1):
|
|||||||
if(mode == 2):
|
if(mode == 2):
|
||||||
# open the settings dialog
|
# open the settings dialog
|
||||||
utils.openSettings()
|
utils.openSettings()
|
||||||
elif(mode == 3 and int(utils.getSetting('backup_selection_type')) == 1):
|
elif(mode == 3 and utils.getSettingInt('backup_selection_type') == 1):
|
||||||
# open the advanced editor
|
# open the advanced editor
|
||||||
xbmc.executebuiltin('RunScript(special://home/addons/script.xbmcbackup/launcher.py, action=advanced_editor)')
|
xbmc.executebuiltin('RunScript(special://home/addons/script.xbmcbackup/launcher.py, action=advanced_editor)')
|
||||||
elif(backup.remoteConfigured()):
|
elif(backup.remoteConfigured()):
|
||||||
|
|||||||
2
resources/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
*.pyo
|
|
||||||
|
Before Width: | Height: | Size: 125 KiB |
BIN
resources/images/screenshot1.jpg
Normal file
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 129 KiB |
BIN
resources/images/screenshot2.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 270 KiB |
BIN
resources/images/screenshot3.jpg
Normal file
|
After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 150 KiB |
BIN
resources/images/screenshot4.jpg
Normal file
|
After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 150 KiB |
BIN
resources/images/screenshot5.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 102 KiB |
BIN
resources/images/screenshot6.jpg
Normal file
|
After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 76 KiB |
@@ -555,4 +555,29 @@ msgstr ""
|
|||||||
|
|
||||||
msgctxt "#30141"
|
msgctxt "#30141"
|
||||||
msgid "This will erase any current Advanced Editor settings"
|
msgid "This will erase any current Advanced Editor settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30142"
|
||||||
|
msgid "Enable Verbose Logging"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30143"
|
||||||
|
msgid "Exclude a specific folder from this backup set"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30144"
|
||||||
|
msgid "Include a specific folder to this backup set"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30145"
|
||||||
|
msgid "Type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30146"
|
||||||
|
msgid "Include Sub Folders"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30147"
|
||||||
|
msgid "Toggle Sub Folders"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -122,13 +122,13 @@ class AdvancedBackupEditor:
|
|||||||
rootPath = backupSet['root']
|
rootPath = backupSet['root']
|
||||||
|
|
||||||
while(optionSelected != -1):
|
while(optionSelected != -1):
|
||||||
options = [xbmcgui.ListItem(utils.getString(30120), "Exclude a specific folder from this backup set"), xbmcgui.ListItem(utils.getString(30135), "Include a specific folder to this backup set"), xbmcgui.ListItem(rootPath, utils.getString(30121))]
|
options = [xbmcgui.ListItem(utils.getString(30120), utils.getString(30143)), xbmcgui.ListItem(utils.getString(30135), utils.getString(30144)), xbmcgui.ListItem(rootPath, utils.getString(30121))]
|
||||||
|
|
||||||
for aDir in backupSet['dirs']:
|
for aDir in backupSet['dirs']:
|
||||||
if(aDir['type'] == 'exclude'):
|
if(aDir['type'] == 'exclude'):
|
||||||
options.append(xbmcgui.ListItem(self._cleanPath(rootPath, aDir['path']), "%s: %s" % ("Type", utils.getString(30129))))
|
options.append(xbmcgui.ListItem(self._cleanPath(rootPath, aDir['path']), "%s: %s" % (utils.getString(30145), utils.getString(30129))))
|
||||||
elif(aDir['type'] == 'include'):
|
elif(aDir['type'] == 'include'):
|
||||||
options.append(xbmcgui.ListItem(self._cleanPath(rootPath, aDir['path']), "%s: %s | %s: %s" % ("Type", utils.getString(30134), "Include Sub Folders", str(aDir['recurse']))))
|
options.append(xbmcgui.ListItem(self._cleanPath(rootPath, aDir['path']), "%s: %s | %s: %s" % (utils.getString(30145), utils.getString(30134), utils.getString(30146), str(aDir['recurse']))))
|
||||||
|
|
||||||
optionSelected = self.dialog.select(utils.getString(30122) + ' ' + name, options, useDetails=True)
|
optionSelected = self.dialog.select(utils.getString(30122) + ' ' + name, options, useDetails=True)
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ class AdvancedBackupEditor:
|
|||||||
|
|
||||||
cOptions = ['Delete']
|
cOptions = ['Delete']
|
||||||
if(backupSet['dirs'][optionSelected - 3]['type'] == 'include'):
|
if(backupSet['dirs'][optionSelected - 3]['type'] == 'include'):
|
||||||
cOptions.append('Toggle Sub Folders')
|
cOptions.append(utils.getString(30147))
|
||||||
|
|
||||||
contextOption = self.dialog.contextmenu(cOptions)
|
contextOption = self.dialog.contextmenu(cOptions)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import json
|
|||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
import os.path
|
||||||
from . import utils as utils
|
from . import utils as utils
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from . vfs import XBMCFileSystem, DropboxFileSystem, ZipFileSystem
|
from . vfs import XBMCFileSystem, DropboxFileSystem, ZipFileSystem
|
||||||
@@ -39,8 +40,8 @@ class XbmcBackup:
|
|||||||
|
|
||||||
# for the progress bar
|
# for the progress bar
|
||||||
progressBar = None
|
progressBar = None
|
||||||
filesLeft = 0
|
transferSize = 0
|
||||||
filesTotal = 1
|
transferLeft = 0
|
||||||
|
|
||||||
restore_point = None
|
restore_point = None
|
||||||
skip_advanced = False # if we should check for the existance of advancedsettings in the restore
|
skip_advanced = False # if we should check for the existance of advancedsettings in the restore
|
||||||
@@ -92,8 +93,8 @@ class XbmcBackup:
|
|||||||
|
|
||||||
if(file_ext == 'zip' and len(folderName) == 12 and folderName.isdigit()):
|
if(file_ext == 'zip' and len(folderName) == 12 and folderName.isdigit()):
|
||||||
|
|
||||||
# format the name according to regional settings
|
# format the name according to regional settings and display the file size
|
||||||
folderName = self._dateFormat(folderName)
|
folderName = "%s - %s" % (self._dateFormat(folderName), utils.diskString(self.remote_vfs.fileSize(self.remote_base_path + aFile)))
|
||||||
|
|
||||||
result.append((aFile, folderName))
|
result.append((aFile, folderName))
|
||||||
|
|
||||||
@@ -124,14 +125,14 @@ class XbmcBackup:
|
|||||||
utils.log('File Selection Type: ' + str(utils.getSetting('backup_selection_type')))
|
utils.log('File Selection Type: ' + str(utils.getSetting('backup_selection_type')))
|
||||||
allFiles = []
|
allFiles = []
|
||||||
|
|
||||||
if(int(utils.getSetting('backup_selection_type')) == 0):
|
if(utils.getSettingInt('backup_selection_type') == 0):
|
||||||
# read in a list of the directories to backup
|
# read in a list of the directories to backup
|
||||||
selectedDirs = self._readBackupConfig(utils.addon_dir() + "/resources/data/default_files.json")
|
selectedDirs = self._readBackupConfig(utils.addon_dir() + "/resources/data/default_files.json")
|
||||||
|
|
||||||
# simple mode - get file listings for all enabled directories
|
# simple mode - get file listings for all enabled directories
|
||||||
for aDir in self.simple_directory_list:
|
for aDir in self.simple_directory_list:
|
||||||
# if this dir enabled
|
# if this dir enabled
|
||||||
if(utils.getSetting('backup_' + aDir) == 'true'):
|
if(utils.getSettingBool('backup_' + aDir)):
|
||||||
# get a file listing and append it to the allfiles array
|
# get a file listing and append it to the allfiles array
|
||||||
allFiles.append(self._addBackupDir(aDir, selectedDirs[aDir]['root'], selectedDirs[aDir]['dirs']))
|
allFiles.append(self._addBackupDir(aDir, selectedDirs[aDir]['root'], selectedDirs[aDir]['dirs']))
|
||||||
else:
|
else:
|
||||||
@@ -162,7 +163,7 @@ class XbmcBackup:
|
|||||||
orig_base_path = self.remote_vfs.root_path
|
orig_base_path = self.remote_vfs.root_path
|
||||||
|
|
||||||
# backup all the files
|
# backup all the files
|
||||||
self.filesLeft = self.filesTotal
|
self.transferLeft = self.transferSize
|
||||||
for fileGroup in allFiles:
|
for fileGroup in allFiles:
|
||||||
self.xbmc_vfs.set_root(xbmc.translatePath(fileGroup['source']))
|
self.xbmc_vfs.set_root(xbmc.translatePath(fileGroup['source']))
|
||||||
self.remote_vfs.set_root(fileGroup['dest'] + fileGroup['name'])
|
self.remote_vfs.set_root(fileGroup['dest'] + fileGroup['name'])
|
||||||
@@ -176,7 +177,7 @@ class XbmcBackup:
|
|||||||
self.xbmc_vfs.set_root("special://home/")
|
self.xbmc_vfs.set_root("special://home/")
|
||||||
self.remote_vfs.set_root(orig_base_path)
|
self.remote_vfs.set_root(orig_base_path)
|
||||||
|
|
||||||
if(utils.getSetting("compress_backups") == 'true'):
|
if(utils.getSettingBool("compress_backups")):
|
||||||
fileManager = FileManager(self.xbmc_vfs)
|
fileManager = FileManager(self.xbmc_vfs)
|
||||||
|
|
||||||
# send the zip file to the real remote vfs
|
# send the zip file to the real remote vfs
|
||||||
@@ -185,11 +186,13 @@ class XbmcBackup:
|
|||||||
self.xbmc_vfs.rename(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"), xbmc.translatePath("special://temp/" + zip_name))
|
self.xbmc_vfs.rename(xbmc.translatePath("special://temp/xbmc_backup_temp.zip"), xbmc.translatePath("special://temp/" + zip_name))
|
||||||
fileManager.addFile(xbmc.translatePath("special://temp/" + zip_name))
|
fileManager.addFile(xbmc.translatePath("special://temp/" + zip_name))
|
||||||
|
|
||||||
# set root to data dir home
|
# set root to data dir home and reset remote
|
||||||
self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/"))
|
self.xbmc_vfs.set_root(xbmc.translatePath("special://temp/"))
|
||||||
|
|
||||||
self.remote_vfs = self.saved_remote_vfs
|
self.remote_vfs = self.saved_remote_vfs
|
||||||
self.progressBar.updateProgress(98, utils.getString(30088))
|
|
||||||
|
# update the amount to transfer
|
||||||
|
self.transferSize = fileManager.fileSize()
|
||||||
|
self.transferLeft = self.transferSize
|
||||||
fileCopied = self._copyFiles(fileManager.getFiles(), self.xbmc_vfs, self.remote_vfs)
|
fileCopied = self._copyFiles(fileManager.getFiles(), self.xbmc_vfs, self.remote_vfs)
|
||||||
|
|
||||||
if(not fileCopied):
|
if(not fileCopied):
|
||||||
@@ -221,9 +224,12 @@ class XbmcBackup:
|
|||||||
|
|
||||||
if(not self.xbmc_vfs.exists(xbmc.translatePath("special://temp/" + self.restore_point))):
|
if(not self.xbmc_vfs.exists(xbmc.translatePath("special://temp/" + self.restore_point))):
|
||||||
# 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)
|
||||||
zipFile = []
|
zipFile = []
|
||||||
zipFile.append(self.remote_base_path + self.restore_point)
|
zipFile.append({'file': self.remote_base_path + self.restore_point, 'size': self.transferSize})
|
||||||
|
|
||||||
|
# set transfer size
|
||||||
|
self.transferLeft = self.transferSize
|
||||||
self._copyFiles(zipFile, self.remote_vfs, self.xbmc_vfs)
|
self._copyFiles(zipFile, self.remote_vfs, self.xbmc_vfs)
|
||||||
else:
|
else:
|
||||||
utils.log("zip file exists already")
|
utils.log("zip file exists already")
|
||||||
@@ -298,14 +304,15 @@ class XbmcBackup:
|
|||||||
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
|
||||||
fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/')
|
fileManager.walkTree(self.remote_vfs.root_path + aDir['name'] + '/')
|
||||||
self.filesTotal = self.filesTotal + fileManager.size()
|
self.transferSize = self.transferSize + fileManager.fileSize()
|
||||||
|
|
||||||
allFiles.append({"source": self.remote_vfs.root_path + aDir['name'], "dest": self.xbmc_vfs.root_path, "files": fileManager.getFiles()})
|
allFiles.append({"source": self.remote_vfs.root_path + aDir['name'], "dest": self.xbmc_vfs.root_path, "files": fileManager.getFiles()})
|
||||||
else:
|
else:
|
||||||
utils.log("error path not found: " + self.remote_vfs.root_path + aDir['name'])
|
utils.log("error path not found: " + self.remote_vfs.root_path + aDir['name'])
|
||||||
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30045), self.remote_vfs.root_path + aDir['name'])
|
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30045), self.remote_vfs.root_path + aDir['name'])
|
||||||
|
|
||||||
# restore all the files
|
# restore all the files
|
||||||
self.filesLeft = self.filesTotal
|
self.transferLeft = self.transferSize
|
||||||
for fileGroup in allFiles:
|
for fileGroup in allFiles:
|
||||||
self.remote_vfs.set_root(fileGroup['source'])
|
self.remote_vfs.set_root(fileGroup['source'])
|
||||||
self.xbmc_vfs.set_root(fileGroup['dest'])
|
self.xbmc_vfs.set_root(fileGroup['dest'])
|
||||||
@@ -333,7 +340,7 @@ class XbmcBackup:
|
|||||||
# append backup folder name
|
# append backup folder name
|
||||||
progressBarTitle = utils.getString(30010) + " - "
|
progressBarTitle = utils.getString(30010) + " - "
|
||||||
if(mode == self.Backup and self.remote_vfs.root_path != ''):
|
if(mode == self.Backup and self.remote_vfs.root_path != ''):
|
||||||
if(utils.getSetting("compress_backups") == 'true'):
|
if(utils.getSettingBool("compress_backups")):
|
||||||
# delete old temp file
|
# delete old temp file
|
||||||
if(self.xbmc_vfs.exists(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
|
if(self.xbmc_vfs.exists(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
|
||||||
if(not self.xbmc_vfs.rmfile(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
|
if(not self.xbmc_vfs.rmfile(xbmc.translatePath('special://temp/xbmc_backup_temp.zip'))):
|
||||||
@@ -379,7 +386,7 @@ class XbmcBackup:
|
|||||||
result = True
|
result = True
|
||||||
|
|
||||||
utils.log("Source: " + source.root_path)
|
utils.log("Source: " + source.root_path)
|
||||||
utils.log("Desintation: " + dest.root_path)
|
utils.log("Destination: " + dest.root_path)
|
||||||
|
|
||||||
# make sure the dest folder exists - can cause write errors if the full path doesn't exist
|
# make sure the dest folder exists - can cause write errors if the full path doesn't exist
|
||||||
if(not dest.exists(dest.root_path)):
|
if(not dest.exists(dest.root_path)):
|
||||||
@@ -387,21 +394,24 @@ class XbmcBackup:
|
|||||||
|
|
||||||
for aFile in fileList:
|
for aFile in fileList:
|
||||||
if(not self.progressBar.checkCancel()):
|
if(not self.progressBar.checkCancel()):
|
||||||
utils.log('Writing file: ' + aFile, xbmc.LOGDEBUG)
|
if(utils.getSettingBool('verbose_logging')):
|
||||||
if(aFile.startswith("-")):
|
utils.log('Writing file: ' + aFile['file'])
|
||||||
self._updateProgress(aFile[len(source.root_path) + 1:])
|
|
||||||
dest.mkdir(dest.root_path + aFile[len(source.root_path) + 1:])
|
if(aFile['file'].startswith("-")):
|
||||||
|
self._updateProgress('%s remaining, writing %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:])
|
||||||
else:
|
else:
|
||||||
self._updateProgress()
|
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
|
wroteFile = True
|
||||||
destFile = dest.root_path + aFile[len(source.root_path):]
|
destFile = dest.root_path + aFile['file'][len(source.root_path):]
|
||||||
if(isinstance(source, DropboxFileSystem)):
|
if(isinstance(source, DropboxFileSystem)):
|
||||||
# if copying from cloud storage we need the file handle, use get_file
|
# if copying from cloud storage we need the file handle, use get_file
|
||||||
wroteFile = source.get_file(aFile, destFile)
|
wroteFile = source.get_file(aFile['file'], destFile)
|
||||||
else:
|
else:
|
||||||
# copy using normal method
|
# copy using normal method
|
||||||
wroteFile = dest.put(aFile, destFile)
|
wroteFile = dest.put(aFile['file'], destFile)
|
||||||
|
|
||||||
# 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):
|
||||||
@@ -419,8 +429,9 @@ class XbmcBackup:
|
|||||||
|
|
||||||
# walk all the root trees
|
# walk all the root trees
|
||||||
fileManager.walk()
|
fileManager.walk()
|
||||||
# update total files
|
|
||||||
self.filesTotal = self.filesTotal + fileManager.size()
|
# update total size
|
||||||
|
self.transferSize = self.transferSize + fileManager.fileSize()
|
||||||
|
|
||||||
return {"name": folder_name, "source": root_path, "dest": self.remote_vfs.root_path, "files": fileManager.getFiles()}
|
return {"name": folder_name, "source": root_path, "dest": self.remote_vfs.root_path, "files": fileManager.getFiles()}
|
||||||
|
|
||||||
@@ -434,11 +445,10 @@ class XbmcBackup:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _updateProgress(self, message=None):
|
def _updateProgress(self, message=None):
|
||||||
self.filesLeft = self.filesLeft - 1
|
self.progressBar.updateProgress(int((float(self.transferSize - self.transferLeft) / float(self.transferSize)) * 100), message)
|
||||||
self.progressBar.updateProgress(int((float(self.filesTotal - self.filesLeft) / float(self.filesTotal)) * 100), message)
|
|
||||||
|
|
||||||
def _rotateBackups(self):
|
def _rotateBackups(self):
|
||||||
total_backups = int(utils.getSetting('backup_rotation'))
|
total_backups = utils.getSettingInt('backup_rotation')
|
||||||
|
|
||||||
if(total_backups > 0):
|
if(total_backups > 0):
|
||||||
# get a list of valid backup folders
|
# get a list of valid backup folders
|
||||||
@@ -447,7 +457,6 @@ class XbmcBackup:
|
|||||||
if(len(dirs) > total_backups):
|
if(len(dirs) > total_backups):
|
||||||
# remove backups to equal total wanted
|
# remove backups to equal total wanted
|
||||||
remove_num = 0
|
remove_num = 0
|
||||||
self.filesTotal = self.filesTotal + remove_num + 1
|
|
||||||
|
|
||||||
# update the progress bar if it is available
|
# update the progress bar if it is available
|
||||||
while(remove_num < (len(dirs) - total_backups) and not self.progressBar.checkCancel()):
|
while(remove_num < (len(dirs) - total_backups) and not self.progressBar.checkCancel()):
|
||||||
@@ -456,9 +465,9 @@ class XbmcBackup:
|
|||||||
|
|
||||||
if(dirs[remove_num][0].split('.')[-1] == 'zip'):
|
if(dirs[remove_num][0].split('.')[-1] == 'zip'):
|
||||||
# this is a file, remove it that way
|
# this is a file, remove it that way
|
||||||
self.remote_vfs.rmfile(self.remote_base_path + dirs[remove_num][0])
|
self.remote_vfs.rmfile(self.remote_vfs.clean_path(self.remote_base_path) + dirs[remove_num][0])
|
||||||
else:
|
else:
|
||||||
self.remote_vfs.rmdir(self.remote_base_path + dirs[remove_num][0] + "/")
|
self.remote_vfs.rmdir(self.remote_vfs.clean_path(self.remote_base_path) + dirs[remove_num][0] + "/")
|
||||||
|
|
||||||
remove_num = remove_num + 1
|
remove_num = remove_num + 1
|
||||||
|
|
||||||
@@ -499,9 +508,8 @@ class XbmcBackup:
|
|||||||
else:
|
else:
|
||||||
self.xbmc_vfs.put(path + "xbmcbackup.val", xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
|
self.xbmc_vfs.put(path + "xbmcbackup.val", xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
|
||||||
|
|
||||||
vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"), 'r')
|
with xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"), 'r') as vFile:
|
||||||
jsonString = vFile.read()
|
jsonString = vFile.read()
|
||||||
vFile.close()
|
|
||||||
|
|
||||||
# delete after checking
|
# delete after checking
|
||||||
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
|
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
|
||||||
@@ -536,6 +544,7 @@ class FileManager:
|
|||||||
exclude_dir = []
|
exclude_dir = []
|
||||||
root_dirs = []
|
root_dirs = []
|
||||||
pathSep = '/'
|
pathSep = '/'
|
||||||
|
totalSize = 0
|
||||||
|
|
||||||
def __init__(self, vfs):
|
def __init__(self, vfs):
|
||||||
self.vfs = vfs
|
self.vfs = vfs
|
||||||
@@ -550,7 +559,9 @@ class FileManager:
|
|||||||
self.walkTree(xbmc.translatePath(aDir['path']), aDir['recurse'])
|
self.walkTree(xbmc.translatePath(aDir['path']), aDir['recurse'])
|
||||||
|
|
||||||
def walkTree(self, directory, recurse=True):
|
def walkTree(self, directory, recurse=True):
|
||||||
utils.log('walking ' + directory + ', recurse: ' + str(recurse))
|
if(utils.getSettingBool('verbose_logging')):
|
||||||
|
utils.log('walking ' + directory + ', recurse: ' + str(recurse))
|
||||||
|
|
||||||
if(directory[-1:] == '/' or directory[-1:] == '\\'):
|
if(directory[-1:] == '/' or directory[-1:] == '\\'):
|
||||||
directory = directory[:-1]
|
directory = directory[:-1]
|
||||||
|
|
||||||
@@ -560,7 +571,7 @@ class FileManager:
|
|||||||
if(recurse):
|
if(recurse):
|
||||||
# create all the subdirs first
|
# create all the subdirs first
|
||||||
for aDir in dirs:
|
for aDir in dirs:
|
||||||
dirPath = xbmc.validatePath(xbmc.translatePath(directory + self.pathSep + aDir))
|
dirPath = xbmcvfs.validatePath(xbmc.translatePath(directory + self.pathSep + aDir))
|
||||||
file_ext = aDir.split('.')[-1]
|
file_ext = aDir.split('.')[-1]
|
||||||
|
|
||||||
# check if directory is excluded
|
# check if directory is excluded
|
||||||
@@ -591,11 +602,16 @@ class FileManager:
|
|||||||
|
|
||||||
def addFile(self, filename):
|
def addFile(self, filename):
|
||||||
# write the full remote path name of this file
|
# write the full remote path name of this file
|
||||||
utils.log("Add File: " + filename)
|
if(utils.getSettingBool('verbose_logging')):
|
||||||
self.fileArray.append(filename)
|
utils.log("Add File: " + filename)
|
||||||
|
|
||||||
|
# get the file size
|
||||||
|
fSize = self.vfs.fileSize(filename)
|
||||||
|
self.totalSize = self.totalSize + fSize
|
||||||
|
|
||||||
|
self.fileArray.append({'file': filename, 'size': fSize})
|
||||||
|
|
||||||
def excludeFile(self, filename):
|
def excludeFile(self, filename):
|
||||||
|
|
||||||
# remove trailing slash
|
# remove trailing slash
|
||||||
if(filename[-1] == '/' or filename[-1] == '\\'):
|
if(filename[-1] == '/' or filename[-1] == '\\'):
|
||||||
filename = filename[:-1]
|
filename = filename[:-1]
|
||||||
@@ -609,8 +625,12 @@ class FileManager:
|
|||||||
self.fileArray = []
|
self.fileArray = []
|
||||||
self.root_dirs = []
|
self.root_dirs = []
|
||||||
self.exclude_dir = []
|
self.exclude_dir = []
|
||||||
|
self.totalSize = 0
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def size(self):
|
def totalFiles(self):
|
||||||
return len(self.fileArray)
|
return len(self.fileArray)
|
||||||
|
|
||||||
|
def fileSize(self):
|
||||||
|
return self.totalSize
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ class BackupProgressBar:
|
|||||||
self.override = progressOverride
|
self.override = progressOverride
|
||||||
|
|
||||||
# check if we should use the progress bar
|
# check if we should use the progress bar
|
||||||
if(int(utils.getSetting('progress_mode')) != 2):
|
if(utils.getSettingInt('progress_mode') != 2):
|
||||||
# check if background or normal
|
# check if background or normal
|
||||||
if(int(utils.getSetting('progress_mode')) == 0 and not self.override):
|
if(utils.getSettingInt('progress_mode') == 0 and not self.override):
|
||||||
self.mode = self.DIALOG
|
self.mode = self.DIALOG
|
||||||
self.progressBar = xbmcgui.DialogProgress()
|
self.progressBar = xbmcgui.DialogProgress()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,200 +1,194 @@
|
|||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
import resources.lib.utils as utils
|
from . import utils as utils
|
||||||
from resources.lib.croniter import croniter
|
from resources.lib.croniter import croniter
|
||||||
from resources.lib.backup import XbmcBackup
|
from resources.lib.backup import XbmcBackup
|
||||||
|
|
||||||
UPGRADE_INT = 2 # to keep track of any upgrade notifications
|
UPGRADE_INT = 2 # to keep track of any upgrade notifications
|
||||||
|
|
||||||
|
|
||||||
class BackupScheduler:
|
class BackupScheduler:
|
||||||
monitor = None
|
monitor = None
|
||||||
enabled = "false"
|
enabled = False
|
||||||
next_run = 0
|
next_run = 0
|
||||||
next_run_path = None
|
next_run_path = None
|
||||||
restore_point = None
|
restore_point = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.monitor = UpdateMonitor(update_method=self.settingsChanged)
|
self.monitor = UpdateMonitor(update_method=self.settingsChanged)
|
||||||
self.enabled = utils.getSetting("enable_scheduler")
|
self.enabled = utils.getSettingBool("enable_scheduler")
|
||||||
self.next_run_path = xbmc.translatePath(utils.data_dir()) + 'next_run.txt'
|
self.next_run_path = xbmc.translatePath(utils.data_dir()) + 'next_run.txt'
|
||||||
|
|
||||||
if(self.enabled == "true"):
|
if(self.enabled):
|
||||||
|
|
||||||
# sleep for 2 minutes so Kodi can start and time can update correctly
|
# sleep for 2 minutes so Kodi can start and time can update correctly
|
||||||
xbmc.Monitor().waitForAbort(120)
|
xbmc.Monitor().waitForAbort(120)
|
||||||
|
|
||||||
nr = 0
|
nr = 0
|
||||||
if(xbmcvfs.exists(self.next_run_path)):
|
if(xbmcvfs.exists(self.next_run_path)):
|
||||||
|
|
||||||
fh = xbmcvfs.File(self.next_run_path)
|
with xbmcvfs.File(self.next_run_path) as fh:
|
||||||
try:
|
try:
|
||||||
# check if we saved a run time from the last run
|
# check if we saved a run time from the last run
|
||||||
nr = float(fh.read())
|
nr = float(fh.read())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
nr = 0
|
nr = 0
|
||||||
|
|
||||||
fh.close()
|
# if we missed and the user wants to play catch-up
|
||||||
|
if(0 < nr <= time.time() and utils.getSettingBool('schedule_miss')):
|
||||||
# if we missed and the user wants to play catch-up
|
utils.log("scheduled backup was missed, doing it now...")
|
||||||
if(0 < nr <= time.time() and utils.getSetting('schedule_miss') == 'true'):
|
progress_mode = utils.getSettingInt('progress_mode')
|
||||||
utils.log("scheduled backup was missed, doing it now...")
|
|
||||||
progress_mode = int(utils.getSetting('progress_mode'))
|
if(progress_mode == 0):
|
||||||
|
progress_mode = 1 # Kodi just started, don't block it with a foreground progress bar
|
||||||
if(progress_mode == 0):
|
|
||||||
progress_mode = 1 # Kodi just started, don't block it with a foreground progress bar
|
self.doScheduledBackup(progress_mode)
|
||||||
|
|
||||||
self.doScheduledBackup(progress_mode)
|
self.setup()
|
||||||
|
|
||||||
self.setup()
|
def setup(self):
|
||||||
|
# scheduler was turned on, find next run time
|
||||||
def setup(self):
|
utils.log("scheduler enabled, finding next run time")
|
||||||
# scheduler was turned on, find next run time
|
self.findNextRun(time.time())
|
||||||
utils.log("scheduler enabled, finding next run time")
|
|
||||||
self.findNextRun(time.time())
|
def start(self):
|
||||||
|
|
||||||
def start(self):
|
# display upgrade messages if they exist
|
||||||
|
if(utils.getSettingInt('upgrade_notes') < UPGRADE_INT):
|
||||||
# display upgrade messages if they exist
|
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30132))
|
||||||
if(int(utils.getSetting('upgrade_notes')) < UPGRADE_INT):
|
utils.setSetting('upgrade_notes', str(UPGRADE_INT))
|
||||||
xbmcgui.Dialog().ok(utils.getString(30010), utils.getString(30132))
|
|
||||||
utils.setSetting('upgrade_notes', str(UPGRADE_INT))
|
# check if a backup should be resumed
|
||||||
|
resumeRestore = self._resumeCheck()
|
||||||
# check if a backup should be resumed
|
|
||||||
resumeRestore = self._resumeCheck()
|
if(resumeRestore):
|
||||||
|
restore = XbmcBackup()
|
||||||
if(resumeRestore):
|
restore.selectRestore(self.restore_point)
|
||||||
restore = XbmcBackup()
|
# skip the advanced settings check
|
||||||
restore.selectRestore(self.restore_point)
|
restore.skipAdvanced()
|
||||||
# skip the advanced settings check
|
restore.restore()
|
||||||
restore.skipAdvanced()
|
|
||||||
restore.restore()
|
while(not self.monitor.abortRequested()):
|
||||||
|
|
||||||
while(not self.monitor.abortRequested()):
|
if(self.enabled):
|
||||||
|
# scheduler is still on
|
||||||
if(self.enabled == "true"):
|
now = time.time()
|
||||||
# scheduler is still on
|
|
||||||
now = time.time()
|
if(self.next_run <= now):
|
||||||
|
progress_mode = utils.getSettingInt('progress_mode')
|
||||||
if(self.next_run <= now):
|
self.doScheduledBackup(progress_mode)
|
||||||
progress_mode = int(utils.getSetting('progress_mode'))
|
|
||||||
self.doScheduledBackup(progress_mode)
|
# check if we should shut the computer down
|
||||||
|
if(utils.getSettingBool("cron_shutdown")):
|
||||||
# check if we should shut the computer down
|
# wait 10 seconds to make sure all backup processes and files are completed
|
||||||
if(utils.getSetting("cron_shutdown") == 'true'):
|
time.sleep(10)
|
||||||
# wait 10 seconds to make sure all backup processes and files are completed
|
xbmc.executebuiltin('ShutDown()')
|
||||||
time.sleep(10)
|
else:
|
||||||
xbmc.executebuiltin('ShutDown()')
|
# find the next run time like normal
|
||||||
else:
|
self.findNextRun(now)
|
||||||
# find the next run time like normal
|
|
||||||
self.findNextRun(now)
|
xbmc.sleep(500)
|
||||||
|
|
||||||
xbmc.sleep(500)
|
# delete monitor to free up memory
|
||||||
|
del self.monitor
|
||||||
# delete monitor to free up memory
|
|
||||||
del self.monitor
|
def doScheduledBackup(self, progress_mode):
|
||||||
|
if(progress_mode != 2):
|
||||||
def doScheduledBackup(self, progress_mode):
|
utils.showNotification(utils.getString(30053))
|
||||||
if(progress_mode != 2):
|
|
||||||
utils.showNotification(utils.getString(30053))
|
backup = XbmcBackup()
|
||||||
|
|
||||||
backup = XbmcBackup()
|
if(backup.remoteConfigured()):
|
||||||
|
|
||||||
if(backup.remoteConfigured()):
|
if(utils.getSettingInt('progress_mode') in [0, 1]):
|
||||||
|
backup.backup(True)
|
||||||
if(int(utils.getSetting('progress_mode')) in [0, 1]):
|
else:
|
||||||
backup.backup(True)
|
backup.backup(False)
|
||||||
else:
|
|
||||||
backup.backup(False)
|
# check if this is a "one-off"
|
||||||
|
if(utils.getSettingInt("schedule_interval") == 0):
|
||||||
# check if this is a "one-off"
|
# disable the scheduler after this run
|
||||||
if(int(utils.getSetting("schedule_interval")) == 0):
|
self.enabled = False
|
||||||
# disable the scheduler after this run
|
utils.setSetting('enable_scheduler', 'false')
|
||||||
self.enabled = "false"
|
else:
|
||||||
utils.setSetting('enable_scheduler', 'false')
|
utils.showNotification(utils.getString(30045))
|
||||||
else:
|
|
||||||
utils.showNotification(utils.getString(30045))
|
def findNextRun(self, now):
|
||||||
|
progress_mode = utils.getSettingInt('progress_mode')
|
||||||
def findNextRun(self, now):
|
|
||||||
progress_mode = int(utils.getSetting('progress_mode'))
|
# find the cron expression and get the next run time
|
||||||
|
cron_exp = self.parseSchedule()
|
||||||
# find the cron expression and get the next run time
|
|
||||||
cron_exp = self.parseSchedule()
|
cron_ob = croniter(cron_exp, datetime.fromtimestamp(now))
|
||||||
|
new_run_time = cron_ob.get_next(float)
|
||||||
cron_ob = croniter(cron_exp, datetime.fromtimestamp(now))
|
|
||||||
new_run_time = cron_ob.get_next(float)
|
if(new_run_time != self.next_run):
|
||||||
|
self.next_run = new_run_time
|
||||||
if(new_run_time != self.next_run):
|
utils.log("scheduler will run again on " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run), ['dateshort', 'time']))
|
||||||
self.next_run = new_run_time
|
|
||||||
utils.log("scheduler will run again on " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run), ['dateshort', 'time']))
|
# write the next time to a file
|
||||||
|
with xbmcvfs.File(self.next_run_path, 'w') as fh:
|
||||||
# write the next time to a file
|
fh.write(str(self.next_run))
|
||||||
fh = xbmcvfs.File(self.next_run_path, 'w')
|
|
||||||
fh.write(str(self.next_run))
|
# only show when not in silent mode
|
||||||
fh.close()
|
if(progress_mode != 2):
|
||||||
|
utils.showNotification(utils.getString(30081) + " " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run), ['dateshort', 'time']))
|
||||||
# only show when not in silent mode
|
|
||||||
if(progress_mode != 2):
|
def settingsChanged(self):
|
||||||
utils.showNotification(utils.getString(30081) + " " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run), ['dateshort', 'time']))
|
current_enabled = utils.getSettingBool("enable_scheduler")
|
||||||
|
|
||||||
def settingsChanged(self):
|
if(current_enabled and not self.enabled):
|
||||||
current_enabled = utils.getSetting("enable_scheduler")
|
# scheduler was just turned on
|
||||||
|
self.enabled = current_enabled
|
||||||
if(current_enabled == "true" and self.enabled == "false"):
|
self.setup()
|
||||||
# scheduler was just turned on
|
elif (not current_enabled and self.enabled):
|
||||||
self.enabled = current_enabled
|
# schedule was turn off
|
||||||
self.setup()
|
self.enabled = current_enabled
|
||||||
elif (current_enabled == "false" and self.enabled == "true"):
|
|
||||||
# schedule was turn off
|
if(self.enabled):
|
||||||
self.enabled = current_enabled
|
# always recheck the next run time after an update
|
||||||
|
self.findNextRun(time.time())
|
||||||
if(self.enabled == "true"):
|
|
||||||
# always recheck the next run time after an update
|
def parseSchedule(self):
|
||||||
self.findNextRun(time.time())
|
schedule_type = utils.getSettingInt("schedule_interval")
|
||||||
|
cron_exp = utils.getSetting("cron_schedule")
|
||||||
def parseSchedule(self):
|
|
||||||
schedule_type = int(utils.getSetting("schedule_interval"))
|
hour_of_day = utils.getSetting("schedule_time")
|
||||||
cron_exp = utils.getSetting("cron_schedule")
|
hour_of_day = int(hour_of_day[0:2])
|
||||||
|
if(schedule_type == 0 or schedule_type == 1):
|
||||||
hour_of_day = utils.getSetting("schedule_time")
|
# every day
|
||||||
hour_of_day = int(hour_of_day[0:2])
|
cron_exp = "0 " + str(hour_of_day) + " * * *"
|
||||||
if(schedule_type == 0 or schedule_type == 1):
|
elif(schedule_type == 2):
|
||||||
# every day
|
# once a week
|
||||||
cron_exp = "0 " + str(hour_of_day) + " * * *"
|
day_of_week = utils.getSetting("day_of_week")
|
||||||
elif(schedule_type == 2):
|
cron_exp = "0 " + str(hour_of_day) + " * * " + day_of_week
|
||||||
# once a week
|
elif(schedule_type == 3):
|
||||||
day_of_week = utils.getSetting("day_of_week")
|
# first day of month
|
||||||
cron_exp = "0 " + str(hour_of_day) + " * * " + day_of_week
|
cron_exp = "0 " + str(hour_of_day) + " 1 * *"
|
||||||
elif(schedule_type == 3):
|
|
||||||
# first day of month
|
return cron_exp
|
||||||
cron_exp = "0 " + str(hour_of_day) + " 1 * *"
|
|
||||||
|
def _resumeCheck(self):
|
||||||
return cron_exp
|
shouldContinue = False
|
||||||
|
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "resume.txt"))):
|
||||||
def _resumeCheck(self):
|
rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"), 'r')
|
||||||
shouldContinue = False
|
self.restore_point = rFile.read()
|
||||||
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "resume.txt"))):
|
rFile.close()
|
||||||
rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"), 'r')
|
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "resume.txt"))
|
||||||
self.restore_point = rFile.read()
|
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042), utils.getString(30043), utils.getString(30044))
|
||||||
rFile.close()
|
|
||||||
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "resume.txt"))
|
return shouldContinue
|
||||||
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042), utils.getString(30043), utils.getString(30044))
|
|
||||||
|
|
||||||
return shouldContinue
|
class UpdateMonitor(xbmc.Monitor):
|
||||||
|
update_method = None
|
||||||
|
|
||||||
class UpdateMonitor(xbmc.Monitor):
|
def __init__(self, *args, **kwargs):
|
||||||
update_method = None
|
xbmc.Monitor.__init__(self)
|
||||||
|
self.update_method = kwargs['update_method']
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
xbmc.Monitor.__init__(self)
|
def onSettingsChanged(self):
|
||||||
self.update_method = kwargs['update_method']
|
self.update_method()
|
||||||
|
|
||||||
def onSettingsChanged(self):
|
|
||||||
self.update_method()
|
|
||||||
|
|
||||||
|
|
||||||
BackupScheduler().start()
|
|
||||||
@@ -30,6 +30,14 @@ def getSetting(name):
|
|||||||
return __Addon.getSetting(name)
|
return __Addon.getSetting(name)
|
||||||
|
|
||||||
|
|
||||||
|
def getSettingBool(name):
|
||||||
|
return bool(__Addon.getSettingBool(name))
|
||||||
|
|
||||||
|
|
||||||
|
def getSettingInt(name):
|
||||||
|
return __Addon.getSettingInt(name)
|
||||||
|
|
||||||
|
|
||||||
def setSetting(name, value):
|
def setSetting(name, value):
|
||||||
__Addon.setSetting(name, value)
|
__Addon.setSetting(name, value)
|
||||||
|
|
||||||
@@ -45,3 +53,16 @@ def getRegionalTimestamp(date_time, dateformat=['dateshort']):
|
|||||||
result = result + ("%s " % date_time.strftime(xbmc.getRegion(aFormat)))
|
result = result + ("%s " % date_time.strftime(xbmc.getRegion(aFormat)))
|
||||||
|
|
||||||
return result.strip()
|
return result.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def diskString(fSize):
|
||||||
|
# convert a size in kilobytes to the best possible match and return as a string
|
||||||
|
fSize = float(fSize)
|
||||||
|
i = 0
|
||||||
|
sizeNames = ['KB', 'MB', 'GB', 'TB']
|
||||||
|
|
||||||
|
while(fSize > 1024):
|
||||||
|
fSize = fSize / 1024
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
return "%0.2f%s" % (fSize, sizeNames[i])
|
||||||
|
|||||||
@@ -17,16 +17,19 @@ class Vfs:
|
|||||||
def __init__(self, rootString):
|
def __init__(self, rootString):
|
||||||
self.set_root(rootString)
|
self.set_root(rootString)
|
||||||
|
|
||||||
def set_root(self, rootString):
|
def clean_path(self, path):
|
||||||
old_root = self.root_path
|
|
||||||
self.root_path = rootString
|
|
||||||
|
|
||||||
# fix slashes
|
# fix slashes
|
||||||
self.root_path = self.root_path.replace("\\", "/")
|
path = path.replace("\\", "/")
|
||||||
|
|
||||||
# check if trailing slash is included
|
# check if trailing slash is included
|
||||||
if(self.root_path[-1:] != "/"):
|
if(path[-1:] != '/'):
|
||||||
self.root_path = self.root_path + "/"
|
path = path + '/'
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def set_root(self, rootString):
|
||||||
|
old_root = self.root_path
|
||||||
|
self.root_path = self.clean_path(rootString)
|
||||||
|
|
||||||
# return the old root
|
# return the old root
|
||||||
return old_root
|
return old_root
|
||||||
@@ -55,6 +58,9 @@ class Vfs:
|
|||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def fileSize(self, filename):
|
||||||
|
return 0 # result should be in KB
|
||||||
|
|
||||||
|
|
||||||
class XBMCFileSystem(Vfs):
|
class XBMCFileSystem(Vfs):
|
||||||
|
|
||||||
@@ -79,6 +85,12 @@ class XBMCFileSystem(Vfs):
|
|||||||
def exists(self, aFile):
|
def exists(self, aFile):
|
||||||
return xbmcvfs.exists(aFile)
|
return xbmcvfs.exists(aFile)
|
||||||
|
|
||||||
|
def fileSize(self, filename):
|
||||||
|
with xbmcvfs.File(filename) as f:
|
||||||
|
result = f.size() / 1024 # bytes to kilobytes
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class ZipFileSystem(Vfs):
|
class ZipFileSystem(Vfs):
|
||||||
zip = None
|
zip = None
|
||||||
@@ -246,6 +258,16 @@ class DropboxFileSystem(Vfs):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def fileSize(self, filename):
|
||||||
|
result = 0
|
||||||
|
aFile = self._fix_slashes(filename)
|
||||||
|
|
||||||
|
if(self.client is not None):
|
||||||
|
metadata = self.client.files_get_metadata(aFile)
|
||||||
|
result = metadata.size / 1024 # bytes to KB
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def get_file(self, source, dest):
|
def get_file(self, source, dest):
|
||||||
if(self.client is not None):
|
if(self.client is not None):
|
||||||
# write the file locally
|
# write the file locally
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<settings>
|
<settings>
|
||||||
<category id="general" label="30011">
|
<category id="general" label="30011" level="expert">
|
||||||
<setting id="compress_backups" type="bool" label="30087" default="false" />
|
<setting id="compress_backups" type="bool" label="30087" default="false" />
|
||||||
<setting id="backup_rotation" type="number" label="30026" default="0" />
|
<setting id="backup_rotation" type="number" label="30026" default="0" />
|
||||||
<setting id="progress_mode" type="enum" label="30022" lvalues="30082|30083|30084" default="0" />
|
<setting id="progress_mode" type="enum" label="30022" lvalues="30082|30083|30084" default="0" />
|
||||||
|
<setting type="sep" />
|
||||||
|
<setting id="verbose_logging" type="bool" label="Enable Verbose Logging" default="false" />
|
||||||
<setting id="upgrade_notes" type="number" label="upgrade_notes" visible="false" default="1" />
|
<setting id="upgrade_notes" type="number" label="upgrade_notes" visible="false" default="1" />
|
||||||
</category>
|
</category>
|
||||||
<category id="backup_path" label="30048">
|
<category id="backup_path" label="30048">
|
||||||
|
|||||||
4
service.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from resources.lib.scheduler import BackupScheduler
|
||||||
|
|
||||||
|
# start the backup scheduler
|
||||||
|
BackupScheduler().start()
|
||||||