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 { 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] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
Reference in New Issue
Block a user