2025-10-29 22:20:21 +08:00
|
|
|
|
# coding: utf-8
|
|
|
|
|
|
|
2025-11-02 19:17:20 +08:00
|
|
|
|
import re
|
2025-10-29 22:20:21 +08:00
|
|
|
|
from loguru import logger
|
|
|
|
|
|
from PyQt6.QtCore import Qt, pyqtSignal
|
|
|
|
|
|
from PyQt6.QtGui import QPixmap
|
|
|
|
|
|
from PyQt6.QtWidgets import QHBoxLayout
|
|
|
|
|
|
from qfluentwidgets import Action, BodyLabel, CardWidget, ImageLabel, InfoBar, InfoBarPosition, MenuAnimationType, \
|
|
|
|
|
|
MessageBox, PushButton, RoundMenu, StrongBodyLabel
|
|
|
|
|
|
from qfluentwidgets import FluentIcon as FIF
|
|
|
|
|
|
|
2025-11-02 19:17:20 +08:00
|
|
|
|
from app.core import (DeleteFileThread, RenameFileThread, formatDate, formatSize, getFileIcon, lang, signalBus)
|
2025-10-29 22:20:21 +08:00
|
|
|
|
from app.view.widgets.share_file_messageBox import ShareFileMessageBox
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FileCard(CardWidget):
|
|
|
|
|
|
def __init__(self, _id, fileName, fileType, path, date, size, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self._id = _id
|
|
|
|
|
|
self.fileName = fileName
|
|
|
|
|
|
self.fileSize = size
|
|
|
|
|
|
self.changeTime = date
|
|
|
|
|
|
self.fileType = fileType
|
|
|
|
|
|
self.filePath = path
|
|
|
|
|
|
self.setFixedHeight(50)
|
|
|
|
|
|
|
|
|
|
|
|
self.iconLabel = ImageLabel(self)
|
|
|
|
|
|
|
|
|
|
|
|
self.fileNameLabel = StrongBodyLabel(self.fileName, self)
|
|
|
|
|
|
self.fileSizeLabel = BodyLabel(formatSize(self.fileSize), self)
|
|
|
|
|
|
self.changeTimeLabel = BodyLabel(formatDate(self.changeTime), self)
|
|
|
|
|
|
|
|
|
|
|
|
self.hBoxLayout = QHBoxLayout(self)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.iconLabel)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.fileNameLabel)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.fileSizeLabel)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.changeTimeLabel)
|
|
|
|
|
|
|
|
|
|
|
|
self.hBoxLayout.setStretch(0, 1)
|
|
|
|
|
|
self.hBoxLayout.setStretch(1, 2)
|
|
|
|
|
|
self.hBoxLayout.setStretch(2, 1)
|
|
|
|
|
|
|
|
|
|
|
|
self.hBoxLayout.setContentsMargins(10, 10, 10, 10)
|
|
|
|
|
|
self.hBoxLayout.setSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
self.loadIcon()
|
|
|
|
|
|
|
|
|
|
|
|
if self.fileType == "dir":
|
|
|
|
|
|
self.clicked.connect(self.dirClicked)
|
|
|
|
|
|
|
|
|
|
|
|
self.suffix = fileName.split(".")[-1].lower()
|
|
|
|
|
|
|
|
|
|
|
|
def mousePressEvent(self, event):
|
|
|
|
|
|
"""重写鼠标点击事件,区分左右键"""
|
|
|
|
|
|
if event.button() == Qt.MouseButton.RightButton:
|
|
|
|
|
|
# 使用globalPosition()获取全局位置并转换为适合菜单显示的坐标
|
|
|
|
|
|
global_pos = event.globalPosition().toPoint()
|
|
|
|
|
|
if self.fileType == "file":
|
|
|
|
|
|
self.showFileContextMenu(global_pos)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.showFolderContextMenu(global_pos)
|
|
|
|
|
|
else:
|
|
|
|
|
|
super().mousePressEvent(event)
|
|
|
|
|
|
|
|
|
|
|
|
def loadIcon(self):
|
|
|
|
|
|
icon_name = getFileIcon(self.fileType, self.fileName)
|
|
|
|
|
|
self.iconLabel.setImage(QPixmap(f":app/icons/{icon_name}"))
|
|
|
|
|
|
self.iconLabel.scaledToHeight(25)
|
|
|
|
|
|
self.iconLabel.scaledToWidth(25)
|
|
|
|
|
|
|
|
|
|
|
|
def dirClicked(self):
|
|
|
|
|
|
if self.filePath == "/":
|
|
|
|
|
|
paths = "/" + self.fileName
|
|
|
|
|
|
else:
|
|
|
|
|
|
paths = f"{self.filePath}/{self.fileName}"
|
|
|
|
|
|
signalBus.dirOpenSignal.emit(paths)
|
|
|
|
|
|
|
|
|
|
|
|
def selfPreview(self):
|
|
|
|
|
|
if self.fileType == "file" and self.suffix in [
|
|
|
|
|
|
"jpg",
|
|
|
|
|
|
"png",
|
|
|
|
|
|
"jpeg",
|
|
|
|
|
|
"bmp",
|
|
|
|
|
|
"gif",
|
|
|
|
|
|
]:
|
|
|
|
|
|
signalBus.imagePreviewSignal.emit(self._id)
|
2025-10-29 22:27:39 +08:00
|
|
|
|
if self.fileType == "file" and self.suffix in ["txt", "py", "md", "js", "html"]:
|
2025-10-29 22:20:21 +08:00
|
|
|
|
signalBus.txtPreviewSignal.emit(self._id)
|
|
|
|
|
|
|
|
|
|
|
|
def downloadFile(self):
|
|
|
|
|
|
if self.fileType == "file":
|
|
|
|
|
|
# 构建Cloudreve V4 API所需的正确路径格式
|
|
|
|
|
|
# 确保不会出现重复的前缀和文件名
|
|
|
|
|
|
if self.filePath == "/":
|
|
|
|
|
|
# 根目录情况
|
|
|
|
|
|
full_path = f"cloudreve://my/{self.fileName}"
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 子目录情况,确保正确拼接路径
|
2025-11-01 20:14:35 +08:00
|
|
|
|
# 清理路径,避免重复的斜杠和前缀
|
2025-10-29 22:20:21 +08:00
|
|
|
|
clean_path = self.filePath.lstrip("/")
|
2025-11-01 20:14:35 +08:00
|
|
|
|
# 确保路径中不包含cloudreve://my前缀
|
|
|
|
|
|
clean_path = clean_path.replace("cloudreve://my/", "")
|
2025-10-29 22:20:21 +08:00
|
|
|
|
full_path = f"cloudreve://my/{clean_path}/{self.fileName}"
|
2025-11-01 20:14:35 +08:00
|
|
|
|
|
|
|
|
|
|
# 确保路径格式正确,移除重复的前缀
|
2025-10-29 22:20:21 +08:00
|
|
|
|
full_path = full_path.replace("cloudreve://my/cloudreve://my", "cloudreve://my")
|
2025-11-01 20:14:35 +08:00
|
|
|
|
|
|
|
|
|
|
# 更健壮地处理重复文件名的情况
|
|
|
|
|
|
# 分割路径并去重
|
|
|
|
|
|
path_parts = full_path.split('/')
|
|
|
|
|
|
if len(path_parts) > 1:
|
|
|
|
|
|
# 检查最后一个部分是否是文件名
|
|
|
|
|
|
if path_parts[-1] == self.fileName:
|
|
|
|
|
|
# 检查倒数第二个部分是否也是文件名
|
|
|
|
|
|
if len(path_parts) > 2 and path_parts[-2] == self.fileName:
|
|
|
|
|
|
# 移除重复的文件名部分
|
|
|
|
|
|
path_parts.pop(-2)
|
|
|
|
|
|
full_path = '/'.join(path_parts)
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
|
|
|
|
|
signalBus.addDownloadFileTask.emit(
|
|
|
|
|
|
f"own.{self.suffix}", self.fileName, full_path
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def showFileContextMenu(self, pos):
|
|
|
|
|
|
"""显示上下文菜单"""
|
|
|
|
|
|
menu = RoundMenu(parent=self)
|
|
|
|
|
|
|
|
|
|
|
|
menu.addActions(
|
|
|
|
|
|
[
|
|
|
|
|
|
Action(
|
|
|
|
|
|
FIF.DOWNLOAD, lang("下载"), triggered=lambda: self.downloadFile()
|
|
|
|
|
|
),
|
|
|
|
|
|
Action(
|
|
|
|
|
|
FIF.PROJECTOR, lang("预览"), triggered=lambda: self.selfPreview()
|
|
|
|
|
|
),
|
2025-11-02 19:17:20 +08:00
|
|
|
|
Action(
|
|
|
|
|
|
FIF.EDIT, lang("重命名"), triggered=lambda: self.renameSelf()
|
|
|
|
|
|
),
|
2025-10-29 22:20:21 +08:00
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
menu.addSeparator()
|
|
|
|
|
|
menu.addActions(
|
|
|
|
|
|
[
|
|
|
|
|
|
Action(FIF.DELETE, lang("删除"), triggered=lambda: self.deleteSelf()),
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
menu.exec(pos, aniType=MenuAnimationType.DROP_DOWN)
|
|
|
|
|
|
|
|
|
|
|
|
def showFolderContextMenu(self, pos):
|
|
|
|
|
|
"""显示上下文菜单"""
|
|
|
|
|
|
menu = RoundMenu(parent=self)
|
|
|
|
|
|
|
|
|
|
|
|
menu.addActions(
|
|
|
|
|
|
[
|
|
|
|
|
|
Action(FIF.DOWNLOAD, lang("进入"), triggered=lambda: self.dirClicked()),
|
2025-11-02 19:17:20 +08:00
|
|
|
|
Action(
|
|
|
|
|
|
FIF.EDIT, lang("重命名"), triggered=lambda: self.renameSelf()
|
|
|
|
|
|
),
|
2025-10-29 22:20:21 +08:00
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
menu.addSeparator()
|
|
|
|
|
|
menu.addActions(
|
|
|
|
|
|
[
|
|
|
|
|
|
Action(FIF.DELETE, lang("删除"), triggered=lambda: self.deleteSelf()),
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
menu.exec(pos, aniType=MenuAnimationType.DROP_DOWN)
|
|
|
|
|
|
|
|
|
|
|
|
def deleteSelf(self):
|
|
|
|
|
|
w = MessageBox(
|
|
|
|
|
|
"确认删除",
|
|
|
|
|
|
f"你确定要删除{self.fileName}吗?\n删除后不可恢复噢!",
|
|
|
|
|
|
parent=self.window(),
|
|
|
|
|
|
)
|
|
|
|
|
|
if w.exec():
|
2025-11-01 20:14:35 +08:00
|
|
|
|
# 构建Cloudreve V4 API所需的正确路径格式
|
2025-11-02 19:17:20 +08:00
|
|
|
|
# 确保路径格式规范,避免Path not exist错误
|
|
|
|
|
|
# 只使用路径中的目录部分,不包含文件名,确保不会重复
|
|
|
|
|
|
dir_path = self.filePath
|
|
|
|
|
|
if dir_path == "/":
|
2025-11-01 20:14:35 +08:00
|
|
|
|
# 根目录情况
|
|
|
|
|
|
full_path = f"cloudreve://my/{self.fileName}"
|
|
|
|
|
|
else:
|
2025-11-02 19:17:20 +08:00
|
|
|
|
# 子目录情况,确保只使用目录路径
|
|
|
|
|
|
# 清理路径,移除协议前缀
|
|
|
|
|
|
clean_dir = dir_path
|
|
|
|
|
|
if clean_dir.startswith("cloudreve://my/"):
|
|
|
|
|
|
clean_dir = clean_dir[15:] # 移除 "cloudreve://my/"
|
|
|
|
|
|
elif clean_dir.startswith("/"):
|
|
|
|
|
|
clean_dir = clean_dir[1:] # 移除前导斜杠
|
2025-11-01 20:14:35 +08:00
|
|
|
|
|
2025-11-02 19:17:20 +08:00
|
|
|
|
# 确保路径中间没有多余的斜杠
|
|
|
|
|
|
clean_dir = '/'.join([part for part in clean_dir.split('/') if part])
|
2025-11-01 20:14:35 +08:00
|
|
|
|
|
2025-11-02 19:17:20 +08:00
|
|
|
|
# 构建完整URI,只包含目录路径和文件名一次
|
|
|
|
|
|
full_path = f"cloudreve://my/{clean_dir}/{self.fileName}"
|
|
|
|
|
|
|
|
|
|
|
|
# 检查并移除可能的重复文件名部分
|
|
|
|
|
|
# 避免同时包含编码形式和原始形式的文件名
|
|
|
|
|
|
import re
|
|
|
|
|
|
# 查找可能的编码形式+原始形式的重复模式
|
2025-11-01 20:14:35 +08:00
|
|
|
|
path_parts = full_path.split('/')
|
2025-11-02 19:17:20 +08:00
|
|
|
|
if len(path_parts) > 3:
|
|
|
|
|
|
# 检查最后两个部分是否可能是编码和原始形式的关系
|
|
|
|
|
|
potential_encoding = path_parts[-2]
|
|
|
|
|
|
original = path_parts[-1]
|
|
|
|
|
|
# 如果倒数第二个部分看起来像URL编码且最后一个部分是中文
|
|
|
|
|
|
if re.match(r'^%[0-9A-Fa-f]{2}%[0-9A-Fa-f]{2}%[0-9A-Fa-f]{2}%[0-9A-Fa-f]{2}$', potential_encoding) and any('\u4e00'-'\u9fff' in c for c in original):
|
|
|
|
|
|
# 移除倒数第二个部分,避免重复
|
|
|
|
|
|
path_parts.pop(-2)
|
|
|
|
|
|
full_path = '/'.join(path_parts)
|
|
|
|
|
|
|
|
|
|
|
|
# 最终清理
|
|
|
|
|
|
full_path = full_path.replace("//", "/") # 移除多余的斜杠
|
|
|
|
|
|
|
|
|
|
|
|
# 记录构建的路径用于调试
|
|
|
|
|
|
print(f"[DEBUG] 重命名路径: {full_path}")
|
2025-11-01 20:14:35 +08:00
|
|
|
|
|
|
|
|
|
|
self.deleteThread = DeleteFileThread(full_path, self.fileType)
|
2025-10-29 22:20:21 +08:00
|
|
|
|
self.deleteThread.successDelete.connect(self.deleteSuccess)
|
|
|
|
|
|
self.deleteThread.errorDelete.connect(self.deleteError)
|
|
|
|
|
|
self.deleteThread.start()
|
|
|
|
|
|
else:
|
|
|
|
|
|
InfoBar.info(
|
|
|
|
|
|
"提示",
|
|
|
|
|
|
"删除已取消",
|
|
|
|
|
|
Qt.Orientation.Horizontal,
|
|
|
|
|
|
True,
|
|
|
|
|
|
1000,
|
|
|
|
|
|
InfoBarPosition.TOP_RIGHT,
|
|
|
|
|
|
self.window(),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def deleteSuccess(self):
|
|
|
|
|
|
InfoBar.success(
|
|
|
|
|
|
"成功",
|
|
|
|
|
|
"成功删除",
|
|
|
|
|
|
Qt.Orientation.Horizontal,
|
|
|
|
|
|
True,
|
|
|
|
|
|
1000,
|
|
|
|
|
|
InfoBarPosition.TOP_RIGHT,
|
|
|
|
|
|
self.window(),
|
|
|
|
|
|
)
|
|
|
|
|
|
signalBus.refreshFolderListSignal.emit()
|
|
|
|
|
|
|
|
|
|
|
|
def deleteError(self, error_msg):
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
"失败",
|
|
|
|
|
|
"删除失败",
|
|
|
|
|
|
Qt.Orientation.Horizontal,
|
|
|
|
|
|
True,
|
|
|
|
|
|
1000,
|
|
|
|
|
|
InfoBarPosition.TOP_RIGHT,
|
|
|
|
|
|
self.window(),
|
|
|
|
|
|
)
|
|
|
|
|
|
logger.error(f"删除文件失败:{error_msg}")
|
2025-11-02 19:17:20 +08:00
|
|
|
|
|
|
|
|
|
|
def renameSelf(self):
|
|
|
|
|
|
"""重命名文件或文件夹"""
|
|
|
|
|
|
# 使用输入对话框获取新名称
|
|
|
|
|
|
from qfluentwidgets import LineEdit, MessageBoxBase, SubtitleLabel
|
|
|
|
|
|
from PyQt6.QtCore import Qt
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
|
|
# 创建自定义对话框
|
|
|
|
|
|
class RenameMessageBox(MessageBoxBase):
|
|
|
|
|
|
"""自定义重命名对话框"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None, initial_name=""):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.titleLabel = SubtitleLabel(f"{lang("请输入新名称")}:")
|
|
|
|
|
|
self.lineEdit = LineEdit()
|
|
|
|
|
|
self.lineEdit.setText(initial_name)
|
|
|
|
|
|
|
|
|
|
|
|
# 选择文件名部分(不包括扩展名)
|
|
|
|
|
|
if "." in initial_name:
|
|
|
|
|
|
name_part = initial_name.rsplit(".", 1)[0]
|
|
|
|
|
|
self.lineEdit.setSelection(0, len(name_part))
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.lineEdit.selectAll()
|
|
|
|
|
|
|
|
|
|
|
|
# 将组件添加到布局中
|
|
|
|
|
|
self.viewLayout.addWidget(self.titleLabel)
|
|
|
|
|
|
self.viewLayout.addWidget(self.lineEdit)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置对话框的最小宽度
|
|
|
|
|
|
self.widget.setMinimumWidth(400)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置窗口标题
|
|
|
|
|
|
self.setWindowTitle(lang("重命名"))
|
|
|
|
|
|
|
|
|
|
|
|
# 创建并显示对话框
|
|
|
|
|
|
dlg = RenameMessageBox(self.window(), self.fileName)
|
|
|
|
|
|
if dlg.exec():
|
|
|
|
|
|
new_name = dlg.lineEdit.text().strip()
|
|
|
|
|
|
|
|
|
|
|
|
# 验证文件名
|
|
|
|
|
|
if not new_name:
|
|
|
|
|
|
InfoBar.warning(
|
|
|
|
|
|
lang("警告"),
|
|
|
|
|
|
lang("文件名不能为空"),
|
|
|
|
|
|
Qt.Orientation.Horizontal,
|
|
|
|
|
|
True,
|
|
|
|
|
|
1000,
|
|
|
|
|
|
InfoBarPosition.TOP_RIGHT,
|
|
|
|
|
|
self.window(),
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 检查文件名是否包含无效字符
|
|
|
|
|
|
if re.search(r'[<>"/\\|?*]', new_name):
|
|
|
|
|
|
InfoBar.warning(
|
|
|
|
|
|
lang("警告"),
|
|
|
|
|
|
lang("文件名包含无效字符"),
|
|
|
|
|
|
Qt.Orientation.Horizontal,
|
|
|
|
|
|
True,
|
|
|
|
|
|
1000,
|
|
|
|
|
|
InfoBarPosition.TOP_RIGHT,
|
|
|
|
|
|
self.window(),
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 检查文件名是否与当前目录中的其他文件重复
|
|
|
|
|
|
# 获取当前目录下的所有文件名称
|
|
|
|
|
|
current_folder = self.filePath
|
|
|
|
|
|
# 从父窗口获取文件列表(假设父窗口有fileList属性)
|
|
|
|
|
|
parent_window = self.window()
|
|
|
|
|
|
if hasattr(parent_window, 'fileList'):
|
|
|
|
|
|
# 遍历文件列表,检查是否有相同名称的文件(排除当前正在重命名的文件)
|
|
|
|
|
|
for file_item in parent_window.fileList:
|
|
|
|
|
|
# 跳过当前正在重命名的文件
|
|
|
|
|
|
if hasattr(file_item, 'fileName') and file_item.fileName == self.fileName:
|
|
|
|
|
|
continue
|
|
|
|
|
|
# 检查是否有同名文件
|
|
|
|
|
|
if hasattr(file_item, 'fileName') and file_item.fileName == new_name:
|
|
|
|
|
|
InfoBar.warning(
|
|
|
|
|
|
lang("警告"),
|
|
|
|
|
|
lang("文件名已存在"),
|
|
|
|
|
|
Qt.Orientation.Horizontal,
|
|
|
|
|
|
True,
|
|
|
|
|
|
1000,
|
|
|
|
|
|
InfoBarPosition.TOP_RIGHT,
|
|
|
|
|
|
self.window(),
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 构建Cloudreve V4 API所需的正确路径格式
|
|
|
|
|
|
# 确保路径格式规范,避免Path not exist错误
|
|
|
|
|
|
# 只使用路径中的目录部分,不包含文件名
|
|
|
|
|
|
if self.filePath == "/":
|
|
|
|
|
|
# 根目录情况,URI只包含根目录路径
|
|
|
|
|
|
full_path = "cloudreve://my" # 直接使用正确的双斜杠格式
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 子目录情况,确保只使用目录路径,不包含文件名
|
|
|
|
|
|
# 清理路径,移除协议前缀
|
|
|
|
|
|
clean_path = self.filePath
|
|
|
|
|
|
if clean_path.startswith("cloudreve://my/"):
|
|
|
|
|
|
clean_path = clean_path[15:] # 移除 "cloudreve://my/"
|
|
|
|
|
|
elif clean_path.startswith("/"):
|
|
|
|
|
|
clean_path = clean_path[1:] # 移除前导斜杠
|
|
|
|
|
|
|
|
|
|
|
|
# 构建完整URI,只包含目录路径
|
|
|
|
|
|
full_path = f"cloudreve://my/{clean_path}" # 直接使用正确的双斜杠格式
|
|
|
|
|
|
|
|
|
|
|
|
# 强制确保协议前缀格式正确,使用双斜杠
|
|
|
|
|
|
# 直接替换所有可能的单斜杠格式
|
|
|
|
|
|
full_path = full_path.replace("cloudreve:/my", "cloudreve://my")
|
|
|
|
|
|
full_path = full_path.replace("cloudreve:/", "cloudreve://")
|
|
|
|
|
|
# 再次确认格式完全正确
|
|
|
|
|
|
if not full_path.startswith("cloudreve://"):
|
|
|
|
|
|
# 如果格式仍然不正确,重新构建整个URI
|
|
|
|
|
|
path_part = full_path.replace("cloudreve:", "").lstrip("/")
|
|
|
|
|
|
full_path = f"cloudreve://{path_part}"
|
|
|
|
|
|
|
|
|
|
|
|
# 不再需要处理文件名重复,因为URI中只包含目录路径
|
|
|
|
|
|
|
|
|
|
|
|
# 最终清理
|
|
|
|
|
|
full_path = full_path.replace("//", "/") # 移除多余的斜杠
|
|
|
|
|
|
|
|
|
|
|
|
# 记录构建的路径用于调试
|
|
|
|
|
|
print(f"[DEBUG] 重命名路径: {full_path}")
|
|
|
|
|
|
|
|
|
|
|
|
# 调用重命名线程
|
|
|
|
|
|
self.renameThread = RenameFileThread(full_path, new_name, self.fileType)
|
|
|
|
|
|
self.renameThread.successRename.connect(self.renameSuccess)
|
|
|
|
|
|
self.renameThread.errorRename.connect(self.renameError)
|
|
|
|
|
|
self.renameThread.start()
|
|
|
|
|
|
|
|
|
|
|
|
def renameSuccess(self):
|
|
|
|
|
|
InfoBar.success(
|
|
|
|
|
|
lang("成功"),
|
|
|
|
|
|
lang("重命名成功"),
|
|
|
|
|
|
Qt.Orientation.Horizontal,
|
|
|
|
|
|
True,
|
|
|
|
|
|
1000,
|
|
|
|
|
|
InfoBarPosition.TOP_RIGHT,
|
|
|
|
|
|
self.window(),
|
|
|
|
|
|
)
|
|
|
|
|
|
# 刷新文件夹列表
|
|
|
|
|
|
signalBus.refreshFolderListSignal.emit()
|
|
|
|
|
|
|
|
|
|
|
|
def renameError(self, error_msg):
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
lang("失败"),
|
|
|
|
|
|
f"{lang("重命名失败")}: {error_msg}",
|
|
|
|
|
|
Qt.Orientation.Horizontal,
|
|
|
|
|
|
True,
|
|
|
|
|
|
1000,
|
|
|
|
|
|
InfoBarPosition.TOP_RIGHT,
|
|
|
|
|
|
self.window(),
|
|
|
|
|
|
)
|
|
|
|
|
|
logger.error(f"重命名文件失败:{error_msg}")
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
|
|
|
|
|
def contextMenuEvent(self, e):
|
|
|
|
|
|
"""重写上下文菜单事件,确保只有右键点击才会触发"""
|
|
|
|
|
|
pass
|
2025-11-01 20:14:35 +08:00
|
|
|
|
|
|
|
|
|
|
def mouseDoubleClickEvent(self, event):
|
|
|
|
|
|
"""重写鼠标双击事件,双击文件夹时进入文件夹"""
|
|
|
|
|
|
if self.fileType == "dir":
|
|
|
|
|
|
self.dirClicked()
|
|
|
|
|
|
# 阻止事件继续传播,避免可能的干扰
|
|
|
|
|
|
event.accept()
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 非文件夹时调用父类处理
|
|
|
|
|
|
super().mouseDoubleClickEvent(event)
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ShareFileCard(CardWidget):
|
|
|
|
|
|
def __init__(self, data, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self._id = data["key"]
|
|
|
|
|
|
self.fileName = data["source"]["name"]
|
|
|
|
|
|
self.fileSize = data["source"]["size"]
|
|
|
|
|
|
self.changeTime = data["create_date"]
|
|
|
|
|
|
self.fileType = "dir" if data["is_dir"] else "file"
|
|
|
|
|
|
self.preview = data["preview"]
|
|
|
|
|
|
self.passWord = data["password"]
|
|
|
|
|
|
self.remainDownloads = data["remain_downloads"]
|
|
|
|
|
|
self.downloads = data["downloads"]
|
|
|
|
|
|
self.score = data["score"]
|
|
|
|
|
|
self.views = data["views"]
|
|
|
|
|
|
self.expireTime = data["expire"]
|
|
|
|
|
|
self.setFixedHeight(50)
|
|
|
|
|
|
|
|
|
|
|
|
self.iconLabel = ImageLabel(self)
|
|
|
|
|
|
|
|
|
|
|
|
self.fileNameLabel = StrongBodyLabel(self.fileName, self)
|
|
|
|
|
|
self.changeTimeLabel = BodyLabel(formatDate(self.changeTime), self)
|
|
|
|
|
|
|
|
|
|
|
|
self.viewButton = PushButton("查看", self)
|
|
|
|
|
|
self.viewButton.clicked.connect(self.viewFile)
|
|
|
|
|
|
|
|
|
|
|
|
self.hBoxLayout = QHBoxLayout(self)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.iconLabel)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.fileNameLabel)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.changeTimeLabel)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.viewButton)
|
|
|
|
|
|
|
|
|
|
|
|
self.hBoxLayout.setStretch(0, 1)
|
|
|
|
|
|
self.hBoxLayout.setStretch(1, 2)
|
|
|
|
|
|
self.hBoxLayout.setStretch(2, 1)
|
|
|
|
|
|
|
|
|
|
|
|
self.hBoxLayout.setContentsMargins(10, 10, 10, 10)
|
|
|
|
|
|
self.hBoxLayout.setSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
self.loadIcon()
|
|
|
|
|
|
self.suffix = self.fileName.split(".")[-1].lower()
|
|
|
|
|
|
|
|
|
|
|
|
def loadIcon(self):
|
|
|
|
|
|
icon_name = getFileIcon(self.fileType, self.fileName)
|
|
|
|
|
|
self.iconLabel.setImage(QPixmap(f":app/icons/{icon_name}"))
|
|
|
|
|
|
self.iconLabel.scaledToHeight(25)
|
|
|
|
|
|
self.iconLabel.scaledToWidth(25)
|
|
|
|
|
|
|
|
|
|
|
|
def viewFile(self):
|
|
|
|
|
|
if self.fileType == "file":
|
|
|
|
|
|
w = ShareFileMessageBox(
|
|
|
|
|
|
self._id, self.iconLabel.pixmap(), self.suffix, self.window()
|
|
|
|
|
|
)
|
|
|
|
|
|
if w.exec():
|
|
|
|
|
|
...
|
|
|
|
|
|
else:
|
|
|
|
|
|
signalBus.shareFolderViewSignal.emit(self._id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SharedFolderFileCard(CardWidget):
|
|
|
|
|
|
shareFileDownloadSignal = pyqtSignal() # 共享文件下载信号
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, key, _id, fileName, fileType, path, date, size, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self._id = _id
|
|
|
|
|
|
self.key = key
|
|
|
|
|
|
self.fileName = fileName
|
|
|
|
|
|
self.fileSize = size
|
|
|
|
|
|
self.changeTime = date
|
|
|
|
|
|
self.fileType = fileType
|
|
|
|
|
|
self.filePath = path
|
|
|
|
|
|
self.setFixedHeight(50)
|
|
|
|
|
|
|
|
|
|
|
|
self.iconLabel = ImageLabel(self)
|
|
|
|
|
|
|
|
|
|
|
|
self.fileNameLabel = StrongBodyLabel(self.fileName, self)
|
|
|
|
|
|
self.fileSizeLabel = BodyLabel(formatSize(self.fileSize), self)
|
|
|
|
|
|
self.changeTimeLabel = BodyLabel(formatDate(self.changeTime), self)
|
|
|
|
|
|
|
|
|
|
|
|
self.hBoxLayout = QHBoxLayout(self)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.iconLabel)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.fileNameLabel)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.fileSizeLabel)
|
|
|
|
|
|
self.hBoxLayout.addWidget(self.changeTimeLabel)
|
|
|
|
|
|
|
|
|
|
|
|
self.hBoxLayout.setStretch(0, 1)
|
|
|
|
|
|
self.hBoxLayout.setStretch(1, 2)
|
|
|
|
|
|
self.hBoxLayout.setStretch(2, 1)
|
|
|
|
|
|
|
|
|
|
|
|
self.hBoxLayout.setContentsMargins(10, 10, 10, 10)
|
|
|
|
|
|
self.hBoxLayout.setSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
self.loadIcon()
|
|
|
|
|
|
|
|
|
|
|
|
self.suffix = self.fileName.split(".")[-1].lower()
|
|
|
|
|
|
|
|
|
|
|
|
if self.fileType == "dir":
|
|
|
|
|
|
# 连接左键点击信号
|
|
|
|
|
|
self.clicked.connect(self.dirClicked)
|
|
|
|
|
|
|
|
|
|
|
|
def mousePressEvent(self, event):
|
|
|
|
|
|
"""重写鼠标点击事件,区分左右键"""
|
|
|
|
|
|
if event.button() == Qt.MouseButton.RightButton:
|
|
|
|
|
|
# 右键点击,显示上下文菜单
|
|
|
|
|
|
if self.fileType == "file":
|
|
|
|
|
|
self.showFileContextMenu(event.globalPos())
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.showFolderContextMenu(event.globalPos())
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 左键或其他按钮点击,调用父类处理
|
|
|
|
|
|
super().mousePressEvent(event)
|
|
|
|
|
|
|
|
|
|
|
|
def loadIcon(self):
|
|
|
|
|
|
icon_name = getFileIcon(self.fileType, self.fileName)
|
|
|
|
|
|
self.iconLabel.setImage(QPixmap(f":app/icons/{icon_name}"))
|
|
|
|
|
|
self.iconLabel.scaledToHeight(25)
|
|
|
|
|
|
self.iconLabel.scaledToWidth(25)
|
|
|
|
|
|
|
|
|
|
|
|
def dirClicked(self):
|
|
|
|
|
|
if self.filePath == "/":
|
|
|
|
|
|
paths = "/" + self.fileName
|
|
|
|
|
|
else:
|
|
|
|
|
|
paths = f"{self.filePath}/{self.fileName}"
|
|
|
|
|
|
signalBus.shareDirOpenSignal.emit(paths)
|
|
|
|
|
|
|
|
|
|
|
|
def downloadFile(self):
|
|
|
|
|
|
if self.fileType == "file":
|
|
|
|
|
|
signalBus.addDownloadFileTask.emit(
|
|
|
|
|
|
f"share.{self.suffix}",
|
|
|
|
|
|
self.fileName,
|
|
|
|
|
|
f"{self.filePath}/{self.fileName}.{self.key}",
|
|
|
|
|
|
)
|
|
|
|
|
|
signalBus.shareFileDownloadSignal.emit()
|
|
|
|
|
|
|
|
|
|
|
|
def showFileContextMenu(self, pos):
|
|
|
|
|
|
"""显示上下文菜单"""
|
|
|
|
|
|
menu = RoundMenu(parent=self)
|
|
|
|
|
|
|
|
|
|
|
|
menu.addActions(
|
|
|
|
|
|
[
|
|
|
|
|
|
Action(
|
|
|
|
|
|
FIF.DOWNLOAD, lang("下载"), triggered=lambda: self.downloadFile()
|
|
|
|
|
|
),
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
menu.exec(pos, aniType=MenuAnimationType.DROP_DOWN)
|
|
|
|
|
|
|
|
|
|
|
|
def showFolderContextMenu(self, pos):
|
|
|
|
|
|
"""显示上下文菜单"""
|
|
|
|
|
|
menu = RoundMenu(parent=self)
|
|
|
|
|
|
|
|
|
|
|
|
menu.addActions(
|
|
|
|
|
|
[
|
|
|
|
|
|
Action(FIF.DOWNLOAD, lang("进入"), triggered=lambda: self.dirClicked()),
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
menu.exec(pos, aniType=MenuAnimationType.DROP_DOWN)
|
2025-11-01 20:14:35 +08:00
|
|
|
|
|
|
|
|
|
|
def mouseDoubleClickEvent(self, event):
|
|
|
|
|
|
"""重写鼠标双击事件,双击文件夹时进入文件夹"""
|
|
|
|
|
|
if self.fileType == "dir":
|
|
|
|
|
|
self.dirClicked()
|
|
|
|
|
|
# 阻止事件继续传播,避免可能的干扰
|
|
|
|
|
|
event.accept()
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 非文件夹时调用父类处理
|
|
|
|
|
|
super().mouseDoubleClickEvent(event)
|