Skip to content

Commit 7f47499

Browse files
committed
优化 #45 #46
1 parent 0ee89d7 commit 7f47499

File tree

6 files changed

+201
-82
lines changed

6 files changed

+201
-82
lines changed

Straper/Straper.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</PropertyGroup>
1212
<ItemGroup>
1313
<PackageReference Include="CHTCHSConv" Version="1.0.0" />
14-
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
14+
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
1515
<PackageReference Include="Microsoft.NETCore.Platforms" Version="8.0.0-preview.7.23375.6" />
1616
</ItemGroup>
1717
<ItemGroup>

以图搜图/Form1.cs

Lines changed: 27 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -115,64 +115,46 @@ private async void btnIndex_Click(object sender, EventArgs e)
115115
return;
116116
}
117117

118-
if (string.IsNullOrEmpty(txtDirectory.Text))
118+
IndexRunning = true;
119+
btnIndex.Text = "停止索引";
120+
var dirs = string.IsNullOrWhiteSpace(txtDirectory.Text) ? PathPrefixFinder.FindLongestCommonPathPrefixes(_index.Keys.Union(_frameIndex.Keys), 3).Where(Directory.Exists).ToArray() : [txtDirectory.Text];
121+
if (dirs.Length == 0)
119122
{
120123
MessageBox.Show(this, "请先选择文件夹", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
124+
IndexRunning = false;
125+
btnIndex.Text = "更新索引";
121126
return;
122127
}
123128

129+
cbRemoveInvalidIndex.Hide();
130+
var imageHasher = new ImageHasher(new ImageSharpTransformer());
131+
lblProcess.Text = "正在扫描文件...";
132+
var files = File.Exists("Everything64.dll") && Process.GetProcessesByName("Everything").Length > 0 ? dirs.SelectMany(s =>
133+
{
134+
var array = EverythingHelper.EnumerateFiles(s).ToArray();
135+
return array.Length == 0 ? Directory.GetFiles(s, "*", SearchOption.AllDirectories) : array;
136+
}).ToArray() : dirs.SelectMany(s => Directory.GetFiles(s, "*", SearchOption.AllDirectories)).ToArray();
124137
if (cbRemoveInvalidIndex.Checked)
125138
{
126139
_ = Task.Run(() =>
127140
{
128-
foreach (var key in _index.Keys.Except(_index.Keys.GroupBy(x => string.Join('\\', x.Split('\\')[..2])).SelectMany(g =>
129-
{
130-
if (Directory.Exists(g.Key))
131-
{
132-
if (File.Exists("Everything64.dll") && Process.GetProcessesByName("Everything").Length > 0)
133-
{
134-
var files = EverythingHelper.EnumerateFiles(FindLCP(g.ToArray()), "*.jpg|*.jpeg|*.bmp|*.png|*.webp").ToList();
135-
if (files.Count == 0)
136-
{
137-
throw new Exception("所选目录未包含在everything索引范围,或文件数为0,请检查");
138-
}
139-
140-
return files;
141-
}
142-
143-
return Directory.EnumerateFiles(FindLCP(g.ToArray()), "*", new EnumerationOptions()
144-
{
145-
IgnoreInaccessible = true,
146-
AttributesToSkip = FileAttributes.System | FileAttributes.Temporary,
147-
RecurseSubdirectories = true
148-
}).Where(s => picRegex.IsMatch(s));
149-
}
150-
151-
return [];
152-
})).AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount * 4).Where(s => !File.Exists(s)))
141+
var allfiles = files;
142+
if (!string.IsNullOrWhiteSpace(txtDirectory.Text))
143+
{
144+
var alldirs = PathPrefixFinder.FindLongestCommonPathPrefixes(_index.Keys.Union(_frameIndex.Keys), 3).Where(Directory.Exists);
145+
allfiles = File.Exists("Everything64.dll") && Process.GetProcessesByName("Everything").Length > 0 ? alldirs.SelectMany(s =>
146+
{
147+
var array = EverythingHelper.EnumerateFiles(s).ToArray();
148+
return array.Length == 0 ? Directory.GetFiles(s, "*", SearchOption.AllDirectories) : array;
149+
}).ToArray() : alldirs.SelectMany(s => Directory.GetFiles(s, "*", SearchOption.AllDirectories)).ToArray();
150+
}
151+
152+
foreach (var key in _index.Keys.Except(allfiles).AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount * 4).Where(s => !File.Exists(s)))
153153
{
154154
_index.TryRemove(key, out _);
155155
}
156156

157-
foreach (var key in _frameIndex.Keys.Except(_frameIndex.Keys.GroupBy(x => string.Join('\\', x.Split('\\')[..2])).SelectMany(g =>
158-
{
159-
if (Directory.Exists(g.Key))
160-
{
161-
if (File.Exists("Everything64.dll") && Process.GetProcessesByName("Everything").Length > 0)
162-
{
163-
return EverythingHelper.EnumerateFiles(FindLCP(g.ToArray()), "*.gif");
164-
}
165-
166-
return Directory.EnumerateFiles(FindLCP(g.ToArray()), "*.gif", new EnumerationOptions()
167-
{
168-
IgnoreInaccessible = true,
169-
AttributesToSkip = FileAttributes.System | FileAttributes.Temporary,
170-
RecurseSubdirectories = true
171-
});
172-
}
173-
174-
return [];
175-
})).AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount * 4).Where(s => !File.Exists(s)))
157+
foreach (var key in _frameIndex.Keys.Except(allfiles).AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount * 4).Where(s => !File.Exists(s)))
176158
{
177159
_frameIndex.TryRemove(key, out _);
178160
}
@@ -187,12 +169,6 @@ private async void btnIndex_Click(object sender, EventArgs e)
187169
}).ConfigureAwait(false);
188170
}
189171

190-
IndexRunning = true;
191-
btnIndex.Text = "停止索引";
192-
cbRemoveInvalidIndex.Hide();
193-
var imageHasher = new ImageHasher(new ImageSharpTransformer());
194-
lblProcess.Text = "正在扫描文件...";
195-
var files = File.Exists("Everything64.dll") && Process.GetProcessesByName("Everything").Length > 0 ? EverythingHelper.EnumerateFiles(txtDirectory.Text).ToArray() : Directory.GetFiles(txtDirectory.Text, "*", SearchOption.AllDirectories);
196172
int? filesCount = files.Except(_index.Keys).Except(_frameIndex.Keys).Count(s => Regex.IsMatch(s, "(gif|jpg|jpeg|png|bmp|webp)$", RegexOptions.IgnoreCase));
197173
var local = new ThreadLocal<int>(true);
198174
var errors = new List<string>();
@@ -285,34 +261,6 @@ await Task.Run(() =>
285261
btnIndex.Text = "更新索引";
286262
}
287263

288-
private static string FindLCP(string[] strs)
289-
{
290-
if (strs == null || strs.Length == 0) return "";
291-
292-
// 找到最短的字符串,因为LCP不会超过最短字符串的长度
293-
string shortest = strs[0];
294-
for (int i = 1; i < strs.Length; i++)
295-
{
296-
if (strs[i].Length < shortest.Length)
297-
shortest = strs[i];
298-
}
299-
300-
// 逐字符比较,直到找到不匹配的字符
301-
for (int i = 0; i < shortest.Length; i++)
302-
{
303-
char c = shortest[i];
304-
for (int j = 1; j < strs.Length; j++)
305-
{
306-
// 如果当前索引越界或字符不匹配,则返回当前LCP
307-
if (i >= strs[j].Length || strs[j][i] != c)
308-
return shortest.Substring(0, i);
309-
}
310-
}
311-
312-
// 如果所有字符串共享整个最短字符串,则返回它
313-
return shortest;
314-
}
315-
316264
private void btnSearch_Click(object sender, EventArgs e)
317265
{
318266
var filename = txtPic.Text;

以图搜图/PathPrefixFinder.cs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
using Masuit.Tools.Systems;
2+
3+
namespace 以图搜图;
4+
5+
public static class PathPrefixFinder
6+
{
7+
/// <summary>
8+
/// 从路径列表中获取每个根路径组的最长公共路径前缀
9+
/// </summary>
10+
/// <param name="paths">路径字符串数组</param>
11+
/// <param name="depth">最小路径深度</param>
12+
/// <returns>最长公共路径前缀列表</returns>
13+
public static ISet<string> FindLongestCommonPathPrefixes(IEnumerable<string> paths, int depth = 2)
14+
{
15+
var results = new ConcurrentHashSet<string>();
16+
// 按照根路径分组(前depth层)
17+
paths.Select(path => new { Path = path, Segments = GetPathSegments(path) }).GroupBy(item => BuildPathFromSegments(item.Segments.Take(depth).ToList())).AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount * 4).ForAll(group =>
18+
{
19+
// 对每个组找到最长公共前缀
20+
var pathsInGroup = group.Select(g => g.Path).ToList();
21+
if (pathsInGroup.Count == 1)
22+
{
23+
// 只有一个路径,取其父目录
24+
var singlePath = pathsInGroup[0];
25+
var parentDir = Path.GetDirectoryName(singlePath);
26+
if (!string.IsNullOrEmpty(parentDir))
27+
{
28+
results.Add(parentDir);
29+
}
30+
}
31+
else
32+
{
33+
// 多个路径,找最长公共前缀
34+
var commonPrefix = FindLongestCommonPrefix(pathsInGroup);
35+
if (!string.IsNullOrEmpty(commonPrefix))
36+
{
37+
results.Add(commonPrefix);
38+
}
39+
}
40+
});
41+
return results;
42+
}
43+
44+
/// <summary>
45+
/// 找到多个路径的最长公共前缀目录
46+
/// </summary>
47+
private static string FindLongestCommonPrefix(List<string> paths)
48+
{
49+
if (paths.Count == 0) return string.Empty;
50+
if (paths.Count == 1)
51+
{
52+
var parentDir = Path.GetDirectoryName(paths[0]);
53+
return parentDir ?? string.Empty;
54+
}
55+
56+
// 将所有路径转换为路径段列表
57+
var allSegments = paths.Select(GetPathSegments).ToList();
58+
59+
// 找到最短路径的长度
60+
var minLength = allSegments.Min(segments => segments.Count);
61+
62+
// 逐层比较,找到公共前缀
63+
var commonSegments = new List<string>();
64+
for (int i = 0; i < minLength - 1; i++) // -1 是为了排除文件名
65+
{
66+
var currentSegment = allSegments[0][i];
67+
if (allSegments.All(segments => segments[i].Equals(currentSegment, StringComparison.OrdinalIgnoreCase)))
68+
{
69+
commonSegments.Add(currentSegment);
70+
}
71+
else
72+
{
73+
break;
74+
}
75+
}
76+
77+
// 如果没有公共前缀或公共前缀太短,返回空
78+
if (commonSegments.Count == 0)
79+
return string.Empty;
80+
81+
return BuildPathFromSegments(commonSegments);
82+
}
83+
84+
/// <summary>
85+
/// 获取路径段列表
86+
/// </summary>
87+
private static List<string> GetPathSegments(string path)
88+
{
89+
var segments = new List<string>();
90+
91+
// 处理Windows路径 (C:/)
92+
if (path.Length >= 2 && char.IsLetter(path[0]) && path[1] == ':')
93+
{
94+
segments.Add(path[..2]); // "C:"
95+
if (path.Length > 3)
96+
{
97+
var remaining = path[3..];
98+
if (!string.IsNullOrEmpty(remaining))
99+
{
100+
segments.AddRange(remaining.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries));
101+
}
102+
}
103+
}
104+
// 处理Unix路径 (/)
105+
else if (path.StartsWith("/"))
106+
{
107+
if (path.Length > 1)
108+
{
109+
var remaining = path[1..];
110+
if (!string.IsNullOrEmpty(remaining))
111+
{
112+
segments.AddRange(remaining.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries));
113+
}
114+
}
115+
}
116+
// 相对路径
117+
else
118+
{
119+
segments.AddRange(path.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries));
120+
}
121+
122+
return segments;
123+
}
124+
125+
/// <summary>
126+
/// 从路径段构建完整路径
127+
/// </summary>
128+
private static string BuildPathFromSegments(IList<string> segments)
129+
{
130+
if (segments.Count == 0) return string.Empty;
131+
132+
// Windows路径
133+
if (segments[0].Length == 2 && char.IsLetter(segments[0][0]) && segments[0][1] == ':')
134+
{
135+
if (segments.Count == 1)
136+
{
137+
return segments[0] + "\\";
138+
}
139+
140+
return string.Join("\\", segments);
141+
}
142+
// Unix路径
143+
return "/" + string.Join("/", segments);
144+
}
145+
}

以图搜图/Properties/PublishProfiles/FolderProfile.pubxml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
99
<PublishDir>bin\Release\publish</PublishDir>
1010
<PublishProtocol>FileSystem</PublishProtocol>
1111
<TargetFramework>net9.0-windows7.0</TargetFramework>
12-
<SelfContained>false</SelfContained>
12+
<SelfContained>true</SelfContained>
1313
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
1414
<PublishSingleFile>true</PublishSingleFile>
1515
<PublishReadyToRun>false</PublishReadyToRun>
16+
<PublishTrimmed>false</PublishTrimmed>
1617
</PropertyGroup>
1718
</Project>

以图搜图/以图搜图.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<Content Include="logo.ico" />
1818
</ItemGroup>
1919
<ItemGroup>
20-
<PackageReference Include="Masuit.Tools.Abstractions" Version="2025.4.4" />
20+
<PackageReference Include="Masuit.Tools.Abstractions" Version="2025.4.6" />
2121
</ItemGroup>
2222
<ItemGroup>
2323
<None Update="Everything64.dll">

以图搜图/以图搜图.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.5.2.0
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "以图搜图", "以图搜图.csproj", "{C14E06A2-8489-C82C-9607-73011F88247C}"
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+
{C14E06A2-8489-C82C-9607-73011F88247C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{C14E06A2-8489-C82C-9607-73011F88247C}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{C14E06A2-8489-C82C-9607-73011F88247C}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{C14E06A2-8489-C82C-9607-73011F88247C}.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 = {5A11159A-4AD3-4A1D-9D8F-178566EA8DD5}
24+
EndGlobalSection
25+
EndGlobal

0 commit comments

Comments
 (0)