Upload movie subtitles in background

This commit is contained in:
LASER-Yi 2021-08-22 01:08:39 +08:00
parent 9c8119df3b
commit 87123ab4c7
6 changed files with 92 additions and 65 deletions

View file

@ -4,10 +4,14 @@ export function useIsAnyTaskRunning() {
return BGT.isRunning();
}
export function useIsAnyTaskRunningWithId(id: number) {
return BGT.hasId(id);
}
export function useIsGroupTaskRunning(groupName: string) {
return BGT.has(groupName);
}
export function useIsIdRunning(groupName: string, id: number) {
export function useIsGroupTaskRunningWithId(groupName: string, id: number) {
return BGT.find(groupName, id);
}

View file

@ -54,6 +54,16 @@ class BackgroundTask {
return groupName in this.groups;
}
hasId(id: number) {
for (const key in this.groups) {
const tasks = this.groups[key];
if (tasks.find((v) => v.id === id) !== undefined) {
return true;
}
}
return false;
}
isRunning() {
return keys(this.groups).length > 0;
}

View file

@ -11,6 +11,7 @@ import React, { FunctionComponent, useState } from "react";
import { Container, Row } from "react-bootstrap";
import { Helmet } from "react-helmet";
import { Redirect, RouteComponentProps, withRouter } from "react-router-dom";
import { useIsGroupTaskRunningWithId } from "../../@modules/task/hooks";
import { useMovieBy, useProfileBy } from "../../@redux/hooks";
import { MoviesApi, ProvidersApi } from "../../apis";
import {
@ -23,6 +24,7 @@ import {
useShowModal,
} from "../../components";
import { ManualSearchModal } from "../../components/modals/ManualSearchModal";
import { TaskGroupName } from "../../components/modals/MovieUploadModal";
import ItemOverview from "../../generic/ItemOverview";
import { RouterEmptyPath } from "../../special-pages/404";
import { useOnLoadedOnce } from "../../utilites";
@ -57,6 +59,8 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
const [valid, setValid] = useState(true);
const hasTask = useIsGroupTaskRunningWithId(TaskGroupName, id);
useOnLoadedOnce(() => {
if (movie.content === null) {
setValid(false);
@ -82,6 +86,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
<ContentHeader.Group pos="start">
<ContentHeader.AsyncButton
icon={faSync}
disabled={hasTask}
promise={() =>
MoviesApi.action({ action: "scan-disk", radarrid: item.radarrId })
}
@ -90,7 +95,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
</ContentHeader.AsyncButton>
<ContentHeader.AsyncButton
icon={faSearch}
disabled={item.profileId === null}
disabled={item.profileId === null || hasTask}
promise={() =>
MoviesApi.action({
action: "search-missing",
@ -102,7 +107,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
</ContentHeader.AsyncButton>
<ContentHeader.Button
icon={faUser}
disabled={item.profileId === null}
disabled={item.profileId === null || hasTask}
onClick={() => showModal<Item.Movie>("manual-search", item)}
>
Manual
@ -115,6 +120,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
</ContentHeader.Button>
<ContentHeader.Button
icon={faToolbox}
disabled={hasTask}
onClick={() => showModal("tools", [item])}
>
Tools
@ -123,7 +129,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
<ContentHeader.Group pos="end">
<ContentHeader.Button
disabled={!allowEdit || item.profileId === null}
disabled={!allowEdit || item.profileId === null || hasTask}
icon={faCloudUploadAlt}
onClick={() => showModal("upload", item)}
>
@ -131,6 +137,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
</ContentHeader.Button>
<ContentHeader.Button
icon={faWrench}
disabled={hasTask}
onClick={() => showModal("edit", item)}
>
Edit Movie

View file

@ -97,12 +97,14 @@ const MovieView: FunctionComponent<Props> = () => {
{
accessor: "radarrId",
selectHide: true,
Cell: ({ row, externalUpdate }) => (
Cell: ({ row, value, externalUpdate }) => {
return (
<ActionBadge
icon={faWrench}
onClick={() => externalUpdate && externalUpdate(row, "edit")}
></ActionBadge>
),
);
},
},
],
[]

View file

@ -1,6 +1,7 @@
import React, { FunctionComponent, useMemo, useState } from "react";
import { Container, Form } from "react-bootstrap";
import { AsyncButton, Selector } from "../";
import { useIsAnyTaskRunningWithId } from "../../@modules/task/hooks";
import { useLanguageProfiles } from "../../@redux/hooks";
import { GetItemId } from "../../utilites";
import BaseModal, { BaseModalProps } from "./BaseModal";
@ -20,6 +21,9 @@ const Editor: FunctionComponent<Props & BaseModalProps> = (props) => {
modal.modalKey
);
// TODO: Separate movies and series
const hasTask = useIsAnyTaskRunningWithId(payload ? GetItemId(payload) : -1);
const profileOptions = useMemo<SelectorOption<number>[]>(
() =>
profiles?.map((v) => {
@ -31,11 +35,11 @@ const Editor: FunctionComponent<Props & BaseModalProps> = (props) => {
const [updating, setUpdating] = useState(false);
const footer = useMemo(
() => (
const footer = (
<AsyncButton
noReset
onChange={setUpdating}
disabled={hasTask}
promise={() => {
if (payload) {
const itemId = GetItemId(payload);
@ -54,8 +58,6 @@ const Editor: FunctionComponent<Props & BaseModalProps> = (props) => {
>
Save
</AsyncButton>
),
[closeModal, id, payload, onSuccess, submit]
);
return (
@ -81,6 +83,7 @@ const Editor: FunctionComponent<Props & BaseModalProps> = (props) => {
<Form.Label>Languages Profiles</Form.Label>
<Selector
clearable
disabled={hasTask}
options={profileOptions}
defaultValue={payload?.profileId}
onChange={(v) => setId(v === undefined ? null : v)}

View file

@ -1,6 +1,9 @@
import React, { FunctionComponent, useEffect, useMemo, useState } from "react";
import { Container, Form } from "react-bootstrap";
import { AsyncButton, FileForm, LanguageSelector } from "..";
import { Button, Container, Form } from "react-bootstrap";
import { FileForm, LanguageSelector } from "..";
import BackgroundTask from "../../@modules/task";
import { useIsGroupTaskRunning } from "../../@modules/task/hooks";
import { createTask } from "../../@modules/task/utilites";
import {
useEnabledLanguages,
useLanguageBy,
@ -9,11 +12,10 @@ import {
import { MoviesApi } from "../../apis";
import BaseModal, { BaseModalProps } from "./BaseModal";
import { useModalInformation } from "./hooks";
interface MovieProps {}
const MovieUploadModal: FunctionComponent<MovieProps & BaseModalProps> = (
props
) => {
export const TaskGroupName = "Uploading Movie Subtitles...";
const MovieUploadModal: FunctionComponent<BaseModalProps> = (props) => {
const modal = props;
const availableLanguages = useEnabledLanguages();
@ -22,8 +24,6 @@ const MovieUploadModal: FunctionComponent<MovieProps & BaseModalProps> = (
modal.modalKey
);
const [uploading, setUpload] = useState(false);
const [language, setLanguage] = useState<Nullable<Language.Info>>(null);
const profile = useProfileBy(payload?.profileId);
@ -35,40 +35,41 @@ const MovieUploadModal: FunctionComponent<MovieProps & BaseModalProps> = (
const [file, setFile] = useState<Nullable<File>>(null);
const [forced, setForced] = useState(false);
const hasTask = useIsGroupTaskRunning(TaskGroupName);
const canUpload = useMemo(() => {
return file !== null && language?.code2;
}, [language, file]);
return file !== null && language?.code2 && !hasTask;
}, [language, file, hasTask]);
const footer = (
<AsyncButton
noReset
<Button
disabled={!canUpload}
onChange={setUpload}
promise={() => {
onClick={() => {
if (file && payload && language) {
return MoviesApi.uploadSubtitles(payload.radarrId, {
const id = payload.radarrId;
const task = createTask(
file.name,
id,
MoviesApi.uploadSubtitles.bind(MoviesApi),
id,
{
file: file,
forced,
hi: false,
language: language.code2,
});
} else {
return null;
}
);
BackgroundTask.dispatch(TaskGroupName, [task]);
closeModal(props.modalKey);
}
}}
onSuccess={() => closeModal()}
>
Upload
</AsyncButton>
</Button>
);
return (
<BaseModal
title={`Upload - ${payload?.title}`}
closeable={!uploading}
footer={footer}
{...modal}
>
<BaseModal title={`Upload - ${payload?.title}`} footer={footer} {...modal}>
<Container fluid>
<Form>
<Form.Group>