feat: downloads widget (#844)

Usenet and Torrent downloads in 1 widget.
sabNZBd, NzbGet, Deluge, qBitTorrent, and transmission support.
Columns can be reordered in Edit mode.
Sorting enabled.
Time uses Dayjs with auto translation.
Can pause/resume single items, clients, or all.
Can delete items (With option to delete assossiated files).
Clients list and details.
Include all filtering and processing for ratio from oldmarr torrent widget.
Invalidation of old data (older than 30 seconds) to show an integration is not responding anymore.

Misc (So many miscs):
Fixed validation error with multiText.
Fixed translation application for multiSelect to behave the same as select.
Added background to gitignore (I needed to add a background to visually test opacity, probably will in the future too)
Added setOptions to frontend components so potential updates made from the Dashboard can be saved.
Extracted background and border color to use in widgets.
humanFileSize function based on the si format (powers of 1024, not 1000).
Improved integrationCreatorByKind by @Meierschlumpf.
Changed integrationCreatorByKind to integrationCreator so it functions directly from the integration.
Added integrationCreatorFromSecrets to directly work with secrets from db.
Added getIntegrationKindsByCategory to get a list of integrations sharing categories.
Added IntegrationKindByCategory type to get the types possible for a category (Great to cast on integration.kind that isn't already properly limited/typed but for which we know the limitation)
Added a common AtLeastOneOf type. Applied to TKind and IntegrationSecretKind[] where it was already being used and Added to the getIntegrationKindsByCategory's output to be more freely used.
Added the Modify type, instead of omiting to then add again just to change a parameters type, use the modify instead. Applied code wide already.
Hook to get list of integration depending on permission level of user. (By @Meierschlumpf)
This commit is contained in:
Manuel
2024-09-11 17:30:21 +02:00
committed by GitHub
parent 3d4e607a9d
commit 2535192b2c
81 changed files with 4176 additions and 390 deletions

View File

@@ -0,0 +1,7 @@
import type { DownloadClientItem } from "./download-client-items";
import type { DownloadClientStatus } from "./download-client-status";
export interface DownloadClientJobsAndStatus {
status: DownloadClientStatus;
items: DownloadClientItem[];
}

View File

@@ -0,0 +1,18 @@
import { Integration } from "../../base/integration";
import type { DownloadClientJobsAndStatus } from "./download-client-data";
import type { DownloadClientItem } from "./download-client-items";
export abstract class DownloadClientIntegration extends Integration {
/** Get download client's status and list of all of it's items */
public abstract getClientJobsAndStatusAsync(): Promise<DownloadClientJobsAndStatus>;
/** Pauses the client or all of it's items */
public abstract pauseQueueAsync(): Promise<void>;
/** Pause a single item using it's ID */
public abstract pauseItemAsync(item: DownloadClientItem): Promise<void>;
/** Resumes the client or all of it's items */
public abstract resumeQueueAsync(): Promise<void>;
/** Resume a single item using it's ID */
public abstract resumeItemAsync(item: DownloadClientItem): Promise<void>;
/** Delete an entry on the client or a file from disk */
public abstract deleteItemAsync(item: DownloadClientItem, fromDisk: boolean): Promise<void>;
}

View File

@@ -0,0 +1,56 @@
import type { Integration } from "@homarr/db/schema/sqlite";
import { z } from "@homarr/validation";
const usenetQueueState = ["downloading", "queued", "paused"] as const;
const usenetHistoryState = ["completed", "failed", "processing"] as const;
const torrentState = ["leeching", "stalled", "paused", "seeding"] as const;
/**
* DownloadClientItem
* Description:
* Normalized interface for downloading clients for Usenet and
* Torrents alike, using common properties and few extra optionals
* from each.
*/
export const downloadClientItemSchema = z.object({
/** Unique Identifier provided by client */
id: z.string(),
/** Position in queue */
index: z.number(),
/** Filename */
name: z.string(),
/** Torrent/Usenet identifier */
type: z.enum(["torrent", "usenet"]),
/** Item size in Bytes */
size: z.number(),
/** Total uploaded in Bytes, only required for Torrent items */
sent: z.number().optional(),
/** Download speed in Bytes/s, only required if not complete
* (Says 0 only if it should be downloading but isn't) */
downSpeed: z.number().optional(),
/** Upload speed in Bytes/s, only required for Torrent items */
upSpeed: z.number().optional(),
/** Positive = eta (until completion, 0 meaning infinite), Negative = time since completion, in milliseconds*/
time: z.number(),
/** Unix timestamp in milliseconds when the item was added to the client */
added: z.number().optional(),
/** Status message, mostly as information to display and not for logic */
state: z.enum(["unknown", ...usenetQueueState, ...usenetHistoryState, ...torrentState]),
/** Progress expressed between 0 and 1, can infer completion from progress === 1 */
progress: z.number().min(0).max(1),
/** Category given to the item */
category: z.string().or(z.array(z.string())).optional(),
});
export type DownloadClientItem = z.infer<typeof downloadClientItemSchema>;
export type ExtendedDownloadClientItem = {
integration: Integration;
received: number;
ratio?: number;
actions?: {
resume: () => void;
pause: () => void;
delete: ({ fromDisk }: { fromDisk: boolean }) => void;
};
} & DownloadClientItem;

View File

@@ -0,0 +1,23 @@
import type { Integration } from "@homarr/db/schema/sqlite";
export interface DownloadClientStatus {
/** If client is considered paused */
paused: boolean;
/** Download/Upload speeds for the client */
rates: {
down: number;
up?: number;
};
type: "usenet" | "torrent";
}
export interface ExtendedClientStatus {
integration: Integration;
interact: boolean;
status?: {
/** To derive from current items */
totalDown?: number;
/** To derive from current items */
totalUp?: number;
ratio?: number;
} & DownloadClientStatus;
}