Compare commits

...

6 Commits

Author SHA1 Message Date
9173247212 fix toplist folder 2025-05-29 15:08:14 +02:00
7dd107fc72 add top list folder 2025-05-29 12:38:11 +00:00
1eb7148498 add Title in top 20 2025-05-29 11:56:13 +00:00
08b093b63c possible fix for not working downloads 2025-05-29 11:45:43 +00:00
9d39f7178d add categories to dashboard 2025-05-29 09:41:12 +00:00
7c8fd33b69 add category to top 20 files 2025-05-29 09:22:30 +00:00
7 changed files with 168 additions and 51 deletions

View File

@ -4,6 +4,7 @@ from datetime import datetime, timedelta, timezone
import geoip2.database
from auth import require_secret
from collections import defaultdict
import pandas as pd
import json
import os
import auth
@ -383,12 +384,29 @@ def dashboard():
WHERE timestamp >= ? {filetype_filter_sql}
GROUP BY rel_path
ORDER BY access_count DESC
LIMIT 20
LIMIT 1000
'''
with log_db:
cursor = log_db.execute(query, params_for_filter)
rows = cursor.fetchall()
# Convert rows to a list of dictionaries and add category
rows = [
{
'rel_path': rel_path,
'access_count': access_count,
'category': hf.extract_structure_from_string(rel_path)[0]
}
for rel_path, access_count in rows
]
top20_overall = rows[:20]
top20_predigt = [r for r in rows if r['category'] == 'Predigt'][:20]
top20_chor = [r for r in rows if r['category'] == 'Chor'][:20]
top20_ggesang = [r for r in rows if r['category'] == 'Gemeinsamer Gesang'][:20]
top20_glied = [r for r in rows if r['category'] == 'Gruppenlied'][:20]
# 2. Distinct device trend
# We'll group by hour if "today", by day if "7days"/"30days", by month if "365days"
if session['timeframe'] == 'last24hours':
@ -588,16 +606,17 @@ def dashboard():
location_data.sort(key=lambda x: x['count'], reverse=True)
location_data = location_data[:20]
# Convert the top-files rows to a list of dictionaries
rows = [dict(rel_path=r[0], access_count=r[1]) for r in rows]
title_short = app_config.get('TITLE_SHORT', 'Default Title')
title_long = app_config.get('TITLE_LONG' , 'Default Title')
return render_template(
"dashboard.html",
timeframe=session['timeframe'],
rows=rows,
top20_overall = top20_overall,
top20_predigt = top20_predigt,
top20_chor = top20_chor,
top20_ggesang = top20_ggesang,
top20_glied = top20_glied,
distinct_device_data=distinct_device_data,
user_agent_data=user_agent_data,
folder_data=folder_data,
@ -614,7 +633,6 @@ def dashboard():
def export_to_excel():
"""Export search_db to an Excel file and store it locally."""
import pandas as pd
# Query all data from the search_db
query = "SELECT * FROM files"

22
app.py
View File

@ -274,6 +274,28 @@ def api_browse(subpath):
'folder_today': [],
'folder_yesterday': []
})
if subpath.startswith('toplist'):
foldernames = []
files = []
split_path = subpath.split('/')
if len(split_path) == 1 and split_path[0] == 'toplist':
foldernames = [
{'name': 'Predigten', 'path': 'toplist/Predigt'},
{'name': 'Chorlieder', 'path': 'toplist/Chor'},
{'name': 'Gemeinsamer Gesang', 'path': 'toplist/Gemeinsamer Gesang'},
{'name': 'Gruppenlieder', 'path': 'toplist/Gruppenlied'},
]
elif len(split_path) > 1 and split_path[0] == 'toplist':
files = hf.generate_top_list(split_path[1])
return jsonify({
'breadcrumbs': generate_breadcrumbs(subpath),
'directories': foldernames,
'files': files
})
root, *relative_parts = subpath.split('/')
base_path = session['folders'][root]

View File

@ -1,5 +1,10 @@
from datetime import datetime
import re
import os
import sqlite3
from datetime import datetime, timedelta
log_db = sqlite3.connect("access_log.db", check_same_thread=False)
def extract_date_from_string(string_with_date):
# grab X.Y.Z where X,Y,Z are 14 digits
@ -43,4 +48,95 @@ def extract_date_from_string(string_with_date):
dt = datetime.strptime(date_str, fmt)
return dt.strftime('%Y-%m-%d')
except ValueError:
return None
return None
def extract_structure_from_string(input_string):
# extract category and titel from filename
filename_ext = os.path.splitext(input_string)[0]
left_side, right_side = filename_ext.split('-', 1) if '-' in filename_ext else (filename_ext, None)
try:
int(left_side.strip())
# first part is only a number
previous_right_side = right_side
left_side, right_side = previous_right_side.split('-', 1) if '-' in previous_right_side else (previous_right_side, None)
except:
# first part not a number
pass
if 'predig' in left_side.lower():
category = 'Predigt'
elif 'wort' in left_side.lower() or 'einladung' in left_side.lower():
category = 'Vorwort'
elif 'chor' in left_side.lower():
category = 'Chor'
elif 'orchester' in left_side.lower():
category = 'Orchester'
elif 'gruppenlied' in left_side.lower() or 'jugendlied' in left_side.lower():
category = 'Gruppenlied'
elif 'gemeinsam' in left_side.lower() or 'gesang' in left_side.lower() or 'lied' in left_side.lower():
category = 'Gemeinsamer Gesang'
elif 'gedicht' in left_side.lower():
category = 'Gedicht'
elif 'instrumental' in left_side.lower() or 'musikstück' in left_side.lower():
category = 'Instrumental'
else:
category = None
if right_side:
titel, name = right_side.split('-', 1) if '-' in right_side else (right_side, None)
if category == 'Predigt' or category == 'Vorwort' or category == 'Gedicht':
if not name: # kein Titel, nur Name
name = titel
titel = None
else:
titel = None
name = None
return category, titel, name
def generate_top_list(category):
now = datetime.now()
# We'll compare the timestamp
start_dt = now - timedelta(days=30)
start_str = start_dt.isoformat()
# Filter for mimes that start with the given type
params_for_filter = (start_str,)
# 1. Top files by access count
query = f'''
SELECT rel_path, COUNT(*) as access_count
FROM file_access_log
WHERE timestamp >= ? AND mime LIKE 'audio/%'
GROUP BY rel_path
ORDER BY access_count DESC
LIMIT 1000
'''
with log_db:
cursor = log_db.execute(query, params_for_filter)
rows = cursor.fetchall()
# Convert rows to a list of dictionaries and add category
rows = [
{
'rel_path': rel_path,
'access_count': access_count,
'category': extract_structure_from_string(rel_path)[0]
}
for rel_path, access_count in rows
]
rows = [r for r in rows if r['category'] == category][:20]
filelist = [
{
'name': rel_path.split('/')[-1],
'path': rel_path,
'file_type': 'music'
}
for rel_path in [r['rel_path'] for r in rows]
]
return filelist

View File

@ -110,13 +110,13 @@ def updatefileindex():
# Retrieve the hit count for this file.
hit_count = get_hit_count(relative_path)
category, titel, name, performance_date, site = None, None, None, None, None
# Determine the site
if foldername == 'Gottesdienste Speyer':
site = 'Speyer'
elif foldername == 'Gottesdienste Schwegenheim':
site = 'Schwegenheim'
else:
site = None
# Check for a corresponding transcript file in a sibling "Transkription" folder.
parent_dir = os.path.dirname(entry_path)
@ -130,46 +130,7 @@ def updatefileindex():
except Exception:
transcript = None
# extract category and titel from filename
filename_ext = os.path.splitext(entry.name)[0]
left_side, right_side = filename_ext.split('-', 1) if '-' in filename_ext else (filename_ext, None)
try:
int(left_side.strip())
# first part is only a number
previous_right_side = right_side
left_side, right_side = previous_right_side.split('-', 1) if '-' in previous_right_side else (previous_right_side, None)
except:
# first part not a number
pass
if 'predig' in left_side.lower():
category = 'Predigt'
elif 'wort' in left_side.lower() or 'einladung' in left_side.lower():
category = 'Vorwort'
elif 'chor' in left_side.lower():
category = 'Chor'
elif 'orchester' in left_side.lower():
category = 'Orchester'
elif 'gruppenlied' in left_side.lower() or 'jugendlied' in left_side.lower():
category = 'Gruppenlied'
elif 'gemeinsam' in left_side.lower() or 'gesang' in left_side.lower() or 'lied' in left_side.lower():
category = 'Gemeinsamer Gesang'
elif 'gedicht' in left_side.lower():
category = 'Gedicht'
elif 'instrumental' in left_side.lower() or 'musikstück' in left_side.lower():
category = 'Instrumental'
else:
category = None
if right_side:
titel, name = right_side.split('-', 1) if '-' in right_side else (right_side, None)
if category == 'Predigt' or category == 'Vorwort' or category == 'Gedicht':
if not name: # kein Titel, nur Name
name = titel
titel = None
else:
titel = None
name = None
category, titel, name = hf.extract_structure_from_string(entry.name)
performance_date = hf.extract_date_from_string(relative_path)

View File

@ -116,6 +116,9 @@ function renderContent(data) {
} else if (data.breadcrumbs.length === 1 && Array.isArray(data.folder_yesterday) && data.folder_yesterday.length > 0) {
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="gestern">📅 Gestern</a></li>`;
}
if (data.breadcrumbs.length === 1 ) {
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="toplist">🔥 oft angehört</a></li>`;
}
console.log(data.folder_today, data.folder_yesterday);
data.directories.forEach(dir => {
if (admin_enabled && data.breadcrumbs.length != 1 && dir.share) {

View File

@ -119,6 +119,8 @@ async function downloadAudio() {
// Create a “real” link to that URL and click it
const a = document.createElement('a');
a.href = downloadUrl.toString();
a.download = ''; // tell Safari “this is a download”
a.target = '_blank'; // force a real navigation on iOS
// NOTE: do NOT set a.download here we want the server's Content-Disposition to drive it
document.body.appendChild(a);
a.click();

View File

@ -211,9 +211,21 @@
</div>
<!-- Detailed Table of Top File Accesses -->
{% for top20 in [top20_overall, top20_predigt, top20_chor, top20_ggesang, top20_glied] %}
<div class="card mb-4">
<div class="card-header">
Detaillierte Dateizugriffe (Top 20)
Top 20 Dateizugriffe
{% if top20 == top20_overall %}
(Gesamt)
{% elif top20 == top20_predigt %}
(Predigt)
{% elif top20 == top20_chor %}
(Chor)
{% elif top20 == top20_ggesang %}
(Gemeinsamer Gesang)
{% elif top20 == top20_glied %}
(Gruppenlieder)
{% endif %}
</div>
<div class="card-body">
<div class="table-responsive">
@ -222,13 +234,15 @@
<tr>
<th>File Path</th>
<th>Access Count</th>
<th>Kategorie</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
{% for row in top20 %}
<tr>
<td>{{ row.rel_path }}</td>
<td>{{ row.access_count }}</td>
<td>{{ row.category }}</td>
</tr>
{% else %}
<tr>
@ -240,6 +254,7 @@
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}