mirror of
https://github.com/panatgithub/AnkiHeadingSync.git
synced 2026-07-22 06:51:43 +00:00
102 lines
No EOL
3.8 KiB
TypeScript
102 lines
No EOL
3.8 KiB
TypeScript
import { MarkdownWriteConflictError } from "@/application/ports/VaultGateway";
|
|
import type { ManualSyncVaultGateway } from "@/application/ports/ManualSyncVaultGateway";
|
|
import type { IndexedFile } from "@/domain/manual-sync/entities/IndexedFile";
|
|
import type { PendingWriteBackState } from "@/domain/manual-sync/entities/PluginState";
|
|
import type { PlannedCard } from "@/domain/manual-sync/value-objects/ManualSyncPlan";
|
|
import { CardMarkerService, type MarkerWriteRequest } from "@/domain/manual-sync/services/CardMarkerService";
|
|
|
|
export interface MarkdownWriteBackResult {
|
|
writtenCardIds: string[];
|
|
conflictFiles: string[];
|
|
failureFiles: Array<{ filePath: string; message: string }>;
|
|
pendingEntries: PendingWriteBackState[];
|
|
}
|
|
|
|
export class MarkdownWriteBackService {
|
|
constructor(
|
|
private readonly vaultGateway: ManualSyncVaultGateway,
|
|
private readonly markerService = new CardMarkerService(),
|
|
) {}
|
|
|
|
async write(plannedCards: PlannedCard[], indexedFilesByPath: Map<string, IndexedFile>): Promise<MarkdownWriteBackResult> {
|
|
const plannedByFile = new Map<string, PlannedCard[]>();
|
|
const writtenCardIds: string[] = [];
|
|
const conflictFiles: string[] = [];
|
|
const failureFiles: Array<{ filePath: string; message: string }> = [];
|
|
const pendingEntries: PendingWriteBackState[] = [];
|
|
|
|
for (const plannedCard of plannedCards) {
|
|
const entries = plannedByFile.get(plannedCard.card.filePath);
|
|
if (entries) {
|
|
entries.push(plannedCard);
|
|
continue;
|
|
}
|
|
|
|
plannedByFile.set(plannedCard.card.filePath, [plannedCard]);
|
|
}
|
|
|
|
for (const [filePath, fileCards] of plannedByFile.entries()) {
|
|
const indexedFile = indexedFilesByPath.get(filePath);
|
|
const sourceContent = indexedFile?.content ?? fileCards[0]?.card.sourceContent;
|
|
|
|
if (!sourceContent || !indexedFile) {
|
|
pendingEntries.push(...fileCards.map((plannedCard) => this.createPendingEntry(filePath, plannedCard, indexedFile?.fileHash ?? "")));
|
|
failureFiles.push({ filePath, message: `Missing scanned source content for ${filePath}.` });
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const nextContent = this.markerService.applyBatch(
|
|
sourceContent,
|
|
fileCards.map((plannedCard) => this.toWriteRequest(plannedCard, sourceContent)),
|
|
);
|
|
|
|
await this.vaultGateway.replaceMarkdownFile(filePath, sourceContent, nextContent);
|
|
writtenCardIds.push(...fileCards.map((plannedCard) => plannedCard.card.cardId));
|
|
} catch (error) {
|
|
pendingEntries.push(...fileCards.map((plannedCard) => this.createPendingEntry(filePath, plannedCard, indexedFile.fileHash)));
|
|
|
|
if (error instanceof MarkdownWriteConflictError) {
|
|
conflictFiles.push(filePath);
|
|
continue;
|
|
}
|
|
|
|
failureFiles.push({
|
|
filePath,
|
|
message: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
writtenCardIds,
|
|
conflictFiles,
|
|
failureFiles,
|
|
pendingEntries,
|
|
};
|
|
}
|
|
|
|
private toWriteRequest(plannedCard: PlannedCard, sourceContent: string): MarkerWriteRequest {
|
|
return {
|
|
filePath: plannedCard.card.filePath,
|
|
cardId: plannedCard.card.cardId,
|
|
noteId: plannedCard.noteId,
|
|
blockStartLine: plannedCard.card.blockStartLine,
|
|
contentEndLine: plannedCard.card.contentEndLine,
|
|
blockEndLine: plannedCard.card.blockEndLine,
|
|
markerLine: plannedCard.card.markerLine,
|
|
sourceContent,
|
|
};
|
|
}
|
|
|
|
private createPendingEntry(filePath: string, plannedCard: PlannedCard, expectedFileHash: string): PendingWriteBackState {
|
|
return {
|
|
filePath,
|
|
cardId: plannedCard.card.cardId,
|
|
noteId: plannedCard.noteId,
|
|
expectedFileHash,
|
|
targetMarker: this.markerService.create(plannedCard.card.cardId, plannedCard.noteId).raw,
|
|
rawBlockHash: plannedCard.card.rawBlockHash,
|
|
};
|
|
}
|
|
} |