From 4c88566b0148cc3ea6348c373846c420745eab7f Mon Sep 17 00:00:00 2001 From: Pierre Date: Sat, 10 Jan 2026 19:14:55 +0100 Subject: [PATCH] bugfix --- Dockerfile | 10 +++++ package.json | 8 +--- src/cache.ts | 54 +++++++++++++++++++++++++ src/index.ts | 20 +++++++++- src/plugin.ts | 107 +++++++++++++------------------------------------- 5 files changed, 113 insertions(+), 86 deletions(-) create mode 100644 src/cache.ts diff --git a/Dockerfile b/Dockerfile index 7c4d4cc..57a5555 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,10 +3,15 @@ FROM node:20-slim AS builder +# Install git for GitHub dependencies +RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* + WORKDIR /app COPY package.json tsconfig.json ./ RUN npm install +# Build the git dependency (filename-tools) +RUN cd node_modules/@metazla/filename-tools && npm install && npm run build COPY src/ ./src/ RUN npm run build @@ -16,8 +21,13 @@ FROM node:20-slim WORKDIR /app +# Install git for GitHub dependencies +RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* + COPY package.json ./ RUN npm install --omit=dev +# Build the git dependency in production too +RUN cd node_modules/@metazla/filename-tools && npm install && npm run build COPY --from=builder /app/dist ./dist diff --git a/package.json b/package.json index 57367b6..13ea56d 100644 --- a/package.json +++ b/package.json @@ -6,18 +6,14 @@ "main": "dist/index.js", "scripts": { "build": "tsc", - "start": "node dist/index.js", - "dev": "tsx watch src/index.ts" + "start": "node dist/index.js" }, "dependencies": { "fastify": "^5.0.0", - "file-type": "^19.0.0", - "mime-types": "^2.1.35" + "@metazla/filename-tools": "github:worph/filename-tool" }, "devDependencies": { - "@types/mime-types": "^2.1.4", "@types/node": "^20.0.0", - "tsx": "^4.0.0", "typescript": "^5.3.0" }, "engines": { diff --git a/src/cache.ts b/src/cache.ts new file mode 100644 index 0000000..0415883 --- /dev/null +++ b/src/cache.ts @@ -0,0 +1,54 @@ +/** + * File-based cache utility for plugin data + * Matches the old ctx.cache API: readJson, writeJson + */ + +import { readFile, writeFile, mkdir } from 'fs/promises'; +import { existsSync } from 'fs'; +import path from 'path'; + +const CACHE_DIR = process.env.CACHE_DIR || '/cache'; + +/** + * Ensure cache directory exists + */ +async function ensureCacheDir(): Promise { + if (!existsSync(CACHE_DIR)) { + await mkdir(CACHE_DIR, { recursive: true }); + } +} + +/** + * Read JSON from cache + * @param filename - Cache filename (e.g., "abc123.json") + * @returns Parsed JSON object or null if not found + */ +export async function readJson(filename: string): Promise { + try { + const filePath = path.join(CACHE_DIR, filename); + if (!existsSync(filePath)) { + return null; + } + const content = await readFile(filePath, 'utf-8'); + return JSON.parse(content) as T; + } catch (error) { + console.debug(`[cache] Failed to read ${filename}:`, error); + return null; + } +} + +/** + * Write JSON to cache + * @param filename - Cache filename (e.g., "abc123.json") + * @param data - Data to cache + */ +export async function writeJson(filename: string, data: unknown): Promise { + try { + await ensureCacheDir(); + const filePath = path.join(CACHE_DIR, filename); + await writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8'); + console.debug(`[cache] Wrote ${filename}`); + } catch (error) { + console.warn(`[cache] Failed to write ${filename}:`, error); + } +} diff --git a/src/index.ts b/src/index.ts index f55ad3f..ddfbed2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,24 @@ /** * MetaMesh Plugin: file-info - * Entry point + * + * ============================================================================ + * PLUGIN MOUNT ARCHITECTURE - DO NOT MODIFY WITHOUT AUTHORIZATION + * ============================================================================ + * + * Each plugin container has exactly 3 mounts (2 for plugins without output): + * + * 1. /files (READ-ONLY) - Shared media files, read access only + * 2. /cache (READ-WRITE) - Plugin-specific cache folder + * 3. /files/plugin/ (READ-WRITE) - Plugin output folder (if needed) + * + * This plugin (file-info) only requires mounts 1 and 2: + * - /files (RO) - to read files for type/size detection + * - /cache (RW) - to cache file info results + * + * SECURITY: Plugins must NEVER write to /files directly. + * All write operations go to /cache or /files/plugin/ only. + * + * ============================================================================ */ import Fastify from 'fastify'; diff --git a/src/plugin.ts b/src/plugin.ts index ffa6560..8c04beb 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -3,15 +3,23 @@ * * Extracts basic file information: type, MIME type, and size. * This is the first plugin in the processing chain. + * + * Matches old FileProcessor output: + * - fileType + * - mimeType + * - sizeByte + * - fileName + * - extension */ -import { stat } from 'fs/promises'; -import { basename, extname } from 'path'; -import { fileTypeFromFile } from 'file-type'; -import { lookup as mimeTypeLookup } from 'mime-types'; +import { promises as fs } from 'fs'; +import * as path from 'path'; +import { FileType } from '@metazla/filename-tools'; import type { PluginManifest, ProcessRequest, CallbackPayload } from './types.js'; import { MetaCoreClient } from './meta-core-client.js'; +const fileType = new FileType(); + export const manifest: PluginManifest = { id: 'file-info', name: 'File Information', @@ -24,12 +32,6 @@ export const manifest: PluginManifest = { defaultQueue: 'fast', timeout: 30000, schema: { - privateFilePath: { - label: 'Original File Path', - type: 'string', - readonly: true, - hint: 'Physical path on disk (used by Stremio addon and FUSE)', - }, fileType: { label: 'File Type', type: 'string', @@ -59,41 +61,6 @@ export const manifest: PluginManifest = { config: {}, }; -// File type detection based on extension (matching @metazla/filename-tools) -// Note: Images are classified as 'document' not 'image' per original design -const EXTENSION_MAPPINGS: Record = { - // Video - 'mp4': 'video', 'mkv': 'video', 'webm': 'video', 'avi': 'video', - 'mov': 'video', 'wmv': 'video', 'flv': 'video', 'm4v': 'video', - 'mpg': 'video', 'mpeg': 'video', '3gp': 'video', '3g2': 'video', - 'f4v': 'video', 'm2ts': 'video', 'mts': 'video', 'ts': 'video', - 'vob': 'video', 'ogm': 'video', 'divx': 'video', 'xvid': 'video', - // Audio - 'mp3': 'audio', 'wav': 'audio', 'flac': 'audio', 'aac': 'audio', - 'ogg': 'audio', 'm4a': 'audio', - // Document (includes images per original design) - 'pdf': 'document', 'doc': 'document', 'docx': 'document', - 'ppt': 'document', 'pptx': 'document', 'xls': 'document', - 'xlsx': 'document', 'txt': 'document', 'rtf': 'document', - // Images are classified as 'document' per original library - 'jpg': 'document', 'jpeg': 'document', 'png': 'document', - 'gif': 'document', 'bmp': 'document', 'tif': 'document', - 'tiff': 'document', 'svg': 'document', 'webp': 'document', - // Archive - 'zip': 'archive', 'rar': 'archive', '7z': 'archive', - 'tar': 'archive', 'gz': 'archive', 'bz2': 'archive', 'xz': 'archive', - // Subtitles - 'srt': 'subtitle', 'sub': 'subtitle', 'sbv': 'subtitle', 'vtt': 'subtitle', - // Torrent - 'torrent': 'torrent', -}; - -function getFileTypeFromExtension(ext: string): string { - if (!ext) return 'undefined'; - const lowerExt = ext.toLowerCase(); - return EXTENSION_MAPPINGS[lowerExt] || 'other'; -} - export async function process( request: ProcessRequest, sendCallback: (payload: CallbackPayload) => Promise @@ -104,44 +71,27 @@ export async function process( try { const { cid, filePath } = request; - // Get file stats - const stats = await stat(filePath); - - // Get file name and extension - const fileName = basename(filePath); - const extension = extname(filePath).slice(1).toLowerCase(); - - // Determine file type - let fileType = getFileTypeFromExtension(extension); - - // Try to get MIME type from file magic bytes - let mimeType: string | undefined; - try { - const detected = await fileTypeFromFile(filePath); - if (detected) { - mimeType = detected.mime; - } - } catch { - // Fallback to extension-based lookup + // Get file type (video, audio, image, etc.) - matches old FileProcessor + const typeResult = await fileType.getFileType(filePath); + if (typeResult) { + await metaCore.setProperty(cid, 'fileType', typeResult); } - // Fallback MIME type lookup by extension - if (!mimeType) { - mimeType = mimeTypeLookup(extension) || 'application/octet-stream'; + // Get MIME type - matches old FileProcessor + const mimeType = await fileType.getMimeTypeFromFile(filePath); + if (mimeType) { + await metaCore.setProperty(cid, 'mimeType', mimeType); } - // Prepare metadata - const metadata = { - fileType, - mimeType, - sizeByte: String(stats.size), - fileName, - extension, - filePath, // Store the file path for other plugins/services - }; + // Get file size - matches old FileProcessor + const stats = await fs.stat(filePath); + await metaCore.setProperty(cid, 'sizeByte', String(stats.size)); - // Write metadata to meta-core - await metaCore.mergeMetadata(cid, metadata); + // Also store filename and extension for convenience - matches old FileProcessor + const fileName = path.basename(filePath); + const extension = path.extname(filePath).slice(1).toLowerCase(); + await metaCore.setProperty(cid, 'fileName', fileName); + await metaCore.setProperty(cid, 'extension', extension); const duration = Date.now() - startTime; console.log(`[file-info] Processed ${fileName} in ${duration}ms`); @@ -150,7 +100,6 @@ export async function process( taskId: request.taskId, status: 'completed', duration, - metadata, // Include metadata in callback as fallback }); } catch (error) { const duration = Date.now() - startTime;