├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.yml
│ └── feature-request.yml
└── workflows
│ └── release.yml
├── .gitignore
├── FindDuplicateFiles.sln
├── LICENSE
├── README.md
├── docs
└── resources
│ └── images
│ ├── demo1.png
│ ├── demo2.png
│ ├── icon.png
│ └── logo.png
└── src
├── AboutWindow.xaml
├── AboutWindow.xaml.cs
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── Common
└── GlobalArgs.cs
├── Filters
├── IFileSearchFilter.cs
├── IgnoreEmptyFileFilter.cs
├── IgnoreExtensionFilter.cs
├── IgnoreHiddenFileFilter.cs
├── IgnoreSmallFileFilter.cs
├── OnlyExtensionFilter.cs
└── OnlyFileNameFilter.cs
├── FindDuplicateFiles.csproj
├── Images
├── ThemeDark.png
├── ThemeGreen.png
├── Themes
│ ├── Dark
│ │ ├── about.png
│ │ ├── available_updates.png
│ │ ├── c_sharp_logo.png
│ │ ├── close.png
│ │ ├── delete.png
│ │ ├── delete_bin.png
│ │ ├── download.png
│ │ ├── filter.png
│ │ ├── github.png
│ │ ├── home_page.png
│ │ ├── horizontal_line.png
│ │ ├── loader.png
│ │ ├── maximize.png
│ │ ├── multiple_choice.png
│ │ ├── package_search.png
│ │ ├── reset.png
│ │ ├── restore.png
│ │ ├── search.png
│ │ └── stop.png
│ └── Green
│ │ ├── about.png
│ │ ├── available_updates.png
│ │ ├── c_sharp_logo.png
│ │ ├── close.png
│ │ ├── delete.png
│ │ ├── delete_bin.png
│ │ ├── download.png
│ │ ├── filter.png
│ │ ├── github.png
│ │ ├── home_page.png
│ │ ├── horizontal_line.png
│ │ ├── loader.png
│ │ ├── maximize.png
│ │ ├── multiple_choice.png
│ │ ├── package_search.png
│ │ ├── reset.png
│ │ ├── restore.png
│ │ ├── search.png
│ │ └── stop.png
├── icon.ico
└── icon.png
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Model
├── AppConfigInfo.cs
├── SearchConfigs.cs
├── SimpleFileInfo.cs
├── UpdateAppInfo.cs
└── UpdateConfigInfo.cs
├── Resource.Designer.cs
├── Resource.resx
├── SearchFile
├── CheckDuplicateQueue.cs
└── SearchFilesJob.cs
├── Styles
├── AboutWindowStyle.xaml
└── MainWindowStyle.xaml
├── Themes
├── ThemeDark.xaml
└── ThemeGreen.xaml
├── ViewModel
├── AboutWindowViewModel.cs
├── DuplicateFileModel.cs
├── MainWindowViewModel.cs
├── SearchDirectoryModel.cs
└── ViewModelBase.cs
├── icon.ico
└── 首次运行请看这里!!!.txt
/.github/ISSUE_TEMPLATE/bug-report.yml:
--------------------------------------------------------------------------------
1 | name: 报告错误
2 | description: 报告一个错误
3 | labels: [错误]
4 | assignees: [JiuLing-zhang]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: 问题描述
10 | description: 请简短描述下你所遇到的错误。
11 | placeholder: 问题描述
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: repro-steps
16 | attributes:
17 | label: 重现步骤
18 | description: 请描述问题出现的操作步骤。
19 | placeholder: |
20 | 1. 点击"按钮A"
21 | 2. 点击"按钮B"
22 | 3. 出现错误
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: remark
27 | attributes:
28 | label: 问题补充
29 | description: 如果可以,请补充提供下错误截图等。
30 | placeholder: 问题补充
31 | - type: input
32 | id: os
33 | attributes:
34 | label: 操作系统
35 | description: 请提供错误出现的操作系统。
36 | placeholder: 例如 Windows 10、Windows 11
37 | validations:
38 | required: true
39 | - type: dropdown
40 | id: version
41 | attributes:
42 | label: 程序版本
43 | description: 错误出现在哪个版本中。
44 | options:
45 | - 1.7.5
46 | - 1.7.4
47 | - 1.7.3
48 | - 1.7.2
49 | - 更早
50 | validations:
51 | required: true
52 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yml:
--------------------------------------------------------------------------------
1 | name: 功能建议
2 | description: 提议一个好的想法,帮我们完善程序
3 | labels: [功能建议]
4 | assignees: [JiuLing-zhang]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | label: 功能描述
10 | description: 用简短的话描述下功能建议。
11 | placeholder: 功能描述
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: solution
16 | attributes:
17 | label: 解决方案
18 | description: 请描述下你理想的解决方案是什么。
19 | placeholder: 解决方案
20 | validations:
21 | required: true
22 | - type: textarea
23 | id: remark
24 | attributes:
25 | label: 补充说明
26 | description: 如果还有其它需要补充的,请说明。
27 | placeholder: 补充说明
28 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release-job
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*.*.*"
7 |
8 | jobs:
9 | build:
10 | runs-on: windows-latest
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 |
16 | - name: Setup .NET
17 | uses: actions/setup-dotnet@v1
18 | with:
19 | dotnet-version: 7.0.x
20 |
21 | - name: Restore dependencies
22 | run: dotnet restore
23 |
24 | - name: Build
25 | run: dotnet build --no-restore -c release
26 |
27 | - name: Get version
28 | uses: olegtarasov/get-tag@v2.1
29 | id: tagName
30 |
31 | - name: Create zip
32 | shell: pwsh
33 | # 配置【编译后的文件地址】
34 | run: Compress-Archive -Path ${{github.workspace}}\src\bin\Release\net7.0-windows\* -DestinationPath ${{github.workspace}}\src\bin\Release\FindDuplicateFiles_${{ steps.tagName.outputs.tag }}.zip
35 |
36 | - name: Release
37 | uses: softprops/action-gh-release@v0.1.13
38 | if: startsWith(github.ref, 'refs/tags/')
39 | with:
40 | # 配置【README地址】
41 | body: |
42 | Release为自动生成,具体内容请查看提交日志,或直接查看[README](https://github.com/JiuLing-zhang/FindDuplicateFiles#readme)
43 | # 配置【zip文件地址】
44 | files: ${{github.workspace}}\src\bin\Release\FindDuplicateFiles_${{ steps.tagName.outputs.tag }}.zip
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build and Object Folders
2 | bin/
3 | obj/
4 |
5 | # Nuget packages directory
6 | packages/
7 |
8 | ## Ignore Visual Studio temporary files, build results, and
9 | ## files generated by popular Visual Studio add-ons.
10 |
11 | # User-specific files
12 | *.suo
13 | *.user
14 | *.sln.docstates
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Rr]elease/
19 | x64/
20 | *_i.c
21 | *_p.c
22 | *.ilk
23 | *.meta
24 | *.obj
25 | *.pch
26 | *.pdb
27 | *.pgc
28 | *.pgd
29 | *.rsp
30 | *.sbr
31 | *.tlb
32 | *.tli
33 | *.tlh
34 | *.tmp
35 | *.log
36 | *.vspscc
37 | *.vssscc
38 | .builds
39 |
40 | # Visual C++ cache files
41 | ipch/
42 | *.aps
43 | *.ncb
44 | *.opensdf
45 | *.sdf
46 |
47 | # Visual Studio profiler
48 | *.psess
49 | *.vsp
50 | *.vspx
51 |
52 | # Guidance Automation Toolkit
53 | *.gpState
54 |
55 | # ReSharper is a .NET coding add-in
56 | _ReSharper*
57 |
58 | # NCrunch
59 | *.ncrunch*
60 | .*crunch*.local.xml
61 |
62 | # Installshield output folder
63 | [Ee]xpress
64 |
65 | # DocProject is a documentation generator add-in
66 | DocProject/buildhelp/
67 | DocProject/Help
68 | UpgradeLog*.XML
69 |
70 | # Lightswitch
71 | _Pvt_Extensions
72 | GeneratedArtifacts
73 | *.xap
74 | ModelManifest.xml
75 |
76 | #Backup file
77 | *.bak
78 | .vs
79 |
--------------------------------------------------------------------------------
/FindDuplicateFiles.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31410.223
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FindDuplicateFiles", "src\FindDuplicateFiles.csproj", "{22729FF4-61AB-418C-BBD3-FD005204270B}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {22729FF4-61AB-418C-BBD3-FD005204270B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {22729FF4-61AB-418C-BBD3-FD005204270B}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {22729FF4-61AB-418C-BBD3-FD005204270B}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {22729FF4-61AB-418C-BBD3-FD005204270B}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {8B3B1DD1-C688-46AB-8C27-867B5845453D}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 九零
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 解放你的硬盘空间,红盘用户的福音
5 |
6 |
7 | 
8 | 
9 | 
10 |
11 |
12 |
13 | 
14 |
15 | ## 使用说明
16 | * 选项 - 启用图片文件预览
17 | 启用时,在搜索结果中点击图片文件,右下角会出现图片预览结果。
18 |
19 | * 选项 - 删除时移除空目录
20 | 删除文件的时候,如果文件夹中只剩余当前文件,删除的时候会顺便删除文件夹。
21 |
22 | * 结果区域 - 一键选中重复文件
23 | 自动选中所有重复文件(每个文件只保留一份)。
24 |
25 | * 结果区域 - 自定义选择重复文件
26 | 弹出筛选窗口,用户可以根据查找路径选择本次要删除的目标文件夹。
27 |
28 | * 其它
29 | 其它选项都比较通俗易懂,还请同学自行摸索。
30 |
31 | 
32 |
33 | ## License
34 | MIT License
--------------------------------------------------------------------------------
/docs/resources/images/demo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/docs/resources/images/demo1.png
--------------------------------------------------------------------------------
/docs/resources/images/demo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/docs/resources/images/demo2.png
--------------------------------------------------------------------------------
/docs/resources/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/docs/resources/images/icon.png
--------------------------------------------------------------------------------
/docs/resources/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/docs/resources/images/logo.png
--------------------------------------------------------------------------------
/src/AboutWindow.xaml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 重复文件查找
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 | 作者:九零
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/AboutWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Windows;
4 | using System.Windows.Navigation;
5 | using FindDuplicateFiles.Common;
6 | using FindDuplicateFiles.ViewModel;
7 |
8 | namespace FindDuplicateFiles
9 | {
10 | ///
11 | /// AboutWindow.xaml 的交互逻辑
12 | ///
13 | public partial class AboutWindow : Window
14 | {
15 | private readonly AboutWindowViewModel _myModel = new();
16 | public AboutWindow()
17 | {
18 | InitializeComponent();
19 | DataContext = _myModel;
20 | _myModel.Version = $"版本:{GlobalArgs.AppVersion}";
21 | _myModel.DownloadUrl = "https://github.com/JiuLing-zhang";
22 | }
23 | private void Window_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
24 | {
25 | this.DragMove();
26 | }
27 |
28 | private void GoWebsite_RequestNavigate(object sender, RequestNavigateEventArgs e)
29 | {
30 | OpenUrl(e.Uri.AbsoluteUri);
31 | e.Handled = true;
32 | }
33 |
34 | private void BtnCheckUpdate_Click(object sender, RoutedEventArgs e)
35 | {
36 | var app = JiuLing.AutoUpgrade.Shell.AutoUpgradeFactory.Create();
37 | app.SetUpgrade(x =>
38 | {
39 | x.IsCheckSign = true;
40 | });
41 | app.UseHttpMode(Resource.AutoUpgradePath).Run();
42 | }
43 | private static void OpenUrl(string url)
44 | {
45 | using var compiler = new Process();
46 | compiler.StartInfo.FileName = url;
47 | compiler.StartInfo.UseShellExecute = true;
48 | compiler.Start();
49 | }
50 | private void BtnClose_Click(object sender, RoutedEventArgs e)
51 | {
52 | this.Close();
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace FindDuplicateFiles
4 | {
5 | ///
6 | /// Interaction logic for App.xaml
7 | ///
8 | public partial class App : Application
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/src/Common/GlobalArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using FindDuplicateFiles.Model;
4 |
5 | namespace FindDuplicateFiles.Common
6 | {
7 | public class GlobalArgs
8 | {
9 | ///
10 | /// App名称
11 | ///
12 | public static string AppName { get; set; } = AppDomain.CurrentDomain.FriendlyName;
13 |
14 | ///
15 | /// App Data文件夹路径
16 | ///
17 | private static readonly string AppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
18 | ///
19 | /// 配置文件路径
20 | ///
21 | public static string AppConfigPath { get; set; } = Path.Combine(AppDataPath, AppName, "config.json");
22 |
23 | public static AppConfigInfo AppConfig { get; set; }
24 | public static string AppVersion { get; set; } = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString();
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Filters/IFileSearchFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 |
4 | namespace FindDuplicateFiles.Filters
5 | {
6 | ///
7 | /// 文件过滤器
8 | ///
9 | interface IFileSearchFilter
10 | {
11 | ///
12 | /// 根据条件过滤
13 | ///
14 | /// 文件列表
15 | /// 符合条件的文件列表
16 | public List FilterByCondition(List files);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Filters/IgnoreEmptyFileFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | namespace FindDuplicateFiles.Filters
6 | {
7 | ///
8 | /// 空文件过滤器
9 | ///
10 | public class IgnoreEmptyFileFilter : IFileSearchFilter
11 | {
12 | public List FilterByCondition(List files)
13 | {
14 | return files.Where(x => x.Length != 0).ToList();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Filters/IgnoreExtensionFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace FindDuplicateFiles.Filters
9 | {
10 | ///
11 | /// 忽略扩展名过滤器
12 | ///
13 | public class IgnoreExtensionFilter : IFileSearchFilter
14 | {
15 | private readonly List _extensions;
16 | public IgnoreExtensionFilter(List extensions)
17 | {
18 | _extensions = extensions;
19 | }
20 | public List FilterByCondition(List files)
21 | {
22 | return files.Where(x => _extensions.Contains(x.Extension.ToLower()) == false).ToList();
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Filters/IgnoreHiddenFileFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | namespace FindDuplicateFiles.Filters
6 | {
7 | ///
8 | /// 隐藏文件过滤器
9 | ///
10 | public class IgnoreHiddenFileFilter : IFileSearchFilter
11 | {
12 | public List FilterByCondition(List files)
13 | {
14 | return files.Where(x => x.Attributes != FileAttributes.Hidden).ToList();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Filters/IgnoreSmallFileFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | namespace FindDuplicateFiles.Filters
6 | {
7 | ///
8 | /// 小文件过滤器
9 | ///
10 | public class IgnoreSmallFileFilter : IFileSearchFilter
11 | {
12 | private readonly decimal _minLength;
13 | ///
14 | /// 初始化过滤器
15 | ///
16 | /// 最小的文件大小(KB)
17 | public IgnoreSmallFileFilter(decimal minLength)
18 | {
19 | _minLength = minLength;
20 | }
21 | public List FilterByCondition(List files)
22 | {
23 | return files.Where(x => x.Length / 1024m >= _minLength).ToList();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Filters/OnlyExtensionFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | namespace FindDuplicateFiles.Filters
6 | {
7 | ///
8 | /// 指定扩展名过滤器
9 | ///
10 | public class OnlyExtensionFilter : IFileSearchFilter
11 | {
12 | private readonly List _extensions;
13 | ///
14 | /// 初始化过滤器
15 | ///
16 | /// 指定的扩展名列表
17 | public OnlyExtensionFilter(List extensions)
18 | {
19 | _extensions = extensions;
20 | }
21 | public List FilterByCondition(List files)
22 | {
23 | return files.Where(x => _extensions.Contains(x.Extension.ToLower())).ToList();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Filters/OnlyFileNameFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | namespace FindDuplicateFiles.Filters
6 | {
7 | ///
8 | /// 指定文件名过滤器
9 | ///
10 | internal class OnlyFileNameFilter : IFileSearchFilter
11 | {
12 | private readonly List _fileNames;
13 | ///
14 | /// 初始化过滤器
15 | ///
16 | /// 指定的文件名列表
17 | public OnlyFileNameFilter(List fileNames)
18 | {
19 | _fileNames = fileNames;
20 | }
21 | public List FilterByCondition(List files)
22 | {
23 | return files.Where(x => _fileNames.Contains(x.Name.ToLower())).ToList();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/FindDuplicateFiles.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net7.0-windows
6 | true
7 | true
8 | icon.ico
9 |
10 | 九零
11 | 重复文件查找
12 | 1.7.9.4
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | PreserveNewest
80 |
81 |
82 | PreserveNewest
83 |
84 |
85 | PreserveNewest
86 |
87 |
88 | PreserveNewest
89 |
90 |
91 | PreserveNewest
92 |
93 |
94 | PreserveNewest
95 |
96 |
97 | PreserveNewest
98 |
99 |
100 | PreserveNewest
101 |
102 |
103 | PreserveNewest
104 |
105 |
106 | PreserveNewest
107 |
108 |
109 | PreserveNewest
110 |
111 |
112 | PreserveNewest
113 |
114 |
115 | PreserveNewest
116 |
117 |
118 | PreserveNewest
119 |
120 |
121 | PreserveNewest
122 |
123 |
124 | PreserveNewest
125 |
126 |
127 | PreserveNewest
128 |
129 |
130 | PreserveNewest
131 |
132 |
133 | PreserveNewest
134 |
135 |
136 | PreserveNewest
137 |
138 |
139 | PreserveNewest
140 |
141 |
142 | PreserveNewest
143 |
144 |
145 | PreserveNewest
146 |
147 |
148 | PreserveNewest
149 |
150 |
151 | PreserveNewest
152 |
153 |
154 | PreserveNewest
155 |
156 |
157 | PreserveNewest
158 |
159 |
160 | PreserveNewest
161 |
162 |
163 | PreserveNewest
164 |
165 |
166 | PreserveNewest
167 |
168 |
169 | PreserveNewest
170 |
171 |
172 | PreserveNewest
173 |
174 |
175 | PreserveNewest
176 |
177 |
178 | PreserveNewest
179 |
180 |
181 | PreserveNewest
182 |
183 |
184 | PreserveNewest
185 |
186 |
187 | PreserveNewest
188 |
189 |
190 | PreserveNewest
191 |
192 |
193 | PreserveNewest
194 |
195 |
196 | PreserveNewest
197 |
198 |
199 | PreserveNewest
200 |
201 |
202 | PreserveNewest
203 |
204 |
205 |
206 |
207 |
208 | PreserveNewest
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 | True
220 | True
221 | Resource.resx
222 |
223 |
224 |
225 |
226 |
227 | ResXFileCodeGenerator
228 | Resource.Designer.cs
229 |
230 |
231 |
232 |
233 |
--------------------------------------------------------------------------------
/src/Images/ThemeDark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/ThemeDark.png
--------------------------------------------------------------------------------
/src/Images/ThemeGreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/ThemeGreen.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/about.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/about.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/available_updates.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/available_updates.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/c_sharp_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/c_sharp_logo.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/close.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/delete.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/delete_bin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/delete_bin.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/download.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/filter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/filter.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/github.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/home_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/home_page.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/horizontal_line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/horizontal_line.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/loader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/loader.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/maximize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/maximize.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/multiple_choice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/multiple_choice.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/package_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/package_search.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/reset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/reset.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/restore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/restore.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/search.png
--------------------------------------------------------------------------------
/src/Images/Themes/Dark/stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Dark/stop.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/about.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/about.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/available_updates.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/available_updates.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/c_sharp_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/c_sharp_logo.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/close.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/delete.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/delete_bin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/delete_bin.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/download.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/filter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/filter.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/github.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/home_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/home_page.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/horizontal_line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/horizontal_line.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/loader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/loader.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/maximize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/maximize.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/multiple_choice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/multiple_choice.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/package_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/package_search.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/reset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/reset.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/restore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/restore.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/search.png
--------------------------------------------------------------------------------
/src/Images/Themes/Green/stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/Themes/Green/stop.png
--------------------------------------------------------------------------------
/src/Images/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/icon.ico
--------------------------------------------------------------------------------
/src/Images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/Images/icon.png
--------------------------------------------------------------------------------
/src/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
57 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
69 |
70 |
71 |
72 |
73 |
74 |
77 |
80 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | 匹配方式
110 | 文件名
111 | 文件大小
112 | 文件修改时间
113 | MD5(速度慢,准确性高)
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | 选项
122 | 仅查找以下文件名(多个用|隔开)
129 |
134 | 忽略空文件
135 | 忽略隐藏文件
136 | 忽略系统文件
137 | 忽略小文件(小于1024KB)
138 | 查找所有文件
139 | 仅查找图片文件
140 | 仅查找媒体文件
141 | 仅查找文档文件
142 | 启用图片文件预览
143 | 删除时移除空目录
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | 查找路径(这里也支持拖拽哦~~)
152 |
161 |
162 |
163 |
164 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
204 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
253 |
256 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 | 从下方选择要删除的文件所在路径
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
293 |
294 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
--------------------------------------------------------------------------------
/src/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Data;
8 | using System.Windows.Input;
9 | using System.Windows.Media;
10 | using System.Windows.Media.Animation;
11 | using System.Windows.Media.Imaging;
12 | using FindDuplicateFiles.Common;
13 | using FindDuplicateFiles.Model;
14 | using FindDuplicateFiles.SearchFile;
15 | using FindDuplicateFiles.ViewModel;
16 | using JiuLing.CommonLibs.ExtensionMethods;
17 |
18 | namespace FindDuplicateFiles
19 | {
20 | ///
21 | /// Interaction logic for MainWindow.xaml
22 | ///
23 | public partial class MainWindow : Window
24 | {
25 | ///
26 | /// 是否正在进行搜索
27 | ///
28 | private bool _isSearching;
29 |
30 | private readonly SearchFilesJob _searchFilesJob = new();
31 | private readonly MainWindowViewModel _myModel = new();
32 | public MainWindow()
33 | {
34 | InitializeComponent();
35 |
36 | LoadingAppConfig();
37 | LoadingTheme(GlobalArgs.AppConfig.Theme);
38 |
39 | BindingItemsSource();
40 | BindingSearchEvent();
41 |
42 | InitializeSearchCondition();
43 | InitializeLoadingBlock();
44 | }
45 |
46 | private void LoadingAppConfig()
47 | {
48 | if (!File.Exists(GlobalArgs.AppConfigPath))
49 | {
50 | GlobalArgs.AppConfig = new AppConfigInfo();
51 | }
52 | else
53 | {
54 | string json = File.ReadAllText(GlobalArgs.AppConfigPath);
55 | GlobalArgs.AppConfig = System.Text.Json.JsonSerializer.Deserialize(json);
56 | }
57 | //TODO 这里可以为配置文件加入版本号,有更新时才覆盖保存
58 | SaveAppConfig();
59 |
60 | // string updateConfigPath = $"{GlobalArgs.AppPath}{GlobalArgs.UpdateConfigPath}";
61 | // string updateConfigString = File.ReadAllText(updateConfigPath);
62 | // GlobalArgs.UpdateConfig = System.Text.Json.JsonSerializer.Deserialize(updateConfigString);
63 | }
64 |
65 | private void InitializeSearchCondition()
66 | {
67 | //匹配方式
68 | ChkFileName.IsChecked = true;
69 | ChkFileSize.IsChecked = true;
70 | ChkFileLastWriteTimeUtc.IsChecked = true;
71 | ChkMD5.IsChecked = false;
72 |
73 | //选项
74 | ChkOnlyFileName.IsChecked = false;
75 | ChkIgnoreEmptyFile.IsChecked = true;
76 | ChkIgnoreHiddenFile.IsChecked = true;
77 | ChkIgnoreSystemFile.IsChecked = true;
78 | ChkIgnoreSmallFile.IsChecked = false;
79 | RdoAllFile.IsChecked = true;
80 | ChkPreviewImage.IsChecked = false;
81 | ChkDeleteEmptyDirectory.IsChecked = false;
82 |
83 | _myModel.SearchDirectory.Clear();
84 | }
85 |
86 |
87 | ///
88 | /// UI数据绑定
89 | ///
90 | private void BindingItemsSource()
91 | {
92 | ListBoxSearchFolders.ItemsSource = _myModel.SearchDirectory;
93 | ListBoxDirectoryFilter.ItemsSource = _myModel.SearchDirectory;
94 | ListViewDuplicateFile.ItemsSource = _myModel.DuplicateFiles;
95 |
96 | CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(ListViewDuplicateFile.ItemsSource);
97 | var groupDescription = new PropertyGroupDescription("Key");
98 | view.GroupDescriptions?.Add(groupDescription);
99 |
100 | RdoOnlyImageFile.ToolTip = $"包括:{GlobalArgs.AppConfig.ImageExtension}文件";
101 | RdoOnlyMediaFile.ToolTip = $"包括:{GlobalArgs.AppConfig.MediaExtension}文件";
102 | RdoOnlyDocumentFile.ToolTip = $"包括:{GlobalArgs.AppConfig.DocumentExtension}文件";
103 | ChkIgnoreSystemFile.ToolTip = $"包括:{GlobalArgs.AppConfig.SystemExtension}文件";
104 | DataContext = _myModel;
105 | }
106 |
107 | ///
108 | /// 绑定查找任务的内部事件
109 | ///
110 | private void BindingSearchEvent()
111 | {
112 | _searchFilesJob.EventMessage = ExecutedMessage;
113 | _searchFilesJob.EventDuplicateFound = DuplicateFilesFound;
114 | _searchFilesJob.EventSearchFinished = SearchFinished;
115 | }
116 | private void InitializeLoadingBlock()
117 | {
118 | var da = new DoubleAnimation()
119 | {
120 | From = 0,
121 | To = 360,
122 | Duration = new Duration(TimeSpan.FromSeconds(3)),
123 | RepeatBehavior = RepeatBehavior.Forever
124 | };
125 | var rt = new RotateTransform();
126 | ImgLoading.RenderTransform = rt;
127 | rt.BeginAnimation(RotateTransform.AngleProperty, da);
128 | }
129 |
130 | private void ExecutedMessage(string message)
131 | {
132 | _myModel.JobMessage = message;
133 | }
134 | private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
135 | {
136 | this.DragMove();
137 |
138 | if (GridImage.Visibility == Visibility)
139 | {
140 | ImgPreview.Source = null;
141 | GridImage.Visibility = Visibility.Collapsed;
142 | }
143 | }
144 |
145 | private void BtnMinimize_Click(object sender, RoutedEventArgs e)
146 | {
147 | this.WindowState = WindowState.Minimized;
148 | }
149 |
150 | private void BtnMaximize_Click(object sender, RoutedEventArgs e)
151 | {
152 | if (this.WindowState == WindowState.Maximized)
153 | {
154 | this.WindowState = WindowState.Normal;
155 | this.ImgMaximize.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/maximize.png"));
156 | }
157 | else
158 | {
159 | this.WindowState = WindowState.Maximized;
160 | this.ImgMaximize.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/restore.png"));
161 | }
162 | }
163 |
164 | private void BtnClose_Click(object sender, RoutedEventArgs e)
165 | {
166 | this.Close();
167 | }
168 |
169 | private void BtnAddSearchFolder_Click(object sender, RoutedEventArgs e)
170 | {
171 | var fbd = new System.Windows.Forms.FolderBrowserDialog();
172 | fbd.ShowDialog();
173 | var path = fbd.SelectedPath;
174 | if (path.IsEmpty())
175 | {
176 | return;
177 | }
178 |
179 | AddSearchFolderOnly(path);
180 | }
181 |
182 | private void AddSearchFolderOnly(string path)
183 | {
184 | if (_myModel.SearchDirectory.Any(x => x.DirectoryName == path))
185 | {
186 | return;
187 | }
188 |
189 | if (_myModel.SearchDirectory.Any(x => x.DirectoryName.IndexOf(path) == 0 || path.IndexOf(x.DirectoryName) == 0))
190 | {
191 | MessageBox.Show("添加失败:查找路径不能相互包含", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Error);
192 | return;
193 | }
194 |
195 | _myModel.SearchDirectory.Add(new SearchDirectoryModel()
196 | {
197 | DirectoryName = path
198 | });
199 | }
200 |
201 | private void BtnRemoveSearchFolder_Click(object sender, RoutedEventArgs e)
202 | {
203 | var tag = (sender as Button)?.Tag;
204 | if (tag == null)
205 | {
206 | return;
207 | }
208 |
209 | string path = tag.ToString();
210 | var obj = _myModel.SearchDirectory.First(x => x.DirectoryName == path);
211 | _myModel.SearchDirectory.Remove(obj);
212 | }
213 |
214 | private void BtnSearch_Click(object sender, RoutedEventArgs e)
215 | {
216 | if (_isSearching)
217 | {
218 | if (MessageBox.Show("确定要停止搜索吗?", "重复文件查找", MessageBoxButton.OKCancel, MessageBoxImage.Warning) == MessageBoxResult.Cancel)
219 | {
220 | return;
221 | }
222 | EndSearch();
223 | }
224 | else
225 | {
226 | BeginSearch();
227 | }
228 | }
229 |
230 | private void BeginSearch()
231 | {
232 | if (_myModel.SearchDirectory.Count == 0)
233 | {
234 | MessageBox.Show("请选择要查找的文件夹", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Stop);
235 | return;
236 | }
237 |
238 | //匹配方式校验
239 | SearchMatchEnum searchMatch = 0;
240 | if (ChkFileName.IsChecked == true)
241 | {
242 | searchMatch |= SearchMatchEnum.Name;
243 | }
244 | if (ChkFileSize.IsChecked == true)
245 | {
246 | searchMatch |= SearchMatchEnum.Size;
247 | }
248 | if (ChkFileLastWriteTimeUtc.IsChecked == true)
249 | {
250 | searchMatch |= SearchMatchEnum.LastWriteTime;
251 | }
252 | if (ChkMD5.IsChecked == true)
253 | {
254 | searchMatch |= SearchMatchEnum.MD5;
255 | }
256 | if (searchMatch == 0)
257 | {
258 | MessageBox.Show("请选择匹配方式", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Stop);
259 | return;
260 | }
261 |
262 | //选项
263 | SearchOptionEnum searchOption = 0;
264 | var searchOptionData = new SearchOptionData();
265 | if (ChkIgnoreEmptyFile.IsChecked == true)
266 | {
267 | searchOption |= SearchOptionEnum.IgnoreEmptyFile;
268 | }
269 | if (ChkIgnoreHiddenFile.IsChecked == true)
270 | {
271 | searchOption |= SearchOptionEnum.IgnoreHiddenFile;
272 | }
273 | if (ChkIgnoreSystemFile.IsChecked == true)
274 | {
275 | searchOption |= SearchOptionEnum.IgnoreSystemFile;
276 | }
277 | if (ChkIgnoreSmallFile.IsChecked == true)
278 | {
279 | searchOption |= SearchOptionEnum.IgnoreSmallFile;
280 | }
281 | if (RdoOnlyImageFile.IsChecked == true)
282 | {
283 | searchOption |= SearchOptionEnum.OnlyImageFile;
284 | }
285 | if (RdoOnlyMediaFile.IsChecked == true)
286 | {
287 | searchOption |= SearchOptionEnum.OnlyMediaFile;
288 | }
289 | if (RdoOnlyDocumentFile.IsChecked == true)
290 | {
291 | searchOption |= SearchOptionEnum.OnlyDocumentFile;
292 | }
293 | if (ChkOnlyFileName.IsChecked == true)
294 | {
295 | var onlyFileNames = TxtOnlyFileNames.Text.Trim();
296 | if (onlyFileNames.IsEmpty())
297 | {
298 | MessageBox.Show("请输入要查找的文件名", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Stop);
299 | TxtOnlyFileNames.Focus();
300 | return;
301 | }
302 | searchOptionData.OnlyFileNames = new List();
303 | searchOption |= SearchOptionEnum.OnlyFileName;
304 | foreach (var onlyFileName in onlyFileNames.Split('|'))
305 | {
306 | searchOptionData.OnlyFileNames.Add(onlyFileName.Trim().ToLower());
307 | }
308 | }
309 | SetBeginSearchStyle();
310 | _myModel.DuplicateFiles.Clear();
311 | var config = new SearchConfigs()
312 | {
313 | Folders = new List(_myModel.SearchDirectory.Select(x => x.DirectoryName).ToList()),
314 | SearchMatch = searchMatch,
315 | SearchOption = searchOption,
316 | SearchOptionData = searchOptionData
317 | };
318 | _searchFilesJob.Start(config);
319 | }
320 |
321 | ///
322 | /// 开始搜索
323 | ///
324 | private void SetBeginSearchStyle()
325 | {
326 | TxtSearch.Text = "停止";
327 | ImgSearch.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/stop.png"));
328 | _isSearching = true;
329 | _myModel.IsShowLoading = _isSearching;
330 | }
331 |
332 | private void EndSearch()
333 | {
334 | SetEndSearchStyle();
335 | _searchFilesJob.Stop();
336 | }
337 |
338 | ///
339 | /// 结束搜索
340 | ///
341 | private void SetEndSearchStyle()
342 | {
343 | TxtSearch.Text = "查找";
344 | ImgSearch.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/search.png"));
345 | _isSearching = false;
346 | _myModel.IsShowLoading = _isSearching;
347 | }
348 |
349 | private void BtnReset_Click(object sender, RoutedEventArgs e)
350 | {
351 | if (_isSearching)
352 | {
353 | MessageBox.Show("任务执行中,禁止重置", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Stop);
354 | return;
355 | }
356 | InitializeSearchCondition();
357 | }
358 |
359 | private void ChangeTheme_Click(object sender, RoutedEventArgs e)
360 | {
361 | if (_isSearching)
362 | {
363 | MessageBox.Show("任务执行中不允许换肤", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Stop);
364 | return;
365 | }
366 |
367 | var tag = (sender as Button)?.Tag;
368 | if (tag == null)
369 | {
370 | MessageBox.Show("修改主题失败:系统错误", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Stop);
371 | return;
372 | }
373 |
374 | string theme = tag.ToString();
375 | GlobalArgs.AppConfig.Theme = theme;
376 |
377 | SaveAppConfig();
378 |
379 | LoadingTheme(theme);
380 | }
381 |
382 | private void LoadingTheme(string themeName)
383 | {
384 | try
385 | {
386 | var mResourceSkin = new ResourceDictionary()
387 | {
388 | Source = new Uri($"/Themes/Theme{themeName}.xaml", UriKind.RelativeOrAbsolute)
389 | };
390 | Application.Current.Resources.MergedDictionaries[0] = mResourceSkin;
391 |
392 | ImgSearch.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/search.png"));
393 | ImgReset.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/reset.png"));
394 | ImgFilter.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/filter.png"));
395 | ImgMultipleChoice.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/multiple_choice.png"));
396 | ImgDeleteBin.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/delete_bin.png"));
397 | ImgLoading.Source = new BitmapImage(new Uri($"pack://application:,,,/Images/Themes/{GlobalArgs.AppConfig.Theme}/loader.png"));
398 | }
399 | catch (IOException ex)
400 | {
401 | MessageBox.Show($"主题加载失败,{ex.Message}", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Error);
402 | }
403 | }
404 |
405 | private void DuplicateFilesFound(string key, SimpleFileInfo simpleFile)
406 | {
407 | Application.Current.Dispatcher.Invoke(() =>
408 | {
409 | _myModel.DuplicateFiles.Add(new DuplicateFileModel()
410 | {
411 | Key = key,
412 | Name = simpleFile.Name,
413 | Path = simpleFile.Path,
414 | Size = Math.Ceiling(simpleFile.Size / 1024),
415 | LastWriteTime = simpleFile.LastWriteTime,
416 | Extension = simpleFile.Extension,
417 | IsCheckedFile = false
418 | });
419 | });
420 | }
421 | private void SearchFinished()
422 | {
423 | Application.Current.Dispatcher.Invoke(() =>
424 | {
425 | SetEndSearchStyle();
426 | MessageBox.Show("查找完成", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Information);
427 | });
428 | }
429 |
430 | private void ListViewDuplicateFile_SelectionChanged(object sender, SelectionChangedEventArgs e)
431 | {
432 | if (ChkPreviewImage.IsChecked == false)
433 | {
434 | return;
435 | }
436 |
437 | var lv = sender as ListView;
438 | if (lv?.SelectedItem is not DuplicateFileModel selectFile)
439 | {
440 | return;
441 | }
442 | if (selectFile.Extension.IsNotEmpty() && GlobalArgs.AppConfig.ImageExtension.Contains(selectFile.Extension))
443 | {
444 | ImgPreview.Source = new BitmapImage(new Uri(selectFile.Path, UriKind.Absolute));
445 | GridImage.Visibility = Visibility.Visible;
446 | }
447 | }
448 |
449 | private void BtnAbout_Click(object sender, RoutedEventArgs e)
450 | {
451 | var about = new AboutWindow
452 | {
453 | Owner = this
454 | };
455 | about.ShowDialog();
456 | }
457 | private void BtnChooseFile_Click(object sender, RoutedEventArgs e)
458 | {
459 | if (_myModel.DuplicateFiles.Count == 0)
460 | {
461 | MessageBox.Show("没有可用数据", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Information);
462 | return;
463 | }
464 | if (_myModel.DuplicateFiles.Any(x => x.IsCheckedFile))
465 | {
466 | RemoveChooseAllFiles();
467 | }
468 | else
469 | {
470 | ChooseAllFiles();
471 | }
472 |
473 | }
474 |
475 | ///
476 | /// 是否在全选操作
477 | ///
478 | private bool _isSelectingAll = false;
479 | private void ChooseAllFiles()
480 | {
481 | _isSelectingAll = true;
482 | foreach (var duplicateFile in _myModel.DuplicateFiles)
483 | {
484 | duplicateFile.IsCheckedFile = true;
485 | }
486 | var keyList = _myModel.DuplicateFiles.GroupBy(x => x.Key);
487 | foreach (var keyItem in keyList)
488 | {
489 | _myModel.DuplicateFiles.First(x => x.Key == keyItem.Key).IsCheckedFile = false;
490 | }
491 | _isSelectingAll = false;
492 | }
493 |
494 |
495 | private void RemoveChooseAllFiles()
496 | {
497 | foreach (var duplicateFile in _myModel.DuplicateFiles)
498 | {
499 | duplicateFile.IsCheckedFile = false;
500 | }
501 | }
502 | private void BtnDeleteFile_Click(object sender, RoutedEventArgs e)
503 | {
504 | try
505 | {
506 | if (_myModel.DuplicateFiles.Count == 0)
507 | {
508 | MessageBox.Show("没有可用数据", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Information);
509 | return;
510 | }
511 |
512 | var selectCount = _myModel.DuplicateFiles.Count(x => x.IsCheckedFile);
513 | if (MessageBox.Show($"确认要删除选中文件吗?文件删除后不可恢复!{System.Environment.NewLine}待删除的文件总数:{selectCount}", "重复文件查找", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
514 | {
515 | return;
516 | }
517 |
518 | //删除文件
519 | for (int i = _myModel.DuplicateFiles.Count - 1; i >= 0; i--)
520 | {
521 | if (_myModel.DuplicateFiles[i].IsCheckedFile == false)
522 | {
523 | continue;
524 | }
525 |
526 | string fileFullName = _myModel.DuplicateFiles[i].Path;
527 | string directory = Path.GetDirectoryName(fileFullName);
528 | if (File.Exists(fileFullName))
529 | {
530 | File.Delete(fileFullName);
531 | }
532 | if (ChkDeleteEmptyDirectory.IsChecked == true && Directory.GetDirectories(directory).Length == 0 && Directory.GetFiles(directory).Length == 0)
533 | {
534 | Directory.Delete(directory);
535 | }
536 |
537 | _myModel.DuplicateFiles.RemoveAt(i);
538 | }
539 |
540 | //文件删除后,清除不再重复的文件
541 | var singleKeys = _myModel.DuplicateFiles
542 | .GroupBy(x => x.Key).Select(x => new { Key = x.Key, Count = x.Count() })
543 | .Where(x => x.Count < 2)
544 | .Select(x => x.Key)
545 | .ToList();
546 | for (int i = _myModel.DuplicateFiles.Count - 1; i >= 0; i--)
547 | {
548 | if (!singleKeys.Contains(_myModel.DuplicateFiles[i].Key))
549 | {
550 | continue;
551 | }
552 | _myModel.DuplicateFiles.RemoveAt(i);
553 | }
554 | MessageBox.Show("删除完成", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Information);
555 | }
556 | catch (Exception ex)
557 | {
558 | MessageBox.Show($"删除失败:{ex.Message}", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Error);
559 | }
560 | }
561 |
562 | private void ChkIsChecked_Checked(object sender, RoutedEventArgs e)
563 | {
564 | if (_isSelectingAll)
565 | {
566 | //全选操作时,不处理事件
567 | return;
568 | }
569 |
570 | var chk = (sender as CheckBox);
571 | if (chk == null)
572 | {
573 | MessageBox.Show("选择失败,系统内部错误", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Error);
574 | return;
575 | }
576 |
577 | if (chk.DataContext is not DuplicateFileModel checkedFile)
578 | {
579 | MessageBox.Show("选择失败,系统内部错误", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Error);
580 | return;
581 | }
582 |
583 | if (!_myModel.DuplicateFiles.Any(x => x.Key == checkedFile.Key && x.IsCheckedFile == false))
584 | {
585 | MessageBox.Show("必须至少保留重复文件中的一个", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Stop);
586 | chk.IsChecked = false;
587 | return;
588 | }
589 | }
590 |
591 | private void SaveAppConfig()
592 | {
593 | var directory = Path.GetDirectoryName(GlobalArgs.AppConfigPath) ?? throw new ArgumentException("配置文件路径异常");
594 | if (!Directory.Exists(directory))
595 | {
596 | Directory.CreateDirectory(directory);
597 | }
598 | string appConfigString = System.Text.Json.JsonSerializer.Serialize(GlobalArgs.AppConfig);
599 | File.WriteAllText(GlobalArgs.AppConfigPath, appConfigString);
600 | }
601 |
602 | private void ListBoxSearchFolders_Drop(object sender, DragEventArgs e)
603 | {
604 | var directories = (string[])e.Data.GetData(DataFormats.FileDrop);
605 | if (directories == null)
606 | {
607 | return;
608 | }
609 |
610 | foreach (var directory in directories)
611 | {
612 | if (!Directory.Exists(directory))
613 | {
614 | continue;
615 | }
616 | AddSearchFolderOnly(directory);
617 | }
618 | }
619 |
620 | private void BtnFilter_Click(object sender, RoutedEventArgs e)
621 | {
622 | if (grid1.RowDefinitions[1].Height.GridUnitType == GridUnitType.Pixel)
623 | {
624 | grid1.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Auto);
625 | }
626 | else
627 | {
628 | grid1.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Pixel);
629 | }
630 | }
631 |
632 | private void RadioButtonFilter_Checked(object sender, RoutedEventArgs e)
633 | {
634 | var directory = _myModel.SearchDirectory.FirstOrDefault(x => x.IsSelected)?.DirectoryName;
635 | if (directory.IsEmpty())
636 | {
637 | MessageBox.Show("出错了:获取目录失败!", "重复文件查找", MessageBoxButton.OK, MessageBoxImage.Error);
638 | return;
639 | }
640 |
641 | //清除上次选中的遗留状态
642 | foreach (var duplicateFile in _myModel.DuplicateFiles)
643 | {
644 | duplicateFile.IsCheckedFile = false;
645 | }
646 |
647 | //查找所选文件夹下所有重复文件的 key
648 | var filterFilesKeys = _myModel.DuplicateFiles.Where(x => x.Path.IndexOf(directory) == 0).Select(x => x.Key).ToList();
649 | foreach (var duplicateFile in _myModel.DuplicateFiles)
650 | {
651 | if (duplicateFile.Path.IndexOf(directory) != 0)
652 | {
653 | continue;
654 | }
655 |
656 | if (!filterFilesKeys.Contains(duplicateFile.Key))
657 | {
658 | continue;
659 | }
660 | duplicateFile.IsCheckedFile = true;
661 | }
662 | }
663 |
664 | private void ChkOnlyFileName_Checked(object sender, RoutedEventArgs e)
665 | {
666 | TxtOnlyFileNames.IsEnabled = true;
667 | }
668 |
669 | private void ChkOnlyFileName_Unchecked(object sender, RoutedEventArgs e)
670 | {
671 | TxtOnlyFileNames.Text = "";
672 | TxtOnlyFileNames.IsEnabled = false;
673 | }
674 |
675 | }
676 | }
677 |
--------------------------------------------------------------------------------
/src/Model/AppConfigInfo.cs:
--------------------------------------------------------------------------------
1 | namespace FindDuplicateFiles.Model
2 | {
3 | public class AppConfigInfo
4 | {
5 | public string Theme { get; set; } = "Dark";
6 | public string ImageExtension { get; set; } = ".gif;.jpg;.jpeg;.png;.bmp";
7 | public string MediaExtension { get; set; } = ".mp3;.wma;.wav;.mpeg;.avi;.mov;.wmv;.rm;.rmvb;.flv";
8 | public string SystemExtension { get; set; } = ".dll";
9 | public string DocumentExtension { get; set; } = ".txt;.pdf;.doc;.docx;.xls;.xlsx;.ppt;.pptx";
10 |
11 | ///
12 | /// 每次查询允许的最大重复文件数量
13 | ///
14 | public int MaxAllowDuplicateFileCount { get; set; } = 500;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Model/SearchConfigs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace FindDuplicateFiles.Model
5 | {
6 | public class SearchConfigs
7 | {
8 | public List Folders { get; set; }
9 | public SearchMatchEnum SearchMatch { get; set; }
10 | public SearchOptionEnum SearchOption { get; set; }
11 | public SearchOptionData SearchOptionData { get; set; }
12 | }
13 |
14 | ///
15 | /// 匹配方式
16 | ///
17 | [Flags]
18 | public enum SearchMatchEnum
19 | {
20 | Name = 1,
21 | Size = 2,
22 | LastWriteTime = 4,
23 | MD5 = 8,
24 | }
25 | ///
26 | /// 查找选项
27 | ///
28 | [Flags]
29 | public enum SearchOptionEnum
30 | {
31 | IgnoreEmptyFile = 1,
32 | IgnoreHiddenFile = 2,
33 | IgnoreSmallFile = 4,
34 | OnlyImageFile = 8,
35 | OnlyMediaFile = 16,
36 | OnlyDocumentFile = 32,
37 | IgnoreSystemFile = 64,
38 | OnlyFileName = 128
39 | }
40 |
41 | ///
42 | /// 查找选项的数据
43 | ///
44 | public class SearchOptionData
45 | {
46 | public List OnlyFileNames { get; set; }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Model/SimpleFileInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FindDuplicateFiles.Model
4 | {
5 | public class SimpleFileInfo
6 | {
7 | public string Name { get; set; }
8 | public string Path { get; set; }
9 | public decimal Size { get; set; }
10 | public DateTime LastWriteTime { get; set; }
11 | ///
12 | /// 文件后缀名
13 | ///
14 | public string Extension { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Model/UpdateAppInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace FindDuplicateFiles.Model
8 | {
9 | public class UpdateAppInfo
10 | {
11 | public int code { get; set; }
12 | public string message { get; set; }
13 | public Data data { get; set; }
14 | }
15 |
16 | public class Data
17 | {
18 | public int versionCode { get; set; }
19 | public string versionName { get; set; }
20 | public string minVersionName { get; set; }
21 | public string downloadUrl { get; set; }
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/Model/UpdateConfigInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace FindDuplicateFiles.Model
8 | {
9 | public class UpdateConfigInfo
10 | {
11 | public string Url { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Resource.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace FindDuplicateFiles {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resource {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resource() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FindDuplicateFiles.Resource", typeof(Resource).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 重写当前线程的 CurrentUICulture 属性,对
51 | /// 使用此强类型资源类的所有资源查找执行重写。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// 查找类似 http://api.jiuling.cc/app/check-update/581671619f5131a82ea6b813329be6f2/windows 的本地化字符串。
65 | ///
66 | internal static string AutoUpgradePath {
67 | get {
68 | return ResourceManager.GetString("AutoUpgradePath", resourceCulture);
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Resource.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | http://api.jiuling.cc/app/check-update/581671619f5131a82ea6b813329be6f2/windows
122 |
123 |
--------------------------------------------------------------------------------
/src/SearchFile/CheckDuplicateQueue.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using FindDuplicateFiles.Model;
7 |
8 | namespace FindDuplicateFiles.SearchFile
9 | {
10 | public class CheckDuplicateQueue
11 | {
12 | ///
13 | /// 是否停止
14 | ///
15 | private bool _isStop;
16 | ///
17 | /// 用于处理文件的队列
18 | ///
19 | private readonly ConcurrentQueue _fileProcessing = new();
20 | ///
21 | /// 任务是否添加完成
22 | ///
23 | private bool _isFinished;
24 | ///
25 | /// 匹配方式
26 | ///
27 | private SearchMatchEnum _searchMatch;
28 |
29 | private static readonly SemaphoreSlim MySemaphoreSlim = new(1, 2);
30 | private readonly ConcurrentDictionary> _duplicateFiles = new();
31 |
32 | ///
33 | /// 执行任务的消息
34 | ///
35 | public Action EventMessage;
36 | ///
37 | /// 发现重复文件
38 | ///
39 | public Action EventDuplicateFound;
40 | ///
41 | /// 搜索完成
42 | ///
43 | public Action EventSearchFinished;
44 |
45 | ///
46 | /// 正在执行的任务总数
47 | ///
48 | private int _searchingCount;
49 | public async void Start(SearchMatchEnum searchMatch)
50 | {
51 | _isStop = false;
52 | _isFinished = false;
53 | EventMessage?.Invoke("准备查找....");
54 | await Task.Run(() =>
55 | {
56 | _searchMatch = searchMatch;
57 | while (!_isStop)
58 | {
59 | if (!_fileProcessing.TryDequeue(out var fileInfo))
60 | {
61 | if (_isFinished)
62 | {
63 | return;
64 | }
65 | continue;
66 | }
67 |
68 | Task.Run(() =>
69 | {
70 | SearchDuplicate(fileInfo);
71 | });
72 | }
73 | });
74 | }
75 | ///
76 | /// 查找重复文件的具体逻辑
77 | ///
78 | ///
79 | private async void SearchDuplicate(SimpleFileInfo fileInfo)
80 | {
81 | Interlocked.Increment(ref _searchingCount);
82 | await MySemaphoreSlim.WaitAsync();
83 | try
84 | {
85 | if (_isStop)
86 | {
87 | return;
88 | }
89 | EventMessage?.Invoke($"检查文件:{fileInfo.Path}");
90 | string fileKey = "";
91 | if ((_searchMatch & SearchMatchEnum.Name) == SearchMatchEnum.Name)
92 | {
93 | fileKey = fileInfo.Name;
94 | }
95 |
96 | if ((_searchMatch & SearchMatchEnum.Size) == SearchMatchEnum.Size)
97 | {
98 | fileKey = $"{fileKey}${fileInfo.Size}";
99 | }
100 |
101 | if ((_searchMatch & SearchMatchEnum.LastWriteTime) == SearchMatchEnum.LastWriteTime)
102 | {
103 | fileKey = $"{fileKey}${fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}";
104 | }
105 |
106 | if ((_searchMatch & SearchMatchEnum.MD5) == SearchMatchEnum.MD5)
107 | {
108 | string md5 = JiuLing.CommonLibs.Security.MD5Utils.GetFileValueToLower(fileInfo.Path);
109 | fileKey = $"{fileKey}${md5}";
110 | }
111 |
112 | var newFile = new List { fileInfo };
113 | var resultFile = _duplicateFiles.AddOrUpdate(fileKey, newFile, (_, oldValue) =>
114 | {
115 | oldValue.Add(fileInfo);
116 | return oldValue;
117 | });
118 | if (resultFile.Count > 1)
119 | {
120 | //有重复文件
121 | if (resultFile.Count == 2)
122 | {
123 | //如果是第一次发现重复,则需要连同之前一次的文件信息一并通知
124 | EventDuplicateFound?.Invoke(fileKey, resultFile[0]);
125 | }
126 |
127 | EventDuplicateFound?.Invoke(fileKey, fileInfo);
128 | }
129 | }
130 | finally
131 | {
132 | MySemaphoreSlim.Release();
133 | if (Interlocked.Decrement(ref _searchingCount) == 0 && _isFinished)
134 | {
135 | EventSearchFinished.Invoke();
136 | }
137 | }
138 | }
139 |
140 | ///
141 | /// 任务完成
142 | ///
143 | public void Finished()
144 | {
145 | _isFinished = true;
146 | }
147 |
148 | public void AddOneFileToQueue(SimpleFileInfo file)
149 | {
150 | _fileProcessing.Enqueue(file);
151 | }
152 |
153 | public void AddMultipleFilesToQueue(List files)
154 | {
155 | foreach (var file in files)
156 | {
157 | _fileProcessing.Enqueue(file);
158 | }
159 | }
160 |
161 | public void Stop()
162 | {
163 | _isStop = true;
164 | EventMessage?.Invoke("任务被终止");
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/SearchFile/SearchFilesJob.cs:
--------------------------------------------------------------------------------
1 | using FindDuplicateFiles.Common;
2 | using FindDuplicateFiles.Filters;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 | using FindDuplicateFiles.Model;
9 |
10 | namespace FindDuplicateFiles.SearchFile
11 | {
12 | public class SearchFilesJob
13 | {
14 | ///
15 | /// 是否停止
16 | ///
17 | private bool _isStop;
18 |
19 | ///
20 | /// 执行任务的消息
21 | ///
22 | public Action EventMessage;
23 |
24 | ///
25 | /// 文件查找任务队列
26 | ///
27 | private CheckDuplicateQueue _checkDuplicateQueue;
28 |
29 | ///
30 | /// 发现重复文件
31 | ///
32 | public Action EventDuplicateFound;
33 | ///
34 | /// 搜索完成
35 | ///
36 | public Action EventSearchFinished;
37 |
38 | private SearchConfigs _searchConfig;
39 | public async void Start(SearchConfigs config)
40 | {
41 | _isStop = false;
42 | _searchConfig = config;
43 | await Task.Run(() =>
44 | {
45 | _checkDuplicateQueue = new CheckDuplicateQueue
46 | {
47 | EventDuplicateFound = EventDuplicateFound,
48 | EventMessage = EventMessage,
49 | EventSearchFinished = EventSearchFinished
50 | };
51 |
52 | _checkDuplicateQueue.Start(_searchConfig.SearchMatch);
53 | foreach (string folderPath in _searchConfig.Folders)
54 | {
55 | EachDirectory(folderPath, paths =>
56 | {
57 | CalcFilesInfo(paths);
58 | });
59 | }
60 |
61 | _checkDuplicateQueue.Finished();
62 | });
63 | }
64 |
65 | private void EachDirectory(string folderPath, Action> callbackFilePaths)
66 | {
67 | try
68 | {
69 | if (_isStop)
70 | {
71 | return;
72 | }
73 |
74 | if (!Directory.Exists(folderPath))
75 | {
76 | return;
77 | }
78 | Directory.GetDirectories(folderPath).ToList().ForEach(path =>
79 | {
80 | //继续遍历文件夹内容
81 | EachDirectory(path, callbackFilePaths);
82 | });
83 |
84 | callbackFilePaths.Invoke(Directory.GetFiles(folderPath).ToList());
85 | }
86 | catch (UnauthorizedAccessException)
87 | {
88 | //todo 没有权限时记录错误
89 | }
90 | }
91 |
92 | private void CalcFilesInfo(List paths)
93 | {
94 | EventMessage?.Invoke($"读取文件:{string.Join(",", paths)}");
95 | //根据路径加载文件信息
96 | var files = paths.Select(path => new FileInfo(path)).ToList();
97 |
98 | //条件过滤器
99 | if ((_searchConfig.SearchOption & SearchOptionEnum.IgnoreEmptyFile) == SearchOptionEnum.IgnoreEmptyFile)
100 | {
101 | IFileSearchFilter filter = new IgnoreEmptyFileFilter();
102 | files = filter.FilterByCondition(files);
103 | }
104 | if ((_searchConfig.SearchOption & SearchOptionEnum.IgnoreHiddenFile) == SearchOptionEnum.IgnoreHiddenFile)
105 | {
106 | IFileSearchFilter filter = new IgnoreHiddenFileFilter();
107 | files = filter.FilterByCondition(files);
108 | }
109 | if ((_searchConfig.SearchOption & SearchOptionEnum.IgnoreSmallFile) == SearchOptionEnum.IgnoreSmallFile)
110 | {
111 | IFileSearchFilter filter = new IgnoreSmallFileFilter(1024);
112 | files = filter.FilterByCondition(files);
113 | }
114 | if ((_searchConfig.SearchOption & SearchOptionEnum.IgnoreSystemFile) == SearchOptionEnum.IgnoreSystemFile)
115 | {
116 | IFileSearchFilter filter = new IgnoreExtensionFilter(GlobalArgs.AppConfig.SystemExtension.Split(';').ToList());
117 | files = filter.FilterByCondition(files);
118 | }
119 | if ((_searchConfig.SearchOption & SearchOptionEnum.OnlyDocumentFile) == SearchOptionEnum.OnlyDocumentFile)
120 | {
121 | IFileSearchFilter filter = new OnlyExtensionFilter(GlobalArgs.AppConfig.DocumentExtension.Split(';').ToList());
122 | files = filter.FilterByCondition(files);
123 | }
124 | if ((_searchConfig.SearchOption & SearchOptionEnum.OnlyImageFile) == SearchOptionEnum.OnlyImageFile)
125 | {
126 | IFileSearchFilter filter = new OnlyExtensionFilter(GlobalArgs.AppConfig.ImageExtension.Split(';').ToList());
127 | files = filter.FilterByCondition(files);
128 | }
129 | if ((_searchConfig.SearchOption & SearchOptionEnum.OnlyMediaFile) == SearchOptionEnum.OnlyMediaFile)
130 | {
131 | IFileSearchFilter filter = new OnlyExtensionFilter(GlobalArgs.AppConfig.MediaExtension.Split(';').ToList());
132 | files = filter.FilterByCondition(files);
133 | }
134 | if ((_searchConfig.SearchOption & SearchOptionEnum.OnlyFileName) == SearchOptionEnum.OnlyFileName)
135 | {
136 | IFileSearchFilter filter = new OnlyFileNameFilter(_searchConfig.SearchOptionData.OnlyFileNames);
137 | files = filter.FilterByCondition(files);
138 | }
139 | files.ForEach(file =>
140 | {
141 | //符合条件的文件写入队列
142 | var simpleInfo = new SimpleFileInfo()
143 | {
144 | Name = file.Name,
145 | Path = file.FullName,
146 | Size = file.Length,
147 | LastWriteTime = file.LastWriteTime,
148 | Extension = file.Extension.ToLower()
149 | };
150 | _checkDuplicateQueue.AddOneFileToQueue(simpleInfo);
151 | });
152 | }
153 | public void Stop()
154 | {
155 | _isStop = true;
156 | _checkDuplicateQueue.Stop();
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/Styles/AboutWindowStyle.xaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
24 |
25 |
33 |
34 |
42 |
43 |
49 |
50 |
57 |
58 |
66 |
67 |
72 |
73 |
76 |
77 |
82 |
--------------------------------------------------------------------------------
/src/Styles/MainWindowStyle.xaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
33 |
34 |
35 |
49 |
65 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
87 |
92 |
97 |
102 |
105 |
108 |
111 |
112 |
115 |
116 |
120 |
121 |
124 |
125 |
130 |
131 |
136 |
137 |
140 |
141 |
144 |
145 |
148 |
149 |
153 |
154 |
161 |
162 |
166 |
167 |
--------------------------------------------------------------------------------
/src/Themes/ThemeDark.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/Themes/ThemeGreen.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/ViewModel/AboutWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace FindDuplicateFiles.ViewModel
2 | {
3 | public class AboutWindowViewModel : ViewModelBase
4 | {
5 | private string _downloadUrl;
6 | ///
7 | /// 下载地址
8 | ///
9 | public string DownloadUrl
10 | {
11 | get => _downloadUrl;
12 | set
13 | {
14 | _downloadUrl = value;
15 | OnPropertyChanged();
16 | }
17 | }
18 |
19 | private string _version;
20 | ///
21 | /// 版本
22 | ///
23 | public string Version
24 | {
25 | get => _version;
26 | set
27 | {
28 | _version = value;
29 | OnPropertyChanged();
30 | }
31 | }
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/ViewModel/DuplicateFileModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FindDuplicateFiles.ViewModel
4 | {
5 | public class DuplicateFileModel : ViewModelBase
6 | {
7 | private string _key;
8 | public string Key
9 | {
10 | get => _key;
11 | set
12 | {
13 | _key = value;
14 | OnPropertyChanged();
15 | }
16 | }
17 |
18 | private bool _isCheckedFile;
19 | public bool IsCheckedFile
20 | {
21 | get => _isCheckedFile;
22 | set
23 | {
24 | _isCheckedFile = value;
25 | OnPropertyChanged();
26 | }
27 | }
28 |
29 | private string _name;
30 | public string Name
31 | {
32 | get => _name;
33 | set
34 | {
35 | _name = value;
36 | OnPropertyChanged();
37 | }
38 | }
39 |
40 | private string _path;
41 | public string Path
42 | {
43 | get => _path;
44 | set
45 | {
46 | _path = value;
47 | OnPropertyChanged();
48 | }
49 | }
50 |
51 | private decimal _size;
52 | public decimal Size
53 | {
54 | get => _size;
55 | set
56 | {
57 | _size = value;
58 | OnPropertyChanged();
59 | }
60 | }
61 |
62 | private DateTime _lastWriteTime;
63 | public DateTime LastWriteTime
64 | {
65 | get => _lastWriteTime;
66 | set
67 | {
68 | _lastWriteTime = value;
69 | OnPropertyChanged();
70 | }
71 | }
72 |
73 |
74 | private string _extension;
75 | public string Extension
76 | {
77 | get => _extension;
78 | set
79 | {
80 | _extension = value;
81 | OnPropertyChanged();
82 | }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/ViewModel/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace FindDuplicateFiles.ViewModel
4 | {
5 | public class MainWindowViewModel : ViewModelBase
6 | {
7 | public MainWindowViewModel()
8 | {
9 | SearchDirectory = new ObservableCollection();
10 | DuplicateFiles = new ObservableCollection();
11 | }
12 |
13 | private ObservableCollection _searchDirectory;
14 | ///
15 | /// 要搜索的文件夹集合
16 | ///
17 | public ObservableCollection SearchDirectory
18 | {
19 | get => _searchDirectory;
20 | set
21 | {
22 | _searchDirectory = value;
23 | OnPropertyChanged();
24 | }
25 | }
26 |
27 |
28 | private ObservableCollection _duplicateFiles;
29 | ///
30 | /// 重复文件集合
31 | ///
32 | public ObservableCollection DuplicateFiles
33 | {
34 | get => _duplicateFiles;
35 | set
36 | {
37 | _duplicateFiles = value;
38 | OnPropertyChanged();
39 | }
40 | }
41 |
42 | private bool _isShowLoading;
43 | ///
44 | /// 是否显示loading窗口
45 | ///
46 | public bool IsShowLoading
47 | {
48 | get => _isShowLoading;
49 | set
50 | {
51 | _isShowLoading = value;
52 | OnPropertyChanged();
53 | }
54 | }
55 |
56 | private string _jobMessage;
57 | ///
58 | /// 任务消息
59 | ///
60 | public string JobMessage
61 | {
62 | get => _jobMessage;
63 | set
64 | {
65 | _jobMessage = value;
66 | OnPropertyChanged();
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/ViewModel/SearchDirectoryModel.cs:
--------------------------------------------------------------------------------
1 | namespace FindDuplicateFiles.ViewModel
2 | {
3 | public class SearchDirectoryModel : ViewModelBase
4 | {
5 | private string _directoryName;
6 | ///
7 | /// 已选取的要搜索的文件夹
8 | ///
9 | public string DirectoryName
10 | {
11 | get => _directoryName;
12 | set
13 | {
14 | _directoryName = value;
15 | OnPropertyChanged();
16 | }
17 | }
18 |
19 | private bool _isSelected;
20 | ///
21 | /// 是否选中
22 | ///
23 | public bool IsSelected
24 | {
25 | get => _isSelected;
26 | set
27 | {
28 | _isSelected = value;
29 | OnPropertyChanged();
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/ViewModel/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace FindDuplicateFiles.ViewModel
5 | {
6 | public class ViewModelBase : INotifyPropertyChanged
7 | {
8 | public event PropertyChangedEventHandler PropertyChanged;
9 |
10 | public void OnPropertyChanged([CallerMemberName] string caller = null)
11 | {
12 | var handler = PropertyChanged;
13 | if (handler != null)
14 | {
15 | handler(this, new PropertyChangedEventArgs(caller));
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiuLing-zhang/FindDuplicateFiles/510fd287ee0538f2bf3f6c25d215ac1abc119dcf/src/icon.ico
--------------------------------------------------------------------------------
/src/首次运行请看这里!!!.txt:
--------------------------------------------------------------------------------
1 | 首次运行时需要安装.Net7运行时,下载地址:
2 | https://aka.ms/dotnet/7.0/windowsdesktop-runtime-win-x64.exe
--------------------------------------------------------------------------------