refactor: Reorganize imports in cloudScanner.ts
This commit is contained in:
@@ -9,12 +9,11 @@ import { IMAGES_DIR } from "../../config/paths";
|
||||
import { formatVideoFilename } from "../../utils/helpers";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { getVideos, saveVideo } from "../storageService";
|
||||
import { CloudDriveConfig, ScanResult, FileType } from "./types";
|
||||
import { clearFileListCache } from "./fileLister";
|
||||
import { getFilesRecursively } from "./fileLister";
|
||||
import { getSignedUrl, clearSignedUrlCache } from "./urlSigner";
|
||||
import { clearFileListCache, getFilesRecursively } from "./fileLister";
|
||||
import { uploadFile } from "./fileUploader";
|
||||
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)
|
||||
@@ -150,16 +149,41 @@ export async function scanCloudFiles(
|
||||
);
|
||||
|
||||
// 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)
|
||||
// We need to extract the relative directory path
|
||||
const normalizedFilePath = filePath.startsWith("/")
|
||||
? filePath.substring(1)
|
||||
: filePath;
|
||||
const videoDir = path.dirname(normalizedFilePath);
|
||||
// If videoDir is "." it means root, otherwise it's the subdirectory path
|
||||
const remoteThumbnailDir = videoDir === "." ? "" : videoDir;
|
||||
// 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 normalize everything to be safe
|
||||
|
||||
// 1. Get the upload root path
|
||||
const uploadRoot = normalizeUploadPath(config.uploadPath);
|
||||
|
||||
// 2. Ensure filePath is absolute-like for path.relative calculation if it isn't already
|
||||
// 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
|
||||
? `${remoteThumbnailDir}/${newThumbnailFilename}`
|
||||
? path
|
||||
.join(remoteThumbnailDir, newThumbnailFilename)
|
||||
.replace(/\\/g, "/")
|
||||
: newThumbnailFilename;
|
||||
|
||||
// Ensure directory exists
|
||||
@@ -230,6 +254,12 @@ export async function scanCloudFiles(
|
||||
Date.now() + Math.floor(Math.random() * 10000)
|
||||
).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 = {
|
||||
id: videoId,
|
||||
title: originalTitle || "Untitled Video",
|
||||
@@ -237,8 +267,8 @@ export async function scanCloudFiles(
|
||||
source: "cloud",
|
||||
sourceUrl: "",
|
||||
videoFilename: filename, // Keep original filename
|
||||
// Store path relative to upload root (remove leading slash if present)
|
||||
videoPath: `cloud:${normalizedFilePath}`, // Store relative path (e.g., mytube-uploads/subdir/video.mp4)
|
||||
// Store path relative to upload root (e.g., video/1.mp4, not a/b/Youtube/video/1.mp4)
|
||||
videoPath: `cloud:${relativeVideoPath}`,
|
||||
thumbnailFilename: newThumbnailFilename,
|
||||
thumbnailPath: `cloud:${remoteThumbnailPath}`, // Store relative path
|
||||
thumbnailUrl: `cloud:${remoteThumbnailPath}`,
|
||||
@@ -258,8 +288,8 @@ export async function scanCloudFiles(
|
||||
);
|
||||
|
||||
// Clear cache for the new files
|
||||
// Use normalized paths (relative to upload root) for cache keys
|
||||
clearSignedUrlCache(normalizedFilePath, "video");
|
||||
// Use relative paths (relative to upload root) for cache keys
|
||||
clearSignedUrlCache(relativeVideoPath, "video");
|
||||
clearSignedUrlCache(remoteThumbnailPath, "thumbnail");
|
||||
|
||||
// Also clear file list cache for the directory where thumbnail was added
|
||||
@@ -292,8 +322,7 @@ export async function scanCloudFiles(
|
||||
|
||||
return { added, errors };
|
||||
} catch (error: any) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
logger.error(
|
||||
"[CloudStorage] Cloud scan failed:",
|
||||
error instanceof Error ? error : new Error(errorMessage)
|
||||
@@ -302,4 +331,3 @@ export async function scanCloudFiles(
|
||||
return { added: 0, errors: [errorMessage] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,19 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
|
||||
const [playerMenuAnchor, setPlayerMenuAnchor] = useState<null | HTMLElement>(null);
|
||||
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 (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
|
||||
if (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 videoUrl = getVideoUrl();
|
||||
const handlePlayerSelect = async (player: string) => {
|
||||
const resolvedVideoUrl = await getVideoUrl();
|
||||
|
||||
if (!resolvedVideoUrl) {
|
||||
showSnackbar(t('videoUrlNotAvailable') || 'Video URL not available', 'error');
|
||||
handlePlayerMenuClose();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let playerUrl = '';
|
||||
|
||||
switch (player) {
|
||||
case 'vlc':
|
||||
playerUrl = `vlc://${videoUrl}`;
|
||||
playerUrl = `vlc://${resolvedVideoUrl}`;
|
||||
break;
|
||||
case 'iina':
|
||||
playerUrl = `iina://weblink?url=${encodeURIComponent(videoUrl)}`;
|
||||
playerUrl = `iina://weblink?url=${encodeURIComponent(resolvedVideoUrl)}`;
|
||||
break;
|
||||
case 'mpv':
|
||||
playerUrl = `mpv://${videoUrl}`;
|
||||
playerUrl = `mpv://${resolvedVideoUrl}`;
|
||||
break;
|
||||
case 'potplayer':
|
||||
playerUrl = `potplayer://${videoUrl}`;
|
||||
playerUrl = `potplayer://${resolvedVideoUrl}`;
|
||||
break;
|
||||
case 'infuse':
|
||||
playerUrl = `infuse://x-callback-url/play?url=${encodeURIComponent(videoUrl)}`;
|
||||
playerUrl = `infuse://x-callback-url/play?url=${encodeURIComponent(resolvedVideoUrl)}`;
|
||||
break;
|
||||
case 'copy':
|
||||
// Copy URL to clipboard
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(videoUrl).then(() => {
|
||||
navigator.clipboard.writeText(resolvedVideoUrl).then(() => {
|
||||
showSnackbar(t('linkCopied'), 'success');
|
||||
}).catch(() => {
|
||||
showSnackbar(t('copyFailed'), 'error');
|
||||
@@ -92,7 +105,7 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
|
||||
} else {
|
||||
// Fallback
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = videoUrl;
|
||||
textArea.value = resolvedVideoUrl;
|
||||
textArea.style.position = "fixed";
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
|
||||
@@ -42,7 +42,7 @@ const VideoMetadata: React.FC<VideoMetadataProps> = ({
|
||||
</a>
|
||||
</Typography>
|
||||
)}
|
||||
{(videoUrl || video.videoPath) && (
|
||||
{(videoUrl || (video.videoPath && !video.videoPath.startsWith("cloud:"))) && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
|
||||
Reference in New Issue
Block a user