mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-09-21 05:21:43 +00:00
feat(worker): enable RAR ranged listing + format-aware destination reads
This commit is contained in:
@@ -1015,6 +1015,7 @@ export async function createAutoGroup(input: {
|
|||||||
export interface PlaceholderCandidate {
|
export interface PlaceholderCandidate {
|
||||||
id: string;
|
id: string;
|
||||||
archiveType: string;
|
archiveType: string;
|
||||||
|
fileName: string;
|
||||||
fileCount: number;
|
fileCount: number;
|
||||||
fileSize: bigint;
|
fileSize: bigint;
|
||||||
destMessageId: bigint | null;
|
destMessageId: bigint | null;
|
||||||
@@ -1046,7 +1047,7 @@ export async function findPlaceholderCandidates(
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true, archiveType: true, fileCount: true, fileSize: true,
|
id: true, archiveType: true, fileName: true, fileCount: true, fileSize: true,
|
||||||
destMessageId: true, destMessageIds: true, destChannelId: true,
|
destMessageId: true, destMessageIds: true, destChannelId: true,
|
||||||
},
|
},
|
||||||
orderBy: { indexedAt: "asc" },
|
orderBy: { indexedAt: "asc" },
|
||||||
@@ -1065,6 +1066,7 @@ export async function findPlaceholderCandidates(
|
|||||||
return rows.map((row) => ({
|
return rows.map((row) => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
archiveType: row.archiveType,
|
archiveType: row.archiveType,
|
||||||
|
fileName: row.fileName,
|
||||||
fileCount: row.fileCount,
|
fileCount: row.fileCount,
|
||||||
fileSize: row.fileSize,
|
fileSize: row.fileSize,
|
||||||
destMessageId: row.destMessageId,
|
destMessageId: row.destMessageId,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from "./db/queries.js";
|
} from "./db/queries.js";
|
||||||
import type { FileEntry } from "./archive/zip-reader.js";
|
import type { FileEntry } from "./archive/zip-reader.js";
|
||||||
import { readSevenZListingRanged, type RangedPart } from "./archive/ranged/sevenz-ranged.js";
|
import { readSevenZListingRanged, type RangedPart } from "./archive/ranged/sevenz-ranged.js";
|
||||||
|
import { readRarListingRanged } from "./archive/ranged/rar-ranged.js";
|
||||||
import { tdlibRangeReader } from "./archive/ranged/range-reader.js";
|
import { tdlibRangeReader } from "./archive/ranged/range-reader.js";
|
||||||
import { fullDownloadListing } from "./archive/ranged/fallback.js";
|
import { fullDownloadListing } from "./archive/ranged/fallback.js";
|
||||||
import type { Client } from "tdl";
|
import type { Client } from "tdl";
|
||||||
@@ -67,32 +68,40 @@ async function readScannedZipListing(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readZipListingFromDestination(
|
/**
|
||||||
|
* Resolve the destination copy's message(s) into ranged parts (file id +
|
||||||
|
* size + name), in order, so a multipart destination copy is reconstructed
|
||||||
|
* with correct per-part sizes and names (the last message carries the
|
||||||
|
* EOCD-bearing tail part for ZIP; multipart RAR needs correctly-suffixed
|
||||||
|
* `.partN.rar` names for `unrar` sibling discovery). Cheap-only: any TDLib
|
||||||
|
* failure here degrades the caller to name-size confidence rather than
|
||||||
|
* falling back to a full download.
|
||||||
|
*/
|
||||||
|
async function resolveDestParts(
|
||||||
client: Client,
|
client: Client,
|
||||||
destChatTelegramId: bigint,
|
destChatTelegramId: bigint,
|
||||||
destMessageIds: bigint[],
|
destMessageIds: bigint[],
|
||||||
destMessageId: bigint | null,
|
destMessageId: bigint | null,
|
||||||
): Promise<FileEntry[] | null> {
|
fallbackFileName: string,
|
||||||
|
): Promise<RangedPart[] | null> {
|
||||||
const messageIds = destMessageIds.length > 0 ? destMessageIds : destMessageId ? [destMessageId] : [];
|
const messageIds = destMessageIds.length > 0 ? destMessageIds : destMessageId ? [destMessageId] : [];
|
||||||
if (messageIds.length === 0) return null;
|
if (messageIds.length === 0) return null;
|
||||||
try {
|
try {
|
||||||
// Resolve each destination message's document file id + size, in order,
|
const parts: RangedPart[] = [];
|
||||||
// so a multipart destination copy is reconstructed with correct
|
|
||||||
// per-part sizes (the last message carries the EOCD-bearing tail part).
|
|
||||||
const parts: { fileId: string; fileSize: bigint }[] = [];
|
|
||||||
for (const msgId of messageIds) {
|
for (const msgId of messageIds) {
|
||||||
const msg = (await invokeWithTimeout(client, {
|
const msg = (await invokeWithTimeout(client, {
|
||||||
_: "getMessage",
|
_: "getMessage",
|
||||||
chat_id: Number(destChatTelegramId),
|
chat_id: Number(destChatTelegramId),
|
||||||
message_id: Number(msgId),
|
message_id: Number(msgId),
|
||||||
})) as { content?: { document?: { document?: { id: number; size?: number } } } };
|
})) as { content?: { document?: { document?: { id: number; size?: number }; file_name?: string } } };
|
||||||
const doc = msg?.content?.document?.document;
|
const doc = msg?.content?.document?.document;
|
||||||
if (!doc?.id) return null;
|
if (!doc?.id) return null;
|
||||||
parts.push({ fileId: String(doc.id), fileSize: BigInt(doc.size ?? 0) });
|
const fileName = msg?.content?.document?.file_name || fallbackFileName;
|
||||||
|
parts.push({ fileId: String(doc.id), fileSize: BigInt(doc.size ?? 0), fileName });
|
||||||
}
|
}
|
||||||
return await readScannedZipListing(client, parts);
|
return parts;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.warn({ err, destMessageIds: messageIds.map(Number) }, "destination ZIP listing read failed");
|
log.warn({ err, destMessageIds: messageIds.map(Number) }, "destination archive part resolution failed");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,7 +114,7 @@ async function readScannedListingRanged(
|
|||||||
const read = tdlibRangeReader(client);
|
const read = tdlibRangeReader(client);
|
||||||
if (archiveType === "ZIP") return readScannedZipListing(client, parts);
|
if (archiveType === "ZIP") return readScannedZipListing(client, parts);
|
||||||
if (archiveType === "SEVEN_Z") return readSevenZListingRanged(parts, read);
|
if (archiveType === "SEVEN_Z") return readSevenZListingRanged(parts, read);
|
||||||
// RAR enabled in Task 8.
|
if (archiveType === "RAR") return readRarListingRanged(parts, read);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,12 +134,22 @@ async function resolveCandidateFingerprintEntries(
|
|||||||
}));
|
}));
|
||||||
const hasDestMessage = candidate.destMessageIds.length > 0 || candidate.destMessageId != null;
|
const hasDestMessage = candidate.destMessageIds.length > 0 || candidate.destMessageId != null;
|
||||||
if (!crcFingerprint(candidateEntries).complete && hasDestMessage && candidate.destChannel) {
|
if (!crcFingerprint(candidateEntries).complete && hasDestMessage && candidate.destChannel) {
|
||||||
const destEntries = await readZipListingFromDestination(
|
const destParts = await resolveDestParts(
|
||||||
client,
|
client,
|
||||||
candidate.destChannel.telegramId,
|
candidate.destChannel.telegramId,
|
||||||
candidate.destMessageIds,
|
candidate.destMessageIds,
|
||||||
candidate.destMessageId,
|
candidate.destMessageId,
|
||||||
|
candidate.fileName,
|
||||||
);
|
);
|
||||||
|
let destEntries: FileEntry[] | null = null;
|
||||||
|
if (destParts) {
|
||||||
|
const read = tdlibRangeReader(client);
|
||||||
|
destEntries =
|
||||||
|
candidate.archiveType === "ZIP" ? await readScannedZipListing(client, destParts)
|
||||||
|
: candidate.archiveType === "SEVEN_Z" ? await readSevenZListingRanged(destParts, read)
|
||||||
|
: candidate.archiveType === "RAR" ? await readRarListingRanged(destParts, read)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
if (destEntries) {
|
if (destEntries) {
|
||||||
candidateEntries = destEntries;
|
candidateEntries = destEntries;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user