Creating download retry button for recurring downloads.

This commit is contained in:
Roland 2023-09-07 21:38:19 -06:00
parent 6713b9e4c9
commit 9a3796bcd0
No known key found for this signature in database
GPG key ID: 9D6DBD3ACF11AB93
5 changed files with 95 additions and 8 deletions

View file

@ -486,6 +486,10 @@ export async function killDownload(download_id) {
return response;
}
export async function restartDownload(download_id) {
return await apiPost(`${API_URI}/download/${download_id}/restart`);
}
export async function killDownloads() {
let response = await apiPost(`${API_URI}/download/kill`);
return response;

View file

@ -1,5 +1,5 @@
import React from "react";
import {clearCompletedDownloads, clearFailedDownloads, deleteDownload, killDownload, postDownload} from "../../api";
import {clearCompletedDownloads, clearFailedDownloads, deleteDownload, killDownload, restartDownload} from "../../api";
import {Link} from "react-router-dom";
import {
APIButton,
@ -24,6 +24,7 @@ import {
} from "semantic-ui-react";
import {Button, Header, Modal, ModalActions, ModalContent, ModalHeader, Placeholder, Segment, Table} from "../Theme";
import {useDownloads} from "../../hooks/customHooks";
import {toast} from "react-semantic-toasts-2";
function ClearCompleteDownloads({callback}) {
async function localClearDownloads() {
@ -67,7 +68,7 @@ function ClearFailedDownloads({callback}) {
</APIButton>
}
class DownloadRow extends React.Component {
class RecurringDownloadRow extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -86,6 +87,28 @@ class DownloadRow extends React.Component {
}
}
handleRetry = async () => {
const {id} = this.props;
try {
const response = await restartDownload(id);
if (response.status !== 204) {
throw Error('Unable to restart download');
}
} catch (e) {
toast({
type: 'error',
title: 'Error',
description: 'Unable to start download',
time: 5000,
})
throw e;
} finally {
if (this.props.fetchDownloads) {
this.props.fetchDownloads();
}
}
}
render() {
let {url, frequency, last_successful_download, status, location, next_download, error} = this.props;
const {errorModalOpen} = this.state;
@ -121,6 +144,17 @@ class DownloadRow extends React.Component {
/>
</>;
const retryButton = <>
<APIButton
color='green'
icon='redo'
confirmContent='Are you sure you want to retry this download?'
confirmButton='Retry'
onClick={this.handleRetry}
obeyWROLMode={true}
/>
</>;
// Show "now" if we have passed the next_download.
let next = 'now';
if (next_download) {
@ -142,12 +176,13 @@ class DownloadRow extends React.Component {
<TableCell>
{error && errorModal}
{deleteButton}
{retryButton}
</TableCell>
</TableRow>
}
}
class StoppableRow extends React.Component {
class OnceDownloadRow extends React.Component {
constructor(props) {
super(props);
}
@ -163,8 +198,17 @@ class StoppableRow extends React.Component {
};
handleStart = async () => {
let downloader = this.props.downloader;
await postDownload(`${this.props.url}`, downloader);
try {
await restartDownload(this.props.id);
} catch (e) {
toast({
type: 'error',
title: 'Error',
description: 'Unable to start download',
time: 5000,
})
throw e;
}
await this.props.fetchDownloads();
};
@ -203,11 +247,12 @@ class StoppableRow extends React.Component {
/>
<APIButton
color='green'
icon='redo'
confirmContent='Are you sure you want to restart this download?'
confirmButton='Start'
onClick={this.handleStart}
obeyWROLMode={true}
>Retry</APIButton>
/>
</TableCell>
);
} else if (status === 'complete' && location) {
@ -256,7 +301,7 @@ export function OnceDownloadsTable({downloads, fetchDownloads}) {
</TableRow>
</TableHeader>
<TableBody>
{downloads.map(i => <StoppableRow fetchDownloads={fetchDownloads} key={i.id} {...i}/>)}
{downloads.map(i => <OnceDownloadRow fetchDownloads={fetchDownloads} key={i.id} {...i}/>)}
</TableBody>
<TableFooter>
<TableRow>
@ -292,7 +337,7 @@ export function RecurringDownloadsTable({downloads, fetchDownloads}) {
</TableRow>
</TableHeader>
<TableBody>
{downloads.map(i => <DownloadRow key={i.id} fetchDownloads={fetchDownloads} {...i}/>)}
{downloads.map(i => <RecurringDownloadRow key={i.id} fetchDownloads={fetchDownloads} {...i}/>)}
</TableBody>
</Table>
} else if (downloads) {

View file

@ -766,6 +766,17 @@ class DownloadManager:
return True
return False
@optional_session
def restart_download(self, download_id: int, session: Session = None) -> Download:
"""Renews a download and resets its download attempts."""
download = self.get_download(session, id_=download_id)
if not download:
raise InvalidDownload(f'No download with id {download_id}')
download.renew(reset_attempts=True)
session.commit()
return download
def kill_download(self, download_id: int):
"""Fail a Download. If it is pending, kill the Downloader so the download stops."""
with get_db_session(commit=True) as session:

View file

@ -251,6 +251,13 @@ async def post_download(_: Request, body: schema.DownloadRequest):
return response.empty()
@api_bp.post('/download/<download_id:int>/restart')
@openapi.description('Restart a download.')
async def restart_download(_: Request, download_id: int):
download_manager.restart_download(download_id)
return response.empty()
@api_bp.get('/download')
@openapi.description('Get all Downloads that need to be processed.')
async def get_downloads(_: Request):

View file

@ -1,3 +1,4 @@
import asyncio
import json
from http import HTTPStatus
from itertools import zip_longest
@ -374,6 +375,25 @@ def test_get_downloaders(test_client):
'No downloaders returned'
@pytest.mark.asyncio
async def test_restart_download(test_session, test_async_client, test_download_manager, test_downloader):
"""A Download can be restarted."""
# Create a download, fail it, it should be restarted.
download = test_download_manager.create_download('https://example.com', test_downloader.name)
download.fail()
test_session.commit()
assert test_session.query(Download).one().status == 'failed'
# Download is now "new" again.
request, response = await test_async_client.post(f'/api/download/{download.id}/restart')
assert response.status_code == HTTPStatus.NO_CONTENT
assert test_session.query(Download).one().status == 'new'
# Wait for the background download to fail. It should be deferred.
await asyncio.sleep(0.5)
assert test_session.query(Download).one().status == 'deferred'
def test_get_global_statistics(test_session, test_client):
request, response = test_client.get('/api/statistics')
assert response.json['global_statistics']['db_size'] > 1