| |
| |
| |
| |
| |
| |
| |
|
|
| import type { AnalysisData } from '../api/GLTR_API'; |
| import type { IDemoStorage, LoadResult } from './demoStorage'; |
| import { ServerStorage } from './demoStorage'; |
| import { LocalDemoCache } from './localDemoCache'; |
| import type { TextAnalysisAPI } from '../api/GLTR_API'; |
| import { ensureJsonExtension, validateDemoFormat } from '../utils/localFileUtils'; |
| import { extractErrorMessage } from '../utils/errorUtils'; |
| import { isValidHash } from '../utils/hashUtils'; |
| import { trf } from '../lang/i18n-lite'; |
|
|
| export type ResourceIdentifier = string; |
|
|
| |
| |
| |
| |
| |
| function parseResourceIdentifier(identifier: ResourceIdentifier): { |
| type: 'server' | 'local'; |
| path: string; |
| hash?: string; |
| filename?: string; |
| } { |
| if (identifier.startsWith('local://')) { |
| const rest = identifier.substring('local://'.length); |
| |
| const hashIndex = rest.lastIndexOf('~'); |
| |
| if (hashIndex >= 0) { |
| const filename = rest.substring(0, hashIndex); |
| const hash = rest.substring(hashIndex + 1); |
| |
| |
| if (!isValidHash(hash)) { |
| throw new Error(trf('Invalid hash format: "{hash}", expected 4 hexadecimal characters', { hash })); |
| } |
| |
| return { |
| type: 'local', |
| path: hash, |
| hash, |
| filename |
| }; |
| } |
| |
| |
| throw new Error(trf('Local resource identifier missing hash: "{identifier}", format should be local://filename.json~hash', { identifier })); |
| } |
| |
| return { |
| type: 'server', |
| path: identifier |
| }; |
| } |
|
|
| |
| |
| |
| |
| export class DemoResourceLoader { |
| private serverStorage: ServerStorage; |
| private localDemoCache: LocalDemoCache; |
|
|
| constructor(api: TextAnalysisAPI) { |
| this.serverStorage = new ServerStorage(api); |
| this.localDemoCache = new LocalDemoCache(); |
| } |
|
|
| |
| |
| |
| |
| async load(identifier: ResourceIdentifier): Promise<LoadResult> { |
| const { type, path, hash, filename } = parseResourceIdentifier(identifier); |
|
|
| const storage: IDemoStorage = type === 'local' |
| ? this.localDemoCache |
| : this.serverStorage; |
|
|
| |
| |
| const loadKey = type === 'local' && filename && hash |
| ? `${filename}~${hash}` |
| : path; |
|
|
| |
| const result = await storage.load(loadKey); |
|
|
| |
| if (result.success && result.data) { |
| try { |
| validateDemoFormat(result.data); |
| } catch (error) { |
| const errorMessage = extractErrorMessage(error, '数据格式无效'); |
| console.error(`资源验证失败 [${identifier}]:`, error); |
| return { |
| success: false, |
| message: `数据格式无效: ${errorMessage}` |
| }; |
| } |
| } |
|
|
| return result; |
| } |
|
|
| |
| |
| |
| getLocalDemoCache(): LocalDemoCache { |
| return this.localDemoCache; |
| } |
|
|
| |
| |
| |
| getServerStorage(): ServerStorage { |
| return this.serverStorage; |
| } |
|
|
| |
| |
| |
| static isLocalResource(identifier: ResourceIdentifier): boolean { |
| return identifier.startsWith('local://'); |
| } |
|
|
| |
| |
| |
| |
| |
| static createLocalIdentifier(filename: string, hash: string): ResourceIdentifier { |
| const name = ensureJsonExtension(filename); |
| return `local://${name}~${hash}`; |
| } |
|
|
| |
| |
| |
| |
| static extractLocalInfo(identifier: ResourceIdentifier): { filename: string; hash: string } { |
| const parsed = parseResourceIdentifier(identifier); |
| if (parsed.type === 'local' && parsed.hash && parsed.filename) { |
| return { |
| filename: parsed.filename, |
| hash: parsed.hash |
| }; |
| } |
| throw new Error(trf('Unable to extract local resource info: "{identifier}"', { identifier })); |
| } |
| } |
|
|
|
|