mirror of
https://github.com/mattintech/simplefileupload-server.git
synced 2026-07-11 16:31:53 +00:00
Migrate storage from JSON to SQLite with enhanced MVC architecture
- Replace JSON file storage with SQLite database for improved concurrency and data integrity - Implement repository pattern with dedicated model layer (admin_model, file_model, chunk_model) - Add database.py with automatic migration from existing JSON files - Enable WAL mode for thread-safe concurrent access across Gunicorn workers - Store database files in dedicated db/ folder - Update controllers to use model layer instead of direct JSON access - Add admin authentication system with TOTP 2FA support
This commit is contained in:
199
src/models/file_model.py
Normal file
199
src/models/file_model.py
Normal file
@@ -0,0 +1,199 @@
|
||||
import sqlite3
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from models.database import get_db
|
||||
import utils.mLogger as log
|
||||
|
||||
def create_file(code, filename, path, max_downloads, expiry_hours):
|
||||
"""Create a new file metadata record
|
||||
|
||||
Args:
|
||||
code: 6-character unique code
|
||||
filename: Original filename
|
||||
path: Absolute path to file
|
||||
max_downloads: Maximum number of downloads allowed
|
||||
expiry_hours: Hours until file expires
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
with get_db() as conn:
|
||||
expiry_time = datetime.now() + timedelta(hours=expiry_hours)
|
||||
|
||||
conn.execute("""
|
||||
INSERT INTO files (code, filename, path, max_downloads, downloads, expiry_time)
|
||||
VALUES (?, ?, ?, ?, 0, ?)
|
||||
""", (code, filename, path, max_downloads, expiry_time.isoformat()))
|
||||
|
||||
log.i(f"Created file record: {code} - {filename}")
|
||||
return True
|
||||
except sqlite3.Error as e:
|
||||
log.e(f"Error creating file record: {e}")
|
||||
return False
|
||||
|
||||
def get_file(code):
|
||||
"""Get file metadata by code
|
||||
|
||||
Args:
|
||||
code: 6-character file code
|
||||
|
||||
Returns:
|
||||
dict: File metadata with keys: code, filename, path, max_downloads, downloads, expiry_time
|
||||
None: If file not found
|
||||
"""
|
||||
try:
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
row = cursor.execute("""
|
||||
SELECT code, filename, path, max_downloads, downloads, expiry_time
|
||||
FROM files
|
||||
WHERE code = ?
|
||||
""", (code,)).fetchone()
|
||||
|
||||
if row:
|
||||
return dict(row)
|
||||
return None
|
||||
except sqlite3.Error as e:
|
||||
log.e(f"Error getting file {code}: {e}")
|
||||
return None
|
||||
|
||||
def increment_download(code):
|
||||
"""Atomically increment download counter for a file
|
||||
|
||||
Args:
|
||||
code: 6-character file code
|
||||
|
||||
Returns:
|
||||
int: New download count, or None if file not found
|
||||
"""
|
||||
try:
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
UPDATE files
|
||||
SET downloads = downloads + 1
|
||||
WHERE code = ?
|
||||
""", (code,))
|
||||
|
||||
if cursor.rowcount > 0:
|
||||
# Get the new download count
|
||||
row = cursor.execute("""
|
||||
SELECT downloads FROM files WHERE code = ?
|
||||
""", (code,)).fetchone()
|
||||
|
||||
if row:
|
||||
new_count = row['downloads']
|
||||
log.i(f"Incremented download counter for {code}: {new_count}")
|
||||
return new_count
|
||||
|
||||
return None
|
||||
except sqlite3.Error as e:
|
||||
log.e(f"Error incrementing download for {code}: {e}")
|
||||
return None
|
||||
|
||||
def delete_file(code):
|
||||
"""Delete file metadata record
|
||||
|
||||
Args:
|
||||
code: 6-character file code
|
||||
|
||||
Returns:
|
||||
bool: True if deleted, False if not found or error
|
||||
"""
|
||||
try:
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM files WHERE code = ?", (code,))
|
||||
|
||||
if cursor.rowcount > 0:
|
||||
log.i(f"Deleted file record: {code}")
|
||||
return True
|
||||
return False
|
||||
except sqlite3.Error as e:
|
||||
log.e(f"Error deleting file {code}: {e}")
|
||||
return False
|
||||
|
||||
def get_expired_files():
|
||||
"""Get list of expired files or files that exceeded download limit
|
||||
|
||||
Returns:
|
||||
list: List of dicts with file metadata
|
||||
"""
|
||||
try:
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
now = datetime.now().isoformat()
|
||||
|
||||
rows = cursor.execute("""
|
||||
SELECT code, filename, path, max_downloads, downloads, expiry_time
|
||||
FROM files
|
||||
WHERE expiry_time < ? OR downloads >= max_downloads
|
||||
""", (now,)).fetchall()
|
||||
|
||||
return [dict(row) for row in rows]
|
||||
except sqlite3.Error as e:
|
||||
log.e(f"Error getting expired files: {e}")
|
||||
return []
|
||||
|
||||
def cleanup_expired():
|
||||
"""Delete expired file records and files that reached download limit
|
||||
|
||||
Returns:
|
||||
int: Number of files cleaned up
|
||||
"""
|
||||
try:
|
||||
# Get expired files first so we can delete physical files
|
||||
expired_files = get_expired_files()
|
||||
|
||||
if not expired_files:
|
||||
return 0
|
||||
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
now = datetime.now().isoformat()
|
||||
|
||||
# Delete from database
|
||||
cursor.execute("""
|
||||
DELETE FROM files
|
||||
WHERE expiry_time < ? OR downloads >= max_downloads
|
||||
""", (now,))
|
||||
|
||||
count = cursor.rowcount
|
||||
|
||||
# Delete physical files
|
||||
for file_info in expired_files:
|
||||
if os.path.exists(file_info['path']):
|
||||
try:
|
||||
os.remove(file_info['path'])
|
||||
log.i(f"Deleted expired file: {file_info['path']}")
|
||||
except Exception as e:
|
||||
log.w(f"Could not delete file {file_info['path']}: {e}")
|
||||
|
||||
if count > 0:
|
||||
log.i(f"Cleaned up {count} expired file(s)")
|
||||
|
||||
return count
|
||||
except sqlite3.Error as e:
|
||||
log.e(f"Error cleaning up expired files: {e}")
|
||||
return 0
|
||||
|
||||
def get_all_files():
|
||||
"""Get all file metadata records
|
||||
|
||||
Returns:
|
||||
list: List of dicts with file metadata
|
||||
"""
|
||||
try:
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
rows = cursor.execute("""
|
||||
SELECT code, filename, path, max_downloads, downloads, expiry_time, created_at
|
||||
FROM files
|
||||
ORDER BY created_at DESC
|
||||
""").fetchall()
|
||||
|
||||
return [dict(row) for row in rows]
|
||||
except sqlite3.Error as e:
|
||||
log.e(f"Error getting all files: {e}")
|
||||
return []
|
||||
Reference in New Issue
Block a user