Files
mini-flees/code/utils/files.py
2025-01-28 11:51:19 +02:00

281 lines
7.3 KiB
Python

import os
import shutil
import sqlite3
import time
from datetime import datetime
from flask import current_app as app
from .misc import file_date_human, file_size_human, file_time_human, random_token
def get_db():
db = sqlite3.connect(app.config["DB"], timeout=5)
c = db.cursor()
return db, c
def db_store_file(token, name, expires, max_dl, password, ips=None, hidden=None):
db, c = get_db()
c.execute(
"""
insert into files(token,name,added,expires,downloads,max_downloads,passhash,allowed_ip,hidden)
values (?,?,?,?,?,?,?,?,?)
""",
(token, name, int(time.time()), expires, 0, max_dl, password, ips, hidden),
)
if c.rowcount > 0:
db.commit()
return
def db_get_file(token, name):
db, c = get_db()
return db.execute(
"""
SELECT added,expires,downloads,max_downloads,passhash,hidden,allowed_ip
FROM files WHERE token = ? AND name = ?
""",
(token, name),
).fetchone()
def db_get_name(token):
db, c = get_db()
return db.execute(
"""
SELECT name
FROM files WHERE token = ?
""",
(token,),
).fetchone()
def db_get_files():
db, c = get_db()
return db.execute(
"""
SELECT token,name,added,expires,downloads,max_downloads,passhash,allowed_ip
FROM files
WHERE expires > ?
AND (downloads < max_downloads or max_downloads = -1)
AND (hidden IS NULL OR hidden = 0)
ORDER BY added
""",
(time.time(),),
)
def db_delete_file(token, name):
db, c = get_db()
c.execute(
"""
DELETE FROM files WHERE token = ? and name = ?
""",
(token, name),
)
if c.rowcount > 0:
db.commit()
def db_add_download(token, name):
db, c = get_db()
c.execute(
"""
UPDATE files SET downloads = downloads + 1 WHERE token = ? AND name = ?
""",
(token, name),
)
if c.rowcount > 0:
db.commit()
return
def db_maintenance():
messages = []
# === Delete DB entries where expiry or max DL is used up ===
db, c = get_db()
rows = db.execute(
"""
select
token, name
from files
where expires < ? or (downloads >= max_downloads and max_downloads != -1)
""",
(int(time.time()),),
)
deleted_tokens = []
for row in rows:
deleted_tokens.append((row[0],))
messages.append(f"Deleting DB {row[0]}/{row[1]}")
if len(deleted_tokens) > 0:
db, c = get_db()
c.executemany("DELETE FROM files WHERE token = ?", deleted_tokens)
if c.rowcount > 0:
db.commit()
# Delete files, if DB entry is gone
subdirs = next(os.walk(app.config["DATAFOLDER"]))[1]
db, c = get_db()
for d in subdirs:
keep = db.execute(
"SELECT 1 FROM files WHERE token = ?",
(d,),
).fetchone()
if not keep:
try:
for fname in os.listdir(os.path.join(app.config["DATAFOLDER"], d)):
os.remove(os.path.join(app.config["DATAFOLDER"], d, fname))
messages.append(f"Deleting file {d}/{fname}")
except Exception:
pass
shutil.rmtree(os.path.join(app.config["DATAFOLDER"], d), ignore_errors=True)
messages.append(f"Deleting folder {d}")
# Delete DB entries, if files have been deleted (probably manually)
db, c = get_db()
rows = db.execute(
"""
select
token, name
from files
"""
)
deleted_tokens = []
for row in rows:
full_path = file_full_path(row[0], row[1])
if not os.path.exists(full_path):
deleted_tokens.append((row[0],))
messages.append(f"Deleting DB {row[0]}/{row[1]} - files missing")
if len(deleted_tokens) > 0:
db, c = get_db()
c.executemany("DELETE FROM files WHERE token = ?", deleted_tokens)
if c.rowcount > 0:
db.commit()
# === Delete upload token entries where expiry is used up ===
db, c = get_db()
rows = db.execute(
"""
select
token
from upload_tokens
where expires < ?
""",
(int(time.time()),),
)
deleted_tokens = []
for row in rows:
deleted_tokens.append((row[0],))
messages.append(f"Deleting upload_token {row[0]}")
if len(deleted_tokens) > 0:
db, c = get_db()
c.executemany("DELETE FROM upload_tokens WHERE token = ?", deleted_tokens)
if c.rowcount > 0:
db.commit()
messages.append("Maintenance done.")
return "\n".join(messages)
def file_age(path):
now = datetime.now()
then = datetime.fromtimestamp(os.stat(path).st_mtime)
diff = now - then
return (
diff,
"%03d d %s" % (diff.days, datetime.utcfromtimestamp(diff.seconds).strftime("%H:%M:%S")),
)
def file_details(token, name):
full_path = file_full_path(token, name)
try:
s = os.stat(full_path)
db_stat = db_get_file(token, name)
if db_stat:
added, expires, downloads, max_dl, password, hidden, allowed_ip = db_stat
else:
return {}
return {
"size": s.st_size,
"hsize": file_size_human(s.st_size, HTML=False),
"added": file_time_human(added),
"name": name,
"url": file_full_url(token, name),
"expires": file_time_human(expires),
"downloaded": downloads,
"max-dl": max_dl,
"protected": password is not None,
"hidden": hidden,
"allowed_ip": allowed_ip,
}
except FileNotFoundError:
return {}
def file_full_path(token, name):
return os.path.join(app.config["DATAFOLDER"], token, name)
def file_full_url(token, name):
return f"{app.config['PUBLIC_URL']}/d/{token}/{name}"
def file_list():
details = []
details.append(" Added/Expiry DL/MaxDL ID URL")
details.append("=" * 75)
for file in db_get_files():
url = file_full_url(file[0], file[1])
added = file_date_human(file[2])
expiry = file_date_human(file[3])
pw = " (PW)" if file[6] else ""
ips = f" [{file[7]}]" if file[7] else ""
details.append(f"{added}/{expiry} {file[4]:4d}/{file[5]:4d} {file[0]} {url}{pw}{ips}")
return details
def file_list_simple():
return [f"{r[0]}/{r[1]}" for r in db_get_files()]
def new_upload_token(expires):
db, c = get_db()
token = random_token(32)
c.execute(
"""
insert into upload_tokens(token,added,expires)
values (?,?,?)
""",
(token, int(time.time()), expires),
)
if c.rowcount > 0:
db.commit()
return token
def validate_upload_token(token):
db, c = get_db()
return db.execute(
"""
SELECT 1
FROM upload_tokens WHERE token = ? AND expires > ?
""",
(token, int(time.time())),
).fetchone()
def invalidate_upload_token(token):
db, c = get_db()
c.execute(
"""
DELETE FROM upload_tokens WHERE token = ?
""",
(token,),
)
if c.rowcount > 0:
db.commit()