自用 python写的 Rclone WebDav 挂载工具 挂载本地磁盘

自用 python写的 Rclone WebDav 挂载工具 挂载本地磁盘-狗窝源码站
自用 python写的 Rclone WebDav 挂载工具 挂载本地磁盘
此内容为免费资源,请登录后查看
G币0
免费资源
温馨提示
温馨提示:资源来源互联网搬运,遇到源码有授权加密以及后门,请放弃使用,本站不会添加任何后门。请勿相信源码里的广告QQ以及其他联系方式,谨慎被骗

Rclone  单 WebDav  图形化挂载工具

功能简单,  托盘图标,  自定义图标(同目录app.ico)
本地磁盘和网络磁盘位置可选
自定义显示容量大小(只是显示大小,和实际容量无关)
需要安装winfsp,同目录需要rclone.exe。
欢迎修改打包分享!

图片[1]-自用 python写的 Rclone WebDav 挂载工具 挂载本地磁盘-狗窝源码站
图片[2]-自用 python写的 Rclone WebDav 挂载工具 挂载本地磁盘-狗窝源码站
import sys
import os
import json
import ctypes
import subprocess
import time
from PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
    QListWidget, QListWidgetItem, QMessageBox, QSystemTrayIcon,
    QMenu, QAction, QStyle, QDialog, QFormLayout, QLineEdit,
    QComboBox, QSpinBox, QCheckBox, QLabel, QDialogButtonBox,
)
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFont, QIcon
 
DEBUG = True
def debug_print(*args, **kwargs):
    if DEBUG:
        print("[DEBUG]", *args, **kwargs)
 
RCLONE_EXE = "rclone.exe"
CONFIG_FILE = "mounts.json"
 
# ---------- 工具函数 ----------
def get_used_drives():
    drives = set()
    try:
        mask = ctypes.windll.kernel32.GetLogicalDrives()
        for i in range(26):
            if mask & (1 << i):
                drives.add(chr(ord('A') + i))
    except:
        for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
            if os.path.exists(f"{letter}:\"):
                drives.add(letter)
    return drives
 
def rclone_reveal(obscured: str) -> str:
    cmd = [RCLONE_EXE, "reveal", obscured]
    try:
        proc = subprocess.run(cmd, capture_output=True, text=True,
                              encoding='utf-8', errors='replace',
                              creationflags=subprocess.CREATE_NO_WINDOW)
        if proc.returncode == 0:
            return proc.stdout.strip()
    except:
        pass
    return ""
 
def get_remote_password(remote_name: str) -> str:
    ret, out, err = rclone_cmd(["config", "dump"])
    if ret != 0:
        return ""
    try:
        config = json.loads(out)
        remote_config = config.get(remote_name, {})
        obscured = remote_config.get("pass", "")
        if obscured:
            return rclone_reveal(obscured)
    except:
        pass
    return ""
 
def rclone_cmd(args, cwd=None):
    cmd = [RCLONE_EXE] + args
    debug_print("执行:", " ".join(cmd))
    try:
        proc = subprocess.run(
            cmd, capture_output=True, text=True, encoding='utf-8',
            errors='replace', cwd=cwd,
            creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
        )
        stdout = proc.stdout.strip() if proc.stdout else ""
        stderr = proc.stderr.strip() if proc.stderr else ""
        debug_print("返回码:", proc.returncode)
        if stdout:
            debug_print("stdout:", stdout[:200])
        if stderr:
            debug_print("stderr:", stderr[:200])
        return proc.returncode, stdout, stderr
    except Exception as e:
        debug_print("命令执行异常:", e)
        return -1, "", str(e)
 
# ---------- 编辑对话框 ----------
class MountEditDialog(QDialog):
    def __init__(self, parent=None, existing=None, existing_password=None):
        super().__init__(parent)
        self.existing = existing
        self.setWindowTitle("编辑挂载配置" if existing else "添加挂载配置")
        self.setFixedSize(420, 350)
        self.setStyleSheet(parent.styleSheet())
 
        layout = QFormLayout(self)
        layout.setSpacing(12)
 
        self.name_edit = QLineEdit()
        self.name_edit.setPlaceholderText("显示名称(也是 remote 名)")
        layout.addRow("配置名称:", self.name_edit)
 
        self.url_edit = QLineEdit()
        self.url_edit.setPlaceholderText("http://192.168.1.5:5244/alist/dav")
        layout.addRow("DAV 地址:", self.url_edit)
 
        self.user_edit = QLineEdit()
        self.user_edit.setPlaceholderText("用户名")
        layout.addRow("用户名:", self.user_edit)
 
        self.pass_edit = QLineEdit()
        self.pass_edit.setEchoMode(QLineEdit.Password)
        self.pass_edit.setPlaceholderText("密码(留空则不修改)" if existing else "密码")
        layout.addRow("密码:", self.pass_edit)
 
        drive_layout = QHBoxLayout()
        self.drive_combo = QComboBox()
        self.drive_combo.setFixedWidth(80)
        drive_layout.addWidget(self.drive_combo)
        drive_layout.addStretch()
        layout.addRow("盘符:", drive_layout)
 
        size_layout = QHBoxLayout()
        self.size_spin = QSpinBox()
        self.size_spin.setRange(1, 99999)
        self.size_spin.setValue(20)
        self.size_spin.setFixedWidth(100)
        size_layout.addWidget(self.size_spin)
        self.unit_combo = QComboBox()
        self.unit_combo.addItems(["M", "G", "T", "P", "E"])
        self.unit_combo.setCurrentText("G")
        self.unit_combo.setFixedWidth(80)
        size_layout.addWidget(self.unit_combo)
        size_layout.addStretch()
        layout.addRow("磁盘容量:", size_layout)
 
        self.network_check = QCheckBox("网络位置")
        self.network_check.setChecked(False)
        layout.addRow("", self.network_check)
 
        self.auto_mount_check = QCheckBox("启动时自动挂载")
        self.auto_mount_check.setChecked(False)
        layout.addRow("", self.auto_mount_check)
 
        # 自定义确认/取消按钮
        btn_confirm = QPushButton("确认")
        btn_confirm.setFixedSize(80, 30)
        btn_confirm.setStyleSheet("""
            QPushButton {
                background-color: #3498DB; color: white; border-radius: 4px; font-weight: bold;
            }
            QPushButton:hover {
                background-color: #2980B9;
            }
        """)
        btn_confirm.clicked.connect(self.accept)
 
        btn_cancel = QPushButton("取消")
        btn_cancel.setFixedSize(80, 30)
        btn_cancel.setStyleSheet("""
            QPushButton {
                background-color: #95A5A6; color: white; border-radius: 4px; font-weight: bold;
            }
            QPushButton:hover {
                background-color: #7F8C8D;
            }
        """)
        btn_cancel.clicked.connect(self.reject)
 
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        btn_layout.addWidget(btn_confirm)
        btn_layout.addWidget(btn_cancel)
        layout.addRow(btn_layout)
 
        if existing:
            self.name_edit.setText(existing.get("name", ""))
            self.url_edit.setText(existing.get("url", ""))
            self.user_edit.setText(existing.get("user", ""))
            self.size_spin.setValue(existing.get("size_value", 20))
            old_unit = existing.get("size_unit", "G")
            if old_unit.endswith("B"): old_unit = old_unit[0]
            self.unit_combo.setCurrentText(old_unit)
            self.network_check.setChecked(existing.get("network_mode", True))
            self.auto_mount_check.setChecked(existing.get("auto_mount", False))
 
            if existing_password is not None:
                self.pass_edit.setText(existing_password)
 
    def populate_drives(self, used_letters, reserved_letters=set()):
        self.drive_combo.clear()
        all_letters = set(chr(i) for i in range(ord('D'), ord('Z')+1))
        excluded = used_letters - reserved_letters
        available = sorted(all_letters - excluded)
        self.drive_combo.addItems([f"{d}:" for d in available])
        if self.existing and self.existing.get("drive"):
            idx = self.drive_combo.findText(self.existing["drive"])
            if idx >= 0:
                self.drive_combo.setCurrentIndex(idx)
 
    def get_data(self):
        return {
            "name": self.name_edit.text().strip(),
            "url": self.url_edit.text().strip(),
            "user": self.user_edit.text().strip(),
            "password": self.pass_edit.text(),
            "drive": self.drive_combo.currentText(),
            "size_value": self.size_spin.value(),
            "size_unit": self.unit_combo.currentText(),
            "network_mode": self.network_check.isChecked(),
            "auto_mount": self.auto_mount_check.isChecked()
        }
 
# ---------- 列表项部件 ----------
class MountItemWidget(QWidget):
    BTN_OFFSET = "margin-top: -3px;"
 
    @staticmethod
    def _btn_style(bg_color, extra=""):
        return f"{MountItemWidget.BTN_OFFSET} background-color: {bg_color}; color: white; border-radius:4px; font-weight:bold; {extra}"
 
    def __init__(self, entry, parent_app):
        super().__init__()
        self.entry = entry
        self.app = parent_app
        self.is_mounted = False
 
        layout = QHBoxLayout(self)
        layout.setContentsMargins(8, 6, 8, 6)
        layout.setSpacing(6)
        layout.setAlignment(Qt.AlignVCenter)
 
        drive = entry.get("drive", "") or "?"
        self.drive_label = QLabel(drive)
        self.drive_label.setFixedWidth(30)
        self.drive_label.setStyleSheet("font-weight: bold; color: #2c3e50;")
 
        mode = "网络" if entry.get("network_mode", True) else "本地"
        self.mode_label = QLabel(mode)
        self.mode_label.setFixedWidth(40)
        color = "#2980b9" if mode == "网络" else "#27ae60"
        self.mode_label.setStyleSheet(f"color: {color}; font-weight: bold;")
 
        self.name_label = QLabel(entry["name"])
        self.name_label.setFixedWidth(140)
        self.name_label.setStyleSheet("font-weight: bold; color: #2c3e50;")
 
        cap = f"{entry.get('size_value',20)}{entry.get('size_unit','G')}"
        self.cap_label = QLabel(cap)
        self.cap_label.setFixedWidth(60)
        self.cap_label.setStyleSheet("color: #7f8c8d;")
 
        self.btn_auto = QPushButton("自动挂载")
        self.btn_auto.setFixedSize(64, 30)
        self.btn_auto.setCheckable(True)
        self.btn_auto.setChecked(entry.get("auto_mount", False))
        self.btn_auto.clicked.connect(self.on_auto_toggled)
        self.update_auto_style()
 
        self.btn_action = QPushButton("启动")
        self.btn_action.setFixedSize(64, 30)
        self.btn_action.clicked.connect(self.on_action_clicked)
        self.btn_action.setStyleSheet(self._btn_style("#3498DB"))
 
        self.btn_edit = QPushButton("编辑")
        self.btn_edit.setFixedSize(64, 30)
        self.btn_edit.clicked.connect(self.edit_entry)
        self.btn_edit.setStyleSheet(self._btn_style("#3498DB"))
 
        self.btn_delete = QPushButton("删除")
        self.btn_delete.setFixedSize(64, 30)
        self.btn_delete.clicked.connect(self.delete_entry)
        self.btn_delete.setStyleSheet(self._btn_style("#E74C3C"))
 
        layout.addWidget(self.drive_label)
        layout.addWidget(self.mode_label)
        layout.addWidget(self.name_label)
        layout.addWidget(self.cap_label)
        layout.addWidget(self.btn_auto)
        layout.addWidget(self.btn_action)
        layout.addWidget(self.btn_edit)
        layout.addWidget(self.btn_delete)
 
    def on_auto_toggled(self):
        checked = self.btn_auto.isChecked()
        self.entry["auto_mount"] = checked
        self.app.update_entry_metadata(self.entry)
        self.update_auto_style()
 
    def update_auto_style(self):
        if self.btn_auto.isChecked():
            self.btn_auto.setText("自动:开")
            self.btn_auto.setStyleSheet(self._btn_style("#27AE60"))
        else:
            self.btn_auto.setText("自动:关")
            self.btn_auto.setStyleSheet(self._btn_style("#95A5A6"))
 
    def on_action_clicked(self):
        if not self.entry.get("drive"):
            QMessageBox.warning(self, "错误", "请先设置盘符!")
            return
        if self.is_mounted:
            self.open_drive()
        else:
            self.app.mount_entry(self.entry)
 
    def open_drive(self):
        drive = self.entry.get("drive")
        if not drive:
            QMessageBox.warning(self, "错误", "盘符未设置!")
            return
        for _ in range(30):
            if os.path.exists(drive + "\"):
                os.startfile(drive + "\")
                return
            time.sleep(0.1)
        QMessageBox.warning(self, "错误", f"盘符 {drive} 不可用,请等待挂载完成。")
 
    def edit_entry(self):
        self.app.edit_entry_dialog(self.entry)
 
    def delete_entry(self):
        self.app.delete_entry(self.entry)
 
# ---------- 主窗口 ----------
class RcloneTrayApp(QWidget):
    def __init__(self):
        super().__init__()
        self.base_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
        self.metadata = {}
        self.active_mounts = {}
        self.load_metadata()
        self.init_ui()
        self.init_tray()
        self.sync_from_rclone()
        self.refresh_list()
        QTimer.singleShot(1000, self.auto_mount_selected)
        debug_print("启动完成,程序目录:", self.base_dir)
 
    def get_app_icon(self):
        """优先使用 app.ico,找不到则使用系统标准图标"""
        # 1. 程序所在目录
        ico_path = os.path.join(self.base_dir, "app.ico")
        if os.path.exists(ico_path):
            return QIcon(ico_path)
 
        # 2. PyInstaller 打包后的释放目录
        if getattr(sys, 'frozen', False):
            meipass = getattr(sys, '_MEIPASS', None)
            if meipass:
                ico_path = os.path.join(meipass, "app.ico")
                if os.path.exists(ico_path):
                    return QIcon(ico_path)
 
        # 3. 兜底:Qt 内置图标
        return self.style().standardIcon(QStyle.SP_DriveNetIcon)
 
    def load_metadata(self):
        try:
            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                self.metadata = json.load(f)
        except:
            self.metadata = {}
 
    def save_metadata(self):
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump(self.metadata, f, indent=2, ensure_ascii=False)
 
    def update_entry_metadata(self, entry):
        self.metadata[entry["id"]] = {
            k: entry[k] for k in ("name", "url", "user", "drive",
                                 "size_value", "size_unit", "network_mode",
                                 "auto_mount")
        }
        self.save_metadata()
 
    def sync_from_rclone(self):
        ret, out, err = rclone_cmd(["listremotes"], cwd=self.base_dir)
        if ret != 0:
            debug_print("rclone listremotes 失败:", err)
            return
        remotes = [r.strip(":") for r in out.splitlines() if r.strip()]
        debug_print("发现远程配置:", remotes)
        for remote in remotes:
            if remote not in self.metadata:
                self.metadata[remote] = {
                    "name": remote, "url": "", "user": "",
                    "drive": "", "size_value": 20, "size_unit": "G",
                    "network_mode": True, "auto_mount": False
                }
        for remote in list(self.metadata.keys()):
            if remote not in remotes:
                del self.metadata[remote]
        self.save_metadata()
 
    def get_all_entries(self):
        entries = []
        for rid, meta in self.metadata.items():
            entry = {"id": rid}
            entry.update(meta)
            entries.append(entry)
        return entries
 
    def init_ui(self):
        self.setWindowTitle("Rclone WebDav 挂载管理")
        self.setFixedSize(620, 480)
        self.setWindowIcon(self.get_app_icon())
 
        # 全局样式
        self.setStyleSheet("""
            QWidget {
                background: #FFFFFF;
                font-family: "Microsoft YaHei", "微软雅黑";
                font-size: 13px;
                color: #2c3e50;
            }
            QLineEdit, QComboBox, QSpinBox {
                background: #FFFFFF;
                border: 1px solid #BDC3C7;
                border-radius: 4px;
                padding: 5px 8px;
                color: #2c3e50;
                selection-background-color: #3498DB;
            }
            QLineEdit:focus, QComboBox:focus, QSpinBox:focus {
                border-color: #3498DB;
            }
            QComboBox::drop-down {
                width: 0px;
                border: none;
            }
            QSpinBox::up-button, QSpinBox::down-button {
                width: 0px;
                border: none;
            }
            QCheckBox {
                spacing: 6px;
                color: #2c3e50;
            }
            QListWidget {
                background: transparent;
                border: none;
                outline: none;
            }
            QListWidget::item {
                background: #FFFFFF;
                border: none;
                border-bottom: 1px solid #E0E4E8;
                margin: 0px;
                padding: 0px;
            }
            QListWidget::item:selected {
                background: #EBF5FB;
            }
        """)
 
        main_layout = QVBoxLayout(self)
        main_layout.setContentsMargins(10, 10, 10, 10)
        main_layout.setSpacing(6)
 
        top_layout = QHBoxLayout()
        top_layout.setSpacing(10)
 
        self.btn_add = QPushButton("+ 添加配置")
        self.btn_add.setFixedSize(120, 30)
        self.btn_add.setStyleSheet(
            "QPushButton { background-color: #3498DB; color: white; border-radius: 4px; font-weight: bold; }"
            "QPushButton:hover { background-color: #2980B9; }"
        )
        self.btn_add.clicked.connect(self.add_entry_dialog)
 
        self.btn_disconnect = QPushButton("断开挂载")
        self.btn_disconnect.setFixedSize(120, 30)
        self.btn_disconnect.setStyleSheet(
            "QPushButton { background-color: #E74C3C; color: white; border-radius: 4px; font-weight: bold; }"
            "QPushButton:hover { background-color: #C0392B; }"
        )
        self.btn_disconnect.clicked.connect(self.disconnect_all_mounts)
 
        top_layout.addWidget(self.btn_add)
        top_layout.addWidget(self.btn_disconnect)
        top_layout.addStretch()
        main_layout.addLayout(top_layout)
 
        self.list_widget = QListWidget()
        self.list_widget.setSpacing(0)
        self.list_widget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        main_layout.addWidget(self.list_widget)
 
    def init_tray(self):
        tray_icon = self.get_app_icon()
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(tray_icon)
        tray_menu = QMenu()
        show_act = QAction("显示窗口", self, triggered=self.show_window)
        exit_act = QAction("退出", self, triggered=self.quit_app)
        tray_menu.addAction(show_act)
        tray_menu.addAction(exit_act)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.activated.connect(self.on_tray_activated)
        self.tray_icon.show()
 
    def show_window(self):
        self.showNormal()
        self.activateWindow()
        self.raise_()
 
    def close_to_tray(self):
        self.hide()
 
    def quit_app(self):
        subprocess.run("taskkill /IM rclone.exe /F", shell=True,
                       capture_output=True, creationflags=subprocess.CREATE_NO_WINDOW)
        self.active_mounts.clear()
        self.tray_icon.hide()
        QApplication.quit()
 
    def on_tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.show_window()
 
    def closeEvent(self, event):
        event.ignore()
        self.hide()
 
    def disconnect_all_mounts(self):
        subprocess.run("taskkill /IM rclone.exe /F", shell=True,
                       capture_output=True, creationflags=subprocess.CREATE_NO_WINDOW)
        self.active_mounts.clear()
        self.refresh_list()
        QMessageBox.information(self, "提示", "所有挂载已断开")
 
    def refresh_list(self):
        self.list_widget.clear()
        entries = self.get_all_entries()
        for entry in entries:
            item = QListWidgetItem()
            widget = MountItemWidget(entry, self)
            item.setSizeHint(widget.sizeHint())
            self.list_widget.addItem(item)
            self.list_widget.setItemWidget(item, widget)
 
            auto_on = entry.get("auto_mount", False)
            widget.btn_auto.setChecked(auto_on)
            widget.update_auto_style()
 
            is_mounted = (entry["drive"] in self.active_mounts)
            widget.is_mounted = is_mounted
            if is_mounted:
                widget.btn_action.setText("打开")
                widget.btn_action.setStyleSheet(MountItemWidget._btn_style("#27AE60"))
            else:
                widget.btn_action.setText("启动")
                widget.btn_action.setStyleSheet(MountItemWidget._btn_style("#3498DB"))
 
    def get_used_drive_letters(self):
        used = get_used_drives()
        for entry in self.get_all_entries():
            if entry.get("drive"):
                used.add(entry["drive"][0])
        return used
 
    def auto_mount_selected(self):
        auto_entries = [e for e in self.get_all_entries() if e.get("auto_mount") and e.get("drive")]
        debug_print(f"自动挂载条目数: {len(auto_entries)}")
        for entry in auto_entries:
            debug_print(f"自动挂载: {entry['name']} -> {entry['drive']}")
            self.mount_entry(entry)
 
    # ---------- 增删改 ----------
    def add_entry_dialog(self):
        used = self.get_used_drive_letters()
        dlg = MountEditDialog(self)
        dlg.populate_drives(used)
        if dlg.exec_() != QDialog.Accepted:
            return
        data = dlg.get_data()
        if not data["name"] or not data["url"] or not data["user"] or not data["password"]:
            QMessageBox.warning(self, "警告", "必填项不能为空")
            return
 
        remote_name = data["name"]
        if remote_name in self.metadata:
            QMessageBox.warning(self, "警告", "配置名称已存在")
            return
 
        ret, out, err = rclone_cmd([
            "config", "create", remote_name, "webdav",
            "vendor=other", f"url={data['url']}", f"user={data['user']}",
            f"pass={data['password']}", "--obscure"
        ], cwd=self.base_dir)
        if ret != 0:
            QMessageBox.warning(self, "创建失败", err)
            return
 
        self.metadata[remote_name] = {
            "name": data["name"], "url": data["url"], "user": data["user"],
            "drive": data["drive"], "size_value": data["size_value"],
            "size_unit": data["size_unit"], "network_mode": data["network_mode"],
            "auto_mount": data["auto_mount"]
        }
        self.save_metadata()
        self.refresh_list()
 
    def edit_entry_dialog(self, entry):
        used = self.get_used_drive_letters()
        reserved = set(entry["drive"][0]) if entry["drive"] else set()
        existing_password = get_remote_password(entry["id"])
 
        dlg = MountEditDialog(self, existing=entry, existing_password=existing_password)
        dlg.populate_drives(used, reserved)
        if dlg.exec_() != QDialog.Accepted:
            return
        data = dlg.get_data()
        if not data["name"] or not data["url"] or not data["user"]:
            QMessageBox.warning(self, "警告", "名称、地址、用户名不能为空")
            return
 
        old_remote = entry["id"]
        new_name = data["name"]
        name_changed = (new_name != old_remote)
        new_pass = data["password"]
 
        need_recreate = name_changed or bool(new_pass)
 
        if need_recreate:
            if name_changed and new_name in self.metadata:
                QMessageBox.warning(self, "警告", "新配置名称已存在")
                return
 
            actual_pass = new_pass if new_pass else existing_password
            if not actual_pass:
                QMessageBox.warning(self, "错误", "无法获取原始密码,请重新输入密码")
                return
 
            if name_changed:
                rclone_cmd(["config", "delete", old_remote], cwd=self.base_dir)
 
            ret, _, err = rclone_cmd([
                "config", "create", new_name, "webdav",
                "vendor=other", f"url={data['url']}", f"user={data['user']}",
                f"pass={actual_pass}", "--obscure"
            ], cwd=self.base_dir)
            if ret != 0:
                QMessageBox.warning(self, "更新失败", err)
                return
 
        if name_changed:
            del self.metadata[old_remote]
        self.metadata[new_name] = {
            "name": data["name"], "url": data["url"], "user": data["user"],
            "drive": data["drive"], "size_value": data["size_value"],
            "size_unit": data["size_unit"], "network_mode": data["network_mode"],
            "auto_mount": data["auto_mount"]
        }
        self.save_metadata()
 
        if entry["drive"] in self.active_mounts:
            self.unmount_entry(entry)
        self.refresh_list()
 
    def delete_entry(self, entry):
        if QMessageBox.question(self, "确认", f"删除 '{entry['name']}'?") != QMessageBox.Yes:
            return
        if entry["drive"] in self.active_mounts:
            self.unmount_entry(entry)
        rclone_cmd(["config", "delete", entry["id"]], cwd=self.base_dir)
        del self.metadata[entry["id"]]
        self.save_metadata()
        self.refresh_list()
 
    # ---------- 挂载/卸载 ----------
    def mount_entry(self, entry):
        drive = entry.get("drive")
        if not drive:
            QMessageBox.warning(self, "挂载失败", "未设置盘符")
            return
 
        if drive in self.active_mounts:
            self.unmount_entry(entry)
 
        cache_subdir = os.path.join(self.base_dir, "Temp", entry["name"])
        os.makedirs(cache_subdir, exist_ok=True)
 
        total_size = f"{entry.get('size_value',20)}{entry.get('size_unit','G')}"
        args = [
            "mount", f"{entry['id']}:", drive,
            "--cache-dir", f"./Temp/{entry['name']}",
            "--vfs-cache-mode", "full",
            "--allow-non-empty",
            "--dir-cache-time", "5s",
            "--allow-other",
            "--vfs-disk-space-total-size", total_size
        ]
        if entry.get("network_mode", True):
            args.append("--network-mode")
 
        debug_print("挂载命令:", f'rclone {" ".join(args)}')
        try:
            proc = subprocess.Popen(
                [RCLONE_EXE] + args,
                cwd=self.base_dir,
                creationflags=subprocess.CREATE_NO_WINDOW
            )
            self.active_mounts[drive] = proc
            self.refresh_list()
            debug_print(f"挂载成功: {drive}")
        except Exception as e:
            QMessageBox.warning(self, "挂载失败", str(e))
 
    def unmount_entry(self, entry):
        drive = entry["drive"]
        if drive not in self.active_mounts:
            return
        proc = self.active_mounts.pop(drive)
        rclone_cmd(["unmount", drive], cwd=self.base_dir)
        if proc.poll() is None:
            try:
                proc.terminate()
                proc.wait(timeout=5)
            except:
                proc.kill()
        self.refresh_list()
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)
    app.setFont(QFont("Microsoft YaHei", 9))
    window = RcloneTrayApp()
    window.show()
    sys.exit(app.exec_())
© 版权声明
THE END
喜欢就支持一下吧
点赞10赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容