Backup config file

This commit is contained in:
JonnyWong16 2016-05-12 22:00:34 -07:00
parent b0eb98c667
commit 6aa786698e
9 changed files with 127 additions and 64 deletions

4
API.md
View file

@ -36,6 +36,10 @@ General optional parameters:
Get to the chopper! Get to the chopper!
### backup_config
Create a manual backup of the `config.ini` file.
### backup_db ### backup_db
Create a manual backup of the `plexpy.db` file. Create a manual backup of the `plexpy.db` file.

View file

@ -33,7 +33,7 @@ import signal
import time import time
import plexpy import plexpy
from plexpy import logger, web_socket, webstart from plexpy import config, database, logger, web_socket, webstart
# Register signals, such as CTRL + C # Register signals, such as CTRL + C
@ -148,7 +148,7 @@ def main():
if args.config: if args.config:
config_file = args.config config_file = args.config
else: else:
config_file = os.path.join(plexpy.DATA_DIR, 'config.ini') config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME)
# Try to create the DATA_DIR if it doesn't exist # Try to create the DATA_DIR if it doesn't exist
if not os.path.exists(plexpy.DATA_DIR): if not os.path.exists(plexpy.DATA_DIR):
@ -164,7 +164,7 @@ def main():
'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...') 'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...')
# Put the database in the DATA_DIR # Put the database in the DATA_DIR
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, 'plexpy.db') plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
if plexpy.DAEMON: if plexpy.DAEMON:
plexpy.daemonize() plexpy.daemonize()

View file

@ -198,12 +198,11 @@
<div class="form-group"> <div class="form-group">
<label for="backup_dir">Backup Directory</label> <label for="backup_dir">Backup Directory</label>
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-6">
<div class="input-group"> <input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}">
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}"> <div class="btn-group">
<span class="input-group-btn"> <button class="btn btn-form" type="button" id="backup_config">Backup Config</button>
<button class="btn btn-form" type="button" id="backup_database">Backup Database</button> <button class="btn btn-form" type="button" id="backup_database">Backup Database</button>
</span>
</div> </div>
</div> </div>
</div> </div>
@ -211,12 +210,11 @@
<div class="form-group"> <div class="form-group">
<label for="cache_dir">Cache Directory</label> <label for="cache_dir">Cache Directory</label>
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-6">
<div class="input-group"> <input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}">
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}"> <div class="btn-group">
<span class="input-group-btn"> <button class="btn btn-form" type="button" id="clear_cache">Clear All Cache</button>
<button class="btn btn-form" type="button" id="clear_cache">Clear Cache</button> <button class="btn btn-form" type="button" id="clear_image_cache">Clear Image Cache</button>
</span>
</div> </div>
</div> </div>
</div> </div>
@ -224,12 +222,10 @@
<div class="form-group"> <div class="form-group">
<label for="log_dir">Log Directory</label> <label for="log_dir">Log Directory</label>
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-6">
<div class="input-group"> <input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}">
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}"> <div class="btn-group">
<span class="input-group-btn"> <button class="btn btn-form" type="button" id="clear_logs">Clear Logs</button>
<button class="btn btn-form" type="button" id="clear_logs">Clear Logs</button>
</span>
</div> </div>
</div> </div>
</div> </div>
@ -525,7 +521,7 @@
<label> <label>
<input type="checkbox" name="http_hash_password" id="http_hash_password" value="1" ${config['http_hash_password']} data-parsley-trigger="change"> Hash Password in the Config File <input type="checkbox" name="http_hash_password" id="http_hash_password" value="1" ${config['http_hash_password']} data-parsley-trigger="change"> Hash Password in the Config File
</label> </label>
<p class="help-block">Store a hashed password in the config.ini file.<br />Warning: Your password cannot be recovered if forgotten!</p> <p class="help-block">Store a hashed password in the config file.<br />Warning: Your password cannot be recovered if forgotten!</p>
</div> </div>
<input type="text" id="http_hashed_password" name="http_hashed_password" value="${config['http_hashed_password']}" style="display: none;" data-parsley-trigger="change" data-parsley-type="integer" data-parsley-range="[0, 1]" <input type="text" id="http_hashed_password" name="http_hashed_password" value="${config['http_hashed_password']}" style="display: none;" data-parsley-trigger="change" data-parsley-type="integer" data-parsley-range="[0, 1]"
data-parsley-errors-container="#http_hash_password_error" data-parsley-error-message="Cannot un-hash password, please set a new password." data-parsley-no-focus required> data-parsley-errors-container="#http_hash_password_error" data-parsley-error-message="Cannot un-hash password, please set a new password." data-parsley-no-focus required>
@ -2138,12 +2134,12 @@ $(document).ready(function() {
} }
getSchedulerTable(); getSchedulerTable();
$("#backup_database").click(function () { function confirmAjaxCall (url, msg) {
$("#confirm-message").text("Are you sure you want to create a backup of the PlexPy database?"); $("#confirm-message").text(msg);
$('#confirm-modal').modal(); $('#confirm-modal').modal();
$('#confirm-modal').one('click', '#confirm-button', function () { $('#confirm-modal').one('click', '#confirm-button', function () {
$.ajax({ $.ajax({
url: 'backup_db', url: url,
type: 'POST', type: 'POST',
complete: function (xhr, status) { complete: function (xhr, status) {
result = $.parseJSON(xhr.responseText); result = $.parseJSON(xhr.responseText);
@ -2156,46 +2152,36 @@ $(document).ready(function() {
} }
}); });
}); });
}
$("#backup_config").click(function () {
var msg = 'Are you sure you want to create a backup of the PlexPy config?';
var url = 'backup_config';
confirmAjaxCall(url, msg);
});
$("#backup_database").click(function () {
var msg = 'Are you sure you want to create a backup of the PlexPy database?';
var url = 'backup_db';
confirmAjaxCall(url, msg);
}); });
$("#clear_cache").click(function () { $("#clear_cache").click(function () {
$("#confirm-message").text("Are you sure you want to clear the PlexPy cache?"); var msg = 'Are you sure you want to clear the PlexPy cache?';
$('#confirm-modal').modal(); var url = 'delete_cache';
$('#confirm-modal').one('click', '#confirm-button', function () { confirmAjaxCall(url, msg);
$.ajax({ });
url: 'delete_cache',
type: 'POST', $("#clear_image_cache").click(function () {
complete: function (xhr, status) { var msg = 'Are you sure you want to clear the PlexPy image cache?';
result = $.parseJSON(xhr.responseText); var url = 'delete_image_cache';
msg = result.message; confirmAjaxCall(url, msg);
if (result.result == 'success') {
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
} else {
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
}
}
});
});
}); });
$("#clear_logs").click(function () { $("#clear_logs").click(function () {
$("#confirm-message").text("Are you sure you want to clear the PlexPy logs?"); var msg = 'Are you sure you want to clear the PlexPy logs?';
$('#confirm-modal').modal(); var url = 'delete_logs';
$('#confirm-modal').one('click', '#confirm-button', function () { confirmAjaxCall(url, msg);
$.ajax({
url: 'delete_logs',
type: 'POST',
complete: function (xhr, status) {
result = $.parseJSON(xhr.responseText);
msg = result.message;
if (result.result == 'success') {
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
} else {
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
}
}
});
});
}); });

View file

@ -345,6 +345,7 @@ def initialize_scheduler():
hours=hours, minutes=0, seconds=0) hours=hours, minutes=0, seconds=0)
schedule_job(database.make_backup, 'Backup PlexPy database', hours=6, minutes=0, seconds=0, args=(True, True)) schedule_job(database.make_backup, 'Backup PlexPy database', hours=6, minutes=0, seconds=0, args=(True, True))
schedule_job(config.make_backup, 'Backup PlexPy config', hours=6, minutes=0, seconds=0, args=(True, True))
# Start scheduler # Start scheduler
if start_jobs and len(SCHED.get_jobs()): if start_jobs and len(SCHED.get_jobs()):

View file

@ -31,6 +31,7 @@ import cherrypy
import xmltodict import xmltodict
import plexpy import plexpy
import config
import database import database
import logger import logger
import plextv import plextv
@ -295,6 +296,18 @@ class API2:
self.data = rows self.data = rows
return rows return rows
def backup_config(self):
""" Create a manual backup of the `config.ini` file. """
data = config.make_backup()
if data:
self.result_type = 'success'
else:
self.result_type = 'failed'
return data
def backup_db(self): def backup_db(self):
""" Create a manual backup of the `plexpy.db` file. """ """ Create a manual backup of the `plexpy.db` file. """

View file

@ -69,5 +69,6 @@ SCHEDULER_LIST = ['Check GitHub for updates',
'Refresh libraries list', 'Refresh libraries list',
'Refresh Plex server URLs', 'Refresh Plex server URLs',
'Refresh Plex server name', 'Refresh Plex server name',
'Backup PlexPy database' 'Backup PlexPy database',
'Backup PlexPy config'
] ]

View file

@ -13,10 +13,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
import arrow
import os
import re import re
import shutil
from configobj import ConfigObj from configobj import ConfigObj
import plexpy
import logger import logger
@ -29,6 +33,8 @@ def bool_int(value):
value = 0 value = 0
return int(bool(value)) return int(bool(value))
FILENAME = "config.ini"
_CONFIG_DEFINITIONS = { _CONFIG_DEFINITIONS = {
'ALLOW_GUEST_ACCESS': (int, 'General', 0), 'ALLOW_GUEST_ACCESS': (int, 'General', 0),
'DATE_FORMAT': (str, 'General', 'YYYY-MM-DD'), 'DATE_FORMAT': (str, 'General', 'YYYY-MM-DD'),
@ -480,6 +486,43 @@ _BLACKLIST_KEYS = ['_APITOKEN', '_TOKEN', '_KEY', '_SECRET', '_PASSWORD', '_APIK
_WHITELIST_KEYS = ['HTTPS_KEY', 'UPDATE_SECTION_IDS'] _WHITELIST_KEYS = ['HTTPS_KEY', 'UPDATE_SECTION_IDS']
def make_backup(cleanup=False, scheduler=False):
""" Makes a backup of config file, removes all but the last 5 backups """
if scheduler:
backup_file = 'config.backup-%s.sched.ini' % arrow.now().format('YYYYMMDDHHmmss')
else:
backup_file = 'config.backup-%s.ini' % arrow.now().format('YYYYMMDDHHmmss')
backup_folder = plexpy.CONFIG.BACKUP_DIR
backup_file_fp = os.path.join(backup_folder, backup_file)
# In case the user has deleted it manually
if not os.path.exists(backup_folder):
os.makedirs(backup_folder)
plexpy.CONFIG.write()
shutil.copyfile(plexpy.CONFIG_FILE, backup_file_fp)
if cleanup:
# Delete all scheduled backup files except from the last 5.
for root, dirs, files in os.walk(backup_folder):
db_files = [os.path.join(root, f) for f in files if f.endswith('.sched.ini')]
if len(db_files) > 5:
backups_sorted_on_age = sorted(db_files, key=os.path.getctime, reverse=True)
for file_ in backups_sorted_on_age[5:]:
try:
os.remove(file_)
except OSError as e:
logger.error(u"PlexPy Config :: Failed to delete %s from the backup folder: %s" % (file_, e))
if backup_file in os.listdir(backup_folder):
logger.debug(u"PlexPy Config :: Successfully backed up %s to %s" % (plexpy.CONFIG_FILE, backup_file))
return True
else:
logger.warn(u"PlexPy Config :: Failed to backup %s to %s" % (plexpy.CONFIG_FILE, backup_file))
return False
# pylint:disable=R0902 # pylint:disable=R0902
# it might be nice to refactor for fewer instance variables # it might be nice to refactor for fewer instance variables
class Config(object): class Config(object):
@ -557,12 +600,12 @@ class Config(object):
new_config[section][ini_key] = self._config[section][ini_key] new_config[section][ini_key] = self._config[section][ini_key]
# Write it to file # Write it to file
logger.info("Writing configuration to file") logger.info(u"PlexPy Config :: Writing configuration to file")
try: try:
new_config.write() new_config.write()
except IOError as e: except IOError as e:
logger.error("Error writing configuration file: %s", e) logger.error(u"PlexPy Config :: Error writing configuration file: %s", e)
self._blacklist() self._blacklist()

View file

@ -23,6 +23,7 @@ import time
import plexpy import plexpy
import logger import logger
FILENAME = "plexpy.db"
db_lock = threading.Lock() db_lock = threading.Lock()
@ -52,7 +53,7 @@ def delete_sessions():
logger.warn(u"PlexPy Database :: Unable to clear temporary sessions from database: %s." % e) logger.warn(u"PlexPy Database :: Unable to clear temporary sessions from database: %s." % e)
return 'Unable to clear temporary sessions.' return 'Unable to clear temporary sessions.'
def db_filename(filename="plexpy.db"): def db_filename(filename=FILENAME):
""" Returns the filepath to the db """ """ Returns the filepath to the db """
return os.path.join(plexpy.DATA_DIR, filename) return os.path.join(plexpy.DATA_DIR, filename)
@ -115,7 +116,7 @@ def dict_factory(cursor, row):
class MonitorDatabase(object): class MonitorDatabase(object):
def __init__(self, filename='plexpy.db'): def __init__(self, filename=FILENAME):
self.filename = filename self.filename = filename
self.connection = sqlite3.connect(db_filename(filename), timeout=20) self.connection = sqlite3.connect(db_filename(filename), timeout=20)
# Don't wait for the disk to finish writing # Don't wait for the disk to finish writing

View file

@ -30,6 +30,7 @@ from mako import exceptions
import plexpy import plexpy
import common import common
import config
import database import database
import datafactory import datafactory
import graphs import graphs
@ -2428,6 +2429,19 @@ class WebInterface(object):
return {'result': 'success', 'message': 'Settings saved.'} return {'result': 'success', 'message': 'Settings saved.'}
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def backup_config(self):
""" Creates a manual backup of the plexpy.db file """
result = config.make_backup()
if result:
return {'result': 'success', 'message': 'Config backup successful.'}
else:
return {'result': 'error', 'message': 'Config backup failed.'}
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def get_scheduler_table(self, **kwargs): def get_scheduler_table(self, **kwargs):