拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

dupeGuru 文件夹选择完全指南:从启动窗口到扫描前的目录管理与状态控制

dupeGuru 文件夹选择完全指南:从启动窗口到扫描前的目录管理与状态控制
  • 桌面应用

【免费下载链接】dupeguru

Find duplicate files

项目地址:https://gitcode.com/gh_mirrors/du/dupeguru
点击查看免费下载

dupeGuru 是一款用于查找重复文件的跨平台桌面工具,而文件夹选择窗口(Folder Selection)是每次启动时看到的第一个界面,也是整个扫描流程的入口。本文围绕官方文档 help/en/folders.rst 展开,结合仓库中core/directories.py、core/gui/directory_tree.py、qt/directories_dialog.py等源码与测试,系统讲解应用模式、扫描类型、文件夹列表操作与文件夹三种状态的完整机制。读完本文,你将掌握如何正确配置扫描输入、理解 Reference/Excluded 状态的底层语义,并能从源码角度推断状态继承与文件收集的真实行为。

窗口概览:一次扫描需要哪些基本输入

启动 dupeGuru 后看到的第一个窗口就是文件夹选择窗口。它收集开始一次扫描所需的全部基础输入,官方文档将其归纳为三部分:

  • 应用模式(Application Mode):决定用哪一套扫描引擎与功能特性;
  • 扫描类型(Scan Type):决定匹配算法采用哪种策略;
  • 待扫描文件夹(Folders to scan):决定扫描覆盖哪些目录。

从 Qt 实现看,该窗口对应 qt/directories_dialog.py 中的DirectoriesDialog:顶部是应用模式单选钮(appModeRadioBox,选项为 Standard/Music/Picture),中间是扫描类型下拉框(scanTypeComboBox)与 "More Options"(偏好设置)按钮,中部为文件夹树视图QTreeView,底部是-、+、Load Results 与 Scan 按钮。窗口默认尺寸为 420×338,并提供DirectoriesWindowRect偏好记忆窗口位置(见 qt/directories_dialog.py)。

应用模式:三种引擎,三种扫描能力

dupeGuru 拥有三种主要应用模式,官方文档将其称为 Standard、Music 与 Picture,对应源码 core/app.py 中的AppMode枚举:

模式AppMode 常量定位核心能力
Standard(标准)STANDARD = 0适用于任何类型文件最通用,但缺少其他模式的专属特性
Music(音乐)MUSIC = 1只扫描音乐文件支持标签(Tags)比较,结果窗口含大量音频相关信息列
Picture(图片)PICTURE = 2只扫描图片提供强大的模糊匹配扫描类型,可找到"相似但不完全相同"的图片

选择应用模式不仅改变下方的扫描类型列表,还会改变偏好设置面板中可用的选项。因此官方文档特别强调:若想精细调整扫描参数,请先选定应用模式,再打开偏好设置面板。

底层来看,应用模式直接决定扫描器类(Scanner class)。在 core/app.py 中,SCANNER_CLASS属性将三种模式分别映射到pe.scanner.ScannerPE(Picture)、me.scanner.ScannerME(Music)与se.scanner.ScannerSE(Standard);同时fileclasses与METADATA_TO_READ也会随模式切换(例如 Picture 模式会读取尺寸与 EXIF 时间戳元数据)。界面侧,切换应用模式由appModeButtonSelected触发,调用self.app.model.app_mode = mode并刷新扫描类型下拉列表(见 qt/directories_dialog.py)。

扫描类型:选择匹配策略

扫描类型选择器决定本次扫描采用的匹配策略。官方文档此处指引读者参阅 扫描过程详解,本文只做入口性说明:三种模式各自的可用扫描类型由对应扫描器的get_scan_options()静态方法提供,例如 Standard 模式的 core/se/scanner.py 返回:

  • Filename(ScanType.FILENAME)——按文件名分词匹配;
  • Contents(ScanType.CONTENTS)——按文件内容精确匹配;
  • Folders(ScanType.FOLDERS)——按文件夹内容整体匹配。

扫描类型在底层统一由 core/scanner.py 的ScanType枚举描述:FILENAME=0、FIELDS=1、FIELDSNOORDER=2、TAG=3、FOLDERS=4、CONTENTS=5,Picture 模式另有FUZZYBLOCK=10与EXIFTIMESTAMP=11。界面下拉框的选项由SCANNER_CLASS.get_scan_options()动态填充,当前选择会通过set_scan_type()持久化到各模式对应的偏好中(见 qt/directories_dialog.py)。

文件夹列表操作:添加、拖拽与移除

添加文件夹

点击+按钮添加文件夹。如果此前添加过文件夹,会弹出包含"最近添加文件夹"的菜单:

  • 直接点击列表中的历史文件夹即可将其加入列表;
  • 点击菜单第一项Add New Folder...会弹出文件夹选择对话框;
  • 如果从未添加过任何文件夹,则不弹菜单,直接进入文件夹选择对话框。

源码中"最近文件夹"由Recent类管理(self.recentFolders = Recent(self.app, "recentFolders")),添加动作addFolderTriggered使用QFileDialog(DirectoryOnly 模式,支持多选)选择目录,随后逐个调用app.model.add_directory(path)并写入最近列表(见 qt/directories_dialog.py)。

需要留意的是,add_directory并非简单追加。在 core/app.py 中它调用directories.add_path(Path(d)),而 core/directories.py 的add_path有去重与折叠逻辑:路径已存在时抛出AlreadyThereError;路径不存在时抛出InvalidPathError(界面会分别提示"已在列表中"或"不存在");若新路径是已有目录的父目录,则先移除其下所有子目录再添加,避免扫描范围重叠。

拖拽添加

另一种添加方式是把文件夹直接拖入列表。界面层DirectoriesModel支持text/uri-listMIME 类型,dropMimeData会解析拖入的 URI 列表并逐个调用add_directory(见 qt/directories_model.py)。

移除文件夹

选中要移除的文件夹后点击-按钮。关键在于:如果选中移除的是一个子文件夹,该文件夹不会被移除,而是被设为 Excluded(排除)状态。这一行为对应 core/gui/directory_tree.py 的remove_selected():只有处于第一层(根目录)的选中项会被真正删除;二层级以上的选中节点一律切换到 Excluded 状态(若已全部是 Excluded 则切回 Normal,作为简单的撤销手段)。根目录的真正删除由app.remove_directories(indexes)完成(见 core/app.py),按索引倒序删除列表项。

此外,菜单栏的File → Save Directories / Load Directories可将当前文件夹选择保存为*.dupegurudirsXML 文件并随时恢复(见 qt/directories_dialog.py)。

文件夹状态:Normal、Reference 与 Excluded

文件夹列表中的每一项都处于三种状态之一,这是本窗口最具控制力的功能。官方文档给出语义如下:

状态含义
Normal(正常)该文件夹中被找到的重复文件可以删除
Reference(参考)该文件夹中的文件不能被删除;其中的文件只能作为重复组的reference(参考/保留)位置;若同一重复组中有多个 Reference 文件夹的文件,仅保留一个,其余移出该组
Excluded(排除)该目录中的文件不参与扫描

默认状态当然是Normal。若你希望确保某个文件夹内的文件绝不被误删,可将其设为 Reference。在界面上,文件夹树有两列:Name与State,状态列通过下拉框编辑,且 Reference 状态显示为蓝色、Excluded 显示为红色(见 qt/directories_model.py)。

底层状态由 core/directories.py 的DirectoryState枚举定义:NORMAL = 0、REFERENCE = 1、EXCLUDED = 2。文件被收集时,Directories._get_files会依据状态处理(core/directories.py):Excluded 状态的文件直接跳过;Reference 状态的文件设置file.is_ref = True,这一标记随后被扫描器与结果分组逻辑使用——core/scanner.py 中重复组只会保留"组内存在非 ref 文件"的分组,从而实现"Reference 文件只作保留参照、不可删除"。

状态继承:子文件夹自动继承父状态

为某个目录设置状态后,其下所有子文件夹会自动继承该状态,除非你显式地为某个子文件夹单独设置状态。这一继承逻辑实现于 core/directories.py 的get_state(path):

  1. 若路径在states字典中有直接记录,直接返回其状态;
  2. 否则查询默认状态_default_state_for_path;
  3. 再从该路径的各级父路径(path.parents按从长到短排序)中找最近的已记录状态并返回。

对应的设置方法set_state(path, state)(core/directories.py)在设置时会清除该路径下所有已显式设置的子状态,保证父状态变化能立即覆盖整棵子树——这也印证了测试test_states_overwritten_when_larger_directory_eat_smaller_ones中对"大目录状态吃掉小目录状态"行为的约束。

默认排除逻辑:隐藏目录与排除列表

_default_state_for_path(core/directories.py)还内置了两条默认规则:

  • 若配置了排除列表(exclude_list)且其中有生效的正则,先对目录名做正则匹配,命中即默认 Excluded;
  • 否则,以.开头的隐藏目录默认设为 Excluded(此默认状态可被用户显式覆盖)。

排除列表对"目录级正则"与"文件级正则"区分处理,目录级正则用于默认状态判定,文件级正则用于_get_files中对单个文件名的过滤(core/directories.py)。测试 core/tests/directories_test.py 验证了隐藏目录默认排除、且可被覆盖;TestExcludeList系列测试(core/tests/directories_test.py)则覆盖了排除正则对目录、文件、子目录继承及覆写行为的完整场景。

点击 Scan:扫描流程如何被触发

一切就绪后,点击Scan按钮启动扫描。界面侧scanButtonClicked(qt/directories_dialog.py)会检查当前是否有未保存结果(有则询问是否继续),然后调用app.model.start_scanning(self.app.prefs.profile_scan)。

核心流程位于 core/app.py 的start_scanning:

  1. 依据应用模式实例化对应扫描器(SCANNER_CLASS());
  2. 调用directories.has_any_file()预检——若所选目录不含任何可扫描文件,直接提示并返回;
  3. 将偏好中的扫描选项(如min_match_percentage、word_weighting等)逐个写入扫描器实例;
  4. 按扫描类型收集文件:FOLDERS类型调用directories.get_folders(),其余调用directories.get_files();
  5. 交给scanner.get_dupe_groups()生成重复分组,随后展示 结果窗口。

其中Directories.get_files()会递归遍历每个根目录,期间持续向任务对象回报已收集文件数(j.set_progress(-1, tr("Collected {} files to scan")),并在遍历中检查任务取消(j.check_if_cancelled()),同时静默跳过无权限访问的路径(捕获OSError),保证个别坏目录不会中断整个扫描(见 core/directories.py)。

从测试看状态机制的可验证行为

仓库测试 core/tests/directories_test.py 为上述机制提供了可验证的边界行为,建议阅读以加深理解:

  • test_states:对onefile目录设置 Reference 后,其不存在的子路径dir1查询状态同样返回 Reference(继承生效);
  • test_get_files:Reference 目录下的文件is_ref=True,Excluded 目录下的文件完全缺席;
  • test_get_files_with_inherited_exclusion:根目录设为 Excluded 后,get_files()返回空列表;
  • test_save_and_load:save_to_file/load_from_file的 XML 序列化往返后,目录与状态完全复原;
  • test_load_from_file_with_invalid_path:加载时某个目录已失效,其余目录仍正常加载,不因单个坏路径而失败;
  • test_get_state_returns_excluded_by_default_for_hidden_directories:.foo隐藏目录默认 Excluded,set_state(..., NORMAL)可覆盖。

相关文档与进一步阅读

  • 扫描过程详解:Worded 扫描、Contents 扫描、Picture Blocks、EXIF Timestamp 等各扫描类型的具体算法;
  • 结果窗口:扫描完成后如何审阅重复组、执行删除/移动等操作;
  • 源码入口:core/directories.py(目录与状态模型)、core/gui/directory_tree.py(树形目录模型)、qt/directories_dialog.py(Qt 界面)、qt/directories_model.py(树视图模型与状态着色)、core/scanner.py(扫描类型枚举与公共扫描流程)。
  • 桌面应用

【免费下载链接】dupeguru

Find duplicate files

项目地址:https://gitcode.com/gh_mirrors/du/dupeguru
点击查看免费下载

相关推荐

上一篇:如何永久保存网易云音乐歌单为无损FLAC格式:完整指南
下一篇:网易云音乐无损音乐下载终极指南:永久珍藏你的专属歌单

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表