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 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-content">
<div class="modal-header">
<h5 class="modal-title">Warning</h5>
</div>
<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 class="modal-footer">
<button type="button" class="btn btn-secondary" 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-light" data-dismiss="modal">Cancel</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>

View File

@ -157,6 +157,7 @@ var settingsModal = createModal('settingsMenu');
var userDefaultsModal = createModal('userDefaults');
var usersModal = createModal('users');
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>]
function parseInvite(invite, empty = false) {
@ -961,8 +962,12 @@ document.getElementById('openSettings').onclick = function () {
triggerTooltips();
function sendConfig(modalId) {
function sendConfig(modalId, restart = false) {
let modal = document.getElementById(modalId);
modifiedConfig['restart-program'] = false;
if (restart) {
modifiedConfig['restart-program'] = true;
}
let send = JSON.stringify(modifiedConfig);
let req = new XMLHttpRequest();
req.open("POST", "/modifyConfig", true);
@ -975,6 +980,8 @@ function sendConfig(modalId) {
if (modalId != 'settingsMenu') {
settingsModal.hide();
}
} else if (restart) {
refreshModal.show();
}
}
};
@ -1010,7 +1017,8 @@ document.getElementById('settingsSave').onclick = function() {
}
}
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();
restartModal.show();
} else if (settings_changed) {

View File

@ -355,7 +355,7 @@
<div class="card-body text-center">
<h5 class="card-title">Finished!</h5>
<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>
<button id="submitButton" class="btn btn-primary">Submit</button>
</div>

View File

@ -44,7 +44,7 @@ class Jellyfin:
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
have no effect on the client's capability.
@ -61,7 +61,7 @@ class Jellyfin:
self.version = version
self.device = device
self.deviceId = deviceId
self.timeout = 30 * 60
self.timeout = cacheMinutes * 60
self.userCacheAge = time.time() - self.timeout - 1
self.userCachePublicAge = self.userCacheAge
self.useragent = f"{self.client}/{self.version}"
@ -83,6 +83,16 @@ class Jellyfin:
self.info = requests.get(self.server + "/System/Info/Public").json()
except:
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):
"""

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 web_log as log
import os
import psutil
import sys
if first_run:
@ -51,8 +53,16 @@ if first_run:
with open(config_path, "w") as config_file:
temp_config.write(config_file)
log.debug("Config written")
# ugly exit, sorry
os._exit(1)
log.info('Restarting...')
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()
@app.route("/testJF", methods=["GET", "POST"])

View File

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

View File

@ -6,6 +6,9 @@ import datetime
import secrets
import time
import threading
import os
import sys
import psutil
from jellyfin_accounts import (
config,
config_path,
@ -440,7 +443,7 @@ def modifyConfig():
)
temp_config.read(config_path)
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]:
temp_config[section][item] = data[section][item]
data[section][item] = True
@ -448,7 +451,18 @@ def modifyConfig():
with open(config_path, "w") as config_file:
temp_config.write(config_file)
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()

26
poetry.lock generated
View File

@ -242,6 +242,17 @@ optional = false
python-versions = "*"
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]]
category = "main"
description = "C parser in Python"
@ -422,7 +433,7 @@ dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-
watchdog = ["watchdog"]
[metadata]
content-hash = "fa8b5fb1ded41b673b8062a2bfc6467e6a484ff62b578147bec001d7d9d8ca16"
content-hash = "1c2741c9be187d9d0be662509fb4a87f5978e5f44420e5049a20504824c29a59"
python-versions = "^3.6"
[metadata.files]
@ -628,6 +639,19 @@ pathspec = [
pathtools = [
{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 = [
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},

View File

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