Spaces:
Sleeping
Sleeping
File size: 10,086 Bytes
3dab6f8 3424ff7 ef59b82 3424ff7 56a75e2 3dab6f8 3424ff7 ef59b82 3424ff7 3dab6f8 3424ff7 3dab6f8 3424ff7 3dab6f8 3424ff7 3dab6f8 56a75e2 3dab6f8 3424ff7 56a75e2 3424ff7 3dab6f8 3424ff7 3dab6f8 56a75e2 3dab6f8 56a75e2 3dab6f8 3424ff7 3dab6f8 3424ff7 3dab6f8 3424ff7 3dab6f8 3424ff7 3dab6f8 3424ff7 3dab6f8 3424ff7 3dab6f8 3424ff7 56a75e2 3424ff7 3dab6f8 3424ff7 3dab6f8 56a75e2 3424ff7 56a75e2 3424ff7 ef59b82 3424ff7 3dab6f8 3424ff7 56a75e2 3424ff7 56a75e2 3dab6f8 3424ff7 ef59b82 56a75e2 ef59b82 56a75e2 ef59b82 56a75e2 ef59b82 3424ff7 3dab6f8 3424ff7 |
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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# app.py (نسخه نهایی، پایدار و بهینه شده)
import os
import json
import time
from flask import Flask, request, jsonify, render_template, Response, stream_with_context
from flask_cors import CORS
import logging
import threading
from huggingface_hub import HfApi, hf_hub_download
from huggingface_hub.utils import RepositoryNotFoundError, EntryNotFoundError
import requests
import tempfile
# --- تنظیمات اصلی ---
DATASET_REPO = "Ezmary/Karbaran-rayegan-tedad"
DATASET_FILENAME = "image_usage_data.json"
USAGE_LIMIT = 5
HF_TOKEN = os.environ.get("HF_TOKEN")
TEMP_DIR = "/app/tmp" # <<< تغییر کلیدی: استفاده از پوشه موقت امن
# --- راهاندازی Flask و لاگها ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
app = Flask(__name__)
CORS(app)
# --- مدیریت دادههای کاربران و قفلها ---
usage_data_cache = []
cache_lock = threading.Lock() # قفل برای دسترسی به حافظه کش
data_changed = threading.Event() # برای اطلاع از وجود تغییرات برای ذخیره
persistence_lock = threading.Lock() # <<< تغییر کلیدی: قفل جدید برای اتمی کردن عملیات ذخیرهسازی >>>
api = None
if not HF_TOKEN:
logging.error("CRITICAL: Secret 'HF_TOKEN' not found. Cannot access the private dataset.")
else:
api = HfApi(token=HF_TOKEN)
logging.info("HfApi initialized successfully.")
def load_initial_data():
global usage_data_cache
with cache_lock:
if not api: return
try:
logging.info(f"Attempting to load data from '{DATASET_REPO}/{DATASET_FILENAME}'...")
# <<< تغییر کلیدی: دانلود در پوشه موقت برای جلوگیری از مشکلات دسترسی >>>
with tempfile.TemporaryDirectory(dir=TEMP_DIR) as tmp_download_dir:
local_path = hf_hub_download(
repo_id=DATASET_REPO,
filename=DATASET_FILENAME,
repo_type="dataset",
token=HF_TOKEN,
force_download=True,
cache_dir=tmp_download_dir
)
with open(local_path, 'r', encoding='utf-8') as f:
content = f.read()
data_from_hub = json.loads(content) if content else []
logging.info(f"Loaded {len(data_from_hub)} records from {DATASET_FILENAME}.")
# --- حذف خودکار رکوردهای قدیمیتر از ۶ ماه ---
now = time.time()
six_months_ago = now - (6 * 30 * 24 * 60 * 60)
cleaned_data = [user for user in data_from_hub if user.get('last_seen', 0) > six_months_ago]
pruned_count = len(data_from_hub) - len(cleaned_data)
if pruned_count > 0:
logging.info(f"Pruned {pruned_count} user records older than 6 months.")
data_changed.set() # <<< تغییر کلیدی: فقط فلگ را ست میکنیم، ترد پسزمینه ذخیره میکند >>>
usage_data_cache = cleaned_data
except json.JSONDecodeError:
# <<< تغییر کلیدی: مدیریت فایل خراب برای جلوگیری از کرش و صفحه سفید >>>
logging.error(f"CRITICAL: Failed to decode JSON from '{DATASET_FILENAME}'. The file is likely corrupted. Starting fresh.")
usage_data_cache = []
except (RepositoryNotFoundError, EntryNotFoundError):
logging.warning(f"Dataset file '{DATASET_FILENAME}' not found. A new one will be created.")
usage_data_cache = []
except Exception as e:
logging.error(f"Failed to load initial data: {e}", exc_info=True)
usage_data_cache = []
def persist_data_to_hub():
# <<< تغییر کلیدی: این تابع اکنون کاملاً Thread-Safe و امن است >>>
with persistence_lock:
if not data_changed.is_set() or not api:
return
with cache_lock:
data_to_write = list(usage_data_cache)
data_changed.clear()
temp_filepath = None
try:
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', dir=TEMP_DIR, delete=False, suffix='.json') as temp_f:
temp_filepath = temp_f.name
json.dump(data_to_write, temp_f, ensure_ascii=False, indent=2)
logging.info(f"Change detected, persisting {len(data_to_write)} records to Hub...")
api.upload_file(
path_or_fileobj=temp_filepath,
path_in_repo=DATASET_FILENAME,
repo_id=DATASET_REPO,
repo_type="dataset",
commit_message="Update image editor usage data [automated]"
)
logging.info(f"Successfully persisted data to Hub.")
except Exception as e:
logging.error(f"CRITICAL: Failed to persist data to Hub: {e}", exc_info=True)
data_changed.set() # اگر آپلود ناموفق بود، فلگ را دوباره ست میکنیم
finally:
if temp_filepath and os.path.exists(temp_filepath):
os.remove(temp_filepath)
def background_persister():
# <<< تغییر کلیدی: این ترد اصلی برای ذخیرهسازی است و هر ۳۰ ثانیه اجرا میشود >>>
while True:
time.sleep(30)
persist_data_to_hub()
# --- روتهای API ---
@app.route('/')
def index():
return render_template('index.html')
def get_user_identifier(data):
fingerprint = data.get('fingerprint')
if fingerprint: return str(fingerprint)
if request.headers.getlist("X-Forwarded-For"): return request.headers.getlist("X-Forwarded-For")[0].split(',')[0].strip()
return request.remote_addr
@app.route('/api/check-credit', methods=['POST'])
def check_credit():
data = request.get_json()
if not data: return jsonify({"error": "Invalid request"}), 400
user_id = get_user_identifier(data)
if not user_id: return jsonify({"error": "User identifier is required."}), 400
with cache_lock:
now = time.time()
one_week_seconds = 7 * 24 * 60 * 60
user_record = next((user for user in usage_data_cache if user.get('id') == user_id), None)
credits_remaining = USAGE_LIMIT
limit_reached = False
reset_timestamp = 0
if user_record:
user_record['last_seen'] = now # آپدیت زمان آخرین فعالیت
if user_record.get('week_start', 0) < (now - one_week_seconds):
user_record['count'] = 0
user_record['week_start'] = now
data_changed.set() # اگر هفته ریست شد، باید ذخیره شود
credits_remaining = max(0, USAGE_LIMIT - user_record.get('count', 0))
if credits_remaining == 0:
limit_reached = True
reset_timestamp = user_record.get('week_start', now) + one_week_seconds
return jsonify({"credits_remaining": credits_remaining, "limit_reached": limit_reached, "reset_timestamp": reset_timestamp})
@app.route('/api/use-credit', methods=['POST'])
def use_credit():
data = request.get_json()
if not data: return jsonify({"error": "Invalid request"}), 400
user_id = get_user_identifier(data)
if not user_id: return jsonify({"error": "User identifier is required."}), 400
with cache_lock:
now = time.time()
one_week_seconds = 7 * 24 * 60 * 60
user_record = next((user for user in usage_data_cache if user.get('id') == user_id), None)
if user_record:
if user_record.get('week_start', 0) < (now - one_week_seconds):
user_record['count'] = 0
user_record['week_start'] = now
if user_record.get('count', 0) >= USAGE_LIMIT:
reset_timestamp = user_record.get('week_start', now) + one_week_seconds
return jsonify({"status": "limit_reached", "credits_remaining": 0, "reset_timestamp": reset_timestamp}), 429
user_record['count'] += 1
user_record['last_seen'] = now
else:
user_record = {"id": user_id, "count": 1, "week_start": now, "last_seen": now}
usage_data_cache.append(user_record)
credits_remaining = USAGE_LIMIT - user_record['count']
# <<< تغییر کلیدی: به جای ایجاد ترد جدید، فقط فلگ تغییرات را فعال میکنیم >>>
data_changed.set()
return jsonify({"status": "success", "credits_remaining": credits_remaining})
@app.route('/api/proxy')
def proxy_image():
image_url = request.args.get('url')
if not image_url: return "URL parameter is missing.", 400
try:
req = requests.get(image_url, stream=True, timeout=20)
req.raise_for_status()
return Response(stream_with_context(req.iter_content(chunk_size=4096)), content_type=req.headers.get('content-type', 'image/png'))
except requests.exceptions.RequestException as e:
logging.error(f"Could not proxy image from {image_url}: {e}")
return f"Failed to retrieve image.", 502
# --- اجرای برنامه ---
if __name__ != '__main__':
# <<< تغییر کلیدی: افزایش پایداری در استارتآپ >>>
try:
load_initial_data()
persister_thread = threading.Thread(target=background_persister, daemon=True)
persister_thread.start()
logging.info("Application startup complete.")
except Exception as e:
logging.critical(f"A critical error occurred during application startup: {e}", exc_info=True)
if __name__ == '__main__':
port = int(os.environ.get('PORT', 7860))
app.run(host='0.0.0.0', port=port) |