refactor: Reorganize imports in cloudScanner.ts

This commit is contained in:
Peifan Li
2025-12-22 16:07:13 -05:00
parent 2816ea17f4
commit d96c785da9
3 changed files with 72 additions and 31 deletions

View File

@@ -9,12 +9,11 @@ import { IMAGES_DIR } from "../../config/paths";
import { formatVideoFilename } from "../../utils/helpers"; import { formatVideoFilename } from "../../utils/helpers";
import { logger } from "../../utils/logger"; import { logger } from "../../utils/logger";
import { getVideos, saveVideo } from "../storageService"; import { getVideos, saveVideo } from "../storageService";
import { CloudDriveConfig, ScanResult, FileType } from "./types"; import { clearFileListCache, getFilesRecursively } from "./fileLister";
import { clearFileListCache } from "./fileLister";
import { getFilesRecursively } from "./fileLister";
import { getSignedUrl, clearSignedUrlCache } from "./urlSigner";
import { uploadFile } from "./fileUploader"; import { uploadFile } from "./fileUploader";
import { normalizeUploadPath } from "./pathUtils"; import { normalizeUploadPath } from "./pathUtils";
import { CloudDriveConfig, ScanResult } from "./types";
import { clearSignedUrlCache, getSignedUrl } from "./urlSigner";
/** /**
* Scan cloud storage for videos not in database (Two-way Sync) * Scan cloud storage for videos not in database (Two-way Sync)
@@ -150,16 +149,41 @@ export async function scanCloudFiles(
); );
// Determine remote thumbnail path (put it in the same folder as video) // Determine remote thumbnail path (put it in the same folder as video)
// filePath is the full path from upload root (e.g., /mytube-uploads/subdir/video.mp4) // file.path is the full path from upload root (e.g., /a/b/Youtube/video/1.mp4) or relative path depending on alist version
// We need to extract the relative directory path // We normalize everything to be safe
const normalizedFilePath = filePath.startsWith("/")
? filePath.substring(1) // 1. Get the upload root path
: filePath; const uploadRoot = normalizeUploadPath(config.uploadPath);
const videoDir = path.dirname(normalizedFilePath);
// If videoDir is "." it means root, otherwise it's the subdirectory path // 2. Ensure filePath is absolute-like for path.relative calculation if it isn't already
const remoteThumbnailDir = videoDir === "." ? "" : videoDir; // path.relative works best when both are absolute or both relative
// We treats them as absolute paths rooted at /
const absoluteUploadRoot = uploadRoot.startsWith("/")
? uploadRoot
: "/" + uploadRoot;
const absoluteFilePath = filePath.startsWith("/")
? filePath
: "/" + filePath;
// 3. Calculate relative path from upload root to the file
// e.g. from "/a/b/Youtube" to "/a/b/Youtube/video/1.mp4" -> "video/1.mp4"
const relativeFilePath = path.relative(
absoluteUploadRoot,
absoluteFilePath
);
// 4. Get the directory of the video file relative to upload root
// e.g. "video"
const relativeVideoDir = path.dirname(relativeFilePath);
// 5. Construct thumbnail path
// If dir is root ".", relativeVideoDir is "."
const remoteThumbnailDir =
relativeVideoDir === "." ? "" : relativeVideoDir;
const remoteThumbnailPath = remoteThumbnailDir const remoteThumbnailPath = remoteThumbnailDir
? `${remoteThumbnailDir}/${newThumbnailFilename}` ? path
.join(remoteThumbnailDir, newThumbnailFilename)
.replace(/\\/g, "/")
: newThumbnailFilename; : newThumbnailFilename;
// Ensure directory exists // Ensure directory exists
@@ -230,6 +254,12 @@ export async function scanCloudFiles(
Date.now() + Math.floor(Math.random() * 10000) Date.now() + Math.floor(Math.random() * 10000)
).toString(); ).toString();
// Store path relative to upload root
// relativeFilePath was already calculated above using path.relative
// e.g. "video/1.mp4" from "/a/b/Youtube" to "/a/b/Youtube/video/1.mp4"
// Just ensure it is normalized to forward slashes
const relativeVideoPath = relativeFilePath.replace(/\\/g, "/");
const newVideo = { const newVideo = {
id: videoId, id: videoId,
title: originalTitle || "Untitled Video", title: originalTitle || "Untitled Video",
@@ -237,8 +267,8 @@ export async function scanCloudFiles(
source: "cloud", source: "cloud",
sourceUrl: "", sourceUrl: "",
videoFilename: filename, // Keep original filename videoFilename: filename, // Keep original filename
// Store path relative to upload root (remove leading slash if present) // Store path relative to upload root (e.g., video/1.mp4, not a/b/Youtube/video/1.mp4)
videoPath: `cloud:${normalizedFilePath}`, // Store relative path (e.g., mytube-uploads/subdir/video.mp4) videoPath: `cloud:${relativeVideoPath}`,
thumbnailFilename: newThumbnailFilename, thumbnailFilename: newThumbnailFilename,
thumbnailPath: `cloud:${remoteThumbnailPath}`, // Store relative path thumbnailPath: `cloud:${remoteThumbnailPath}`, // Store relative path
thumbnailUrl: `cloud:${remoteThumbnailPath}`, thumbnailUrl: `cloud:${remoteThumbnailPath}`,
@@ -258,8 +288,8 @@ export async function scanCloudFiles(
); );
// Clear cache for the new files // Clear cache for the new files
// Use normalized paths (relative to upload root) for cache keys // Use relative paths (relative to upload root) for cache keys
clearSignedUrlCache(normalizedFilePath, "video"); clearSignedUrlCache(relativeVideoPath, "video");
clearSignedUrlCache(remoteThumbnailPath, "thumbnail"); clearSignedUrlCache(remoteThumbnailPath, "thumbnail");
// Also clear file list cache for the directory where thumbnail was added // Also clear file list cache for the directory where thumbnail was added
@@ -292,8 +322,7 @@ export async function scanCloudFiles(
return { added, errors }; return { added, errors };
} catch (error: any) { } catch (error: any) {
const errorMessage = const errorMessage = error instanceof Error ? error.message : String(error);
error instanceof Error ? error.message : String(error);
logger.error( logger.error(
"[CloudStorage] Cloud scan failed:", "[CloudStorage] Cloud scan failed:",
error instanceof Error ? error : new Error(errorMessage) error instanceof Error ? error : new Error(errorMessage)
@@ -302,4 +331,3 @@ export async function scanCloudFiles(
return { added: 0, errors: [errorMessage] }; return { added: 0, errors: [errorMessage] };
} }
} }

View File

@@ -32,12 +32,19 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
const [playerMenuAnchor, setPlayerMenuAnchor] = useState<null | HTMLElement>(null); const [playerMenuAnchor, setPlayerMenuAnchor] = useState<null | HTMLElement>(null);
const videoUrl = useCloudStorageUrl(video.videoPath, 'video'); const videoUrl = useCloudStorageUrl(video.videoPath, 'video');
const getVideoUrl = (): string => { const getVideoUrl = async (): Promise<string> => {
// If we have a cloud storage URL, use it directly // If we have a cloud storage URL, use it directly
if (videoUrl) { if (videoUrl) {
return videoUrl; return videoUrl;
} }
// If cloud storage path but URL not loaded yet, wait for it
if (video.videoPath?.startsWith('cloud:')) {
// Return empty string if cloud URL is not available yet
// The hook will update videoUrl when ready
return videoUrl || '';
}
// Otherwise, construct URL from videoPath // Otherwise, construct URL from videoPath
if (video.videoPath) { if (video.videoPath) {
const videoPath = video.videoPath.startsWith('/') ? video.videoPath : `/${video.videoPath}`; const videoPath = video.videoPath.startsWith('/') ? video.videoPath : `/${video.videoPath}`;
@@ -59,32 +66,38 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
}; };
const handlePlayerSelect = (player: string) => { const handlePlayerSelect = async (player: string) => {
const videoUrl = getVideoUrl(); const resolvedVideoUrl = await getVideoUrl();
if (!resolvedVideoUrl) {
showSnackbar(t('videoUrlNotAvailable') || 'Video URL not available', 'error');
handlePlayerMenuClose();
return;
}
try { try {
let playerUrl = ''; let playerUrl = '';
switch (player) { switch (player) {
case 'vlc': case 'vlc':
playerUrl = `vlc://${videoUrl}`; playerUrl = `vlc://${resolvedVideoUrl}`;
break; break;
case 'iina': case 'iina':
playerUrl = `iina://weblink?url=${encodeURIComponent(videoUrl)}`; playerUrl = `iina://weblink?url=${encodeURIComponent(resolvedVideoUrl)}`;
break; break;
case 'mpv': case 'mpv':
playerUrl = `mpv://${videoUrl}`; playerUrl = `mpv://${resolvedVideoUrl}`;
break; break;
case 'potplayer': case 'potplayer':
playerUrl = `potplayer://${videoUrl}`; playerUrl = `potplayer://${resolvedVideoUrl}`;
break; break;
case 'infuse': case 'infuse':
playerUrl = `infuse://x-callback-url/play?url=${encodeURIComponent(videoUrl)}`; playerUrl = `infuse://x-callback-url/play?url=${encodeURIComponent(resolvedVideoUrl)}`;
break; break;
case 'copy': case 'copy':
// Copy URL to clipboard // Copy URL to clipboard
if (navigator.clipboard && navigator.clipboard.writeText) { if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(videoUrl).then(() => { navigator.clipboard.writeText(resolvedVideoUrl).then(() => {
showSnackbar(t('linkCopied'), 'success'); showSnackbar(t('linkCopied'), 'success');
}).catch(() => { }).catch(() => {
showSnackbar(t('copyFailed'), 'error'); showSnackbar(t('copyFailed'), 'error');
@@ -92,7 +105,7 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
} else { } else {
// Fallback // Fallback
const textArea = document.createElement("textarea"); const textArea = document.createElement("textarea");
textArea.value = videoUrl; textArea.value = resolvedVideoUrl;
textArea.style.position = "fixed"; textArea.style.position = "fixed";
document.body.appendChild(textArea); document.body.appendChild(textArea);
textArea.focus(); textArea.focus();

View File

@@ -42,7 +42,7 @@ const VideoMetadata: React.FC<VideoMetadataProps> = ({
</a> </a>
</Typography> </Typography>
)} )}
{(videoUrl || video.videoPath) && ( {(videoUrl || (video.videoPath && !video.videoPath.startsWith("cloud:"))) && (
<Typography <Typography
variant="body2" variant="body2"
sx={{ sx={{