Spaces:
Runtime error
Runtime error
| import os | |
| import uuid | |
| import requests | |
| import io | |
| import logging | |
| import sqlite3 | |
| import time | |
| from datetime import timedelta | |
| from flask import Flask, request, jsonify, send_from_directory, Response | |
| from huggingface_hub import HfApi | |
| from apscheduler.schedulers.background import BackgroundScheduler | |
| import mimetypes | |
| # 1. تنظیمات لاگینگ | |
| logging.basicConfig( | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
| level=logging.INFO | |
| ) | |
| logger = logging.getLogger(__name__) | |
| app = Flask(__name__, static_folder='static', static_url_path='/static') | |
| # 2. خواندن متغیرهای محیطی و تعریف ثابتها | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| DB_FILE = "/tmp/uploads.db" | |
| space_host = os.getenv('SPACE_HOST') | |
| if space_host: | |
| UPLOADER_BASE_URL = f"https://{space_host}" | |
| else: | |
| port = os.getenv('PORT', 7860) | |
| UPLOADER_BASE_URL = f"http://localhost:{port}" | |
| HF_REPO_IDS = [ | |
| "Hamed744/Uploadfile1", | |
| "Hamed744/Uploadfile2", | |
| "Hamed744/Uploadfile3", | |
| "Hamed744/Uploadfile4", | |
| "Hamed744/Uploadfile5" | |
| ] | |
| # 3. بررسی وجود توکن | |
| if not HF_TOKEN: | |
| logger.error("FATAL ERROR: HF_TOKEN environment variable not set!") | |
| exit() | |
| # 4. ایجاد یک نمونه از HfApi | |
| hf_api = HfApi(token=HF_TOKEN) | |
| logger.info(f"File Upload API Initialized. Auto-detected Base URL: {UPLOADER_BASE_URL}") | |
| logger.info(f"Target repos: {', '.join(HF_REPO_IDS)}") | |
| logger.info(f"Using database file at: {DB_FILE}") | |
| # --- بخش مدیریت پایگاه داده و توابع کمکی --- | |
| # (این بخشها بدون تغییر باقی میمانند) | |
| def init_db(): | |
| conn = sqlite3.connect(DB_FILE) | |
| cursor = conn.cursor() | |
| cursor.execute('CREATE TABLE IF NOT EXISTS uploaded_files (id INTEGER PRIMARY KEY, path_in_repo TEXT NOT NULL, repo_id TEXT NOT NULL, upload_timestamp REAL NOT NULL)') | |
| cursor.execute('CREATE TABLE IF NOT EXISTS app_state (key TEXT PRIMARY KEY, value INTEGER NOT NULL)') | |
| cursor.execute("INSERT OR IGNORE INTO app_state (key, value) VALUES ('repo_index', 0)") | |
| conn.commit() | |
| conn.close() | |
| logger.info("Database initialized successfully.") | |
| init_db() | |
| def get_next_repo(): | |
| with sqlite3.connect(DB_FILE) as conn: | |
| cursor = conn.cursor() | |
| cursor.execute("SELECT value FROM app_state WHERE key = 'repo_index'") | |
| current_index = cursor.fetchone()[0] | |
| repo_id = HF_REPO_IDS[current_index] | |
| next_index = (current_index + 1) % len(HF_REPO_IDS) | |
| cursor.execute("UPDATE app_state SET value = ? WHERE key = 'repo_index'", (next_index,)) | |
| conn.commit() | |
| logger.info(f"Selected repo for current upload: {repo_id} (next index will be {next_index})") | |
| return repo_id | |
| def add_file_to_db(path_in_repo, repo_id): | |
| with sqlite3.connect(DB_FILE) as conn: | |
| conn.execute("INSERT INTO uploaded_files (path_in_repo, repo_id, upload_timestamp) VALUES (?, ?, ?)", | |
| (path_in_repo, repo_id, time.time())) | |
| conn.commit() | |
| logger.info(f"File metadata added to DB: {path_in_repo} in repo {repo_id}") | |
| def delete_old_files(): | |
| # این تابع کامل است و نیازی به تغییر ندارد | |
| with app.app_context(): | |
| logger.info("Running scheduled job: Checking for old files to delete...") | |
| two_weeks_ago = time.time() - timedelta(days=14).total_seconds() | |
| try: | |
| with sqlite3.connect(DB_FILE) as conn: | |
| cursor = conn.cursor() | |
| cursor.execute("SELECT id, path_in_repo, repo_id FROM uploaded_files WHERE upload_timestamp < ?", (two_weeks_ago,)) | |
| for file_id, path, repo_id in cursor.fetchall(): | |
| try: | |
| logger.warning(f"Deleting old file: {path} from {repo_id}") | |
| hf_api.delete_file(path_in_repo=path, repo_id=repo_id, repo_type="dataset") | |
| cursor.execute("DELETE FROM uploaded_files WHERE id = ?", (file_id,)) | |
| conn.commit() | |
| logger.info(f"Successfully deleted {path}") | |
| except Exception as e: | |
| logger.error(f"Failed to delete {path}: {e}") | |
| except Exception as e: | |
| logger.error(f"DB error in deletion job: {e}") | |
| def get_folder_by_mimetype(mimetype): | |
| if not mimetype: return "others" | |
| main_type = mimetype.split('/')[0] | |
| types = {"image": "images", "video": "videos", "audio": "audios", "text": "documents/texts"} | |
| if main_type in types: return types[main_type] | |
| if main_type == "application": | |
| if 'pdf' in mimetype: return "documents/pdfs" | |
| if any(x in mimetype for x in ['zip', 'rar', '7z']): return "archives" | |
| return "documents/applications" | |
| return "others" | |
| def process_and_upload(file_content, original_filename, mimetype): | |
| target_repo_id = get_next_repo() | |
| ext = os.path.splitext(original_filename)[1] or mimetypes.guess_extension(mimetype) or "" | |
| filename = f"{uuid.uuid4().hex}{ext}" | |
| folder = get_folder_by_mimetype(mimetype) | |
| path_in_repo = f"{folder}/{filename}" | |
| hf_api.upload_file( | |
| path_or_fileobj=io.BytesIO(file_content), | |
| path_in_repo=path_in_repo, | |
| repo_id=target_repo_id, | |
| repo_type="dataset" | |
| ) | |
| add_file_to_db(path_in_repo, target_repo_id) | |
| # *** مهم: نحوه ساخت URL تغییری نمیکند، چون target_repo_id خودش شامل user/repo است *** | |
| proxy_url = f"{UPLOADER_BASE_URL}/proxy/{target_repo_id}/{path_in_repo}" | |
| logger.info(f"Full Proxy URL generated: {proxy_url}") | |
| return proxy_url | |
| # --- بخش API --- | |
| def serve_html(): | |
| return send_from_directory(app.static_folder, 'index.html') | |
| def upload_file_endpoint(): | |
| try: | |
| if 'file' in request.files: | |
| file = request.files['file'] | |
| if not file.filename: return jsonify({"error": "No selected file"}), 400 | |
| proxy_url = process_and_upload(file.read(), file.filename, file.mimetype) | |
| return jsonify({"hf_url": proxy_url}), 200 | |
| elif request.is_json and 'url' in request.json: | |
| url = request.json['url'] | |
| if not url: return jsonify({"error": "No URL provided"}), 400 | |
| resp = requests.get(url, timeout=60) | |
| resp.raise_for_status() | |
| filename = url.split('/')[-1].split('?')[0] | |
| proxy_url = process_and_upload(resp.content, filename, resp.headers.get('Content-Type')) | |
| return jsonify({"hf_url": proxy_url}), 200 | |
| return jsonify({"error": "Invalid request"}), 400 | |
| except Exception as e: | |
| logger.error(f"Internal server error: {e}", exc_info=True) | |
| return jsonify({"error": str(e)}), 500 | |
| # *** رفع خطا: تغییر ساختار Route برای پراکسی *** | |
| def file_proxy(user, repo, file_path): | |
| repo_id = f"{user}/{repo}" # بازسازی repo_id کامل | |
| if not HF_TOKEN: | |
| logger.error("FATAL ERROR: HF_TOKEN not configured for proxy!") | |
| return "Server token not configured", 500 | |
| file_url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_path}" | |
| headers = {"Authorization": f"Bearer {HF_TOKEN}"} | |
| try: | |
| hf_response = requests.get(file_url, headers=headers, stream=True, timeout=30) | |
| hf_response.raise_for_status() | |
| return Response( | |
| hf_response.iter_content(chunk_size=8192), | |
| content_type=hf_response.headers.get('Content-Type', 'application/octet-stream') | |
| ) | |
| except requests.exceptions.HTTPError as e: | |
| if e.response.status_code == 404: | |
| logger.error(f"File not found on Hugging Face: {file_url}") | |
| return "File not found or access denied", 404 | |
| raise e | |
| except requests.exceptions.RequestException as e: | |
| logger.error(f"Proxy request error for '{file_url}': {e}") | |
| return "Error fetching file", 500 | |
| # --- راهاندازی زمانبندی و برنامه --- | |
| scheduler = BackgroundScheduler(daemon=True) | |
| scheduler.add_job(delete_old_files, 'interval', hours=24) | |
| scheduler.start() | |
| if __name__ == '__main__': | |
| port = int(os.environ.get('PORT', 7860)) | |
| app.run(host='0.0.0.0', port=port, debug=False) |