Skip to content

Commit 7b6ab84

Browse files
committed
torrents-info: add qbt_torrents.py functionality
1 parent 077fbb4 commit 7b6ab84

File tree

2 files changed

+217
-90
lines changed

2 files changed

+217
-90
lines changed

library/playback/torrents_info.py

Lines changed: 213 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,48 @@
33

44
from library import usage
55
from library.mediafiles import torrents_start
6+
from library.utils import arggroups, argparse_utils, consts, iterables, printing, strings
7+
from library.mediafiles import torrents_start
68
from library.utils import arggroups, argparse_utils, consts, iterables, printing, processes, strings
79
from library.utils.path_utils import domain_from_url
810

911

1012
def parse_args():
1113
parser = argparse_utils.ArgumentParser(usage=usage.torrents_info)
1214
arggroups.qBittorrent(parser)
15+
parser.add_argument(
16+
"--downloading",
17+
"--download",
18+
"--down",
19+
"--dl",
20+
"--leech",
21+
action=argparse.BooleanOptionalAction,
22+
default=True,
23+
help="Include downloading torrents",
24+
)
25+
parser.add_argument(
26+
"--uploading",
27+
"--upload",
28+
"--up",
29+
"--ul",
30+
"--seeds",
31+
action=argparse.BooleanOptionalAction,
32+
default=False,
33+
help="Include uploading torrents",
34+
)
35+
36+
parser.add_argument("--priority", action='store_true', help="Sort by priority")
37+
38+
parser.add_argument("--active", action=argparse.BooleanOptionalAction, default=True, help="Show active torrents")
39+
parser.add_argument(
40+
"--inactive", "--dead", action=argparse.BooleanOptionalAction, default=False, help="Show inactive torrents"
41+
)
42+
43+
parser.add_argument("--status", action=argparse.BooleanOptionalAction, default=True, help="Show status summary")
44+
parser.add_argument("--transfers", action=argparse.BooleanOptionalAction, default=True, help="Show transfer summary")
45+
parser.add_argument("--trackers", action=argparse.BooleanOptionalAction, default=False, help="Show tracker summary")
46+
parser.add_argument("--file-count", "--count", action='store_true', help="Include file counts (a bit slow)")
47+
1348
parser.add_argument(
1449
"--force-start", "--start", action=argparse.BooleanOptionalAction, help="Force start matching torrents"
1550
)
@@ -36,49 +71,118 @@ def qbt_get_tracker(qbt_client, torrent):
3671
def torrents_info():
3772
args = parse_args()
3873

74+
def shorten(s, width):
75+
return s if args.verbose >= consts.LOG_INFO else strings.shorten(s, width)
76+
3977
qbt_client = torrents_start.start_qBittorrent(args)
40-
all_torrents = qbt_client.torrents_info()
78+
torrents = qbt_client.torrents_info()
79+
80+
torrents = [
81+
t
82+
for t in torrents
83+
if t.state_enum.is_downloading == args.downloading or t.state_enum.is_uploading == args.uploading
84+
]
4185

4286
if args.torrent_search or args.file_search:
43-
torrents = [t for t in all_torrents if strings.glob_match(args.torrent_search, [t.name, t.save_path, t.hash])]
87+
torrents = [t for t in torrents if strings.glob_match(args.torrent_search, [t.name, t.save_path, t.hash])]
4488

4589
if args.file_search:
4690
torrents = [t for t in torrents if strings.glob_match(args.file_search, [f.name for f in t.files])]
4791

48-
if not torrents:
49-
processes.no_media_found()
92+
if not torrents:
93+
processes.no_media_found()
5094

51-
torrents = sorted(torrents, key=lambda t: -t.time_active)
52-
for torrent in torrents:
53-
printing.extended_view(torrent)
95+
if args.downloading and not args.uploading:
96+
if args.priority:
97+
torrents = sorted(torrents, key=lambda t: t.priority)
98+
else:
99+
torrents = sorted(torrents, key=lambda t: t.eta)
100+
else:
101+
torrents = sorted(torrents, key=lambda t: (t.added_on, t.time_active))
54102

55-
files = torrent.files
56-
if args.file_search:
57-
files = [f for f in torrent.files if strings.glob_match(args.file_search, [f.name])]
103+
if args.torrent_search or args.file_search:
104+
print(len(torrents), "matching torrents")
105+
106+
if args.inactive:
107+
active_torrents = [t for t in torrents if t.downloaded_session > 0]
108+
if active_torrents:
109+
print('Active')
110+
111+
def gen_row(t):
112+
d = {
113+
'name': shorten(t.name, 35),
114+
"num_seeds": f"{t.num_seeds} ({t.num_complete})",
115+
'progress': strings.safe_percent(t.progress),
116+
'remaining': strings.file_size(t.amount_left),
117+
'speed': strings.file_size(t.dlspeed) + '/s' if t.dlspeed else None,
118+
'eta': strings.duration(t.eta),
119+
'downloaded_session': strings.file_size(t.downloaded_session),
120+
}
121+
if args.file_search:
122+
files = t.files
123+
files = [f for f in t.files if strings.glob_match(args.file_search, [f.name])]
124+
125+
print(t.name)
126+
printing.extended_view(files)
127+
print()
128+
129+
d |= {'files': f"{len(files)} ({len(t.files)})"}
130+
if args.verbose >= consts.LOG_INFO:
131+
d |= {
132+
'tracker': qbt_get_tracker(qbt_client, t),
133+
'seen_complete': strings.relative_datetime(t.seen_complete),
134+
'added_on': strings.relative_datetime(t.added_on),
135+
'last_activity': strings.relative_datetime(t.last_activity),
136+
'size': strings.file_size(t.total_size),
137+
'comment': t.comment,
138+
'content_path': t.content_path,
139+
}
58140

59-
if args.verbose >= consts.LOG_INFO:
60-
printing.extended_view(files)
141+
return d
61142

62-
if len(torrent.files) == 1:
63-
print("1 file")
64-
elif args.file_search:
65-
print(len(files), "files of", len(torrent.files), "matched")
66-
else:
67-
print(len(torrent.files), "total files")
68-
print()
143+
printing.table(iterables.conform([gen_row(t) for t in active_torrents]))
144+
145+
if args.inactive:
146+
inactive_torrents = [t for t in torrents if t.downloaded_session == 0]
147+
if inactive_torrents:
148+
print('Inactive')
149+
inactive_torrents = sorted(
150+
inactive_torrents, key=lambda t: (t.downloaded > 0, t.time_active * t.last_activity)
151+
)
69152

70-
print(len(torrents), "matched torrents")
153+
def gen_row(t):
154+
d = {
155+
'name': shorten(t.name, 50),
156+
'progress': strings.safe_percent(t.progress),
157+
'downloaded': strings.file_size(t.downloaded),
158+
'time_active': strings.duration(t.time_active),
159+
"num_seeds": f"{t.num_seeds} ({t.num_complete})",
160+
'seen_complete': strings.relative_datetime(t.seen_complete),
161+
'last_activity': strings.relative_datetime(t.last_activity),
162+
}
163+
if args.file_search:
164+
files = t.files
165+
files = [f for f in t.files if strings.glob_match(args.file_search, [f.name])]
166+
167+
print(t.name)
168+
printing.extended_view(files)
169+
print()
170+
171+
d |= {'files': f"{len(files)} ({len(t.files)})"}
172+
if args.verbose >= consts.LOG_INFO:
173+
d |= {
174+
'state': t.state,
175+
'tracker': qbt_get_tracker(qbt_client, t),
176+
'added_on': strings.relative_datetime(t.added_on),
177+
'size': strings.file_size(t.total_size),
178+
'remaining': strings.file_size(t.amount_left),
179+
'comment': t.comment,
180+
'content_path': t.content_path,
181+
}
71182

72-
torrent_hashes = [t.hash for t in torrents]
73-
if args.mark_deleted:
74-
qbt_client.torrents_add_tags(tags="library-delete", torrent_hashes=torrent_hashes)
75-
elif args.delete_files:
76-
qbt_client.torrents_delete(delete_files=True, torrent_hashes=torrent_hashes)
77-
elif args.delete_rows:
78-
qbt_client.torrents_delete(delete_files=False, torrent_hashes=torrent_hashes)
79-
elif args.force_start is not None:
80-
qbt_client.torrents_set_force_start(args.force_start, torrent_hashes=torrent_hashes)
81-
return
183+
return d
184+
185+
printing.table(iterables.conform([gen_row(t) for t in torrents]))
82186

83187
interesting_states = [
84188
"stoppedUP",
@@ -94,36 +198,34 @@ def torrents_info():
94198
"error",
95199
]
96200

97-
torrents_by_state = {}
98-
for torrent in all_torrents:
99-
torrents_by_state.setdefault(torrent.state, []).append(torrent)
100-
101-
if args.verbose >= consts.LOG_INFO:
102-
torrents_by_tracker = {}
103-
for torrent in all_torrents:
104-
torrents_by_tracker.setdefault(qbt_get_tracker(qbt_client, torrent), []).append(torrent)
201+
if args.status:
202+
torrents_by_state = {}
203+
for torrent in torrents:
204+
torrents_by_state.setdefault(torrent.state, []).append(torrent)
105205

106206
tbl = []
107207
for state in {*interesting_states} - {"uploading", "downloading"}:
108-
torrents = torrents_by_state.get(state)
109-
if not torrents:
208+
state_torrents = torrents_by_state.get(state)
209+
if not state_torrents:
110210
continue
111211

112-
torrents = sorted(torrents, key=lambda t: (-t.seen_complete, t.time_active))
212+
state_torrents = sorted(state_torrents, key=lambda t: (-t.seen_complete, t.time_active))
113213

114214
tbl.extend(
115215
[
116216
{
117217
"state": state,
118218
"name": printing.path_fill(t.name, width=76),
119-
"seen_complete": strings.relative_datetime(t.seen_complete) if t.seen_complete > 0 else None,
219+
"seen_complete": (
220+
strings.relative_datetime(t.seen_complete) if t.seen_complete > 0 else None
221+
),
120222
"last_activity": strings.relative_datetime(t.last_activity),
121223
"time_active": strings.duration(t.time_active),
122224
"num_seeds": f"{t.num_seeds} ({t.num_complete})",
123225
# 'num_leechs': f"{t.num_leechs} ({t.num_incomplete})",
124226
# 'comment': t.comment,
125227
}
126-
for t in torrents
228+
for t in state_torrents
127229
]
128230
)
129231
if tbl:
@@ -132,12 +234,12 @@ def torrents_info():
132234

133235
tbl = []
134236
for state in ["downloading", "missingFiles", "error"]:
135-
torrents = torrents_by_state.get(state)
136-
if not torrents:
237+
state_torrents = torrents_by_state.get(state)
238+
if not state_torrents:
137239
continue
138240

139-
torrents = sorted(
140-
torrents, key=lambda t: (t.amount_left == t.total_size, t.eta, t.amount_left), reverse=True
241+
state_torrents = sorted(
242+
state_torrents, key=lambda t: (t.amount_left == t.total_size, t.eta, t.amount_left), reverse=True
141243
)
142244

143245
tbl.extend(
@@ -149,25 +251,59 @@ def torrents_info():
149251
"eta": strings.duration(t.eta) if t.eta < 8640000 else None,
150252
"remaining": strings.file_size(t.amount_left),
151253
}
152-
for t in torrents
254+
for t in state_torrents
153255
]
154256
)
155257
if tbl:
156258
printing.table(tbl)
157259
print()
158260

261+
categories = []
262+
for state, state_torrents in torrents_by_state.items():
263+
remaining = sum(t.amount_left for t in state_torrents)
264+
categories.append(
265+
{
266+
"state": state,
267+
"count": len(state_torrents),
268+
"size": strings.file_size(sum(t.total_size for t in state_torrents)),
269+
"remaining": strings.file_size(remaining) if remaining else None,
270+
"file_count": (
271+
sum(len(t.files) for t in state_torrents) if args.verbose >= 2 else None
272+
), # a bit slow
273+
}
274+
)
275+
276+
categories = sorted(
277+
categories,
278+
key=lambda d: (
279+
d["state"].endswith(("missingFiles", "error")),
280+
d["state"].endswith(("downloading", "DL")),
281+
iterables.safe_index(interesting_states, d["state"]),
282+
),
283+
)
284+
printing.table(iterables.list_dict_filter_bool(categories))
285+
print()
286+
287+
if args.trackers:
288+
torrents_by_tracker = {}
289+
for torrent in torrents:
290+
torrents_by_tracker.setdefault(qbt_get_tracker(qbt_client, torrent), []).append(torrent)
291+
292+
159293
trackers = []
160-
for tracker, torrents in torrents_by_tracker.items():
161-
torrents = [t for t in torrents if args.verbose >= 2 or t.state not in ("stoppedDL",)]
162-
remaining = sum(t.amount_left for t in torrents)
294+
for tracker, tracker_torrents in torrents_by_tracker.items():
295+
tracker_torrents = [t for t in tracker_torrents if args.verbose >= 2 or t.state not in ("stoppedDL",)]
296+
remaining = sum(t.amount_left for t in tracker_torrents)
163297
if remaining or args.verbose >= 2:
164298
trackers.append(
165299
{
166300
"tracker": tracker,
167-
"count": len(torrents),
168-
"size": sum(t.total_size for t in torrents),
301+
"count": len(tracker_torrents),
302+
"size": sum(t.total_size for t in tracker_torrents),
169303
"remaining": remaining,
170-
"file_count": sum(len(t.files) for t in torrents) if args.verbose >= 2 else None, # a bit slow
304+
"file_count": (
305+
sum(len(t.files) for t in tracker_torrents) if args.verbose >= 2 else None
306+
), # a bit slow
171307
}
172308
)
173309
if trackers:
@@ -183,39 +319,26 @@ def torrents_info():
183319
printing.table(iterables.list_dict_filter_bool(trackers))
184320
print()
185321

186-
categories = []
187-
for state, torrents in torrents_by_state.items():
188-
remaining = sum(t.amount_left for t in torrents)
189-
categories.append(
190-
{
191-
"state": state,
192-
"count": len(torrents),
193-
"size": strings.file_size(sum(t.total_size for t in torrents)),
194-
"remaining": strings.file_size(remaining) if remaining else None,
195-
"file_count": sum(len(t.files) for t in torrents) if args.verbose >= 2 else None, # a bit slow
196-
}
197-
)
198-
199-
categories = sorted(
200-
categories,
201-
key=lambda d: (
202-
d["state"].endswith(("missingFiles", "error")),
203-
d["state"].endswith(("downloading", "DL")),
204-
iterables.safe_index(interesting_states, d["state"]),
205-
),
206-
)
207-
printing.table(iterables.list_dict_filter_bool(categories))
208-
print()
209-
210-
transfer = qbt_client.transfer_info()
211-
print(transfer.connection_status.upper())
212-
213-
dl_speed = strings.file_size(transfer.dl_info_speed)
214-
dl_limit = f"[{strings.file_size(transfer.dl_rate_limit)}/s]" if transfer.dl_rate_limit > 0 else ""
215-
dl_d = strings.file_size(transfer.dl_info_data)
216-
print(f"DL {dl_speed}/s {dl_limit} ({dl_d})")
217-
218-
up_speed = strings.file_size(transfer.up_info_speed)
219-
up_limit = f"[{strings.file_size(transfer.up_rate_limit)}/s]" if transfer.up_rate_limit > 0 else ""
220-
up_d = strings.file_size(transfer.up_info_data)
221-
print(f"UP {up_speed}/s {up_limit} ({up_d})")
322+
if args.transfers:
323+
transfer = qbt_client.transfer_info()
324+
print(transfer.connection_status.upper())
325+
326+
dl_speed = strings.file_size(transfer.dl_info_speed)
327+
dl_limit = f"[{strings.file_size(transfer.dl_rate_limit)}/s]" if transfer.dl_rate_limit > 0 else ""
328+
dl_d = strings.file_size(transfer.dl_info_data)
329+
print(f"DL {dl_speed}/s {dl_limit} ({dl_d})")
330+
331+
up_speed = strings.file_size(transfer.up_info_speed)
332+
up_limit = f"[{strings.file_size(transfer.up_rate_limit)}/s]" if transfer.up_rate_limit > 0 else ""
333+
up_d = strings.file_size(transfer.up_info_data)
334+
print(f"UP {up_speed}/s {up_limit} ({up_d})")
335+
336+
torrent_hashes = [t.hash for t in torrents]
337+
if args.mark_deleted:
338+
qbt_client.torrents_add_tags(tags="library-delete", torrent_hashes=torrent_hashes)
339+
elif args.delete_files:
340+
qbt_client.torrents_delete(delete_files=True, torrent_hashes=torrent_hashes)
341+
elif args.delete_rows:
342+
qbt_client.torrents_delete(delete_files=False, torrent_hashes=torrent_hashes)
343+
elif args.force_start is not None:
344+
qbt_client.torrents_set_force_start(args.force_start, torrent_hashes=torrent_hashes)

0 commit comments

Comments
 (0)