Stirling-PDF/frontend/src/utils/fileHistoryUtils.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

195 lines
6.4 KiB
TypeScript
Raw Normal View History

2025-09-02 17:24:26 +01:00
/**
* File History Utilities
2025-09-03 14:48:14 +01:00
*
2025-09-10 10:03:35 +01:00
* Helper functions for IndexedDB-based file history management.
* Handles file history operations and lineage tracking.
2025-09-02 17:24:26 +01:00
*/
2025-09-05 17:41:53 +01:00
import { StirlingFileStub } from '../types/fileContext';
2025-09-10 10:03:35 +01:00
import { FileId } from '../types/file';
import { StoredFileMetadata } from '../services/fileStorage';
2025-09-02 17:24:26 +01:00
2025-09-03 14:48:14 +01:00
2025-09-02 17:24:26 +01:00
/**
2025-09-03 17:47:58 +01:00
* Group files by processing branches - each branch ends in a leaf file
* Returns Map<fileId, lineagePath[]> where fileId is the leaf and lineagePath is the path back to original
2025-09-02 17:24:26 +01:00
*/
2025-09-05 17:41:53 +01:00
export function groupFilesByOriginal(StirlingFileStubs: StirlingFileStub[]): Map<string, StirlingFileStub[]> {
const groups = new Map<string, StirlingFileStub[]>();
2025-09-02 17:24:26 +01:00
2025-09-03 17:47:58 +01:00
// Create a map for quick lookups
2025-09-05 17:41:53 +01:00
const fileMap = new Map<string, StirlingFileStub>();
for (const record of StirlingFileStubs) {
2025-09-03 17:47:58 +01:00
fileMap.set(record.id, record);
}
2025-09-03 14:48:14 +01:00
2025-09-03 17:47:58 +01:00
// Find leaf files (files that are not parents of any other files AND have version history)
// Original files (v0) should only be leaves if they have no processed versions at all
2025-09-05 17:41:53 +01:00
const leafFiles = StirlingFileStubs.filter(stub => {
const isParentOfOthers = StirlingFileStubs.some(otherStub => otherStub.parentFileId === stub.id);
const isOriginalOfOthers = StirlingFileStubs.some(otherStub => otherStub.originalFileId === stub.id);
2025-09-03 17:47:58 +01:00
// A file is a leaf if:
// 1. It's not a parent of any other files, AND
// 2. It has processing history (versionNumber > 0) OR it's not referenced as original by others
2025-09-05 17:41:53 +01:00
return !isParentOfOthers && (stub.versionNumber && stub.versionNumber > 0 || !isOriginalOfOthers);
2025-09-03 17:47:58 +01:00
});
// For each leaf file, build its complete lineage path back to original
for (const leafFile of leafFiles) {
2025-09-05 17:41:53 +01:00
const lineagePath: StirlingFileStub[] = [];
let currentFile: StirlingFileStub | undefined = leafFile;
2025-09-03 17:47:58 +01:00
// Trace back through parentFileId chain to build this specific branch
while (currentFile) {
lineagePath.push(currentFile);
2025-09-05 17:41:53 +01:00
2025-09-03 17:47:58 +01:00
// Move to parent file in this branch
2025-09-05 17:41:53 +01:00
let nextFile: StirlingFileStub | undefined = undefined;
2025-09-03 17:47:58 +01:00
if (currentFile.parentFileId) {
nextFile = fileMap.get(currentFile.parentFileId);
} else if (currentFile.originalFileId && currentFile.originalFileId !== currentFile.id) {
// For v1 files, the original file might be referenced by originalFileId
nextFile = fileMap.get(currentFile.originalFileId);
2025-09-03 14:48:14 +01:00
}
2025-09-05 17:41:53 +01:00
2025-09-03 17:47:58 +01:00
// Check for infinite loops before moving to next
if (nextFile && lineagePath.some(file => file.id === nextFile!.id)) {
break;
}
2025-09-05 17:41:53 +01:00
2025-09-03 17:47:58 +01:00
currentFile = nextFile;
2025-09-03 14:48:14 +01:00
}
2025-09-05 17:41:53 +01:00
2025-09-03 17:47:58 +01:00
// Sort lineage with latest version first (leaf at top)
lineagePath.sort((a, b) => (b.versionNumber || 0) - (a.versionNumber || 0));
2025-09-05 17:41:53 +01:00
2025-09-03 17:47:58 +01:00
// Use leaf file ID as the group key - each branch gets its own group
groups.set(leafFile.id, lineagePath);
2025-09-02 17:24:26 +01:00
}
return groups;
}
/**
2025-09-04 11:26:55 +01:00
* Get the latest version of each file group (optimized version using leaf flags)
2025-09-02 17:24:26 +01:00
*/
2025-09-05 17:41:53 +01:00
export function getLatestVersions(fileStubs: StirlingFileStub[]): StirlingFileStub[] {
2025-09-04 11:26:55 +01:00
// If we have leaf flags, use them for much faster filtering
2025-09-05 17:41:53 +01:00
const hasLeafFlags = fileStubs.some(fileStub => fileStub.isLeaf !== undefined);
2025-09-04 11:26:55 +01:00
if (hasLeafFlags) {
// Fast path: just return files marked as leaf nodes
2025-09-05 17:41:53 +01:00
return fileStubs.filter(fileStub => fileStub.isLeaf !== false); // Default to true if undefined
2025-09-04 11:26:55 +01:00
} else {
// Fallback to expensive calculation for backward compatibility
2025-09-05 17:41:53 +01:00
const groups = groupFilesByOriginal(fileStubs);
const latestVersions: StirlingFileStub[] = [];
2025-09-04 11:26:55 +01:00
2025-09-05 17:41:53 +01:00
for (const [_, fileStubs] of groups) {
if (fileStubs.length > 0) {
2025-09-04 11:26:55 +01:00
// First item is the latest version (sorted desc by version number)
2025-09-05 17:41:53 +01:00
latestVersions.push(fileStubs[0]);
2025-09-04 11:26:55 +01:00
}
2025-09-02 17:24:26 +01:00
}
2025-09-04 11:26:55 +01:00
return latestVersions;
}
2025-09-02 17:24:26 +01:00
}
/**
* Get version history for a file
*/
export function getVersionHistory(
2025-09-05 17:41:53 +01:00
targetFileStub: StirlingFileStub,
allFileStubs: StirlingFileStub[]
): StirlingFileStub[] {
const originalId = targetFileStub.originalFileId || targetFileStub.id;
return allFileStubs
.filter(fileStub => {
const fileStubOriginalId = fileStub.originalFileId || fileStub.id;
return fileStubOriginalId === originalId;
2025-09-02 17:24:26 +01:00
})
.sort((a, b) => (b.versionNumber || 0) - (a.versionNumber || 0));
}
/**
* Check if a file has version history
*/
2025-09-05 17:41:53 +01:00
export function hasVersionHistory(fileStub: StirlingFileStub): boolean {
return !!(fileStub.originalFileId && fileStub.versionNumber && fileStub.versionNumber > 0);
2025-09-02 17:24:26 +01:00
}
/**
* Generate a descriptive name for a file version
*/
2025-09-05 17:41:53 +01:00
export function generateVersionName(fileStub: StirlingFileStub): string {
const baseName = fileStub.name.replace(/\.pdf$/i, '');
2025-09-03 14:48:14 +01:00
2025-09-05 17:41:53 +01:00
if (!hasVersionHistory(fileStub)) {
return fileStub.name;
2025-09-02 17:24:26 +01:00
}
2025-09-05 17:41:53 +01:00
const versionInfo = fileStub.versionNumber ? ` (v${fileStub.versionNumber})` : '';
const toolInfo = fileStub.toolHistory && fileStub.toolHistory.length > 0
? ` - ${fileStub.toolHistory[fileStub.toolHistory.length - 1].toolName}`
2025-09-02 17:24:26 +01:00
: '';
2025-09-03 14:48:14 +01:00
2025-09-02 17:24:26 +01:00
return `${baseName}${versionInfo}${toolInfo}.pdf`;
}
2025-09-04 11:26:55 +01:00
/**
* Get recent files efficiently using leaf flags from IndexedDB
* This is much faster than loading all files and calculating leaf nodes
*/
export async function getRecentLeafFiles(): Promise<import('../services/fileStorage').StoredFile[]> {
try {
const { fileStorage } = await import('../services/fileStorage');
return await fileStorage.getLeafFiles();
} catch (error) {
console.warn('Failed to get recent leaf files from IndexedDB:', error);
return [];
}
}
/**
* Get recent file metadata efficiently using leaf flags from IndexedDB
* This is much faster than loading all files and calculating leaf nodes
*/
2025-09-10 10:03:35 +01:00
export async function getRecentLeafFileMetadata(): Promise<StoredFileMetadata[]> {
2025-09-04 11:26:55 +01:00
try {
const { fileStorage } = await import('../services/fileStorage');
return await fileStorage.getLeafFileMetadata();
} catch (error) {
console.warn('Failed to get recent leaf file metadata from IndexedDB:', error);
return [];
}
}
2025-09-04 12:11:09 +01:00
/**
2025-09-10 10:03:35 +01:00
* Create basic metadata for storing files
* History information is managed separately in IndexedDB
2025-09-02 17:24:26 +01:00
*/
export async function createFileMetadataWithHistory(
2025-09-03 14:48:14 +01:00
file: File,
fileId: FileId,
2025-09-02 17:24:26 +01:00
thumbnail?: string
2025-09-10 10:03:35 +01:00
): Promise<StoredFileMetadata> {
return {
2025-09-02 17:24:26 +01:00
id: fileId,
name: file.name,
type: file.type,
size: file.size,
lastModified: file.lastModified,
2025-09-04 11:26:55 +01:00
thumbnail,
isLeaf: true // New files are leaf nodes by default
2025-09-02 17:24:26 +01:00
};
2025-09-03 14:48:14 +01:00
}