mirror of
https://github.com/moparisthebest/SickRage
synced 2024-12-13 03:22:22 -05:00
Added backup and restore feature, this allows you to backup your config.ini and sickbeard.db files into a zipfile and save it to a destination of your choice and as well you can restore the same zip file later on then perform a restart to have the changes take affect automatically. Backups are saved date/time stamped.
This commit is contained in:
parent
ddbcfb40ba
commit
2b7df8e67d
23
SickBeard.py
23
SickBeard.py
@ -21,6 +21,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
if sys.version_info < (2, 6):
|
||||
print "Sorry, requires Python 2.6 or 2.7."
|
||||
@ -133,6 +134,20 @@ def daemonize():
|
||||
dev_null = file('/dev/null', 'r')
|
||||
os.dup2(dev_null.fileno(), sys.stdin.fileno())
|
||||
|
||||
def restore(srcDir, dstDir):
|
||||
try:
|
||||
for file in os.listdir(srcDir):
|
||||
srcFile = os.path.join(srcDir, file)
|
||||
dstFile = os.path.join(dstDir, file)
|
||||
bakFile = os.path.join(dstDir, file + '.bak')
|
||||
shutil.move(dstFile, bakFile)
|
||||
shutil.move(srcFile, dstFile)
|
||||
|
||||
os.rmdir(srcDir)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""
|
||||
TV for me
|
||||
@ -177,6 +192,14 @@ def main():
|
||||
# Rename the main thread
|
||||
threading.currentThread().name = "MAIN"
|
||||
|
||||
# Check if we need to perform a restore first
|
||||
restoreDir = os.path.join(sickbeard.PROG_DIR, 'restore')
|
||||
if os.path.exists(restoreDir):
|
||||
if restore(restoreDir, sickbeard.PROG_DIR):
|
||||
print "Restore successful..."
|
||||
else:
|
||||
print "Restore FAILED!"
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "qfdp::",
|
||||
['quiet', 'forceupdate', 'daemon', 'port=', 'pidfile=', 'nolaunch', 'config=',
|
||||
|
83
gui/slick/interfaces/default/config_backuprestore.tmpl
Normal file
83
gui/slick/interfaces/default/config_backuprestore.tmpl
Normal file
@ -0,0 +1,83 @@
|
||||
#import os.path
|
||||
#import datetime
|
||||
#import locale
|
||||
#import sickbeard
|
||||
#from sickbeard.common import *
|
||||
#from sickbeard.sbdatetime import *
|
||||
#from sickbeard import config
|
||||
#from sickbeard import metadata
|
||||
#from sickbeard.metadata.generic import GenericMetadata
|
||||
#set global $title = "Config - Backup/Restore"
|
||||
#set global $header = "Backup/Restore"
|
||||
|
||||
#set global $sbPath="../.."
|
||||
|
||||
#set global $topmenu="config"#
|
||||
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
|
||||
|
||||
<script type="text/javascript" src="$sbRoot/js/configBackupRestore.js?$sbPID"></script>
|
||||
#if $varExists('header')
|
||||
<h1 class="header">$header</h1>
|
||||
#else
|
||||
<h1 class="title">$title</h1>
|
||||
#end if
|
||||
|
||||
#set $indexer = 0
|
||||
#if $sickbeard.INDEXER_DEFAULT
|
||||
#set $indexer = $sickbeard.INDEXER_DEFAULT
|
||||
#end if
|
||||
|
||||
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
|
||||
|
||||
<div id="config">
|
||||
<div id="config-content">
|
||||
|
||||
<form name="configForm" method="post" action="backuprestore" style="line-height: 44px">
|
||||
<div id="config-components">
|
||||
<ul>
|
||||
<li><a href="#core-component-group1">Backup</a></li>
|
||||
<li><a href="#core-component-group2">Restore</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="core-component-group1" class="component-group clearfix">
|
||||
<div class="component-group-desc">
|
||||
<h3>Backup</h3>
|
||||
<p><b>Backup your main database file and config.</b></p>
|
||||
</div>
|
||||
|
||||
<b>Select the folder you wish to save your backup file to:</b>
|
||||
<br/>
|
||||
<input type="text" name="backupDir" id="backupDir" size="50" /><br/>
|
||||
<br/>
|
||||
<div class="Backup" id="Backup-result"></div>
|
||||
<input class="btn" type="button" value="Backup" id="Backup" />
|
||||
</div><!-- /component-group1 //-->
|
||||
|
||||
<div id="core-component-group2" class="component-group clearfix">
|
||||
<div class="component-group-desc">
|
||||
<h3>Restore</h3>
|
||||
<p><b>Restore your main database file and config.</b></p>
|
||||
</div>
|
||||
|
||||
<b>Select the backup file you wish to restore:</b>
|
||||
<br/>
|
||||
<input type="text" name="backupFile" id="backupFile" size="50" /><br/>
|
||||
<br/>
|
||||
<div class="Restore" id="Restore-result"></div>
|
||||
<input class="btn" type="button" value="Restore" id="Restore" />
|
||||
</div><!-- /component-group2 //-->
|
||||
</div><!-- /config-components -->
|
||||
</form>
|
||||
</div></div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
<!--
|
||||
jQuery('#backupDir').fileBrowser({ title: 'Select backup folder to save to', key: 'backupPath' });
|
||||
jQuery('#backupFile').fileBrowser({ title: 'Select backup files to restore', key: 'backupFile', includeFiles: 1 });
|
||||
jQuery('#config-components').tabs();
|
||||
//-->
|
||||
</script>
|
||||
|
||||
#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl")
|
@ -161,6 +161,7 @@ a > i.icon-question-sign { background-image: url("$sbRoot/images/glyphicons-half
|
||||
\$("#SubMenu a:contains('Anime')").addClass('btn').html('<span class="ui-icon ui-icon-anime pull-left"></span> Anime');
|
||||
\$("#SubMenu a:contains('Settings')").addClass('btn').html('<span class="ui-icon ui-icon-search pull-left"></span> Search Settings');
|
||||
\$("#SubMenu a:contains('Provider')").addClass('btn').html('<span class="ui-icon ui-icon-search pull-left"></span> Search Providers');
|
||||
\$("#SubMenu a:contains('Backup/Restore')").addClass('btn').html('<span class="ui-icon ui-icon-gear pull-left"></span> Backup/Restore');
|
||||
\$("#SubMenu a:contains('General')").addClass('btn').html('<span class="ui-icon ui-icon-gear pull-left"></span> General');
|
||||
\$("#SubMenu a:contains('Episode Status')").addClass('btn').html('<span class="ui-icon ui-icon-transferthick-e-w pull-left"></span> Episode Status Management');
|
||||
\$("#SubMenu a:contains('Missed Subtitle')").addClass('btn').html('<span class="ui-icon ui-icon-transferthick-e-w pull-left"></span> Missed Subtitles');
|
||||
@ -272,6 +273,7 @@ a > i.icon-question-sign { background-image: url("$sbRoot/images/glyphicons-half
|
||||
<ul>
|
||||
<li><a href="$sbRoot/config/"><i class="icon-question-sign" style=" margin-left: -21px;margin-right: 8px;position: absolute;"></i>Help & Info</a></li>
|
||||
<li><a href="$sbRoot/config/general/"><img src="$sbRoot/images/menu/config16.png" alt="" width="16" height="16" />General</a></li>
|
||||
<li><a href="$sbRoot/config/backuprestore/"><img src="$sbRoot/images/menu/config16.png" alt="" width="16" height="16" />Backup/Restore</a></li>
|
||||
<li><a href="$sbRoot/config/search/"><img src="$sbRoot/images/menu/config16.png" alt="" width="16" height="16" />Search Settings</a></li>
|
||||
<li><a href="$sbRoot/config/providers/"><img src="$sbRoot/images/menu/config16.png" alt="" width="16" height="16" />Search Providers</a></li>
|
||||
<li><a href="$sbRoot/config/subtitles/"><img src="$sbRoot/images/menu/config16.png" alt="" width="16" height="16" />Subtitles Settings</a></li>
|
||||
|
@ -5,13 +5,14 @@
|
||||
defaults: {
|
||||
title: 'Choose Directory',
|
||||
url: sbRoot + '/browser/',
|
||||
autocompleteURL: sbRoot + '/browser/complete'
|
||||
autocompleteURL: sbRoot + '/browser/complete',
|
||||
includeFiles: 0
|
||||
}
|
||||
};
|
||||
|
||||
var fileBrowserDialog, currentBrowserPath, currentRequest = null;
|
||||
|
||||
function browse(path, endpoint) {
|
||||
function browse(path, endpoint, includeFiles) {
|
||||
|
||||
if (currentBrowserPath == path) {
|
||||
return;
|
||||
@ -25,7 +26,7 @@
|
||||
|
||||
fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog busy');
|
||||
|
||||
currentRequest = $.getJSON(endpoint, { path: path }, function (data) {
|
||||
currentRequest = $.getJSON(endpoint, { path: path, includeFiles: includeFiles }, function (data) {
|
||||
fileBrowserDialog.empty();
|
||||
var first_val = data[0];
|
||||
var i = 0;
|
||||
@ -36,7 +37,7 @@
|
||||
$('<h2>').text(first_val.current_path).appendTo(fileBrowserDialog);
|
||||
list = $('<ul>').appendTo(fileBrowserDialog);
|
||||
$.each(data, function (i, entry) {
|
||||
link = $("<a href='javascript:void(0)' />").click(function () { browse(entry.path, endpoint); }).text(entry.name);
|
||||
link = $("<a href='javascript:void(0)' />").click(function () { browse(entry.path, endpoint, includeFiles); }).text(entry.name);
|
||||
$('<span class="ui-icon ui-icon-folder-collapsed"></span>').prependTo(link);
|
||||
link.hover(
|
||||
function () {$("span", this).addClass("ui-icon-folder-open"); },
|
||||
@ -93,7 +94,8 @@
|
||||
if (options.initialDir) {
|
||||
initialDir = options.initialDir;
|
||||
}
|
||||
browse(initialDir, options.url);
|
||||
|
||||
browse(initialDir, options.url, options.includeFiles);
|
||||
fileBrowserDialog.dialog('open');
|
||||
|
||||
return false;
|
||||
@ -110,7 +112,7 @@
|
||||
position: { my : "top", at: "bottom", collision: "flipfit" },
|
||||
source: function (request, response) {
|
||||
//keep track of user submitted search term
|
||||
query = $.ui.autocomplete.escapeRegex(request.term);
|
||||
query = $.ui.autocomplete.escapeRegex(request.term, options.includeFiles);
|
||||
$.ajax({
|
||||
url: options.autocompleteURL,
|
||||
data: request,
|
||||
|
24
gui/slick/js/configBackupRestore.js
Normal file
24
gui/slick/js/configBackupRestore.js
Normal file
@ -0,0 +1,24 @@
|
||||
$(document).ready(function(){
|
||||
var loading = '<img src="' + sbRoot + '/images/loading16.gif" height="16" width="16" />';
|
||||
|
||||
$('#Backup').click(function() {
|
||||
$("#Backup").attr("disabled", true);
|
||||
$('#Backup-result').html(loading);
|
||||
var backupDir = $("#backupDir").val();
|
||||
$.get(sbRoot + "/config/backup", {'backupDir': backupDir})
|
||||
.done(function (data) {
|
||||
$('#Backup-result').html(data);
|
||||
$("#Backup").attr("disabled", false);
|
||||
});
|
||||
});
|
||||
$('#Restore').click(function() {
|
||||
$("#Restore").attr("disabled", true);
|
||||
$('#Restore-result').html(loading);
|
||||
var backupFile = $("#backupFile").val();
|
||||
$.get(sbRoot + "/config/restore", {'backupFile': backupFile})
|
||||
.done(function (data) {
|
||||
$('#Restore-result').html(data);
|
||||
$("#Restore").attr("disabled", false);
|
||||
});
|
||||
});
|
||||
});
|
@ -48,7 +48,7 @@ def getWinDrives():
|
||||
return drives
|
||||
|
||||
|
||||
def foldersAtPath(path, includeParent=False):
|
||||
def foldersAtPath(path, includeParent=False, includeFiles=False):
|
||||
""" Returns a list of dictionaries with the folders contained at the given path
|
||||
Give the empty string as the path to list the contents of the root path
|
||||
under Unix this means "/", on Windows this will be a list of drive letters)
|
||||
@ -81,7 +81,8 @@ def foldersAtPath(path, includeParent=False):
|
||||
parentPath = ""
|
||||
|
||||
fileList = [{'name': filename, 'path': ek.ek(os.path.join, path, filename)} for filename in ek.ek(os.listdir, path)]
|
||||
fileList = filter(lambda entry: ek.ek(os.path.isdir, entry['path']), fileList)
|
||||
if not includeFiles:
|
||||
fileList = filter(lambda entry: ek.ek(os.path.isdir, entry['path']), fileList)
|
||||
|
||||
# prune out directories to proect the user from doing stupid things (already lower case the dir to reduce calls)
|
||||
hideList = ["boot", "bootmgr", "cache", "msocache", "recovery", "$recycle.bin", "recycler",
|
||||
@ -101,11 +102,11 @@ def foldersAtPath(path, includeParent=False):
|
||||
|
||||
|
||||
class WebFileBrowser(RequestHandler):
|
||||
def index(self, path='', *args, **kwargs):
|
||||
def index(self, path='', includeFiles=False, *args, **kwargs):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return json.dumps(foldersAtPath(path, True))
|
||||
return json.dumps(foldersAtPath(path, True, bool(int(includeFiles))))
|
||||
|
||||
def complete(self, term):
|
||||
def complete(self, term, includeFiles=0):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term)) if 'path' in entry]
|
||||
return json.dumps(paths)
|
||||
paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term), includeFiles=bool(int(includeFiles))) if 'path' in entry]
|
||||
return json.dumps(paths)
|
@ -32,6 +32,7 @@ import urlparse
|
||||
import uuid
|
||||
import base64
|
||||
import string
|
||||
import zipfile
|
||||
|
||||
from lib import requests
|
||||
from lib.requests import exceptions
|
||||
@ -529,7 +530,7 @@ def rename_ep_file(cur_path, new_path, old_path_length=0):
|
||||
# Extract subtitle language from filename
|
||||
sublang = os.path.splitext(cur_file_name)[1][1:]
|
||||
|
||||
#Check if the language extracted from filename is a valid language
|
||||
# Check if the language extracted from filename is a valid language
|
||||
try:
|
||||
language = subliminal.language.Language(sublang, strict=True)
|
||||
cur_file_ext = '.' + sublang + cur_file_ext
|
||||
@ -679,6 +680,7 @@ def is_anime_in_show_list():
|
||||
def update_anime_support():
|
||||
sickbeard.ANIMESUPPORT = is_anime_in_show_list()
|
||||
|
||||
|
||||
def get_absolute_number_from_season_and_episode(show, season, episode):
|
||||
with db.DBConnection() as myDB:
|
||||
sql = "SELECT * FROM tv_episodes WHERE showid = ? and season = ? and episode = ?"
|
||||
@ -692,10 +694,12 @@ def get_absolute_number_from_season_and_episode(show, season, episode):
|
||||
return absolute_number
|
||||
else:
|
||||
logger.log(
|
||||
"No entries for absolute number in show: " + show.name + " found using " + str(season) + "x" + str(episode),logger.DEBUG)
|
||||
"No entries for absolute number in show: " + show.name + " found using " + str(season) + "x" + str(episode),
|
||||
logger.DEBUG)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_all_episodes_from_absolute_number(show, indexer_id, absolute_numbers):
|
||||
if len(absolute_numbers) == 0:
|
||||
raise EpisodeNotFoundByAbsoluteNumberException()
|
||||
@ -885,6 +889,7 @@ def backupVersionedFile(old_file, version):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def restoreVersionedFile(backup_file, version):
|
||||
numTries = 0
|
||||
|
||||
@ -896,10 +901,14 @@ def restoreVersionedFile(backup_file, version):
|
||||
return False
|
||||
|
||||
try:
|
||||
logger.log(u"Trying to backup " + new_file + " to " + new_file + "." + "r" + str(version) + " before restoring backup", logger.DEBUG)
|
||||
logger.log(
|
||||
u"Trying to backup " + new_file + " to " + new_file + "." + "r" + str(version) + " before restoring backup",
|
||||
logger.DEBUG)
|
||||
shutil.move(new_file, new_file + '.' + 'r' + str(version))
|
||||
except Exception, e:
|
||||
logger.log(u"Error while trying to backup DB file " + restore_file + " before proceeding with restore: " + ex(e), logger.WARNING)
|
||||
logger.log(
|
||||
u"Error while trying to backup DB file " + restore_file + " before proceeding with restore: " + ex(e),
|
||||
logger.WARNING)
|
||||
return False
|
||||
|
||||
while not ek.ek(os.path.isfile, new_file):
|
||||
@ -919,11 +928,13 @@ def restoreVersionedFile(backup_file, version):
|
||||
logger.log(u"Trying again.", logger.DEBUG)
|
||||
|
||||
if numTries >= 10:
|
||||
logger.log(u"Unable to restore " + restore_file + " to " + new_file + " please do it manually.", logger.ERROR)
|
||||
logger.log(u"Unable to restore " + restore_file + " to " + new_file + " please do it manually.",
|
||||
logger.ERROR)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# try to convert to int, if it fails the default will be returned
|
||||
def tryInt(s, s_default=0):
|
||||
try:
|
||||
@ -1044,7 +1055,6 @@ def full_sanitizeSceneName(name):
|
||||
|
||||
|
||||
def _check_against_names(nameInQuestion, show, season=-1):
|
||||
|
||||
showNames = []
|
||||
if season in [-1, 1]:
|
||||
showNames = [show.name]
|
||||
@ -1069,7 +1079,8 @@ def get_show_by_name(name, useIndexer=False):
|
||||
return showObj
|
||||
if not showObj and sickbeard.showList:
|
||||
if name in sickbeard.scene_exceptions.exceptionIndexerCache:
|
||||
showObj = findCertainShow(sickbeard.showList, int(sickbeard.scene_exceptions.exceptionIndexerCache[name]))
|
||||
showObj = findCertainShow(sickbeard.showList,
|
||||
int(sickbeard.scene_exceptions.exceptionIndexerCache[name]))
|
||||
|
||||
if useIndexer and not showObj:
|
||||
(sn, idx, id) = searchIndexerForShowID(name, ui=classes.ShowListUI)
|
||||
@ -1084,6 +1095,7 @@ def get_show_by_name(name, useIndexer=False):
|
||||
|
||||
return showObj
|
||||
|
||||
|
||||
def is_hidden_folder(folder):
|
||||
"""
|
||||
Returns True if folder is hidden.
|
||||
@ -1144,4 +1156,45 @@ def set_up_anidb_connection():
|
||||
else:
|
||||
return True
|
||||
|
||||
return sickbeard.ADBA_CONNECTION.authed()
|
||||
return sickbeard.ADBA_CONNECTION.authed()
|
||||
|
||||
|
||||
def makeZip(fileList, archive):
|
||||
"""
|
||||
'fileList' is a list of file names - full path each name
|
||||
'archive' is the file name for the archive with a full path
|
||||
"""
|
||||
try:
|
||||
a = zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED)
|
||||
for f in fileList:
|
||||
a.write(f)
|
||||
a.close()
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def extractZip(archive, targetDir):
|
||||
"""
|
||||
'fileList' is a list of file names - full path each name
|
||||
'archive' is the file name for the archive with a full path
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(targetDir):
|
||||
os.mkdir(targetDir)
|
||||
|
||||
with zipfile.ZipFile(archive) as zip_file:
|
||||
for member in zip_file.namelist():
|
||||
filename = os.path.basename(member)
|
||||
# skip directories
|
||||
if not filename:
|
||||
continue
|
||||
|
||||
# copy file (taken from zipfile's extract)
|
||||
source = zip_file.open(member)
|
||||
target = file(os.path.join(targetDir, filename), "wb")
|
||||
with source, target:
|
||||
shutil.copyfileobj(source, target)
|
||||
return True
|
||||
except:
|
||||
return False
|
@ -18,13 +18,12 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
import base64
|
||||
import functools
|
||||
import inspect
|
||||
import zipfile
|
||||
|
||||
import os.path
|
||||
|
||||
import time
|
||||
import traceback
|
||||
import urllib
|
||||
import re
|
||||
import threading
|
||||
@ -84,6 +83,8 @@ from tornado import gen
|
||||
from tornado.web import RequestHandler, HTTPError, asynchronous
|
||||
|
||||
req_headers = None
|
||||
|
||||
|
||||
def authenticated(handler_class):
|
||||
def wrap_execute(handler_execute):
|
||||
def basicauth(handler, transforms, *args, **kwargs):
|
||||
@ -98,7 +99,7 @@ def authenticated(handler_class):
|
||||
if not (sickbeard.WEB_USERNAME and sickbeard.WEB_PASSWORD):
|
||||
return True
|
||||
elif handler.request.uri.startswith('/calendar') or (
|
||||
handler.request.uri.startswith('/api') and '/api/builder' not in handler.request.uri):
|
||||
handler.request.uri.startswith('/api') and '/api/builder' not in handler.request.uri):
|
||||
return True
|
||||
|
||||
auth_hdr = handler.request.headers.get('Authorization')
|
||||
@ -205,7 +206,7 @@ class MainHandler(RequestHandler):
|
||||
return func(**args)
|
||||
|
||||
raise HTTPError(404)
|
||||
|
||||
|
||||
def redirect(self, url, permanent=False, status=None):
|
||||
self._transforms = []
|
||||
super(MainHandler, self).redirect(sickbeard.WEB_ROOT + url, permanent, status)
|
||||
@ -456,6 +457,7 @@ class MainHandler(RequestHandler):
|
||||
|
||||
browser = WebFileBrowser
|
||||
|
||||
|
||||
class PageTemplate(Template):
|
||||
def __init__(self, *args, **KWs):
|
||||
KWs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/",
|
||||
@ -728,7 +730,8 @@ class Manage(MainHandler):
|
||||
all_eps = [str(x["season"]) + 'x' + str(x["episode"]) for x in all_eps_results]
|
||||
to_change[cur_indexer_id] = all_eps
|
||||
|
||||
Home(self.application, self.request).setStatus(cur_indexer_id, '|'.join(to_change[cur_indexer_id]), newStatus, direct=True)
|
||||
Home(self.application, self.request).setStatus(cur_indexer_id, '|'.join(to_change[cur_indexer_id]),
|
||||
newStatus, direct=True)
|
||||
|
||||
return self.redirect('/manage/episodeStatuses/')
|
||||
|
||||
@ -1319,6 +1322,7 @@ class History(MainHandler):
|
||||
|
||||
ConfigMenu = [
|
||||
{'title': 'General', 'path': 'config/general/'},
|
||||
{'title': 'Backup/Restore', 'path': 'config/backuprestore/'},
|
||||
{'title': 'Search Settings', 'path': 'config/search/'},
|
||||
{'title': 'Search Providers', 'path': 'config/providers/'},
|
||||
{'title': 'Subtitles Settings', 'path': 'config/subtitles/'},
|
||||
@ -1474,6 +1478,55 @@ class ConfigGeneral(MainHandler):
|
||||
ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE))
|
||||
|
||||
|
||||
class ConfigBackupRestore(MainHandler):
|
||||
def index(self, *args, **kwargs):
|
||||
t = PageTemplate(file="config_backuprestore.tmpl")
|
||||
t.submenu = ConfigMenu
|
||||
return _munge(t)
|
||||
|
||||
def backup(self, backupDir=None):
|
||||
self.set_header('Cache-Control', "max-age=0,no-cache,no-store")
|
||||
|
||||
finalResult = ''
|
||||
|
||||
if backupDir:
|
||||
source = [os.path.join(sickbeard.PROG_DIR, 'sickbeard.db'), os.path.join(sickbeard.PROG_DIR, 'config.ini')]
|
||||
target = os.path.join(backupDir, 'sickrage-' + time.strftime('%Y%m%d%H%M%S') + '.zip')
|
||||
|
||||
if helpers.makeZip(source, target):
|
||||
finalResult += "Successful backup to " + target
|
||||
else:
|
||||
finalResult += "Backup FAILED"
|
||||
else:
|
||||
finalResult += "You need to choose a folder to save your backup to!"
|
||||
|
||||
finalResult += "<br />\n"
|
||||
|
||||
return finalResult
|
||||
|
||||
|
||||
def restore(self, backupFile=None):
|
||||
self.set_header('Cache-Control', "max-age=0,no-cache,no-store")
|
||||
|
||||
finalResult = ''
|
||||
|
||||
if backupFile:
|
||||
source = backupFile
|
||||
target_dir = os.path.join(sickbeard.PROG_DIR, 'restore')
|
||||
|
||||
if helpers.extractZip(source, target_dir):
|
||||
finalResult += "Successfully extracted restore files to " + target_dir
|
||||
finalResult += "<br>Restart sickrage to complete the restore."
|
||||
else:
|
||||
finalResult += "Restore FAILED"
|
||||
else:
|
||||
finalResult += "You need to select a backup file to restore!"
|
||||
|
||||
finalResult += "<br />\n"
|
||||
|
||||
return finalResult
|
||||
|
||||
|
||||
class ConfigSearch(MainHandler):
|
||||
def index(self, *args, **kwargs):
|
||||
|
||||
@ -1558,7 +1611,6 @@ class ConfigSearch(MainHandler):
|
||||
|
||||
|
||||
class ConfigPostProcessing(MainHandler):
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
|
||||
t = PageTemplate(file="config_postProcessing.tmpl")
|
||||
@ -1730,7 +1782,6 @@ class ConfigPostProcessing(MainHandler):
|
||||
|
||||
|
||||
class ConfigProviders(MainHandler):
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
t = PageTemplate(file="config_providers.tmpl")
|
||||
t.submenu = ConfigMenu
|
||||
@ -2101,7 +2152,6 @@ class ConfigProviders(MainHandler):
|
||||
|
||||
|
||||
class ConfigNotifications(MainHandler):
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
t = PageTemplate(file="config_notifications.tmpl")
|
||||
t.submenu = ConfigMenu
|
||||
@ -2303,7 +2353,6 @@ class ConfigNotifications(MainHandler):
|
||||
|
||||
|
||||
class ConfigSubtitles(MainHandler):
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
t = PageTemplate(file="config_subtitles.tmpl")
|
||||
t.submenu = ConfigMenu
|
||||
@ -2361,7 +2410,6 @@ class ConfigSubtitles(MainHandler):
|
||||
|
||||
|
||||
class ConfigAnime(MainHandler):
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
|
||||
t = PageTemplate(file="config_anime.tmpl")
|
||||
@ -2407,7 +2455,6 @@ class ConfigAnime(MainHandler):
|
||||
|
||||
|
||||
class Config(MainHandler):
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
t = PageTemplate(file="config.tmpl")
|
||||
t.submenu = ConfigMenu
|
||||
@ -2416,6 +2463,7 @@ class Config(MainHandler):
|
||||
|
||||
# map class names to urls
|
||||
general = ConfigGeneral
|
||||
backuprestore = ConfigBackupRestore
|
||||
search = ConfigSearch
|
||||
providers = ConfigProviders
|
||||
subtitles = ConfigSubtitles
|
||||
@ -2454,7 +2502,6 @@ def HomeMenu():
|
||||
|
||||
|
||||
class HomePostProcess(MainHandler):
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
|
||||
t = PageTemplate(file="home_postprocess.tmpl")
|
||||
@ -2501,7 +2548,6 @@ class HomePostProcess(MainHandler):
|
||||
|
||||
|
||||
class NewHomeAddShows(MainHandler):
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
|
||||
t = PageTemplate(file="home_addShows.tmpl")
|
||||
@ -2887,7 +2933,6 @@ ErrorLogsMenu = [
|
||||
|
||||
|
||||
class ErrorLogs(MainHandler):
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
|
||||
t = PageTemplate(file="errorlogs.tmpl")
|
||||
@ -2956,7 +3001,6 @@ class ErrorLogs(MainHandler):
|
||||
|
||||
|
||||
class Home(MainHandler):
|
||||
|
||||
def is_alive(self, *args, **kwargs):
|
||||
if 'callback' in kwargs and '_' in kwargs:
|
||||
callback, _ = kwargs['callback'], kwargs['_']
|
||||
|
Loading…
Reference in New Issue
Block a user