Skip to content

Commit 3a8fa57

Browse files
committed
[Feat] Improve performance by adding icon caching and limiting the maximum number of results
1 parent 56eaae4 commit 3a8fa57

File tree

7 files changed

+180
-41
lines changed

7 files changed

+180
-41
lines changed

FaviconFetcher.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using Community.PowerToys.Run.Plugin.TabPort.util;
34
using Microsoft.Data.Sqlite;
45
using Wox.Plugin.Logger;
@@ -8,6 +9,11 @@ namespace Community.PowerToys.Run.Plugin.TabPort;
89
public class FaviconFetcher
910
{
1011
private const int MaxRetryCount = 1;
12+
private const int CacheCapacity = 500;
13+
14+
private static readonly LruCache<string, byte[]> FaviconCache = new(CacheCapacity);
15+
16+
public static void ClearCache() => FaviconCache.Clear();
1117

1218
private const string FirefoxQuery =
1319
"""
@@ -33,6 +39,15 @@ public static byte[] FetchFaviconLocalDatabase(string domain, int retryCount = 0
3339
if (TemporarilyDisabled) return null;
3440
if (retryCount > MaxRetryCount) return null;
3541

42+
var hostName = GetHostName(domain);
43+
if (hostName == null) return null;
44+
45+
// Check cache first
46+
if (FaviconCache.TryGet(hostName, out var cached))
47+
{
48+
return cached.Length == 0 ? null : cached;
49+
}
50+
3651
try
3752
{
3853
var connection = SqliteConnectionUtils.Instance.GetConnection();
@@ -41,7 +56,7 @@ public static byte[] FetchFaviconLocalDatabase(string domain, int retryCount = 0
4156
: ChromiumQuery;
4257

4358
using var command = new SqliteCommand(query, connection);
44-
command.Parameters.AddWithValue("@domain", $"%{GetHostName(domain)}%");
59+
command.Parameters.AddWithValue("@domain", $"%{hostName}%");
4560

4661
using var reader = command.ExecuteReader();
4762
if (reader.Read())
@@ -56,8 +71,11 @@ public static byte[] FetchFaviconLocalDatabase(string domain, int retryCount = 0
5671
offset += (int)bytesRead;
5772
}
5873

74+
FaviconCache.Set(hostName, icoBlob);
5975
return icoBlob;
6076
}
77+
78+
FaviconCache.Set(hostName, []);
6179
}
6280
catch (SqliteException ex)
6381
{
@@ -77,6 +95,13 @@ public static byte[] FetchFaviconLocalDatabase(string domain, int retryCount = 0
7795

7896
private static string GetHostName(string url)
7997
{
80-
return new Uri(url).Host;
98+
try
99+
{
100+
return new Uri(url).Host;
101+
}
102+
catch
103+
{
104+
return null;
105+
}
81106
}
82107
}

Main.cs

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,9 @@ public void UpdateSettings(PowerLauncherPluginSettings settings)
117117
x.Key == nameof(Setting.FaviconDbPathPriority))?.ComboBoxValue;
118118
if (comboBoxValue != null)
119119
Setting.FaviconDbPathPriority = (Settings.FaviconDbPathPriorityItem)comboBoxValue;
120-
/*if (Setting.Port != null)
121-
{
122-
WsUtils.RestartWebSocketServer(Setting.Port);
123-
}*/
124-
120+
Setting.MaxResults = Convert.ToInt32(settings.AdditionalOptions.FirstOrDefault(x =>
121+
x.Key == nameof(Setting.MaxResults))?.NumberValue ?? 20);
122+
FaviconFetcher.ClearCache();
125123
}
126124

127125
public IEnumerable<PluginAdditionalOption> AdditionalOptions => Setting.AdditionalOptions;
@@ -135,43 +133,62 @@ public List<Result> Query(Query query, bool delayedExecution)
135133
{
136134
browserTabs.AddRange(RuntimeStaticData.BrowserTabData[key].Select(tab => Tuple.Create(key, tab)));
137135
}
138-
return browserTabs.Select(tuple =>
136+
137+
var maxResults = Setting.MaxResults;
138+
139+
var scored = browserTabs.Select(tuple =>
140+
{
141+
var tab = tuple.Item2;
142+
var score = string.IsNullOrEmpty(search)
143+
? 0
144+
: (int)(StringMatcher.FuzzySearch(search, tab.Title).Score * Setting.TitleWeight +
145+
StringMatcher.FuzzySearch(search, tab.Url).Score * Setting.UrlWeight);
146+
return (tuple, score);
147+
});
148+
149+
var filtered = string.IsNullOrEmpty(search)
150+
? scored
151+
: scored.Where(x => x.score > 0);
152+
153+
var topResults = filtered
154+
.OrderByDescending(x => x.score)
155+
.Take(maxResults);
156+
157+
return [.. topResults.Select(x =>
158+
{
159+
var browserTab = x.tuple.Item2;
160+
var browser = x.tuple.Item1;
161+
var faviconBin = FaviconFetcher.FetchFaviconLocalDatabase(browserTab.Url);
162+
163+
var result = new Result
139164
{
140-
var browserTab = tuple.Item2;
141-
var browser = tuple.Item1;
142-
var faviconBin = FaviconFetcher.FetchFaviconLocalDatabase(browserTab.Url);
143-
var result = new Result
165+
QueryTextDisplay = browserTab.Title,
166+
Title = browserTab.Title,
167+
SubTitle = browserTab.Url,
168+
ToolTipData = new ToolTipData(browserTab.Title, browserTab.Status),
169+
Action = context =>
144170
{
145-
QueryTextDisplay = browserTab.Title,
146-
Title = browserTab.Title,
147-
SubTitle = browserTab.Url,
148-
ToolTipData = new ToolTipData(browserTab.Title, browserTab.Status),
149-
Action = context =>
150-
{
151-
Clipboard.SetDataObject(browserTab.Title);
152-
RuntimeStaticData.SwitchToTabAction(browserTab);
153-
_ = WebSocketUtils.SendMessageAsync($"switch {browserTab.Id} {browserTab.WindowId}",
154-
browser);
155-
return true;
156-
},
157-
ContextData = new Tuple<WebSocket, BrowserTab>(browser, browserTab),
158-
Score =
159-
(int)(StringMatcher.FuzzySearch(search, browserTab.Title).Score * Setting.TitleWeight +
160-
StringMatcher.FuzzySearch(search, browserTab.Url).Score * Setting.UrlWeight),
161-
Glyph = "\xE838"
162-
};
163-
if (faviconBin != null && faviconBin.Length > 0)
164-
{
165-
result.Icon = () => GetImageSourceFromRawPngData(faviconBin);
166-
}
167-
else
168-
{
169-
result.IcoPath = IconPath;
170-
}
171+
Clipboard.SetDataObject(browserTab.Title);
172+
RuntimeStaticData.SwitchToTabAction(browserTab);
173+
_ = WebSocketUtils.SendMessageAsync($"switch {browserTab.Id} {browserTab.WindowId}",
174+
browser);
175+
return true;
176+
},
177+
ContextData = new Tuple<WebSocket, BrowserTab>(browser, browserTab),
178+
Score = x.score,
179+
Glyph = "\xE838"
180+
};
181+
if (faviconBin != null && faviconBin.Length > 0)
182+
{
183+
result.Icon = () => GetImageSourceFromRawPngData(faviconBin);
184+
}
185+
else
186+
{
187+
result.IcoPath = IconPath;
188+
}
171189

172-
return result;
173-
})
174-
.ToList();
190+
return result;
191+
})];
175192
}
176193

177194
private static BitmapImage GetImageSourceFromRawPngData(byte[] rawPngData)

Properties/Resources.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Properties/Resources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,10 @@
6060
<data name="open_connection_sqlite_exception_toast_description" xml:space="preserve">
6161
<value>Website icon display will be temporarily disabled until reset.</value>
6262
</data>
63+
<data name="max_results" xml:space="preserve">
64+
<value>Max Results</value>
65+
</data>
66+
<data name="max_results_description" xml:space="preserve">
67+
<value>Maximum number of results to display. Reducing this value improves search performance when you have many tabs.</value>
68+
</data>
6369
</root>

Properties/Resources.zh-hans.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,10 @@
5353
<data name="url_weight" xml:space="preserve">
5454
<value>网站Url的权重</value>
5555
</data>
56+
<data name="max_results" xml:space="preserve">
57+
<value>最大结果数</value>
58+
</data>
59+
<data name="max_results_description" xml:space="preserve">
60+
<value>显示的最大结果数量。当标签页较多时,减小此值可提升搜索性能。</value>
61+
</data>
5662
</root>

Settings.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class Settings
1212
public string CustomFaviconDbPath { get; set; }
1313
public double TitleWeight { get; set; } = 1;
1414
public double UrlWeight { get; set; } = 0;
15+
public int MaxResults { get; set; } = 20;
1516
public FaviconDbPathPriorityItem FaviconDbPathPriority { get; set; }
1617

1718
public enum FaviconDbPathPriorityItem
@@ -67,6 +68,14 @@ public enum FaviconDbPathPriorityItem
6768
DisplayLabel = Resources.custom_favicon_db_path,
6869
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Textbox,
6970
TextValue = CustomFaviconDbPath
71+
},
72+
new()
73+
{
74+
Key = nameof(MaxResults),
75+
DisplayDescription = Resources.max_results_description,
76+
DisplayLabel = Resources.max_results,
77+
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Numberbox,
78+
NumberValue = MaxResults
7079
}
7180
];
7281
}

Util/LruCache.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Collections.Generic;
2+
using System.Threading;
3+
4+
namespace Community.PowerToys.Run.Plugin.TabPort;
5+
6+
public class LruCache<TKey, TValue>(int capacity)
7+
{
8+
private readonly Dictionary<TKey, LinkedListNode<(TKey Key, TValue Value)>> _map = new(capacity);
9+
private readonly LinkedList<(TKey Key, TValue Value)> _order = new();
10+
private readonly Lock _lock = new();
11+
12+
public bool TryGet(TKey key, out TValue value)
13+
{
14+
lock (_lock)
15+
{
16+
if (_map.TryGetValue(key, out var node))
17+
{
18+
_order.Remove(node);
19+
_order.AddFirst(node);
20+
value = node.Value.Value;
21+
return true;
22+
}
23+
24+
value = default;
25+
return false;
26+
}
27+
}
28+
29+
public void Set(TKey key, TValue value)
30+
{
31+
lock (_lock)
32+
{
33+
if (_map.TryGetValue(key, out var existing))
34+
{
35+
_order.Remove(existing);
36+
_map.Remove(key);
37+
}
38+
else if (_map.Count >= capacity)
39+
{
40+
var last = _order.Last!;
41+
_map.Remove(last.Value.Key);
42+
_order.RemoveLast();
43+
}
44+
45+
var node = _order.AddFirst((key, value));
46+
_map[key] = node;
47+
}
48+
}
49+
50+
public void Clear()
51+
{
52+
lock (_lock)
53+
{
54+
_map.Clear();
55+
_order.Clear();
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)