feat: add local files provider with indexing and rescan functionality

- Implemented LocalFilesProvider to manage local video files.
- Added LocalIndex for in-memory and SQLite-backed indexing of video files.
- Introduced scanning functionality to detect video files and extract metadata.
- Added API endpoints for listing collections, genres, and series based on provider capabilities.
- Enhanced existing routes to check for provider capabilities before processing requests.
- Updated frontend to utilize provider capabilities for conditional rendering of UI elements.
- Implemented rescan functionality to refresh the local files index.
- Added database migration for local files index schema.
This commit is contained in:
2026-03-14 03:44:32 +01:00
parent 9b6bcfc566
commit 8f42164bce
30 changed files with 1033 additions and 59 deletions

View File

@@ -11,6 +11,8 @@ import {
useGenerateSchedule,
} from "@/hooks/use-channels";
import { useAuthContext } from "@/context/auth-context";
import { useConfig } from "@/hooks/use-config";
import { useRescanLibrary } from "@/hooks/use-library";
import { api } from "@/lib/api";
import { toast } from "sonner";
import { useQueryClient } from "@tanstack/react-query";
@@ -34,11 +36,14 @@ export default function DashboardPage() {
const { token } = useAuthContext();
const queryClient = useQueryClient();
const { data: channels, isLoading, error } = useChannels();
const { data: config } = useConfig();
const capabilities = config?.provider_capabilities;
const createChannel = useCreateChannel();
const updateChannel = useUpdateChannel();
const deleteChannel = useDeleteChannel();
const generateSchedule = useGenerateSchedule();
const rescanLibrary = useRescanLibrary();
// Channel ordering — persisted to localStorage
const [channelOrder, setChannelOrder] = useState<string[]>([]);
@@ -226,6 +231,22 @@ export default function DashboardPage() {
</p>
</div>
<div className="flex gap-2">
{capabilities?.rescan && (
<Button
onClick={() =>
rescanLibrary.mutate(undefined, {
onSuccess: (d) => toast.success(`Rescan complete: ${d.items_found} files found`),
onError: () => toast.error("Rescan failed"),
})
}
disabled={rescanLibrary.isPending}
title="Rescan local files directory"
className="border-zinc-700 text-zinc-400 hover:text-zinc-100"
>
<RefreshCw className={`size-4 ${rescanLibrary.isPending ? "animate-spin" : ""}`} />
Rescan library
</Button>
)}
{channels && channels.length > 0 && (
<Button
onClick={handleRegenerateAll}
@@ -346,6 +367,7 @@ export default function DashboardPage() {
onSubmit={handleEdit}
isPending={updateChannel.isPending}
error={updateChannel.error?.message}
capabilities={capabilities}
/>
<ScheduleSheet