Stirling-PDF/frontend/src/services/processingCache.ts
Reece Browne 922bbc9076
Stirling 2.0 (#3928)
# Description of Changes

<!--

File context for managing files between tools and views
Optimisation for large files
Updated Split to work with new file system and match Matts stepped
design closer

-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-07-16 17:53:50 +01:00

138 lines
3.2 KiB
TypeScript

import { ProcessedFile, CacheConfig, CacheEntry, CacheStats } from '../types/processing';
export class ProcessingCache {
private cache = new Map<string, CacheEntry>();
private totalSize = 0;
constructor(private config: CacheConfig = {
maxFiles: 20,
maxSizeBytes: 2 * 1024 * 1024 * 1024, // 2GB
ttlMs: 30 * 60 * 1000 // 30 minutes
}) {}
set(key: string, data: ProcessedFile): void {
// Remove expired entries first
this.cleanup();
// Calculate entry size (rough estimate)
const size = this.calculateSize(data);
// Make room if needed
this.makeRoom(size);
this.cache.set(key, {
data,
size,
lastAccessed: Date.now(),
createdAt: Date.now()
});
this.totalSize += size;
}
get(key: string): ProcessedFile | null {
const entry = this.cache.get(key);
if (!entry) return null;
// Check TTL
if (Date.now() - entry.createdAt > this.config.ttlMs) {
this.delete(key);
return null;
}
// Update last accessed
entry.lastAccessed = Date.now();
return entry.data;
}
has(key: string): boolean {
const entry = this.cache.get(key);
if (!entry) return false;
// Check TTL
if (Date.now() - entry.createdAt > this.config.ttlMs) {
this.delete(key);
return false;
}
return true;
}
private makeRoom(neededSize: number): void {
// Remove oldest entries until we have space
while (
this.cache.size >= this.config.maxFiles ||
this.totalSize + neededSize > this.config.maxSizeBytes
) {
const oldestKey = this.findOldestEntry();
if (oldestKey) {
this.delete(oldestKey);
} else break;
}
}
private findOldestEntry(): string | null {
let oldest: { key: string; lastAccessed: number } | null = null;
for (const [key, entry] of this.cache) {
if (!oldest || entry.lastAccessed < oldest.lastAccessed) {
oldest = { key, lastAccessed: entry.lastAccessed };
}
}
return oldest?.key || null;
}
private cleanup(): void {
const now = Date.now();
for (const [key, entry] of this.cache) {
if (now - entry.createdAt > this.config.ttlMs) {
this.delete(key);
}
}
}
private calculateSize(data: ProcessedFile): number {
// Rough size estimation
let size = 0;
// Estimate size of thumbnails (main memory consumer)
data.pages.forEach(page => {
if (page.thumbnail) {
// Base64 thumbnail is roughly 50KB each
size += 50 * 1024;
}
});
// Add some overhead for other data
size += 10 * 1024; // 10KB overhead
return size;
}
delete(key: string): void {
const entry = this.cache.get(key);
if (entry) {
this.totalSize -= entry.size;
this.cache.delete(key);
}
}
clear(): void {
this.cache.clear();
this.totalSize = 0;
}
getStats(): CacheStats {
return {
entries: this.cache.size,
totalSizeBytes: this.totalSize,
maxSizeBytes: this.config.maxSizeBytes
};
}
// Get all cached keys (for debugging and cleanup)
getKeys(): string[] {
return Array.from(this.cache.keys());
}
}