feat: utilisation des bar de telechargement par fichier dans le torrent plutot que celui du torrent
This commit is contained in:
1 parent
4955a730b7
commit
7e53bab0fc
6 files changed
+87
-26
No files matched your search
@@ -34,6 +34,11 @@ export interface SavedClient {
|
|||||||
config: Record<string, string | number>
|
config: Record<string, string | number>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TorrentFileProgress {
|
||||||
|
index : number
|
||||||
|
progress: number // 0–100
|
||||||
|
}
|
||||||
|
|
||||||
export interface TorrentInfo {
|
export interface TorrentInfo {
|
||||||
hash : string
|
hash : string
|
||||||
name : string
|
name : string
|
||||||
@@ -48,6 +53,7 @@ export interface TorrentInfo {
|
|||||||
eta : number
|
eta : number
|
||||||
save_path : string
|
save_path : string
|
||||||
category : string
|
category : string
|
||||||
|
files ?: TorrentFileProgress[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloadOptions {
|
export interface DownloadOptions {
|
||||||
|
|||||||
@@ -171,21 +171,39 @@ const QB: TorrentClientDriver = {
|
|||||||
})
|
})
|
||||||
if (!res.ok) throw new Error(`qB list échoué : ${res.status}`)
|
if (!res.ok) throw new Error(`qB list échoué : ${res.status}`)
|
||||||
const data: any[] = await res.json()
|
const data: any[] = await res.json()
|
||||||
return data.map(t => ({
|
|
||||||
hash : t.hash,
|
// Récupérer la progression par fichier pour chaque torrent (requêtes parallèles)
|
||||||
name : t.name,
|
const fileMap = new Map<string, any[]>()
|
||||||
state : mapState(t.state),
|
await Promise.all(data.map(async (t: any) => {
|
||||||
progress : Math.round(t.progress * 100),
|
try {
|
||||||
size : t.size,
|
const r = await fetch(`${config.url}/api/v2/torrents/files?hash=${t.hash}`, {
|
||||||
downloaded: t.downloaded,
|
headers: { Cookie: `SID=${sid}` },
|
||||||
uploaded : t.uploaded ?? 0,
|
})
|
||||||
ratio : Math.round((t.ratio ?? 0) * 100) / 100,
|
if (r.ok) fileMap.set(t.hash, await r.json())
|
||||||
speed : t.dlspeed,
|
} catch {}
|
||||||
upspeed : t.upspeed ?? 0,
|
}))
|
||||||
eta : t.eta ?? -1,
|
|
||||||
save_path : t.save_path,
|
return data.map(t => {
|
||||||
category : t.category ?? '',
|
const fileInfo = fileMap.get(t.hash) ?? []
|
||||||
} satisfies TorrentInfo))
|
return {
|
||||||
|
hash : t.hash,
|
||||||
|
name : t.name,
|
||||||
|
state : mapState(t.state),
|
||||||
|
progress : Math.round(t.progress * 100),
|
||||||
|
size : t.size,
|
||||||
|
downloaded: t.downloaded,
|
||||||
|
uploaded : t.uploaded ?? 0,
|
||||||
|
ratio : Math.round((t.ratio ?? 0) * 100) / 100,
|
||||||
|
speed : t.dlspeed,
|
||||||
|
upspeed : t.upspeed ?? 0,
|
||||||
|
eta : t.eta ?? -1,
|
||||||
|
save_path : t.save_path,
|
||||||
|
category : t.category ?? '',
|
||||||
|
files : fileInfo.length > 0
|
||||||
|
? fileInfo.map((f: any, i: number) => ({ index: i, progress: Math.round((f.progress ?? 0) * 100) }))
|
||||||
|
: undefined,
|
||||||
|
} satisfies TorrentInfo
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
async add(config, url, options?: DownloadOptions) {
|
async add(config, url, options?: DownloadOptions) {
|
||||||
|
|||||||
@@ -229,17 +229,29 @@ const RT: TorrentClientDriver = {
|
|||||||
|
|
||||||
const rows = await d_multicall(config, 'main', methods)
|
const rows = await d_multicall(config, 'main', methods)
|
||||||
|
|
||||||
|
// Récupérer la progression par fichier pour chaque torrent (requêtes parallèles)
|
||||||
|
const fileMap = new Map<string, any[]>()
|
||||||
|
await Promise.all(rows.map(async (r: any) => {
|
||||||
|
const hash = String(r[0]).toUpperCase()
|
||||||
|
try {
|
||||||
|
const files = await rpcCall(config, 'f.multicall', [hash, '', 'f.completed_length=', 'f.size_bytes='])
|
||||||
|
if (Array.isArray(files) && files.length > 0) fileMap.set(hash.toLowerCase(), files)
|
||||||
|
} catch {}
|
||||||
|
}))
|
||||||
|
|
||||||
return rows
|
return rows
|
||||||
.map(r => {
|
.map(r => {
|
||||||
const [hash, name, isOpen, isActive, isChecking, isComplete,
|
const [hash, name, isOpen, isActive, isChecking, isComplete,
|
||||||
size, downloaded, uploadedTotal, ratioRaw,
|
size, downloaded, uploadedTotal, ratioRaw,
|
||||||
dlSpeed, ulSpeed, leftBytes, directory, label] = r
|
dlSpeed, ulSpeed, leftBytes, directory, label] = r
|
||||||
|
|
||||||
const progress = size > 0 ? Math.min(100, Math.round(((size - leftBytes) / size) * 100)) : 0
|
const progress = size > 0 ? Math.min(100, Math.round(((size - leftBytes) / size) * 100)) : 0
|
||||||
const eta = Number(dlSpeed) > 0 && Number(leftBytes) > 0 ? Math.round(Number(leftBytes) / Number(dlSpeed)) : -1
|
const eta = Number(dlSpeed) > 0 && Number(leftBytes) > 0 ? Math.round(Number(leftBytes) / Number(dlSpeed)) : -1
|
||||||
|
const hashLower = String(hash).toLowerCase()
|
||||||
|
const fileInfo = fileMap.get(hashLower) ?? []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hash : String(hash).toLowerCase(),
|
hash : hashLower,
|
||||||
name : String(name),
|
name : String(name),
|
||||||
state : mapState(Number(isOpen), Number(isActive), Number(isChecking), Number(isComplete)),
|
state : mapState(Number(isOpen), Number(isActive), Number(isChecking), Number(isComplete)),
|
||||||
progress,
|
progress,
|
||||||
@@ -252,6 +264,12 @@ const RT: TorrentClientDriver = {
|
|||||||
eta,
|
eta,
|
||||||
save_path : String(directory),
|
save_path : String(directory),
|
||||||
category : String(label ?? ''),
|
category : String(label ?? ''),
|
||||||
|
files : fileInfo.length > 0
|
||||||
|
? fileInfo.map((f: any, i: number) => ({
|
||||||
|
index : i,
|
||||||
|
progress: Number(f[1]) > 0 ? Math.round((Number(f[0]) / Number(f[1])) * 100) : 0,
|
||||||
|
}))
|
||||||
|
: undefined,
|
||||||
} satisfies TorrentInfo
|
} satisfies TorrentInfo
|
||||||
})
|
})
|
||||||
.filter(t => {
|
.filter(t => {
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ const TR: TorrentClientDriver = {
|
|||||||
'hashString', 'name', 'status', 'percentDone', 'totalSize',
|
'hashString', 'name', 'status', 'percentDone', 'totalSize',
|
||||||
'downloadedEver', 'uploadedEver', 'uploadRatio',
|
'downloadedEver', 'uploadedEver', 'uploadRatio',
|
||||||
'rateDownload', 'rateUpload', 'eta', 'downloadDir', 'labels',
|
'rateDownload', 'rateUpload', 'eta', 'downloadDir', 'labels',
|
||||||
|
'fileStats', // progression par fichier (bytesCompleted + length)
|
||||||
]
|
]
|
||||||
const data = await trRequest(config, 'torrent-get', { fields })
|
const data = await trRequest(config, 'torrent-get', { fields })
|
||||||
const torrents: any[] = data.arguments?.torrents ?? []
|
const torrents: any[] = data.arguments?.torrents ?? []
|
||||||
@@ -119,6 +120,12 @@ const TR: TorrentClientDriver = {
|
|||||||
eta : t.eta ?? -1,
|
eta : t.eta ?? -1,
|
||||||
save_path : t.downloadDir,
|
save_path : t.downloadDir,
|
||||||
category : t.labels?.[0] ?? '',
|
category : t.labels?.[0] ?? '',
|
||||||
|
files : Array.isArray(t.fileStats) && t.fileStats.length > 0
|
||||||
|
? t.fileStats.map((fs: any, i: number) => ({
|
||||||
|
index : i,
|
||||||
|
progress: fs.length > 0 ? Math.round((fs.bytesCompleted / fs.length) * 100) : 0,
|
||||||
|
}))
|
||||||
|
: undefined,
|
||||||
} satisfies TorrentInfo))
|
} satisfies TorrentInfo))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@
|
|||||||
import { ref, computed, h } from 'vue'
|
import { ref, computed, h } from 'vue'
|
||||||
import { ChevronUp, Download, Loader, Check, X } from 'lucide-vue-next'
|
import { ChevronUp, Download, Loader, Check, X } from 'lucide-vue-next'
|
||||||
|
|
||||||
interface ActiveTorrent { hash: string; progress: number; state: string }
|
interface ActiveTorrent { hash: string; progress: number; state: string; files?: { index: number; progress: number }[] }
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
season : any
|
season : any
|
||||||
@@ -232,13 +232,25 @@ const seasonBtnIcon = computed(() => {
|
|||||||
|
|
||||||
// ── État épisode ───────────────────────────────────────────────
|
// ── État épisode ───────────────────────────────────────────────
|
||||||
function epProgress(ep: any): ActiveTorrent | null {
|
function epProgress(ep: any): ActiveTorrent | null {
|
||||||
|
// Un épisode déjà dans la bibliothèque n'a jamais besoin d'une barre de progression.
|
||||||
|
if (ep.organized) return null
|
||||||
|
|
||||||
|
const torrent = torrentProgress(extractHash(ep.torrent))
|
||||||
|
if (!torrent) return null
|
||||||
|
|
||||||
if (ep.torrent?.file_index != null) {
|
if (ep.torrent?.file_index != null) {
|
||||||
// Épisode dans un pack : le hash est partagé par tous les épisodes du pack.
|
// Épisode dans un pack : on n'affiche que si téléchargé dans cette session.
|
||||||
// On affiche la progression du pack UNIQUEMENT pour cet épisode s'il a été
|
if (!isDownloaded(`ep-${ep.id}`)) return null
|
||||||
// explicitement téléchargé dans cette session (isDownloaded).
|
// Si le client remonte la progression par fichier, on utilise celle du fichier précis
|
||||||
return isDownloaded(`ep-${ep.id}`) ? torrentProgress(extractHash(ep.torrent)) : null
|
// plutôt que la progression globale du pack.
|
||||||
|
if (torrent.files && torrent.files.length > 0) {
|
||||||
|
const file = torrent.files.find(f => f.index === ep.torrent.file_index)
|
||||||
|
if (file != null) return { ...torrent, progress: file.progress }
|
||||||
|
}
|
||||||
|
return torrent
|
||||||
}
|
}
|
||||||
return torrentProgress(extractHash(ep.torrent))
|
|
||||||
|
return torrent
|
||||||
}
|
}
|
||||||
|
|
||||||
function epState(ep: any): 'idle' | 'loading' | 'done' | 'unavailable' {
|
function epState(ep: any): 'idle' | 'loading' | 'done' | 'unavailable' {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ const epActionLoading = ref<Record<number, boolean>>({})
|
|||||||
const downloadingAll = ref(false)
|
const downloadingAll = ref(false)
|
||||||
const downloadingSeason = ref<Record<number, boolean>>({})
|
const downloadingSeason = ref<Record<number, boolean>>({})
|
||||||
|
|
||||||
interface ActiveTorrent { hash: string; progress: number; state: string }
|
export interface ActiveTorrent { hash: string; progress: number; state: string; files?: { index: number; progress: number }[] }
|
||||||
const activeTorrents = ref<ActiveTorrent[]>([])
|
const activeTorrents = ref<ActiveTorrent[]>([])
|
||||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ async function fetchActiveDownloads() {
|
|||||||
const res = await fetch('/api/downloads', { credentials: 'include' })
|
const res = await fetch('/api/downloads', { credentials: 'include' })
|
||||||
if (!res.ok) return
|
if (!res.ok) return
|
||||||
const list: any[] = await res.json()
|
const list: any[] = await res.json()
|
||||||
activeTorrents.value = list.map(t => ({ hash: t.hash, progress: t.progress ?? 0, state: t.state }))
|
activeTorrents.value = list.map(t => ({ hash: t.hash, progress: t.progress ?? 0, state: t.state, files: t.files }))
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user