source dump of claude code
at main 167 lines 4.3 kB view raw
1import { mkdir, open } from 'fs/promises' 2import { join } from 'path' 3import { getSessionId } from '../bootstrap/state.js' 4import type { PastedContent } from './config.js' 5import { logForDebugging } from './debug.js' 6import { getClaudeConfigHomeDir } from './envUtils.js' 7import { getFsImplementation } from './fsOperations.js' 8 9const IMAGE_STORE_DIR = 'image-cache' 10const MAX_STORED_IMAGE_PATHS = 200 11 12// In-memory cache of stored image paths 13const storedImagePaths = new Map<number, string>() 14 15/** 16 * Get the image store directory for the current session. 17 */ 18function getImageStoreDir(): string { 19 return join(getClaudeConfigHomeDir(), IMAGE_STORE_DIR, getSessionId()) 20} 21 22/** 23 * Ensure the image store directory exists. 24 */ 25async function ensureImageStoreDir(): Promise<void> { 26 const dir = getImageStoreDir() 27 await mkdir(dir, { recursive: true }) 28} 29 30/** 31 * Get the file path for an image by ID. 32 */ 33function getImagePath(imageId: number, mediaType: string): string { 34 const extension = mediaType.split('/')[1] || 'png' 35 return join(getImageStoreDir(), `${imageId}.${extension}`) 36} 37 38/** 39 * Cache the image path immediately (fast, no file I/O). 40 */ 41export function cacheImagePath(content: PastedContent): string | null { 42 if (content.type !== 'image') { 43 return null 44 } 45 const imagePath = getImagePath(content.id, content.mediaType || 'image/png') 46 evictOldestIfAtCap() 47 storedImagePaths.set(content.id, imagePath) 48 return imagePath 49} 50 51/** 52 * Store an image from pastedContents to disk. 53 */ 54export async function storeImage( 55 content: PastedContent, 56): Promise<string | null> { 57 if (content.type !== 'image') { 58 return null 59 } 60 61 try { 62 await ensureImageStoreDir() 63 const imagePath = getImagePath(content.id, content.mediaType || 'image/png') 64 const fh = await open(imagePath, 'w', 0o600) 65 try { 66 await fh.writeFile(content.content, { encoding: 'base64' }) 67 await fh.datasync() 68 } finally { 69 await fh.close() 70 } 71 evictOldestIfAtCap() 72 storedImagePaths.set(content.id, imagePath) 73 logForDebugging(`Stored image ${content.id} to ${imagePath}`) 74 return imagePath 75 } catch (error) { 76 logForDebugging(`Failed to store image: ${error}`) 77 return null 78 } 79} 80 81/** 82 * Store all images from pastedContents to disk. 83 */ 84export async function storeImages( 85 pastedContents: Record<number, PastedContent>, 86): Promise<Map<number, string>> { 87 const pathMap = new Map<number, string>() 88 89 for (const [id, content] of Object.entries(pastedContents)) { 90 if (content.type === 'image') { 91 const path = await storeImage(content) 92 if (path) { 93 pathMap.set(Number(id), path) 94 } 95 } 96 } 97 98 return pathMap 99} 100 101/** 102 * Get the file path for a stored image by ID. 103 */ 104export function getStoredImagePath(imageId: number): string | null { 105 return storedImagePaths.get(imageId) ?? null 106} 107 108/** 109 * Clear the in-memory cache of stored image paths. 110 */ 111export function clearStoredImagePaths(): void { 112 storedImagePaths.clear() 113} 114 115function evictOldestIfAtCap(): void { 116 while (storedImagePaths.size >= MAX_STORED_IMAGE_PATHS) { 117 const oldest = storedImagePaths.keys().next().value 118 if (oldest !== undefined) { 119 storedImagePaths.delete(oldest) 120 } else { 121 break 122 } 123 } 124} 125 126/** 127 * Clean up old image cache directories from previous sessions. 128 */ 129export async function cleanupOldImageCaches(): Promise<void> { 130 const fsImpl = getFsImplementation() 131 const baseDir = join(getClaudeConfigHomeDir(), IMAGE_STORE_DIR) 132 const currentSessionId = getSessionId() 133 134 try { 135 let sessionDirs 136 try { 137 sessionDirs = await fsImpl.readdir(baseDir) 138 } catch { 139 return 140 } 141 142 for (const sessionDir of sessionDirs) { 143 if (sessionDir.name === currentSessionId) { 144 continue 145 } 146 147 const sessionPath = join(baseDir, sessionDir.name) 148 try { 149 await fsImpl.rm(sessionPath, { recursive: true, force: true }) 150 logForDebugging(`Cleaned up old image cache: ${sessionPath}`) 151 } catch { 152 // Ignore errors for individual directories 153 } 154 } 155 156 try { 157 const remaining = await fsImpl.readdir(baseDir) 158 if (remaining.length === 0) { 159 await fsImpl.rmdir(baseDir) 160 } 161 } catch { 162 // Ignore 163 } 164 } catch { 165 // Ignore errors reading base directory 166 } 167}