From f7328b2841be21a60c6ac04d10a3b2e12de31654 Mon Sep 17 00:00:00 2001 From: Tom Anschutz Date: Mon, 8 Mar 2021 11:43:19 -0500 Subject: [PATCH 01/12] Update base.py Changed the way `sum` was computed in `getImagesTotal()` to avoid black screen with "no (new) images could be found" in the warning log. --- services/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/base.py b/services/base.py index b0972d2..e9eac4d 100755 --- a/services/base.py +++ b/services/base.py @@ -178,13 +178,15 @@ def getId(self): def getImagesTotal(self): # return the total number of images provided by this service + sum = 0 if self.needKeywords(): for keyword in self.getKeywords(): if keyword not in self._STATE["_NUM_IMAGES"] or keyword not in self._STATE['_NEXT_SCAN'] or self._STATE['_NEXT_SCAN'][keyword] < time.time(): logging.debug('Keywords either not scanned or we need to scan now') self._getImagesFor(keyword) # Will make sure to get images self._STATE['_NEXT_SCAN'][keyword] = time.time() + self.REFRESH_DELAY - return sum([self._STATE["_NUM_IMAGES"][k] for k in self._STATE["_NUM_IMAGES"]]) + sum = sum + self._STATE["_NUM_IMAGES"][keyword] + return sum def getImagesSeen(self): count = 0 From 0373c28d8851939799733ab4387c7126eac95bb7 Mon Sep 17 00:00:00 2001 From: Tom Anschutz Date: Tue, 9 Mar 2021 16:39:47 -0500 Subject: [PATCH 02/12] Update display.py Fix Fill and Do Nothing image handling. --- modules/display.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/display.py b/modules/display.py index 0f09943..32b5a95 100755 --- a/modules/display.py +++ b/modules/display.py @@ -227,8 +227,6 @@ def image(self, filename): args = [ 'convert', filename + '[0]', - '-resize', - '%dx%d' % (self.width, self.height), '-background', 'black', '-gravity', From 51a3174389c8199ee8d39b371960f2a8d9b76baa Mon Sep 17 00:00:00 2001 From: Dadr Date: Wed, 16 Jun 2021 22:21:47 -0400 Subject: [PATCH 03/12] Update base.py Addresses #186 --- services/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/base.py b/services/base.py index e9eac4d..1a6054b 100755 --- a/services/base.py +++ b/services/base.py @@ -696,7 +696,7 @@ def requestUrl(self, url, destination=None, params=None, data=None, usePost=Fals break except: logging.exception('Issues downloading') - time.sleep(tries / 10) # Back off 10, 20, ... depending on tries + time.sleep(tries * 10) # Back off 10, 20, ... depending on tries tries += 1 logging.warning('Retrying again, attempt #%d', tries) From 837e3426bc81037fa81a4147122bedae92ec75cc Mon Sep 17 00:00:00 2001 From: Dadr Date: Sun, 30 Jan 2022 22:52:29 -0500 Subject: [PATCH 04/12] Initial Backup commit for legacy frame --- load_config.py | 106 ++++++++++++++++++++++++++++++++++++++ routes/maintenance.py | 36 +++++++++++++ routes/upload.py | 18 +++++++ static/template/main.html | 11 +++- 4 files changed, 169 insertions(+), 2 deletions(-) create mode 100755 load_config.py diff --git a/load_config.py b/load_config.py new file mode 100755 index 0000000..10dcc64 --- /dev/null +++ b/load_config.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# +# This file is part of photoframe (https://github.com/mrworf/photoframe). +# +# photoframe is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# photoframe is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with photoframe. If not, see . +# +# Synopsis: +# load_config.py settings.tar.gz +# +# Description: +# This program loads a saved configuration into the system. It will: +# Verify config file, +# stop frame process, +# backup current config, +# set desired config. +# Note: restarting the frame service is up to the caller. +# +# +import sys +import os +import subprocess +import shutil + +from modules.path import path + +# Make sure we run from our own directory +os.chdir(os.path.dirname(sys.argv[0])) + +# get input filename from command line +if len(sys.argv) < 2: + print('Argument missing') + print('Usage:', sys.argv[0], ' settings.tar.gz') + sys.exit(1) +if len(sys.argv) > 2: + print('Too many arguments') + print('Usage:', sys.argv[0], ' settings.tar.gz') + sys.exit(1) +updatefile = sys.argv[1] + +# test that the file exists and is a tar.gz file +if not os.path.isfile(updatefile): + print('file not found: ', updatefile) + sys.exit(1) +try: + filetype = subprocess.check_output(['file', '-b', '--mime-type', updatefile]).decode("utf-8") +except Exception: + print('Error determining Mime-type of ', updatefile) + sys.exit(1) +if filetype != 'application/gzip': + print(updatefile, 'is not a tar.gz archive') + sys.exit(1) + +#Stop existing frame thread +# Do NOT kill the service itself, since it will actually tear down the python script +subprocess.call(['pkill', '-SIGHUP', '-f', 'frame.py']) + +# move existing config if necessary +configdir=path.CONFIGFOLDER +if os.path.isdir(configdir): + had_config = True + shutil.rmtree(configdir + '.bak', ignore_errors = True) + os.rename(configdir, configdir + '.bak') +else: + had_config = False +try: + os.mkdir(configdir) +except Exception: + print('Cannot make new configuration directory:', configdir) + os.rename(configdir + '.bak', configdir) + sys.exit(1) + +#Un-tar the config file +try: + subprocess.call(['/usr/bin/tar', '-xzf', updatefile, '-C', configdir]) +except Exception: + print('tar failed to unpack', updatefile, 'into', configdir) + if had_config: + print('Restoring Previous configuration') + shutil.rmtree(configdir, ignore_errors = True) + os.rename(configdir + '.bak', configdir) + sys.exit(1) + +#Test config data for validity +if not all([os.path.isdir(configdir + '/display-drivers'), os.path.isdir(configdir + '/services'), + os.path.isfile(configdir + '/settings.json'), os.path.isfile(configdir + '/version.json')]): + print('New config is incomplete') + if had_config: + print('Restoring Previous configuration') + shutil.rmtree(configdir, ignore_errors = True) + os.rename(configdir + '.bak', configdir) + sys.exit(1) + +#All done +print('Successfully loaded new config from ', updatefile) +sys.exit(0) diff --git a/routes/maintenance.py b/routes/maintenance.py index b91fd57..8a31294 100755 --- a/routes/maintenance.py +++ b/routes/maintenance.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU General Public License # along with photoframe. If not, see . # +import logging import os import subprocess import shutil @@ -83,3 +84,38 @@ def handle(self, app, cmd): elif cmd == 'ssh': subprocess.call(['systemctl', 'restart', 'ssh'], stderr=self.void) return self.jsonify({'ssh': True}) + elif cmd == 'backup': + if debug.config_version(): + try: + subprocess.call(['tar', '-czf', '/boot/settings.tar.gz', '-C', path.CONFIGFOLDER, '.']) + except: + return 'Backup Failed', 404 + else: + return 'Backup Successful', 200 + else: + return 'Backup Failed', 404 + elif cmd == 'restore': + if os.path.isfile("/boot/settings.tar.gz"): + try: + subprocess.run(path.BASEDIR + 'photoframe/load_config.py /boot/settings.tar.gz', shell=True) + except: + logging.info('FAILED to load new settings with: ' + path.BASEDIR + 'photoframe/load_config.py /boot/settings.tar.gz') + return 'Failed to load new settings', 404 + else: + logging.info('Loaded new settings with: ' + path.BASEDIR + 'photoframe/load_config.py /boot/settings.tar.gz') + subprocess.Popen('systemctl restart frame', shell=True) + return 'Restoring settings and restarting photofame', 200 + else: + return 'File not found: /boot/settings.tar.gz', 404 + elif cmd == 'dnldcfg': + if debug.config_version(): + try: + subprocess.call(['tar', '-czf', '/tmp/settings.tar.gz', '-C', path.CONFIGFOLDER, '.'], stderr=self.void) + except: + return 'Download Failed', 404 + else: + return flask.send_from_directory("/tmp", "settings.tar.gz", as_attachment=True) + else: + return 'Download failed', 404 + # The route to upload settings from the browser is in routes/upload.py + diff --git a/routes/upload.py b/routes/upload.py index 61fccea..1a5b3ee 100644 --- a/routes/upload.py +++ b/routes/upload.py @@ -16,6 +16,7 @@ import logging import os +import subprocess from werkzeug.utils import secure_filename from baseroute import BaseRoute @@ -43,6 +44,13 @@ def handle(self, app, item): logging.error('No filename or invalid filename') self.setAbort(405) return + elif item == 'config': + # if user does not select file, browser also + # submit an empty part without filename + if file.filename == '' or not file.filename.lower().endswith('.tar.gz'): + logging.error('No configuration filename or does not end in .tar.gz') + self.setAbort(405) + return filename = os.path.join('/tmp/', secure_filename(file.filename)) file.save(filename) @@ -64,6 +72,16 @@ def handle(self, app, item): else: retval['return'] = {'reboot' : False} + elif item == 'config': + try: + subprocess.run(path.BASEDIR + 'photoframe/load_config.py ' + filename, shell=True) + except: + logging.info('FAILED to load settings with: ' + path.BASEDIR + 'photoframe/load_config.py ' + filename) + retval['status'] = 500 + else: + logging.info('Loading settings with: ' + path.BASEDIR + 'photoframe/load_config.py ' + filename) + retval['return'] = {'reboot': False, 'restart': True} + try: os.remove(filename) except: diff --git a/static/template/main.html b/static/template/main.html index 7494e58..bcc5bab 100755 --- a/static/template/main.html +++ b/static/template/main.html @@ -165,10 +165,17 @@

PhotoFrame Configuration
-
- +
+ +
+ + +
+ + +
{{#if service-defined}} From 1c13b062d0e93d382dd91656b480fd94e62cc2a0 Mon Sep 17 00:00:00 2001 From: Dadr Date: Sun, 30 Jan 2022 23:06:58 -0500 Subject: [PATCH 05/12] Add functions to buttons for backup and restore --- static/js/main.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/static/js/main.js b/static/js/main.js index 63997fa..d5b8d5f 100755 --- a/static/js/main.js +++ b/static/js/main.js @@ -327,6 +327,55 @@ $("#reboot").click(function() { } }); +$("#backup").click(function() { + if (confirm("Backup current settings to /boot/settings.tar.gz ?")) { + $.ajax({ + url:"/maintenance/backup" + }).done(function (){ + }); + } +}); + +$("#restore").click(function() { + if (confirm("This will remove the current configuration and restore saved settings from /boot/settings.tar.gz ?")) { + $.ajax({ + url:"/maintenance/restore" + }).done(function(){ + rebootWatch(); + }); + } +}); + +$("#dnldcfg").click(function() { + window.location.assign("/maintenance/dnldcfg") +}); + +$('#config').fileupload({ + add: function (e, data) { + data.submit(); + }, + done: function (e, data) { + console.log(data); + if (data.result['restart']) { + $.ajax({ + url:"/maintenance/restart" + }).done(function(){ + rebootWatch(); + }); + } else { + alert("Failed to install configuration - is the file OK?"); + location.reload(); + } + }, + fail: function (e, data) { + alert('Failed to upload configuration'); + }, +}); + +$("#config-button").click(function() { + $('#config').trigger('click'); +}); + $("#shutdown").click(function() { if (confirm("Are you sure you want to POWER OFF the frame?")) { $.ajax({ From a7d94a89238b62df46172e0bc069110154abbbe3 Mon Sep 17 00:00:00 2001 From: Dadr Date: Sun, 30 Jan 2022 23:22:47 -0500 Subject: [PATCH 06/12] Add function to create version file --- modules/debug.py | 29 +++++++++++++++++++++++++++++ routes/maintenance.py | 1 + 2 files changed, 30 insertions(+) diff --git a/modules/debug.py b/modules/debug.py index ef960ea..063142b 100755 --- a/modules/debug.py +++ b/modules/debug.py @@ -71,3 +71,32 @@ def version(): if lines: lines = lines.splitlines() return (title, lines, None) + +def config_version(): +origin = subprocess_check_output(['git', 'config', '--get', 'remote.origin.url']) +if origin: + origin = origin.strip() + +branch = "" +branchlines = subprocess_check_output(['git', 'status']).splitlines() +for line in branchlines: + if line.startswith('On branch'): + branch = line.partition("branch")[2].strip() + +commit = "" +commitlines = subprocess_check_output(['git', 'log', '-n 1']).splitlines() +for line in commitlines: + if line.startswith('commit'): + commit = line.partition("commit")[2].strip() + +config = { + "release": platform.release(), + "python_version": platform.python_version(), + "origin": origin, + "branch": branch, + "commit": commit + } +versionfile = open(path.CONFIGFOLDER + "/version.json", 'w+') +json.dump(config, versionfile) + +return (True) diff --git a/routes/maintenance.py b/routes/maintenance.py index 8a31294..5168420 100755 --- a/routes/maintenance.py +++ b/routes/maintenance.py @@ -17,6 +17,7 @@ import os import subprocess import shutil +import modules.debug as debug from baseroute import BaseRoute from modules.path import path From 8271c03711d0944240fac663426664b28fcbe872 Mon Sep 17 00:00:00 2001 From: Dadr Date: Sun, 30 Jan 2022 23:29:27 -0500 Subject: [PATCH 07/12] Cut-Paste Indent error --- modules/debug.py | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/modules/debug.py b/modules/debug.py index 063142b..3f7a777 100755 --- a/modules/debug.py +++ b/modules/debug.py @@ -73,30 +73,30 @@ def version(): return (title, lines, None) def config_version(): -origin = subprocess_check_output(['git', 'config', '--get', 'remote.origin.url']) -if origin: - origin = origin.strip() + origin = subprocess_check_output(['git', 'config', '--get', 'remote.origin.url']) + if origin: + origin = origin.strip() -branch = "" -branchlines = subprocess_check_output(['git', 'status']).splitlines() -for line in branchlines: - if line.startswith('On branch'): - branch = line.partition("branch")[2].strip() + branch = "" + branchlines = subprocess_check_output(['git', 'status']).splitlines() + for line in branchlines: + if line.startswith('On branch'): + branch = line.partition("branch")[2].strip() -commit = "" -commitlines = subprocess_check_output(['git', 'log', '-n 1']).splitlines() -for line in commitlines: - if line.startswith('commit'): - commit = line.partition("commit")[2].strip() + commit = "" + commitlines = subprocess_check_output(['git', 'log', '-n 1']).splitlines() + for line in commitlines: + if line.startswith('commit'): + commit = line.partition("commit")[2].strip() -config = { - "release": platform.release(), - "python_version": platform.python_version(), - "origin": origin, - "branch": branch, - "commit": commit - } -versionfile = open(path.CONFIGFOLDER + "/version.json", 'w+') -json.dump(config, versionfile) + config = { + "release": platform.release(), + "python_version": platform.python_version(), + "origin": origin, + "branch": branch, + "commit": commit + } + versionfile = open(path.CONFIGFOLDER + "/version.json", 'w+') + json.dump(config, versionfile) -return (True) + return (True) From df04f5b39e7f6d5df92a0f191e54d65d1d09feef Mon Sep 17 00:00:00 2001 From: Dadr Date: Sun, 30 Jan 2022 23:35:06 -0500 Subject: [PATCH 08/12] Update debug.py --- modules/debug.py | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/modules/debug.py b/modules/debug.py index 3f7a777..f2cfebe 100755 --- a/modules/debug.py +++ b/modules/debug.py @@ -73,30 +73,30 @@ def version(): return (title, lines, None) def config_version(): - origin = subprocess_check_output(['git', 'config', '--get', 'remote.origin.url']) - if origin: - origin = origin.strip() + origin = subprocess_check_output(['git', 'config', '--get', 'remote.origin.url']) + if origin: + origin = origin.strip() - branch = "" - branchlines = subprocess_check_output(['git', 'status']).splitlines() - for line in branchlines: - if line.startswith('On branch'): - branch = line.partition("branch")[2].strip() + branch = "" + branchlines = subprocess_check_output(['git', 'status']).splitlines() + for line in branchlines: + if line.startswith('On branch'): + branch = line.partition("branch")[2].strip() - commit = "" - commitlines = subprocess_check_output(['git', 'log', '-n 1']).splitlines() - for line in commitlines: - if line.startswith('commit'): - commit = line.partition("commit")[2].strip() + commit = "" + commitlines = subprocess_check_output(['git', 'log', '-n 1']).splitlines() + for line in commitlines: + if line.startswith('commit'): + commit = line.partition("commit")[2].strip() - config = { - "release": platform.release(), - "python_version": platform.python_version(), - "origin": origin, - "branch": branch, - "commit": commit - } - versionfile = open(path.CONFIGFOLDER + "/version.json", 'w+') - json.dump(config, versionfile) + config = { + "release": platform.release(), + "python_version": platform.python_version(), + "origin": origin, + "branch": branch, + "commit": commit + } + versionfile = open(path.CONFIGFOLDER + "/version.json", 'w+') + json.dump(config, versionfile) - return (True) + return (True) From ad0a75c67b6c1ffb3e1c9ff4dc4e56e1c8dc0f25 Mon Sep 17 00:00:00 2001 From: Dadr Date: Sun, 30 Jan 2022 23:49:43 -0500 Subject: [PATCH 09/12] Update debug.py --- modules/debug.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/debug.py b/modules/debug.py index f2cfebe..bdaa43f 100755 --- a/modules/debug.py +++ b/modules/debug.py @@ -16,9 +16,12 @@ import subprocess import logging import os +import platform import datetime import sys import traceback +import json +from modules.path import path def _stringify(args): result = '' @@ -95,7 +98,7 @@ def config_version(): "origin": origin, "branch": branch, "commit": commit - } + } versionfile = open(path.CONFIGFOLDER + "/version.json", 'w+') json.dump(config, versionfile) From 45c046a4a6d2f11651c1380a846bf313e10ba02b Mon Sep 17 00:00:00 2001 From: Dadr Date: Sun, 30 Jan 2022 23:58:55 -0500 Subject: [PATCH 10/12] Update maintenance.py --- routes/maintenance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/maintenance.py b/routes/maintenance.py index 5168420..8d55bbe 100755 --- a/routes/maintenance.py +++ b/routes/maintenance.py @@ -17,6 +17,7 @@ import os import subprocess import shutil +import flask import modules.debug as debug from baseroute import BaseRoute From 3488bb675c39c2c6d84b8be0dac2eb4231e47b82 Mon Sep 17 00:00:00 2001 From: Dadr Date: Mon, 31 Jan 2022 13:57:12 -0500 Subject: [PATCH 11/12] Backport BASEDIR and bugfix on shell mime checking --- load_config.py | 4 ++-- modules/path.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/load_config.py b/load_config.py index 10dcc64..76c603e 100755 --- a/load_config.py +++ b/load_config.py @@ -53,7 +53,7 @@ print('file not found: ', updatefile) sys.exit(1) try: - filetype = subprocess.check_output(['file', '-b', '--mime-type', updatefile]).decode("utf-8") + filetype = subprocess.check_output(['file', '-b', '--mime-type', updatefile]).decode("utf-8").strip() except Exception: print('Error determining Mime-type of ', updatefile) sys.exit(1) @@ -82,7 +82,7 @@ #Un-tar the config file try: - subprocess.call(['/usr/bin/tar', '-xzf', updatefile, '-C', configdir]) + subprocess.call(['tar', '-xzf', updatefile, '-C', configdir]) except Exception: print('tar failed to unpack', updatefile, 'into', configdir) if had_config: diff --git a/modules/path.py b/modules/path.py index 8b5dbe2..8287f64 100755 --- a/modules/path.py +++ b/modules/path.py @@ -17,6 +17,7 @@ import logging class path: + BASEDIR = '/root/' CONFIGFOLDER = '/root/photoframe_config' CONFIGFILE = '/root/photoframe_config/settings.json' COLORMATCH = '/root/photoframe_config/colortemp.sh' @@ -33,6 +34,7 @@ def reassignConfigTxt(self, newconfig): path.CONFIG_TXT = newconfig def reassignBase(self, newbase): + path.BASEDIR = path.BASEDIR = newbase path.CONFIGFOLDER = path.CONFIGFOLDER.replace('/root/', newbase) path.CONFIGFILE = path.CONFIGFILE.replace('/root/', newbase) path.OPTIONSFILE = path.OPTIONSFILE.replace('/root/', newbase) From c2392a03710bc8b2abacb14aa0da25259542ccb4 Mon Sep 17 00:00:00 2001 From: Dadr Date: Mon, 31 Jan 2022 16:00:11 -0500 Subject: [PATCH 12/12] Finish Porting and debugging --- routes/maintenance.py | 5 ++++- routes/upload.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/routes/maintenance.py b/routes/maintenance.py index 8d55bbe..296e458 100755 --- a/routes/maintenance.py +++ b/routes/maintenance.py @@ -99,7 +99,7 @@ def handle(self, app, cmd): elif cmd == 'restore': if os.path.isfile("/boot/settings.tar.gz"): try: - subprocess.run(path.BASEDIR + 'photoframe/load_config.py /boot/settings.tar.gz', shell=True) + subprocess.call(path.BASEDIR + 'photoframe/load_config.py /boot/settings.tar.gz', shell=True) except: logging.info('FAILED to load new settings with: ' + path.BASEDIR + 'photoframe/load_config.py /boot/settings.tar.gz') return 'Failed to load new settings', 404 @@ -120,4 +120,7 @@ def handle(self, app, cmd): else: return 'Download failed', 404 # The route to upload settings from the browser is in routes/upload.py + elif cmd == 'restart': + subprocess.Popen('systemctl restart frame', shell=True) + return 'Restarting photoframe', 200 diff --git a/routes/upload.py b/routes/upload.py index 1a5b3ee..75c1952 100644 --- a/routes/upload.py +++ b/routes/upload.py @@ -20,6 +20,7 @@ from werkzeug.utils import secure_filename from baseroute import BaseRoute +from modules.path import path class RouteUpload(BaseRoute): def setupex(self, settingsmgr, drivermgr): @@ -74,7 +75,7 @@ def handle(self, app, item): elif item == 'config': try: - subprocess.run(path.BASEDIR + 'photoframe/load_config.py ' + filename, shell=True) + subprocess.call(path.BASEDIR + 'photoframe/load_config.py ' + filename, shell=True) except: logging.info('FAILED to load settings with: ' + path.BASEDIR + 'photoframe/load_config.py ' + filename) retval['status'] = 500