Spaces:
Sleeping
Sleeping
import streamlit as st | |
from pathlib import Path | |
from datetime import datetime | |
import zipfile | |
from PIL import Image | |
import time | |
import humanize | |
# تنظیمات صفحه | |
st.set_page_config( | |
page_title="سیستم مدیریت فایل", | |
page_icon="💼", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# استایلهای سفارشی با تم چتبات | |
CUSTOM_CSS = """ | |
<style> | |
/* فونت و تنظیمات پایه */ | |
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;700&display=swap'); | |
* { | |
font-family: 'Vazirmatn', sans-serif; | |
direction: rtl; | |
} | |
/* کانتینر اصلی */ | |
.main-container { | |
max-width: 1200px; | |
margin: 0 auto; | |
padding: 2rem; | |
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
border-radius: 20px; | |
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); | |
} | |
/* کارت فایل */ | |
.file-card { | |
background: white; | |
border-radius: 15px; | |
padding: 1.5rem; | |
margin: 1rem 0; | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); | |
transition: all 0.3s ease; | |
animation: slideIn 0.3s ease-out; | |
} | |
.file-card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); | |
} | |
/* دکمههای عملیات */ | |
.action-button { | |
background: #2196F3; | |
color: white; | |
border: none; | |
border-radius: 10px; | |
padding: 0.8rem 1.2rem; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
} | |
.action-button:hover { | |
background: #1976D2; | |
transform: scale(1.05); | |
} | |
/* ناحیه آپلود */ | |
.upload-area { | |
border: 2px dashed #2196F3; | |
border-radius: 15px; | |
padding: 2rem; | |
text-align: center; | |
background: rgba(255, 255, 255, 0.9); | |
cursor: pointer; | |
transition: all 0.3s ease; | |
} | |
.upload-area:hover { | |
border-color: #1976D2; | |
background: rgba(255, 255, 255, 1); | |
} | |
/* نوار پیشرفت */ | |
.progress-bar { | |
height: 4px; | |
background: #e0e0e0; | |
border-radius: 2px; | |
overflow: hidden; | |
margin: 1rem 0; | |
} | |
.progress-fill { | |
height: 100%; | |
background: #2196F3; | |
width: 0%; | |
animation: fillProgress 2s ease-out forwards; | |
} | |
/* انیمیشنها */ | |
@keyframes slideIn { | |
from { | |
opacity: 0; | |
transform: translateY(20px); | |
} | |
to { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
} | |
@keyframes fillProgress { | |
from { width: 0%; } | |
to { width: 100%; } | |
} | |
/* فیلتر و جستجو */ | |
.search-box { | |
display: flex; | |
gap: 1rem; | |
margin-bottom: 2rem; | |
} | |
.search-input { | |
flex: 1; | |
padding: 0.8rem 1.2rem; | |
border: 2px solid #e0e0e0; | |
border-radius: 10px; | |
font-size: 1rem; | |
transition: all 0.3s ease; | |
} | |
.search-input:focus { | |
border-color: #2196F3; | |
outline: none; | |
} | |
/* پیامهای اطلاعرسانی */ | |
.info-message { | |
background: rgba(33, 150, 243, 0.1); | |
border-left: 4px solid #2196F3; | |
padding: 1rem; | |
border-radius: 0 10px 10px 0; | |
margin: 1rem 0; | |
} | |
</style> | |
""" | |
st.markdown(CUSTOM_CSS, unsafe_allow_html=True) | |
class FileManager: | |
def __init__(self): | |
self.root_path = Path("uploads") | |
self.root_path.mkdir(exist_ok=True) | |
def get_file_info(self, filename): | |
"""دریافت اطلاعات کامل فایل""" | |
path = self.root_path / filename | |
stats = path.stat() | |
return { | |
'size': humanize.naturalsize(stats.st_size), | |
'modified': datetime.fromtimestamp(stats.st_mtime).strftime('%Y/%m/%d %H:%M'), | |
'type': path.suffix[1:].upper(), | |
'icon': self._get_file_icon(path.suffix) | |
} | |
def _get_file_icon(self, suffix): | |
"""انتخاب آیکون مناسب برای هر نوع فایل""" | |
icons = { | |
'.txt': '📄', | |
'.pdf': '📕', | |
'.jpg': '🖼️', | |
'.jpeg': '🖼️', | |
'.png': '🖼️', | |
'.gif': '🎞️', | |
'.zip': '📦', | |
'.mp3': '🎵', | |
'.mp4': '🎥', | |
'.doc': '📘', | |
'.docx': '📘', | |
'.xls': '📊', | |
'.xlsx': '📊', | |
} | |
return icons.get(suffix.lower(), '📎') | |
def upload_file(self, file): | |
"""آپلود فایل با نمایش پیشرفت""" | |
try: | |
progress_text = st.empty() | |
progress_bar = st.progress(0) | |
dest_path = self.root_path / file.name | |
total_size = file.size | |
with open(dest_path, "wb") as f: | |
bytes_data = file.getbuffer() | |
f.write(bytes_data) | |
# شبیهسازی پیشرفت آپلود | |
for i in range(100): | |
time.sleep(0.01) | |
progress = (i + 1) / 100 | |
progress_bar.progress(progress) | |
progress_text.text(f"در حال آپلود... {int(progress * 100)}%") | |
progress_text.empty() | |
progress_bar.empty() | |
return "success", f"✅ فایل '{file.name}' با موفقیت آپلود شد" | |
except Exception as e: | |
return "error", f"❌ خطا در آپلود فایل: {str(e)}" | |
def list_files(self, search_term="", file_type=None): | |
"""لیست فایلها با قابلیت جستجو و فیلتر""" | |
files = [] | |
for f in self.root_path.iterdir(): | |
if f.is_file(): | |
if file_type and file_type != "همه" and f.suffix.lower() != file_type.lower(): | |
continue | |
if search_term and search_term.lower() not in f.name.lower(): | |
continue | |
files.append(f.name) | |
return sorted(files) | |
def preview_file(self, filename): | |
"""پیشنمایش پیشرفته فایل""" | |
try: | |
path = self.root_path / filename | |
if path.suffix.lower() in ['.jpg', '.jpeg', '.png', '.gif']: | |
img = Image.open(path) | |
return "image", img | |
elif path.suffix.lower() == '.txt': | |
with open(path, "r", encoding="utf-8") as f: | |
content = f.read(1000) | |
if len(content) >= 1000: | |
content += "\n...[ادامه متن]" | |
return "text", content | |
return "unsupported", "⚠️ پیشنمایش برای این نوع فایل در دسترس نیست" | |
except Exception as e: | |
return "error", f"❌ خطا در نمایش فایل: {str(e)}" | |
def compress_files(self, files): | |
"""فشردهسازی فایلها با نمایش پیشرفت""" | |
try: | |
if not files: | |
return "error", "⚠️ لطفاً حداقل یک فایل انتخاب کنید" | |
zip_name = f"compressed_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip" | |
zip_path = self.root_path / zip_name | |
progress_text = st.empty() | |
progress_bar = st.progress(0) | |
with zipfile.ZipFile(zip_path, 'w') as zipf: | |
for i, file in enumerate(files, 1): | |
file_path = self.root_path / file | |
zipf.write(file_path, file) | |
progress = i / len(files) | |
progress_bar.progress(progress) | |
progress_text.text(f"در حال فشردهسازی... {int(progress * 100)}%") | |
progress_text.empty() | |
progress_bar.empty() | |
return "success", f"✅ فایلها با موفقیت در '{zip_name}' فشرده شدند" | |
except Exception as e: | |
return "error", f"❌ خطا در فشردهسازی: {str(e)}" | |
def main(): | |
st.markdown('<div class="main-container">', unsafe_allow_html=True) | |
st.title("💼 سیستم مدیریت فایل پیشرفته") | |
file_manager = FileManager() | |
# بخش آپلود فایل | |
st.markdown(""" | |
<div class="upload-area"> | |
<h3>📤 آپلود فایل</h3> | |
<p>فایل خود را اینجا رها کنید یا کلیک کنید</p> | |
<div class="progress-bar"> | |
<div class="progress-fill"></div> | |
</div> | |
</div> | |
""", unsafe_allow_html=True) | |
uploaded_file = st.file_uploader( | |
"", | |
type=["jpg", "jpeg", "png", "gif", "txt", "pdf", "doc", "docx", "xls", "xlsx", "mp3", "mp4", "zip"], | |
accept_multiple_files=False | |
) | |
if uploaded_file: | |
status, message = file_manager.upload_file(uploaded_file) | |
if status == "success": | |
st.success(message) | |
else: | |
st.error(message) | |
# بخش جستجو و فیلتر | |
col1, col2 = st.columns([2, 1]) | |
with col1: | |
search_term = st.text_input("🔍 جستجوی فایل", placeholder="نام فایل را وارد کنید...") | |
with col2: | |
file_type = st.selectbox( | |
"📁 نوع فایل", | |
["همه", ".jpg", ".jpeg", ".png", ".gif", ".txt", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".mp3", ".mp4", ".zip"] | |
) | |
# نمایش لیست فایلها | |
files = file_manager.list_files(search_term, file_type) | |
if not files: | |
st.markdown(""" | |
<div class="info-message"> | |
📭 هیچ فایلی یافت نشد | |
</div> | |
""", unsafe_allow_html=True) | |
else: | |
for file in files: | |
file_info = file_manager.get_file_info(file) | |
with st.container(): | |
st.markdown(f""" | |
<div class="file-card"> | |
<div style="display: flex; justify-content: space-between; align-items: center;"> | |
<div> | |
<h3>{file_info['icon']} {file}</h3> | |
<p>اندازه: {file_info['size']} | نوع: {file_info['type']} | آخرین تغییر: {file_info['modified']}</p> | |
</div> | |
</div> | |
</div> | |
""", unsafe_allow_html=True) | |
col1, col2, col3, col4 = st.columns([1, 1, 1, 1]) | |
with col1: | |
if st.button("👁️ نمایش", key=f"preview_{file}"): | |
preview_type, preview_content = file_manager.preview_file(file) | |
if preview_type == "image": | |
st.image(preview_content, use_column_width=True) | |
elif preview_type == "text": | |
st.text(preview_content) | |
else: | |
st.warning(preview_content) | |
with col2: | |
if st.button("🗑️ حذف", key=f"delete_{file}"): | |
if st.session_state.get('admin_logged_in', False): | |
status, message = file_manager.delete_file(file) | |
st.success(message) if status == "success" else st.error(message) | |
else: | |
st.error("⛔ فقط مدیر میتواند فایلها را حذف کند") | |
with col3: | |
with open(file_manager.root_path / file, 'rb') as f: | |
st.download_button( | |
"⬇️ دانلود", | |
f.read(), | |
file_name=file, | |
key=f"download_{file}" | |
) | |
# بخش فشردهسازی | |
st.markdown(""" | |
<div class="file-card"> | |
<h3>🗜️ فشردهسازی فایلها</h3> | |
</div> | |
""", unsafe_allow_html=True) | |
selected_files = st.multiselect( | |
"فایلهای مورد نظر را انتخاب کنید", | |
files, | |
key="compress_files" | |
) | |
if st.button("📦 ساخت فایل ZIP"): | |
if selected_files: | |
status, message = file_manager.compress_files(selected_files) | |
if status == "success": | |
st.success(message) | |
else: | |
st.error(message) | |
else: | |
st.warning("⚠️ لطفاً حداقل یک فایل انتخاب کنید") | |
st.markdown('</div>', unsafe_allow_html=True) | |
if __name__ == "__main__": | |
main() | |