diff --git a/analytics.py b/analytics.py index 8c6cbf1..d1983a9 100644 --- a/analytics.py +++ b/analytics.py @@ -7,8 +7,10 @@ from collections import defaultdict import json import os import auth +import helperfunctions as hf file_access_temp = [] +folder_today = [] app_config = auth.return_app_config() @@ -98,11 +100,13 @@ def parse_timestamp(ts_str): raise def log_file_access(rel_path, filesize, mime, ip_address, user_agent, device_id, cached): - """Insert a file access record into the database and prune entries older than 10 minutes.""" - global file_access_temp - # Create a timezone-aware timestamp (local time with offset) - timestamp = datetime.now(timezone.utc).astimezone() - iso_ts = timestamp.isoformat() + """Insert a file access record into the database and prune entries older than 10 minutes, + and track today’s files separately in folder_today.""" + global file_access_temp, folder_today + + # Create a timezone-aware timestamp + now = datetime.now(timezone.utc).astimezone() + iso_ts = now.isoformat() # Convert the IP address to a location city, country = lookup_location(ip_address) @@ -113,18 +117,47 @@ def log_file_access(rel_path, filesize, mime, ip_address, user_agent, device_id, (timestamp, rel_path, filesize, mime, city, country, user_agent, device_id, cached) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (iso_ts, rel_path, filesize, mime, city, country, user_agent, device_id, cached)) - - # Remove entries older than 10 minutes using our robust parser. - cutoff_time = datetime.now(timezone.utc).astimezone() - timedelta(minutes=10) + + # Prune temp entries older than 10 minutes + cutoff = now - timedelta(minutes=10) file_access_temp[:] = [ entry for entry in file_access_temp - if parse_timestamp(entry[0]) >= cutoff_time + if parse_timestamp(entry[0]) >= cutoff ] - - # Add the new entry at the beginning of the list - file_access_temp.insert(0, [iso_ts, rel_path, filesize, mime, f"{city}, {country}", user_agent, device_id, cached]) + + # Keep only today's entries in folder_today + date_str = iso_ts.split('T', 1)[0] + folder_today[:] = [ + entry for entry in folder_today + if entry['date_str'] == date_str + ] + + # If this new access is from today, record it + # Compare the helper’s YYYY-MM-DD string to today’s ISO date string + date_from_path = hf.extract_date_from_string(rel_path) + + if date_from_path == date_str: + # get just the folder part (everything before the final '/') + folder_path = rel_path.rsplit('/', 1)[0] if '/' in rel_path else rel_path + # only append if that folder isn't already in folder_today + if not any(entry['rel_path'] == folder_path for entry in folder_today): + folder_today.append({'date_str': date_str, 'rel_path': folder_path}) + + # Finally, insert the new access at the top of the temp log + file_access_temp.insert(0, [ + iso_ts, + rel_path, + filesize, + mime, + f"{city}, {country}", + user_agent, + device_id, + cached + ]) + return True + def return_file_access(): """Return recent file access logs from memory (the last 10 minutes).""" global file_access_temp @@ -140,6 +173,14 @@ def return_file_access(): else: return [] + +def return_folder_today(): + global folder_today + if folder_today: + return folder_today + else: + return [] + def songs_dashboard(): # — SESSION & PARAM HANDLING (unchanged) — if 'songs_dashboard_timeframe' not in session: diff --git a/app.py b/app.py index 5d3404c..b990e81 100755 --- a/app.py +++ b/app.py @@ -23,6 +23,7 @@ import search import auth import analytics as a import folder_secret_config_editor as fsce +import helperfunctions as hf app_config = auth.return_app_config() BASE_DIR = os.path.realpath(app_config['BASE_DIR']) @@ -243,6 +244,7 @@ def serve_sw(): @app.route('/api/path/') @auth.require_secret def api_browse(subpath): + if subpath == '': # root directory foldernames = [] for foldername, _ in session['folders'].items(): @@ -251,7 +253,20 @@ def api_browse(subpath): return jsonify({ 'breadcrumbs': generate_breadcrumbs(), 'directories': foldernames, - 'files': [] + 'files': [], + 'folder_today': a.return_folder_today() + }) + + if subpath == 'today': + foldernames = [] + for item in a.return_folder_today(): + foldernames.append({'name': item['rel_path'], 'path': item['rel_path']}) + + return jsonify({ + 'breadcrumbs': generate_breadcrumbs(), + 'directories': foldernames, + 'files': [], + 'folder_today': [] }) root, *relative_parts = subpath.split('/') @@ -283,7 +298,8 @@ def api_browse(subpath): response = { 'breadcrumbs': breadcrumbs, 'directories': directories, - 'files': files + 'files': files, + 'folder_today': a.return_folder_today() } # If a filename was selected include it. diff --git a/helperfunctions.py b/helperfunctions.py new file mode 100644 index 0000000..2ca5124 --- /dev/null +++ b/helperfunctions.py @@ -0,0 +1,46 @@ +from datetime import datetime +import re + +def extract_date_from_string(string_with_date): + # grab X.Y.Z where X,Y,Z are 1–4 digits + m = re.search(r'(\d{1,4}\.\d{1,2}\.\d{1,4})', string_with_date) + if not m: + return None + + date_str = m.group(1) + parts = date_str.split('.') + + # 1) Unambiguous “last group = YYYY” + if len(parts) == 3 and len(parts[2]) == 4: + fmt = '%d.%m.%Y' + + # 2) Unambiguous “first group = YYYY” + elif len(parts) == 3 and len(parts[0]) == 4: + fmt = '%Y.%m.%d' + + # 3) Ambiguous “XX.XX.XX” → prefer DD.MM.YY, fallback to YY.MM.DD + elif len(parts) == 3 and all(len(p) == 2 for p in parts): + # try last-group-as-year first + try: + dt = datetime.strptime(date_str, '%d.%m.%y') + return dt.strftime('%Y-%m-%d') + except ValueError: + # fallback to first-group-as-year + fmt = '%y.%m.%d' + + else: + # optional: handle ISO with dashes + if '-' in date_str: + try: + dt = datetime.strptime(date_str, '%Y-%m-%d') + return dt.strftime('%Y-%m-%d') + except ValueError: + return None + return None + + # parse with whichever fmt we settled on + try: + dt = datetime.strptime(date_str, fmt) + return dt.strftime('%Y-%m-%d') + except ValueError: + return None \ No newline at end of file diff --git a/static/app.js b/static/app.js index 3c4b1e1..43e2b5c 100644 --- a/static/app.js +++ b/static/app.js @@ -111,6 +111,9 @@ function renderContent(data) { contentHTML += ''; } else { contentHTML += '