Spaces:
Running
Running
File size: 5,462 Bytes
f913c73 09418c2 f913c73 09418c2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | """File-system helpers."""
from __future__ import annotations
import os
import re
import secrets
import shutil
import string
import time
from pathlib import Path
def ensure_dir(path: str | Path) -> Path:
p = Path(path)
p.mkdir(parents=True, exist_ok=True)
return p
def get_file_extension(filename: str) -> str:
_, ext = os.path.splitext(filename)
return ext.lower()
def format_size(size_bytes: int) -> str:
units = ("B", "KB", "MB", "GB", "TB")
val = float(size_bytes)
for unit in units:
if val < 1024:
return f"{val:.2f} {unit}"
val /= 1024
return f"{val:.2f} PB"
def format_speed(bytes_per_sec: float) -> str:
return f"{format_size(int(bytes_per_sec))}/s"
def format_duration(seconds: float) -> str:
m, s = divmod(int(seconds), 60)
h, m = divmod(m, 60)
if h:
return f"{h}h {m}m {s}s"
if m:
return f"{m}m {s}s"
return f"{s}s"
def random_filename(length: int = 10) -> str:
alphabet = string.ascii_lowercase + string.digits
return "".join(secrets.choice(alphabet) for _ in range(length))
def sanitize_filename(name: str, max_length: int = 100) -> str:
"""Remove dangerous characters from a filename.
Strips path separators, parent-directory sequences, and any
characters that could cause issues on common filesystems.
"""
name = name.replace("..", "").replace("/", "").replace("\\", "")
name = re.sub(r'[^\w\.\-\(\) ]', "", name)
return name[:max_length]
def cleanup_old_files(directory: str | Path, max_age_minutes: int = 30):
"""Delete files older than max_age_minutes inside *directory*."""
now = time.time()
cutoff = now - max_age_minutes * 60
if not Path(directory).exists():
return
for entry in Path(directory).iterdir():
if entry.is_file() and entry.stat().st_mtime < cutoff:
entry.unlink(missing_ok=True)
elif entry.is_dir() and entry.stat().st_mtime < cutoff:
shutil.rmtree(entry, ignore_errors=True)
def smart_disk_cleanup(
upload_dir: str | Path,
min_free_mb: int = 500,
max_age_minutes: int = 60,
protected_dirs: list[str] | None = None,
) -> dict:
"""تنظيف ذكي لقرص التخزين — يحذف الملفات القديمة عندما تقل المساحة عن الحد.
Args:
upload_dir: مسار مجلد الرفع.
min_free_mb: الحد الأدنى للمساحة الحرة (ميغابايت).
max_age_minutes: عمر الملف (بالدقائق) الذي يعتبر قديمًا وقابلاً للحذف.
protected_dirs: قائمة بأسماء المجلدات المحمية (لا تُحذف).
Returns:
dict: ملخص التنظيف (deleted_files, freed_bytes, free_mb, status).
"""
import shutil
from pathlib import Path
from shutil import disk_usage
result = {
"deleted_files": 0,
"deleted_dirs": 0,
"freed_bytes": 0,
"free_mb": 0,
"status": "ok",
"message": "",
}
upload_path = Path(upload_dir)
if not upload_path.exists():
result["status"] = "no_directory"
return result
# فحص المساحة الحالية
usage = disk_usage(upload_path)
free_mb = usage.free / (1024 * 1024)
result["free_mb"] = round(free_mb, 1)
# إذا كانت المساحة كافية، لا نقوم بالتنظيف
if free_mb >= min_free_mb:
result["message"] = f"Enough space: {free_mb:.0f} MB free (min: {min_free_mb} MB)"
return result
protected = set(protected_dirs or [])
protected.add("_db_fallback") # مجلد قاعدة البيانات الاحتياطية محمي دائماً
now = time.time()
cutoff = now - max_age_minutes * 60
# جمع جميع الملفات والمجلدات المرشحة للحذف
for entry in sorted(upload_path.iterdir(), key=lambda x: x.stat().st_mtime):
if entry.name in protected:
continue
if not entry.is_file() and not entry.is_dir():
continue
# التحقق من العمر
if entry.stat().st_mtime > cutoff:
continue
# حذف الملف
try:
if entry.is_file():
sz = entry.stat().st_size
entry.unlink(missing_ok=True)
result["deleted_files"] += 1
result["freed_bytes"] += sz
elif entry.is_dir():
# تحقق أن المجلد ليس قيد الاستخدام (مجلد مستخدم نشط)
sz = sum(f.stat().st_size for f in entry.rglob("*") if f.is_file())
shutil.rmtree(entry, ignore_errors=True)
result["deleted_dirs"] += 1
result["freed_bytes"] += sz
except (OSError, PermissionError):
continue
# تحقق مرة أخرى بعد كل حذف — إذا أصبحت المساحة كافية، توقف
if disk_usage(upload_path).free / (1024 * 1024) >= min_free_mb:
break
free_after = disk_usage(upload_path).free / (1024 * 1024)
result["free_mb"] = round(free_after, 1)
result["message"] = (
f"Cleaned {result['deleted_files']} files, "
f"{result['deleted_dirs']} dirs, "
f"freed {format_size(result['freed_bytes'])}, "
f"now {free_after:.0f} MB free"
)
return result
|