2022-06-07 09:50:04 +02:00
|
|
|
import {
|
|
|
|
|
Table,
|
|
|
|
|
Text,
|
|
|
|
|
Tooltip,
|
|
|
|
|
Title,
|
|
|
|
|
Group,
|
|
|
|
|
Progress,
|
|
|
|
|
Skeleton,
|
|
|
|
|
ScrollArea,
|
|
|
|
|
Center,
|
2022-08-25 11:07:25 +02:00
|
|
|
Stack,
|
2022-06-07 09:50:04 +02:00
|
|
|
} from '@mantine/core';
|
2022-05-29 18:42:58 +02:00
|
|
|
import { IconDownload as Download } from '@tabler/icons';
|
2022-05-26 18:19:12 +02:00
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
|
import axios from 'axios';
|
|
|
|
|
import { NormalizedTorrent } from '@ctrl/shared-torrent';
|
2022-06-12 15:35:41 +02:00
|
|
|
import { useViewportSize } from '@mantine/hooks';
|
2022-07-22 17:18:33 +02:00
|
|
|
import { showNotification } from '@mantine/notifications';
|
2022-08-24 19:51:54 +02:00
|
|
|
import { useTranslation } from 'next-i18next';
|
2022-07-23 22:22:55 +02:00
|
|
|
import { IModule } from '../ModuleTypes';
|
|
|
|
|
import { useConfig } from '../../tools/state';
|
|
|
|
|
import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem';
|
|
|
|
|
import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval';
|
|
|
|
|
import { humanFileSize } from '../../tools/humanFileSize';
|
2022-05-26 18:19:12 +02:00
|
|
|
|
|
|
|
|
export const DownloadsModule: IModule = {
|
2022-08-24 20:13:53 +02:00
|
|
|
title: 'Torrent',
|
2022-05-26 18:19:12 +02:00
|
|
|
icon: Download,
|
|
|
|
|
component: DownloadComponent,
|
|
|
|
|
options: {
|
|
|
|
|
hidecomplete: {
|
2022-08-24 19:51:54 +02:00
|
|
|
name: 'descriptor.settings.hideComplete',
|
2022-05-26 18:19:12 +02:00
|
|
|
value: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
2022-08-25 11:07:25 +02:00
|
|
|
id: 'torrents-status',
|
2022-05-26 18:19:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default function DownloadComponent() {
|
|
|
|
|
const { config } = useConfig();
|
2022-06-12 00:52:12 -04:00
|
|
|
const { height, width } = useViewportSize();
|
2022-06-06 21:38:50 +02:00
|
|
|
const downloadServices =
|
|
|
|
|
config.services.filter(
|
|
|
|
|
(service) =>
|
|
|
|
|
service.type === 'qBittorrent' ||
|
|
|
|
|
service.type === 'Transmission' ||
|
|
|
|
|
service.type === 'Deluge'
|
|
|
|
|
) ?? [];
|
2022-05-26 18:19:12 +02:00
|
|
|
const hideComplete: boolean =
|
2022-08-25 11:07:25 +02:00
|
|
|
(config?.modules?.[DownloadsModule.id]?.options?.hidecomplete?.value as boolean) ?? false;
|
2022-06-06 21:38:50 +02:00
|
|
|
const [torrents, setTorrents] = useState<NormalizedTorrent[]>([]);
|
2022-06-06 15:20:46 +02:00
|
|
|
const setSafeInterval = useSetSafeInterval();
|
2022-06-07 09:50:04 +02:00
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
2022-08-22 09:50:54 +02:00
|
|
|
|
2022-08-25 11:07:25 +02:00
|
|
|
const { t } = useTranslation(`modules/${DownloadsModule.id}`);
|
2022-08-22 09:50:54 +02:00
|
|
|
|
2022-05-26 18:19:12 +02:00
|
|
|
useEffect(() => {
|
2022-06-07 09:50:04 +02:00
|
|
|
setIsLoading(true);
|
|
|
|
|
if (downloadServices.length === 0) return;
|
2022-07-22 18:08:32 +02:00
|
|
|
const interval = setInterval(() => {
|
2022-06-06 21:38:50 +02:00
|
|
|
// Send one request with each download service inside
|
2022-07-22 17:18:33 +02:00
|
|
|
axios
|
|
|
|
|
.post('/api/modules/downloads')
|
|
|
|
|
.then((response) => {
|
|
|
|
|
setTorrents(response.data);
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
setTorrents([]);
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.error('Error while fetching torrents', error.response.data);
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
showNotification({
|
|
|
|
|
title: 'Error fetching torrents',
|
2022-07-22 18:08:32 +02:00
|
|
|
autoClose: 1000,
|
|
|
|
|
disallowClose: true,
|
|
|
|
|
id: 'fail-torrent-downloads-module',
|
2022-07-22 17:18:33 +02:00
|
|
|
color: 'red',
|
|
|
|
|
message:
|
|
|
|
|
'Please check your config for any potential errors, check the console for more info',
|
|
|
|
|
});
|
2022-07-22 18:08:32 +02:00
|
|
|
clearInterval(interval);
|
2022-07-22 17:18:33 +02:00
|
|
|
});
|
2022-06-11 18:37:13 +02:00
|
|
|
}, 5000);
|
2022-06-24 13:44:43 +02:00
|
|
|
}, []);
|
2022-05-26 18:19:12 +02:00
|
|
|
|
2022-06-06 21:38:50 +02:00
|
|
|
if (downloadServices.length === 0) {
|
2022-05-26 18:19:12 +02:00
|
|
|
return (
|
2022-08-25 11:07:25 +02:00
|
|
|
<Stack>
|
2022-08-24 17:58:14 +02:00
|
|
|
<Title order={3}>{t('card.errors.noDownloadClients.title')}</Title>
|
2022-05-26 18:19:12 +02:00
|
|
|
<Group>
|
2022-08-24 17:58:14 +02:00
|
|
|
<Text>{t('card.errors.noDownloadClients.text')}</Text>
|
2022-05-26 18:19:12 +02:00
|
|
|
<AddItemShelfButton />
|
|
|
|
|
</Group>
|
2022-08-25 11:07:25 +02:00
|
|
|
</Stack>
|
2022-05-26 18:19:12 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-07 09:50:04 +02:00
|
|
|
if (isLoading) {
|
2022-05-26 19:45:18 +02:00
|
|
|
return (
|
2022-05-29 15:31:04 +02:00
|
|
|
<>
|
|
|
|
|
<Skeleton height={40} mt={10} />
|
|
|
|
|
<Skeleton height={40} mt={10} />
|
|
|
|
|
<Skeleton height={40} mt={10} />
|
|
|
|
|
<Skeleton height={40} mt={10} />
|
|
|
|
|
<Skeleton height={40} mt={10} />
|
|
|
|
|
</>
|
2022-05-26 19:45:18 +02:00
|
|
|
);
|
2022-05-26 18:19:12 +02:00
|
|
|
}
|
2022-06-12 15:35:41 +02:00
|
|
|
const DEVICE_WIDTH = 576;
|
2022-05-26 18:19:12 +02:00
|
|
|
const ths = (
|
|
|
|
|
<tr>
|
2022-08-22 09:50:54 +02:00
|
|
|
<th>{t('card.table.header.name')}</th>
|
|
|
|
|
<th>{t('card.table.header.size')}</th>
|
|
|
|
|
{width > 576 ? <th>{t('card.table.header.download')}</th> : ''}
|
|
|
|
|
{width > 576 ? <th>{t('card.table.header.upload')}</th> : ''}
|
|
|
|
|
<th>{t('card.table.header.estimatedTimeOfArrival')}</th>
|
|
|
|
|
<th>{t('card.table.header.progress')}</th>
|
2022-05-26 18:19:12 +02:00
|
|
|
</tr>
|
|
|
|
|
);
|
2022-06-12 15:35:41 +02:00
|
|
|
// Convert Seconds to readable format.
|
|
|
|
|
function calculateETA(givenSeconds: number) {
|
|
|
|
|
// If its superior than one day return > 1 day
|
|
|
|
|
if (givenSeconds > 86400) {
|
|
|
|
|
return '> 1 day';
|
|
|
|
|
}
|
|
|
|
|
// Transform the givenSeconds into a readable format. e.g. 1h 2m 3s
|
|
|
|
|
const hours = Math.floor(givenSeconds / 3600);
|
|
|
|
|
const minutes = Math.floor((givenSeconds % 3600) / 60);
|
|
|
|
|
const seconds = Math.floor(givenSeconds % 60);
|
|
|
|
|
// Only show hours if it's greater than 0.
|
|
|
|
|
const hoursString = hours > 0 ? `${hours}h ` : '';
|
|
|
|
|
const minutesString = minutes > 0 ? `${minutes}m ` : '';
|
|
|
|
|
const secondsString = seconds > 0 ? `${seconds}s` : '';
|
|
|
|
|
return `${hoursString}${minutesString}${secondsString}`;
|
|
|
|
|
}
|
2022-05-26 21:08:16 +02:00
|
|
|
// Loop over qBittorrent torrents merging with deluge torrents
|
2022-06-07 12:41:49 +02:00
|
|
|
const rows = torrents
|
|
|
|
|
.filter((torrent) => !(torrent.progress === 1 && hideComplete))
|
|
|
|
|
.map((torrent) => {
|
|
|
|
|
const downloadSpeed = torrent.downloadSpeed / 1024 / 1024;
|
|
|
|
|
const uploadSpeed = torrent.uploadSpeed / 1024 / 1024;
|
2022-06-12 01:15:38 -04:00
|
|
|
const size = torrent.totalSelected;
|
2022-06-07 12:41:49 +02:00
|
|
|
return (
|
|
|
|
|
<tr key={torrent.id}>
|
|
|
|
|
<td>
|
|
|
|
|
<Tooltip position="top" label={torrent.name}>
|
|
|
|
|
<Text
|
|
|
|
|
style={{
|
|
|
|
|
maxWidth: '30vw',
|
|
|
|
|
}}
|
|
|
|
|
size="xs"
|
|
|
|
|
lineClamp={1}
|
|
|
|
|
>
|
|
|
|
|
{torrent.name}
|
|
|
|
|
</Text>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</td>
|
2022-06-11 18:59:45 -04:00
|
|
|
<td>
|
2022-06-12 15:35:41 +02:00
|
|
|
<Text size="xs">{humanFileSize(size)}</Text>
|
2022-06-07 12:41:49 +02:00
|
|
|
</td>
|
2022-06-12 15:35:41 +02:00
|
|
|
{width > 576 ? (
|
|
|
|
|
<td>
|
|
|
|
|
<Text size="xs">{downloadSpeed > 0 ? `${downloadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
|
|
|
|
|
</td>
|
|
|
|
|
) : (
|
|
|
|
|
''
|
|
|
|
|
)}
|
|
|
|
|
{width > 576 ? (
|
|
|
|
|
<td>
|
|
|
|
|
<Text size="xs">{uploadSpeed > 0 ? `${uploadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
|
|
|
|
|
</td>
|
|
|
|
|
) : (
|
|
|
|
|
''
|
|
|
|
|
)}
|
2022-06-12 00:59:12 -04:00
|
|
|
<td>
|
|
|
|
|
<Text size="xs">{torrent.eta <= 0 ? '∞' : calculateETA(torrent.eta)}</Text>
|
|
|
|
|
</td>
|
2022-06-07 12:41:49 +02:00
|
|
|
<td>
|
|
|
|
|
<Text>{(torrent.progress * 100).toFixed(1)}%</Text>
|
|
|
|
|
<Progress
|
|
|
|
|
radius="lg"
|
2022-06-12 15:35:41 +02:00
|
|
|
color={
|
2022-06-14 21:14:26 -04:00
|
|
|
torrent.progress === 1 ? 'green' : torrent.state === 'paused' ? 'yellow' : 'blue'
|
2022-06-12 15:35:41 +02:00
|
|
|
}
|
2022-06-07 12:41:49 +02:00
|
|
|
value={torrent.progress * 100}
|
|
|
|
|
size="lg"
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
);
|
|
|
|
|
});
|
2022-06-07 09:50:04 +02:00
|
|
|
|
2022-05-26 18:19:12 +02:00
|
|
|
return (
|
2022-08-08 13:46:14 +02:00
|
|
|
<ScrollArea mt="xl" sx={{ height: 300, width: '100%' }}>
|
|
|
|
|
{rows.length > 0 ? (
|
|
|
|
|
<Table highlightOnHover>
|
|
|
|
|
<thead>{ths}</thead>
|
|
|
|
|
<tbody>{rows}</tbody>
|
|
|
|
|
</Table>
|
|
|
|
|
) : (
|
|
|
|
|
<Center style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
2022-08-22 09:50:54 +02:00
|
|
|
<Title order={3}>{t('card.table.body.nothingFound')}</Title>
|
2022-08-08 13:46:14 +02:00
|
|
|
</Center>
|
|
|
|
|
)}
|
|
|
|
|
</ScrollArea>
|
2022-05-26 18:19:12 +02:00
|
|
|
);
|
|
|
|
|
}
|