通过http文件服务快速访问Windows电脑的文件小工具

软件下载地址:https://xyweb.lanzout.com/iVRYb2urezaj

利用Caddy程序的Windows版命令实现。

image

import tkinter as tk
from tkinter import messagebox
import subprocess
import socket
import threading
import os
import sys
import webbrowser

class CaddyGUI:
    def __init__(self, root):
        self.root = root
        self.root.title('Caddy File Server | 作者:www.cnblogs.com/Ojox')
        self.root.resizable(False, False)  # 禁用窗口大小调整
        self.server_running = False  # 添加服务器运行状态标志
        self.debug_mode = False  # 添加debug模式状态标志
        self.debug_window = None  # 添加debug窗口引用
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)  # 设置窗口关闭事件处理

        # 设置默认值
        default_path = os.path.join(os.path.expanduser('~'), 'Downloads')
        default_port = '8000'

        tk.Label(root, text='文件夹路径:').grid(row=0, column=0, sticky=tk.W)
        self.root_path_entry = tk.Entry(root, width=60)
        self.root_path_entry.insert(0, default_path)
        self.root_path_entry.grid(row=0, column=1, sticky=tk.W)

        tk.Label(root, text='端口:').grid(row=1, column=0, sticky=tk.W)
        self.port_entry = tk.Entry(root, width=10)
        self.port_entry.insert(0, default_port)
        self.port_entry.grid(row=1, column=1, sticky=tk.W)

        # 创建按钮容器框架并居中显示
        button_frame = tk.Frame(root)
        button_frame.grid(row=2, column=0, columnspan=2, pady=10)
  
        # 创建按钮并保存引用
        self.start_button = tk.Button(button_frame, text='启动服务器', command=self.start_server)
        self.stop_button = tk.Button(button_frame, text='停止服务器', command=self.stop_server)
        self.debug_button = tk.Button(button_frame, text='Debug', command=self.toggle_debug)
  
        # 设置按钮初始状态
        self.start_button.pack(side=tk.LEFT, padx=5)
        self.stop_button.pack(side=tk.LEFT, padx=5)
        self.debug_button.pack(side=tk.LEFT, padx=5)
        self.stop_button['state'] = 'disabled'  # 初始状态下停止按钮不可用

        self.ip_label = tk.Label(root, text='可访问地址:')
        self.ip_label.grid(row=3, column=0, sticky=tk.W)
        self.ip_text = tk.Text(root, width=50, height=2)
        self.ip_text.grid(row=4, column=0, columnspan=2, sticky=tk.W+tk.E)  # 修改为第4行并跨两列
  
        # 配置Text小部件的标签绑定
        self.ip_text.tag_configure('link', foreground='blue', underline=True)
        self.ip_text.tag_bind('link', '<Button-1>', self.open_url)
        self.ip_text.tag_bind('link', '<Enter>', lambda e: self.ip_text.configure(cursor='hand2'))
        self.ip_text.tag_bind('link', '<Leave>', lambda e: self.ip_text.configure(cursor=''))

    def toggle_debug(self):
        if self.server_running:
            messagebox.showwarning('警告', '服务运行时无法切换Debug模式!')
            return
      
        self.debug_mode = not self.debug_mode
        self.debug_button.config(relief='sunken' if self.debug_mode else 'raised')
  
        if self.debug_mode:
            # 创建新的调试窗口
            if not self.debug_window:
                self.debug_window = tk.Toplevel(self.root)
                self.debug_window.title('Debug输出 | 作者:www.cnblogs.com/Ojox | 免费作品,免费使用 | 2025年4月28日19:09:49')
                self.debug_window.geometry('800x600')
          
                # 创建文本框和滚动条
                self.debug_text = tk.Text(self.debug_window, width=80, height=30)
                self.debug_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
          
                debug_scroll = tk.Scrollbar(self.debug_window, command=self.debug_text.yview)
                debug_scroll.pack(side=tk.RIGHT, fill=tk.Y)
          
                self.debug_text.configure(yscrollcommand=debug_scroll.set)
          
                # 设置窗口关闭事件
                self.debug_window.protocol("WM_DELETE_WINDOW", self.close_debug_window)
            else:
                self.debug_window.deiconify()
        else:
            # 隐藏调试窗口
            if self.debug_window:
                self.debug_window.withdraw()
  
    def close_debug_window(self):
        """处理调试窗口的关闭事件"""
        self.debug_mode = False
        self.debug_button.config(relief='raised')
        self.debug_window.withdraw()

    def update_cmd_display(self, text):
        if self.debug_mode and self.debug_window:
            # 在调试窗口显示输出
            self.debug_text.insert(tk.END, text + '\n')
            self.debug_text.see(tk.END)
  
    def output_reader(self, pipe, callback):
        """从管道中读取输出并通过回调函数更新到GUI"""
        for line in iter(pipe.readline, b''):
            self.root.after(0, callback, line.decode().strip())
        pipe.close()

    def start_server(self):
        root_path = self.root_path_entry.get()
        port = self.port_entry.get()
        try:
            # 获取程序运行时目录
            base_path = os.path.dirname(os.path.abspath(sys.argv[0]))
      
            # 新增资源路径判断逻辑
            if getattr(sys, 'frozen', False):
                # 打包后的资源路径
                base_path = sys._MEIPASS
          
            caddy_path = os.path.join(base_path, 'Caddy.exe')
            cmd = [caddy_path, 'file-server', '--root', root_path, '--listen', f':{port}', '--browse', '--reveal-symlinks']
            if self.debug_mode:
                cmd.append('--debug')
            # 创建进程并捕获输出
            self.process = subprocess.Popen(
                cmd, 
                stdout=subprocess.PIPE, 
                stderr=subprocess.PIPE, 
                bufsize=1, 
                universal_newlines=False,
            creationflags=subprocess.CREATE_NO_WINDOW,  # 新增标志
            startupinfo=subprocess.STARTUPINFO(         # 新增启动配置
                dwFlags=subprocess.STARTF_USESHOWWINDOW,
                wShowWindow=subprocess.SW_HIDE
            )
            )
      
            # 启动输出读取线程
            threading.Thread(target=self.output_reader, args=(self.process.stdout, self.update_cmd_display), daemon=True).start()
            threading.Thread(target=self.output_reader, args=(self.process.stderr, self.update_cmd_display), daemon=True).start()
            self.update_ips(port)
            # 更新按钮状态和服务器运行状态
            self.start_button['state'] = 'disabled'
            self.stop_button['state'] = 'normal'
            self.debug_button['state'] = 'disabled'
            self.server_running = True
        except Exception as e:
            messagebox.showerror('错误', str(e))

    def stop_server(self):
        try:
            self.process.terminate()
            # 更新按钮状态和服务器运行状态
            self.start_button['state'] = 'normal'
            self.stop_button['state'] = 'disabled'
            self.debug_button['state'] = 'normal'
            self.server_running = False
            # 清空地址显示
            self.ip_text.delete(1.0, tk.END)
            # 清空调试窗口内容
            if self.debug_window:
                self.debug_text.delete(1.0, tk.END)
        except Exception as e:
            messagebox.showerror('错误', str(e))

    def on_closing(self):
        """处理窗口关闭事件"""
        if self.server_running:
            messagebox.showwarning('警告', '请先停止服务器再关闭窗口!不然会存在进程残留。')
            return
        self.root.destroy()

    def update_ips(self, port):
        hostname = socket.gethostname()
        # 获取所有网络接口的IP地址
        ips = socket.getaddrinfo(hostname, None)
        unique_ips = set()
  
        # 收集所有唯一的IP地址
        for ip in ips:
            if ip[0] in (socket.AF_INET, socket.AF_INET6):  # 只处理IPv4和IPv6地址
                addr = ip[4][0]
                if addr not in unique_ips:
                    unique_ips.add(addr)
  
        # 清空并更新显示
        self.ip_text.delete(1.0, tk.END)
        self.ip_text.config(height=len(unique_ips))  # 根据实际地址数量调整高度
  
        # 分别显示IPv4和IPv6地址
        ipv4_addrs = [ip for ip in unique_ips if ':' not in ip]  # IPv4地址不包含冒号
        ipv6_addrs = [ip for ip in unique_ips if ':' in ip]
  
        # 先显示IPv4地址
        for addr in sorted(ipv4_addrs):
            url = f'http://{addr}:{port}'
            self.ip_text.insert(tk.END, url + '\n', 'link')
  
        # 再显示IPv6地址
        for addr in sorted(ipv6_addrs):
            url = f'http://[{addr}]:{port}'
            self.ip_text.insert(tk.END, url + '\n', 'link')  # IPv6地址需要用方括号括起来

    def open_url(self, event):
        # 获取点击位置的行号
        index = self.ip_text.index(f"@{event.x},{event.y}")
        line = self.ip_text.get(f"{index} linestart", f"{index} lineend")
        if line.strip():
            webbrowser.open(line.strip())

if __name__ == '__main__':
    root = tk.Tk()
    app = CaddyGUI(root)
    root.mainloop()
posted @ 2025-04-28 20:00  Ojox  阅读(42)  评论(0)    收藏  举报