TMDB cahce issue fix and upload to webdav
This commit is contained in:
+51
-59
@@ -3,21 +3,21 @@
|
|||||||
*
|
*
|
||||||
* Fetches metadata from The Movie Database (TMDB) API.
|
* Fetches metadata from The Movie Database (TMDB) API.
|
||||||
* Supports both v3 API keys and v4 Bearer tokens.
|
* Supports both v3 API keys and v4 Bearer tokens.
|
||||||
* Downloads poster and backdrop images to /output/
|
* Downloads poster and backdrop images via WebDAV to /files/plugin/tmdb/
|
||||||
*
|
*
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
* PLUGIN MOUNT ARCHITECTURE - DO NOT MODIFY WITHOUT AUTHORIZATION
|
* PLUGIN FILE ACCESS ARCHITECTURE
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*
|
*
|
||||||
* Each plugin container has exactly 3 mounts:
|
* File access via WebDAV (WEBDAV_URL environment variable):
|
||||||
|
* - Read media files: GET /webdav/watch/... or /webdav/test/...
|
||||||
|
* - Write output: PUT /webdav/plugin/tmdb/...
|
||||||
|
* - Cache: Local /cache mount (for JSON cache files)
|
||||||
*
|
*
|
||||||
* 1. /files (READ-ONLY) - Shared media files, read access only
|
* Benefits:
|
||||||
* 2. /cache (READ-WRITE) - Plugin-specific cache folder
|
* - No output mount needed on plugin containers
|
||||||
* 3. /output (READ-WRITE) - Plugin output folder for posters/images
|
* - Consistent read/write architecture via HTTP
|
||||||
*
|
* - Works in any orchestration environment
|
||||||
* SECURITY: Plugins must NEVER write to /files directly.
|
|
||||||
* - Use /cache for temporary/cache data (e.g., TMDB API responses)
|
|
||||||
* - Use /output for output files (e.g., posters, backdrops)
|
|
||||||
*
|
*
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*
|
*
|
||||||
@@ -30,37 +30,34 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { createWriteStream, existsSync, mkdirSync, statSync, openSync, readSync, closeSync } from 'fs';
|
import { statSync, openSync, readSync, closeSync } from 'fs';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { pipeline } from 'stream/promises';
|
|
||||||
import type { PluginManifest, ProcessRequest, CallbackPayload } from './types.js';
|
import type { PluginManifest, ProcessRequest, CallbackPayload } from './types.js';
|
||||||
import { MetaCoreClient } from './meta-core-client.js';
|
import { MetaCoreClient } from './meta-core-client.js';
|
||||||
import { readJson, writeJson } from './cache.js';
|
import { readJson, writeJson } from './cache.js';
|
||||||
import { createWebDAVClient, WebDAVClient } from './webdav-client.js';
|
import { createWebDAVClient, WebDAVClient } from './webdav-client.js';
|
||||||
|
|
||||||
// Initialize WebDAV client if WEBDAV_URL is set
|
// Initialize WebDAV client - required for plugin operation
|
||||||
const webdavClient = createWebDAVClient();
|
const webdavClient = createWebDAVClient();
|
||||||
if (webdavClient) {
|
if (webdavClient) {
|
||||||
console.log('[tmdb] Using WebDAV for file access');
|
console.log('[tmdb] WebDAV client initialized for file access');
|
||||||
} else {
|
} else {
|
||||||
console.log('[tmdb] Using direct filesystem access');
|
console.warn('[tmdb] WARNING: WEBDAV_URL not set - plugin will not be able to write output files');
|
||||||
}
|
}
|
||||||
|
|
||||||
const IMAGE_BASE_URL = 'https://image.tmdb.org/t/p/original';
|
const IMAGE_BASE_URL = 'https://image.tmdb.org/t/p/original';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
* MOUNT PATHS - Enforced by plugin architecture
|
* PLUGIN OUTPUT PATH - Written via WebDAV
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
* FILES_PATH (/files) - READ-ONLY access to media files
|
* Output files (posters, backdrops) are written via WebDAV PUT requests.
|
||||||
* PLUGIN_OUTPUT_PATH (/output) - READ-WRITE for plugin-generated files
|
* Path: /files/plugin/tmdb/<filename> (accessible at WEBDAV_URL/plugin/tmdb/)
|
||||||
* CACHE_PATH (/cache) - READ-WRITE for plugin cache (handled by cache.ts)
|
* CACHE_PATH (/cache) - Local mount for plugin cache (handled by cache.ts)
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*/
|
*/
|
||||||
// Use globalThis.process to avoid conflict with the exported process function
|
const PLUGIN_OUTPUT_WEBDAV_PATH = '/files/plugin/tmdb';
|
||||||
const FILES_PATH = (globalThis as any).process?.env?.FILES_PATH || '/files';
|
|
||||||
const PLUGIN_OUTPUT_PATH = '/output';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute midhash256 CID for a file (matches meta-hash algorithm)
|
* Compute midhash256 CID for a file (matches meta-hash algorithm)
|
||||||
@@ -315,34 +312,32 @@ function sanitizeFilename(name: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download an image from URL to local path
|
* Download an image from URL and upload to WebDAV
|
||||||
*
|
*
|
||||||
* IMPORTANT: Images are saved to PLUGIN_OUTPUT_PATH (/output)
|
* Images are written via WebDAV PUT to /files/plugin/tmdb/
|
||||||
* NOT to /files directly. See mount architecture comments at top of file.
|
* This allows output without mounting a volume to the plugin container.
|
||||||
*/
|
*/
|
||||||
async function downloadImage(imageUrl: string, localPath: string): Promise<boolean> {
|
async function downloadImageToWebDAV(
|
||||||
|
client: WebDAVClient,
|
||||||
|
imageUrl: string,
|
||||||
|
webdavPath: string
|
||||||
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
|
// Download image as buffer
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: imageUrl,
|
url: imageUrl,
|
||||||
responseType: 'stream',
|
responseType: 'arraybuffer',
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure directory exists
|
// Upload to WebDAV
|
||||||
const dir = path.dirname(localPath);
|
await client.writeFile(webdavPath, Buffer.from(response.data));
|
||||||
if (!existsSync(dir)) {
|
|
||||||
mkdirSync(dir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipe the response to a file
|
console.log(`[tmdb] Uploaded image to WebDAV: ${webdavPath}`);
|
||||||
const writer = createWriteStream(localPath);
|
|
||||||
await pipeline(response.data, writer);
|
|
||||||
|
|
||||||
console.log(`[tmdb] Downloaded image to ${localPath}`);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[tmdb] Failed to download image from ${imageUrl}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(`[tmdb] Failed to download/upload image from ${imageUrl}: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,8 +345,7 @@ async function downloadImage(imageUrl: string, localPath: string): Promise<boole
|
|||||||
/**
|
/**
|
||||||
* Download and hash an image, returning its CID
|
* Download and hash an image, returning its CID
|
||||||
*
|
*
|
||||||
* IMPORTANT: Images are saved to PLUGIN_OUTPUT_PATH (/output)
|
* Images are written via WebDAV PUT to /files/plugin/tmdb/
|
||||||
* This is the plugin's dedicated output folder with READ-WRITE access.
|
|
||||||
* The files will be picked up by meta-sort's file watcher and processed like any other file.
|
* The files will be picked up by meta-sort's file watcher and processed like any other file.
|
||||||
*
|
*
|
||||||
* @returns {Promise<string | null>} The midhash256 CID of the downloaded image
|
* @returns {Promise<string | null>} The midhash256 CID of the downloaded image
|
||||||
@@ -367,6 +361,11 @@ async function downloadAndHashImage(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!webdavClient) {
|
||||||
|
console.error(`[tmdb] WebDAV client not available, cannot write output files`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const imageUrl = `${IMAGE_BASE_URL}${imagePath}`;
|
const imageUrl = `${IMAGE_BASE_URL}${imagePath}`;
|
||||||
const ext = path.extname(imagePath) || '.jpg';
|
const ext = path.extname(imagePath) || '.jpg';
|
||||||
|
|
||||||
@@ -376,35 +375,28 @@ async function downloadAndHashImage(
|
|||||||
const yearStr = year ? ` (${year})` : '';
|
const yearStr = year ? ` (${year})` : '';
|
||||||
const filename = `${safeName}${yearStr}[tmdb${tmdbId}]_${imageType}${ext}`;
|
const filename = `${safeName}${yearStr}[tmdb${tmdbId}]_${imageType}${ext}`;
|
||||||
|
|
||||||
// IMPORTANT: Write to PLUGIN_OUTPUT_PATH, not FILES_PATH
|
// WebDAV path for output file
|
||||||
// This respects the mount architecture: /files is READ-ONLY
|
const webdavPath = `${PLUGIN_OUTPUT_WEBDAV_PATH}/${filename}`;
|
||||||
const localPath = path.join(PLUGIN_OUTPUT_PATH, filename);
|
|
||||||
|
|
||||||
// Check if file already exists (skip download)
|
// Check if file already exists via WebDAV HEAD
|
||||||
if (existsSync(localPath)) {
|
const exists = await webdavClient.exists(webdavPath);
|
||||||
console.log(`[tmdb] Image already exists: ${localPath}`);
|
if (exists) {
|
||||||
|
console.log(`[tmdb] Image already exists: ${webdavPath}`);
|
||||||
} else {
|
} else {
|
||||||
// Ensure plugin output directory exists
|
// Download the image and upload via WebDAV
|
||||||
if (!existsSync(PLUGIN_OUTPUT_PATH)) {
|
const uploaded = await downloadImageToWebDAV(webdavClient, imageUrl, webdavPath);
|
||||||
mkdirSync(PLUGIN_OUTPUT_PATH, { recursive: true });
|
if (!uploaded) {
|
||||||
console.log(`[tmdb] Created plugin output directory: ${PLUGIN_OUTPUT_PATH}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download the image
|
|
||||||
const downloaded = await downloadImage(imageUrl, localPath);
|
|
||||||
if (!downloaded) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute midhash256 CID (same algorithm as meta-sort file entries)
|
// Compute midhash256 CID via WebDAV (reads file back to compute hash)
|
||||||
// Note: For /output files, we use sync version since they're on local filesystem
|
|
||||||
try {
|
try {
|
||||||
const cid = computeMidHash256Sync(localPath);
|
const cid = await computeMidHash256WebDAV(webdavClient, webdavPath);
|
||||||
console.log(`[tmdb] Image ${imageType} CID: ${cid}`);
|
console.log(`[tmdb] Image ${imageType} CID: ${cid}`);
|
||||||
return cid;
|
return cid;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[tmdb] Failed to compute CID for ${localPath}: ${e instanceof Error ? e.message : String(e)}`);
|
console.error(`[tmdb] Failed to compute CID for ${webdavPath}: ${e instanceof Error ? e.message : String(e)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user