Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16e979fe77 | ||
|
|
03ec3f0c68 |
@@ -104,6 +104,18 @@ def safe_join(base_dir, user_filename):
|
|||||||
return candidate
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
|
UPLOADED_PLAYLIST_RE = re.compile(r'^upload_[0-9a-f]{32}\.m3u$')
|
||||||
|
|
||||||
|
|
||||||
|
def validate_id_playlist(value):
|
||||||
|
"""Playlist ids are UUIDs; anything else is rejected (they end up in file paths
|
||||||
|
and Mongo collection names)."""
|
||||||
|
try:
|
||||||
|
return str(uuid.UUID(str(value)))
|
||||||
|
except (ValueError, TypeError, AttributeError):
|
||||||
|
raise ValueError('invalid id_playlist')
|
||||||
|
|
||||||
|
|
||||||
def findInfo(tag, s):
|
def findInfo(tag, s):
|
||||||
end = '"'
|
end = '"'
|
||||||
if (s.find(tag)>-1):
|
if (s.find(tag)>-1):
|
||||||
@@ -451,8 +463,16 @@ async def analyse_m3u_file(request):
|
|||||||
playlist_comment = data.get('comment', '').strip()
|
playlist_comment = data.get('comment', '').strip()
|
||||||
|
|
||||||
|
|
||||||
new_path_filename = "./" + UPLOAD_PLAYLIST_DIR + '/' + id_playlist + '.m3u'
|
try:
|
||||||
old_path_filename = "./" + UPLOAD_PLAYLIST_DIR + '/' +filename
|
id_playlist = validate_id_playlist(id_playlist)
|
||||||
|
if not UPLOADED_PLAYLIST_RE.match(filename or ''):
|
||||||
|
raise ValueError('invalid filename')
|
||||||
|
new_path_filename = safe_join(UPLOAD_PLAYLIST_DIR, id_playlist + '.m3u')
|
||||||
|
old_path_filename = safe_join(UPLOAD_PLAYLIST_DIR, filename)
|
||||||
|
except ValueError as exc:
|
||||||
|
return web.json_response({'error': str(exc)}, status=400)
|
||||||
|
if not os.path.isfile(old_path_filename):
|
||||||
|
return web.json_response({'error': 'uploaded file not found'}, status=400)
|
||||||
os.rename(old_path_filename,new_path_filename)
|
os.rename(old_path_filename,new_path_filename)
|
||||||
|
|
||||||
collection_channels = get_playlist_collection(id_playlist)
|
collection_channels = get_playlist_collection(id_playlist)
|
||||||
@@ -721,27 +741,36 @@ async def handle_upload(request):
|
|||||||
# Handle both form field and file in one pass
|
# Handle both form field and file in one pass
|
||||||
async for part in reader:
|
async for part in reader:
|
||||||
if part.name == "id_playlist_to_action":
|
if part.name == "id_playlist_to_action":
|
||||||
id_playlist_data = await part.text()
|
id_playlist_data = (await part.text()).strip()
|
||||||
if len(id_playlist_data) == 0:
|
if len(id_playlist_data) == 0:
|
||||||
print('No playlist to update', flush=True)
|
print('No playlist to update', flush=True)
|
||||||
id_playlist = str(uuid.uuid4())
|
id_playlist = str(uuid.uuid4())
|
||||||
else:
|
else:
|
||||||
id_playlist = id_playlist_data
|
try:
|
||||||
|
id_playlist = validate_id_playlist(id_playlist_data)
|
||||||
|
except ValueError:
|
||||||
|
if filepath and os.path.exists(filepath):
|
||||||
|
os.remove(filepath)
|
||||||
|
return web.Response(text="Invalid id_playlist.", status=400)
|
||||||
print('playlist to update :', id_playlist, flush=True)
|
print('playlist to update :', id_playlist, flush=True)
|
||||||
|
|
||||||
elif part.name == "file":
|
elif part.name == "file":
|
||||||
filename = part.filename
|
if not part.filename:
|
||||||
if not filename:
|
|
||||||
return web.Response(text="No file provided.", status=400)
|
return web.Response(text="No file provided.", status=400)
|
||||||
|
|
||||||
filepath = os.path.join(UPLOAD_PLAYLIST_DIR, filename)
|
# Never trust the client-supplied filename: always store under a
|
||||||
|
# server-generated .m3u name inside the playlists directory.
|
||||||
|
filename = f"upload_{uuid.uuid4().hex}.m3u"
|
||||||
|
filepath = safe_join(UPLOAD_PLAYLIST_DIR, filename)
|
||||||
|
|
||||||
with open(filepath, "wb") as f:
|
with open(filepath, "wb") as f:
|
||||||
while chunk := await part.read_chunk():
|
while chunk := await part.read_chunk():
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
|
|
||||||
if filepath:
|
if filepath:
|
||||||
return web.json_response({'status': 'ok', 'id_playlist': id_playlist})
|
if id_playlist is None:
|
||||||
|
id_playlist = str(uuid.uuid4())
|
||||||
|
return web.json_response({'status': 'ok', 'id_playlist': id_playlist, 'filename': filename})
|
||||||
|
|
||||||
return web.Response(text="No file field found in the request.", status=400)
|
return web.Response(text="No file field found in the request.", status=400)
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -1114,8 +1114,8 @@ def communication(data):
|
|||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
sendMessage(toSidDevice, fromSidDevice, 'warning', 'Invalid snapshot interval', 0)
|
sendMessage(toSidDevice, fromSidDevice, 'warning', 'Invalid snapshot interval', 0)
|
||||||
else:
|
else:
|
||||||
if interval_seconds < 2:
|
if interval_seconds < 1:
|
||||||
sendMessage(toSidDevice, fromSidDevice, 'warning', 'Snapshot interval must be at least 2 seconds', 0)
|
sendMessage(toSidDevice, fromSidDevice, 'warning', 'Snapshot interval must be at least 1 second', 0)
|
||||||
else:
|
else:
|
||||||
SNAPSHOT_INTERVAL_SECONDS = interval_seconds
|
SNAPSHOT_INTERVAL_SECONDS = interval_seconds
|
||||||
sendMessage(
|
sendMessage(
|
||||||
@@ -1302,7 +1302,7 @@ def initIPTV():
|
|||||||
|
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
num_version='5.0.6'
|
num_version='5.0.7'
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
|
|
||||||
@@ -1328,7 +1328,7 @@ mediaRunning = ''
|
|||||||
status_vpn = 'disconnected'
|
status_vpn = 'disconnected'
|
||||||
data_running = {}
|
data_running = {}
|
||||||
auto_snapshot_enabled = True
|
auto_snapshot_enabled = True
|
||||||
SNAPSHOT_INTERVAL_SECONDS = 10
|
SNAPSHOT_INTERVAL_SECONDS = 2
|
||||||
POLLING_INTERVAL_SECONDS = 10
|
POLLING_INTERVAL_SECONDS = 10
|
||||||
SOCKET_CONNECT_TIMEOUT = 10
|
SOCKET_CONNECT_TIMEOUT = 10
|
||||||
SOCKET_RETRY_MIN_INTERVAL = 3
|
SOCKET_RETRY_MIN_INTERVAL = 3
|
||||||
|
|||||||
+24
-6
@@ -1220,7 +1220,7 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div id="snapshotIntervalSettingsAlert"></div>
|
<div id="snapshotIntervalSettingsAlert"></div>
|
||||||
<label for="snapshot_interval_input" class="form-label">Seconds between 2 snapshots</label>
|
<label for="snapshot_interval_input" class="form-label">Seconds between 2 snapshots</label>
|
||||||
<input type="number" min="2" step="1" class="form-control" id="snapshot_interval_input" placeholder="10">
|
<input type="number" min="1" step="1" class="form-control" id="snapshot_interval_input" placeholder="2">
|
||||||
<div class="form-text">This value is applied to the currently selected device.</div>
|
<div class="form-text">This value is applied to the currently selected device.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@@ -1528,7 +1528,7 @@
|
|||||||
var PROGRESS_STALE_MS = 15000
|
var PROGRESS_STALE_MS = 15000
|
||||||
var playlistImportProgress = 0
|
var playlistImportProgress = 0
|
||||||
var playlistImportInFlight = false
|
var playlistImportInFlight = false
|
||||||
var selectedDeviceSnapshotIntervalSeconds = 10
|
var selectedDeviceSnapshotIntervalSeconds = 2
|
||||||
var currentSelectedDeviceData = null
|
var currentSelectedDeviceData = null
|
||||||
|
|
||||||
var socket = io('https://iptv.mrk.ovh', {
|
var socket = io('https://iptv.mrk.ovh', {
|
||||||
@@ -1710,6 +1710,22 @@
|
|||||||
$placeholder.hide();
|
$placeholder.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reloads the currently selected device's snapshot image on its own fast
|
||||||
|
// timer, independent of the 5s full device-list poll, so a low snapshot
|
||||||
|
// interval (see Settings > Snapshot interval) actually feels near-live.
|
||||||
|
function refreshSelectedDeviceSnapshotFast() {
|
||||||
|
var device = currentSelectedDeviceData;
|
||||||
|
if (!device || !device.snapshot_url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var dataRunning = device.data_running || {};
|
||||||
|
var hasChannel = Object.keys(dataRunning).length > 0 && dataRunning.name_channel;
|
||||||
|
if (!hasChannel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('#selectedDeviceSnapshot').attr('src', device.snapshot_url + '?t=' + Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
function updateAutoSnapshotToggle(device) {
|
function updateAutoSnapshotToggle(device) {
|
||||||
var $toggle = $('#autoSnapshotToggle');
|
var $toggle = $('#autoSnapshotToggle');
|
||||||
if (!device || !device.id_device) {
|
if (!device || !device.id_device) {
|
||||||
@@ -1723,12 +1739,12 @@
|
|||||||
|
|
||||||
function updateSnapshotIntervalState(device) {
|
function updateSnapshotIntervalState(device) {
|
||||||
if (!device || !device.id_device) {
|
if (!device || !device.id_device) {
|
||||||
selectedDeviceSnapshotIntervalSeconds = 10;
|
selectedDeviceSnapshotIntervalSeconds = 2;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var interval = Number(device.snapshot_interval_seconds);
|
var interval = Number(device.snapshot_interval_seconds);
|
||||||
if (!Number.isFinite(interval) || interval < 2) {
|
if (!Number.isFinite(interval) || interval < 1) {
|
||||||
interval = selectedDeviceSnapshotIntervalSeconds;
|
interval = selectedDeviceSnapshotIntervalSeconds;
|
||||||
}
|
}
|
||||||
selectedDeviceSnapshotIntervalSeconds = interval;
|
selectedDeviceSnapshotIntervalSeconds = interval;
|
||||||
@@ -1789,6 +1805,7 @@
|
|||||||
$(window).on('scroll resize', lazyLoad);
|
$(window).on('scroll resize', lazyLoad);
|
||||||
updateListDevices()
|
updateListDevices()
|
||||||
setInterval(updateListDevices, 5000)
|
setInterval(updateListDevices, 5000)
|
||||||
|
setInterval(refreshSelectedDeviceSnapshotFast, 1000)
|
||||||
update_list_playlist()
|
update_list_playlist()
|
||||||
display_most_frequent()
|
display_most_frequent()
|
||||||
setInterval(display_most_frequent, 15000)
|
setInterval(display_most_frequent, 15000)
|
||||||
@@ -2986,6 +3003,7 @@
|
|||||||
$('#add_playlist_alert').html('<div class="alert alert-success" role="alert">Upload successful!</div>');
|
$('#add_playlist_alert').html('<div class="alert alert-success" role="alert">Upload successful!</div>');
|
||||||
var response = JSON.parse(xhr.response);
|
var response = JSON.parse(xhr.response);
|
||||||
var id_playlist = response['id_playlist'];
|
var id_playlist = response['id_playlist'];
|
||||||
|
$("#fileName").val(response['filename']);
|
||||||
if (response.status == 'ok') {
|
if (response.status == 'ok') {
|
||||||
$.post('analyse_m3u_file', {
|
$.post('analyse_m3u_file', {
|
||||||
'id_playlist': id_playlist,
|
'id_playlist': id_playlist,
|
||||||
@@ -3158,9 +3176,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var intervalValue = Number($('#snapshot_interval_input').val());
|
var intervalValue = Number($('#snapshot_interval_input').val());
|
||||||
if (!Number.isInteger(intervalValue) || intervalValue < 2) {
|
if (!Number.isInteger(intervalValue) || intervalValue < 1) {
|
||||||
$('#snapshot_interval_input').addClass('is-invalid');
|
$('#snapshot_interval_input').addClass('is-invalid');
|
||||||
$('#snapshotIntervalSettingsAlert').html('<div class="alert alert-danger" role="alert">Enter an integer value greater than or equal to 2.</div>');
|
$('#snapshotIntervalSettingsAlert').html('<div class="alert alert-danger" role="alert">Enter an integer value greater than or equal to 1.</div>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user