Coverage for seedboxsync/cli/commands/cmd_search.py: 100%
38 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-26 17:14 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-26 17:14 +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.database.models import Download, Torrent
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 conditions = []
41 if search:
42 conditions.append(Torrent.name.contains(search))
44 # DB query
45 query = (
46 Torrent.select(
47 Torrent.id,
48 Torrent.name,
49 fn.coalesce(fn.humanize(Torrent.total_size), "").alias("total_size"),
50 fn.coalesce(Torrent.total_files, "").alias("total_files"),
51 fn.short_datetime(Torrent.sent),
52 )
53 .limit(number)
54 .order_by(Torrent.sent.desc())
55 )
57 # if "where" expression
58 if conditions:
59 query = query.where(*conditions)
60 data = query.dicts()
62 click.echo(
63 ctx.render(
64 reversed(data),
65 headers={"id": "Id", "name": "Name", "total_files": "File(s)", "total_size": "Size", "sent": "Sent datetime"},
66 )
67 )
70@cli.command("downloaded", help="Search last files downloaded from seedbox.") # type: ignore[untyped-decorator]
71@click.option("-n", "--number", type=int, default=10, help="Number of torrents to display.")
72@click.option("-s", "--search", help="Term to search.")
73@pass_context
74def downloaded(ctx: Context, number: int, search: str) -> None:
75 """
76 Search for the most recent files downloaded from the seedbox.
78 Filters downloads by an optional search term and limits
79 the number of results displayed.
81 Renders a list of download IDs, paths, finished timestamps, and sizes.
83 Args:
84 ctx (Context): The Click context object.
85 number (int): The maximum number of torrents to display.
86 search (str): An optional search term to filter torrent names.
87 """
88 # Build "where" expression
89 where = (Download.finished != 0) & Download.path.contains(search) if search else Download.finished != 0
91 # DB query
92 data = (
93 Download.select(
94 Download.id,
95 fn.SUBSTR(Download.path, -100).alias("path"),
96 fn.short_datetime(Download.finished),
97 fn.humanize(Download.local_size).alias("size"),
98 )
99 .where(where)
100 .limit(number)
101 .order_by(Download.finished.desc())
102 .dicts()
103 )
105 click.echo(
106 ctx.render(
107 reversed(data),
108 headers={
109 "id": "Id",
110 "path": "Path",
111 "finished": "Finished",
112 "size": "Size",
113 },
114 )
115 )
118@cli.command("progress", help="Search files currently in download from seedbox.") # type: ignore[untyped-decorator]
119@click.option("-n", "--number", type=int, default=10, help="Number of torrents to display.")
120@click.option("-s", "--search", help="Term to search.")
121@pass_context
122def progress(ctx: Context, number: int, search: str) -> None:
123 """
124 Search for files currently in download from the seedbox.
126 Filters in-progress downloads by an optional search term and limits
127 the number of results displayed.
129 Calculates local download progress and ETA, and renders a list
130 including ID, path, start time, progress percentage, ETA, and size.
132 Args:
133 ctx (Context): The Click context object.
134 number (int): The maximum number of torrents to display.
135 search (str): An optional search term to filter torrent names.
136 """
137 # Build "where" expression
138 where = (Download.finished == 0) & Download.path.contains(search) if search else Download.finished == 0
140 # Calculate columns
141 progress_expr = 100.0 * Download.local_size / fn.NULLIF(Download.seedbox_size, 0)
142 eta_expr = (fn.STRFTIME("%s", "now", "localtime") - fn.STRFTIME("%s", Download.started)) * (100.0 - progress_expr) / fn.NULLIF(progress_expr, 0)
144 # DB query
145 data = (
146 Download.select(
147 Download.id,
148 fn.SUBSTR(Download.path, -100).alias("path"),
149 fn.short_datetime(Download.started),
150 fn.ROUND(progress_expr, 0).cast("INTEGER").concat("%").alias("progress"),
151 fn.naturaldelta(eta_expr).alias("eta"),
152 fn.humanize(Download.seedbox_size).alias("size"),
153 )
154 .where(where)
155 .limit(number)
156 .order_by(Download.started.desc())
157 .dicts()
158 )
160 click.echo(
161 ctx.render(
162 reversed(data),
163 headers={"id": "Id", "path": "Path", "started": "Started", "progress": "Progress", "eta": "ETA", "size": "Size"},
164 )
165 )