ittup 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. ittup/__init__.py +1 -0
  2. ittup/__main__.py +5 -0
  3. ittup/common/__init__.py +4 -0
  4. ittup/common/about.py +15 -0
  5. ittup/common/bdinfo_string.py +98 -0
  6. ittup/common/bittorrent.py +14 -0
  7. ittup/common/command.py +162 -0
  8. ittup/common/constants.py +18 -0
  9. ittup/common/database.py +78 -0
  10. ittup/common/external_services/__init__.py +9 -0
  11. ittup/common/external_services/ftpx/__init__.py +0 -0
  12. ittup/common/external_services/ftpx/client.py +305 -0
  13. ittup/common/external_services/ftpx/core/__init__.py +0 -0
  14. ittup/common/external_services/ftpx/core/ftpx_service.py +76 -0
  15. ittup/common/external_services/ftpx/core/ftpx_session.py +159 -0
  16. ittup/common/external_services/ftpx/core/menu.py +17 -0
  17. ittup/common/external_services/ftpx/core/models/__init__.py +0 -0
  18. ittup/common/external_services/ftpx/core/models/list.py +17 -0
  19. ittup/common/external_services/igdb/__init__.py +0 -0
  20. ittup/common/external_services/igdb/client.py +262 -0
  21. ittup/common/external_services/igdb/core/__init__.py +0 -0
  22. ittup/common/external_services/igdb/core/api.py +70 -0
  23. ittup/common/external_services/igdb/core/models/__init__.py +0 -0
  24. ittup/common/external_services/igdb/core/models/search.py +13 -0
  25. ittup/common/external_services/igdb/core/platformid.py +24 -0
  26. ittup/common/external_services/igdb/core/tags.py +13 -0
  27. ittup/common/external_services/imageHost.py +324 -0
  28. ittup/common/external_services/mediaresult.py +25 -0
  29. ittup/common/external_services/sessions/__init__.py +0 -0
  30. ittup/common/external_services/sessions/agents.py +212 -0
  31. ittup/common/external_services/sessions/exceptions.py +100 -0
  32. ittup/common/external_services/sessions/session.py +149 -0
  33. ittup/common/external_services/theMovieDB/__init__.py +4 -0
  34. ittup/common/external_services/theMovieDB/core/__init__.py +8 -0
  35. ittup/common/external_services/theMovieDB/core/api.py +574 -0
  36. ittup/common/external_services/theMovieDB/core/keywords.py +7 -0
  37. ittup/common/external_services/theMovieDB/core/models/__init__.py +0 -0
  38. ittup/common/external_services/theMovieDB/core/models/movie/__init__.py +0 -0
  39. ittup/common/external_services/theMovieDB/core/models/movie/alternative.py +2 -0
  40. ittup/common/external_services/theMovieDB/core/models/movie/details.py +82 -0
  41. ittup/common/external_services/theMovieDB/core/models/movie/movie.py +48 -0
  42. ittup/common/external_services/theMovieDB/core/models/movie/nowplaying.py +69 -0
  43. ittup/common/external_services/theMovieDB/core/models/movie/release_info.py +49 -0
  44. ittup/common/external_services/theMovieDB/core/models/movie/translations.py +33 -0
  45. ittup/common/external_services/theMovieDB/core/models/tvshow/__init__.py +0 -0
  46. ittup/common/external_services/theMovieDB/core/models/tvshow/alternative.py +14 -0
  47. ittup/common/external_services/theMovieDB/core/models/tvshow/details.py +139 -0
  48. ittup/common/external_services/theMovieDB/core/models/tvshow/on_the_air.py +27 -0
  49. ittup/common/external_services/theMovieDB/core/models/tvshow/translations.py +45 -0
  50. ittup/common/external_services/theMovieDB/core/models/tvshow/tvshow.py +50 -0
  51. ittup/common/external_services/theMovieDB/core/videos.py +21 -0
  52. ittup/common/external_services/trailers/__init__.py +3 -0
  53. ittup/common/external_services/trailers/api.py +120 -0
  54. ittup/common/external_services/trailers/response.py +51 -0
  55. ittup/common/external_services/tvdb.py +45 -0
  56. ittup/common/extractor.py +124 -0
  57. ittup/common/frames.py +221 -0
  58. ittup/common/mediainfo.py +206 -0
  59. ittup/common/mediainfo_string.py +103 -0
  60. ittup/common/settings.py +952 -0
  61. ittup/common/tags.py +415 -0
  62. ittup/common/title.py +127 -0
  63. ittup/common/torrent_clients.py +280 -0
  64. ittup/common/trackers/__init__.py +8 -0
  65. ittup/common/trackers/ban_list.py +5 -0
  66. ittup/common/trackers/data.py +22 -0
  67. ittup/common/trackers/itt.py +153 -0
  68. ittup/common/trackers/signs_list.py +293 -0
  69. ittup/common/trackers/tags_list.py +118 -0
  70. ittup/common/trackers/trackers.py +46 -0
  71. ittup/common/utility.py +394 -0
  72. ittup/core/__init__.py +5 -0
  73. ittup/core/__main__.py +289 -0
  74. ittup/core/automode.py +122 -0
  75. ittup/core/bot.py +294 -0
  76. ittup/core/duplicate.py +270 -0
  77. ittup/core/exceptions.py +122 -0
  78. ittup/core/media.py +378 -0
  79. ittup/core/media_manager/ContentManager.py +199 -0
  80. ittup/core/media_manager/DocuManager.py +76 -0
  81. ittup/core/media_manager/GameManager.py +107 -0
  82. ittup/core/media_manager/MediaInfoManager.py +26 -0
  83. ittup/core/media_manager/SeedManager.py +49 -0
  84. ittup/core/media_manager/TorrentManager.py +153 -0
  85. ittup/core/media_manager/VideoManager.py +145 -0
  86. ittup/core/media_manager/__init__.py +0 -0
  87. ittup/core/media_manager/common.py +265 -0
  88. ittup/core/pvtDocu.py +123 -0
  89. ittup/core/pvtTorrent.py +78 -0
  90. ittup/core/pvtTracker.py +481 -0
  91. ittup/core/pvtVideo.py +104 -0
  92. ittup/core/torrent.py +467 -0
  93. ittup/core/upload.py +168 -0
  94. ittup/view/__init__.py +3 -0
  95. ittup/view/custom_console.py +148 -0
  96. ittup-1.1.0.dist-info/METADATA +182 -0
  97. ittup-1.1.0.dist-info/RECORD +101 -0
  98. ittup-1.1.0.dist-info/WHEEL +5 -0
  99. ittup-1.1.0.dist-info/entry_points.txt +2 -0
  100. ittup-1.1.0.dist-info/licenses/LICENSE +22 -0
  101. ittup-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,305 @@
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+
4
+ from rich.prompt import Prompt
5
+ from rich.table import Table
6
+ from rich.text import Text
7
+
8
+ from ittup.common.external_services.ftpx.core.models.list import FTPDirectory
9
+ from ittup.common.external_services.ftpx.core.ftpx_service import FtpX
10
+ from ittup.common import config_settings
11
+
12
+ from ittup.view import custom_console
13
+
14
+ class Folder:
15
+ """
16
+ Represents a category with a name
17
+ """
18
+
19
+ def __init__(self, name: str):
20
+ self.size: str | None = None
21
+ self.time: str | None = None
22
+ self.date: str | None = None
23
+ self.group: str | None = None
24
+ self.type: str | None = None
25
+ self.name: str = name
26
+
27
+
28
+ class MyPage:
29
+ """
30
+ Handles pagination logic
31
+ """
32
+
33
+ def __init__(self, items: list[Folder], items_per_page=50):
34
+ self.items = items
35
+ self.items_per_page = items_per_page
36
+ self.current_page = 1
37
+ self.total_pages = (
38
+ len(self.items) + self.items_per_page - 1
39
+ ) // self.items_per_page
40
+
41
+ def get_items(self) -> list[Folder]:
42
+ """
43
+ Returns the items for the current page
44
+ """
45
+ start_index = (self.current_page - 1) * self.items_per_page
46
+ end_index = start_index + self.items_per_page
47
+ return self.items[start_index:end_index]
48
+
49
+ def home_page(self) -> None:
50
+ self.current_page = 1
51
+
52
+ def next_page(self) -> None:
53
+ """
54
+ Moves the 'cursor' to the next page
55
+ """
56
+ if self.current_page < self.total_pages:
57
+ self.current_page += 1
58
+
59
+ def prev_page(self) -> None:
60
+ """
61
+ Moves the 'cursor' to the previous page
62
+ """
63
+ if self.current_page > 1:
64
+ self.current_page -= 1
65
+
66
+ @staticmethod
67
+ def is_valid(selection: int, current_page_items: list[Folder]) -> bool:
68
+ """
69
+ Validates the selected number
70
+ """
71
+ return 1 <= selection <= len(current_page_items)
72
+
73
+ def get_absolute_index(self, selection: int) -> int:
74
+ """
75
+ Returns the absolute index
76
+ """
77
+ return (self.current_page - 1) * self.items_per_page + selection - 1
78
+
79
+ def build_page(self, page) -> Table:
80
+ """
81
+ Builds a page (Table) with the current list of categories
82
+ """
83
+ table = Table(
84
+ title=f"[bold magenta]Category Menu[/bold magenta] (Page {self.current_page}/{self.total_pages})",
85
+ style="dim",
86
+ title_style="bold yellow",
87
+ )
88
+ table.add_column(
89
+ "Index", style="cyan", header_style="bold cyan", justify="center", width=5
90
+ )
91
+ table.add_column("Category", style="green", header_style="bold green")
92
+
93
+ for idx, category in enumerate(
94
+ page, start=1
95
+ ): # Adjust index to start from 1 for display
96
+ row_style = "white" if idx % 2 == 0 else "bright_black"
97
+ table.add_row(
98
+ f"[bold yellow]{idx}[/bold yellow]",
99
+ f"[italic green]{category.name}[/italic green]",
100
+ style=row_style,
101
+ )
102
+ return table
103
+
104
+ def select_category(self, action: str) -> Folder | None:
105
+ category_items_list = self.get_items()
106
+
107
+ try:
108
+ selection = int(action)
109
+ if self.is_valid(selection, category_items_list):
110
+ # Get the absolute index
111
+ absolute_index = self.get_absolute_index(selection)
112
+ selected_category = self.items[absolute_index]
113
+ return selected_category
114
+ else:
115
+ custom_console.bot_error_log(
116
+ f"[red]Invalid number. Choose between 1 and {len(category_items_list)}[/red]"
117
+ )
118
+ except ValueError:
119
+ custom_console.bot_error_log(
120
+ "[red]Please enter a valid command or folder number[/red]"
121
+ )
122
+
123
+ return None
124
+
125
+
126
+ class Client:
127
+ """
128
+ DATA
129
+ """
130
+
131
+ def __init__(self):
132
+ # New Ftp connection
133
+ self.ftpx_service = FtpX.new()
134
+
135
+ # // Build the first Home page
136
+ home_folder = self.ftpx_service.get_list(remote_path=config_settings.options.FTPX_ROOT)
137
+
138
+ # Quit if there are no files in the root folder
139
+ if not home_folder:
140
+ custom_console.bot_error_log(
141
+ "Root folder not found or there are no files.."
142
+ )
143
+ self.ftpx_service.quit()
144
+
145
+ # Create a page with the current folder
146
+ self.page = MyPage(home_folder)
147
+ # current ftp remote path
148
+ self.remote_path = None
149
+ # current download local path
150
+ self.download_to_local_path = None
151
+ # Current list of files (as shown in the Table of Contents)
152
+ self.current_list_of_files: list["FTPDirectory"] = []
153
+ # Selected single file
154
+ self.single_file_selected: bool = False
155
+
156
+ def home_page(self):
157
+ # Build the current Home page
158
+ self.page.home_page()
159
+ # Get a list of items
160
+ page_items_list = self.page.get_items()
161
+ # Build and fill the table
162
+ home_table = self.page.build_page(page_items_list)
163
+ # return the data
164
+ return home_table
165
+
166
+ def page_next(self):
167
+ # Set the 'program counter...' to next page
168
+ self.page.next_page()
169
+ # Get a list of items
170
+ page_items_list = self.page.get_items()
171
+ # return the built data table
172
+ return self.page.build_page(page_items_list)
173
+
174
+ def page_prev(self):
175
+ # Set the 'program counter...' to prev page
176
+ self.page.prev_page()
177
+ # Get a list of items
178
+ page_items_list = self.page.get_items()
179
+ # return the built data table
180
+ return self.page.build_page(page_items_list)
181
+
182
+ def page_up(self):
183
+ # Deselect any previously selected single file
184
+ self.single_file_selected = False
185
+ # get the previous path
186
+ split_path = [part for part in self.remote_path.split("/") if part]
187
+ # build the new path
188
+ up_path = f"/{'/'.join(split_path[:-1])}"
189
+ # return the built data table
190
+ return self.change_path(selected_folder=up_path)
191
+
192
+ def download(self):
193
+
194
+ if not self.single_file_selected:
195
+ # Get the download list
196
+ download_list = [
197
+ (os.path.join(self.remote_path, file.name), file.size)
198
+ for file in self.current_list_of_files
199
+ ]
200
+ else:
201
+ # Get the single file selected
202
+ download_list = [(self.remote_path, self.current_list_of_files[0].size)]
203
+
204
+ for remote_file, size in download_list:
205
+
206
+ # format the path. Replace '\' to '/' from Windows OS to linux
207
+ remote_file = remote_file.replace("\\", "/")
208
+
209
+ # Skip the first '/' otherwise it would create a list with a leading space
210
+ remote_file_path = remote_file[1:].split("/")
211
+ # Get only the last two subfolders
212
+ if len(remote_file_path) > 2:
213
+ remote_short_path = remote_file_path[-2:]
214
+ else:
215
+ remote_short_path = remote_file_path
216
+
217
+ self.download_to_local_path = os.path.join(
218
+ config_settings.options.FTPX_LOCAL_PATH, *remote_short_path
219
+ )
220
+ custom_console.bot_log(
221
+ f"Server:{os.path.basename(remote_file)} -> Client:{self.download_to_local_path}"
222
+ )
223
+
224
+ self.ftpx_service.download_file(
225
+ remote_path=remote_file, local_path=self.download_to_local_path
226
+ )
227
+
228
+ def select_file(self, one_file_selected: FTPDirectory):
229
+ # Update the remote path
230
+ self.remote_path = os.path.join(
231
+ self.ftpx_service.current_path(), one_file_selected.name
232
+ )
233
+ self.single_file_selected = True
234
+ # format the path. Replace '\' to '/' from Windows OS to linux
235
+ self.remote_path = self.remote_path.replace("\\", "/")
236
+
237
+ # Create a list of FPTDirectory for a single file
238
+ self.current_list_of_files = [one_file_selected]
239
+
240
+ def change_path(self, selected_folder: str):
241
+ self.remote_path = os.path.join(
242
+ self.ftpx_service.current_path(), selected_folder
243
+ )
244
+
245
+ # format the path. Replace '\' to '/' from Windows OS to linux
246
+ self.remote_path = self.remote_path.replace("\\", "/")
247
+ self.ftpx_service.change_dir(new_path=self.remote_path)
248
+ # // Build a new Home page for the current folder
249
+ home_folder = self.ftpx_service.get_list(remote_path=config_settings.options.FTPX_ROOT)
250
+ # Create a page with the current folder
251
+ self.page = MyPage(home_folder)
252
+ # Save the current list of files
253
+ self.current_list_of_files = home_folder
254
+ return self.home_page()
255
+
256
+ def user_input_search(self):
257
+ custom_console.print("Search '->' ", end='', style='violet bold')
258
+ keyword = input()
259
+ for item in self.page.get_items():
260
+ if keyword.lower() in item.name.lower():
261
+ custom_console.bot_question_log(
262
+ f"\nTYPE: {item.type} \nNAME: {item.name} \nGROUP: {item.group} \nSIZE: {item.size}"
263
+ f"DATE: {item.date}\n TIME: {item.time}\n")
264
+ custom_console.rule(style='violet bold')
265
+
266
+ def search(self):
267
+ self.user_input_search()
268
+
269
+ def user_input(self) -> str:
270
+ """
271
+ Asks the user for the folder.
272
+ """
273
+ if not self.remote_path:
274
+ self.remote_path = "/"
275
+
276
+ prompt_message = Text(f"\n-> {self.remote_path}\n", style="bold violet")
277
+
278
+ prompt_message.append(
279
+ "[N]next, [P]rev, [U]p, [D]ownload, [Q]uit, [S]earch or enter a valid number",
280
+ style="reset",
281
+ )
282
+ return Prompt.ask(prompt_message)
283
+
284
+ def input_manager(self, action: str):
285
+ if action.upper() == "N":
286
+ return self.page_next()
287
+ elif action.upper() == "P":
288
+ return self.page_prev()
289
+ elif action.upper() == "U":
290
+ return self.page_up()
291
+ elif action.upper() == "D":
292
+ self.download()
293
+ return 0
294
+ elif action.upper() == "S":
295
+ self.search()
296
+ elif action.upper() == "Q":
297
+ return 0
298
+ else:
299
+ return self.page.select_category(action)
300
+
301
+ def quit(self):
302
+ self.ftpx_service.quit()
303
+
304
+ def sys_info(self):
305
+ return self.ftpx_service.syst()
File without changes
@@ -0,0 +1,76 @@
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+ from datetime import datetime
4
+
5
+ from ittup.common.external_services.ftpx.core.ftpx_session import FtpXCmds
6
+ from ittup.common.external_services.ftpx.core.models.list import FTPDirectory
7
+ from ittup.common.utility import MyString
8
+
9
+
10
+ class FtpX(FtpXCmds):
11
+ def __init__(self):
12
+ super().__init__()
13
+
14
+ def get_list(self, remote_path=".") -> ["FTPDirectory"]:
15
+ # Get a list from the FTP server
16
+ file_list = self._list(path=remote_path)
17
+ if not file_list:
18
+ return []
19
+
20
+ folder = [
21
+ FTPDirectory(
22
+ permissions=parts[0],
23
+ type="Folder" if parts[0][:1] == "d" else "File",
24
+ links=int(parts[1]),
25
+ owner=parts[2],
26
+ group=parts[3],
27
+ size=int(parts[4]),
28
+ date=date_time.date(),
29
+ time=date_time.time(),
30
+ name=parts[8],
31
+ )
32
+ for file in file_list
33
+ if (parts := file.split()) and "total" not in parts
34
+ if (
35
+ date_time := MyString.parse_date(file)
36
+ ) # Assign date_time only if its valid
37
+ ]
38
+
39
+ # The first in list is the most recent
40
+ sorted_folder = sorted(
41
+ folder,
42
+ key=lambda entry: datetime.combine(entry.date, entry.time),
43
+ reverse=True,
44
+ )
45
+
46
+ return sorted_folder
47
+
48
+ def current_path(self):
49
+ return self._pwd()
50
+
51
+ def change_dir(self, new_path: str):
52
+ return self.cwd(dirname=new_path)
53
+
54
+ def download_file(self, remote_path: str, local_path: str):
55
+ """Download a file from the ftp server"""
56
+
57
+ # format the path. Replace '\' to '/' from Windows OS to linux
58
+ remote_path = remote_path.replace("\\", "/")
59
+
60
+ # Create the local folder if it does not exist
61
+ local_dir = os.path.dirname(local_path)
62
+ if not os.path.exists(local_dir):
63
+ os.makedirs(local_dir)
64
+
65
+ # Download...
66
+ self._retr(
67
+ remote_path=remote_path,
68
+ local_path=local_path,
69
+ size=self.file_size(remote_path),
70
+ )
71
+
72
+ def file_size(self, file_path: str):
73
+ return self._size(file_path=file_path)
74
+
75
+ def syst(self):
76
+ return self._syst()
@@ -0,0 +1,159 @@
1
+ # -*- coding: utf-8 -*-
2
+ import ftplib
3
+ import threading
4
+ import time
5
+ from rich.progress import Progress
6
+
7
+ from ftplib import FTP_TLS, all_errors
8
+ from ittup.common import config_settings
9
+ from ittup.view import custom_console
10
+
11
+ class FtpXCmds(FTP_TLS):
12
+ def __init__(self):
13
+ super().__init__()
14
+ self.is_quitting = False
15
+ if config_settings.options.FTPX_KEEP_ALIVE:
16
+ # Time interval to keep the connection alive (in seconds)
17
+ self.keep_alive_interval = 10
18
+ # flag to stop the keep-alive thread
19
+ self.keep_alive_stop_flag = threading.Event()
20
+ # Separate thread to run noop to the server
21
+ self.keep_alive_thread = threading.Thread(target=self._keep_alive)
22
+ # Daemon mode
23
+ self.keep_alive_thread.daemon = True
24
+ self.keep_alive_thread.start()
25
+
26
+ def _keep_alive(self):
27
+ while not self.keep_alive_stop_flag.is_set():
28
+ time.sleep(self.keep_alive_interval)
29
+ try:
30
+ self.voidcmd("NOOP")
31
+ except Exception as e:
32
+ print(f"Error during keep-alive: {e}")
33
+ if not self.is_quitting:
34
+ self.quit()
35
+
36
+ @staticmethod
37
+ def _execute_command(command_fn, *args, **kwargs):
38
+ try:
39
+ return command_fn(*args, **kwargs)
40
+ except Exception as e:
41
+ custom_console.bot_error_log(
42
+ f"Error during {command_fn.__name__} command: {e}"
43
+ )
44
+ exit(1)
45
+
46
+ def quit(self):
47
+ custom_console.bot_question_log("Exiting... Please wait for the app to close\n")
48
+ if self.is_quitting:
49
+ return
50
+ if config_settings.options.FTPX_KEEP_ALIVE:
51
+ # Set the stop flag for the keep-alive thread
52
+ self.keep_alive_stop_flag.set()
53
+ if self.keep_alive_thread.is_alive():
54
+ # Wait for the thread to finish
55
+ self.keep_alive_thread.join()
56
+ self.is_quitting = True
57
+
58
+ try:
59
+ super().quit()
60
+ except ftplib.error_temp as e:
61
+ custom_console.bot_error_log(e)
62
+
63
+ @classmethod
64
+ def new(
65
+ cls,
66
+ host=config_settings.options.FTPX_IP,
67
+ port=int(config_settings.options.FTPX_PORT),
68
+ user=config_settings.options.FTPX_USER,
69
+ passwd=config_settings.options.FTPX_PASS,
70
+ ):
71
+
72
+ validate_ftpx_config = True
73
+ if not config_settings.options.FTPX_IP:
74
+ custom_console.bot_question_log("No FTPX_IP provided\n")
75
+ validate_ftpx_config = False
76
+
77
+ if not config_settings.options.FTPX_PORT:
78
+ custom_console.bot_question_log("No FTPX_PORT provided\n")
79
+ validate_ftpx_config = False
80
+
81
+ if not config_settings.options.FTPX_USER:
82
+ custom_console.bot_question_log("No FTPX_USER provided\n")
83
+ validate_ftpx_config = False
84
+
85
+ if not config_settings.options.FTPX_PASS:
86
+ custom_console.bot_question_log("No FTPX_PASS provided\n")
87
+ validate_ftpx_config = False
88
+
89
+ if not config_settings.options.FTPX_LOCAL_PATH:
90
+ custom_console.bot_question_log("No FTPX_LOCAL_PATH provided\n")
91
+ validate_ftpx_config = False
92
+
93
+ if not validate_ftpx_config:
94
+ custom_console.bot_error_log(f"Please check your config file or verify if the FTP server is online")
95
+ exit(1)
96
+
97
+ """Create an instance of FtpXCmds and handle connection and login"""
98
+ ftp = cls()
99
+ try:
100
+ ftp.connect(host, port)
101
+ ftp.login(user=user, passwd=passwd)
102
+ ftp.prot_p() # Enable SSL
103
+ except all_errors as e:
104
+ custom_console.bot_error_log(f"\nFTP Server Error: {e}")
105
+ custom_console.bot_error_log(
106
+ f"Please check your config file or verify if the FTP server is online"
107
+ )
108
+ exit(1)
109
+ return ftp
110
+
111
+ def _send_pret(self, command):
112
+ """Send PRET command to the FTP server"""
113
+ return self.sendcmd(f"PRET {command}")
114
+
115
+ def _list(self, path="") -> list:
116
+ """Run LIST with PRET and return list of files and directories"""
117
+ output_lines = []
118
+
119
+ def collect_line(line):
120
+ output_lines.append(line)
121
+
122
+ self._execute_command(self._send_pret, "LIST")
123
+ self._execute_command(self.retrlines, f"LIST {path}", callback=collect_line)
124
+ return output_lines
125
+
126
+ def _retr(self, remote_path, local_path: str, size: int) -> bool:
127
+ def download():
128
+ with open(local_path, "wb") as local_file:
129
+ with Progress() as progress:
130
+ task = progress.add_task(
131
+ "[cyan]Downloading...", total=size, visible=True
132
+ )
133
+
134
+ def write_chunk(chunk):
135
+ local_file.write(chunk)
136
+ progress.update(task, advance=len(chunk))
137
+
138
+ self.retrbinary(f"RETR {remote_path}", write_chunk)
139
+
140
+ return self._execute_command(
141
+ self._send_pret, f"RETR {remote_path}"
142
+ ) and self._execute_command(download)
143
+
144
+ def _cwd(self, path):
145
+ # Change the working directory on the FTP server using PRET
146
+ self._execute_command(self._send_pret, f"CWD {path}")
147
+ self._execute_command(self.cwd, path)
148
+
149
+ def _pwd(self) -> str:
150
+ # Get the current directory on the FTP server
151
+ return self._execute_command(self.pwd)
152
+
153
+ def _size(self, file_path: str):
154
+ # Get the size of a file on the FTP server
155
+ return self._execute_command(self.size, filename=file_path)
156
+
157
+ def _syst(self):
158
+ # Send the syst command to get system info from the server
159
+ return self._execute_command(self.sendcmd, "SYST")
@@ -0,0 +1,17 @@
1
+ from rich.console import Console
2
+
3
+
4
+ class Menu:
5
+ """
6
+ Create a new Menu.
7
+ VIEW
8
+ """
9
+
10
+ def __init__(self):
11
+ self.console = Console()
12
+
13
+ def show(self, table) -> None:
14
+ """
15
+ Displays the current page.
16
+ """
17
+ self.console.print(table)
@@ -0,0 +1,17 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from dataclasses import dataclass
4
+ import datetime
5
+
6
+
7
+ @dataclass
8
+ class FTPDirectory:
9
+ permissions: str | None = None
10
+ type: str | None = None
11
+ links: int | None = None
12
+ owner: str | None = None
13
+ group: str | None = None
14
+ size: int | None = None
15
+ date: datetime.date | None = None
16
+ time: datetime.time | None = None
17
+ name: str | None = None
File without changes