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

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.""" 

8 

9import click 

10from peewee import fn 

11from seedboxsync.cli import Context, group, pass_context 

12from seedboxsync.core.dao import Download, Torrent, typed_peewee_dicts 

13 

14 

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.""" 

19 

20 

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. 

28 

29 Filters torrents by an optional search term and limits 

30 the number of results displayed. 

31 

32 Renders a list of torrent IDs, names, and sent timestamps. 

33 

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") 

41 

42 # DB query 

43 data = Torrent.select(Torrent.id, Torrent.name, Torrent.sent).where(where).limit(number).order_by(Torrent.sent.desc()).dicts() 

44 

45 click.echo( 

46 ctx.render( 

47 reversed(data), 

48 headers={"id": "Id", "name": "Name", "sent": "Sent datetime"}, 

49 tablefmt="github", 

50 ) 

51 ) 

52 

53 

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. 

61 

62 Filters downloads by an optional search term and limits 

63 the number of results displayed. 

64 

65 Renders a list of download IDs, paths, finished timestamps, and sizes. 

66 

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 

74 

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 ) 

88 

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 ) 

101 

102 

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. 

110 

111 Filters in-progress downloads by an optional search term and limits 

112 the number of results displayed. 

113 

114 Calculates local download progress and ETA, and renders a list 

115 including ID, path, start time, progress percentage, ETA, and size. 

116 

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 

124 

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) 

128 

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 ) 

144 

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 ] 

157 

158 click.echo( 

159 ctx.render( 

160 rows, 

161 headers=["Id", "Path", "Started", "Progress", "ETA", "Size"], 

162 tablefmt="github", 

163 ) 

164 )