|
1 | | -import { AACTree, AACSemanticCategory, AACSemanticIntent } from '../../core/treeStructure'; |
| 1 | +import { |
| 2 | + AACTree, |
| 3 | + AACSemanticCategory, |
| 4 | + AACSemanticIntent, |
| 5 | + AACButton, |
| 6 | +} from '../../core/treeStructure'; |
2 | 7 | import * as fs from 'fs'; |
3 | 8 | import * as path from 'path'; |
4 | 9 | import Database from 'better-sqlite3'; |
5 | 10 | import { dotNetTicksToDate } from '../../utils/dotnetTicks'; |
| 11 | +import { ProcessorInput } from '../../utils/io'; |
6 | 12 |
|
7 | 13 | // Minimal Snap helpers (stubs) to align with processors/<engine>/helpers pattern |
8 | 14 | // NOTE: Snap buttons currently do not populate resolvedImageEntry; these helpers |
@@ -57,18 +63,84 @@ export function getPageTokenImageMap(tree: AACTree, pageId: string): Map<string, |
57 | 63 |
|
58 | 64 | /** |
59 | 65 | * Collect all image entry paths referenced in a Snap tree. |
60 | | - * Currently empty until resolvedImageEntry is populated by the processor. |
| 66 | + * Returns the set of symbol identifiers (e.g., "SYM:12345") that are referenced by buttons. |
61 | 67 | */ |
62 | | -export function getAllowedImageEntries(_tree: AACTree): Set<string> { |
63 | | - return new Set<string>(); |
| 68 | +export function getAllowedImageEntries(tree: AACTree): Set<string> { |
| 69 | + const out = new Set<string>(); |
| 70 | + Object.values(tree.pages).forEach((page) => { |
| 71 | + page.buttons.forEach((btn: AACButton) => { |
| 72 | + // Extract image_id from parameters if it exists |
| 73 | + if (btn.parameters?.image_id && typeof btn.parameters.image_id === 'string') { |
| 74 | + out.add(btn.parameters.image_id); |
| 75 | + } |
| 76 | + // Also add resolvedImageEntry if it's a symbol identifier |
| 77 | + if (btn.resolvedImageEntry && typeof btn.resolvedImageEntry === 'string') { |
| 78 | + const entry = btn.resolvedImageEntry; |
| 79 | + if (entry.startsWith('SYM:')) { |
| 80 | + out.add(entry); |
| 81 | + } |
| 82 | + } |
| 83 | + }); |
| 84 | + }); |
| 85 | + return out; |
64 | 86 | } |
65 | 87 |
|
66 | 88 | /** |
67 | 89 | * Read a binary asset from a Snap pageset. |
68 | | - * Not implemented yet; provided for API symmetry with other processors. |
| 90 | + * @param dbOrFile Path to Snap .sps/.spb file or Buffer containing the file data |
| 91 | + * @param entryPath Symbol identifier (e.g., "SYM:12345") |
| 92 | + * @returns Image data buffer or null if not found |
69 | 93 | */ |
70 | | -export function openImage(_dbOrFile: string | Buffer, _entryPath: string): Buffer | null { |
71 | | - return null; |
| 94 | +export function openImage(dbOrFile: ProcessorInput, entryPath: string): Buffer | null { |
| 95 | + let dbPath: string; |
| 96 | + let cleanupNeeded = false; |
| 97 | + |
| 98 | + // Handle Buffer input by writing to temp file |
| 99 | + if (Buffer.isBuffer(dbOrFile)) { |
| 100 | + if (typeof fs.mkdtempSync !== 'function') { |
| 101 | + return null; // Not in Node environment |
| 102 | + } |
| 103 | + const tempDir = fs.mkdtempSync(path.join(process.cwd(), 'snap-')); |
| 104 | + dbPath = path.join(tempDir, 'temp.sps'); |
| 105 | + fs.writeFileSync(dbPath, dbOrFile); |
| 106 | + cleanupNeeded = true; |
| 107 | + } else if (typeof dbOrFile === 'string') { |
| 108 | + dbPath = dbOrFile; |
| 109 | + } else { |
| 110 | + return null; |
| 111 | + } |
| 112 | + |
| 113 | + let db: Database.Database | null = null; |
| 114 | + try { |
| 115 | + db = new Database(dbPath, { readonly: true }); |
| 116 | + |
| 117 | + // Query PageSetData for the symbol |
| 118 | + const row = db |
| 119 | + .prepare('SELECT Id, Identifier, Data FROM PageSetData WHERE Identifier = ?') |
| 120 | + .get(entryPath) as { Id: number; Identifier: string; Data: Buffer } | undefined; |
| 121 | + |
| 122 | + if (row && row.Data && row.Data.length > 0) { |
| 123 | + return row.Data; |
| 124 | + } |
| 125 | + |
| 126 | + return null; |
| 127 | + } catch (error) { |
| 128 | + console.warn(`[Snap helpers] Failed to open image ${entryPath}:`, error); |
| 129 | + return null; |
| 130 | + } finally { |
| 131 | + if (db) { |
| 132 | + db.close(); |
| 133 | + } |
| 134 | + if (cleanupNeeded && dbPath) { |
| 135 | + try { |
| 136 | + fs.unlinkSync(dbPath); |
| 137 | + const dir = path.dirname(dbPath); |
| 138 | + fs.rmdirSync(dir); |
| 139 | + } catch (e) { |
| 140 | + // Ignore cleanup errors |
| 141 | + } |
| 142 | + } |
| 143 | + } |
72 | 144 | } |
73 | 145 |
|
74 | 146 | /** |
|
0 commit comments