Self-restarting for config changes

When changing settings that need restart, the option is now available to
do it automatically. Functions on linux at least, might need testing on
    windows.
This commit is contained in:
Harvey Tindall 2020-07-20 15:37:19 +01:00
parent ef8ff531e3
commit 2e20466925
13 changed files with 94 additions and 45 deletions

View File

@ -1,7 +0,0 @@
A user was created using code {{ code }}.
Name: {{ username }}
Address: {{ address }}
Time: {{ time }}
Note: Notification emails can be toggled on the admin dashboard.

View File

@ -1,10 +0,0 @@
Hi {{ username }},
Someone has recently requests a password reset on Jellyfin.
If this was you, enter the below pin into the prompt.
This code will expire on {{ expiry_date }}, at {{ expiry_time }} , which is in {{ expires_in }}.
If this wasn't you, please ignore this email.
PIN: {{ pin }}
{{ message }}

View File

@ -1,5 +0,0 @@
Invite expired.
Code {{ code }} expired at {{ expiry }}.
Note: Notification emails can be toggled on the admin dashboard.

View File

@ -1,8 +0,0 @@
Hi,
You've been invited to Jellyfin.
To join, follow the below link.
This invite will expire on {{ expiry_date }}, at {{ expiry_time }}, which is in {{ expires_in }}, so act quick.
{{ invite_link }}
{{ message }}

View File

@ -214,18 +214,31 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="restartModal" role="dialog" aria-labelledby"Restart Warning" aria-hidden="true"> <div class="modal fade" id="restartModal" role="dialog" aria-labelledby="Restart Warning" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Warning</h5> <h5 class="modal-title">Warning</h5>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>A restart is needed to apply some settings. This must be done manually. Apply now?</p> <p>A restart is needed to apply some settings. Restart now, later, or cancel?</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-light" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="applyRestarts" data-dismiss="alert">Apply</button> <button type="button" class="btn btn-secondary" id="applyRestarts" data-dismiss="modal">Apply, Restart later</button>
<button type="button" class="btn btn-primary" id="applyAndRestart" data-dismiss="modal">Apply &amp; Restart</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="refreshModal" role="dialog" aria-labelledby="Refresh page notice" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Settings applied.</h5>
</div>
<div class="modal-body">
<p>Refresh the page in a few seconds.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -157,6 +157,7 @@ var settingsModal = createModal('settingsMenu');
var userDefaultsModal = createModal('userDefaults'); var userDefaultsModal = createModal('userDefaults');
var usersModal = createModal('users'); var usersModal = createModal('users');
var restartModal = createModal('restartModal'); var restartModal = createModal('restartModal');
var refreshModal = createModal('refreshModal');
// Parsed invite: [<code>, <expires in _>, <1: Empty invite (no delete/link), 0: Actual invite>, <email address>, <remaining uses>, [<used-by>], <date created>, <notify on expiry>, <notify on creation>] // Parsed invite: [<code>, <expires in _>, <1: Empty invite (no delete/link), 0: Actual invite>, <email address>, <remaining uses>, [<used-by>], <date created>, <notify on expiry>, <notify on creation>]
function parseInvite(invite, empty = false) { function parseInvite(invite, empty = false) {
@ -961,8 +962,12 @@ document.getElementById('openSettings').onclick = function () {
triggerTooltips(); triggerTooltips();
function sendConfig(modalId) { function sendConfig(modalId, restart = false) {
let modal = document.getElementById(modalId); let modal = document.getElementById(modalId);
modifiedConfig['restart-program'] = false;
if (restart) {
modifiedConfig['restart-program'] = true;
}
let send = JSON.stringify(modifiedConfig); let send = JSON.stringify(modifiedConfig);
let req = new XMLHttpRequest(); let req = new XMLHttpRequest();
req.open("POST", "/modifyConfig", true); req.open("POST", "/modifyConfig", true);
@ -975,6 +980,8 @@ function sendConfig(modalId) {
if (modalId != 'settingsMenu') { if (modalId != 'settingsMenu') {
settingsModal.hide(); settingsModal.hide();
} }
} else if (restart) {
refreshModal.show();
} }
} }
}; };
@ -1010,7 +1017,8 @@ document.getElementById('settingsSave').onclick = function() {
} }
} }
if (restart_setting_changed) { if (restart_setting_changed) {
document.getElementById('applyRestarts').onclick = function(){sendConfig('restartModal');}; document.getElementById('applyRestarts').onclick = function(){ sendConfig('restartModal'); };
document.getElementById('applyAndRestart').onclick = function(){ sendConfig('restartModal', restart=true); };
settingsModal.hide(); settingsModal.hide();
restartModal.show(); restartModal.show();
} else if (settings_changed) { } else if (settings_changed) {

View File

@ -355,7 +355,7 @@
<div class="card-body text-center"> <div class="card-body text-center">
<h5 class="card-title">Finished!</h5> <h5 class="card-title">Finished!</h5>
<p class="card-text"> <p class="card-text">
Press the button below to submit your settings. The program will quit, so run it again, then refresh this page. Press the button below to submit your settings. The program will restart. Once it's done, refresh this page.
</p> </p>
<button id="submitButton" class="btn btn-primary">Submit</button> <button id="submitButton" class="btn btn-primary">Submit</button>
</div> </div>

View File

@ -44,7 +44,7 @@ class Jellyfin:
pass pass
def __init__(self, server, client, version, device, deviceId): def __init__(self, server, client, version, device, deviceId, cacheMinutes=30):
""" """
Initializes the Jellyfin object. All parameters except server Initializes the Jellyfin object. All parameters except server
have no effect on the client's capability. have no effect on the client's capability.
@ -61,7 +61,7 @@ class Jellyfin:
self.version = version self.version = version
self.device = device self.device = device
self.deviceId = deviceId self.deviceId = deviceId
self.timeout = 30 * 60 self.timeout = cacheMinutes * 60
self.userCacheAge = time.time() - self.timeout - 1 self.userCacheAge = time.time() - self.timeout - 1
self.userCachePublicAge = self.userCacheAge self.userCachePublicAge = self.userCacheAge
self.useragent = f"{self.client}/{self.version}" self.useragent = f"{self.client}/{self.version}"
@ -84,6 +84,16 @@ class Jellyfin:
except: except:
pass pass
def reloadCache(self):
""" Forces a reload of the user caches """
self.userCachePublicAge = time.time() - self.timeout - 1
self.getUsers()
try:
self.userCacheAge = self.userCachePublicAge
self.getUsers(public=False)
except self.AuthenticationRequiredError:
pass
def getUsers(self, username: str = "all", userId: str = "all", public: bool = True): def getUsers(self, username: str = "all", userId: str = "all", public: bool = True):
""" """
Returns details on user(s), such as ID, Name, Policy. Returns details on user(s), such as ID, Name, Policy.

View File

@ -5,6 +5,8 @@ from jellyfin_accounts.jf_api import Jellyfin
from jellyfin_accounts import config, config_path, app, first_run, resp from jellyfin_accounts import config, config_path, app, first_run, resp
from jellyfin_accounts import web_log as log from jellyfin_accounts import web_log as log
import os import os
import psutil
import sys
if first_run: if first_run:
@ -51,8 +53,16 @@ if first_run:
with open(config_path, "w") as config_file: with open(config_path, "w") as config_file:
temp_config.write(config_file) temp_config.write(config_file)
log.debug("Config written") log.debug("Config written")
# ugly exit, sorry log.info('Restarting...')
os._exit(1) try:
p = psutil.Process(os.getpid())
for handler in p.open_files() + p.connections():
os.close(handler.fd)
except:
pass
python = sys.executable
os.execl(python, python, *sys.argv)
return resp() return resp()
@app.route("/testJF", methods=["GET", "POST"]) @app.route("/testJF", methods=["GET", "POST"])

View File

@ -28,7 +28,6 @@ def page_not_found(e):
@app.route("/", methods=["GET", "POST"]) @app.route("/", methods=["GET", "POST"])
def admin(): def admin():
# return app.send_static_file('admin.html')
return render_template( return render_template(
"admin.html", "admin.html",
bs5=config.getboolean("ui", "bs5"), bs5=config.getboolean("ui", "bs5"),

View File

@ -6,6 +6,9 @@ import datetime
import secrets import secrets
import time import time
import threading import threading
import os
import sys
import psutil
from jellyfin_accounts import ( from jellyfin_accounts import (
config, config,
config_path, config_path,
@ -440,7 +443,7 @@ def modifyConfig():
) )
temp_config.read(config_path) temp_config.read(config_path)
for section in data: for section in data:
if section in temp_config: if section in temp_config and 'restart-program' not in section:
for item in data[section]: for item in data[section]:
temp_config[section][item] = data[section][item] temp_config[section][item] = data[section][item]
data[section][item] = True data[section][item] = True
@ -448,7 +451,18 @@ def modifyConfig():
with open(config_path, "w") as config_file: with open(config_path, "w") as config_file:
temp_config.write(config_file) temp_config.write(config_file)
config.trigger_reload() config.trigger_reload()
log.info("Config written. Restart may be needed to load settings.") log.info("Config written.")
if 'restart-program' in data:
if data['restart-program']:
log.info('Restarting...')
try:
proc = psutil.Process(os.getpid())
for handler in proc.open_files() + proc.connections():
os.close(handler.fd)
except Exception as e:
log.error(f'Failed restart: {type(e).__name__}')
python = sys.executable
os.execl(python, python, *sys.argv)
return resp() return resp()

26
poetry.lock generated
View File

@ -242,6 +242,17 @@ optional = false
python-versions = "*" python-versions = "*"
version = "0.1.2" version = "0.1.2"
[[package]]
category = "main"
description = "Cross-platform lib for process and system monitoring in Python."
name = "psutil"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "5.7.2"
[package.extras]
test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"]
[[package]] [[package]]
category = "main" category = "main"
description = "C parser in Python" description = "C parser in Python"
@ -422,7 +433,7 @@ dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-
watchdog = ["watchdog"] watchdog = ["watchdog"]
[metadata] [metadata]
content-hash = "fa8b5fb1ded41b673b8062a2bfc6467e6a484ff62b578147bec001d7d9d8ca16" content-hash = "1c2741c9be187d9d0be662509fb4a87f5978e5f44420e5049a20504824c29a59"
python-versions = "^3.6" python-versions = "^3.6"
[metadata.files] [metadata.files]
@ -628,6 +639,19 @@ pathspec = [
pathtools = [ pathtools = [
{file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"}, {file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"},
] ]
psutil = [
{file = "psutil-5.7.2-cp27-none-win32.whl", hash = "sha256:f2018461733b23f308c298653c8903d32aaad7873d25e1d228765e91ae42c3f2"},
{file = "psutil-5.7.2-cp27-none-win_amd64.whl", hash = "sha256:66c18ca7680a31bf16ee22b1d21b6397869dda8059dbdb57d9f27efa6615f195"},
{file = "psutil-5.7.2-cp35-cp35m-win32.whl", hash = "sha256:5e9d0f26d4194479a13d5f4b3798260c20cecf9ac9a461e718eb59ea520a360c"},
{file = "psutil-5.7.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4080869ed93cce662905b029a1770fe89c98787e543fa7347f075ade761b19d6"},
{file = "psutil-5.7.2-cp36-cp36m-win32.whl", hash = "sha256:d8a82162f23c53b8525cf5f14a355f5d1eea86fa8edde27287dd3a98399e4fdf"},
{file = "psutil-5.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0ee3c36428f160d2d8fce3c583a0353e848abb7de9732c50cf3356dd49ad63f8"},
{file = "psutil-5.7.2-cp37-cp37m-win32.whl", hash = "sha256:ff1977ba1a5f71f89166d5145c3da1cea89a0fdb044075a12c720ee9123ec818"},
{file = "psutil-5.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a5b120bb3c0c71dfe27551f9da2f3209a8257a178ed6c628a819037a8df487f1"},
{file = "psutil-5.7.2-cp38-cp38-win32.whl", hash = "sha256:10512b46c95b02842c225f58fa00385c08fa00c68bac7da2d9a58ebe2c517498"},
{file = "psutil-5.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:68d36986ded5dac7c2dcd42f2682af1db80d4bce3faa126a6145c1637e1b559f"},
{file = "psutil-5.7.2.tar.gz", hash = "sha256:90990af1c3c67195c44c9a889184f84f5b2320dce3ee3acbd054e3ba0b4a7beb"},
]
pycparser = [ pycparser = [
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},

View File

@ -29,6 +29,7 @@ python-dateutil = "^2.8.1"
watchdog = "^0.10.2" watchdog = "^0.10.2"
waitress = "^1.4.3" waitress = "^1.4.3"
packaging = "^20.4" packaging = "^20.4"
psutil = "^5.7.2"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
neovim = "^0.3.1" neovim = "^0.3.1"