-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathfleet.ts
More file actions
178 lines (161 loc) · 4.9 KB
/
fleet.ts
File metadata and controls
178 lines (161 loc) · 4.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// src/lib/fleet.ts
//
// Functions: parseTorrentTags, getRatioBuckets, getSeedTimeBuckets,
// extractTagsFromSnapshots, normalizeCategory, toMonthKey
import { CHART_THEME } from "@/components/charts/lib/theme"
/** Normalized torrent record shape returned by the API — shared by fleet charts and components */
export interface TorrentRaw {
hash: string
name: string
state: string
tags: string
category: string
uploaded: number
downloaded: number
ratio: number
size: number
seedingTime: number
activeTime: number
addedAt: number
completedAt: number
lastActivityAt: number
remaining: number
seedCount: number
leechCount: number
swarmSeeders: number
swarmLeechers: number
uploadSpeed: number
downloadSpeed: number
availability: number
progress: number
clientName: string
}
/** Tracker tag with display metadata */
export interface TrackerTag {
tag: string
name: string
color: string
}
/** Parse comma-separated qBT tag string into trimmed array */
export function parseTorrentTags(rawTags: string, lowercase = true): string[] {
if (!rawTags) return []
return rawTags
.split(",")
.map((t) => (lowercase ? t.trim().toLowerCase() : t.trim()))
.filter((t) => t.length > 0)
}
/** Parsed clientSnapshot row for fleet historical charts */
export interface FleetSnapshot {
clientId: number
clientName: string
polledAt: string
totalSeedingCount: number | null
totalLeechingCount: number | null
uploadSpeedBytes: string | null
downloadSpeedBytes: string | null
tagStats:
| {
tag: string
seedingCount: number
leechingCount: number
uploadSpeed: number
downloadSpeed: number
}[]
| null
}
/** Extract sorted unique tag names from fleet snapshots */
export function extractTagsFromSnapshots(snapshots: FleetSnapshot[]): string[] {
const tagSet = new Set<string>()
for (const snap of snapshots) {
if (!snap.tagStats) continue
for (const stat of snap.tagStats) {
tagSet.add(stat.tag)
}
}
return Array.from(tagSet).sort()
}
export interface Bucket {
label: string
max: number
color: string
}
/** Ratio bucket definitions — shared with TorrentsTab */
export const RATIO_BUCKETS: Bucket[] = [
{ label: "<0.5", max: 0.5, color: CHART_THEME.scale[0] },
{ label: "0.5-1", max: 1, color: CHART_THEME.scale[1] },
{ label: "1-2", max: 2, color: CHART_THEME.scale[2] },
{ label: "2-5", max: 5, color: CHART_THEME.scale[3] },
{ label: "5-10", max: 10, color: CHART_THEME.scale[4] },
{ label: "10+", max: Infinity, color: CHART_THEME.scale[5] },
]
/** Seed time bucket definitions — shared with TorrentsTab */
export const SEED_TIME_BUCKETS: Bucket[] = [
{ label: "<1d", max: 86_400, color: CHART_THEME.scale[0] },
{ label: "1-7d", max: 604_800, color: CHART_THEME.scale[1] },
{ label: "7-30d", max: 2_592_000, color: CHART_THEME.scale[2] },
{ label: "30-90d", max: 7_776_000, color: CHART_THEME.scale[3] },
{ label: "90d+", max: Infinity, color: CHART_THEME.scale[4] },
]
/** Age band bucket definitions for torrent age distribution charts */
export interface AgeBucket {
label: string
maxDays: number
}
export const AGE_BUCKETS: readonly AgeBucket[] = [
{ label: "0-30d", maxDays: 30 },
{ label: "1-3mo", maxDays: 90 },
{ label: "3-6mo", maxDays: 180 },
{ label: "6mo-1y", maxDays: 365 },
{ label: "1-2y", maxDays: 730 },
{ label: "2y+", maxDays: Infinity },
]
/** Normalize a qBT category string to a consistent display value */
export function normalizeCategory(raw: string | undefined | null): string {
const trimmed = raw?.trim()
return trimmed || "Uncategorized"
}
/** Convert a Unix timestamp (seconds) to a YYYY-MM month key */
export function toMonthKey(addedOnSeconds: number): string {
const d = new Date(addedOnSeconds * 1000)
return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`
}
/** Ratio buckets with optional accent color override on the "2-5" range */
export function getRatioBuckets(accentColor?: string): Bucket[] {
if (!accentColor) return RATIO_BUCKETS
return RATIO_BUCKETS.map((b, i) => ({
...b,
color: i === 3 ? accentColor : b.color,
}))
}
/** Seed time buckets with optional accent color override on the "30-90d" range */
export function getSeedTimeBuckets(accentColor?: string): Bucket[] {
if (!accentColor) return SEED_TIME_BUCKETS
return SEED_TIME_BUCKETS.map((b, i) => ({
...b,
color: i === 3 ? accentColor : b.color,
}))
}
export interface FleetStats {
totalSeeding: number
totalLeeching: number
fleetUploadSpeed: number
fleetDownloadSpeed: number
totalLibrarySize: number
crossSeedPercent: number
staleCount: number
}
export const SEEDING_STATES = new Set([
"uploading",
"stalledUP",
"forcedUP",
"queuedUP",
"pausedUP",
])
export const LEECHING_STATES = new Set([
"downloading",
"stalledDL",
"forcedDL",
"queuedDL",
"metaDL",
])
export const STALE_THRESHOLD_MS = 30 * 24 * 60 * 60 * 1000