mirror of
https://github.com/edu4rdshl/Strata.git
synced 2026-07-17 23:24:46 +00:00
perf: reduce main-thread work in the extension
- Clear the thumbnail cache asynchronously in batches instead of a synchronous enumerate + delete on the compositor thread - Prune the thumbnail path cache on item delete and clear-all - Compute the image/url/color row flags once per row instead of recomputing isUrl/isColor several times - Lower-case the excluded-app list once instead of on every read
This commit is contained in:
parent
6c4fa04221
commit
98307b8a2e
3 changed files with 62 additions and 26 deletions
|
|
@ -59,10 +59,10 @@ export default class StrataExtension extends Extension {
|
|||
this._daemonRestartAttempts = 0;
|
||||
|
||||
this._settings = this.getSettings();
|
||||
this._excludedApps = this._settings.get_strv('excluded-apps');
|
||||
this._excludedApps = this._readExcluded();
|
||||
this._readSizeLimits();
|
||||
this._settings.connectObject(
|
||||
'changed::excluded-apps', () => { this._excludedApps = this._settings.get_strv('excluded-apps'); },
|
||||
'changed::excluded-apps', () => { this._excludedApps = this._readExcluded(); },
|
||||
'changed::max-history', () => { this._readSizeLimits(); this._pushConfig(); },
|
||||
'changed::max-text-mb', () => { this._readSizeLimits(); this._pushConfig(); },
|
||||
'changed::max-image-mb', () => { this._readSizeLimits(); this._pushConfig(); },
|
||||
|
|
@ -392,23 +392,50 @@ export default class StrataExtension extends Extension {
|
|||
|
||||
this._historyClearedId = this._proxy.connectSignal('HistoryCleared',
|
||||
() => {
|
||||
try {
|
||||
const dir = `${GLib.get_user_cache_dir()}/strata/thumbnails`;
|
||||
const d = Gio.File.new_for_path(dir);
|
||||
if (d.query_exists(null)) {
|
||||
const en = d.enumerate_children(
|
||||
'standard::name', Gio.FileQueryInfoFlags.NONE, null);
|
||||
let info;
|
||||
while ((info = en.next_file(null))) {
|
||||
try { d.get_child(info.get_name()).delete(null); } catch { /* best-effort cleanup */ }
|
||||
}
|
||||
en.close(null);
|
||||
}
|
||||
} catch (e) { logError('cache clear failed', e); }
|
||||
this._clearThumbnailCache();
|
||||
this._addIdleSource(() => this._panel?.clearItems());
|
||||
});
|
||||
}
|
||||
|
||||
/** Delete every cached thumbnail without blocking the compositor:
|
||||
* enumerate and unlink asynchronously in batches. */
|
||||
_clearThumbnailCache() {
|
||||
const dir = Gio.File.new_for_path(
|
||||
`${GLib.get_user_cache_dir()}/strata/thumbnails`);
|
||||
dir.enumerate_children_async(
|
||||
'standard::name', Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT, null,
|
||||
(d, res) => {
|
||||
let en;
|
||||
try {
|
||||
en = d.enumerate_children_finish(res);
|
||||
} catch {
|
||||
return; // dir missing or unreadable
|
||||
}
|
||||
const step = () => {
|
||||
en.next_files_async(32, GLib.PRIORITY_DEFAULT, null, (e, r) => {
|
||||
let infos;
|
||||
try {
|
||||
infos = e.next_files_finish(r);
|
||||
} catch {
|
||||
infos = [];
|
||||
}
|
||||
if (infos.length === 0) {
|
||||
e.close_async(GLib.PRIORITY_DEFAULT, null, () => {});
|
||||
return;
|
||||
}
|
||||
for (const info of infos) {
|
||||
e.get_child(info).delete_async(
|
||||
GLib.PRIORITY_DEFAULT, null,
|
||||
(f, rr) => { try { f.delete_finish(rr); } catch { /* best-effort */ } });
|
||||
}
|
||||
step();
|
||||
});
|
||||
};
|
||||
step();
|
||||
});
|
||||
}
|
||||
|
||||
_disconnectSignals() {
|
||||
if (this._itemAddedId && this._proxy) {
|
||||
this._proxy.disconnectSignal(this._itemAddedId);
|
||||
|
|
@ -459,9 +486,13 @@ export default class StrataExtension extends Extension {
|
|||
});
|
||||
}
|
||||
|
||||
_readExcluded() {
|
||||
return this._settings.get_strv('excluded-apps').map(s => s.toLowerCase());
|
||||
}
|
||||
|
||||
_isExcluded(appClass) {
|
||||
if (!appClass) return false;
|
||||
return this._excludedApps.some(ex => appClass.includes(ex.toLowerCase()));
|
||||
return this._excludedApps.some(ex => appClass.includes(ex));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -57,8 +57,11 @@ export const ClipboardItem = GObject.registerClass({
|
|||
x_expand: true,
|
||||
});
|
||||
|
||||
row.add_child(this._buildLeading(mimeType, preview));
|
||||
row.add_child(this._buildContent(mimeType, preview));
|
||||
const isImage = mimeType.startsWith('image/');
|
||||
const urlLike = !isImage && isUrl(preview ?? '');
|
||||
const colorLike = !isImage && !urlLike && isColor(preview ?? '');
|
||||
row.add_child(this._buildLeading(mimeType, preview, isImage, colorLike));
|
||||
row.add_child(this._buildContent(preview, isImage, urlLike, colorLike));
|
||||
|
||||
const deleteBtn = new St.Button({
|
||||
style_class: 'strata-item-delete',
|
||||
|
|
@ -88,11 +91,11 @@ export const ClipboardItem = GObject.registerClass({
|
|||
});
|
||||
}
|
||||
|
||||
_buildLeading(mimeType, preview) {
|
||||
if (mimeType.startsWith('image/')) {
|
||||
_buildLeading(mimeType, preview, isImage, colorLike) {
|
||||
if (isImage) {
|
||||
return this._buildThumbnail(this._id);
|
||||
}
|
||||
if (isColor(preview)) {
|
||||
if (colorLike) {
|
||||
return this._buildColorSwatch(preview);
|
||||
}
|
||||
const icon = new St.Icon({
|
||||
|
|
@ -197,7 +200,7 @@ export const ClipboardItem = GObject.registerClass({
|
|||
});
|
||||
}
|
||||
|
||||
_buildContent(mimeType, preview) {
|
||||
_buildContent(preview, isImage, urlLike, colorLike) {
|
||||
preview = preview ?? '';
|
||||
const box = new St.BoxLayout({
|
||||
vertical: true,
|
||||
|
|
@ -208,17 +211,17 @@ export const ClipboardItem = GObject.registerClass({
|
|||
let mainText;
|
||||
let subText = '';
|
||||
|
||||
if (mimeType.startsWith('image/')) {
|
||||
if (isImage) {
|
||||
// Generic label - the thumbnail identifies the image, and the
|
||||
// on-clipboard format (often PNG even for a copied GIF/WebP) is an
|
||||
// implementation detail that misleads more than it informs.
|
||||
mainText = 'Image';
|
||||
} else if (isUrl(preview)) {
|
||||
} else if (urlLike) {
|
||||
mainText = preview.trim();
|
||||
try {
|
||||
subText = GLib.Uri.parse(preview.trim(), GLib.UriFlags.NONE).get_host() ?? '';
|
||||
} catch { /* not a parseable URI */ }
|
||||
} else if (isColor(preview)) {
|
||||
} else if (colorLike) {
|
||||
mainText = preview.trim().toUpperCase();
|
||||
subText = 'Color';
|
||||
} else {
|
||||
|
|
@ -230,7 +233,7 @@ export const ClipboardItem = GObject.registerClass({
|
|||
|
||||
const labelMain = new St.Label({
|
||||
text: mainText || '(empty)',
|
||||
style_class: `strata-item-text${isUrl(preview) ? ' strata-item-url' : ''}`,
|
||||
style_class: `strata-item-text${urlLike ? ' strata-item-url' : ''}`,
|
||||
x_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ export class StrataPanel {
|
|||
|
||||
removeItem(id) {
|
||||
this._items = this._items.filter(i => i.id !== id);
|
||||
this._thumbCache.delete(id);
|
||||
// Keep the search snapshot in sync so a deleted/pruned item sitting in
|
||||
// the not-yet-rendered tail can't be re-created as a phantom row when
|
||||
// scrolling appends the next page. Adjust _searchRendered if the removed
|
||||
|
|
@ -310,6 +311,7 @@ export class StrataPanel {
|
|||
this._hoveredWidget = null;
|
||||
this._activeWidget = null;
|
||||
this._widgets.clear();
|
||||
this._thumbCache.clear();
|
||||
this._itemList.destroy_all_children();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue