Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
93075e8
Initial plan
Copilot Apr 9, 2026
6ef0672
feat: add MyClient torrent management page under Overview
Copilot Apr 9, 2026
017bf0a
fix: address code review feedback - extract helper, fix v-if placemen…
Copilot Apr 9, 2026
dcb6866
refactor(download): streamline downloader instance retrieval for impr…
Rhilip Apr 9, 2026
c2fd813
feat: add per-downloader auto-refresh with failure tracking to MyClie…
Copilot Apr 9, 2026
72c85a7
fix: address code review - improve comment, use window.setTimeout, di…
Copilot Apr 9, 2026
385c500
feat: add bulk pause/resume selected torrents in MyClient
Copilot Apr 9, 2026
93101a0
fix: restore missing NavButton tag and report only succeeded count in…
Copilot Apr 9, 2026
f14eb90
feat: merge delete-with-data option into DeleteDialog via slot + MyCl…
Copilot Apr 9, 2026
d15a4bd
fix: use string type instead of any in MyClient/DeleteDialog wrapper
Copilot Apr 9, 2026
975aa3c
feat: unify torrent management actions by consolidating pause/resume …
Rhilip Apr 10, 2026
f2e4e46
feat: add Push-to-Downloader button and dialog to MyClient nav
Copilot Apr 10, 2026
be052b5
perf: use FileReader.readAsDataURL for torrent file base64 encoding
Copilot Apr 10, 2026
d07bacf
refactor(qBittorrent): update torrent management requests to use POST…
Rhilip Apr 10, 2026
c5d01f3
feat: enhance downloader settings with new options and improve UI com…
Rhilip Apr 10, 2026
9bc097e
feat: add TorrentStateTd component for improved state display in torr…
Rhilip Apr 11, 2026
50a28e9
feat(MyClient): add totalUploaded/totalDownloaded columns, column sel…
Copilot Apr 11, 2026
9e9ce8e
refactor: use typed computed for MyClient table behavior, fix Vuetify…
Copilot Apr 11, 2026
bf2c083
feat: enhance MyClient table with improved column management and UI u…
Rhilip Apr 11, 2026
f2a99a6
feat(MyClient): move downloader filter chips into auto-refresh menu p…
Copilot Apr 11, 2026
a48f85d
feat(MyClient): move downloader server status from SetDownloader to M…
Copilot Apr 11, 2026
24448d8
feat(MyClient): add ClientStatusDialog with aggregate stats button in…
Copilot Apr 11, 2026
e5f333b
feat: refactor ClientStatusDialog and Index components for improved t…
Rhilip Apr 11, 2026
06cc984
feat(ClientStatusDialog): move downloader filter chips into dialog
Copilot Apr 11, 2026
78bab87
refactor(MyClient): abstract refresh state to utils.ts, integrate fil…
Copilot Apr 11, 2026
bf6c1fd
feat(downloader): add CTorrentTracker type and abstract getTorrentTra…
Copilot Apr 12, 2026
830b579
refactor: simplify getTorrentTrackers to return Promise<string[]>
Copilot Apr 12, 2026
4a0d459
refactor: update getTorrentTrackers method signatures to accept CTorr…
Rhilip Apr 12, 2026
7c376b5
feat(Index): enhance table styling and restore ratio column for impro…
Rhilip Apr 12, 2026
c9d090e
feat(ClientStatusDialog): optimize downloader status fetching and add…
Rhilip Apr 12, 2026
aa204d2
refactor(MyClient): change torrents from CTorrent[] to Record<string,…
Copilot Apr 12, 2026
8abec0d
Merge remote-tracking branch 'origin/master' into copilot/add-unified…
Rhilip Apr 12, 2026
ba08047
refactor(i18n): remove unused ClientStatusSpan translations from en.j…
Rhilip Apr 12, 2026
3d6ee3a
Merge branch 'master' into copilot/add-unified-torrent-management
Rhilip Apr 15, 2026
bbb2db3
Merge branch 'master' into copilot/add-unified-torrent-management
Rhilip May 2, 2026
124a949
Merge branch 'master' into copilot/add-unified-torrent-management, fi…
Copilot May 8, 2026
f601cd8
Apply suggestions from code review
Rhilip May 8, 2026
932e305
feat(offscreen): add downloaderInstance cache keyed by config content
Copilot May 8, 2026
4245019
fix(ClientStatusDialog): disable stop button when auto-refresh is not…
Rhilip May 8, 2026
f51e440
Merge branch 'master' into copilot/add-unified-torrent-management
Rhilip May 15, 2026
610ae26
refactor(core/downloaderSetting): clean up configuration and improve …
Rhilip May 15, 2026
3b37a58
refactor(Overview\MyClient): simplify torrent loading logic by consol…
Rhilip May 15, 2026
6938705
refactor(Overview\MyClient): remove unnecessary class for raw torrent…
Rhilip May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/entries/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
import type { ISocialInformation, TSupportSocialSite$1 } from "@ptd/social";
import type { IMediaServerId, IMediaServerSearchOptions, IMediaServerSearchResult } from "@ptd/mediaServer";
import type { IBackupData, IBackupFileInfo } from "@ptd/backupServer";
import type { TorrentClientStatus } from "@ptd/downloader";
import type { CTorrent, TorrentClientStatus } from "@ptd/downloader";

// 可序列化的种子信息,用于辅种检测
export interface ITorrentInfoForVerification {
Expand Down Expand Up @@ -113,6 +113,11 @@ interface ProtocolMap extends TMessageMap {
getTorrentDownloadLink(torrent: ITorrent): string;
getTorrentInfoForVerification(torrent: ITorrent): ITorrentInfoForVerification;

getClientTorrents(downloaderId: string): CTorrent[];
deleteClientTorrent(data: { downloaderId: string; id: any; removeData?: boolean }): boolean;
pauseClientTorrent(data: { downloaderId: string; id: any }): boolean;
resumeClientTorrent(data: { downloaderId: string; id: any }): boolean;
Comment on lines +117 to +119
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleteClientTorrent / pauseClientTorrent / resumeClientTorrent use id: any in the message protocol. Since CTorrent.id is string | number (see src/packages/downloader/types.ts), using any weakens type-safety across the messaging boundary. Prefer id: CTorrent['id'] (or string | number) for these message payloads.

Suggested change
deleteClientTorrent(data: { downloaderId: string; id: any; removeData?: boolean }): boolean;
pauseClientTorrent(data: { downloaderId: string; id: any }): boolean;
resumeClientTorrent(data: { downloaderId: string; id: any }): boolean;
deleteClientTorrent(data: { downloaderId: string; id: CTorrent["id"]; removeData?: boolean }): boolean;
pauseClientTorrent(data: { downloaderId: string; id: CTorrent["id"] }): boolean;
resumeClientTorrent(data: { downloaderId: string; id: CTorrent["id"] }): boolean;

Copilot uses AI. Check for mistakes.

downloadTorrent(data: IDownloadTorrentOption): IDownloadTorrentResult;

getDownloadHistory(): ITorrentDownloadMetadata[];
Expand Down
76 changes: 69 additions & 7 deletions src/entries/offscreen/utils/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { isEmpty } from "es-toolkit/compat";
import {
getDownloader,
getRemoteTorrentFile,
type CTorrent,
type CAddTorrentOptions,
type TorrentClientStatus,
} from "@ptd/downloader";
Expand Down Expand Up @@ -39,6 +40,30 @@ export async function getDownloaderConfig(downloaderId: string) {
return metadataStore?.downloaders?.[downloaderId] ?? ({} as IDownloaderMetadata);
}

type DownloaderInstance = Awaited<ReturnType<typeof getDownloader>>;

const downloaderInstanceCache = new Map<string, { configKey: string; instance: DownloaderInstance }>();

function getDownloaderConfigKey(config: IDownloaderMetadata): string {
const { id, type, address, username, password, timeout } = config;
return JSON.stringify({ id, type, address, username, password, timeout });
}

export async function getDownloaderInstance(downloaderId: string): Promise<DownloaderInstance | null> {
const downloaderConfig = await getDownloaderConfig(downloaderId);
if (!downloaderConfig.id) return null;

const configKey = getDownloaderConfigKey(downloaderConfig);
const cached = downloaderInstanceCache.get(downloaderId);
if (cached && cached.configKey === configKey) {
return cached.instance;
}

const instance = await getDownloader(downloaderConfig);
downloaderInstanceCache.set(downloaderId, { configKey, instance });
return instance;
}
Comment on lines +52 to +65
Comment on lines +45 to +65

onMessage("getDownloaderConfig", async ({ data: downloaderId }) => await getDownloaderConfig(downloaderId));

onMessage("getDownloaderList", async () => {
Expand All @@ -56,9 +81,8 @@ onMessage("getDownloaderList", async () => {
onMessage("getDownloaderVersion", async ({ data: downloaderId }) => {
let downloaderVersion = "unknown";

const downloaderConfig = await getDownloaderConfig(downloaderId);
if (downloaderConfig.id) {
const downloaderInstance = await getDownloader(downloaderConfig);
const downloaderInstance = await getDownloaderInstance(downloaderId);
if (downloaderInstance) {
downloaderVersion = await downloaderInstance.getClientVersion();
}

Expand All @@ -68,9 +92,8 @@ onMessage("getDownloaderVersion", async ({ data: downloaderId }) => {
onMessage("getDownloaderStatus", async ({ data: downloaderId }) => {
let downloaderStatus: TorrentClientStatus = { dlSpeed: 0, upSpeed: 0, dlData: 0, upData: 0 };

const downloaderConfig = await getDownloaderConfig(downloaderId);
if (downloaderConfig.id) {
const downloaderInstance = await getDownloader(downloaderConfig);
const downloaderInstance = await getDownloaderInstance(downloaderId);
if (downloaderInstance) {
downloaderStatus = await downloaderInstance.getClientStatus();
}

Expand Down Expand Up @@ -108,6 +131,44 @@ export async function getTorrentInfoForVerification(torrent: ITorrent) {

onMessage("getTorrentInfoForVerification", async ({ data: torrent }) => await getTorrentInfoForVerification(torrent));

onMessage("getClientTorrents", async ({ data: downloaderId }) => {
let downloaderTorrents: CTorrent[] = [];
const downloaderInstance = await getDownloaderInstance(downloaderId);
if (downloaderInstance) {
downloaderTorrents = await downloaderInstance.getAllTorrents();
}
return downloaderTorrents;
});

onMessage("deleteClientTorrent", async ({ data: { downloaderId, id, removeData } }) => {
let deleteStatus: boolean = false;
const downloaderInstance = await getDownloaderInstance(downloaderId);
if (downloaderInstance) {
deleteStatus = await downloaderInstance.removeTorrent(id, removeData ?? false);
}
return deleteStatus;
});

onMessage("pauseClientTorrent", async ({ data: { downloaderId, id } }) => {
let pauseStatus: boolean = false;
const downloaderInstance = await getDownloaderInstance(downloaderId);
if (downloaderInstance) {
pauseStatus = await downloaderInstance.pauseTorrent(id);
}

return pauseStatus;
});

onMessage("resumeClientTorrent", async ({ data: { downloaderId, id } }) => {
let resumeStatus: boolean = false;
const downloaderInstance = await getDownloaderInstance(downloaderId);
if (downloaderInstance) {
resumeStatus = await downloaderInstance.resumeTorrent(id);
}

return resumeStatus;
});

function buildDownloadHistory(downloadOption: IDownloadTorrentOption): ITorrentDownloadMetadata {
const { torrent = {}, downloaderId = "local" } = downloadOption;
return {
Expand Down Expand Up @@ -298,7 +359,8 @@ async function downloadTorrentToRemote(

const downloaderConfig = await getDownloaderConfig(downloaderId);
if (downloaderConfig.id && downloaderConfig.enabled) {
const downloaderInstance = await getDownloader(downloaderConfig);
const downloaderInstance = await getDownloaderInstance(downloaderId);
if (!downloaderInstance) return downloadStatus;
if (addTorrentOptions.localDownload) {
addTorrentOptions.localDownloadOption = downloadRequestConfig;
}
Expand Down
6 changes: 5 additions & 1 deletion src/entries/options/components/DeleteDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ async function dialogEnter() {
{{ t("common.dialog.title.confirmAction") }}
</v-card-title>

<v-card-text>
<v-card-text class="text-body-1">
{{ t("common.dialog.deleteText", [toDeleteIds!.length]) }}

<slot name="append-text" />
</v-card-text>
Comment thread
Rhilip marked this conversation as resolved.

<v-divider />

<v-card-actions>
<v-spacer />
<v-btn color="info" prepend-icon="mdi-close-circle" variant="text" @click="showDialog = false">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,15 @@ export function sendTorrentToDownloader(
const replaceMap: Record<string, string> = {
"torrent.title": torrent.title ?? "",
"torrent.subTitle": torrent.subTitle ?? "",
"torrent.site": torrent.site,
"torrent.siteName": await metadataStore.getSiteName(torrent.site),
"torrent.category": (torrent.category as string) ?? "",
...baseReplaceMap,
};

if (torrent.site) {
replaceMap["torrent.site"] = torrent.site;
replaceMap["torrent.siteName"] = await metadataStore.getSiteName(torrent.site);
}

for (const key of ["savePath", "label"] as (keyof typeof realAddTorrentOptions)[]) {
if (realAddTorrentOptions[key]) {
if (realAddTorrentOptions[key] === "") {
Expand Down
6 changes: 6 additions & 0 deletions src/entries/options/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ a:not(:hover) {
.list-item-half-spacer > .v-list-item__prepend > .v-icon ~ .v-list-item__spacer {
width: 12px;
}

.status-btn {
i.v-icon + i.v-icon {
margin-left: 4px;
}
}
6 changes: 6 additions & 0 deletions src/entries/options/plugins/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ export const routes: RouteRecordRaw[] = [
meta: { icon: "mdi-play-network" },
component: () => import("../views/Overview/MediaServerEntity/Index.vue"),
},
{
path: "/my-client",
name: "MyClient",
meta: { icon: "mdi-download-network" },
component: () => import("../views/Overview/MyClient/Index.vue"),
},
Comment on lines +80 to +85
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description only mentions an axios bump, but this change set adds a new MyClient page/route, downloader messaging APIs, and multiple UI/config changes. Please update the PR title/description to match the actual scope (or split into separate PRs) so reviewers can assess risk appropriately.

Copilot uses AI. Check for mistakes.
{
path: "/download-history",
name: "DownloadHistory",
Expand Down
18 changes: 17 additions & 1 deletion src/entries/options/stores/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,22 @@ export const useConfigStore = defineStore("config", {
itemsPerPage: 10,
sortBy: [{ key: "enabled", order: "desc" }],
},
MyClient: {
itemsPerPage: 25,
columns: [
"clientId",
"name",
"totalSize",
"progress",
"state",
"ratio",
"uploadSpeed",
"downloadSpeed",
"dateAdded",
"action",
],
sortBy: [{ key: "dateAdded", order: "desc" }],
},
SetSearchSolution: {
itemsPerPage: 10,
},
Expand Down Expand Up @@ -243,7 +259,7 @@ export const useConfigStore = defineStore("config", {

download: {
saveDownloadHistory: true,
startupAutoFetchDownloaderStatus: false,
initDownloaderTorrentOnEnter: false,
Comment on lines 260 to +262
saveLastDownloader: false,
allowDirectSendToClient: false,
localDownloadMethod: "browser",
Expand Down
Loading
Loading