diff --git a/README.md b/README.md index 8a7a667..794c16b 100644 --- a/README.md +++ b/README.md @@ -242,8 +242,8 @@ WEBDAV_USERNAME=metamesh # WebDAV username WEBDAV_PASSWORD=metamesh # WebDAV password WEBDAV_READONLY=true # Read-only mode -# meta-sort WebDAV Access (reading files from meta-sort) -META_SORT_WEBDAV_URL=http://meta-sort-dev/webdav # URL to access files via meta-sort's WebDAV +# meta-core WebDAV Access (reading files from meta-core) +META_CORE_WEBDAV_URL=http://meta-core/webdav # URL to access files via meta-core's WebDAV # Enables access to SMB/rclone mounts ``` @@ -276,8 +276,8 @@ services: - FILES_VOLUME=/files - FUSE_MOUNT_POINT=/mnt/virtual - REDIS_PREFIX=meta-sort: - # Optional: Read files via meta-sort WebDAV (enables SMB/rclone mount access) - - META_SORT_WEBDAV_URL=http://meta-sort/webdav + # Optional: Read files via meta-core WebDAV (enables SMB/rclone mount access) + - META_CORE_WEBDAV_URL=http://meta-core/webdav ``` ## API Endpoints diff --git a/packages/meta-fuse-core/src/index.ts b/packages/meta-fuse-core/src/index.ts index 2455026..20c7525 100644 --- a/packages/meta-fuse-core/src/index.ts +++ b/packages/meta-fuse-core/src/index.ts @@ -57,13 +57,13 @@ async function main(): Promise { vfsRefreshInterval: parseInt(process.env.VFS_REFRESH_INTERVAL ?? '30000', 10), baseUrl: process.env.BASE_URL, serviceVersion: process.env.SERVICE_VERSION ?? '1.0.0', - // WebDAV URL for meta-sort file access (e.g., http://meta-sort-dev/webdav) - metaSortWebdavUrl: process.env.META_SORT_WEBDAV_URL, + // WebDAV URL for meta-core file access (e.g., http://meta-core/webdav) + metaCoreWebdavUrl: process.env.META_CORE_WEBDAV_URL, }; logger.info(`Config: META_CORE_PATH=${config.metaCorePath}, FILES_VOLUME=${config.filesPath}`); - if (config.metaSortWebdavUrl) { - logger.info(`WebDAV: Using meta-sort WebDAV at ${config.metaSortWebdavUrl}`); + if (config.metaCoreWebdavUrl) { + logger.info(`WebDAV: Using meta-core WebDAV at ${config.metaCoreWebdavUrl}`); } else { logger.info(`WebDAV: Not configured, FUSE driver will use local filesystem`); } @@ -125,7 +125,7 @@ async function main(): Promise { gid: parseInt(process.env.PGID ?? '1000', 10), refreshInterval: config.vfsRefreshInterval, configDir: process.env.CONFIG_DIR ?? '/meta-fuse/config', - webdavBaseUrl: config.metaSortWebdavUrl, + webdavBaseUrl: config.metaCoreWebdavUrl, filesPath: config.filesPath, }); diff --git a/packages/meta-fuse-core/src/vfs/VirtualFileSystem.ts b/packages/meta-fuse-core/src/vfs/VirtualFileSystem.ts index 3119899..4dfdaad 100644 --- a/packages/meta-fuse-core/src/vfs/VirtualFileSystem.ts +++ b/packages/meta-fuse-core/src/vfs/VirtualFileSystem.ts @@ -221,7 +221,7 @@ export class VirtualFileSystem { // Compute virtual path using rules or fallback const virtualPath = this.computeVirtualPath(metadata, sourcePath); if (virtualPath) { - this.addFile(virtualPath, metadata); + await this.addFile(virtualPath, metadata); this.sourcePathToVirtualPath.set(sourcePath, virtualPath); } } @@ -325,7 +325,7 @@ export class VirtualFileSystem { } // Add/update file - this.addFile(virtualPath, metadata); + await this.addFile(virtualPath, metadata); this.sourcePathToVirtualPath.set(metadata.sourcePath, virtualPath); logger.debug(`File ${action}ed: ${virtualPath}`); @@ -334,7 +334,7 @@ export class VirtualFileSystem { /** * Add a file to the VFS */ - private addFile(virtualPath: string, metadata: FileMetadata): void { + private async addFile(virtualPath: string, metadata: FileMetadata): Promise { virtualPath = this.normalizePath(virtualPath); // Skip if file already exists at this path @@ -363,8 +363,20 @@ export class VirtualFileSystem { mtime = stats.mtime; ctime = stats.ctime; } catch { - logger.warn(`Cannot stat source file: ${metadata.sourcePath}`); - return; + // Local stat failed, try WebDAV if configured + if (this.config.webdavBaseUrl) { + const webdavStats = await this.statViaWebDAV(metadata.sourcePath); + if (webdavStats) { + size = webdavStats.size; + mtime = webdavStats.mtime || mtime; + } else { + logger.warn(`Cannot stat source file via WebDAV: ${metadata.sourcePath}`); + return; + } + } else { + logger.warn(`Cannot stat source file: ${metadata.sourcePath}`); + return; + } } } @@ -392,6 +404,49 @@ export class VirtualFileSystem { this.cachedStats.totalSize += size; } + /** + * Get file stats via WebDAV HEAD request + */ + private async statViaWebDAV(sourcePath: string): Promise<{ size: number; mtime?: Date } | null> { + if (!this.config.webdavBaseUrl) { + return null; + } + + try { + // Convert source path to WebDAV URL + let relativePath = sourcePath; + if (this.config.filesPath && sourcePath.startsWith(this.config.filesPath)) { + relativePath = sourcePath.slice(this.config.filesPath.length); + } + if (!relativePath.startsWith('/')) { + relativePath = '/' + relativePath; + } + + // URL-encode path segments (but not slashes) + const encodedPath = relativePath.split('/').map(segment => encodeURIComponent(segment)).join('/'); + const webdavUrl = this.config.webdavBaseUrl + encodedPath; + + // Make HEAD request + const response = await fetch(webdavUrl, { method: 'HEAD' }); + + if (response.ok) { + const contentLength = response.headers.get('Content-Length'); + const lastModified = response.headers.get('Last-Modified'); + + return { + size: contentLength ? parseInt(contentLength, 10) : 0, + mtime: lastModified ? new Date(lastModified) : undefined, + }; + } + + logger.debug(`WebDAV HEAD failed for ${webdavUrl}: ${response.status}`); + return null; + } catch (error: any) { + logger.debug(`WebDAV stat error for ${sourcePath}: ${error.message}`); + return null; + } + } + /** * Remove a file from the VFS */