Skip to content

Commit b85e32c

Browse files
committed
Improvement: Extracted the hash blocking functionality into a service.
1 parent bfde378 commit b85e32c

3 files changed

Lines changed: 115 additions & 38 deletions

File tree

gnome-extensions/extension/features/Clipboard/logic/clipboardMonitor.js

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import { LinkProcessor } from '../processors/clipboardLinkProcessor.js';
1313
import { TextProcessor } from '../processors/clipboardTextProcessor.js';
1414

1515
// Configuration
16-
const EXCLUDED_HASH_TTL_MS = 5000;
17-
const MAX_BLOCKED_HASHES = 50;
1816
const MAX_RETRIES = 5;
1917
const RETRY_DELAY_MS = 200;
2018

@@ -34,18 +32,18 @@ export class ClipboardMonitor {
3432
* @param {ExclusionUtils} exclusionUtils Utility for handling exclusion rules.
3533
* @param {string} imagesDir Path for generating color processor gradients.
3634
* @param {Function} onContentCaptured Callback for when content is captured.
35+
* @param {ClipboardCaptureGuardService} captureGuard Guard for suppression decisions.
3736
*/
38-
constructor(exclusionUtils, imagesDir, onContentCaptured) {
37+
constructor(exclusionUtils, imagesDir, onContentCaptured, captureGuard) {
3938
this._exclusionUtils = exclusionUtils;
4039
this._imagesDir = imagesDir;
4140
this._onContentCaptured = onContentCaptured;
41+
this._captureGuard = captureGuard;
4242

4343
this._selection = null;
4444
this._isPaused = false;
4545
this._processClipboardTimeoutId = 0;
4646
this._retryTimeoutId = 0;
47-
48-
this._blockedContentHashes = new Map();
4947
}
5048

5149
/**
@@ -111,54 +109,61 @@ export class ClipboardMonitor {
111109
// Image
112110
const imageResult = await ImageProcessor.extract();
113111
if (imageResult) {
114-
this._onContentCaptured(imageResult);
112+
if (!this._shouldSuppress(imageResult)) {
113+
this._onContentCaptured(imageResult);
114+
}
115115
return;
116116
}
117117

118118
// Text
119119
const textResult = await TextProcessor.extract();
120120
if (textResult) {
121121
const text = textResult.text;
122-
const hash = this._hashString(text);
123-
124-
if (this._blockedContentHashes.has(hash)) {
125-
const expiry = this._blockedContentHashes.get(hash);
126-
if (Date.now() < expiry) return;
127-
this._blockedContentHashes.delete(hash);
128-
}
129122

130123
const fileResult = await FileProcessor.process(text);
131124
if (fileResult) {
132-
this._onContentCaptured(fileResult);
125+
if (!this._shouldSuppress(fileResult)) {
126+
this._onContentCaptured(fileResult);
127+
}
133128
return;
134129
}
135130

136131
const linkResult = LinkProcessor.process(text);
137132
if (linkResult) {
138-
this._onContentCaptured(linkResult);
133+
if (!this._shouldSuppress(linkResult)) {
134+
this._onContentCaptured(linkResult);
135+
}
139136
return;
140137
}
141138

142139
const contactResult = await ContactProcessor.process(text);
143140
if (contactResult) {
144-
this._onContentCaptured(contactResult);
141+
if (!this._shouldSuppress(contactResult)) {
142+
this._onContentCaptured(contactResult);
143+
}
145144
return;
146145
}
147146

148147
const colorResult = ColorProcessor.process(text, this._imagesDir);
149148
if (colorResult) {
150-
this._onContentCaptured(colorResult);
149+
if (!this._shouldSuppress(colorResult)) {
150+
this._onContentCaptured(colorResult);
151+
}
151152
return;
152153
}
153154

154155
const codeResult = CodeProcessor.process(text);
155156
if (codeResult) {
156-
this._onContentCaptured(codeResult);
157+
if (!this._shouldSuppress(codeResult)) {
158+
this._onContentCaptured(codeResult);
159+
}
157160
return;
158161
}
159162

160163
// Fallback
161-
this._onContentCaptured(textResult);
164+
if (!this._shouldSuppress(textResult)) {
165+
this._onContentCaptured(textResult);
166+
}
162167
return;
163168
}
164169

@@ -184,15 +189,7 @@ export class ClipboardMonitor {
184189
_hashAndBlockClipboardContent() {
185190
clipboardGetText()
186191
.then((text) => {
187-
if (text) {
188-
const hash = this._hashString(text);
189-
this._blockedContentHashes.set(hash, Date.now() + EXCLUDED_HASH_TTL_MS);
190-
191-
if (this._blockedContentHashes.size > MAX_BLOCKED_HASHES) {
192-
const first = this._blockedContentHashes.keys().next().value;
193-
this._blockedContentHashes.delete(first);
194-
}
195-
}
192+
if (text) this._captureGuard?.registerText(text);
196193
})
197194
.catch(() => {});
198195
}
@@ -202,18 +199,19 @@ export class ClipboardMonitor {
202199
// ========================================================================
203200

204201
/**
205-
* Generate a simple hash from a string.
202+
* Decide whether a capture should be suppressed and register allowed hashes.
206203
*
207-
* @param {string} str Input string.
208-
* @returns {number} Generated hash.
204+
* @param {Object} result Extracted result with hash.
205+
* @returns {boolean} True if suppressed.
209206
* @private
210207
*/
211-
_hashString(str) {
212-
let hash = 5381;
213-
for (let i = 0; i < str.length; i++) {
214-
hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
215-
}
216-
return hash;
208+
_shouldSuppress(result) {
209+
if (!this._captureGuard || !result?.hash) return false;
210+
211+
if (this._captureGuard.shouldBlockHash(result.hash)) return true;
212+
213+
this._captureGuard.registerHash(result.hash);
214+
return false;
217215
}
218216

219217
// ========================================================================

gnome-extensions/extension/features/Clipboard/managers/clipboardManager.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import GObject from 'gi://GObject';
33
import { ExclusionUtils } from '../../../shared/utilities/utilityExclusions.js';
44
import { Logger } from '../../../shared/utilities/utilityLogger.js';
55

6+
import { ClipboardCaptureGuardService } from '../services/clipboardCaptureGuardService.js';
67
import { ClipboardContentRouter } from '../services/clipboardContentRouter.js';
78
import { ClipboardCopyService } from '../services/clipboardCopyService.js';
89
import { ClipboardMonitor } from '../logic/clipboardMonitor.js';
@@ -48,7 +49,8 @@ export const ClipboardManager = GObject.registerClass(
4849
this._exclusionUtils = new ExclusionUtils();
4950
this._exclusionUtils.initialize(settings);
5051

51-
this._monitor = new ClipboardMonitor(this._exclusionUtils, this._storage.imagesDir, (result) => this._contentRouter.processResult(result));
52+
this._captureGuard = new ClipboardCaptureGuardService();
53+
this._monitor = new ClipboardMonitor(this._exclusionUtils, this._storage.imagesDir, (result) => this._contentRouter.processResult(result), this._captureGuard);
5254

5355
this._history = [];
5456
this._pinned = [];
@@ -590,6 +592,7 @@ export const ClipboardManager = GObject.registerClass(
590592
this._storage.destroy();
591593
this._contentRouter?.destroy();
592594
this._exclusionUtils?.destroy();
595+
this._captureGuard?.destroy();
593596
}
594597
},
595598
);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { ProcessorUtils } from '../utilities/clipboardProcessorUtils.js';
2+
3+
// Configuration
4+
const DEFAULT_TTL_MS = 5000;
5+
const MAX_BLOCKED_HASHES = 50;
6+
7+
/**
8+
* ClipboardCaptureGuardService
9+
*
10+
* Stores short-lived hash blocks to suppress immediate re-capture.
11+
*/
12+
export class ClipboardCaptureGuardService {
13+
constructor(ttlMs = DEFAULT_TTL_MS, maxHashes = MAX_BLOCKED_HASHES) {
14+
this._ttlMs = ttlMs;
15+
this._maxHashes = maxHashes;
16+
this._hashExpiry = new Map();
17+
}
18+
19+
/**
20+
* Check whether a hash is currently blocked.
21+
*
22+
* @param {string} hash
23+
* @returns {boolean}
24+
*/
25+
shouldBlockHash(hash) {
26+
if (!hash) return false;
27+
28+
const expiry = this._hashExpiry.get(hash);
29+
if (!expiry) return false;
30+
31+
if (Date.now() >= expiry) {
32+
this._hashExpiry.delete(hash);
33+
return false;
34+
}
35+
36+
return true;
37+
}
38+
39+
/**
40+
* Register a hash to be blocked for the TTL window.
41+
*
42+
* @param {string} hash
43+
* @param {number} ttlMs
44+
*/
45+
registerHash(hash, ttlMs = this._ttlMs) {
46+
if (!hash) return;
47+
48+
this._hashExpiry.set(hash, Date.now() + ttlMs);
49+
50+
if (this._hashExpiry.size > this._maxHashes) {
51+
const oldestKey = this._hashExpiry.keys().next().value;
52+
if (oldestKey !== undefined) {
53+
this._hashExpiry.delete(oldestKey);
54+
}
55+
}
56+
}
57+
58+
/**
59+
* Convenience registration for text values.
60+
*
61+
* @param {string} text
62+
* @param {number} ttlMs
63+
*/
64+
registerText(text, ttlMs = this._ttlMs) {
65+
if (!text) return;
66+
const hash = ProcessorUtils.computeHashForString(text);
67+
this.registerHash(hash, ttlMs);
68+
}
69+
70+
/**
71+
* Clear all suppression entries.
72+
*/
73+
destroy() {
74+
this._hashExpiry.clear();
75+
}
76+
}

0 commit comments

Comments
 (0)