summaryrefslogtreecommitdiffhomepage
path: root/frontend
diff options
context:
space:
mode:
authorLASER-Yi <[email protected]>2021-08-22 12:30:03 +0800
committerLASER-Yi <[email protected]>2021-08-22 12:30:03 +0800
commit43ebecbdb26d4bcd3fc6f6cf18a6489bcc34b5fc (patch)
treebd3c1c40a61b1a37f9e7bd89c4186729865e27b3 /frontend
parent1e50c515d81762bcd52e567900ad8ff6bc1a6f24 (diff)
downloadbazarr-43ebecbdb26d4bcd3fc6f6cf18a6489bcc34b5fc.tar.gz
bazarr-43ebecbdb26d4bcd3fc6f6cf18a6489bcc34b5fc.zip
Upload serie subtitles in background
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/Movies/Detail/index.tsx5
-rw-r--r--frontend/src/Movies/Detail/table.tsx7
-rw-r--r--frontend/src/Series/Episodes/index.tsx23
-rw-r--r--frontend/src/Series/Episodes/table.tsx12
-rw-r--r--frontend/src/components/modals/MovieUploadModal.tsx2
-rw-r--r--frontend/src/components/modals/SeriesUploadModal.tsx186
6 files changed, 89 insertions, 146 deletions
diff --git a/frontend/src/Movies/Detail/index.tsx b/frontend/src/Movies/Detail/index.tsx
index 29774d95e..94b8a61f0 100644
--- a/frontend/src/Movies/Detail/index.tsx
+++ b/frontend/src/Movies/Detail/index.tsx
@@ -11,7 +11,7 @@ import React, { FunctionComponent, useState } from "react";
import { Alert, 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 { useIsAnyTaskRunningWithId } from "../../@modules/task/hooks";
import { useMovieBy, useProfileBy } from "../../@redux/hooks";
import { MoviesApi, ProvidersApi } from "../../apis";
import {
@@ -24,7 +24,6 @@ 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";
@@ -59,7 +58,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
const [valid, setValid] = useState(true);
- const hasTask = useIsGroupTaskRunningWithId(TaskGroupName, id);
+ const hasTask = useIsAnyTaskRunningWithId(id);
useOnLoadedOnce(() => {
if (movie.content === null) {
diff --git a/frontend/src/Movies/Detail/table.tsx b/frontend/src/Movies/Detail/table.tsx
index 021b1659e..5f86587f8 100644
--- a/frontend/src/Movies/Detail/table.tsx
+++ b/frontend/src/Movies/Detail/table.tsx
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { FunctionComponent, useMemo } from "react";
import { Badge } from "react-bootstrap";
import { Column } from "react-table";
+import { useIsAnyTaskRunningWithId } from "../../@modules/task/hooks";
import { useProfileItemsToLanguages } from "../../@redux/hooks";
import { useShowOnlyDesired } from "../../@redux/hooks/site";
import { MoviesApi } from "../../apis";
@@ -21,6 +22,8 @@ const Table: FunctionComponent<Props> = ({ movie, profile }) => {
const profileItems = useProfileItemsToLanguages(profile);
+ const hasTask = useIsAnyTaskRunningWithId(movie.radarrId);
+
const columns: Column<Subtitle>[] = useMemo<Column<Subtitle>[]>(
() => [
{
@@ -64,6 +67,7 @@ const Table: FunctionComponent<Props> = ({ movie, profile }) => {
} else if (original.path === missingText) {
return (
<AsyncButton
+ disabled={hasTask}
promise={() =>
MoviesApi.downloadSubtitles(movie.radarrId, {
language: original.code2,
@@ -80,6 +84,7 @@ const Table: FunctionComponent<Props> = ({ movie, profile }) => {
} else {
return (
<AsyncButton
+ disabled={hasTask}
variant="light"
size="sm"
promise={() =>
@@ -98,7 +103,7 @@ const Table: FunctionComponent<Props> = ({ movie, profile }) => {
},
},
],
- [movie]
+ [movie, hasTask]
);
const data: Subtitle[] = useMemo(() => {
diff --git a/frontend/src/Series/Episodes/index.tsx b/frontend/src/Series/Episodes/index.tsx
index bf21b0a0c..f279a35c2 100644
--- a/frontend/src/Series/Episodes/index.tsx
+++ b/frontend/src/Series/Episodes/index.tsx
@@ -8,9 +8,10 @@ import {
faWrench,
} from "@fortawesome/free-solid-svg-icons";
import React, { FunctionComponent, useMemo, useState } from "react";
-import { Container, Row } from "react-bootstrap";
+import { Alert, Container, Row } from "react-bootstrap";
import { Helmet } from "react-helmet";
import { Redirect, RouteComponentProps, withRouter } from "react-router-dom";
+import { useIsAnyTaskRunningWithId } from "../../@modules/task/hooks";
import { useEpisodesBy, useProfileBy, useSerieBy } from "../../@redux/hooks";
import { SeriesApi } from "../../apis";
import {
@@ -66,6 +67,8 @@ const SeriesEpisodesView: FunctionComponent<Props> = (props) => {
const profile = useProfileBy(series.content?.profileId);
+ const hasTask = useIsAnyTaskRunningWithId(id);
+
if (isNaN(id) || !valid) {
return <Redirect to={RouterEmptyPath}></Redirect>;
}
@@ -83,7 +86,7 @@ const SeriesEpisodesView: FunctionComponent<Props> = (props) => {
<ContentHeader.Group pos="start">
<ContentHeader.AsyncButton
icon={faSync}
- disabled={!available}
+ disabled={!available || hasTask}
promise={() =>
SeriesApi.action({ action: "scan-disk", seriesid: id })
}
@@ -98,7 +101,8 @@ const SeriesEpisodesView: FunctionComponent<Props> = (props) => {
disabled={
serie.episodeFileCount === 0 ||
serie.profileId === null ||
- !available
+ !available ||
+ hasTask
}
>
Search
@@ -106,7 +110,7 @@ const SeriesEpisodesView: FunctionComponent<Props> = (props) => {
</ContentHeader.Group>
<ContentHeader.Group pos="end">
<ContentHeader.Button
- disabled={serie.episodeFileCount === 0 || !available}
+ disabled={serie.episodeFileCount === 0 || !available || hasTask}
icon={faBriefcase}
onClick={() => showModal("tools", episodes.content)}
>
@@ -125,6 +129,7 @@ const SeriesEpisodesView: FunctionComponent<Props> = (props) => {
</ContentHeader.Button>
<ContentHeader.Button
icon={faWrench}
+ disabled={hasTask}
onClick={() => showModal("edit", serie)}
>
Edit Series
@@ -132,6 +137,16 @@ const SeriesEpisodesView: FunctionComponent<Props> = (props) => {
</ContentHeader.Group>
</ContentHeader>
<Row>
+ <Alert
+ className="w-100 m-0 py-2"
+ show={hasTask}
+ style={{ borderRadius: 0 }}
+ variant="light"
+ >
+ A background task is running for this show, actions are unavailable
+ </Alert>
+ </Row>
+ <Row>
<ItemOverview item={serie} details={details}></ItemOverview>
</Row>
<Row>
diff --git a/frontend/src/Series/Episodes/table.tsx b/frontend/src/Series/Episodes/table.tsx
index ca7a4f00e..987cab835 100644
--- a/frontend/src/Series/Episodes/table.tsx
+++ b/frontend/src/Series/Episodes/table.tsx
@@ -9,6 +9,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { FunctionComponent, useCallback, useMemo } from "react";
import { Badge, ButtonGroup } from "react-bootstrap";
import { Column, TableUpdater } from "react-table";
+import { useIsAnyTaskRunningWithId } from "../../@modules/task/hooks";
import { useProfileItemsToLanguages } from "../../@redux/hooks";
import { useShowOnlyDesired } from "../../@redux/hooks/site";
import { ProvidersApi } from "../../apis";
@@ -54,6 +55,10 @@ const Table: FunctionComponent<Props> = ({ serie, episodes, profile }) => {
const profileItems = useProfileItemsToLanguages(profile);
+ const hasTask = useIsAnyTaskRunningWithId(
+ serie.content?.sonarrSeriesId ?? -1
+ );
+
const columns: Column<Item.Episode>[] = useMemo<Column<Item.Episode>[]>(
() => [
{
@@ -101,7 +106,7 @@ const Table: FunctionComponent<Props> = ({ serie, episodes, profile }) => {
{
Header: "Subtitles",
accessor: "missing_subtitles",
- Cell: ({ row, loose }) => {
+ Cell: ({ row }) => {
const episode = row.original;
const seriesid = episode.sonarrSeriesId;
@@ -147,7 +152,7 @@ const Table: FunctionComponent<Props> = ({ serie, episodes, profile }) => {
<ButtonGroup>
<ActionButton
icon={faUser}
- disabled={serie.content?.profileId === null}
+ disabled={serie.content?.profileId === null || hasTask}
onClick={() => {
externalUpdate && externalUpdate(row, "manual-search");
}}
@@ -160,6 +165,7 @@ const Table: FunctionComponent<Props> = ({ serie, episodes, profile }) => {
></ActionButton>
<ActionButton
icon={faBriefcase}
+ disabled={hasTask}
onClick={() => {
externalUpdate && externalUpdate(row, "tools");
}}
@@ -169,7 +175,7 @@ const Table: FunctionComponent<Props> = ({ serie, episodes, profile }) => {
},
},
],
- [onlyDesired, profileItems, serie]
+ [onlyDesired, profileItems, serie, hasTask]
);
const updateRow = useCallback<TableUpdater<Item.Episode>>(
diff --git a/frontend/src/components/modals/MovieUploadModal.tsx b/frontend/src/components/modals/MovieUploadModal.tsx
index 75cac7475..0d608f0c8 100644
--- a/frontend/src/components/modals/MovieUploadModal.tsx
+++ b/frontend/src/components/modals/MovieUploadModal.tsx
@@ -12,7 +12,7 @@ import { MoviesApi } from "../../apis";
import BaseModal, { BaseModalProps } from "./BaseModal";
import { useModalInformation } from "./hooks";
-export const TaskGroupName = "Uploading Movie Subtitles...";
+export const TaskGroupName = "Uploading Subtitles...";
const MovieUploadModal: FunctionComponent<BaseModalProps> = (props) => {
const modal = props;
diff --git a/frontend/src/components/modals/SeriesUploadModal.tsx b/frontend/src/components/modals/SeriesUploadModal.tsx
index 097d7300c..8e62247cd 100644
--- a/frontend/src/components/modals/SeriesUploadModal.tsx
+++ b/frontend/src/components/modals/SeriesUploadModal.tsx
@@ -1,7 +1,6 @@
import {
faCheck,
faCircleNotch,
- faExclamationTriangle,
faInfoCircle,
faTrash,
} from "@fortawesome/free-solid-svg-icons";
@@ -22,6 +21,8 @@ import {
MessageIcon,
SimpleTable,
} from "..";
+import BackgroundTask from "../../@modules/task";
+import { createTask } from "../../@modules/task/utilites";
import { useProfileBy, useProfileItemsToLanguages } from "../../@redux/hooks";
import { EpisodesApi, SubtitlesApi } from "../../apis";
import { Selector } from "../inputs";
@@ -29,27 +30,17 @@ import BaseModal, { BaseModalProps } from "./BaseModal";
import { useModalInformation } from "./hooks";
enum State {
- Update,
+ Updating,
Valid,
Warning,
- Error,
}
interface PendingSubtitle {
file: File;
- didCheck: boolean;
+ state: State;
instance?: Item.Episode;
}
-type SubtitleState = {
- state: State;
- infos: string[];
-};
-
-type ProcessState = {
- [name: string]: SubtitleState;
-};
-
type EpisodeMap = {
[name: string]: Item.Episode;
};
@@ -58,6 +49,8 @@ interface SerieProps {
episodes: readonly Item.Episode[];
}
+export const TaskGroupName = "Uploading Subtitles...";
+
const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
episodes,
...modal
@@ -70,8 +63,6 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
const [pending, setPending] = useState<PendingSubtitle[]>([]);
- const [processState, setProcessState] = useState<ProcessState>({});
-
const profile = useProfileBy(payload?.profileId);
const avaliableLanguages = useProfileItemsToLanguages(profile);
@@ -86,38 +77,6 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
const filelist = useMemo(() => pending.map((v) => v.file), [pending]);
- // Vaildate
- useEffect(() => {
- const states = pending.reduce<ProcessState>((prev, info) => {
- const subState: SubtitleState = {
- state: State.Valid,
- infos: [],
- };
-
- const { file, instance } = info;
-
- if (!info.didCheck) {
- subState.state = State.Update;
- } else if (!instance) {
- subState.infos.push("Season or episode info is missing");
- subState.state = State.Error;
- } else {
- if (
- instance.subtitles.find((v) => v.code2 === language?.code2) !==
- undefined
- ) {
- subState.infos.push("Overwrite existing subtitle");
- subState.state = State.Warning;
- }
- }
-
- prev[file.name] = subState;
- return prev;
- }, {});
-
- setProcessState(states);
- }, [pending, language?.code2]);
-
const checkEpisodes = useCallback(
async (list: PendingSubtitle[]) => {
const names = list.map((v) => v.file.name);
@@ -138,7 +97,7 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
setPending((pd) =>
pd.map((v) => ({
...v,
- didCheck: true,
+ state: State.Valid,
instance: episodeMap[v.file.name],
}))
);
@@ -154,18 +113,10 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
return {
file: f,
didCheck: false,
+ state: State.Updating,
};
});
setPending(list);
-
- const states = files.reduce<ProcessState>(
- (v, curr) => ({
- ...v,
- [curr.name]: { state: State.Update, infos: [] },
- }),
- {}
- );
- setProcessState(states);
checkEpisodes(list);
},
[checkEpisodes]
@@ -177,51 +128,31 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
}
const { sonarrSeriesId: seriesid } = payload;
+ const { code2, hi, forced } = language;
+
+ const tasks = pending
+ .filter((v) => v.instance !== undefined)
+ .map((v) => {
+ const { sonarrEpisodeId: episodeid } = v.instance!;
+
+ const form: FormType.UploadSubtitle = {
+ file: v.file,
+ language: code2,
+ hi: hi ?? false,
+ forced: forced ?? false,
+ };
- let uploadStates = pending.reduce<ProcessState>((prev, curr) => {
- prev[curr.file.name] = { state: State.Update, infos: [] };
- return prev;
- }, {});
-
- setProcessState(uploadStates);
-
- let exception = false;
-
- for (const info of pending) {
- if (info.instance) {
- const { sonarrEpisodeId: episodeid } = info.instance;
- const { file } = info;
- const { code2, hi, forced } = language;
-
- try {
- const form: FormType.UploadSubtitle = {
- file,
- language: code2,
- hi: hi ?? false,
- forced: forced ?? false,
- };
-
- await EpisodesApi.uploadSubtitles(seriesid, episodeid, form);
-
- uploadStates = {
- ...uploadStates,
- [info.file.name]: { state: State.Valid, infos: [] },
- };
- } catch (error) {
- uploadStates = {
- ...uploadStates,
- [info.file.name]: { state: State.Error, infos: [] },
- };
- exception = true;
- }
-
- setProcessState(uploadStates);
- }
- }
+ return createTask(
+ v.file.name,
+ seriesid,
+ EpisodesApi.uploadSubtitles.bind(EpisodesApi),
+ seriesid,
+ episodeid,
+ form
+ );
+ });
- if (exception) {
- throw new Error("Error when uploading subtitles");
- }
+ BackgroundTask.dispatch(TaskGroupName, tasks);
}, [payload, pending, language]);
const canUpload = useMemo(
@@ -232,47 +163,34 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
[pending, language]
);
- const tableShow = pending.length > 0;
+ const showTable = pending.length > 0;
const columns = useMemo<Column<PendingSubtitle>[]>(
() => [
{
id: "Icon",
- accessor: "instance",
+ accessor: "state",
className: "text-center",
- Cell: ({ row, loose }) => {
- const { file } = row.original;
-
- const name = file.name;
- const states = loose![1] as ProcessState;
-
+ Cell: ({ value: state }) => {
let icon = faCircleNotch;
let color: string | undefined = undefined;
let spin = false;
let msgs: string[] = [];
- if (name in states) {
- const state = states[name];
- msgs = state.infos;
- switch (state.state) {
- case State.Error:
- icon = faExclamationTriangle;
- color = "var(--danger)";
- break;
- case State.Valid:
- icon = faCheck;
- color = "var(--success)";
- break;
- case State.Warning:
- icon = faInfoCircle;
- color = "var(--warning)";
- break;
- case State.Update:
- spin = true;
- break;
- default:
- break;
- }
+ switch (state) {
+ case State.Valid:
+ icon = faCheck;
+ color = "var(--success)";
+ break;
+ case State.Warning:
+ icon = faInfoCircle;
+ color = "var(--warning)";
+ break;
+ case State.Updating:
+ spin = true;
+ break;
+ default:
+ break;
}
return (
@@ -295,7 +213,7 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
className: "vw-1",
Cell: ({ value, loose, row, externalUpdate }) => {
const uploading = loose![0] as boolean;
- const availables = loose![2] as Item.Episode[];
+ const availables = loose![1] as Item.Episode[];
const options = availables.map<SelectorOption<Item.Episode>>(
(ep) => ({
@@ -414,18 +332,18 @@ const SeriesUploadModal: FunctionComponent<SerieProps & BaseModalProps> = ({
<Form.Group>
<FileForm
emptyText="Select..."
- disabled={tableShow || avaliableLanguages.length === 0}
+ disabled={showTable || avaliableLanguages.length === 0}
multiple
value={filelist}
onChange={setFiles}
></FileForm>
</Form.Group>
</Form>
- <div hidden={!tableShow}>
+ <div hidden={!showTable}>
<SimpleTable
columns={columns}
data={pending}
- loose={[uploading, processState, episodes]}
+ loose={[uploading, episodes]}
responsive={false}
externalUpdate={updateItem}
></SimpleTable>