mirror of
https://github.com/moparisthebest/SickRage
synced 2024-12-13 11:32:20 -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
|
from __future__ import with_statement
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
|
|
||||||
if sys.version_info < (2, 6):
|
if sys.version_info < (2, 6):
|
||||||
print "Sorry, requires Python 2.6 or 2.7."
|
print "Sorry, requires Python 2.6 or 2.7."
|
||||||
@ -133,6 +134,20 @@ def daemonize():
|
|||||||
dev_null = file('/dev/null', 'r')
|
dev_null = file('/dev/null', 'r')
|
||||||
os.dup2(dev_null.fileno(), sys.stdin.fileno())
|
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():
|
def main():
|
||||||
"""
|
"""
|
||||||
TV for me
|
TV for me
|
||||||
@ -177,6 +192,14 @@ def main():
|
|||||||
# Rename the main thread
|
# Rename the main thread
|
||||||
threading.currentThread().name = "MAIN"
|
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:
|
try:
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "qfdp::",
|
opts, args = getopt.getopt(sys.argv[1:], "qfdp::",
|
||||||
['quiet', 'forceupdate', 'daemon', 'port=', 'pidfile=', 'nolaunch', 'config=',
|
['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('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('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('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('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('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');
|
\$("#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>
|
<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/"><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/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/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/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>
|
<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: {
|
defaults: {
|
||||||
title: 'Choose Directory',
|
title: 'Choose Directory',
|
||||||
url: sbRoot + '/browser/',
|
url: sbRoot + '/browser/',
|
||||||
autocompleteURL: sbRoot + '/browser/complete'
|
autocompleteURL: sbRoot + '/browser/complete',
|
||||||
|
includeFiles: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var fileBrowserDialog, currentBrowserPath, currentRequest = null;
|
var fileBrowserDialog, currentBrowserPath, currentRequest = null;
|
||||||
|
|
||||||
function browse(path, endpoint) {
|
function browse(path, endpoint, includeFiles) {
|
||||||
|
|
||||||
if (currentBrowserPath == path) {
|
if (currentBrowserPath == path) {
|
||||||
return;
|
return;
|
||||||
@ -25,7 +26,7 @@
|
|||||||
|
|
||||||
fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog busy');
|
fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog busy');
|
||||||
|
|
||||||
currentRequest = $.getJSON(endpoint, { path: path }, function (data) {
|
currentRequest = $.getJSON(endpoint, { path: path, includeFiles: includeFiles }, function (data) {
|
||||||
fileBrowserDialog.empty();
|
fileBrowserDialog.empty();
|
||||||
var first_val = data[0];
|
var first_val = data[0];
|
||||||
var i = 0;
|
var i = 0;
|
||||||
@ -36,7 +37,7 @@
|
|||||||
$('<h2>').text(first_val.current_path).appendTo(fileBrowserDialog);
|
$('<h2>').text(first_val.current_path).appendTo(fileBrowserDialog);
|
||||||
list = $('<ul>').appendTo(fileBrowserDialog);
|
list = $('<ul>').appendTo(fileBrowserDialog);
|
||||||
$.each(data, function (i, entry) {
|
$.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);
|
$('<span class="ui-icon ui-icon-folder-collapsed"></span>').prependTo(link);
|
||||||
link.hover(
|
link.hover(
|
||||||
function () {$("span", this).addClass("ui-icon-folder-open"); },
|
function () {$("span", this).addClass("ui-icon-folder-open"); },
|
||||||
@ -93,7 +94,8 @@
|
|||||||
if (options.initialDir) {
|
if (options.initialDir) {
|
||||||
initialDir = options.initialDir;
|
initialDir = options.initialDir;
|
||||||
}
|
}
|
||||||
browse(initialDir, options.url);
|
|
||||||
|
browse(initialDir, options.url, options.includeFiles);
|
||||||
fileBrowserDialog.dialog('open');
|
fileBrowserDialog.dialog('open');
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -110,7 +112,7 @@
|
|||||||
position: { my : "top", at: "bottom", collision: "flipfit" },
|
position: { my : "top", at: "bottom", collision: "flipfit" },
|
||||||
source: function (request, response) {
|
source: function (request, response) {
|
||||||
//keep track of user submitted search term
|
//keep track of user submitted search term
|
||||||
query = $.ui.autocomplete.escapeRegex(request.term);
|
query = $.ui.autocomplete.escapeRegex(request.term, options.includeFiles);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: options.autocompleteURL,
|
url: options.autocompleteURL,
|
||||||
data: request,
|
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
|
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
|
""" 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
|
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)
|
under Unix this means "/", on Windows this will be a list of drive letters)
|
||||||
@ -81,7 +81,8 @@ def foldersAtPath(path, includeParent=False):
|
|||||||
parentPath = ""
|
parentPath = ""
|
||||||
|
|
||||||
fileList = [{'name': filename, 'path': ek.ek(os.path.join, path, filename)} for filename in ek.ek(os.listdir, path)]
|
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)
|
# 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",
|
hideList = ["boot", "bootmgr", "cache", "msocache", "recovery", "$recycle.bin", "recycler",
|
||||||
@ -101,11 +102,11 @@ def foldersAtPath(path, includeParent=False):
|
|||||||
|
|
||||||
|
|
||||||
class WebFileBrowser(RequestHandler):
|
class WebFileBrowser(RequestHandler):
|
||||||
def index(self, path='', *args, **kwargs):
|
def index(self, path='', includeFiles=False, *args, **kwargs):
|
||||||
self.set_header("Content-Type", "application/json")
|
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")
|
self.set_header("Content-Type", "application/json")
|
||||||
paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term)) if 'path' in entry]
|
paths = [entry['path'] for entry in foldersAtPath(os.path.dirname(term), includeFiles=bool(int(includeFiles))) if 'path' in entry]
|
||||||
return json.dumps(paths)
|
return json.dumps(paths)
|
@ -32,6 +32,7 @@ import urlparse
|
|||||||
import uuid
|
import uuid
|
||||||
import base64
|
import base64
|
||||||
import string
|
import string
|
||||||
|
import zipfile
|
||||||
|
|
||||||
from lib import requests
|
from lib import requests
|
||||||
from lib.requests import exceptions
|
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
|
# Extract subtitle language from filename
|
||||||
sublang = os.path.splitext(cur_file_name)[1][1:]
|
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:
|
try:
|
||||||
language = subliminal.language.Language(sublang, strict=True)
|
language = subliminal.language.Language(sublang, strict=True)
|
||||||
cur_file_ext = '.' + sublang + cur_file_ext
|
cur_file_ext = '.' + sublang + cur_file_ext
|
||||||
@ -679,6 +680,7 @@ def is_anime_in_show_list():
|
|||||||
def update_anime_support():
|
def update_anime_support():
|
||||||
sickbeard.ANIMESUPPORT = is_anime_in_show_list()
|
sickbeard.ANIMESUPPORT = is_anime_in_show_list()
|
||||||
|
|
||||||
|
|
||||||
def get_absolute_number_from_season_and_episode(show, season, episode):
|
def get_absolute_number_from_season_and_episode(show, season, episode):
|
||||||
with db.DBConnection() as myDB:
|
with db.DBConnection() as myDB:
|
||||||
sql = "SELECT * FROM tv_episodes WHERE showid = ? and season = ? and episode = ?"
|
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
|
return absolute_number
|
||||||
else:
|
else:
|
||||||
logger.log(
|
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
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_all_episodes_from_absolute_number(show, indexer_id, absolute_numbers):
|
def get_all_episodes_from_absolute_number(show, indexer_id, absolute_numbers):
|
||||||
if len(absolute_numbers) == 0:
|
if len(absolute_numbers) == 0:
|
||||||
raise EpisodeNotFoundByAbsoluteNumberException()
|
raise EpisodeNotFoundByAbsoluteNumberException()
|
||||||
@ -885,6 +889,7 @@ def backupVersionedFile(old_file, version):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def restoreVersionedFile(backup_file, version):
|
def restoreVersionedFile(backup_file, version):
|
||||||
numTries = 0
|
numTries = 0
|
||||||
|
|
||||||
@ -896,10 +901,14 @@ def restoreVersionedFile(backup_file, version):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
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))
|
shutil.move(new_file, new_file + '.' + 'r' + str(version))
|
||||||
except Exception, e:
|
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
|
return False
|
||||||
|
|
||||||
while not ek.ek(os.path.isfile, new_file):
|
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)
|
logger.log(u"Trying again.", logger.DEBUG)
|
||||||
|
|
||||||
if numTries >= 10:
|
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 False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# try to convert to int, if it fails the default will be returned
|
# try to convert to int, if it fails the default will be returned
|
||||||
def tryInt(s, s_default=0):
|
def tryInt(s, s_default=0):
|
||||||
try:
|
try:
|
||||||
@ -1044,7 +1055,6 @@ def full_sanitizeSceneName(name):
|
|||||||
|
|
||||||
|
|
||||||
def _check_against_names(nameInQuestion, show, season=-1):
|
def _check_against_names(nameInQuestion, show, season=-1):
|
||||||
|
|
||||||
showNames = []
|
showNames = []
|
||||||
if season in [-1, 1]:
|
if season in [-1, 1]:
|
||||||
showNames = [show.name]
|
showNames = [show.name]
|
||||||
@ -1069,7 +1079,8 @@ def get_show_by_name(name, useIndexer=False):
|
|||||||
return showObj
|
return showObj
|
||||||
if not showObj and sickbeard.showList:
|
if not showObj and sickbeard.showList:
|
||||||
if name in sickbeard.scene_exceptions.exceptionIndexerCache:
|
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:
|
if useIndexer and not showObj:
|
||||||
(sn, idx, id) = searchIndexerForShowID(name, ui=classes.ShowListUI)
|
(sn, idx, id) = searchIndexerForShowID(name, ui=classes.ShowListUI)
|
||||||
@ -1084,6 +1095,7 @@ def get_show_by_name(name, useIndexer=False):
|
|||||||
|
|
||||||
return showObj
|
return showObj
|
||||||
|
|
||||||
|
|
||||||
def is_hidden_folder(folder):
|
def is_hidden_folder(folder):
|
||||||
"""
|
"""
|
||||||
Returns True if folder is hidden.
|
Returns True if folder is hidden.
|
||||||
@ -1144,4 +1156,45 @@ def set_up_anidb_connection():
|
|||||||
else:
|
else:
|
||||||
return True
|
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
|
from __future__ import with_statement
|
||||||
import base64
|
import base64
|
||||||
import functools
|
|
||||||
import inspect
|
import inspect
|
||||||
|
import zipfile
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
|
||||||
import urllib
|
import urllib
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
@ -84,6 +83,8 @@ from tornado import gen
|
|||||||
from tornado.web import RequestHandler, HTTPError, asynchronous
|
from tornado.web import RequestHandler, HTTPError, asynchronous
|
||||||
|
|
||||||
req_headers = None
|
req_headers = None
|
||||||
|
|
||||||
|
|
||||||
def authenticated(handler_class):
|
def authenticated(handler_class):
|
||||||
def wrap_execute(handler_execute):
|
def wrap_execute(handler_execute):
|
||||||
def basicauth(handler, transforms, *args, **kwargs):
|
def basicauth(handler, transforms, *args, **kwargs):
|
||||||
@ -98,7 +99,7 @@ def authenticated(handler_class):
|
|||||||
if not (sickbeard.WEB_USERNAME and sickbeard.WEB_PASSWORD):
|
if not (sickbeard.WEB_USERNAME and sickbeard.WEB_PASSWORD):
|
||||||
return True
|
return True
|
||||||
elif handler.request.uri.startswith('/calendar') or (
|
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
|
return True
|
||||||
|
|
||||||
auth_hdr = handler.request.headers.get('Authorization')
|
auth_hdr = handler.request.headers.get('Authorization')
|
||||||
@ -205,7 +206,7 @@ class MainHandler(RequestHandler):
|
|||||||
return func(**args)
|
return func(**args)
|
||||||
|
|
||||||
raise HTTPError(404)
|
raise HTTPError(404)
|
||||||
|
|
||||||
def redirect(self, url, permanent=False, status=None):
|
def redirect(self, url, permanent=False, status=None):
|
||||||
self._transforms = []
|
self._transforms = []
|
||||||
super(MainHandler, self).redirect(sickbeard.WEB_ROOT + url, permanent, status)
|
super(MainHandler, self).redirect(sickbeard.WEB_ROOT + url, permanent, status)
|
||||||
@ -456,6 +457,7 @@ class MainHandler(RequestHandler):
|
|||||||
|
|
||||||
browser = WebFileBrowser
|
browser = WebFileBrowser
|
||||||
|
|
||||||
|
|
||||||
class PageTemplate(Template):
|
class PageTemplate(Template):
|
||||||
def __init__(self, *args, **KWs):
|
def __init__(self, *args, **KWs):
|
||||||
KWs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/",
|
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]
|
all_eps = [str(x["season"]) + 'x' + str(x["episode"]) for x in all_eps_results]
|
||||||
to_change[cur_indexer_id] = all_eps
|
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/')
|
return self.redirect('/manage/episodeStatuses/')
|
||||||
|
|
||||||
@ -1319,6 +1322,7 @@ class History(MainHandler):
|
|||||||
|
|
||||||
ConfigMenu = [
|
ConfigMenu = [
|
||||||
{'title': 'General', 'path': 'config/general/'},
|
{'title': 'General', 'path': 'config/general/'},
|
||||||
|
{'title': 'Backup/Restore', 'path': 'config/backuprestore/'},
|
||||||
{'title': 'Search Settings', 'path': 'config/search/'},
|
{'title': 'Search Settings', 'path': 'config/search/'},
|
||||||
{'title': 'Search Providers', 'path': 'config/providers/'},
|
{'title': 'Search Providers', 'path': 'config/providers/'},
|
||||||
{'title': 'Subtitles Settings', 'path': 'config/subtitles/'},
|
{'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))
|
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):
|
class ConfigSearch(MainHandler):
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -1558,7 +1611,6 @@ class ConfigSearch(MainHandler):
|
|||||||
|
|
||||||
|
|
||||||
class ConfigPostProcessing(MainHandler):
|
class ConfigPostProcessing(MainHandler):
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
|
|
||||||
t = PageTemplate(file="config_postProcessing.tmpl")
|
t = PageTemplate(file="config_postProcessing.tmpl")
|
||||||
@ -1730,7 +1782,6 @@ class ConfigPostProcessing(MainHandler):
|
|||||||
|
|
||||||
|
|
||||||
class ConfigProviders(MainHandler):
|
class ConfigProviders(MainHandler):
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
t = PageTemplate(file="config_providers.tmpl")
|
t = PageTemplate(file="config_providers.tmpl")
|
||||||
t.submenu = ConfigMenu
|
t.submenu = ConfigMenu
|
||||||
@ -2101,7 +2152,6 @@ class ConfigProviders(MainHandler):
|
|||||||
|
|
||||||
|
|
||||||
class ConfigNotifications(MainHandler):
|
class ConfigNotifications(MainHandler):
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
t = PageTemplate(file="config_notifications.tmpl")
|
t = PageTemplate(file="config_notifications.tmpl")
|
||||||
t.submenu = ConfigMenu
|
t.submenu = ConfigMenu
|
||||||
@ -2303,7 +2353,6 @@ class ConfigNotifications(MainHandler):
|
|||||||
|
|
||||||
|
|
||||||
class ConfigSubtitles(MainHandler):
|
class ConfigSubtitles(MainHandler):
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
t = PageTemplate(file="config_subtitles.tmpl")
|
t = PageTemplate(file="config_subtitles.tmpl")
|
||||||
t.submenu = ConfigMenu
|
t.submenu = ConfigMenu
|
||||||
@ -2361,7 +2410,6 @@ class ConfigSubtitles(MainHandler):
|
|||||||
|
|
||||||
|
|
||||||
class ConfigAnime(MainHandler):
|
class ConfigAnime(MainHandler):
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
|
|
||||||
t = PageTemplate(file="config_anime.tmpl")
|
t = PageTemplate(file="config_anime.tmpl")
|
||||||
@ -2407,7 +2455,6 @@ class ConfigAnime(MainHandler):
|
|||||||
|
|
||||||
|
|
||||||
class Config(MainHandler):
|
class Config(MainHandler):
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
t = PageTemplate(file="config.tmpl")
|
t = PageTemplate(file="config.tmpl")
|
||||||
t.submenu = ConfigMenu
|
t.submenu = ConfigMenu
|
||||||
@ -2416,6 +2463,7 @@ class Config(MainHandler):
|
|||||||
|
|
||||||
# map class names to urls
|
# map class names to urls
|
||||||
general = ConfigGeneral
|
general = ConfigGeneral
|
||||||
|
backuprestore = ConfigBackupRestore
|
||||||
search = ConfigSearch
|
search = ConfigSearch
|
||||||
providers = ConfigProviders
|
providers = ConfigProviders
|
||||||
subtitles = ConfigSubtitles
|
subtitles = ConfigSubtitles
|
||||||
@ -2454,7 +2502,6 @@ def HomeMenu():
|
|||||||
|
|
||||||
|
|
||||||
class HomePostProcess(MainHandler):
|
class HomePostProcess(MainHandler):
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
|
|
||||||
t = PageTemplate(file="home_postprocess.tmpl")
|
t = PageTemplate(file="home_postprocess.tmpl")
|
||||||
@ -2501,7 +2548,6 @@ class HomePostProcess(MainHandler):
|
|||||||
|
|
||||||
|
|
||||||
class NewHomeAddShows(MainHandler):
|
class NewHomeAddShows(MainHandler):
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
|
|
||||||
t = PageTemplate(file="home_addShows.tmpl")
|
t = PageTemplate(file="home_addShows.tmpl")
|
||||||
@ -2887,7 +2933,6 @@ ErrorLogsMenu = [
|
|||||||
|
|
||||||
|
|
||||||
class ErrorLogs(MainHandler):
|
class ErrorLogs(MainHandler):
|
||||||
|
|
||||||
def index(self, *args, **kwargs):
|
def index(self, *args, **kwargs):
|
||||||
|
|
||||||
t = PageTemplate(file="errorlogs.tmpl")
|
t = PageTemplate(file="errorlogs.tmpl")
|
||||||
@ -2956,7 +3001,6 @@ class ErrorLogs(MainHandler):
|
|||||||
|
|
||||||
|
|
||||||
class Home(MainHandler):
|
class Home(MainHandler):
|
||||||
|
|
||||||
def is_alive(self, *args, **kwargs):
|
def is_alive(self, *args, **kwargs):
|
||||||
if 'callback' in kwargs and '_' in kwargs:
|
if 'callback' in kwargs and '_' in kwargs:
|
||||||
callback, _ = kwargs['callback'], kwargs['_']
|
callback, _ = kwargs['callback'], kwargs['_']
|
||||||
|
Loading…
Reference in New Issue
Block a user