Implement manual metadata refresh

This commit is contained in:
Zoe Roux 2026-03-27 17:46:55 +01:00
parent 59187a024b
commit 93bc58a2d0
No known key found for this signature in database
6 changed files with 122 additions and 29 deletions

View File

@ -1,11 +1,12 @@
// import Download from "@material-symbols/svg-400/rounded/download.svg";
import Refresh from "@material-symbols/svg-400/rounded/autorenew.svg";
import Info from "@material-symbols/svg-400/rounded/info.svg";
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
import type { ComponentProps } from "react";
import { useTranslation } from "react-i18next";
import { WatchStatusV } from "~/models";
import { IconButton, Menu, tooltip } from "~/primitives";
import { HR, IconButton, Menu, tooltip } from "~/primitives";
import { useAccount } from "~/providers/account-context";
import { useMutation } from "~/query";
import { cn } from "~/utils";
@ -89,11 +90,11 @@ export const ItemContext = ({
invalidate: [kind, slug],
});
// const metadataRefreshMutation = useMutation({
// method: "POST",
// path: [kind, slug, "refresh"],
// invalidate: null,
// });
const metadataRefreshMutation = useMutation({
method: "POST",
path: ["scanner", `${kind}s`, slug, "refresh"],
invalidate: null,
});
return (
<Menu
@ -139,16 +140,16 @@ export const ItemContext = ({
/>
</>
)}
{/* {account?.isAdmin === true && ( */}
{/* <> */}
{/* <HR /> */}
{/* <Menu.Item */}
{/* label={t("home.refreshMetadata")} */}
{/* icon={Refresh} */}
{/* onSelect={() => metadataRefreshMutation.mutate()} */}
{/* /> */}
{/* </> */}
{/* )} */}
{account?.isAdmin === true && (
<>
<HR />
<Menu.Item
label={t("home.refreshMetadata")}
icon={Refresh}
onSelect={() => metadataRefreshMutation.mutate()}
/>
</>
)}
</Menu>
);
};

View File

@ -1,3 +1,4 @@
import Refresh from "@material-symbols/svg-400/rounded/autorenew.svg";
import BookmarkAdd from "@material-symbols/svg-400/rounded/bookmark_add.svg";
import Delete from "@material-symbols/svg-400/rounded/delete.svg";
import MoreHoriz from "@material-symbols/svg-400/rounded/more_horiz.svg";
@ -83,11 +84,11 @@ const ButtonList = ({
invalidate: ["api", "shows"],
});
// const metadataRefreshMutation = useMutation({
// method: "POST",
// path: [kind, slug, "refresh"],
// invalidate: null,
// });
const metadataRefreshMutation = useMutation({
method: "POST",
path: ["scanner", `${kind}s`, slug, "refresh"],
invalidate: null,
});
return (
<View className="flex-row items-center justify-center">
@ -163,6 +164,13 @@ const ButtonList = ({
href={`/${kind === "movie" ? "movies" : "series"}/${slug}/videos`}
/>
{kind !== "collection" && <HR />}
{kind !== "collection" && (
<Menu.Item
label={t("home.refreshMetadata")}
icon={Refresh}
onSelect={() => metadataRefreshMutation.mutate()}
/>
)}
<Menu.Item
label={t("misc.delete")}
icon={Delete}
@ -185,11 +193,6 @@ const ButtonList = ({
);
}}
/>
{/* <Menu.Item */}
{/* label={t("home.refreshMetadata")} */}
{/* icon={Refresh} */}
{/* onSelect={() => metadataRefreshMutation.mutate()} */}
{/* /> */}
</>
)}
</Menu>

View File

@ -126,7 +126,7 @@ export const VideoMenu = ({
key={x.id}
label={getDisplayName({ title: x.label, language: x.language })}
selected={x.selected}
onSelect={() => player.selectAudioTrack(x)}
onSelect={() => player.selectVideoTrack(x)}
/>
))}
</Menu>

View File

@ -97,6 +97,16 @@ class KyooClient(metaclass=Singleton):
await self.raise_for_status(r)
return Page[Show].model_validate(await r.json())
async def get_movie(self, slug: str) -> Show:
async with self._client.get(f"movies/{slug}") as r:
await self.raise_for_status(r)
return Show.model_validate(await r.json())
async def get_serie(self, slug: str) -> Show:
async with self._client.get(f"series/{slug}") as r:
await self.raise_for_status(r)
return Show.model_validate(await r.json())
async def link_videos(
self,
kind: Literal["movie", "serie"],

View File

@ -3,6 +3,7 @@ from typing import Annotated
from fastapi import Header, Request
from langcodes import Language
from ..client import KyooClient
from ..database import get_db
from ..providers.composite import CompositeProvider
from ..requests import RequestCreator
@ -12,6 +13,10 @@ def get_provider(request: Request) -> CompositeProvider:
return request.app.state.provider
def get_client(_request: Request) -> KyooClient:
return KyooClient()
async def get_request_creator():
async with get_db() as db:
yield RequestCreator(db)

View File

@ -4,23 +4,33 @@ from fastapi import (
APIRouter,
BackgroundTasks,
Depends,
Request as HttpRequest,
Security,
)
from fastapi import (
Request as HttpRequest,
)
from ..client import KyooClient
from ..fsscan import create_scanner
from ..identifiers.identify import identify
from ..jwt import validate_bearer
from ..models.metadataid import MetadataId
from ..models.movie import SearchMovie
from ..models.page import Page
from ..models.request import CreateRequest, Request, RequestRet
from ..models.serie import SearchSerie
from ..models.show import Show
from ..models.videos import Video
from ..providers.composite import CompositeProvider
from ..requests import RequestCreator
from ..status import StatusService
from ..utils import Language
from .dependencies import get_preferred_languages, get_provider, get_request_creator
from .dependencies import (
get_client,
get_preferred_languages,
get_provider,
get_request_creator,
)
router = APIRouter()
@ -172,3 +182,67 @@ async def create_serie(
]
)
return ret
@router.post(
"/movies/{slug}/refresh",
status_code=201,
response_description="Movie refresh request created.",
)
async def refresh_movie_by_slug(
slug: str,
client: Annotated[KyooClient, Depends(get_client)],
requests: Annotated[RequestCreator, Depends(get_request_creator)],
_: Annotated[None, Security(validate_bearer, scopes=["scanner.add"])],
) -> RequestRet:
"""
Refresh an existing movie
"""
show = await client.get_movie(slug)
[ret] = await requests.enqueue(
[
Request(
kind="movie",
title=show.name,
year=show.air_date.year
if show.air_date is not None
else None,
external_id=MetadataId.map_dict(show.external_id),
videos=[],
)
]
)
return ret
@router.post(
"/series/{slug}/refresh",
status_code=201,
response_description="Series refresh request created.",
)
async def refresh_serie_by_slug(
slug: str,
client: Annotated[KyooClient, Depends(get_client)],
requests: Annotated[RequestCreator, Depends(get_request_creator)],
_: Annotated[None, Security(validate_bearer, scopes=["scanner.add"])],
) -> RequestRet:
"""
Refresh an existing serie
"""
show = await client.get_serie(slug)
[ret] = await requests.enqueue(
[
Request(
kind="episode",
title=show.name,
year=show.start_air.year
if show.start_air is not None
else None,
external_id=MetadataId.map_dict(show.external_id),
videos=[],
)
]
)
return ret