mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-09-21 13:31:42 +00:00
feat(worker): derive a dedup identity for forward-path packages without bytes
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { createHash } from "crypto";
|
||||
import { deriveForwardContentHash } from "./forward-identity.js";
|
||||
import type { FileEntry } from "./zip-reader.js";
|
||||
|
||||
function entry(crc32: string | null): FileEntry {
|
||||
return { path: "a", fileName: "a", extension: null, compressedSize: 1n, uncompressedSize: 1n, crc32 };
|
||||
}
|
||||
|
||||
describe("deriveForwardContentHash", () => {
|
||||
it("hashes the sorted CRC list when all entries have a CRC32 (ZIP/RAR)", () => {
|
||||
const entries = [entry("BBBB"), entry("AAAA")];
|
||||
const expectedHash = createHash("sha256").update(["aaaa", "bbbb"].join(",")).digest("hex");
|
||||
expect(deriveForwardContentHash(entries, "unique-1", "chan-1", 42n)).toBe(`fingerprint:${expectedHash}`);
|
||||
});
|
||||
|
||||
it("falls back to remoteUniqueId when CRCs are incomplete (7z today)", () => {
|
||||
const entries = [entry(null), entry("AAAA")];
|
||||
expect(deriveForwardContentHash(entries, "unique-42", "chan-1", 42n)).toBe("forward:unique-42");
|
||||
});
|
||||
|
||||
it("falls back to sourceChannelId+sourceMessageId when there's no CRC and no remoteUniqueId", () => {
|
||||
const entries = [entry(null)];
|
||||
expect(deriveForwardContentHash(entries, null, "chan-1", 42n)).toBe("forward:chan-1:42");
|
||||
});
|
||||
|
||||
it("falls back past an empty entries list the same way", () => {
|
||||
expect(deriveForwardContentHash([], null, "chan-1", 7n)).toBe("forward:chan-1:7");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createHash } from "crypto";
|
||||
import { crcFingerprint } from "./fingerprint.js";
|
||||
import type { FileEntry } from "./zip-reader.js";
|
||||
|
||||
/**
|
||||
* Derive a Package.contentHash-compatible identity string for a forward-path
|
||||
* package (no downloaded bytes exist to hash directly). Priority order:
|
||||
* 1. A CRC32-fingerprint hash, when the ranged listing's CRCs are complete
|
||||
* (ZIP/RAR today) — the strongest available signal, since it lets
|
||||
* forward-path and download-path copies of the same archive still
|
||||
* collide/dedupe on identical content.
|
||||
* 2. TDLib's remote.unique_id, when CRCs are incomplete (7z today has none).
|
||||
* 3. sourceChannelId+sourceMessageId, as a last-resort unique value so the
|
||||
* required-unique Package.contentHash column is always satisfiable.
|
||||
* Follows the same `<prefix>:<value>` synthetic-hash convention already used
|
||||
* by `rebuild.ts`'s `rebuild:${destChannelId}:${destMessageId}` placeholder.
|
||||
*/
|
||||
export function deriveForwardContentHash(
|
||||
entries: FileEntry[],
|
||||
remoteUniqueId: string | null,
|
||||
sourceChannelId: string,
|
||||
sourceMessageId: bigint,
|
||||
): string {
|
||||
const fp = crcFingerprint(entries);
|
||||
if (fp.complete && fp.crcs.length > 0) {
|
||||
const hash = createHash("sha256").update(fp.crcs.join(",")).digest("hex");
|
||||
return `fingerprint:${hash}`;
|
||||
}
|
||||
if (remoteUniqueId) {
|
||||
return `forward:${remoteUniqueId}`;
|
||||
}
|
||||
return `forward:${sourceChannelId}:${sourceMessageId}`;
|
||||
}
|
||||
Reference in New Issue
Block a user