2022-06-07 09:50:04 +02:00
|
|
|
import {
|
|
|
|
|
Table,
|
|
|
|
|
Text,
|
|
|
|
|
Tooltip,
|
|
|
|
|
Title,
|
|
|
|
|
Group,
|
|
|
|
|
Progress,
|
|
|
|
|
Skeleton,
|
|
|
|
|
ScrollArea,
|
|
|
|
|
Center,
|
|
|
|
|
Image,
|
|
|
|
|
} 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-05-26 18:19:12 +02:00
|
|
|
import { IModule } from '../modules';
|
|
|
|
|
import { useConfig } from '../../../tools/state';
|
|
|
|
|
import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem';
|
2022-06-06 15:20:46 +02:00
|
|
|
import { useSetSafeInterval } from '../../../tools/hooks/useSetSafeInterval';
|
2022-06-12 01:15:38 -04:00
|
|
|
import { humanFileSize } from '../../../tools/humanFileSize';
|
2022-05-26 18:19:12 +02:00
|
|
|
|
|
|
|
|
export const DownloadsModule: IModule = {
|
2022-05-26 19:45:18 +02:00
|
|
|
title: 'Torrent',
|
2022-05-26 18:19:12 +02:00
|
|
|
description: 'Show the current download speed of supported services',
|
|
|
|
|
icon: Download,
|
|
|
|
|
component: DownloadComponent,
|
|
|
|
|
options: {
|
|
|
|
|
hidecomplete: {
|
|
|
|
|
name: 'Hide completed torrents',
|
|
|
|
|
value: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 =
|
|
|
|
|
(config?.modules?.[DownloadsModule.title]?.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-05-26 18:19:12 +02:00
|
|
|
useEffect(() => {
|
2022-06-07 09:50:04 +02:00
|
|
|
setIsLoading(true);
|
|
|
|
|
if (downloadServices.length === 0) return;
|
2022-06-06 21:38:50 +02:00
|
|
|
setSafeInterval(() => {
|
|
|
|
|
// Send one request with each download service inside
|
|
|
|
|
axios.post('/api/modules/downloads', { config }).then((response) => {
|
|
|
|
|
setTorrents(response.data);
|
2022-06-07 09:50:04 +02:00
|
|
|
setIsLoading(false);
|
2022-06-06 21:38:50 +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 (
|
|
|
|
|
<Group direction="column">
|
2022-06-01 19:53:57 +02:00
|
|
|
<Title order={3}>No supported download clients found!</Title>
|
2022-05-26 18:19:12 +02:00
|
|
|
<Group>
|
2022-06-01 19:53:57 +02:00
|
|
|
<Text>Add a download service to view your current downloads...</Text>
|
2022-05-26 18:19:12 +02:00
|
|
|
<AddItemShelfButton />
|
|
|
|
|
</Group>
|
|
|
|
|
</Group>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
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>
|
|
|
|
|
<th>Name</th>
|
2022-06-12 00:59:12 -04:00
|
|
|
<th>Size</th>
|
2022-06-12 15:35:41 +02:00
|
|
|
{width > 576 ? <th>Down</th> : ''}
|
|
|
|
|
{width > 576 ? <th>Up</th> : ''}
|
2022-06-12 00:59:12 -04:00
|
|
|
<th>ETA</th>
|
2022-05-26 18:19:12 +02:00
|
|
|
<th>Progress</th>
|
|
|
|
|
</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
|
|
|
|
|
|
|
|
const easteregg = (
|
|
|
|
|
<Center style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
|
|
|
<Image fit="cover" height={300} src="https://danjohnvelasco.github.io/images/empty.png" />
|
|
|
|
|
</Center>
|
|
|
|
|
);
|
2022-05-26 18:19:12 +02:00
|
|
|
return (
|
2022-06-08 18:41:22 +02:00
|
|
|
<Group noWrap grow direction="column" mt="xl">
|
2022-05-30 09:19:49 +02:00
|
|
|
<ScrollArea sx={{ height: 300 }}>
|
2022-06-07 09:50:04 +02:00
|
|
|
{rows.length > 0 ? (
|
|
|
|
|
<Table highlightOnHover>
|
|
|
|
|
<thead>{ths}</thead>
|
|
|
|
|
<tbody>{rows}</tbody>
|
|
|
|
|
</Table>
|
|
|
|
|
) : (
|
|
|
|
|
easteregg
|
|
|
|
|
)}
|
2022-05-30 09:19:49 +02:00
|
|
|
</ScrollArea>
|
2022-05-26 18:19:12 +02:00
|
|
|
</Group>
|
|
|
|
|
);
|
|
|
|
|
}
|