import streamlit as st import subprocess import shutil import os import zipfile import io # تابع اجرای دستورات ترمینال def run_command(command): try: # استفاده از shell=True برای دستورات ترکیبی result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True) return True, result.stdout except subprocess.CalledProcessError as e: # ترکیب خروجی استاندارد و خطای ترمینال برای گزارش دقیق خطایابی err_msg = e.stderr if e.stderr else "" out_msg = e.stdout if e.stdout else "" return False, f"{err_msg}\n{out_msg}" # تابع ساخت فایل زیپ در حافظه def create_zip_buffer(source_dir, flatten=False): zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf: for root, dirs, files in os.walk(source_dir): if '.git' in root: continue for file in files: file_path = os.path.join(root, file) if flatten: arcname = file else: arcname = os.path.relpath(file_path, source_dir) zipf.write(file_path, arcname=arcname) return zip_buffer.getvalue() # تنظیمات صفحه st.set_page_config(page_title="Hugging Face Space Tools", page_icon="🛠️") st.title("🛠️ ابزارهای مدیریت اسپیس هاگینگ‌فیس") # ایجاد سه تب tab1, tab2, tab3 = st.tabs([ "🔄 انتقال به گیت‌هاب", "📤 انتقال به هاگینگ‌فیس", "📦 دانلود اسپیس (فایل ZIP)" ]) # --------------------------------------------------------- # تب اول: انتقال به گیت‌هاب (پشتیبانی از لینک و فایل زیپ + حل مشکل پوشه اضافه) # --------------------------------------------------------- with tab1: st.info("منبع خود را برای انتقال به گیت‌هاب انتخاب کنید:") # انتخاب نوع منبع خارج از فرم برای رندر داینامیک source_type = st.radio("نوع منبع:", ["لینک اسپیس هاگینگ‌فیس", "آپلود فایل ZIP"]) with st.form("transfer_form"): hf_url = "" uploaded_zip = None if source_type == "لینک اسپیس هاگینگ‌فیس": hf_url = st.text_input("لینک صفحه اسپیس هاگینگ فیس (منبع)", placeholder="https://huggingface.co/spaces/user/space") else: uploaded_zip = st.file_uploader("آپلود فایل ZIP (منبع)", type=["zip"]) st.markdown("---") gh_url = st.text_input("لینک کامل ریپازیتوری گیت‌هاب (مقصد)", placeholder="https://github.com/Hamed744/Zirnavis.git") gh_token = st.text_input("توکن گیت‌هاب (Classic Token)", type="password") submitted = st.form_submit_button("شروع عملیات انتقال 🚀") if submitted: if not gh_url or not gh_token: st.error("لطفاً لینک گیت‌هاب و توکن را وارد کنید.") elif source_type == "لینک اسپیس هاگینگ‌فیس" and not hf_url: st.error("لطفاً لینک اسپیس را وارد کنید.") elif source_type == "لینک اسپیس هاگینگ‌فیس" and "hf.space" in hf_url: st.error("❌ لینک اشتباه است! لطفاً لینک اصلی پروفایل را وارد کنید.") elif source_type == "آپلود فایل ZIP" and not uploaded_zip: st.error("لطفاً فایل ZIP را آپلود کنید.") else: status_area = st.empty() temp_dir = "temp_repo_gh" if os.path.exists(temp_dir): shutil.rmtree(temp_dir, ignore_errors=True) try: auth_gh_url = gh_url.replace("https://", f"https://{gh_token}@") # مرحله 1: آماده‌سازی فایل‌ها (دانلود یا اکسترکت) if source_type == "لینک اسپیس هاگینگ‌فیس": status_area.info("📥 در حال دانلود از هاگینگ فیس...") success, msg = run_command(f"git clone {hf_url} {temp_dir}") if not success: st.error(f"خطا در دانلود: {msg}") st.stop() else: run_command(f"git -C {temp_dir} lfs pull") else: status_area.info("📥 در حال استخراج فایل ZIP...") os.makedirs(temp_dir, exist_ok=True) with zipfile.ZipFile(uploaded_zip, 'r') as zip_ref: zip_ref.extractall(temp_dir) # بررسی و رفع مشکل پوشه اضافه (انتقال محتویات به روت اصلی) extracted_items = [item for item in os.listdir(temp_dir) if item != '__MACOSX'] if len(extracted_items) == 1: single_folder_path = os.path.join(temp_dir, extracted_items[0]) if os.path.isdir(single_folder_path): for item in os.listdir(single_folder_path): shutil.move(os.path.join(single_folder_path, item), temp_dir) os.rmdir(single_folder_path) # مرحله 2: عملیات گیت و پوش os.chdir(temp_dir) status_area.info("⚙️ در حال تنظیمات گیت...") if os.path.exists(".git"): shutil.rmtree(".git", ignore_errors=True) run_command("git init") run_command("git lfs install") run_command("git config lfs.allowincompletepush true") # نادیده گرفتن خطای عدم دسترسی موقت به فایل‌های LFS run_command(f"git remote add origin {auth_gh_url}") run_command("git add .") run_command('git config user.email "bot@transfer.com"') run_command('git config user.name "Transfer Bot"') run_command('git commit -m "Moved to GitHub automatically"') run_command("git branch -M main") status_area.info("📤 در حال آپلود به گیت‌هاب...") push_success, push_msg = run_command("git push -f origin main") if push_success: st.success("✅ تمام شد! فایل‌ها دقیقاً در روت ریپازیتوری منتقل شدند.") st.balloons() else: clean_error = push_msg.replace(gh_token, "***") st.error(f"خطا در آپلود: {clean_error}") os.chdir("..") except Exception as e: st.error(f"خطای سیستمی: {e}") finally: try: if os.getcwd().endswith(temp_dir): os.chdir("..") except: pass if os.path.exists(temp_dir): shutil.rmtree(temp_dir, ignore_errors=True) # --------------------------------------------------------- # تب دوم: انتقال به هاگینگ‌فیس (پشتیبانی از لینک ریپازیتوری و فایل ZIP) # --------------------------------------------------------- with tab2: st.info("منبع خود را برای انتقال به اسپیس هاگینگ‌فیس انتخاب کنید:") # انتخاب نوع منبع خارج از فرم برای رندر داینامیک source_type_hf = st.radio("نوع منبع (برای هاگینگ‌فیس):", ["لینک ریپازیتوری (گیت‌هاب و غیره)", "آپلود فایل ZIP"], key="src_type_hf") with st.form("transfer_hf_form"): src_url_hf = "" uploaded_zip_hf = None if source_type_hf == "لینک ریپازیتوری (گیت‌هاب و غیره)": src_url_hf = st.text_input("لینک ریپازیتوری منبع (مثلاً گیت‌هاب یا یک اسپیس دیگر)", placeholder="https://github.com/user/repo.git", key="src_url_hf_input") else: uploaded_zip_hf = st.file_uploader("آپلود فایل ZIP (منبع)", type=["zip"], key="zip_hf_input") st.markdown("---") hf_target_url = st.text_input("لینک کامل اسپیس هاگینگ‌فیس (مقصد)", placeholder="https://huggingface.co/spaces/user/space-name", key="target_hf_url") hf_token = st.text_input("توکن هاگینگ‌فیس (با دسترسی Write)", type="password", key="hf_token_input") submitted_hf = st.form_submit_button("شروع عملیات انتقال به هاگینگ‌فیس 🚀") if submitted_hf: if not hf_target_url or not hf_token: st.error("لطفاً لینک اسپیس مقصد و توکن هاگینگ‌فیس را وارد کنید.") elif source_type_hf == "لینک ریپازیتوری (گیت‌هاب و غیره)" and not src_url_hf: st.error("لطفاً لینک ریپازیتوری منبع را وارد کنید.") elif source_type_hf == "آپلود فایل ZIP" and not uploaded_zip_hf: st.error("لطفاً فایل ZIP را آپلود کنید.") elif "huggingface.co/spaces/" not in hf_target_url: st.error("❌ لینک اسپیس مقصد اشتباه است! لینک باید با ساختار huggingface.co/spaces/... باشد.") else: status_area_hf = st.empty() temp_dir_hf = "temp_repo_hf" if os.path.exists(temp_dir_hf): shutil.rmtree(temp_dir_hf, ignore_errors=True) try: # استخراج هوشمند نام‌کاربری مقصد از آدرس وارد شده جهت احراز هویت بدون مشکل username = "user" try: cleaned_url = hf_target_url.replace("https://", "").replace("http://", "") parts = cleaned_url.split("huggingface.co/spaces/")[-1].strip("/").split("/") if len(parts) >= 1 and parts[0] != "": username = parts[0] except Exception: pass # ایجاد آدرس با الگوی معتبر نام‌کاربری و توکن auth_hf_url = hf_target_url.replace("https://", f"https://{username}:{hf_token}@") # مرحله 1: آماده‌سازی فایل‌ها (دانلود یا استخراج زیپ) if source_type_hf == "لینک ریپازیتوری (گیت‌هاب و غیره)": status_area_hf.info("📥 در حال دانلود (کلون) ریپازیتوری منبع...") success, msg = run_command(f"git clone {src_url_hf} {temp_dir_hf}") if not success: st.error(f"خطا در کلون کردن ریپازیتوری منبع: {msg}") st.stop() else: # دانلود کامل تمامی لایبرری‌ها و فایل‌های سیستمی LFS run_command(f"git -C {temp_dir_hf} lfs pull") else: status_area_hf.info("📥 در حال استخراج فایل ZIP...") os.makedirs(temp_dir_hf, exist_ok=True) with zipfile.ZipFile(uploaded_zip_hf, 'r') as zip_ref: zip_ref.extractall(temp_dir_hf) # رفع مشکل پوشه اضافه درون فایل ZIP extracted_items = [item for item in os.listdir(temp_dir_hf) if item != '__MACOSX'] if len(extracted_items) == 1: single_folder_path = os.path.join(temp_dir_hf, extracted_items[0]) if os.path.isdir(single_folder_path): for item in os.listdir(single_folder_path): shutil.move(os.path.join(single_folder_path, item), temp_dir_hf) os.rmdir(single_folder_path) # مرحله 2: ساخت مخزن تمیز گیت و پوش به هاگینگ‌فیس os.chdir(temp_dir_hf) status_area_hf.info("⚙️ در حال تنظیمات گیت...") if os.path.exists(".git"): shutil.rmtree(".git", ignore_errors=True) run_command("git init") run_command("git lfs install") # فعال‌سازی و راه‌اندازی محلی LFS run_command("git config lfs.allowincompletepush true") # نادیده گرفتن خطای اشاره‌گرهای ناقص فایل‌های LFS run_command(f"git remote add origin {auth_hf_url}") # ایجاد ریموت رسمی جهت شناسایی آپلودهای LFS run_command("git add .") run_command('git config user.email "bot@transfer.com"') run_command('git config user.name "Transfer Bot"') run_command('git commit -m "Moved to Hugging Face automatically"') run_command("git branch -M main") status_area_hf.info("📤 در حال آپلود فایل‌ها به اسپیس هاگینگ‌فیس...") push_success, push_msg = run_command("git push -f origin main") if push_success: st.success("✅ انتقال موفقیت‌آمیز بود! فرآیند بیلد (Build) اسپیس شما در هاگینگ‌فیس آغاز شد.") st.balloons() else: clean_error = push_msg.replace(hf_token, "***") st.error(f"خطا در آپلود به هاگینگ‌فیس:\n{clean_error}") os.chdir("..") except Exception as e: st.error(f"خطای سیستمی: {e}") finally: try: if os.getcwd().endswith(temp_dir_hf): os.chdir("..") except: pass if os.path.exists(temp_dir_hf): shutil.rmtree(temp_dir_hf, ignore_errors=True) # --------------------------------------------------------- # تب سوم: دانلود فایل‌ها به صورت زیپ # --------------------------------------------------------- with tab3: st.info("با استفاده از این بخش می‌توانید کل فایل‌های یک اسپیس عمومی را به صورت ZIP دانلود کنید.") hf_url_zip = st.text_input("لینک صفحه اسپیس هاگینگ فیس", placeholder="https://huggingface.co/spaces/elias207/zirnavis52", key="zip_url") zip_mode = st.radio("ساختار فایل ZIP خروجی را انتخاب کنید:", ("حفظ ساختار پوشه‌ها (فایل‌ها در پوشه‌های اصلی خودشان)", "همه فایل‌ها در روت (حذف پوشه‌بندی و انتقال همه فایل‌ها به روت زیپ)")) if st.button("آماده‌سازی فایل دانلود 🗜️"): if not hf_url_zip: st.error("لطفاً لینک اسپیس را وارد کنید.") elif "hf.space" in hf_url_zip: st.error("❌ لینک اشتباه است! لینک اصلی (huggingface.co/spaces/...) را وارد کنید.") else: status_zip = st.empty() temp_dir_zip = "temp_repo_zip" if os.path.exists(temp_dir_zip): shutil.rmtree(temp_dir_zip, ignore_errors=True) try: status_zip.info("📥 در حال دریافت فایل‌ها از هاگینگ‌فیس...") success, msg = run_command(f"git clone {hf_url_zip} {temp_dir_zip}") if not success: st.error(f"خطا در دریافت فایل‌ها: {msg}") else: # اطمینان از دانلود کامل تمامی فایل‌های LFS قبل از عملیات فشرده‌سازی run_command(f"git -C {temp_dir_zip} lfs pull") status_zip.info("⚙️ در حال فشرده‌سازی فایل‌ها...") flatten_files = True if zip_mode == "همه فایل‌ها در روت (حذف پوشه‌بندی و انتقال همه فایل‌ها به روت زیپ)" else False zip_data = create_zip_buffer(temp_dir_zip, flatten=flatten_files) status_zip.success("✅ فایل با موفقیت آماده شد!") repo_name = hf_url_zip.strip("/").split("/")[-1] file_suffix = "flat" if flatten_files else "structured" download_filename = f"{repo_name}_{file_suffix}.zip" st.download_button( label="⬇️ دانلود فایل ZIP", data=zip_data, file_name=download_filename, mime="application/zip", type="primary" ) except Exception as e: st.error(f"خطای سیستمی: {e}") finally: if os.path.exists(temp_dir_zip): shutil.rmtree(temp_dir_zip, ignore_errors=True)