mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge branch 'unstable' into ai/performance-events-cache-dirty-tracking-5412442767823742174
This commit is contained in:
6 files changed
+100
-11
No files matched your search
+4
-3
@@ -111,9 +111,10 @@ To set up the project locally:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npx serve dist
|
||||
```
|
||||
|
||||
(This runs `npm run build:css` to generate Tailwind styles.)
|
||||
(The build command populates the `dist/` directory, which you can then serve locally.)
|
||||
|
||||
3. **Run Tests**:
|
||||
|
||||
@@ -144,8 +145,8 @@ To set up the project locally:
|
||||
npm run lint
|
||||
```
|
||||
|
||||
_Note: `npm run format` currently targets CSS, HTML, Markdown, and Tailwind config files._
|
||||
_Note: `npm run lint` checks for CSS errors, hex color usage (use tokens instead), inline styles, and design token compliance._
|
||||
- **Format**: Targets CSS, HTML, Markdown, and config files. (Note: JavaScript files are not currently auto-formatted by this command).
|
||||
- **Lint**: Checks for CSS errors, hex color usage, inline styles, and design token compliance. (Note: There is no ESLint configuration for JavaScript logic; this step focuses on style and design system guards).
|
||||
|
||||
5. **Git Hooks (Optional)**:
|
||||
We provide a script to set up a git pre-commit hook that runs linting and CSS builds automatically before you commit.
|
||||
|
||||
@@ -164,13 +164,13 @@ To run **bitvid** locally:
|
||||
http://localhost:3000
|
||||
```
|
||||
|
||||
_Note: If you prefer manual steps, you can run `npm run build` followed by `npx serve` or `python -m http.server 8000`._
|
||||
_Note: If you prefer manual steps, you can run `npm run build` followed by `npx serve dist`._
|
||||
|
||||
#### Quick Reference
|
||||
|
||||
- **Run unit tests**: `npm run test:unit`
|
||||
- **Format code**: `npm run format`
|
||||
- **Lint code**: `npm run lint`
|
||||
- **Format code**: `npm run format` (CSS, HTML, MD, Config)
|
||||
- **Lint code**: `npm run lint` (Styles, Tokens, Hex - no JS logic)
|
||||
|
||||
### Dev Container
|
||||
|
||||
|
||||
+37
-1
@@ -747,6 +747,7 @@ class EventsCacheStore {
|
||||
let eventDeletes = 0;
|
||||
let tombstoneWrites = 0;
|
||||
let tombstoneDeletes = 0;
|
||||
let skipped = 0;
|
||||
|
||||
// Optimization: If dirtyEventIds is provided, only check those events.
|
||||
// Otherwise, iterate over all events in the payload.
|
||||
@@ -759,6 +760,16 @@ class EventsCacheStore {
|
||||
if (!id || !video) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
dirtyEventIds &&
|
||||
!dirtyEventIds.has(id) &&
|
||||
this.persistedEventFingerprints.has(id)
|
||||
) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const fingerprint = this.computeEventFingerprint(video);
|
||||
const prevFingerprint = this.persistedEventFingerprints.get(id);
|
||||
if (prevFingerprint === fingerprint) {
|
||||
@@ -797,6 +808,16 @@ class EventsCacheStore {
|
||||
if (!key || !Number.isFinite(timestamp)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
dirtyTombstones &&
|
||||
!dirtyTombstones.has(key) &&
|
||||
this.persistedTombstoneFingerprints.has(key)
|
||||
) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const fingerprint = this.computeTombstoneFingerprint(timestamp);
|
||||
const prevFingerprint = this.persistedTombstoneFingerprints.get(key);
|
||||
if (prevFingerprint === fingerprint) {
|
||||
@@ -834,6 +855,7 @@ class EventsCacheStore {
|
||||
eventDeletes,
|
||||
tombstoneWrites,
|
||||
tombstoneDeletes,
|
||||
skipped,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1272,6 +1294,8 @@ export class NostrClient {
|
||||
* @public
|
||||
*/
|
||||
this.tombstones = new Map();
|
||||
this.dirtyEventIds = new Set();
|
||||
this.dirtyTombstones = new Set();
|
||||
|
||||
this.rootCreatedAtByRoot = new Map();
|
||||
|
||||
@@ -3975,6 +3999,9 @@ export class NostrClient {
|
||||
);
|
||||
}
|
||||
|
||||
this.dirtyEventIds.clear();
|
||||
this.dirtyTombstones.clear();
|
||||
|
||||
return this.allEvents.size > 0;
|
||||
}
|
||||
|
||||
@@ -7533,6 +7560,9 @@ export class NostrClient {
|
||||
let summary = null;
|
||||
let target = "localStorage";
|
||||
|
||||
const dirtyEventsSnapshot = new Set(this.dirtyEventIds);
|
||||
const dirtyTombstonesSnapshot = new Set(this.dirtyTombstones);
|
||||
|
||||
try {
|
||||
summary = await this.eventsCacheStore.persistSnapshot(
|
||||
payload,
|
||||
@@ -7541,6 +7571,12 @@ export class NostrClient {
|
||||
);
|
||||
if (summary?.persisted) {
|
||||
target = "IndexedDB";
|
||||
for (const id of dirtyEventsSnapshot) {
|
||||
this.dirtyEventIds.delete(id);
|
||||
}
|
||||
for (const key of dirtyTombstonesSnapshot) {
|
||||
this.dirtyTombstones.delete(key);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
@@ -7562,7 +7598,7 @@ export class NostrClient {
|
||||
|
||||
const durationMs = Date.now() - startedAt;
|
||||
devLogger.log(
|
||||
`[nostr] Cached events persisted via ${target} (reason=${reason}, duration=${durationMs}ms, events+${summary?.eventWrites ?? 0}/-${summary?.eventDeletes ?? 0}, tombstones+${summary?.tombstoneWrites ?? 0}/-${summary?.tombstoneDeletes ?? 0})`,
|
||||
`[nostr] Cached events persisted via ${target} (reason=${reason}, duration=${durationMs}ms, events+${summary?.eventWrites ?? 0}/-${summary?.eventDeletes ?? 0}, tombstones+${summary?.tombstoneWrites ?? 0}/-${summary?.tombstoneDeletes ?? 0}, skipped=${summary?.skipped ?? 0})`,
|
||||
);
|
||||
|
||||
return summary?.persisted;
|
||||
|
||||
+17
-3
@@ -207,6 +207,11 @@ export class TorrentClient {
|
||||
magnetURI,
|
||||
{ timeoutMs = 8000, maxWebConns = 2, polls = 2, urlList = [] } = {}
|
||||
) {
|
||||
// Debug log for CI investigation
|
||||
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") {
|
||||
console.log(`[probePeers] magnetURI=${magnetURI?.substring(0, 20)}... urlList=${JSON.stringify(urlList)}`);
|
||||
}
|
||||
|
||||
const magnet = stripXsParameter(magnetURI);
|
||||
|
||||
if (!magnet) {
|
||||
@@ -228,6 +233,10 @@ export class TorrentClient {
|
||||
const hasMagnetWebSeed = magnet.includes("ws=") || magnet.includes("webSeed=");
|
||||
const hasExplicitWebSeed = Array.isArray(urlList) && urlList.length > 0;
|
||||
|
||||
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") {
|
||||
console.log(`[probePeers] hasMagnetWebSeed=${hasMagnetWebSeed} hasExplicitWebSeed=${hasExplicitWebSeed}`);
|
||||
}
|
||||
|
||||
if (hasExplicitWebSeed) {
|
||||
const wsParams = urlList
|
||||
.map((url) => `ws=${encodeURIComponent(url)}`)
|
||||
@@ -336,6 +345,9 @@ export class TorrentClient {
|
||||
}
|
||||
torrent = client.add(augmentedMagnet, addOptions);
|
||||
} catch (err) {
|
||||
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") {
|
||||
console.log(`[probePeers] client.add threw:`, err);
|
||||
}
|
||||
finalize({
|
||||
reason: "error",
|
||||
error: toError(err),
|
||||
@@ -403,9 +415,11 @@ export class TorrentClient {
|
||||
|
||||
if (safeTimeout > 0) {
|
||||
timeoutId = setTimeout(() => {
|
||||
const counts = getPeerCounts();
|
||||
const webseedOnly = counts.regularPeers === 0 && counts.webSeeds > 0;
|
||||
const peers = Math.max(0, counts.totalPeers);
|
||||
const peers = Math.max(0, Math.floor(normalizeNumber(torrent?.numPeers, 0)));
|
||||
const webseedOnly = peers === 0 && hasWebSeed;
|
||||
if (typeof process !== "undefined" && process.env.NODE_ENV === "test" && !webseedOnly && hasWebSeed) {
|
||||
console.log(`[probePeers:timeout] logic check: peers=${peers} hasWebSeed=${hasWebSeed} webseedOnly=${webseedOnly}`);
|
||||
}
|
||||
finalize({
|
||||
healthy: webseedOnly,
|
||||
peers,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
TAP version 13
|
||||
# Subtest: WebTorrent Regression Tests
|
||||
# Subtest: probePeers should report healthy if webseed is present even with 0 peers
|
||||
ok 1 - probePeers should report healthy if webseed is present even with 0 peers
|
||||
---
|
||||
duration_ms: 204.415485
|
||||
type: 'test'
|
||||
...
|
||||
# Subtest: probePeers should report unhealthy if no webseed and 0 peers
|
||||
ok 2 - probePeers should report unhealthy if no webseed and 0 peers
|
||||
---
|
||||
duration_ms: 52.544489
|
||||
type: 'test'
|
||||
...
|
||||
1..2
|
||||
ok 1 - WebTorrent Regression Tests
|
||||
---
|
||||
duration_ms: 259.856073
|
||||
type: 'suite'
|
||||
...
|
||||
1..1
|
||||
# tests 2
|
||||
# suites 1
|
||||
# pass 2
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 269.477515
|
||||
@@ -20,6 +20,11 @@ class MockWebTorrent extends EventEmitter {
|
||||
setTimeout(() => {
|
||||
if (typeof cb === 'function') cb(torrent);
|
||||
this.emit('torrent', torrent);
|
||||
|
||||
// Simulate webseed connection if urlList is present
|
||||
if (opts && Array.isArray(opts.urlList) && opts.urlList.length > 0) {
|
||||
torrent.emit('wire', { type: 'webSeed' });
|
||||
}
|
||||
}, 10);
|
||||
|
||||
return torrent;
|
||||
@@ -75,10 +80,14 @@ describe("WebTorrent Regression Tests", () => {
|
||||
const webSeedUrl = "http://localhost:8080/video.mp4";
|
||||
|
||||
const result = await client.probePeers(magnet, {
|
||||
timeoutMs: 50, // Short timeout
|
||||
timeoutMs: 1000, // Significantly increased timeout for CI robustness
|
||||
urlList: [webSeedUrl]
|
||||
});
|
||||
|
||||
if (!result.webseedOnly) {
|
||||
console.log("Failed result:", result);
|
||||
}
|
||||
|
||||
assert.strictEqual(result.webseedOnly, true, "Should detect webseed only scenario");
|
||||
assert.strictEqual(result.healthy, true, "Should be healthy due to webseed");
|
||||
assert.strictEqual(result.reason, "webseed", "Reason should be 'webseed'");
|
||||
|
||||
Reference in new issue
Block a user