Coverage for seedboxsync/cli/commands/cmd_search.py: 100%
34 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-26 17:46 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-26 17:46 +0000
1#
2# Copyright (C) 2015-2026 Guillaume Kulakowski <guillaume@kulakowski.fr>
3#
4# For the full copyright and license information, please view the LICENSE
5# file that was distributed with this source code.
6#
7"""All commands related to search operations in SeedboxSync."""
9import click
10from peewee import fn
11from seedboxsync.cli import Context, group, pass_context
12from seedboxsync.core.dao import Download, Torrent, typed_peewee_dicts
15@group("search", help="Search operations.") # type: ignore[untyped-decorator]
16@pass_context
17def cli(ctx: Context) -> None:
18 """Empty function for Click sub commands."""
21@cli.command("uploaded", help="Search last torrents uploaded from blackhole.") # type: ignore[untyped-decorator]
22@click.option("-n", "--number", type=int, default=10, help="Number of torrents to display.")
23@click.option("-s", "--search", help="Term to search.")
24@pass_context
25def uploaded(ctx: Context, number: int, search: str) -> None:
26 """
27 Search for the most recent torrents uploaded from blackhole.
29 Filters torrents by an optional search term and limits
30 the number of results displayed.
32 Renders a list of torrent IDs, names, and sent timestamps.
34 Args:
35 ctx (Context): The Click context object.
36 number (int): The maximum number of torrents to display.
37 search (str): An optional search term to filter torrent names.
38 """
39 # Build "where" expression
40 where = Torrent.name.contains(search) if search else ~Torrent.id.contains("not_a_int")
42 # DB query
43 data = Torrent.select(Torrent.id, Torrent.name, Torrent.sent).where(where).limit(number).order_by(Torrent.sent.desc()).dicts()
45 click.echo(
46 ctx.render(
47 reversed(data),
48 headers={"id": "Id", "name": "Name", "sent": "Sent datetime"},
49 tablefmt="github",
50 )
51 )
54@cli.command("downloaded", help="Search last files downloaded from seedbox.") # type: ignore[untyped-decorator]
55@click.option("-n", "--number", type=int, default=10, help="Number of torrents to display.")
56@click.option("-s", "--search", help="Term to search.")
57@pass_context
58def downloaded(ctx: Context, number: int, search: str) -> None:
59 """
60 Search for the most recent files downloaded from the seedbox.
62 Filters downloads by an optional search term and limits
63 the number of results displayed.
65 Renders a list of download IDs, paths, finished timestamps, and sizes.
67 Args:
68 ctx (Context): The Click context object.
69 number (int): The maximum number of torrents to display.
70 search (str): An optional search term to filter torrent names.
71 """
72 # Build "where" expression
73 where = (Download.finished != 0) & Download.path.contains(search) if search else Download.finished != 0
75 # DB query
76 data = (
77 Download.select(
78 Download.id,
79 fn.SUBSTR(Download.path, -100).alias("path"),
80 Download.finished,
81 fn.humanize(Download.local_size).alias("size"),
82 )
83 .where(where)
84 .limit(number)
85 .order_by(Download.finished.desc())
86 .dicts()
87 )
89 click.echo(
90 ctx.render(
91 reversed(data),
92 headers={
93 "id": "Id",
94 "finished": "Finished",
95 "path": "Path",
96 "size": "Size",
97 },
98 tablefmt="github",
99 )
100 )
103@cli.command("progress", help="Search files currently in download from seedbox.") # type: ignore[untyped-decorator]
104@click.option("-n", "--number", type=int, default=10, help="Number of torrents to display.")
105@click.option("-s", "--search", help="Term to search.")
106@pass_context
107def progress(ctx: Context, number: int, search: str) -> None:
108 """
109 Search for files currently in download from the seedbox.
111 Filters in-progress downloads by an optional search term and limits
112 the number of results displayed.
114 Calculates local download progress and ETA, and renders a list
115 including ID, path, start time, progress percentage, ETA, and size.
117 Args:
118 ctx (Context): The Click context object.
119 number (int): The maximum number of torrents to display.
120 search (str): An optional search term to filter torrent names.
121 """
122 # Build "where" expression
123 where = (Download.finished == 0) & Download.path.contains(search) if search else Download.finished == 0
125 # Calculate columns
126 progress_expr = 100.0 * Download.local_size / fn.NULLIF(Download.seedbox_size, 0)
127 eta_expr = (fn.STRFTIME("%s", "now", "localtime") - fn.STRFTIME("%s", Download.started)) * (100.0 - progress_expr) / fn.NULLIF(progress_expr, 0)
129 # DB query
130 data = typed_peewee_dicts(
131 Download.select(
132 Download.id,
133 fn.SUBSTR(Download.path, -100).alias("path"),
134 Download.started,
135 fn.ROUND(progress_expr, 0).cast("INTEGER").concat("%").alias("progress"),
136 fn.naturaldelta(eta_expr).alias("eta"),
137 fn.humanize(Download.seedbox_size).alias("size"),
138 )
139 .where(where)
140 .limit(number)
141 .order_by(Download.started.desc())
142 .dicts()
143 )
145 # Define the output column order explicitly
146 rows = [
147 (
148 row["id"],
149 row["path"],
150 row["started"],
151 row["progress"],
152 row["eta"],
153 row["size"],
154 )
155 for row in reversed(list(data))
156 ]
158 click.echo(
159 ctx.render(
160 rows,
161 headers=["Id", "Path", "Started", "Progress", "ETA", "Size"],
162 tablefmt="github",
163 )
164 )