吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 29987|回复: 998
收起左侧

[Windows] 局域网文件共享工具 | 一键共享文件夹到网页

    [复制链接]
暗夜硝烟 发表于 2025-12-4 15:49
本帖最后由 暗夜硝烟 于 2025-12-4 15:55 编辑

局域网文件共享工具

支持添加文件夹右键快速启动共享​

可自定义选择共享文件夹路径​

支持自定义端口(默认 5995,可改)​

启动后通过网页即可上传下载

image.png

image.png

image.png

image.png



程序和源码下载链接:https://www.123865.com/s/klfbVv-L67Ih
llanzou云盘 https://wwbhx.lanzout.com/b00mpz81qj密码:4c0k

免费评分

参与人数 251吾爱币 +224 热心值 +221 收起 理由
woxobo + 1 + 1 谢谢@Thanks!
大话 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
PuzzleA + 1 谢谢@Thanks!
听见整个森林 + 1 + 1 谢谢@Thanks!
xiaoxigua2017 + 1 谢谢@Thanks!
huanling8866 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
pyz + 1 + 1 我很赞同!
m361623666 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
waiter0201 + 1 + 1 我很赞同!
Tassos + 1 + 1 鼓励转贴优秀软件安全工具和文档!
Coonly + 1 + 1 谢谢@Thanks!
rzjh + 1 + 1 我很赞同!
叮铛浪子 + 1 + 1 增加局域网打印机功能就更全了。
wy535564 + 1 + 1 热心回复!
Zhaowa + 1 热心回复!
calinrun + 1 谢谢@Thanks!
feelsg + 1 + 1 我很赞同!
泪之火 + 1 + 1 谢谢@Thanks!
ycat + 2 + 1 谢谢@Thanks!
clangg + 1 + 1 谢谢@Thanks!
Herculee + 1 + 1 谢谢@Thanks!
clffeng + 1 谢谢@Thanks!
一只梦蝶 + 1 + 1 热心回复!
还没②够 + 1 谢谢@Thanks!
yuanfangbro0701 + 1 + 1 我很赞同!
frank3706 + 1 + 1 谢谢@Thanks!
一年三季 + 1 + 1 谢谢@Thanks!
guoruihotel + 1 + 1 谢谢@Thanks!
3211236840 + 1 我很赞同!
yangxu3985190 + 1 + 1 热心回复!
debugok + 1 + 1 谢谢@Thanks!
77wyb + 1 + 1 谢谢@Thanks!
aiqike + 1 + 1 我很赞同!
z小伟style + 1 + 1 谢谢@Thanks!
rrdx0937 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
huanzhiyiran + 1 + 1 我很赞同!
lmh314 + 1 谢谢@Thanks!
FAMUDUI + 1 + 1 谢谢@Thanks!
sincos + 1 + 1 谢谢@Thanks!
beijiyu292 + 1 + 1 谢谢@Thanks!
啊i先生 + 1 热心回复!
flyfish441 + 1 + 1 我很赞同!
小粑666 + 1 + 1 谢谢@Thanks!
jimjinhu + 1 + 1 谢谢@Thanks!
tong_xing + 1 + 1 我很赞同!
msetd21 + 1 + 1 热心回复!
六色魔方 + 1 + 1 我很赞同!
rebirthboy + 1 我很赞同!
xiaomr1990 + 1 + 1 谢谢@Thanks!
wang82530 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
aaa661179 + 1 + 1 热心回复!
yjn866y + 1 + 1 谢谢@Thanks!
muzi337 + 1 + 1 已经处理,感谢您对吾爱破解论坛的支持!
Song0913 + 1 + 1 谢谢@Thanks!
lyq87 + 1 + 1 谢谢@Thanks!
caiaiii + 1 我很赞同!
huzhi888 + 1 为啥有这个ip:26.135.69.148 美国的
jokesun + 1 + 1 谢谢@Thanks!
cccfreedom + 1 我很赞同!
wuaitomyty + 1 热心回复!
Tiniaual + 1 + 1 热心回复!
qingne0130 + 1 + 1 我很赞同!
youngdh + 1 + 1 我很赞同!
luojp52pojie520 + 1 + 1 谢谢@Thanks!
ZYZY123 + 1 + 1 谢谢@Thanks!
alexxi0571 + 1 + 1 谢谢@Thanks!
mmqk + 1 谢谢@Thanks!
zj_tj + 1 + 1 热心回复!
theStyx + 1 我很赞同!
第三世界 + 1 热心回复!
myheng2 + 1 谢谢@Thanks!
af114708 + 1 谢谢@Thanks!
dachui666 + 1 + 1 谢谢@Thanks!
Oraer + 1 + 1 我很赞同!
hxw0204 + 1 + 1 谢谢@Thanks!
voila。 + 1 + 1 热心回复!
xiaoyaodulang + 1 + 1 我很赞同!
ww886 + 1 + 1 谢谢@Thanks!
Duke0910 + 1 + 1 谢谢@Thanks!
mmtzwyd + 1 + 1 我很赞同!
jokern3 + 1 + 1 我很赞同!
equwei + 1 + 1 热心回复!
zp999 + 1 谢谢@Thanks!
不流泪的鱼 + 1 + 1 谢谢@Thanks!
exho945 + 1 + 1 我很赞同!
zjh106 + 1 + 1 谢谢@Thanks!
Popyey + 1 + 1 热心回复!
xiebing_721 + 1 + 1 热心回复!
oranges + 1 我很赞同!
uoyleef + 1 谢谢@Thanks!
kove1314 + 1 + 1 可以可以
Ning1016 + 1 + 1 谢谢@Thanks!
hdydy + 1 谢谢@Thanks!
degas8888 + 1 + 1 热心回复!
wssln + 1 + 1 我很赞同!
superjason + 1 鼓励转贴优秀软件安全工具和文档!
ab0902 + 1 + 1 热心回复!
hjbabin + 1 + 1 我很赞同!
tedcao + 1 + 1 用心讨论,共获提升!
Yuankong + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

ygq170063 发表于 2025-12-4 17:56
本帖最后由 ygq170063 于 2025-12-12 15:00 编辑

写的非常好,然后我给你做了一些修改,你可以试试看,更美观 也更实用

成品打包:https://fjjy.lanzoub.com/b02p560bzg 密码:26f3(v6.0)

v6.0更新内容:

1、实现批量下载和文件夹下载功能
2、支持流式上传
3、流式下载
4、解除1GB限制,对上传文件的大小不做任何限制
5、支持断点下载

v5.0更新内容
1.修复上传失败的问题
2.新增外网穿透访问的功能,此功能并不是非常成熟,但是勉强也可以使用。不推荐,但给感兴趣的朋友尝鲜。

v4.0更新内容:
1.新增开机启动
2.修复右键功能
3.新增最小化至托盘
4.修复打开文件后 无法返回上一级的问题

v3.0更新内容:
1.面板UI重新布局
2.路径支持拖拽
3.启动/停止 按钮合并
4.打开浏览器的同时将局域网链接一并也给复制了,方便一键分享
5.新增访问记录,方便随时查询
6.共享页面,操作列,新增 “打开”按钮
7.一键停止
8.重新更换软件图标,更洋气
9.面板支持位置记忆,支持大小记忆
10.共享路径支持记忆,重新打开,默认上次路径
11.端口支持记忆,重新打开,默认上次端口号
12.字体增大,页面更清晰

5.0源码:
[Python] 纯文本查看 复制代码
import os
import sys
import socket
import webbrowser
import threading
import time
import queue
from pathlib import Path
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import urllib.parse
import base64
import platform
import subprocess
from email.header import Header
import winreg
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit, QFileDialog, QMessageBox, QSizePolicy, QSystemTrayIcon, QMenu, QAction
from PyQt5.QtCore import QSettings, QTimer, pyqtSignal, QSize, QPoint
from PyQt5.QtGui import QIcon, QPixmap, QFont

IS_FROZEN = getattr(sys, 'frozen', False) or "__compiled__" in globals()
DEFAULT_PORT = 5995
DEFAULT_SHARE_PATH = str(Path.home() / "Downloads")
server_instance = None

ICON_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABGZJREFUWEe9V0mMFGUU/t5fVV203UxY3BhmRhlAvYwLonNyLgY7BoIHMTFcEBxITNR4MS5w5aDRuEQ9iHLzhAcvkhghLkziTNAElSjMkNBmgLA1E0en9/qfeX9V9fRS1QtEXtLp1F/1/ve97fvfTwBw5sCGVx1Hv81gELQs1URWSBM08wxr/djw7pOXGj64wQf689NHMymn+jUUWxAAxE1bKjADslzR+tjwrl/HbtBmgzqdPbjxokX6DibfcyLy/yFwAhEEAgIEjysVMLPAkk+b4UaBa9wLCBW1WMsefIit1G0gJ+WbVLKFv61RZB/Q4rP/3hhffNXoVbBDg+GaMz5qruZRXbgMyn7+IC8f24++dU/WqUX5EeVrvYlIc7HZ+ufMN8j9+CYo91WGk8Ob4K64VyLru30TpHJtGgtnj4D01Hg3afx/ILF02OSeLgBEhUXU4tY74Q3TpUIANynuLbgkAlN7OLacYxwxtVLXsp38jX9vAOxmsOm9tiJGzW/lw6D0WnCiD1S+BuSOQ+XPd1KPfk9dAgiNY2gb1O2j8PJXoAuXYKf6Qem74J3YByrmDIsSdXZmEU0PALwVG2CvH0dh4iW4lAdZwtwEXvUE1EAGXJoDewXQqfdAPpt1IV0AMPlmDb7vRVQuTMCZ+xnKTghnm5SI17p/KzQT1NI1IPKA0x/51B3Q+nXXQBh6rRSc0U9QPPos3PTy1vajqgEJ2MDGD1E++S7s4nko9gzQjgAIls+CTaKl6JaNQA1sBqWGUJ14DnZCzoxmkSj5vMCDT0H1Z6AXZsEXDkPN/dYJwDhDTuImEUDV5ACckddQmf0WlewhLLEA5bgxBCRHiUTBQ6lUhr3mGVirM9B/vAOVn40B0aYGmDXokfdRmv4C9tVjsBJiWIqrA2lJDZjjTkHfOgZ199Pg4y+DqNVJc8BH8oB433cPaHAb9C9vwFmytEMuo7Ii8xWAkb3Q2UOw/p2OAN8GAK/eDK+ch3XxCMi2u2iplhyaWHmDW6DLediXjwYRrP+uLYAt8MoLsK5810U7RZSltCgz9NBWeMV5OFe/7xHAnZvg2X2wzn3ZI7stgmGtoYe3w/s7i8TcT72lwIMDa/QDeFMvwOqJXn0A0kUeFOzRj1H4YQeSyaj2jUmBKEu967U7gVtWgX/fbwZSUnVcYQimiTvkUeiZAU8zrAf2Qs//Bcx8BstN9hAB+VQ8IECN7AMnVkLPHDB3BjOINnRi02BiACjY65+HLuZQmXwdbjoddFFzC5sIRBORfxnQYFgouevg3v+KiYJQDRnPQyT+SuMgzyiceAtq/hRc1wYp4ZAoCVMgAY8bzOS+oBmsq9C64oc9jH54iZHZzkzbfvhBCoosKEsBQkCx80Y9gOCWUctsy9kQXI9MhbUhRONIS55iAlBfhFJg9VEwALo919shCmwHN69aAUkx+RNROJZ3MRz3zofxGjUAkzslddchoVK99705Id1ChcOPB6dGu4OuU9J798AvMcJ/PwkXZ+WN+AAAAAAASUVORK5CYII="

class FileShareHandler(BaseHTTPRequestHandler):
    _open_cache = {}
    _open_lock = threading.Lock()
    def __init__(self, *args, **kwargs):
        self.shared_path = kwargs.pop('shared_path', None)
        self.current_path = kwargs.pop('current_path', None) or self.shared_path
        self.access_logger = kwargs.pop('access_logger', None)
        super().__init__(*args, **kwargs)
    def log_message(self, format, *args):
        pass
    def do_GET(self):
        if self.access_logger:
            self.access_logger(self.client_address[0], self.requestline)
        try:
            parsed_path = urllib.parse.urlparse(self.path)
            query_params = urllib.parse.parse_qs(parsed_path.query)
            if parsed_path.path == '/download':
                filename = query_params.get('file', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if filename:
                    self.download_file(filename, current_path)
                    return
            elif parsed_path.path == '/open':
                filename = query_params.get('file', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if filename:
                    self.open_file(filename, current_path)
                    return
            elif parsed_path.path == '/delete':
                filename = query_params.get('file', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if filename:
                    self.delete_file(filename, current_path)
                    return
            elif parsed_path.path == '/dir':
                dir_path = query_params.get('path', [self.shared_path])[0]
                self.current_path = urllib.parse.unquote(dir_path)
                self.send_main_page()
                return
            elif parsed_path.path == '/' or parsed_path.path == '':
                self.current_path = self.shared_path
                self.send_main_page()
                return
            else:
                self.send_error(404, "Not Found")
        except Exception as e:
            self.send_error(500, "Server error: {0}".format(str(e)))
    def do_POST(self):
        if self.access_logger:
            self.access_logger(self.client_address[0], self.requestline)
        try:
            parsed_path = urllib.parse.urlparse(self.path)
            query_params = urllib.parse.parse_qs(parsed_path.query)
            if parsed_path.path == '/upload':
                current_path = query_params.get('path', [self.shared_path])[0]
                self.handle_upload(current_path)
                return
            elif parsed_path.path == '/rename':
                old_name = query_params.get('old', [''])[0]
                new_name = query_params.get('new', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if old_name and new_name:
                    self.rename_file(old_name, new_name, current_path)
                else:
                    self.send_error(400, "Bad Request")
                return
            else:
                self.send_error(404, "Not Found")
        except Exception as e:
            self.send_error(500, "Error: {0}".format(str(e)))
    def send_main_page(self):
        try:
            current_path = Path(self.current_path).resolve()
            shared_path = Path(self.shared_path).resolve()
            if shared_path not in current_path.parents and current_path != shared_path:
                current_path = shared_path
            breadcrumb_html = self.get_breadcrumb(current_path)
            files = []
            dirs = []
            for item in current_path.iterdir():
                if item.is_file():
                    files.append({
                        'name': item.name,
                        'size': self.format_size(item.stat().st_size),
                        'modified': time.strftime('%Y-%m-%d %H:%M', 
                                                time.localtime(item.stat().st_mtime)),
                        'path': str(item)
                    })
                else:
                    dirs.append({
                        'name': item.name,
                        'modified': time.strftime('%Y-%m-%d %H:%M', 
                                                time.localtime(item.stat().st_mtime)),
                        'path': str(item)
                    })
            dirs.sort(key=lambda x: x['name'].lower())
            files.sort(key=lambda x: x['name'].lower())
            file_list_html = ''
            if current_path != shared_path:
                parent_path = str(current_path.parent)
                file_list_html += '''
                <tr>
                    <td><span class="dir-link">&#128193; .. (返回上一级)</span></td>
                    <td>-</td>
                    <td>-</td>
                    <td></td>
                </tr>
                '''.format(urllib.parse.quote(parent_path))
            for d in dirs:
                encoded_path = urllib.parse.quote(d['path'])
                file_list_html += '''
                <tr>
                    <td><span class="dir-link">&#128193; {1}</span></td>
                    <td>-</td>
                    <td>{2}</td>
                    <td></td>
                </tr>
                '''.format(encoded_path, d['name'], d['modified'])
            current_path_encoded = urllib.parse.quote(str(current_path))
            for f in files:
                encoded_name = urllib.parse.quote(f['name'])
                file_list_html += '''
                <tr>
                    <td>&#128196; {0}</td>
                    <td>{1}</td>
                    <td>{2}</td>
                    <td class="action-buttons">
                        <button class="action-btn open-btn">打开</button>
                        <button class="action-btn download-btn">下载</button>
                        <button class="action-btn rename-btn">重命名</button>
                        <button class="action-btn delete-btn">删除</button>
                    </td>
                </tr>
                '''.format(f['name'], f['size'], f['modified'], encoded_name, current_path_encoded)
            html = '''
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="UTF-8">
                <title>文件共享</title>
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <style>
                    body {{ font-family: Arial, sans-serif; margin: 20px; background-color: #f0f0f0; }}
                    .container {{ max-width: 1000px; margin: 0 auto; background: white; padding: 20px; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
                    .header {{ display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; margin-bottom: 20px; }}
                    .header h1 {{ color: #333; margin: 0; }}
                    .upload-btn {{ background: #4CAF50; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }}
                    .upload-btn:hover {{ background: #45a049; }}
                    #fileInput {{ display: none; }}
                    .breadcrumb {{ margin: 15px 0; padding: 10px; background: #f5f5f5; border-radius: 4px; }}
                    .breadcrumb a {{ color: #4CAF50; text-decoration: none; margin: 0 5px; }}
                    .breadcrumb a:hover {{ text-decoration: underline; }}
                    .breadcrumb span {{ color: #666; margin: 0 5px; }}
                    .dir-link {{ color: #4CAF50; cursor: pointer; font-weight: 500; }}
                    .dir-link:hover {{ color: #45a049; text-decoration: underline; }}
                    table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
                    th {{ background-color: #4CAF50; color: white; padding: 12px; text-align: left; }}
                    td {{ padding: 10px; border-bottom: 1px solid #ddd; vertical-align: middle; }}
                    tr:hover {{ background-color: #f5f5f5; }}
                    .action-buttons {{ display: flex; gap: 6px; flex-wrap: nowrap; }}
                    .action-btn {{ padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; white-space: nowrap; flex-shrink: 0; }}
                    .open-btn {{ background: #ff9800; color: white; }}
                    .open-btn:hover {{ background: #f57c00; }}
                    .download-btn {{ background: #4CAF50; color: white; }}
                    .download-btn:hover {{ background: #45a049; }}
                    .rename-btn {{ background: #2196F3; color: white; }}
                    .rename-btn:hover {{ background: #0b7dda; }}
                    .delete-btn {{ background: #f44336; color: white; }}
                    .delete-btn:hover {{ background: #d32f2f; }}
                    .status {{ padding: 10px; margin: 10px 0; border-radius: 5px; display: none; position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 1000; min-width: 200px; text-align: center; }}
                    .success {{ background: #d4edda; color: #155724; }}
                    .error {{ background: #f8d7da; color: #721c24; }}
                </style>
            </head>
            <body>
                <div class="container">
                    <div class="header">
                        <h1>&#128193; 文件共享</h1>
                        <div>
                            <button class="upload-btn">上传文件</button>
                            <input type="file" id="fileInput">
                        </div>
                    </div>
                    <div class="breadcrumb">{0}</div>
                    <div id="status" class="status"></div>
                    <table>
                        <thead><tr><th width="35%">名称</th><th width="15%">大小</th><th width="20%">修改时间</th><th width="30%">操作</th></tr></thead>
                        <tbody>{1}</tbody>
                    </table>
                </div>
                <script>
                    function showStatus(m,t){{const s=document.getElementById('status');s.textContent=m;s.className='status '+t;s.style.display='block';setTimeout(()=>s.style.display='none',3000);}}
                    function handleFileSelect(e){{const f=e.target.files[0];if(f){{uploadFile(f,'{2}');e.target.value='';}}}}
                    async function uploadFile(f,p){{const d=new FormData();d.append('file',f);showStatus('上传中...','');try{{const r=await fetch(`/upload?path=${{p}}`,{{method:'POST',body:d}});if(r.ok){{showStatus('上传成功!','success');setTimeout(()=>location.reload(),1000);}}else throw new Error('上传失败');}}catch(e){{showStatus('上传失败: '+e.message,'error');}}}}
                    function downloadFile(n,p){{window.open(`/download?file=${{n}}&path=${{p}}`,'_blank');}}
                    function deleteFile(n,p){{const nd=decodeURIComponent(n);if(confirm('确定要删除 "'+nd+'" 吗?')){{fetch(`/delete?file=${{n}}&path=${{p}}`).then(r=>r.ok?showStatus('删除成功!','success'):showStatus('删除失败','error')).then(()=>setTimeout(()=>location.reload(),500));}}}}
                    function renameFile(o,p){{const od=decodeURIComponent(o);const nn=prompt('请输入新的文件名:',od);if(nn&&nn!==od){{fetch(`/rename?old=${{o}}&new=${{encodeURIComponent(nn)}}&path=${{p}}`,{{method:'POST'}}).then(r=>r.ok?showStatus('重命名成功!','success'):showStatus('重命名失败','error')).then(()=>setTimeout(()=>location.reload(),500));}}}}
                    let _ofo={{}};function openFile(n,p){{const k=n+p;if(_ofo[k])return;_ofo[k]=1;setTimeout(()=>delete _ofo[k],800);fetch(`/open?file=${{n}}&path=${{p}}`).then(r=>r.json()).then(d=>d.success?showStatus('打开指令已发送','success'):showStatus('打开失败','error'));}}
                    function openDir(p){{window.location.href=`/dir?path=${{p}}`;}}
                </script>
            </body>
            </html>
            '''.format(breadcrumb_html, file_list_html, current_path_encoded)
            self.send_response(200)
            self.send_header('Content-Type', 'text/html; charset=utf-8')
            self.end_headers()
            self.wfile.write(html.encode('utf-8'))
        except Exception as e:
            self.send_error(500, "Page error: {0}".format(str(e)))
    def get_breadcrumb(self, current_path):
        shared_path = Path(self.shared_path).resolve()
        current_path = Path(current_path).resolve()
        breadcrumb = []
        breadcrumb.append('<a href="/dir?path={0}">根目录</a>'.format(urllib.parse.quote(str(shared_path))))
        if current_path != shared_path:
            rel_parts = current_path.relative_to(shared_path).parts
            current_dir = shared_path
            for part in rel_parts:
                current_dir = current_dir / part
                breadcrumb.append('<span>/</span>')
                breadcrumb.append('<a href="/dir?path={0}">{1}</a>'.format(urllib.parse.quote(str(current_dir)), part))
        return ''.join(breadcrumb)
    
    def open_file(self, filename, current_path):
        try:
            filename = urllib.parse.unquote(filename)
            current_path = urllib.parse.unquote(current_path)
            file_path = Path(current_path) / filename
            if not str(file_path.resolve()).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return
            if not file_path.exists() or not file_path.is_file():
                self.send_error(404, "File not found")
                return
            cache_key = str(file_path.resolve())
            current_time = time.time()
            with FileShareHandler._open_lock:
                if cache_key in FileShareHandler._open_cache:
                    if current_time - FileShareHandler._open_cache[cache_key] < 1.5:
                        self.send_response(200)
                        self.send_header('Content-Type', 'application/json')
                        self.end_headers()
                        self.wfile.write(b'{"success": true, "message": "Already opening."}')
                        return
                FileShareHandler._open_cache[cache_key] = current_time
                for k in list(FileShareHandler._open_cache.keys()):
                    if current_time - FileShareHandler._open_cache[k] > 10:
                        del FileShareHandler._open_cache[k]
            system = platform.system()
            if system == "Windows":
                os.startfile(str(file_path))
            elif system == "Darwin":
                subprocess.Popen(["open", str(file_path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            else:
                subprocess.Popen(["xdg-open", str(file_path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"success": true, "message": "Open command sent."}')
        except Exception as e:
            self.send_response(500)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"success": false, "message": "Failed to open file."}')
            
    def download_file(self, filename, current_path):
        try:
            filename = urllib.parse.unquote(filename)
            current_path = urllib.parse.unquote(current_path)
            file_path = Path(current_path) / filename
            if not str(file_path).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return
            if not file_path.exists():
                self.send_error(404, "File not found")
                return
            if not file_path.is_file():
                self.send_error(400, "Not a file")
                return
            encoded_filename = Header(filename, 'utf-8').encode()
            self.send_response(200)
            self.send_header('Content-Type', 'application/octet-stream')
            self.send_header(
                'Content-Disposition',
                f'attachment; filename="{encoded_filename}"; filename*=UTF-8\'\'{urllib.parse.quote(filename)}'
            )
            self.send_header('Content-Length', str(file_path.stat().st_size))
            self.end_headers()
            chunk_size = 1024 * 1024
            with open(file_path, 'rb') as f:
                while chunk := f.read(chunk_size):
                    self.wfile.write(chunk)
        except Exception as e:
            self.send_error(500, "Download error: {0}".format(str(e)))
    def delete_file(self, filename, current_path):
        try:
            filename = urllib.parse.unquote(filename)
            current_path = urllib.parse.unquote(current_path)
            file_path = Path(current_path) / filename
            if not str(file_path).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return
            if not file_path.exists():
                self.send_error(404, "File not found")
                return
            if file_path.is_file():
                file_path.unlink()
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(b'{"success": true}')
            else:
                self.send_error(400, "Not a file")
        except Exception as e:
            self.send_error(500, "Delete error: {0}".format(str(e)))
    def rename_file(self, old_name, new_name, current_path):
        try:
            old_name = urllib.parse.unquote(old_name)
            new_name = urllib.parse.unquote(new_name)
            current_path = urllib.parse.unquote(current_path)
            old_path = Path(current_path) / old_name
            new_path = Path(current_path) / new_name
            if not str(old_path).startswith(str(Path(self.shared_path).resolve())) or \
               not str(new_path).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return
            if not old_path.exists():
                self.send_error(404, "File not found")
                return
            if new_path.exists():
                self.send_error(409, "File already exists")
                return
            if old_path.is_file():
                old_path.rename(new_path)
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(b'{"success": true}')
            else:
                self.send_error(400, "Not a file")
        except Exception as e:
            self.send_error(500, "Rename error: {0}".format(str(e)))
    def handle_upload(self, current_path):
        try:
            current_path = urllib.parse.unquote(current_path)
            current_path = Path(current_path).resolve()
            shared_path = Path(self.shared_path).resolve()
            if not str(current_path).startswith(str(shared_path)):
                current_path = shared_path
            content_length = int(self.headers.get('Content-Length', 0))
            if content_length == 0:
                self.send_error(400, "No content")
                return
            data = self.rfile.read(content_length)
            content_type = self.headers.get('Content-Type', '')
            if 'boundary=' not in content_type:
                self.send_error(400, "Invalid content type")
                return

            boundary = content_type.split('boundary=')[1].strip().strip('"').encode('utf-8')
            parts = data.split(b'--' + boundary)
            for part in parts:
                if b'filename="' in part:
                    parts_split = part.split(b'\r\n\r\n', 1)
                    if len(parts_split) < 2:
                        continue
                    header = parts_split[0].decode('utf-8', errors='ignore')
                    file_content = parts_split[1]

                    # Correctly remove the trailing CRLF before the next boundary
                    if file_content.endswith(b'\r\n'):
                        file_content = file_content[:-2]

                    filename_start = header.find('filename="')

                    
                    if filename_start == -1:
                        continue
                    filename_start += 10
                    filename_end = header.find('"', filename_start)
                    if filename_end == -1:
                        continue
                    filename = header[filename_start:filename_end]
                    if not filename:
                        continue

                    save_path = current_path / filename
                    counter = 1
                    while save_path.exists():
                        name, ext = os.path.splitext(filename)
                        new_name = "{0}_{1}{2}".format(name, counter, ext)
                        save_path = current_path / new_name
                        counter += 1
                    with open(save_path, 'wb') as f:
                        f.write(file_content)
                    self.send_response(200)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(b'{"success": true}')
                    return
            self.send_error(400, "No file uploaded")
        except Exception as e:
            self.send_error(500, "Upload error: {0}".format(str(e)))
    def format_size(self, size):
        for unit in ['B', 'KB', 'MB', 'GB']:
            if size < 1024.0:
                return "{0:.1f} {1}".format(size, unit)
            size /= 1024.0
        return "{0:.1f} TB".format(size)

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    daemon_threads = True
    def __init__(self, server_address, RequestHandlerClass):
        self.allow_reuse_address = True
        super().__init__(server_address, RequestHandlerClass)

class FileSharer:
    def __init__(self):
        self.server = None
        self.server_thread = None
        self.shared_path = None
        self.port = DEFAULT_PORT
        self.is_running = False
        self.status_callback = None
        self.log_queue: queue.Queue = queue.Queue()
    def set_status_callback(self, callback):
        self.status_callback = callback
    def update_status(self, message, is_running):
        self.is_running = is_running
        if self.status_callback:
            self.status_callback(message, is_running)
    def _allow_firewall_access(self):
        if platform.system() != 'Windows':
            return
        try:
            subprocess.run(
                [
                    'netsh', 'advfirewall', 'firewall', 'add', 'rule',
                    'name=FileShareTool_{0}'.format(self.port),
                    'dir=in', 'action=allow', 'protocol=TCP',
                    'localport={0}'.format(self.port),
                    'remoteip=localsubnet', 'profile=private',
                    'enable=yes'
                ],
                capture_output=True,
                text=True,
                check=True
            )
            print("已添加新防火墙规则 {0} 端口".format(self.port))
        except Exception as e:
            print("添加防火墙规则失败: {0}".format(e))
    def stop(self):
        if not self.is_running or not self.server:
            return
        self.update_status("已停止", False)
        def cleanup_worker():
            try:
                self.server.shutdown()
                self.server.server_close()
            except Exception as e:
                print(f"后台清理服务器时出错: {e}")
            finally:
                self.server = None
                global server_instance
                server_instance = None
        threading.Thread(target=cleanup_worker, daemon=True).start()
    def get_all_local_ips(self):
        ips = []
        try:
            for interface in socket.if_nameindex():
                if interface[1] == 'lo':
                    continue
                try:
                    addrs = socket.getaddrinfo(
                        socket.gethostname(),
                        None,
                        socket.AF_INET,
                        0,
                        socket.SOL_TCP
                    )
                    for addr in addrs:
                        ip = addr[4][0]
                        if ip not in ips and not ip.startswith('127.'):
                            ips.append(ip)
                except:
                    continue
            if not ips:
                s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                s.connect(('8.8.8.8', 80))
                ips.append(s.getsockname()[0])
                s.close()
        except:
            ips.append('127.0.0.1')
        return ips if ips else ['127.0.0.1']
    def start(self, path=None, port=None):
        if self.is_running:
            return False, "服务已在运行中。"
        self.update_status("正在启动...", True)
        self.shared_path = path if path and Path(path).exists() else DEFAULT_SHARE_PATH
        self.port = port if port and isinstance(port, int) and 1 <= port <= 65535 else DEFAULT_PORT
        if not Path(self.shared_path).exists():
            self.update_status("启动失败", False)
            return False, "路径不存在: {0}".format(self.shared_path)
        if Path(self.shared_path).is_file():
            self.shared_path = str(Path(self.shared_path).parent)
            print("提示:共享的为单个文件所在目录,路径已自动切换为: {0}".format(self.shared_path))
        def access_logger(client_ip, request_line):
            self.log_queue.put((time.strftime('%H:%M:%S'), client_ip, request_line))
        try:
            self.server = ThreadedHTTPServer(('0.0.0.0', self.port), 
                                           lambda *args: FileShareHandler(*args, shared_path=self.shared_path, access_logger=access_logger))
            self._allow_firewall_access()
            self.server_thread = threading.Thread(target=self.server.serve_forever, daemon=True)
            self.server_thread.start()
            local_ips = self.get_all_local_ips()
            main_ip = local_ips[0] if local_ips else '127.0.0.1'
            local_url = "http://localhost:{0}".format(self.port)
            network_urls = ["http://{0}:{1}".format(ip, self.port) for ip in local_ips]
            server_instance = self
            self.update_status("运行中 (端口 {0})".format(self.port), True)
            return True, {
                'local_url': local_url,
                'network_urls': network_urls,
                'main_ip': main_ip,
                'all_ips': local_ips,
                'port': self.port,
                'folder': self.shared_path,
                'log_queue': self.log_queue
            }
        except Exception as e:
            self.update_status("启动失败", False)
            return False, "启动失败: {0}".format(str(e))

class FileShareGUI(QMainWindow):
    status_updated = pyqtSignal(str, bool)
    start_completed = pyqtSignal(bool, object)
    def __init__(self, initial_path=None, initial_port=None, auto_start=True):
        super().__init__()
        self.setWindowTitle("文件共享工具")
        self.set_window_icon()
        self.settings = QSettings("FileShareTool", "App")
        self.sharer = FileSharer()
        self.start_result = None
        self.initial_path = initial_path
        self.initial_port = initial_port
        self.auto_start = auto_start
        self.reg_key_name = "FileShareTool"
        self.startup_reg_key_name = "FileShareToolStartup"
        self.log_queue = None
        self.log_timer = QTimer(self)
        self.setAcceptDrops(True)
        self.setup_tray_icon()
        self.create_widgets()
        self.connect_signals()
        self.load_settings()
        self.update_menu_btn_text()
        self.update_startup_btn_text()
        self.update_status("已停止", False)
        if self.auto_start:
            QTimer.singleShot(100, self.auto_start_share)
    def set_window_icon(self):
        try:
            if ICON_BASE64:
                icon_data = base64.b64decode(ICON_BASE64)
                pixmap = QPixmap()
                pixmap.loadFromData(icon_data)
                self.setWindowIcon(QIcon(pixmap))
        except Exception as e:
            pass
    def setup_tray_icon(self):
        self.tray_icon = QSystemTrayIcon(self)
        try:
            if ICON_BASE64:
                icon_data = base64.b64decode(ICON_BASE64)
                pixmap = QPixmap()
                pixmap.loadFromData(icon_data)
                self.tray_icon.setIcon(QIcon(pixmap))
            else:
                self.tray_icon.setIcon(self.style().standardIcon(self.style().SP_ComputerIcon))
        except:
            self.tray_icon.setIcon(self.style().standardIcon(self.style().SP_ComputerIcon))
        tray_menu = QMenu()
        show_action = QAction("显示主窗口", self)
        show_action.triggered.connect(self.show_window)
        tray_menu.addAction(show_action)
        toggle_action = QAction("启动/停止共享", self)
        toggle_action.triggered.connect(self.toggle_share)
        tray_menu.addAction(toggle_action)
        tray_menu.addSeparator()
        quit_action = QAction("退出程序", self)
        quit_action.triggered.connect(self.quit_application)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.activated.connect(self.tray_icon_activated)
        self.tray_icon.setToolTip("文件共享工具")
        self.tray_icon.show()
    def show_window(self):
        self.showNormal()
        self.activateWindow()
        self.raise_()
    def tray_icon_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.show_window()
    def quit_application(self):
        self.save_settings()
        if self.sharer.is_running:
            self.sharer.stop()
            time.sleep(0.1)
        self.tray_icon.hide()
        QApplication.quit()
    def changeEvent(self, event):
        if event.type() == event.WindowStateChange:
            if self.isMinimized():
                QTimer.singleShot(0, self.hide)
                self.tray_icon.showMessage("文件共享工具", "程序已最小化到系统托盘", QSystemTrayIcon.Information, 2000)
        super().changeEvent(event)
    def create_widgets(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        path_layout = QHBoxLayout()
        path_label = QLabel("共享路径:")
        self.path_entry = QLineEdit()
        browse_btn = QPushButton("浏览")
        browse_btn.setFixedWidth(60)
        path_layout.addWidget(path_label)
        path_layout.addWidget(self.path_entry)
        path_layout.addWidget(browse_btn)
        main_layout.addLayout(path_layout)
        controls_layout = QHBoxLayout()
        port_label = QLabel("端口设置:")
        self.port_entry = QLineEdit()
        self.port_entry.setFixedWidth(60)
        self.toggle_share_btn = QPushButton("启动共享")
        self.browser_btn = QPushButton("打开浏览器")
        self.menu_btn = QPushButton("添加右键菜单")
        self.startup_btn = QPushButton("开机自启动")
        controls_layout.addWidget(port_label)
        controls_layout.addWidget(self.port_entry)
        controls_layout.addStretch(1)
        controls_layout.addWidget(self.toggle_share_btn)
        controls_layout.addStretch(1)
        controls_layout.addWidget(self.browser_btn)
        controls_layout.addStretch(1)
        controls_layout.addWidget(self.startup_btn)
        controls_layout.addStretch(1)
        controls_layout.addWidget(self.menu_btn)
        main_layout.addLayout(controls_layout)
        info_label = QLabel("访问信息:")
        main_layout.addWidget(info_label)
        self.info_text = QTextEdit()
        self.info_text.setReadOnly(True)
        self.info_text.setFixedHeight(130)
        main_layout.addWidget(self.info_text)
        log_label = QLabel("访问记录:")
        main_layout.addWidget(log_label)
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        main_layout.addWidget(self.log_text)
        browse_btn.clicked.connect(self.browse_path)
    def connect_signals(self):
        self.sharer.set_status_callback(self.emit_status_update)
        self.toggle_share_btn.clicked.connect(self.toggle_share)
        self.browser_btn.clicked.connect(self.open_browser)
        self.menu_btn.clicked.connect(self.toggle_right_click_menu)
        self.startup_btn.clicked.connect(self.toggle_startup)
        self.status_updated.connect(self.update_status)
        self.start_completed.connect(self.update_ui_after_start)
        self.log_timer.timeout.connect(self.update_log_display)
    def load_settings(self):
        self.resize(self.settings.value("size", QSize(700, 500)))
        self.move(self.settings.value("pos", QPoint(200, 200)))
        saved_path = self.settings.value("path", DEFAULT_SHARE_PATH)
        self.path_entry.setText(self.initial_path or saved_path)
        self.port_entry.setText(str(self.initial_port or self.settings.value("port", DEFAULT_PORT)))
    def save_settings(self):
        self.settings.setValue("size", self.size())
        self.settings.setValue("pos", self.pos())
        self.settings.setValue("path", self.path_entry.text())
        self.settings.setValue("port", self.port_entry.text())
    def closeEvent(self, event):
        self.save_settings()
        if self.sharer.is_running:
            reply = QMessageBox.question(self, '确认', '共享服务仍在运行,确定要退出吗?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.sharer.stop()
                time.sleep(0.1)
                self.tray_icon.hide()
                event.accept()
                QApplication.quit()
            else:
                event.ignore()
        else:
            self.tray_icon.hide()
            event.accept()
            QApplication.quit()
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()
    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            url = event.mimeData().urls()[0]
            path = url.toLocalFile()
            if Path(path).exists():
                self.path_entry.setText(path)
    def browse_path(self):
        path = QFileDialog.getExistingDirectory(self, "选择共享文件夹", self.path_entry.text())
        if path:
            self.path_entry.setText(path)
    def toggle_share(self):
        if self.sharer.is_running:
            self.stop_share()
        else:
            self.start_share()
    def start_share(self):
        path = self.path_entry.text().strip()
        if not path or not Path(path).exists():
            QMessageBox.warning(self, "警告", "请选择一个有效的共享路径!")
            return
        try:
            port = int(self.port_entry.text().strip())
            if not (1 <= port <= 65535):
                raise ValueError
        except ValueError:
            QMessageBox.warning(self, "警告", "请输入有效的端口号 (1-65535)!")
            self.port_entry.setText(str(DEFAULT_PORT))
            return
        self.info_text.clear()
        self.log_text.clear()
        self.toggle_share_btn.setEnabled(False)
        threading.Thread(target=self._start_share_thread, args=(path, port), daemon=True).start()
    def _start_share_thread(self, path, port):
        success, result = self.sharer.start(path, port)
        self.start_result = result
        self.start_completed.emit(success, result)
    def stop_share(self):
        self.sharer.stop()
    def open_browser(self):
        url_to_open = ""
        if self.start_result and 'local_url' in self.start_result and self.sharer.is_running:
            url_to_open = self.start_result['local_url']
            if 'network_urls' in self.start_result and self.start_result['network_urls']:
                network_url = self.start_result['network_urls'][0]
                QApplication.clipboard().setText(network_url)
                self.statusBar().showMessage(f"已复制局域网地址: {network_url}", 4000)
        else:
            try:
                port = int(self.port_entry.text().strip())
                url_to_open = f"http://localhost:{port}"
            except:
                url_to_open = f"http://localhost:{DEFAULT_PORT}"
        if url_to_open:
            webbrowser.open(url_to_open)
    def emit_status_update(self, message, is_running):
        self.status_updated.emit(message, is_running)
    def update_status(self, message, is_running):
        self.statusBar().showMessage(message)
        is_transitioning = "正在" in message
        self.toggle_share_btn.setText("停止共享" if is_running else "启动共享")
        self.toggle_share_btn.setEnabled(not is_transitioning)
        self.browser_btn.setEnabled(is_running and not is_transitioning)
        self.path_entry.setReadOnly(is_running or is_transitioning)
        self.port_entry.setReadOnly(is_running or is_transitioning)
        if is_running:
            self.tray_icon.setToolTip("文件共享工具 - 运行中")
        else:
            self.tray_icon.setToolTip("文件共享工具 - 已停止")
        if not is_running and not is_transitioning:
            self.info_text.clear()
            self.log_text.clear()
            self.log_timer.stop()
            self.log_queue = None
    def update_log_display(self):
        if not self.log_queue:
            return
        while not self.log_queue.empty():
            try:
                timestamp, ip, request = self.log_queue.get_nowait()
                log_entry = f"[{timestamp}] {ip} - {request}"
                self.log_text.append(log_entry)
            except queue.Empty:
                break
    def update_ui_after_start(self, success, result):
        if success:
            info = f"&#128193; 共享路径:{result['folder']}\n"
            info += f"&#127991; 本机端口:{result['port']}\n"
            info += f"&#128187; 本机访问:{result['local_url']}\n"
            info += "&#127760; 局域网址:"
            if result['network_urls']:
                info += result['network_urls'][0]
                for url in result['network_urls'][1:]:
                    info += f"\n{url}"
            else:
                info += "(未找到)"
            info += "\n"
            self.info_text.setText(info)
            self.log_queue = result.get('log_queue')
            if self.log_queue:
                self.log_timer.start(1000)
        else:
            self.info_text.setText(f"错误: {result}")
            QMessageBox.critical(self, "错误", f"启动失败: {result}")
    def auto_start_share(self):
        path = self.initial_path if self.initial_path and Path(self.initial_path).exists() else self.path_entry.text()
        port = self.initial_port if self.initial_port and 1 <= self.initial_port <= 65535 else None
        if path:
            self.path_entry.setText(path)
        if port:
            self.port_entry.setText(str(port))
        if self.auto_start and Path(path).exists():
            print(f"自动启动共享:路径={path},端口={port or DEFAULT_PORT}")
            self.start_share()
    def check_menu_exists(self):
        if platform.system() != 'Windows':
            return False
        try:
            key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}")
            winreg.CloseKey(key)
            return True
        except FileNotFoundError:
            return False
        except Exception:
            return False
    def check_startup_exists(self):
        if platform.system() != 'Windows':
            return False
        try:
            key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0, winreg.KEY_READ)
            try:
                winreg.QueryValueEx(key, self.startup_reg_key_name)
                winreg.CloseKey(key)
                return True
            except FileNotFoundError:
                winreg.CloseKey(key)
                return False
        except Exception:
            return False
    def update_menu_btn_text(self):
        if not self.menu_btn:
            return
        if platform.system() != 'Windows':
            self.menu_btn.setText("右键菜单(仅Windows)")
            self.menu_btn.setEnabled(False)
            return
        if self.check_menu_exists():
            self.menu_btn.setText("移除右键")
        else:
            self.menu_btn.setText("添加右键")
    def update_startup_btn_text(self):
        if not self.startup_btn:
            return
        if platform.system() != 'Windows':
            self.startup_btn.setText("开机自启(仅Windows)")
            self.startup_btn.setEnabled(False)
            return
        if self.check_startup_exists():
            self.startup_btn.setText("取消自启")
        else:
            self.startup_btn.setText("开机自启")
    def toggle_right_click_menu(self):
        if platform.system() != 'Windows':
            QMessageBox.warning(self, "警告", "该功能仅支持Windows系统!")
            return
        try:
            if self.check_menu_exists():
                self.remove_from_right_click_menu()
            else:
                self.add_to_right_click_menu()
            self.update_menu_btn_text()
        except PermissionError:
            QMessageBox.critical(self, "错误", "权限不足!请用管理员权限运行本程序再尝试。")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"操作失败:{str(e)}")
    def toggle_startup(self):
        if platform.system() != 'Windows':
            QMessageBox.warning(self, "警告", "该功能仅支持Windows系统!")
            return
        try:
            if self.check_startup_exists():
                self.remove_startup()
            else:
                self.add_startup()
            self.update_startup_btn_text()
        except PermissionError:
            QMessageBox.critical(self, "错误", "权限不足!请用管理员权限运行本程序再尝试。")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"操作失败:{str(e)}")
    def add_startup(self):
        if IS_FROZEN:
            exe_path = sys.executable
            command_str = f'"{exe_path}"'
        else:
            python_exe_path = sys.executable
            script_path = os.path.abspath(__file__)
            command_str = f'"{python_exe_path}" "{script_path}"'
        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0, winreg.KEY_SET_VALUE)
        winreg.SetValueEx(key, self.startup_reg_key_name, 0, winreg.REG_SZ, command_str)
        winreg.CloseKey(key)
        QMessageBox.information(self, "成功", "已成功设置开机自动启动!\n\n下次开机时程序将自动运行。")
    def remove_startup(self):
        try:
            key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0, winreg.KEY_SET_VALUE)
            winreg.DeleteValue(key, self.startup_reg_key_name)
            winreg.CloseKey(key)
        except FileNotFoundError:
            pass
        QMessageBox.information(self, "成功", "已成功取消开机自动启动。")
    def add_to_right_click_menu(self):
        menu_name = "共享此文件夹"
        command_str_on_folder = ''
        command_str_in_folder = ''
        icon_path = ''
        if IS_FROZEN:
            exe_path = sys.executable
            icon_path = f'"{exe_path}",0'
            command_str_on_folder = f'"{exe_path}" "%1"'
            command_str_in_folder = f'"{exe_path}" "%V"'
        else:
            python_exe_path = sys.executable
            script_path = os.path.abspath(__file__)
            icon_path = f'"{python_exe_path}",0'
            command_str_on_folder = f'"{python_exe_path}" "{script_path}" "%1"'
            command_str_in_folder = f'"{python_exe_path}" "{script_path}" "%V"'
        key1 = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}")
        winreg.SetValueEx(key1, "", 0, winreg.REG_SZ, menu_name)
        winreg.SetValueEx(key1, "Icon", 0, winreg.REG_SZ, icon_path)
        winreg.CloseKey(key1)
        key2 = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}\\command")
        winreg.SetValueEx(key2, "", 0, winreg.REG_SZ, command_str_on_folder)
        winreg.CloseKey(key2)
        key3 = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\Background\\shell\\{self.reg_key_name}")
        winreg.SetValueEx(key3, "", 0, winreg.REG_SZ, menu_name)
        winreg.SetValueEx(key3, "Icon", 0, winreg.REG_SZ, icon_path)
        winreg.CloseKey(key3)
        key4 = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\Background\\shell\\{self.reg_key_name}\\command")
        winreg.SetValueEx(key4, "", 0, winreg.REG_SZ, command_str_in_folder)
        winreg.CloseKey(key4)
        QMessageBox.information(self, "成功", f"已成功添加右键菜单!\n\n右键任意文件夹即可看到\"{menu_name}\"选项。")
    def remove_from_right_click_menu(self):
        try:
            winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}\\command")
            winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}")
        except FileNotFoundError:
            pass
        try:
            winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\Background\\shell\\{self.reg_key_name}\\command")
            winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\Background\\shell\\{self.reg_key_name}")
        except FileNotFoundError:
            pass
        QMessageBox.information(self, "成功", "已成功移除右键菜单。")

def parse_arguments():
    args = sys.argv[1:]
    initial_port = None
    initial_path = None
    auto_start = False
    i = 0
    while i < len(args):
        arg = args[i]
        if arg.lower() in ['-p', '--port'] and i + 1 < len(args):
            try:
                initial_port = int(args[i + 1])
                args.pop(i)
                args.pop(i)
            except ValueError:
                print("警告:无效的端口号参数 {0},将使用默认端口。".format(args[i + 1]))
                args.pop(i)
                args.pop(i)
        else:
            i += 1
    if args:
        path_candidate = args[0]
        path_candidate = path_candidate.strip().strip('\'"')
        if path_candidate:
            path_obj = Path(path_candidate)
            if path_obj.exists():
                initial_path = str(path_obj.resolve())
                auto_start = True
                print(f"检测到命令行路径参数:{initial_path}")
            else:
                print(f"警告:路径 {path_candidate} 不存在,将使用默认路径。")
    return initial_path, initial_port, auto_start

def main():
    initial_path, initial_port, auto_start = parse_arguments()
    if initial_path:
        print("接收到共享路径: {0}".format(initial_path))
    if initial_port:
        print("接收到端口: {0}".format(initial_port))
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)
    font_name = "Microsoft YaHei" if platform.system() == "Windows" else "Arial"
    app.setFont(QFont(font_name, 10))
    window = FileShareGUI(initial_path, initial_port, auto_start)
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

免费评分

参与人数 101吾爱币 +92 热心值 +90 收起 理由
xiandazhan + 1 好家伙!!接力赛!!很棒
hongye0 + 1 + 1 谢谢@Thanks!
huanling8866 + 2 + 1 很认同这位朋友的修改和完善。
zhuyu12000 + 1 用心讨论,共获提升!
自己泡面 + 1 + 1 谢谢@Thanks!
zulalala + 1 + 1 希望能提供成品下载直接使用
lazychen + 1 + 1 我很赞同!
兔斯基的眼泪 + 1 + 1 用心讨论,共获提升!
Mike0315 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
comma2019 + 1 + 1 能抽时间提供一个压缩包绿色版本的吗;单第一次启动慢;
shenlrq + 1 + 1 谢谢@Thanks!
jay19118isme + 1 真好,服务端UI好像是我的哎!
键盘侠 + 1 + 1 谢谢@Thanks!
zoshl + 1 + 1 二维码
alxir + 1 + 1 我很赞同!
keate23 + 1 + 1 热心回复!
半城长眠 + 1 我很赞同!
onlyonemoon + 1 + 1 谢谢@Thanks!
弑神者91511 + 1 + 1 谢谢@Thanks!
huanzhiyiran + 1 + 1 我很赞同!
o13674976542 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
我在这里哦 + 1 + 1 我很赞同!
rsxming + 1 + 1 能不能支持文件夹下载
gztf + 1 + 1 谢谢@Thanks!
chsl918 + 1 + 1 谢谢@Thanks!
eden_xudy + 1 + 1 鼓励转贴优秀软件安全工具和文档!
xyling1007 + 1 + 1 用心讨论,共获提升!
flfq + 1 + 1 谢谢@Thanks!
dn336 + 1 + 1 我很赞同!
唐小样儿 + 1 + 1 我很赞同!
qingqingzijin + 2 + 1 我很赞同!
aleewuu + 1 + 1 下了 多谢
love933 + 1 + 1 热心回复!
hwaltz + 1 + 1 用心讨论,共获提升!
Rick_Nikita + 1 + 1 谢谢@Thanks!
72、 + 1 + 1 谢谢@Thanks!
徐州鑫匠 + 1 + 1 谢谢@Thanks!
bjqjlzx + 1 + 1 谢谢@Thanks!
00000041 + 1 + 1 外网访问成功,可以
theStyx + 1 我很赞同!
zj7344426 + 1 + 1 谢谢@Thanks!
junshuiyujianni + 1 + 1 谢谢@Thanks!
J12138 + 1 + 1 热心回复!
银枫月影 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
ioitt + 1 + 1 用心讨论,共获提升!
clide2000 + 1 + 1 我很赞同!
ngfc + 1 我很赞同!
a21232 + 1 + 1 我很赞同!
yexuana + 1 + 1 我很赞同!
maoanran + 1 用心讨论,共获提升!
翅膀. + 1 热心回复!
Lll080821 + 1 我很赞同!
yybaoyymmm + 1 + 1 谢谢@Thanks!
geqian3327 + 1 + 1 好用的很啊
Liang.XH + 1 谢谢@Thanks!
sunb1417 + 1 谢谢@Thanks!
wanhyl + 1 用心讨论,共获提升!
ailiagt + 1 谢谢@Thanks!
wangqida1990 + 1 我很赞同!
抱薪风雪雾 + 1 + 1 谢谢@Thanks!
N2020 + 1 + 1 谢谢@Thanks!
星光伴我心 + 1 + 1 我很赞同!
TCDT + 1 + 1 鼓励转贴优秀软件安全工具和文档!
笨笨家的唯一 + 1 + 1 我很赞同!
爱情街 + 1 + 1 谢谢@Thanks!
hacbu84 + 1 + 1 我很赞同!
midkr + 1 + 1 谢谢@Thanks!
RobinMaas + 2 + 1 谢谢@Thanks!
vincebear + 1 我很赞同!
maliee360 + 1 你是不是觉得你很牛逼?哼,我也这么觉得
olos + 1 + 1 用心讨论,共获提升!
wonder6688 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
所谓口口口 + 1 + 1 希望能在外网地址下面添加内网二维码和外网二维码功能,这会更加便利
zhuroucheng + 1 + 1 html页面的布局可以改一下,如果分享的文件名过长自动换行了,后面的操作按 ...
ak49007 + 1 + 1 热心且牛逼
wuaimhw + 1 + 1 谢谢@Thanks!
cjh127 + 1 + 1 谢谢@Thanks!
gamezha + 1 + 1 谢谢@Thanks!
twl2018 + 1 + 1 谢谢@Thanks!
chonglou123 + 1 + 1 热心回复!
starry888 + 1 + 1 感谢大佬改进
爱已微笑 + 1 + 1 谢谢@Thanks!
Lsygood + 1 大佬太厉害了
tail88 + 1 谢谢@Thanks!
huwen945 + 1 + 1 谢谢@Thanks!
Zed丶小灰狼 + 1 + 1 热心回复!
抱歉、 + 1 用心讨论,共获提升!
示申の孑亥纟氏 + 1 + 1 要是多个二维码,手机扫一扫打开就完美了
神器 + 1 我很赞同!
pptx + 1 谢谢@Thanks!
0464tcx + 1 + 1 谢谢@Thanks!
niweiwei2284 + 1 + 1 我很赞同!
know1234 + 1 + 1 我很赞同!
liuog + 1 + 1 非常好!
damocles1985 + 1 热心回复!
lzy13 + 1 + 1 谢谢@Thanks!
花心乞丐 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
huanglulu009 + 1 + 1 希望能提供成品下载
青涩的柠檬 + 1 + 1 希望提供成品下载
大雨打湿衣 + 1 希望能提供成品下载

查看全部评分

jay19118isme 发表于 2025-12-10 16:06
本帖最后由 jay19118isme 于 2025-12-19 15:12 编辑
ygq170063 发表于 2025-12-4 17:56
写的非常好,然后我给你做了一些修改,你可以试试看,更美观 也更实用

成品打包:https://fjjy.lanzoub. ...

建议大家尊重别人的劳动成果。。。
注:本人发布5.x版本不会增加内网穿透与用户管理功能,因为不想软件太臃肿,所以只对必要的功能做修复与优化
感谢 楼主ygq170063 大佬的修改 我也在您v5.0的基础上进行了若干改动:
打包:(v5.5)
单文件版本(体积更小):https://wwbtm.lanzouu.com/b0187zdgah  密码:5xti
压缩包版本(启动更快):https://wwbtm.lanzouu.com/b0187zdgfc   密码:ho1e  解压密码:52pj


兼容Win7的版本
单文件版本(体积更小):https://jay19118.lanzouu.com/b018806b1c 密码:4pke
压缩包版本(启动更快):https://jay19118.lanzouu.com/b018806b3e 密码:bgun  解压密码:52pj

v5.5更新内容
1、添加批量上传、批量下载、批量删除功能
2、优化管理密码交互(每次访问期间只需输入一次管理密码)

v5.4更新内容
1、打开、下载改为流式传输,支持断点续传
2、添加新建文件夹、上传文件夹功能
3、UI界面优化、多网卡启动速度优化

v5.3更新内容
1、打开按钮逻辑优化,将原版的客户端发送指令在服务端打开文件,改为客户端打开文件
2、修复5.1、5.2版本出现的右键菜单找不到应用程序的问题

v5.2更新内容
1、手机竖屏显示优化,个别按钮优化
2、避免内存占用过大、程序卡死,上传文件改为流式传输,添加上传进度条显示

v5.1更新内容
1、添加二维码功能,多网卡会显示多个对应的二维码,点击选择并扫码即可,自动隐藏不需要的网络,再次点击取消选择。
    (虽然如ygq170063大佬所言,浏览器可以为网址生成二维码,但是很多像我一样的小白和懒人还是希望直接扫码打开)
2、添加简单的权限管理,共享时指定密码,客户端需要输入密码访问

      (总比没有强
3、暂时去掉内网穿透功能
      (本菜公司网测试内网穿透,会出安全事故
4、安全性漏洞修复:XSS 攻击


服务端:


客户端(桌面端):

客户端(手机端):


5.5源码
[Python] 纯文本查看 复制代码
import os
import sys
import io
import socket
import mimetypes
import threading
import time
import queue
import shutil
import re
import zipfile  # 新增:用于打包下载
from pathlib import Path
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import urllib.parse
import base64
import platform
import subprocess
from email.header import Header
import winreg
import segno
import uuid
import html
import json
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, \
    QPushButton, QTextEdit, QFileDialog, QMessageBox, QSystemTrayIcon, QMenu, QAction, QSplitter
from PyQt5.QtCore import Qt, QSettings, QTimer, pyqtSignal, QSize, QPoint
from PyQt5.QtGui import QIcon, QPixmap, QFont

IS_FROZEN = getattr(sys, 'frozen', False) or "__compiled__" in globals()
DEFAULT_PORT = 5995
DEFAULT_SHARE_PATH = str(Path.home() / "Downloads")
server_instance = None

ICON_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABGZJREFUWEe9V0mMFGUU/t5fVV203UxY3BhmRhlAvYwLonNyLgY7BoIHMTFcEBxITNR4MS5w5aDRuEQ9iHLzhAcvkhghLkziTNAElSjMkNBmgLA1E0en9/qfeX9V9fRS1QtEXtLp1F/1/ve97fvfTwBw5sCGVx1Hv81gELQs1URWSBM08wxr/djw7pOXGj64wQf689NHMymn+jUUWxAAxE1bKjADslzR+tjwrl/HbtBmgzqdPbjxokX6DibfcyLy/yFwAhEEAgIEjysVMLPAkk+b4UaBa9wLCBW1WMsefIit1G0gJ+WbVLKFv61RZB/Q4rP/3hhffNXoVbBDg+GaMz5qruZRXbgMyn7+IC8f24++dU/WqUX5EeVrvYlIc7HZ+ufMN8j9+CYo91WGk8Ob4K64VyLru30TpHJtGgtnj4D01Hg3afx/ILF02OSeLgBEhUXU4tY74Q3TpUIANynuLbgkAlN7OLacYxwxtVLXsp38jX9vAOxmsOm9tiJGzW/lw6D0WnCiD1S+BuSOQ+XPd1KPfk9dAgiNY2gb1O2j8PJXoAuXYKf6Qem74J3YByrmDIsSdXZmEU0PALwVG2CvH0dh4iW4lAdZwtwEXvUE1EAGXJoDewXQqfdAPpt1IV0AMPlmDb7vRVQuTMCZ+xnKTghnm5SI17p/KzQT1NI1IPKA0x/51B3Q+nXXQBh6rRSc0U9QPPos3PTy1vajqgEJ2MDGD1E++S7s4nko9gzQjgAIls+CTaKl6JaNQA1sBqWGUJ14DnZCzoxmkSj5vMCDT0H1Z6AXZsEXDkPN/dYJwDhDTuImEUDV5ACckddQmf0WlewhLLEA5bgxBCRHiUTBQ6lUhr3mGVirM9B/vAOVn40B0aYGmDXokfdRmv4C9tVjsBJiWIqrA2lJDZjjTkHfOgZ199Pg4y+DqNVJc8BH8oB433cPaHAb9C9vwFmytEMuo7Ii8xWAkb3Q2UOw/p2OAN8GAK/eDK+ch3XxCMi2u2iplhyaWHmDW6DLediXjwYRrP+uLYAt8MoLsK5810U7RZSltCgz9NBWeMV5OFe/7xHAnZvg2X2wzn3ZI7stgmGtoYe3w/s7i8TcT72lwIMDa/QDeFMvwOqJXn0A0kUeFOzRj1H4YQeSyaj2jUmBKEu967U7gVtWgX/fbwZSUnVcYQimiTvkUeiZAU8zrAf2Qs//Bcx8BstN9hAB+VQ8IECN7AMnVkLPHDB3BjOINnRi02BiACjY65+HLuZQmXwdbjoddFFzC5sIRBORfxnQYFgouevg3v+KiYJQDRnPQyT+SuMgzyiceAtq/hRc1wYp4ZAoCVMgAY8bzOS+oBmsq9C64oc9jH54iZHZzkzbfvhBCoosKEsBQkCx80Y9gOCWUctsy9kQXI9MhbUhRONIS55iAlBfhFJg9VEwALo919shCmwHN69aAUkx+RNROJZ3MRz3zofxGjUAkzslddchoVK99705Id1ChcOPB6dGu4OuU9J798AvMcJ/PwkXZ+WN+AAAAAAASUVORK5CYII="

SESSIONS_STORE = {}
mimetypes.init()


class FileShareHandler(BaseHTTPRequestHandler):
    _open_cache = {}
    _open_lock = threading.Lock()

    def __init__(self, *args, **kwargs):
        self.shared_path = kwargs.pop('shared_path', None)
        self.current_path = kwargs.pop('current_path', None) or self.shared_path
        self.access_logger = kwargs.pop('access_logger', None)
        self.password = kwargs.pop('password', None)
        self.admin_password = kwargs.pop('admin_password', None)
        super().__init__(*args, **kwargs)

    def log_message(self, format, *args):
        pass

    # --- 权限验证 ---
    def get_token_from_cookie(self):
        cookie_header = self.headers.get('Cookie')
        if cookie_header:
            cookies = dict(x.strip().split('=', 1) for x in cookie_header.split(';') if '=' in x)
            return cookies.get('auth_token')
        return None

    def check_auth(self):
        if not self.password:
            return True
        token = self.get_token_from_cookie()
        if token and token in SESSIONS_STORE:
            return True
        return False

    def check_admin_auth(self):
        if not self.admin_password:
            return True
        token = self.get_token_from_cookie()
        if token and token in SESSIONS_STORE:
            if SESSIONS_STORE[token].get('is_admin', False):
                return True
        return False

    # --- 页面响应 ---
    def send_login_page(self, error_msg=""):
        html_content = '''
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>验证 - 文件共享</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <style>
                body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f0f2f5; }
                .login-box { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 100%%; max-width: 320px; text-align: center; }
                input { width: 100%%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
                button { width: 100%%; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
                button:hover { background: #45a049; }
                .error { color: red; font-size: 14px; margin-bottom: 10px; }
            </style>
        </head>
        <body>
            <div class="login-box">
                <h2>&#128274; 访问验证</h2>
                <div class="error">%s</div>
                <form action="/login" method="POST">
                    <input type="password" name="password" placeholder="请输入访问密码" required autofocus>
                    <button type="submit">进入</button>
                </form>
            </div>
        </body>
        </html>
        ''' % error_msg
        self.send_response(200)
        self.send_header('Content-Type', 'text/html; charset=utf-8')
        self.end_headers()
        self.wfile.write(html_content.encode('utf-8'))

    def do_GET(self):
        if self.access_logger:
            self.access_logger(self.client_address[0], self.requestline)

        try:
            parsed_path = urllib.parse.urlparse(self.path)
            query_params = urllib.parse.parse_qs(parsed_path.query)

            if parsed_path.path == '/login':
                self.send_login_page()
                return

            if not self.check_auth():
                self.send_response(302)
                self.send_header('Location', '/login')
                self.end_headers()
                return

            # 新增:检查当前是否拥有管理权限 (用于前端判断是否弹窗)
            if parsed_path.path == '/is_admin':
                is_admin = self.check_admin_auth()
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(json.dumps({'is_admin': is_admin}).encode('utf-8'))
                return

            if parsed_path.path == '/download':
                filename = query_params.get('file', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if filename:
                    self.download_file(filename, current_path)
                    return
            elif parsed_path.path == '/check_exists':
                filename = query_params.get('file', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                self.check_file_exists(filename, current_path)
                return
            elif parsed_path.path == '/open':
                filename = query_params.get('file', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if filename:
                    self.open_file(filename, current_path)
                    return
            elif parsed_path.path == '/delete':
                if not self.check_admin_auth():
                    self.send_error(401, "Admin Required")
                    return
                filename = query_params.get('file', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if filename:
                    self.delete_file(filename, current_path)
                    return
            elif parsed_path.path == '/dir':
                dir_path = query_params.get('path', [self.shared_path])[0]
                self.current_path = urllib.parse.unquote(dir_path)
                self.send_main_page()
                return
            elif parsed_path.path == '/' or parsed_path.path == '':
                self.current_path = self.shared_path
                self.send_main_page()
                return
            else:
                self.send_error(404, "Not Found")

        except Exception as e:
            self.send_error(500, "Server error: {0}".format(str(e)))

    def do_POST(self):
        if self.access_logger:
            self.access_logger(self.client_address[0], self.requestline)

        try:
            parsed_path = urllib.parse.urlparse(self.path)

            if parsed_path.path == '/login':
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length).decode('utf-8')
                post_params = urllib.parse.parse_qs(body)
                input_password = post_params.get('password', [''])[0]

                if input_password == self.password:
                    token = str(uuid.uuid4())
                    SESSIONS_STORE[token] = {'is_admin': False, 'create_time': time.time()}
                    self.send_response(302)
                    self.send_header('Set-Cookie', f'auth_token={token}; Path=/; Max-Age=86400')
                    self.send_header('Location', '/')
                    self.end_headers()
                else:
                    self.send_login_page("密码错误,请重试。")
                return

            # --- 打包下载接口 (无需Admin权限,但需Basic权限) ---
            if parsed_path.path == '/zip':
                if not self.check_auth():
                    print("[ERROR 403] 未登录尝试下载")
                    self.send_error(403, "Forbidden")
                    return
                # 读取表单数据
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length).decode('utf-8')
                post_params = urllib.parse.parse_qs(body)

                # 获取路径和文件列表
                current_path = post_params.get('path', [self.shared_path])[0]
                files = post_params.get('files[]', [])  # 可能是多个

                # 如果没有 files[] 参数,检查是否有单个 file 参数 (下载单文件夹情况)
                if not files:
                    single_file = post_params.get('file', [])
                    if single_file:
                        files = single_file

                self.handle_zip_download(current_path, files)
                return

            if parsed_path.path == '/verify_admin':
                if not self.check_auth():
                    self.send_error(403, "Forbidden")
                    return
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length).decode('utf-8')
                try:
                    data = json.loads(body)
                    input_admin_pwd = data.get('password', '')
                except:
                    input_admin_pwd = ""

                if input_admin_pwd == self.admin_password:
                    token = self.get_token_from_cookie()
                    if token and token in SESSIONS_STORE:
                        SESSIONS_STORE[token]['is_admin'] = True
                        self.send_response(200)
                        self.send_header('Content-Type', 'application/json')
                        self.end_headers()
                        self.wfile.write(b'{"success": true}')
                    else:
                        self.send_error(403, "Session Invalid")
                else:
                    self.send_response(200)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(b'{"success": false, "message": "Wrong password"}')
                return

            if not self.check_auth():
                self.send_error(403, "Forbidden")
                return

            if parsed_path.path in ['/upload', '/rename', '/delete', '/mkdir']:
                if not self.check_admin_auth():
                    self.send_response(401)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(b'{"error": "admin_required"}')
                    return

            query_params = urllib.parse.parse_qs(parsed_path.query)

            if parsed_path.path == '/upload':
                current_path = query_params.get('path', [self.shared_path])[0]
                offset = query_params.get('offset', [None])[0]
                if offset is not None:
                    filename = query_params.get('name', [''])[0]
                    self.handle_chunk_upload(current_path, filename, int(offset))
                else:
                    self.handle_upload(current_path)
                return
            elif parsed_path.path == '/rename':
                old_name = query_params.get('old', [''])[0]
                new_name = query_params.get('new', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if old_name and new_name:
                    self.rename_file(old_name, new_name, current_path)
                else:
                    self.send_error(400, "Bad Request")
                return
            elif parsed_path.path == '/delete':
                filename = query_params.get('file', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if filename:
                    self.delete_file(filename, current_path)
                return
            elif parsed_path.path == '/mkdir':
                dirname = query_params.get('name', [''])[0]
                current_path = query_params.get('path', [self.shared_path])[0]
                if dirname:
                    self.create_directory(dirname, current_path)
                else:
                    self.send_error(400, "Bad Request")
                return
            else:
                self.send_error(404, "Not Found")

        except Exception as e:
            self.send_error(500, "Error: {0}".format(str(e)))

    def send_main_page(self):
        try:
            current_path = Path(self.current_path).resolve()
            shared_path = Path(self.shared_path).resolve()
            if shared_path not in current_path.parents and current_path != shared_path:
                current_path = shared_path
            breadcrumb_html = self.get_breadcrumb(current_path)

            files = []
            dirs = []

            try:
                for item in current_path.iterdir():
                    try:
                        item_info = {
                            'name': item.name,
                            'modified': time.strftime('%Y-%m-%d %H:%M', time.localtime(item.stat().st_mtime)),
                            'path': str(item),
                            'encoded_name': urllib.parse.quote(item.name),
                            'safe_display_name': html.escape(item.name)
                        }

                        if item.is_file():
                            item_info['size'] = self.format_size(item.stat().st_size)
                            files.append(item_info)
                        else:
                            item_info['size'] = '-'
                            dirs.append(item_info)
                    except OSError:
                        pass
            except Exception as e:
                pass

            dirs.sort(key=lambda x: x['name'].lower())
            files.sort(key=lambda x: x['name'].lower())

            current_path_encoded = urllib.parse.quote(str(current_path))
            file_list_html = ''

            if current_path != shared_path:
                parent_path = str(current_path.parent)
                file_list_html += '''
                <tr>
                    <td></td>
                    <td><span class="dir-link">&#128193; .. (返回上一级)</span></td>
                    <td class="size-cell">-</td>
                    <td class="hide-mobile">-</td>
                    <td></td>
                </tr>
                '''.format(urllib.parse.quote(parent_path))

            for d in dirs:
                file_list_html += '''
                <tr>
                    <td class="checkbox-cell"><input type="checkbox" class="file-checkbox" value="{1}" data-type="dir"></td>
                    <td><span class="dir-link">&#128193; {1}</span></td>
                    <td class="size-cell">-</td>
                    <td class="hide-mobile">{2}</td>
                    <td class="action-buttons">
                        <button class="action-btn download-btn">下载</button>
                        <button class="action-btn rename-btn">重命名</button>
                        <button class="action-btn delete-btn">删除</button>
                    </td>
                </tr>
                '''.format(urllib.parse.quote(d['path']), d['name'], d['modified'], d['encoded_name'],
                           current_path_encoded)

            for f in files:
                file_list_html += '''
                <tr>
                    <td class="checkbox-cell"><input type="checkbox" class="file-checkbox" value="{0}" data-type="file"></td>
                    <td class="file-name">&#128196; {0}</td>
                    <td class="size-cell">{1}</td>
                    <td class="hide-mobile">{2}</td>
                    <td class="action-buttons">
                        <button class="action-btn open-btn">打开</button>
                        <button class="action-btn download-btn">下载</button>
                        <button class="action-btn rename-btn">重命名</button>
                        <button class="action-btn delete-btn">删除</button>
                    </td>
                </tr>
                '''.format(f['safe_display_name'], f['size'], f['modified'], f['encoded_name'], current_path_encoded)

            html_str = '''
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="UTF-8">
                <title>文件共享</title>
                <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
                <style>
                    * {{ box-sizing: border-box; }}
                    body {{ font-family: "Microsoft YaHei", Arial, sans-serif; margin: 0; padding: 10px; background-color: #f9f9f9; color: #333; }}
                    .container {{ max-width: 1000px; margin: 0 auto; background: white; padding: 15px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }}

                    .header {{ display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; margin-bottom: 15px; flex-wrap: wrap; gap: 10px; }}
                    .header h1 {{ margin: 0; font-size: 1.5em; }}
                    .toolbar {{ display: flex; gap: 8px; flex-wrap: wrap; }}

                    .btn {{ padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; color: white; }}
                    .upload-btn {{ background: #2196F3; }}
                    .mkdir-btn {{ background: #9C27B0; }}
                    .batch-btn {{ background: #4CAF50; }}
                    /* 新增:批量删除按钮样式 */
                    .delete-batch-btn {{ background: #f44336; }}
                    .delete-batch-btn:hover {{ background: #d32f2f; }}
                    /* 同时隐藏文件和文件夹的输入控件 */
                    #fileInput, #folderInput {{ display: none; }}

                    .breadcrumb {{ margin: 10px 0; padding: 10px; background: #f0f0f0; border-radius: 4px; word-break: break-all; font-size: 14px; border: 1px solid #ddd; }}
                    .breadcrumb a {{ color: #2196F3; text-decoration: none; }}

                    table {{ width: 100%; border-collapse: collapse; margin: 10px 0; table-layout: fixed; }}
                    th, td {{ padding: 10px 8px; vertical-align: middle; border-bottom: 1px solid #eee; word-break: break-all; }}

                    /* 表头全局样式 */
                    th {{ background-color: #546E7A; color: white; font-size: 14px; font-weight: normal; text-align: left; }}
                    tr:hover {{ background-color: #f5f5f5; }}

                    .checkbox-cell {{ padding: 0; }}
                    input[type="checkbox"] {{ transform: scale(1.2); cursor: pointer; }}

                    .dir-link {{ color: #2196F3; cursor: pointer; font-weight: bold; text-decoration: none; }}
                    .file-name {{ color: #333; }}

                    td.size-cell {{ font-size: 13px; color: #666; white-space: nowrap; }}

                    /* --- 桌面端样式 --- */

                    th:nth-child(1), td:nth-child(1) {{ 
                        width: 40px; 
                        text-align: center; 
                    }} 

                    th:nth-child(2), td:nth-child(2) {{ 
                        width: auto; 
                        text-align: left;
                        white-space: normal; 
                        word-break: break-all;
                    }} 

                    th:nth-child(3), td:nth-child(3) {{ 
                        width: 100px; 
                        text-align: left; 
                    }} 

                    th:nth-child(4), td:nth-child(4) {{ 
                        width: 160px; 
                        text-align: left; 
                    }} 

                    th:nth-child(5), td:nth-child(5) {{ 
                        width: 220px; 
                        text-align: left; 
                    }} 

                    .action-buttons {{ display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-start; }}

                    .action-btn {{ padding: 4px 8px; border: none; border-radius: 3px; cursor: pointer; font-size: 12px; color: white; }}
                    .open-btn {{ background: #ff9800; }}
                    .download-btn {{ background: #4CAF50; }}
                    .rename-btn {{ background: #2196F3; }}
                    .delete-btn {{ background: #f44336; }}

                    /* --- 手机端样式 --- */
                    @media (max-width: 768px) {{
                        body {{ padding: 5px; }}
                        .container {{ padding: 8px; }}
                        .header h1 {{ font-size: 1.2em; }}
                        .toolbar {{ width: 100%; }}
                        .btn {{ flex: 1; padding: 8px 0; font-size: 13px; }}
                        .hide-mobile {{ display: none; }} 

                        th:nth-child(1), td:nth-child(1) {{ 
                            width: 30px !important; 
                            padding: 8px 2px !important;
                            text-align: center;
                        }}

                        th:nth-child(2), td:nth-child(2) {{ 
                            width: auto !important;
                            white-space: normal !important; 
                            word-break: break-all !important; 
                            padding: 8px 4px !important;
                            text-align: left !important;
                        }}

                        th:nth-child(3), td:nth-child(3) {{ 
                            width: 75px !important;     
                            font-size: 12px; 
                            text-align: center !important; 
                            padding: 8px 2px !important;
                        }}

                        th:nth-child(3) {{ color: white !important; background-color: #546E7A; }}
                        td:nth-child(3) {{ color: #888; }}

                        th:last-child, td:last-child {{ 
                            width: 60px !important; 
                            padding: 2px !important;
                            text-align: center !important;
                        }}

                        .action-buttons {{ flex-direction: column; gap: 2px; width: 100%; justify-content: center; }}
                        .action-btn {{ width: 100%; margin: 1px 0; padding: 5px 0; font-size: 11px; }}
                    }}

                    .status {{ padding: 10px; margin: 10px 0; border-radius: 5px; display: none; position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 1000; min-width: 200px; text-align: center; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }}
                    .success {{ background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }}
                    .error {{ background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }}
                    .progress-container {{ display: none; width: 100%; background-color: #e9ecef; border-radius: 4px; margin: 10px 0; overflow: hidden; height: 24px; }}
                    .progress-bar {{ width: 0%; height: 100%; background-color: #4CAF50; color: white; text-align: center; line-height: 24px; font-size: 13px; white-space: nowrap; }}
                    .modal-overlay {{ display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 2000; justify-content: center; align-items: center; }}
                    .modal-box {{ background: white; padding: 20px; border-radius: 8px; width: 300px; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.2); }}
                    .modal-input {{ width: 100%; padding: 8px; margin: 10px 0; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }}
                    .modal-btns {{ display: flex; justify-content: space-between; margin-top: 10px; }}
                    .modal-btn {{ width: 48%; padding: 8px; border: none; border-radius: 4px; cursor: pointer; }}
                    .modal-confirm {{ background: #2196F3; color: white; }}
                    .modal-cancel {{ background: #ddd; color: #333; }}
                </style>
            </head>
            <body>
                <div class="container">
                    <div class="header">
                        <h1>&#128193; 文件共享</h1>
                        <div class="toolbar">
                            <button class="btn batch-btn">批量下载</button>
                            <button class="btn upload-btn">批量上传</button>
                            <button class="btn delete-batch-btn">批量删除</button>
                            <button class="btn mkdir-btn">新建文件夹</button>
                            <button class="btn" style="background-color: #ff9800;">上传文件夹</button>

                            <input type="file" id="fileInput" multiple>
                            <input type="file" id="folderInput" webkitdirectory>
                        </div>
                    </div>
                    <div class="breadcrumb">{0}</div>
                    <div id="status" class="status"></div>

                    <div id="progress-container" class="progress-container">
                        <div id="progress-bar" class="progress-bar"></div>
                    </div>

                    <table>
                        <thead>
                            <tr>
                                <th><input type="checkbox"></th>
                                <th>名称</th>
                                <th class="size-cell">大小</th>
                                <th class="hide-mobile">修改时间</th>
                                <th>操作</th>
                            </tr>
                        </thead>
                        <tbody>{1}</tbody>
                    </table>
                </div>

                <div id="admin-modal" class="modal-overlay">
                    <div class="modal-box">
                        <h3>&#128274; 需要管理权限</h3>
                        <p style="font-size:14px; color:#666; margin-bottom:10px;">执行此操作需要验证管理密码</p>
                        <input type="password" id="admin-pwd-input" class="modal-input" placeholder="输入管理密码">
                        <div id="admin-modal-msg" style="color:red; font-size:12px; height:16px;"></div>
                        <div class="modal-btns">
                            <button class="modal-btn modal-cancel">取消</button>
                            <button class="modal-btn modal-confirm">确认</button>
                        </div>
                    </div>
                </div>

                <script>
                    let pendingAction = null; 

                    // 全局变量存储权限状态
                    let g_isAdmin = false;

                    // 页面加载时立即检查权限,这样后续点击就是同步的
                    document.addEventListener('DOMContentLoaded', () => {{
                        fetch('/is_admin')
                            .then(r => r.json())
                            .then(data => {{ g_isAdmin = data.is_admin; }})
                            .catch(e => console.log(e));
                    }});

                    function showStatus(m,t){{
                        const s=document.getElementById('status');
                        s.textContent=m; s.className='status '+t; s.style.display='block';
                        setTimeout(()=>s.style.display='none',3000);
                    }}

                    function openAdminModal(actionCallback) {{
                        pendingAction = actionCallback;
                        document.getElementById('admin-modal').style.display = 'flex';
                        document.getElementById('admin-pwd-input').value = '';
                        document.getElementById('admin-modal-msg').textContent = '';
                        document.getElementById('admin-pwd-input').focus();
                    }}

                    function closeAdminModal() {{
                        document.getElementById('admin-modal').style.display = 'none';
                        pendingAction = null;
                    }}

                    function submitAdminAuth() {{
                        const pwd = document.getElementById('admin-pwd-input').value;
                        fetch('/verify_admin', {{
                            method: 'POST',
                            headers: {{'Content-Type': 'application/json'}},
                            body: JSON.stringify({{password: pwd}})
                        }})
                        .then(r => r.json())
                        .then(data => {{
                            if(data.success) {{
                                document.getElementById('admin-modal').style.display = 'none';

                                // 更新全局状态
                                g_isAdmin = true;

                                if(pendingAction) pendingAction(); 
                            }} else {{
                                document.getElementById('admin-modal-msg').textContent = '密码错误';
                            }}
                        }})
                        .catch(e => {{
                            document.getElementById('admin-modal-msg').textContent = '验证出错';
                        }});
                    }}

                    document.getElementById('admin-pwd-input').addEventListener("keyup", function(event) {{
                        if (event.key === "Enter") submitAdminAuth();
                    }});

                    // 使用预加载的状态进行同步判断
                    function checkAdminAndUpload(inputId) {{
                        if (g_isAdmin) {{
                            // 有权限:同步调用,手机端支持
                            document.getElementById(inputId).click();
                        }} else {{
                            // 无权限:弹出验证框
                            openAdminModal(() => {{
                                // 验证成功后的回调
                                // 注意:此处因为是在 fetch 回调中,无法自动触发 click。
                                // 所以只能提示用户再次点击。
                                showStatus('验证成功,请再次点击上传按钮', 'success');
                            }});
                        }}
                    }}

                    function handleRequest(url, method, body, successMsg, failMsg) {{
                        const opts = {{ method: method }};
                        if(body) opts.body = body;

                        fetch(url, opts).then(r => {{
                            if(r.status === 401) {{
                                openAdminModal(() => handleRequest(url, method, body, successMsg, failMsg));
                                return; 
                            }}
                            if(r.ok) {{
                                showStatus(successMsg, 'success');
                                setTimeout(() => location.reload(), 500);
                            }} else {{
                                showStatus(failMsg, 'error');
                            }}
                        }}).catch(() => showStatus('网络错误', 'error'));
                    }}

                    function toggleAll(source) {{
                        const checkboxes = document.querySelectorAll('.file-checkbox');
                        for(let i=0; i<checkboxes.length; i++) {{
                            checkboxes[i].checked = source.checked;
                        }}
                    }}

                    function downloadBatch(files) {{
                        if (!files || files.length === 0) {{
                            alert("请至少选择一个文件或文件夹!");
                            return;
                        }}

                        const form = document.createElement('form');
                        form.method = 'POST';
                        form.action = '/zip?v=' + new Date().getTime();
                        form.style.display = 'none';

                        const pathInput = document.createElement('input');
                        pathInput.name = 'path';

                        pathInput.value = '{2}'; 
                        form.appendChild(pathInput);

                        files.forEach(f => {{
                            const fileInput = document.createElement('input');
                            fileInput.name = 'files[]';
                            fileInput.value = f;
                            form.appendChild(fileInput);
                        }});

                        document.body.appendChild(form);
                        form.submit();
                        document.body.removeChild(form);
                    }}

                    function downloadSelected() {{
                        const checkboxes = document.querySelectorAll('.file-checkbox:checked');
                        const files = [];
                        checkboxes.forEach((cb) => {{
                            files.push(cb.value);
                        }});
                        downloadBatch(files);
                    }}

                    async function deleteSelected() {{
                        const checkboxes = document.querySelectorAll('.file-checkbox:checked');
                        if (checkboxes.length === 0) {{
                            alert("请至少选择一个项目!");
                            return;
                        }}

                        // 1. 权限检查(参考上传逻辑)
                        // 如果当前不是管理员,先弹窗验证,验证成功后回调自己
                        if (!g_isAdmin) {{
                            openAdminModal(() => deleteSelected());
                            return;
                        }}

                        // 2. 确认提示
                        const confirmMsg = `确定要删除选中的 ${{checkboxes.length}} 个项目吗?\n注意:删除后无法恢复!`;
                        if (!confirm(confirmMsg)) return;

                        // 3. 准备进度条
                        const pBar = document.getElementById('progress-bar');
                        const pContainer = document.getElementById('progress-container');
                        pContainer.style.display = 'block';

                        // 获取当前路径 (Python formatted path)
                        const path = '{2}'; 
                        let successCount = 0;
                        const total = checkboxes.length;

                        // 4. 循环删除
                        for (let i = 0; i < total; i++) {{
                            const name = checkboxes[i].value;
                            const encodedName = encodeURIComponent(name);

                            // 更新进度条文字
                            pBar.textContent = `正在删除 [${{i + 1}}/${{total}}]: ${{name}}`;
                            pBar.style.width = Math.round(((i + 1) / total) * 100) + '%';
                            pBar.style.backgroundColor = '#f44336'; // 红色进度条表示删除

                            try {{
                                // 调用删除接口
                                const res = await fetch(`/delete?file=${{encodedName}}&path=${{path}}`, {{ method: 'POST' }});

                                if (res.ok) {{
                                    successCount++;
                                }} else {{
                                    console.error(`Failed to delete ${{name}}: ${{res.status}}`);
                                }}
                            }} catch (e) {{
                                console.error(e);
                            }}
                        }}

                        // 5. 完成后刷新
                        pBar.style.backgroundColor = '#4CAF50'; // 变绿表示完成
                        pBar.textContent = `操作完成,成功删除 ${{successCount}}/${{total}} 个`;

                        setTimeout(() => location.reload(), 1000);
                    }}

                    async function handleFileSelect(e) {{
                        const files = Array.from(e.target.files);
                        if (!files.length) return;

                        e.target.value = '';

                        const path = '{2}'; 
                        const totalFiles = files.length;

                        for (let i = 0; i < totalFiles; i++) {{
                            const file = files[i];

                            // 获取相对路径 (文件夹上传时) 或 文件名 (普通上传时)
                            const relativePath = file.webkitRelativePath || file.name;

                            // 顺序上传
                            const success = await uploadFile(file, path, i + 1, totalFiles, relativePath);

                            if (!success) {{
                                console.log(`Skipped or failed: ${{relativePath}}`);
                            }}
                        }}

                        setTimeout(() => location.reload(), 500);
                    }}

                    async function uploadFile(file, path, index, totalCount, uploadName) {{
                        const pBar = document.getElementById('progress-bar');
                        const pContainer = document.getElementById('progress-container');
                        const chunkSize = 2 * 1024 * 1024; 

                        pContainer.style.display = 'block';
                        const prefix = totalCount > 1 ? `[${{index}}/${{totalCount}}] ` : '';

                        pBar.textContent = `${{prefix}}准备上传: ${{uploadName}}`;
                        pBar.style.width = '0%';
                        pBar.style.backgroundColor = '#4CAF50';

                        let uploadedSize = 0;
                        const total = file.size;
                        const encodedName = encodeURIComponent(uploadName);

                        // 1. 检查是否存在
                        try {{
                            const res = await fetch(`/check_exists?file=${{encodedName}}&path=${{path}}`);
                            if (res.ok) {{
                                const data = await res.json();
                                uploadedSize = data.size;
                                if (uploadedSize >= total) {{
                                    console.log(`File ${{uploadName}} already exists, skipping.`);
                                    return true; 
                                }}
                            }}
                        }} catch (e) {{
                            console.log('Check exists failed');
                        }}

                        let offset = uploadedSize;

                        while (offset < total) {{
                            const end = Math.min(offset + chunkSize, total);
                            const chunk = file.slice(offset, end);
                            const percent = Math.round((end / total) * 100);

                            pBar.style.width = percent + '%';
                            pBar.textContent = `${{prefix}}上传中 ${{percent}}% (${{uploadName}})`;

                            try {{
                                const res = await fetch(`/upload?path=${{path}}&name=${{encodedName}}&offset=${{offset}}`, {{
                                    method: 'POST',
                                    headers: {{ 'Content-Type': 'application/octet-stream' }}, 
                                    body: chunk
                                }});

                                if (res.status === 401) throw new Error("需管理员权限");
                                if (!res.ok) throw new Error(`Status ${{res.status}}`);

                                offset = end;

                            }} catch (e) {{
                                pBar.style.backgroundColor = '#f44336';
                                pBar.textContent = `${{prefix}}失败: ${{e.message}}`;
                                return false;
                            }}
                        }}

                        pBar.textContent = `${{prefix}}完成!`;
                        return true;
                    }}

                    function downloadFile(n,p){{
                        window.open(`/download?file=${{n}}&path=${{p}}`,'_blank');
                    }}

                    function deleteFile(n,p,isDir){{
                        const nd=decodeURIComponent(n);
                        const typeName = isDir ? '文件夹' : '文件';
                        if(confirm('确定要删除'+typeName+' "'+nd+'" 吗? '+ (isDir?'\\n注意:文件夹内的所有内容也会被删除!':''))) {{
                            handleRequest(`/delete?file=${{n}}&path=${{p}}`, 'POST', null, '删除成功!', '删除失败');
                        }}
                    }}

                    function renameFile(o,p){{
                        const od=decodeURIComponent(o);
                        const nn=prompt('请输入新名称:',od);
                        if(nn&&nn!==od){{
                            handleRequest(`/rename?old=${{o}}&new=${{encodeURIComponent(nn)}}&path=${{p}}`, 'POST', null, '重命名成功!', '重命名失败');
                        }}
                    }}

                    function createDir(p) {{
                        const name = prompt('请输入新文件夹名称:');
                        if (name) {{
                            handleRequest(`/mkdir?name=${{encodeURIComponent(name)}}&path=${{p}}`, 'POST', null, '创建成功!', '创建失败');
                        }}
                    }}

                    function openFile(n, p) {{
                        window.open(`/download?file=${{n}}&path=${{p}}&preview=1`, '_blank');
                    }}
                    function openDir(p){{ window.location.href=`/dir?path=${{p}}`; }}
                </script>
            </body>
            </html>
            '''.format(breadcrumb_html, file_list_html, current_path_encoded)

            self.send_response(200)
            self.send_header('Content-Type', 'text/html; charset=utf-8')
            self.end_headers()
            self.wfile.write(html_str.encode('utf-8'))
        except Exception as e:
            self.send_error(500, "Page error: {0}".format(str(e)))

    def get_breadcrumb(self, current_path):
        shared_path = Path(self.shared_path).resolve()
        current_path = Path(current_path).resolve()
        breadcrumb = []
        breadcrumb.append('<a href="/dir?path={0}">根目录</a>'.format(urllib.parse.quote(str(shared_path))))
        if current_path != shared_path:
            try:
                rel_parts = current_path.relative_to(shared_path).parts
                current_dir = shared_path
                for part in rel_parts:
                    current_dir = current_dir / part
                    breadcrumb.append('<span>/</span>')
                    breadcrumb.append(
                        '<a href="/dir?path={0}">{1}</a>'.format(urllib.parse.quote(str(current_dir)), part))
            except ValueError:
                pass
        return ''.join(breadcrumb)

    def handle_zip_download(self, current_path, files):
        try:
            # 1. 解析路径
            current_path = urllib.parse.unquote(current_path)
            base_dir = Path(current_path).resolve()
            shared_root = Path(self.shared_path).resolve()

            # 2. 路径安全检查 (修复 403 的核心)
            base_dir_str = str(base_dir)
            root_dir_str = str(shared_root)

            # Windows 系统不区分大小写,但字符串比较区分。
            # 这里统一转为小写进行比对,防止误报 403。
            is_safe = False
            if platform.system() == 'Windows':
                if base_dir_str.lower().startswith(root_dir_str.lower()):
                    is_safe = True
            else:
                if base_dir_str.startswith(root_dir_str):
                    is_safe = True

            if not is_safe:
                print(f"[ERROR 403] 安全拦截: 请求路径 '{base_dir_str}' 不在共享根目录 '{root_dir_str}' 之内")
                self.send_error(403, "Forbidden: Path not allowed")
                return

            if not files:
                self.send_error(400, "No files selected")
                return

            # 3. 文件名生成逻辑
            timestamp = time.strftime('%Y%m%d_%H%M%S')
            if len(files) == 1:
                # 尝试获取文件原始名称(不含后缀)
                raw_name = files[0]
                safe_name = Path(raw_name).stem if Path(raw_name).suffix else raw_name
                zip_name_str = f"{safe_name}_{timestamp}.zip"
            else:
                zip_name_str = f"批量下载_{timestamp}.zip"

            encoded_filename = urllib.parse.quote(zip_name_str)

            # 4. 发送响应头 (包含强力禁缓存)
            self.send_response(200)
            self.send_header('Content-Type', 'application/zip')
            self.send_header('Content-Disposition',
                             f'attachment; filename="download.zip"; filename*=UTF-8\'\'{encoded_filename}')
            self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
            self.send_header('Pragma', 'no-cache')
            self.send_header('Expires', '0')
            self.end_headers()

            # 5. 打包流 (仅存储模式,速度最快)
            with zipfile.ZipFile(self.wfile, 'w', zipfile.ZIP_STORED, allowZip64=True) as zf:
                for fname in files:
                    if '%' in fname:
                        fname = urllib.parse.unquote(fname)

                    file_path = base_dir / fname

                    if not file_path.exists():
                        continue

                    if file_path.is_file():
                        zf.write(file_path, fname)
                    elif file_path.is_dir():
                        for root, dirs, filenames in os.walk(file_path):
                            for f in filenames:
                                full_path = Path(root) / f
                                try:
                                    relative_path = full_path.relative_to(base_dir)
                                    zf.write(full_path, str(relative_path))
                                except ValueError:
                                    pass

        except Exception as e:
            # 捕获连接中断等错误,不让程序崩溃
            print(f"[WARN] Zip download interrupted: {e}")

    def open_file(self, filename, current_path):
        try:
            filename = urllib.parse.unquote(filename)
            current_path = urllib.parse.unquote(current_path)
            file_path = Path(current_path) / filename
            if not str(file_path.resolve()).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return
            if not file_path.exists() or not file_path.is_file():
                self.send_error(404, "File not found")
                return

            cache_key = str(file_path.resolve())
            current_time = time.time()
            with FileShareHandler._open_lock:
                if cache_key in FileShareHandler._open_cache:
                    if current_time - FileShareHandler._open_cache[cache_key] < 1.5:
                        self.send_response(200)
                        self.send_header('Content-Type', 'application/json')
                        self.end_headers()
                        self.wfile.write(b'{"success": true, "message": "Already opening."}')
                        return
                FileShareHandler._open_cache[cache_key] = current_time
                for k in list(FileShareHandler._open_cache.keys()):
                    if current_time - FileShareHandler._open_cache[k] > 10:
                        del FileShareHandler._open_cache[k]

            system = platform.system()
            if system == "Windows":
                os.startfile(str(file_path))
            elif system == "Darwin":
                subprocess.Popen(["open", str(file_path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            else:
                subprocess.Popen(["xdg-open", str(file_path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                                 start_new_session=True)
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"success": true, "message": "Open command sent."}')
        except Exception as e:
            self.send_response(500)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"success": false, "message": "Failed to open file."}')

    def check_file_exists(self, filename, current_path):
        try:
            filename = urllib.parse.unquote(filename)
            current_path = urllib.parse.unquote(current_path)
            file_path = Path(current_path) / filename

            if not str(file_path).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403)
                return

            size = 0
            if file_path.exists() and file_path.is_file():
                size = file_path.stat().st_size

            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(json.dumps({'size': size}).encode('utf-8'))
        except Exception:
            self.send_error(500)

    def download_file(self, filename, current_path):
        try:
            filename = urllib.parse.unquote(filename)
            current_path = urllib.parse.unquote(current_path)
            file_path = Path(current_path) / filename
            if not str(file_path).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return
            if not file_path.exists():
                self.send_error(404, "File not found")
                return
            if not file_path.is_file():
                self.send_error(400, "Not a file")
                return

            file_size = file_path.stat().st_size
            content_type, encoding = mimetypes.guess_type(file_path)
            if not content_type:
                content_type = 'application/octet-stream'

            range_header = self.headers.get('Range')
            start = 0
            end = file_size - 1
            status_code = 200

            if range_header:
                try:
                    m = re.search(r'bytes=(\d+)-(\d*)', range_header)
                    if m:
                        g1, g2 = m.groups()
                        start = int(g1)
                        if g2:
                            end = int(g2)
                        if start >= file_size:
                            self.send_error(416, "Requested Range Not Satisfiable")
                            self.send_header("Content-Range", f"bytes */{file_size}")
                            self.end_headers()
                            return
                        status_code = 206
                except:
                    pass

            length = end - start + 1

            self.send_response(status_code)
            self.send_header('Content-Type', content_type)
            self.send_header('Accept-Ranges', 'bytes')
            if status_code == 206:
                self.send_header('Content-Range', f'bytes {start}-{end}/{file_size}')
            self.send_header('Content-Length', str(length))

            parsed_url = urllib.parse.urlparse(self.path)
            query_params = urllib.parse.parse_qs(parsed_url.query)
            is_preview = query_params.get('preview', ['0'])[0] == '1'
            disposition_type = 'inline' if is_preview else 'attachment'

            encoded_filename = Header(filename, 'utf-8').encode()
            self.send_header(
                'Content-Disposition',
                f'{disposition_type}; filename="{encoded_filename}"; filename*=UTF-8\'\'{urllib.parse.quote(filename)}'
            )

            if encoding:
                self.send_header('Content-Encoding', encoding)

            self.end_headers()

            with open(file_path, 'rb') as f:
                f.seek(start)
                bytes_sent = 0
                chunk_size = 64 * 1024
                while bytes_sent < length:
                    read_len = min(chunk_size, length - bytes_sent)
                    chunk = f.read(read_len)
                    if not chunk:
                        break
                    try:
                        self.wfile.write(chunk)
                        bytes_sent += len(chunk)
                    except (BrokenPipeError, ConnectionResetError):
                        break
        except Exception as e:
            pass

    def delete_file(self, filename, current_path):
        try:
            filename = urllib.parse.unquote(filename)
            current_path = urllib.parse.unquote(current_path)
            target_path = Path(current_path) / filename

            if not str(target_path).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return
            if not target_path.exists():
                self.send_error(404, "Not found")
                return

            if target_path.is_file():
                target_path.unlink()
            elif target_path.is_dir():
                shutil.rmtree(target_path)

            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"success": true}')
        except Exception as e:
            self.send_error(500, "Delete error: {0}".format(str(e)))

    def rename_file(self, old_name, new_name, current_path):
        try:
            old_name = urllib.parse.unquote(old_name)
            new_name = urllib.parse.unquote(new_name)
            current_path = urllib.parse.unquote(current_path)
            old_path = Path(current_path) / old_name
            new_path = Path(current_path) / new_name

            if not str(old_path).startswith(str(Path(self.shared_path).resolve())) or \
                    not str(new_path).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return
            if not old_path.exists():
                self.send_error(404, "File not found")
                return
            if new_path.exists():
                self.send_error(409, "Target already exists")
                return

            old_path.rename(new_path)
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"success": true}')
        except Exception as e:
            self.send_error(500, "Rename error: {0}".format(str(e)))

    def create_directory(self, dirname, current_path):
        try:
            dirname = urllib.parse.unquote(dirname)
            current_path = urllib.parse.unquote(current_path)
            new_dir_path = Path(current_path) / dirname

            if not str(new_dir_path).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return
            if new_dir_path.exists():
                self.send_error(409, "Directory already exists")
                return

            new_dir_path.mkdir()
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"success": true}')
        except Exception as e:
            self.send_error(500, "Mkdir error: {0}".format(str(e)))

    def handle_chunk_upload(self, current_path, filename, offset):
        try:
            current_path = urllib.parse.unquote(current_path)
            filename = urllib.parse.unquote(filename)

            filename = filename.lstrip('/').lstrip('\\')
            save_path = Path(current_path) / filename

            if not str(save_path).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Access denied")
                return

            # --- 自动创建父级目录 ---
            try:
                if not save_path.parent.exists():
                    save_path.parent.mkdir(parents=True, exist_ok=True)
            except Exception as e:
                print(f"Create dir error: {e}")
                self.send_error(500, "Failed to create directory")
                return

            if offset == 0:
                with open(save_path, 'wb') as f:
                    pass

            mode = 'r+b' if save_path.exists() else 'wb'

            with open(save_path, mode) as f:
                f.seek(offset)
                chunk_len = int(self.headers.get('Content-Length', 0))
                if chunk_len > 0:
                    bytes_left = chunk_len
                    read_block = 65536
                    while bytes_left > 0:
                        chunk = self.rfile.read(min(read_block, bytes_left))
                        if not chunk: break
                        f.write(chunk)
                        bytes_left -= len(chunk)

            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"success": true}')

        except Exception as e:
            self.send_error(500, f"Chunk upload failed: {str(e)}")

    def handle_upload(self, current_path):
        save_path = None
        try:
            current_path = urllib.parse.unquote(current_path)
            current_path = Path(current_path).resolve()
            shared_path = Path(self.shared_path).resolve()
            if not str(current_path).startswith(str(shared_path)):
                current_path = shared_path

            content_type = self.headers.get('Content-Type', '')
            if 'boundary=' not in content_type:
                self.send_error(400, "Invalid content type")
                return

            content_length = int(self.headers.get('Content-Length', 0))
            if content_length == 0:
                self.send_error(400, "Empty content")
                return

            boundary = content_type.split('boundary=')[1].strip()
            if boundary.startswith('"') and boundary.endswith('"'):
                boundary = boundary[1:-1]
            boundary = boundary.encode('utf-8')
            boundary_start = b'--' + boundary

            bytes_read = 0
            filename = None

            while True:
                line = self.rfile.readline()
                bytes_read += len(line)
                if not line: break
                if b'filename="' in line:
                    line_str = line.decode('utf-8', errors='ignore')
                    filename = line_str.split('filename="')[1].split('"')[0]
                if line == b'\r\n':
                    break

            if not filename:
                self.send_error(400, "Can't find filename")
                return

            filename = filename.lstrip('/').lstrip('\\')  # 去除开头的斜杠
            save_path = current_path / filename
            # 再次安全检查
            if not str(save_path.resolve()).startswith(str(Path(self.shared_path).resolve())):
                self.send_error(403, "Forbidden Path")
                return
            # 自动创建父目录
            if not save_path.parent.exists():
                save_path.parent.mkdir(parents=True, exist_ok=True)

            counter = 1
            while save_path.exists():
                name, ext = os.path.splitext(filename)
                new_name = "{0}_{1}{2}".format(name, counter, ext)
                save_path = current_path / new_name
                counter += 1

            chunk_size = 65536
            remain_bytes = content_length - bytes_read

            with open(save_path, 'wb') as f:
                read_len = min(chunk_size, remain_bytes)
                prev_chunk = self.rfile.read(read_len)
                remain_bytes -= len(prev_chunk)

                if not prev_chunk and remain_bytes > 0:
                    raise ConnectionError("Upload aborted by client")

                while True:
                    if remain_bytes <= 0:
                        if boundary_start in prev_chunk:
                            end_pos = prev_chunk.find(boundary_start)
                            real_data = prev_chunk[:end_pos]
                            if real_data.endswith(b'\r\n'):
                                real_data = real_data[:-2]
                            f.write(real_data)
                        else:
                            f.write(prev_chunk)
                        break

                    read_len = min(chunk_size, remain_bytes)
                    chunk = self.rfile.read(read_len)
                    remain_bytes -= len(chunk)

                    if not chunk:
                        if remain_bytes > 0:
                            raise ConnectionError("Upload aborted by client")
                        f.write(prev_chunk)
                        break

                    if boundary_start in chunk:
                        combined = prev_chunk + chunk
                        end_pos = combined.find(boundary_start)
                        real_data = combined[:end_pos]
                        if real_data.endswith(b'\r\n'):
                            real_data = real_data[:-2]
                        f.write(real_data)
                        break
                    else:
                        f.write(prev_chunk)
                        prev_chunk = chunk

            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(b'{"success": true}')

        except Exception as e:
            print(f"Upload interrupted/error: {e}")
            if save_path and save_path.exists():
                try:
                    time.sleep(0.1)
                    os.remove(save_path)
                except Exception:
                    pass
            try:
                self.send_error(500, f"Upload aborted: {e}")
            except:
                pass

    def format_size(self, size):
        for unit in ['B', 'KB', 'MB', 'GB']:
            if size < 1024.0:
                return "{0:.1f} {1}".format(size, unit)
            size /= 1024.0
        return "{0:.1f} TB".format(size)


class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    daemon_threads = True

    def __init__(self, server_address, RequestHandlerClass):
        self.allow_reuse_address = True
        super().__init__(server_address, RequestHandlerClass)


class FastBindHTTPServer(ThreadingMixIn, HTTPServer):
    address_family = socket.AF_INET

    def server_bind(self):
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)


class FileSharer:
    def __init__(self):
        self.server = None
        self.server_thread = None
        self.shared_path = None
        self.password = None
        self.admin_password = None
        self.port = DEFAULT_PORT
        self.is_running = False
        self.status_callback = None
        self.log_queue: queue.Queue = queue.Queue()

    def set_status_callback(self, callback):
        self.status_callback = callback

    def update_status(self, message, is_running):
        self.is_running = is_running
        if self.status_callback:
            self.status_callback(message, is_running)

    def _allow_firewall_access(self):
        if platform.system() != 'Windows':
            return
        try:
            startupinfo = None
            if platform.system() == 'Windows':
                startupinfo = subprocess.STARTUPINFO()
                startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

            result = subprocess.run(
                [
                    'netsh', 'advfirewall', 'firewall', 'add', 'rule',
                    'name=FileShareTool_{0}'.format(self.port),
                    'dir=in', 'action=allow', 'protocol=TCP',
                    'localport={0}'.format(self.port),
                    'remoteip=localsubnet', 'profile=private',
                    'enable=yes'
                ],
                capture_output=True,
                text=True,
                errors='replace',
                check=False,
                startupinfo=startupinfo,
                creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
            )

            if result.returncode == 0:
                print("已添加新防火墙规则 {0} 端口".format(self.port))
            elif result.returncode == 1:
                print("提示: 自动添加防火墙规则失败(权限不足)。")
            else:
                print(f"防火墙规则添加失败,错误代码: {result.returncode}")

        except Exception as e:
            print("添加防火墙规则失败(未知错误): {0}".format(str(e)))

    def stop(self):
        if not self.is_running or not self.server:
            return
        self.update_status("已停止", False)

        def cleanup_worker():
            try:
                self.server.shutdown()
                self.server.server_close()
            except Exception as e:
                print(f"后台清理服务器时出错: {e}")
            finally:
                self.server = None
                global server_instance
                server_instance = None

        threading.Thread(target=cleanup_worker, daemon=True).start()

    def get_all_local_ips(self):
        ips = []
        try:
            for interface in socket.if_nameindex():
                if interface[1] == 'lo':
                    continue
                try:
                    addrs = socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET, 0, socket.SOL_TCP)
                    for addr in addrs:
                        ip = addr[4][0]
                        if ip not in ips and not ip.startswith('127.'):
                            ips.append(ip)
                except:
                    continue
            if not ips:
                s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                s.connect(('8.8.8.8', 80))
                ips.append(s.getsockname()[0])
                s.close()
        except:
            ips.append('127.0.0.1')
        return ips if ips else ['127.0.0.1']

    def start(self, path=None, port=None, password=None, admin_password=None):
        t_total_start = time.time()

        if self.is_running:
            return False, "服务已在运行中。"
        self.update_status("正在启动...", True)
        self.shared_path = path if path and Path(path).exists() else DEFAULT_SHARE_PATH
        self.port = port if port and isinstance(port, int) and 1 <= port <= 65535 else DEFAULT_PORT
        self.password = password
        self.admin_password = admin_password

        SESSIONS_STORE.clear()

        if not Path(self.shared_path).exists():
            self.update_status("启动失败", False)
            return False, "路径不存在"

        def access_logger(client_ip, request_line):
            self.log_queue.put((time.strftime('%H:%M:%S'), client_ip, request_line))

        try:
            handler_factory = lambda *args: FileShareHandler(*args,
                                                             shared_path=self.shared_path,
                                                             access_logger=access_logger,
                                                             password=self.password,
                                                             admin_password=self.admin_password)

            print(f"[DEBUG] 准备创建服务器...")
            t_server = time.time()

            self.server = FastBindHTTPServer(('', self.port), handler_factory)

            print(f"[DEBUG] HTTPServer 创建耗时: {time.time() - t_server:.4f}s")

            # 会被杀毒软件误报为病毒,所以改为用户自己授权
            # self._allow_firewall_access()

            self.server_thread = threading.Thread(target=self.server.serve_forever, daemon=True)
            self.server_thread.start()

            local_ips = self.get_all_local_ips()
            main_ip = local_ips[0] if local_ips else '127.0.0.1'
            local_url = "http://localhost:{0}".format(self.port)
            network_urls = ["http://{0}:{1}".format(ip, self.port) for ip in local_ips]

            server_instance = self
            self.update_status("运行中 (端口 {0})".format(self.port), True)

            return True, {
                'local_url': local_url,
                'network_urls': network_urls,
                'main_ip': main_ip,
                'all_ips': local_ips,
                'port': self.port,
                'folder': self.shared_path,
                'log_queue': self.log_queue,
                'has_password': bool(self.password),
                'has_admin_password': bool(self.admin_password)
            }
        except Exception as e:
            self.update_status("启动失败", False)
            print(f"[DEBUG] 启动异常: {e}")
            return False, "启动失败: {0}".format(str(e))


class ClickableLabel(QLabel):
    clicked = pyqtSignal()

    def __init__(self, text="", parent=None):
        super().__init__(text, parent)

    def mousePressEvent(self, event):
        self.clicked.emit()
        super().mousePressEvent(event)


class FileShareGUI(QMainWindow):
    status_updated = pyqtSignal(str, bool)
    start_completed = pyqtSignal(bool, object)

    def __init__(self, initial_path=None, initial_port=None, auto_start=True):
        super().__init__()
        self.setWindowTitle("文件共享工具 v5.5")
        self.set_window_icon()
        self.settings = QSettings("FileShareTool", "App")
        self.sharer = FileSharer()
        self.start_result = None
        self.initial_path = initial_path
        self.initial_port = initial_port
        self.auto_start = auto_start
        self.reg_key_name = "FileShareTool"
        self.startup_reg_key_name = "FileShareToolStartup"
        self.log_queue = None
        self.log_timer = QTimer(self)
        self.setAcceptDrops(True)
        self.qr_items = []
        self.selected_qr_index = None
        self.setup_tray_icon()
        self.create_widgets()
        self.connect_signals()
        self.load_settings()
        self.update_menu_btn_text()
        self.update_startup_btn_text()
        self.update_status("已停止", False)
        if self.auto_start:
            QTimer.singleShot(100, self.auto_start_share)

    def set_window_icon(self):
        try:
            if ICON_BASE64:
                icon_data = base64.b64decode(ICON_BASE64)
                pixmap = QPixmap()
                pixmap.loadFromData(icon_data)
                self.setWindowIcon(QIcon(pixmap))
        except Exception as e:
            pass

    def setup_tray_icon(self):
        self.tray_icon = QSystemTrayIcon(self)
        try:
            if ICON_BASE64:
                icon_data = base64.b64decode(ICON_BASE64)
                pixmap = QPixmap()
                pixmap.loadFromData(icon_data)
                self.tray_icon.setIcon(QIcon(pixmap))
            else:
                self.tray_icon.setIcon(self.style().standardIcon(self.style().SP_ComputerIcon))
        except:
            self.tray_icon.setIcon(self.style().standardIcon(self.style().SP_ComputerIcon))
        tray_menu = QMenu()
        show_action = QAction("显示主窗口", self)
        show_action.triggered.connect(self.show_window)
        tray_menu.addAction(show_action)
        toggle_action = QAction("启动/停止共享", self)
        toggle_action.triggered.connect(self.toggle_share)
        tray_menu.addAction(toggle_action)
        tray_menu.addSeparator()
        quit_action = QAction("退出程序", self)
        quit_action.triggered.connect(self.quit_application)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.activated.connect(self.tray_icon_activated)
        self.tray_icon.setToolTip("文件共享工具")
        self.tray_icon.show()

    def show_window(self):
        self.showNormal()
        self.activateWindow()
        self.raise_()

    def tray_icon_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.show_window()

    def quit_application(self):
        self.save_settings()
        if self.sharer.is_running:
            self.sharer.stop()
            time.sleep(0.1)
        self.tray_icon.hide()
        QApplication.quit()

    def changeEvent(self, event):
        if event.type() == event.WindowStateChange:
            if self.isMinimized():
                QTimer.singleShot(0, self.hide)
                self.tray_icon.showMessage("文件共享工具", "程序已最小化到系统托盘", QSystemTrayIcon.Information, 2000)
        super().changeEvent(event)

    def toggle_extra_settings(self):
        is_visible = self.expand_btn.isChecked()
        self.extra_settings_widget.setVisible(is_visible)

        if is_visible:
            self.expand_btn.setText("收起设置↑")
        else:
            self.expand_btn.setText("更多设置...")

    def create_widgets(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # --- 路径选择区域 ---
        path_layout = QHBoxLayout()
        path_label = QLabel("共享路径:")
        self.path_entry = QLineEdit()

        self.select_path_btn = QPushButton("浏览")
        self.select_path_btn.setFixedWidth(80)
        self.select_path_btn.clicked.connect(self.browse_path)

        path_layout.addWidget(path_label)
        path_layout.addWidget(self.path_entry)
        path_layout.addWidget(self.select_path_btn)
        main_layout.addLayout(path_layout)

        # --- 主控制区 ---
        main_controls_layout = QHBoxLayout()
        self.toggle_share_btn = QPushButton("启动共享")
        main_controls_layout.addWidget(self.toggle_share_btn)
        main_controls_layout.addSpacing(10)

        port_label = QLabel("端口:")
        self.port_entry = QLineEdit()
        self.port_entry.setFixedWidth(70)
        main_controls_layout.addWidget(port_label)
        main_controls_layout.addWidget(self.port_entry)
        main_controls_layout.addSpacing(10)

        pwd_label = QLabel("访问密码:")
        self.password_entry = QLineEdit()
        self.password_entry.setPlaceholderText("留空无保护")
        self.password_entry.setFixedWidth(110)
        main_controls_layout.addWidget(pwd_label)
        main_controls_layout.addWidget(self.password_entry)
        main_controls_layout.addSpacing(10)

        self.browser_btn = QPushButton("打开浏览器")
        main_controls_layout.addWidget(self.browser_btn)
        main_controls_layout.addStretch(1)

        self.expand_btn = QPushButton("更多设置...")
        self.expand_btn.setCheckable(True)
        self.expand_btn.setFixedWidth(120)
        self.expand_btn.clicked.connect(self.toggle_extra_settings)
        main_controls_layout.addWidget(self.expand_btn)

        main_layout.addLayout(main_controls_layout)

        # --- 更多设置区域 ---
        self.extra_settings_widget = QWidget()
        self.extra_settings_widget.setObjectName("ExtraWidget")
        self.extra_settings_widget.setStyleSheet("""
            #ExtraWidget {
                background-color: #f5f5f5; 
                border: 1px solid #ddd;
                border-radius: 5px;
            }
        """)

        extra_layout = QHBoxLayout(self.extra_settings_widget)
        extra_layout.setContentsMargins(10, 8, 10, 8)

        admin_pwd_label = QLabel("管理密码:")
        self.admin_password_entry = QLineEdit()
        self.admin_password_entry.setPlaceholderText("留空无保护")
        self.admin_password_entry.setFixedWidth(110)
        self.admin_password_entry.setToolTip("设置后,上传/删除/重命名需二次验证")

        self.startup_btn = QPushButton("开机自启")
        self.menu_btn = QPushButton("添加右键菜单")

        extra_layout.addWidget(admin_pwd_label)
        extra_layout.addWidget(self.admin_password_entry)
        extra_layout.addStretch(1)
        extra_layout.addWidget(self.startup_btn)
        extra_layout.addWidget(self.menu_btn)

        self.extra_settings_widget.setVisible(False)
        main_layout.addWidget(self.extra_settings_widget)

        # --- 信息与日志区域 (使用 QSplitter 实现可拖拽调整高度) ---
        splitter = QSplitter(Qt.Vertical)

        # 1. 上半部分:访问信息
        info_widget = QWidget()
        info_layout = QVBoxLayout(info_widget)
        info_layout.setContentsMargins(0, 0, 0, 0)

        info_label = QLabel("访问信息:")
        self.info_text = QTextEdit()
        self.info_text.setReadOnly(True)
        # 去掉setFixedHeight,改由 Splitter 控制高度

        info_layout.addWidget(info_label)
        info_layout.addWidget(self.info_text)

        # 2. 下半部分:访问记录
        log_widget = QWidget()
        log_layout = QVBoxLayout(log_widget)
        log_layout.setContentsMargins(0, 0, 0, 0)  # 减小内部边距

        log_label = QLabel("访问记录:")
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)

        log_layout.addWidget(log_label)
        log_layout.addWidget(self.log_text)

        # 将上下部分添加到分割器
        splitter.addWidget(info_widget)
        splitter.addWidget(log_widget)

        # 设置初始比例:访问信息默认给 300px,剩下的全部给日志
        splitter.setSizes([300, 600])
        splitter.setCollapsible(0, False)  # 防止被拖拽到消失
        splitter.setCollapsible(1, False)

        main_layout.addWidget(splitter)

        # --- 二维码区域 ---
        self.qr_container = QWidget()
        self.qr_layout = QHBoxLayout(self.qr_container)
        self.qr_layout.setSpacing(20)
        self.qr_layout.addStretch(1)
        self.qr_layout.addStretch(1)
        self.qr_container.hide()
        main_layout.addWidget(self.qr_container)

    def connect_signals(self):
        self.sharer.set_status_callback(self.emit_status_update)
        self.toggle_share_btn.clicked.connect(self.toggle_share)
        self.browser_btn.clicked.connect(self.open_browser)
        self.menu_btn.clicked.connect(self.toggle_right_click_menu)
        self.startup_btn.clicked.connect(self.toggle_startup)
        self.status_updated.connect(self.update_status)
        self.start_completed.connect(self.update_ui_after_start)
        self.log_timer.timeout.connect(self.update_log_display)

    def load_settings(self):
        self.resize(self.settings.value("size", QSize(750, 580)))
        self.move(self.settings.value("pos", QPoint(200, 200)))
        saved_path = self.settings.value("path", DEFAULT_SHARE_PATH)
        self.path_entry.setText(self.initial_path or saved_path)
        self.port_entry.setText(str(self.initial_port or self.settings.value("port", DEFAULT_PORT)))
        self.password_entry.setText(self.settings.value("password", ""))
        self.admin_password_entry.setText(self.settings.value("admin_password", ""))

    def save_settings(self):
        self.settings.setValue("size", self.size())
        self.settings.setValue("pos", self.pos())
        self.settings.setValue("path", self.path_entry.text())
        self.settings.setValue("port", self.port_entry.text())
        self.settings.setValue("password", self.password_entry.text())
        self.settings.setValue("admin_password", self.admin_password_entry.text())

    def closeEvent(self, event):
        self.save_settings()
        if self.sharer.is_running:
            reply = QMessageBox.question(self, '确认', '共享服务仍在运行,确定要退出吗?',
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.sharer.stop()
                time.sleep(0.1)
                self.tray_icon.hide()
                event.accept()
                QApplication.quit()
            else:
                event.ignore()
        else:
            self.tray_icon.hide()
            event.accept()
            QApplication.quit()

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            url = event.mimeData().urls()[0]
            path = url.toLocalFile()
            if Path(path).exists():
                self.path_entry.setText(path)

    def browse_path(self):
        path = QFileDialog.getExistingDirectory(self, "选择共享文件夹", self.path_entry.text())
        if path:
            self.path_entry.setText(path)

    def toggle_share(self):
        if self.sharer.is_running:
            self.stop_share()
        else:
            self.start_share()

    def start_share(self):
        path = self.path_entry.text().strip()
        if not path or not Path(path).exists():
            QMessageBox.warning(self, "警告", "请选择一个有效的共享路径!")
            return
        try:
            port = int(self.port_entry.text().strip())
            if not (1 <= port <= 65535):
                raise ValueError
        except ValueError:
            QMessageBox.warning(self, "警告", "请输入有效的端口号 (1-65535)!")
            self.port_entry.setText(str(DEFAULT_PORT))
            return

        password = self.password_entry.text().strip()
        admin_password = self.admin_password_entry.text().strip()

        self.info_text.clear()
        self.log_text.clear()
        self.toggle_share_btn.setEnabled(False)
        threading.Thread(target=self._start_share_thread, args=(path, port, password, admin_password),
                         daemon=True).start()

    def _start_share_thread(self, path, port, password, admin_password):
        success, result = self.sharer.start(path, port, password, admin_password)
        self.start_result = result
        self.start_completed.emit(success, result)

    def stop_share(self):
        self.sharer.stop()

    def open_browser(self):
        import webbrowser
        url_to_open = ""
        if self.start_result and 'local_url' in self.start_result and self.sharer.is_running:
            url_to_open = self.start_result['local_url']
            if 'network_urls' in self.start_result and self.start_result['network_urls']:
                network_url = self.start_result['network_urls'][0]
                QApplication.clipboard().setText(network_url)
                self.statusBar().showMessage(f"已复制局域网地址: {network_url}", 4000)
        else:
            try:
                port = int(self.port_entry.text().strip())
                url_to_open = f"http://localhost:{port}"
            except:
                url_to_open = f"http://localhost:{DEFAULT_PORT}"
        if url_to_open:
            webbrowser.open(url_to_open)

    def emit_status_update(self, message, is_running):
        self.status_updated.emit(message, is_running)

    def update_status(self, message, is_running):
        self.statusBar().showMessage(message)
        is_transitioning = "正在" in message

        # 1. 切换按钮的颜色和文字
        if is_running:
            self.toggle_share_btn.setText("停止共享")
            self.toggle_share_btn.setStyleSheet(
                "QPushButton { background-color: #f44336; color: white; font-weight: bold; padding: 5px 15px; border: none; border-radius: 4px; } QPushButton:hover { background-color: #d32f2f; }")
        else:
            self.toggle_share_btn.setText("启动共享")
            self.toggle_share_btn.setStyleSheet(
                "QPushButton { background-color: #4CAF50; color: white; font-weight: bold; padding: 5px 15px; border: none; border-radius: 4px; } QPushButton:hover { background-color: #45a049; }")

        # 2. 控制按钮的启用/禁用
        self.toggle_share_btn.setEnabled(not is_transitioning)
        self.browser_btn.setEnabled(is_running and not is_transitioning)

        # --- 控制输入框变灰和只读 ---
        disable_edit = is_running or is_transitioning

        # 定义样式:灰色背景 + 深灰文字 vs 默认白色背景
        # 这里使用了 #e0e0e0 (中灰) 让不可编辑状态更明显
        input_style = "QLineEdit { background-color: #e0e0e0; color: #555555; }" if disable_edit else ""

        # 批量设置所有输入框
        inputs = [self.path_entry, self.port_entry, self.password_entry, self.admin_password_entry]
        for widget in inputs:
            widget.setReadOnly(disable_edit)
            widget.setStyleSheet(input_style)

        # 顺便把“浏览”按钮也禁用了,避免误解
        if hasattr(self, 'select_path_btn'):
            self.select_path_btn.setEnabled(not disable_edit)

        # --- 托盘提示 ---
        if is_running:
            self.toggle_share_btn.setFocus()
            self.tray_icon.setToolTip("文件共享工具 - 运行中")
        else:
            self.tray_icon.setToolTip("文件共享工具 - 已停止")

        # --- 清理逻辑 ---
        if not is_running and not is_transitioning:
            self.info_text.clear()
            self.log_text.clear()
            self.log_timer.stop()
            self.log_queue = None
            self.clear_qrcodes()
            self.qr_container.hide()

    def update_log_display(self):
        if not self.log_queue:
            return
        while not self.log_queue.empty():
            try:
                timestamp, ip, request = self.log_queue.get_nowait()
                log_entry = f"[{timestamp}] {ip} - {request}"
                self.log_text.append(log_entry)
            except queue.Empty:
                break

    def clear_qrcodes(self):
        while self.qr_layout.count() > 2:
            item = self.qr_layout.takeAt(1)
            if item and item.widget():
                item.widget().deleteLater()
        self.qr_items = []
        self.selected_qr_index = None

    def select_qr(self, index):
        self.selected_qr_index = index
        for item in self.qr_items:
            if item['index'] == index:
                item['widget'].show()
                item['qr_label'].setStyleSheet("border: 3px solid #4CAF50; background: white; border-radius: 5px;")
                item['text_label'].setStyleSheet(
                    "font-weight: bold; color: #4CAF50; padding: 5px; background: #e8f5e9; border-radius: 3px;")
            else:
                item['widget'].hide()

    def toggle_qr_selection(self, index):
        if self.selected_qr_index == index:
            self.cancel_qr_selection()
        else:
            self.select_qr(index)

    def cancel_qr_selection(self):
        self.selected_qr_index = None
        for item in self.qr_items:
            item['widget'].show()
            item['qr_label'].setStyleSheet("border: 2px solid #ccc; background: white; border-radius: 5px;")
            item['text_label'].setStyleSheet("font-weight: bold; color: #333; padding: 5px; border-radius: 3px;")

    def show_qrcodes(self, urls):
        try:
            self.clear_qrcodes()
            if not urls:
                self.qr_container.hide()
                return
            self.qr_items = []
            self.selected_qr_index = None
            for index, (label_text, url) in enumerate(urls):
                qr_item_widget = QWidget()
                qr_item_widget.setProperty("qr_index", index)
                qr_item_layout = QVBoxLayout(qr_item_widget)
                qr_item_layout.setContentsMargins(5, 5, 5, 5)
                qr_item_layout.setSpacing(5)
                qr = segno.make(url, error='L')
                buffer = io.BytesIO()
                qr.save(buffer, kind='png', scale=4, border=1)
                qimg_data = buffer.getvalue()
                pix = QPixmap()
                pix.loadFromData(qimg_data)
                if not pix.isNull():
                    qr_label = ClickableLabel()
                    qr_label.setFixedSize(150, 150)
                    qr_label.setScaledContents(True)
                    qr_label.setStyleSheet("border: 2px solid #ccc; background: white; border-radius: 5px;")
                    qr_label.setPixmap(pix)
                    qr_label.setCursor(Qt.PointingHandCursor)
                    qr_label.clicked.connect(lambda idx=index: self.toggle_qr_selection(idx))
                    text_label = ClickableLabel(label_text)
                    text_label.setAlignment(Qt.AlignCenter)
                    text_label.setStyleSheet("font-weight: bold; color: #333; padding: 5px; border-radius: 3px;")
                    text_label.setCursor(Qt.PointingHandCursor)
                    text_label.clicked.connect(lambda idx=index: self.toggle_qr_selection(idx))
                    qr_item_layout.addWidget(qr_label, 0, Qt.AlignCenter)
                    qr_item_layout.addWidget(text_label, 0, Qt.AlignCenter)
                    qr_item_widget.setStyleSheet("QWidget { background: transparent; border-radius: 8px; }")
                    self.qr_items.append({
                        'widget': qr_item_widget,
                        'qr_label': qr_label,
                        'text_label': text_label,
                        'index': index
                    })
                    insert_pos = self.qr_layout.count() - 1
                    self.qr_layout.insertWidget(insert_pos, qr_item_widget)
            self.qr_container.show()
        except Exception as e:
            print(f"二维码生成崩溃: {e}")

    def update_ui_after_start(self, success, result):
        if success:
            info = f"\U0001F4C1 共享路径:{result['folder']}\n"
            info += f"\U0001F3F7 本机端口:{result['port']}\n"
            if result.get('has_password'):
                info += f"\U0001F512 访问:需密码\n"
            else:
                info += f"\U0001F513 访问:公开\n"
            if result.get('has_admin_password'):
                info += f"\U0001F6E1 管理:需密码 (上传/删除/重命名)\n"
            else:
                info += f"\U0001F4DD 管理:公开 (无保护)\n"

            info += f"\U0001F4BB 本机访问:{result['local_url']}\n"
            info += "\U0001F310 局域网址:"
            if result['network_urls']:
                info += result['network_urls'][0]
                for url in result['network_urls'][1:]:
                    info += f"\n{url}"
            else:
                info += "(未找到)"
            info += "\n"
            self.info_text.setText(info)
            self.log_queue = result.get('log_queue')
            if self.log_queue:
                self.log_timer.start(1000)
            qr_urls = []
            if result['network_urls']:
                for url in result['network_urls']:
                    ip_port = url.replace('http://', '').replace('https://', '')
                    qr_urls.append((ip_port, url))
            else:
                ip_port = result['local_url'].replace('http://', '').replace('https://', '')
                qr_urls.append((ip_port, result['local_url']))
            self.show_qrcodes(qr_urls)
        else:
            self.info_text.setText(f"错误: {result}")
            QMessageBox.critical(self, "错误", f"启动失败: {result}")
            self.qr_container.hide()

    def auto_start_share(self):
        path = self.initial_path if self.initial_path and Path(self.initial_path).exists() else self.path_entry.text()
        port = self.initial_port if self.initial_port and 1 <= self.initial_port <= 65535 else None
        if path:
            self.path_entry.setText(path)
        if port:
            self.port_entry.setText(str(port))
        if self.auto_start and Path(path).exists():
            self.start_share()

    def check_menu_exists(self):
        if platform.system() != 'Windows':
            return False
        try:
            key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}")
            winreg.CloseKey(key)
            return True
        except:
            return False

    def check_startup_exists(self):
        if platform.system() != 'Windows':
            return False
        try:
            key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0,
                                 winreg.KEY_READ)
            try:
                winreg.QueryValueEx(key, self.startup_reg_key_name)
                winreg.CloseKey(key)
                return True
            except:
                winreg.CloseKey(key)
                return False
        except:
            return False

    def update_menu_btn_text(self):
        if not self.menu_btn: return
        if platform.system() != 'Windows':
            self.menu_btn.setText("右键菜单(仅Win)")
            self.menu_btn.setEnabled(False)
            return
        self.menu_btn.setText("移除右键" if self.check_menu_exists() else "添加右键")

    def update_startup_btn_text(self):
        if not self.startup_btn: return
        if platform.system() != 'Windows':
            self.startup_btn.setText("开机自启(仅Win)")
            self.startup_btn.setEnabled(False)
            return
        self.startup_btn.setText("取消自启" if self.check_startup_exists() else "开机自启")

    def toggle_right_click_menu(self):
        if platform.system() != 'Windows': return
        try:
            if self.check_menu_exists():
                self.remove_from_right_click_menu()
            else:
                self.add_to_right_click_menu()
            self.update_menu_btn_text()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"操作失败:{str(e)}")

    def toggle_startup(self):
        if platform.system() != 'Windows': return
        try:
            if self.check_startup_exists():
                self.remove_startup()
            else:
                self.add_startup()
            self.update_startup_btn_text()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"操作失败:{str(e)}")

    def add_startup(self):
        if IS_FROZEN:
            exe_path = os.path.abspath(sys.argv[0])
            command_str = f'"{exe_path}"'
        else:
            python_exe_path = sys.executable
            script_path = os.path.abspath(__file__)
            command_str = f'"{python_exe_path}" "{script_path}"'
        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0,
                             winreg.KEY_SET_VALUE)
        winreg.SetValueEx(key, self.startup_reg_key_name, 0, winreg.REG_SZ, command_str)
        winreg.CloseKey(key)
        QMessageBox.information(self, "成功", "已开启开机自启。")

    def remove_startup(self):
        try:
            key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0,
                                 winreg.KEY_SET_VALUE)
            winreg.DeleteValue(key, self.startup_reg_key_name)
            winreg.CloseKey(key)
        except:
            pass
        QMessageBox.information(self, "成功", "已取消开机自启。")

    def add_to_right_click_menu(self):
        menu_name = "共享此文件夹"
        if IS_FROZEN:
            exe_path = os.path.abspath(sys.argv[0])
            icon_path = f'"{exe_path}",0'
            command_str_on_folder = f'"{exe_path}" "%1"'
            command_str_in_folder = f'"{exe_path}" "%V"'
        else:
            python_exe_path = sys.executable
            script_path = os.path.abspath(__file__)
            icon_path = f'"{python_exe_path}",0'
            command_str_on_folder = f'"{python_exe_path}" "{script_path}" "%1"'
            command_str_in_folder = f'"{python_exe_path}" "{script_path}" "%V"'
        key1 = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}")
        winreg.SetValueEx(key1, "", 0, winreg.REG_SZ, menu_name)
        winreg.SetValueEx(key1, "Icon", 0, winreg.REG_SZ, icon_path)
        winreg.CloseKey(key1)
        key2 = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}\\command")
        winreg.SetValueEx(key2, "", 0, winreg.REG_SZ, command_str_on_folder)
        winreg.CloseKey(key2)
        key3 = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\Background\\shell\\{self.reg_key_name}")
        winreg.SetValueEx(key3, "", 0, winreg.REG_SZ, menu_name)
        winreg.SetValueEx(key3, "Icon", 0, winreg.REG_SZ, icon_path)
        winreg.CloseKey(key3)
        key4 = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\Background\\shell\\{self.reg_key_name}\\command")
        winreg.SetValueEx(key4, "", 0, winreg.REG_SZ, command_str_in_folder)
        winreg.CloseKey(key4)
        QMessageBox.information(self, "成功", "右键菜单添加成功。")

    def remove_from_right_click_menu(self):
        try:
            winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}\\command")
            winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\shell\\{self.reg_key_name}")
        except:
            pass
        try:
            winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\Background\\shell\\{self.reg_key_name}\\command")
            winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"Directory\\Background\\shell\\{self.reg_key_name}")
        except:
            pass
        QMessageBox.information(self, "成功", "右键菜单已移除。")


def parse_arguments():
    args = sys.argv[1:]
    initial_port = None
    initial_path = None
    auto_start = False
    i = 0
    while i < len(args):
        arg = args[i]
        if arg.lower() in ['-p', '--port'] and i + 1 < len(args):
            try:
                initial_port = int(args[i + 1])
                args.pop(i)
                args.pop(i)
            except ValueError:
                args.pop(i)
                args.pop(i)
        else:
            i += 1
    if args:
        path_candidate = args[0].strip().strip('\'"')
        if path_candidate:
            path_obj = Path(path_candidate)
            if path_obj.exists():
                initial_path = str(path_obj.resolve())
                auto_start = True
    return initial_path, initial_port, auto_start


def main():
    initial_path, initial_port, auto_start = parse_arguments()
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)
    font_name = "Microsoft YaHei" if platform.system() == "Windows" else "Arial"
    app.setFont(QFont(font_name, 10))
    window = FileShareGUI(initial_path, initial_port, auto_start)
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()


免费评分

参与人数 12吾爱币 +13 热心值 +11 收起 理由
ahuan + 1 + 1 谢谢@Thanks!
xiandazhan + 1 必须给分!!!
wzlong648 + 1 + 1 我很赞同!
comma2019 + 1 + 1 做的非常好;设计满分;
zoshl + 1 + 1 谢谢@Thanks!
枫林听雨 + 1 + 1 我很赞同!
银枫月影 + 1 + 1 二楼已修改至6.0版了
akiyamamio + 1 + 1 谢谢@Thanks!
rightspace79 + 1 + 1 我很赞同!
半城长眠 + 1 我很赞同!
dn336 + 1 + 1 我很赞同!
ygq170063 + 3 + 1 谢谢@Thanks!

查看全部评分

dongmang 发表于 2025-12-4 16:11
要是有权限管理就好了,比如删除,修改都要密码才能进行~~

免费评分

参与人数 6吾爱币 +6 热心值 +5 收起 理由
calinrun + 1 谢谢@Thanks!
ycat + 2 + 1 鼓励转贴优秀软件安全工具和文档!
beijiyu292 + 1 + 1 我很赞同!
freeqd + 1 你这位置好啊,一不小心把赞都给你了
yjn866y + 1 + 1 热心回复!
z小伟style + 1 + 1 谢谢@Thanks!

查看全部评分

dongmang 发表于 2025-12-11 17:01

这样挺好的,如果还能进一步设置,设定一个只读的密码,把删除修改的权限单独设另一个密码,能更大限度保持数据安全。
jay19118isme 发表于 2025-12-18 19:13
本帖最后由 jay19118isme 于 2025-12-18 19:48 编辑
zoshl 发表于 2025-12-18 12:44
批量上传搞一个就完美了

刚更新了 5.5,可以试试
xuange6610 发表于 2025-12-13 09:25
这是我个人开发的软件,增加了可以自定义端口功能,希望大家喜欢

下载链接:http://4275.com/wbzdtf
永久链接【失效联系作者】:通过网盘分享的文件:局域网文件转发工具 v5.0【网传版】.exe
链接: https://pan.baidu.com/s/1VnE8ncbge6uHpeMzlybUYQ?pwd=6666 提取码: 6666
--来自百度网盘超级会员v5的分享
leroy特洛伊 发表于 2025-12-12 12:20
ygq170063 发表于 2025-12-4 17:56
写的非常好,然后我给你做了一些修改,你可以试试看,更美观 也更实用

成品打包:https://fjjy.lanzoub. ...

修改方向:1.增加批量下载功能   比如一个文件夹下N多文件一个一个下载太麻烦,批量搬运爽歪歪。2.增加文件夹下载功能。
ljq0527 发表于 2025-12-4 23:34
看着不错,这个应该没有新系统smb共享那些问题吧;但估计也有两个弱点,1是没有复杂的权限管理功能,2是没有smb共享的传输速度吧
笑丫丫 发表于 2025-12-4 15:51
一看都是好东西,办公室正好需要,能否来个蓝盘大佬
shuaiqingm 发表于 2025-12-4 15:55
小型WEB服务器。。
jackxjkp52pj 发表于 2025-12-4 16:01
好东西,点赞了
dongmang 发表于 2025-12-4 16:01
下载学习一下看看~~
caoyuancool 发表于 2025-12-4 16:05
应该不错 留着给办公室用下
yzxiaowu 发表于 2025-12-4 16:08
办公室的好东西!谢谢!
cioceo 发表于 2025-12-4 16:13
很不错的工具,对比直接局域网共享更便捷
gfy82 发表于 2025-12-4 16:16
很实用的一个小工具,适合小白
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-12-25 16:17

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表