data-sourcerer 0.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 (95) hide show
  1. data_sourcerer-0.1.0.dist-info/METADATA +52 -0
  2. data_sourcerer-0.1.0.dist-info/RECORD +95 -0
  3. data_sourcerer-0.1.0.dist-info/WHEEL +5 -0
  4. data_sourcerer-0.1.0.dist-info/entry_points.txt +2 -0
  5. data_sourcerer-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. data_sourcerer-0.1.0.dist-info/top_level.txt +1 -0
  7. sourcerer/__init__.py +15 -0
  8. sourcerer/domain/__init__.py +13 -0
  9. sourcerer/domain/access_credentials/__init__.py +7 -0
  10. sourcerer/domain/access_credentials/entities.py +53 -0
  11. sourcerer/domain/access_credentials/exceptions.py +17 -0
  12. sourcerer/domain/access_credentials/repositories.py +84 -0
  13. sourcerer/domain/access_credentials/services.py +91 -0
  14. sourcerer/domain/file_system/__init__.py +7 -0
  15. sourcerer/domain/file_system/entities.py +70 -0
  16. sourcerer/domain/file_system/exceptions.py +17 -0
  17. sourcerer/domain/file_system/services.py +64 -0
  18. sourcerer/domain/shared/__init__.py +6 -0
  19. sourcerer/domain/shared/entities.py +18 -0
  20. sourcerer/domain/storage_provider/__init__.py +7 -0
  21. sourcerer/domain/storage_provider/entities.py +86 -0
  22. sourcerer/domain/storage_provider/exceptions.py +17 -0
  23. sourcerer/domain/storage_provider/services.py +130 -0
  24. sourcerer/infrastructure/__init__.py +13 -0
  25. sourcerer/infrastructure/access_credentials/__init__.py +7 -0
  26. sourcerer/infrastructure/access_credentials/exceptions.py +16 -0
  27. sourcerer/infrastructure/access_credentials/registry.py +120 -0
  28. sourcerer/infrastructure/access_credentials/repositories.py +119 -0
  29. sourcerer/infrastructure/access_credentials/services.py +396 -0
  30. sourcerer/infrastructure/db/__init__.py +6 -0
  31. sourcerer/infrastructure/db/config.py +73 -0
  32. sourcerer/infrastructure/db/models.py +47 -0
  33. sourcerer/infrastructure/file_system/__init__.py +7 -0
  34. sourcerer/infrastructure/file_system/exceptions.py +89 -0
  35. sourcerer/infrastructure/file_system/services.py +147 -0
  36. sourcerer/infrastructure/storage_provider/__init__.py +7 -0
  37. sourcerer/infrastructure/storage_provider/exceptions.py +78 -0
  38. sourcerer/infrastructure/storage_provider/registry.py +84 -0
  39. sourcerer/infrastructure/storage_provider/services.py +509 -0
  40. sourcerer/infrastructure/utils.py +106 -0
  41. sourcerer/presentation/__init__.py +12 -0
  42. sourcerer/presentation/app.py +36 -0
  43. sourcerer/presentation/di_container.py +46 -0
  44. sourcerer/presentation/screens/__init__.py +0 -0
  45. sourcerer/presentation/screens/critical_error/__init__.py +0 -0
  46. sourcerer/presentation/screens/critical_error/main.py +78 -0
  47. sourcerer/presentation/screens/critical_error/styles.tcss +41 -0
  48. sourcerer/presentation/screens/file_system_finder/main.py +248 -0
  49. sourcerer/presentation/screens/file_system_finder/styles.tcss +44 -0
  50. sourcerer/presentation/screens/file_system_finder/widgets/__init__.py +0 -0
  51. sourcerer/presentation/screens/file_system_finder/widgets/file_system_navigator.py +810 -0
  52. sourcerer/presentation/screens/main/__init__.py +0 -0
  53. sourcerer/presentation/screens/main/main.py +469 -0
  54. sourcerer/presentation/screens/main/messages/__init__.py +0 -0
  55. sourcerer/presentation/screens/main/messages/delete_request.py +12 -0
  56. sourcerer/presentation/screens/main/messages/download_request.py +12 -0
  57. sourcerer/presentation/screens/main/messages/preview_request.py +10 -0
  58. sourcerer/presentation/screens/main/messages/resizing_rule.py +21 -0
  59. sourcerer/presentation/screens/main/messages/select_storage_item.py +11 -0
  60. sourcerer/presentation/screens/main/messages/uncheck_files_request.py +8 -0
  61. sourcerer/presentation/screens/main/messages/upload_request.py +10 -0
  62. sourcerer/presentation/screens/main/mixins/__init__.py +0 -0
  63. sourcerer/presentation/screens/main/mixins/resize_containers_watcher_mixin.py +144 -0
  64. sourcerer/presentation/screens/main/styles.tcss +32 -0
  65. sourcerer/presentation/screens/main/widgets/__init__.py +0 -0
  66. sourcerer/presentation/screens/main/widgets/gradient.py +45 -0
  67. sourcerer/presentation/screens/main/widgets/resizing_rule.py +67 -0
  68. sourcerer/presentation/screens/main/widgets/storage_content.py +691 -0
  69. sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +143 -0
  70. sourcerer/presentation/screens/preview_content/__init__.py +0 -0
  71. sourcerer/presentation/screens/preview_content/main.py +59 -0
  72. sourcerer/presentation/screens/preview_content/styles.tcss +26 -0
  73. sourcerer/presentation/screens/provider_creds_list/__init__.py +0 -0
  74. sourcerer/presentation/screens/provider_creds_list/main.py +164 -0
  75. sourcerer/presentation/screens/provider_creds_list/styles.tcss +65 -0
  76. sourcerer/presentation/screens/provider_creds_registration/__init__.py +0 -0
  77. sourcerer/presentation/screens/provider_creds_registration/main.py +264 -0
  78. sourcerer/presentation/screens/provider_creds_registration/styles.tcss +42 -0
  79. sourcerer/presentation/screens/question/__init__.py +0 -0
  80. sourcerer/presentation/screens/question/main.py +31 -0
  81. sourcerer/presentation/screens/question/styles.tcss +33 -0
  82. sourcerer/presentation/screens/shared/__init__.py +0 -0
  83. sourcerer/presentation/screens/shared/containers.py +13 -0
  84. sourcerer/presentation/screens/shared/widgets/__init__.py +0 -0
  85. sourcerer/presentation/screens/shared/widgets/button.py +54 -0
  86. sourcerer/presentation/screens/shared/widgets/labeled_input.py +80 -0
  87. sourcerer/presentation/screens/storage_action_progress/__init__.py +0 -0
  88. sourcerer/presentation/screens/storage_action_progress/main.py +476 -0
  89. sourcerer/presentation/screens/storage_action_progress/styles.tcss +43 -0
  90. sourcerer/presentation/settings.py +31 -0
  91. sourcerer/presentation/themes/__init__.py +0 -0
  92. sourcerer/presentation/themes/github_dark.py +21 -0
  93. sourcerer/presentation/utils.py +69 -0
  94. sourcerer/settings.py +72 -0
  95. sourcerer/utils.py +32 -0
File without changes
@@ -0,0 +1,469 @@
1
+ import traceback
2
+ from pathlib import Path
3
+
4
+ from textual import work, on
5
+ from textual.app import App, ComposeResult
6
+ from textual.binding import Binding
7
+ from textual.containers import Horizontal
8
+ from textual.reactive import reactive
9
+ from textual.widgets import Footer
10
+
11
+ from sourcerer.infrastructure.access_credentials.services import CredentialsService
12
+ from sourcerer.infrastructure.storage_provider.exceptions import (
13
+ ListStorageItemsException,
14
+ )
15
+ from sourcerer.infrastructure.utils import generate_uuid
16
+ from sourcerer.presentation.screens.critical_error.main import CriticalErrorScreen
17
+ from sourcerer.presentation.screens.file_system_finder.main import (
18
+ FileSystemNavigationModal,
19
+ )
20
+ from sourcerer.presentation.screens.main.messages.delete_request import DeleteRequest
21
+ from sourcerer.presentation.screens.main.messages.download_request import (
22
+ DownloadRequest,
23
+ )
24
+ from sourcerer.presentation.screens.main.messages.preview_request import PreviewRequest
25
+ from sourcerer.presentation.screens.main.messages.select_storage_item import (
26
+ SelectStorageItem,
27
+ )
28
+ from sourcerer.presentation.screens.main.messages.uncheck_files_request import (
29
+ UncheckFilesRequest,
30
+ )
31
+ from sourcerer.presentation.screens.main.messages.upload_request import UploadRequest
32
+ from sourcerer.presentation.screens.main.mixins.resize_containers_watcher_mixin import (
33
+ ResizeContainersWatcherMixin,
34
+ )
35
+ from sourcerer.presentation.screens.main.widgets.resizing_rule import ResizingRule
36
+ from sourcerer.presentation.screens.main.widgets.storage_content import (
37
+ StorageContentContainer,
38
+ )
39
+ from sourcerer.presentation.screens.main.widgets.storage_list_sidebar import (
40
+ StorageListSidebar,
41
+ )
42
+ from sourcerer.presentation.screens.preview_content.main import PreviewContentScreen
43
+ from sourcerer.presentation.screens.provider_creds_list.main import (
44
+ ProviderCredsListScreen,
45
+ )
46
+ from sourcerer.presentation.screens.storage_action_progress.main import (
47
+ StorageActionProgressScreen,
48
+ UploadKey,
49
+ DownloadKey,
50
+ DeleteKey,
51
+ )
52
+ from sourcerer.presentation.themes.github_dark import github_dark_theme
53
+ from sourcerer.presentation.utils import (
54
+ get_provider_service_by_access_uuid,
55
+ get_provider_service_by_access_credentials,
56
+ )
57
+
58
+
59
+ class Sourcerer(App, ResizeContainersWatcherMixin):
60
+ """
61
+ A Textual application for managing cloud storage credentials and content.
62
+
63
+ This application provides a user interface to list and manage cloud storage
64
+ credentials, view storage content, and handle storage item selection. It
65
+ integrates with various cloud storage providers and supports asynchronous
66
+ operations for fetching and displaying storage data.
67
+
68
+ Attributes:
69
+ CSS_PATH (str): Path to the CSS file for styling the application.
70
+ BINDINGS (list): Key bindings for application actions.
71
+ is_storage_list_loading (reactive): Reactive attribute indicating if the
72
+ storage list is currently loading.
73
+
74
+ Methods:
75
+ compose() -> ComposeResult: Composes the UI layout with storage list and
76
+ content areas.
77
+ on_mount(): Initializes the application theme and storage list on mount.
78
+ action_credentials_list(): Opens the credentials list screen and refreshes
79
+ storages.
80
+ refresh_storages(*args, **kwargs): Refreshes the storage list by clearing
81
+ and reinitializing it.
82
+ init_storages_list() -> None: Asynchronously initializes the storage list
83
+ by fetching and displaying storages.
84
+ on_select_storage_item(event: SelectStorageItem): Handles storage item
85
+ selection and updates storage content.
86
+ """
87
+
88
+ CSS_PATH = "styles.tcss"
89
+ BINDINGS = [Binding("ctrl+r", "registrations", "Registrations list")]
90
+ is_storage_list_loading = reactive(False, recompose=True)
91
+
92
+ def __init__(self, *args, **kwargs):
93
+ super().__init__(*args, **kwargs)
94
+ self.credentials_service = CredentialsService()
95
+ self.storage_list_sidebar = StorageListSidebar(id="storage_list_sidebar")
96
+ self.storage_content = StorageContentContainer(id="storage_content_container")
97
+ self.load_percentage = 0
98
+ self.active_resizing_rule: ResizingRule | None = None
99
+
100
+ def compose(self) -> ComposeResult:
101
+ with Horizontal(id="main"):
102
+ yield self.storage_list_sidebar
103
+ yield ResizingRule(
104
+ id="storage_list_sidebar_container",
105
+ orientation="vertical",
106
+ classes="resize-handle",
107
+ prev_component_id=self.storage_list_sidebar.id,
108
+ next_component_id=self.storage_content.id,
109
+ )
110
+ yield self.storage_content
111
+ yield Footer()
112
+
113
+ def _handle_exception(self, error: Exception) -> None:
114
+ self.push_screen(CriticalErrorScreen(str(error), traceback.format_exc()))
115
+
116
+ def on_mount(self):
117
+ """
118
+ Initializes the application theme and storage list on mount.
119
+ """
120
+
121
+ self.register_theme(github_dark_theme) # pyright: ignore [reportArgumentType]
122
+
123
+ self.theme = "github-dark"
124
+ self.init_storages_list()
125
+
126
+ def action_registrations(self):
127
+ """
128
+ Opens the provider credentials list screen and refreshes the storage list.
129
+ This method is triggered by the key binding "ctrl+r" and allows the user
130
+ to manage their cloud storage credentials. It pushes the
131
+ ProviderCredsListScreen to the application stack and sets a callback
132
+ to refresh the storage list after the screen is closed.
133
+ This method is typically used to allow users to add their
134
+ cloud storage credentials, which will then be reflected in the storage
135
+ """
136
+ self.app.push_screen(ProviderCredsListScreen(), callback=self.refresh_storages)
137
+
138
+ def refresh_storages(self, *args, **kwargs):
139
+ """
140
+ Refreshes the storage list by clearing the current storages and
141
+ reinitializing the storages list. This method is typically used
142
+ to update the storage list after changes in credentials or storage
143
+ configurations.
144
+ """
145
+ self.storage_list_sidebar.storages = {}
146
+ self.init_storages_list()
147
+
148
+ @work(thread=True)
149
+ async def init_storages_list(self) -> None:
150
+ """
151
+ Initializes the list of storages by fetching active access credentials
152
+ and retrieving storages for each credential. Updates the storage list
153
+ sidebar with the retrieved storages. Notifies the user in case of errors.
154
+
155
+ This method is asynchronous and should be awaited.
156
+
157
+ Flow:
158
+ 1. Fetch active access credentials using self.credentials_service.list.
159
+ 2. If no credentials are found, exit the method.
160
+ 3. For each credential, retrieve the corresponding provider service.
161
+ 4. Attempt to list storages using the provider service.
162
+ 5. Update the storage_list_sidebar with the retrieved storages.
163
+ 6. Handle exceptions by printing an error message and notifying the user.
164
+ """
165
+ self.reset_storage_content()
166
+ access_credentials = self.credentials_service.list(active_only=True)
167
+
168
+ if not access_credentials:
169
+ return
170
+
171
+ for credentials in access_credentials:
172
+ provider_service = get_provider_service_by_access_credentials(credentials)
173
+ if not provider_service:
174
+ self.notify(
175
+ f"Could not get storages list for {credentials.name}!",
176
+ severity="error",
177
+ )
178
+ continue
179
+ try:
180
+ storages = provider_service.list_storages()
181
+ self.storage_list_sidebar.storages = {
182
+ credentials.uuid: storages,
183
+ **self.storage_list_sidebar.storages,
184
+ }
185
+ except Exception:
186
+ self.notify(
187
+ f"Could not get storages list for {credentials.name}!",
188
+ severity="error",
189
+ )
190
+
191
+ @on(SelectStorageItem)
192
+ def on_select_storage_item(self, event: SelectStorageItem):
193
+ """
194
+ Handles the selection of a storage item by updating the storage content
195
+ with the selected item's details and retrieving its contents.
196
+
197
+ Args:
198
+ event (SelectStorageItem): The event containing details of the selected storage item.
199
+
200
+ Flow:
201
+ 1. Update storage_content with the path, storage name, and access credentials UUID from the event.
202
+ 2. Retrieve the provider service using the access credentials UUID.
203
+ 3. If the provider service is available, attempt to list storage items.
204
+ 4. Update storage_content with the retrieved storage items.
205
+ 5. Notify the user if an error occurs during the retrieval process.
206
+ """
207
+ self.refresh_storage_content(
208
+ event.access_credentials_uuid, event.name, event.path, event.prefix
209
+ )
210
+
211
+ @on(UploadRequest)
212
+ def on_upload_request(self, event: UploadRequest):
213
+ """
214
+ Handles file upload requests by opening a file system navigation modal.
215
+
216
+ This method is triggered when an UploadRequest event is received. It opens
217
+ a file system navigation modal to allow the user to select a file or directory
218
+ to upload, then calls the _upload_file method with the selected source path.
219
+
220
+ Args:
221
+ event (UploadRequest): The upload request event containing storage details
222
+ """
223
+ self.push_screen(
224
+ FileSystemNavigationModal(),
225
+ callback=lambda src: self._upload_file(
226
+ event.access_credentials_uuid, event.storage, event.path, src # type: ignore
227
+ ),
228
+ )
229
+
230
+ @on(DownloadRequest)
231
+ def on_download_request(self, event: DownloadRequest):
232
+ """
233
+ Handles file download requests by opening a storage action progress screen.
234
+
235
+ This method is triggered when a DownloadRequest event is received. It creates
236
+ a StorageActionProgressScreen to track and display the progress of the download
237
+ operation for the selected files.
238
+
239
+ Args:
240
+ event (DownloadRequest): The download request event containing file details
241
+ """
242
+ self.push_screen(
243
+ StorageActionProgressScreen(
244
+ storage_name=event.storage_name,
245
+ provider_service=get_provider_service_by_access_uuid(
246
+ event.access_credentials_uuid, self.credentials_service
247
+ ),
248
+ path=event.path,
249
+ keys=[
250
+ DownloadKey(display_name=key, uuid=generate_uuid(), path=key) # type: ignore
251
+ for key in event.keys
252
+ ],
253
+ action="download",
254
+ ),
255
+ callback=lambda x: self.after_bulk_operation_callback(
256
+ event.access_credentials_uuid, event.storage_name, event.path
257
+ ),
258
+ )
259
+
260
+ @on(DeleteRequest)
261
+ def on_delete_request(self, event: DeleteRequest):
262
+ """
263
+ Handles file deletion requests by opening a storage action progress screen.
264
+
265
+ This method is triggered when a DeleteRequest event is received. It creates
266
+ a StorageActionProgressScreen to track and display the progress of the delete
267
+ operation for the selected files.
268
+
269
+ Args:
270
+ event (DeleteRequest): The delete request event containing file details
271
+ """
272
+ self.push_screen(
273
+ StorageActionProgressScreen(
274
+ storage_name=event.storage_name,
275
+ provider_service=get_provider_service_by_access_uuid(
276
+ event.access_credentials_uuid, self.credentials_service
277
+ ),
278
+ path=event.path,
279
+ keys=[
280
+ DeleteKey(display_name=key, uuid=generate_uuid(), path=key) for key in event.keys # type: ignore
281
+ ],
282
+ action="delete",
283
+ ),
284
+ callback=lambda x: self.after_bulk_operation_callback(
285
+ event.access_credentials_uuid, event.storage_name, event.path
286
+ ),
287
+ )
288
+
289
+ def after_bulk_operation_callback(
290
+ self, access_credentials_uuid, storage_name, path
291
+ ):
292
+ """
293
+ Callback method executed after bulk operations (download, upload, delete) complete.
294
+
295
+ This method resets the storage content display and refreshes it with the latest
296
+ content from the specified storage path after a bulk operation completes.
297
+
298
+ Args:
299
+ access_credentials_uuid (str): UUID of the access credentials to use
300
+ storage_name (str): Name of the storage to display
301
+ path (str): Path within the storage to display
302
+ """
303
+ self.reset_storage_content()
304
+ self.refresh_storage_content(access_credentials_uuid, storage_name, path)
305
+
306
+ @on(UncheckFilesRequest)
307
+ def uncheck_files_request(self, event: UncheckFilesRequest):
308
+ """
309
+ Handles requests to uncheck selected files in the storage content view.
310
+
311
+ This method is triggered when an UncheckFilesRequest event is received.
312
+ It unchecks all files specified in the event and clears the selected files
313
+ tracking in the storage content widget.
314
+
315
+ Args:
316
+ event (UncheckFilesRequest): The uncheck request event containing file keys
317
+ """
318
+ for key_uuid in event.keys:
319
+ file_item = self.query_one(f"#{key_uuid}")
320
+ file_item.uncheck() # type: ignore
321
+ self.storage_content.selected_files = set()
322
+ self.storage_content.selected_files_n = 0
323
+
324
+ @on(PreviewRequest)
325
+ def on_preview_request(self, event: PreviewRequest):
326
+ """
327
+ Handles requests to preview file content.
328
+
329
+ This method is triggered when a PreviewRequest event is received. It attempts
330
+ to read the content of the specified file using the provider service and
331
+ displays it in a PreviewContentScreen if successful.
332
+
333
+ Args:
334
+ event (PreviewRequest): The preview request event containing file details
335
+
336
+ Note:
337
+ If an error occurs while reading the file content, a notification is shown
338
+ to the user and the preview is not displayed.
339
+ """
340
+ self.push_screen(
341
+ PreviewContentScreen(
342
+ storage_name=event.storage_name,
343
+ key=event.path,
344
+ access_credentials_uuid=event.access_credentials_uuid,
345
+ )
346
+ )
347
+
348
+ def reset_storage_content(self):
349
+ """
350
+ Resets the storage content attributes to their default state.
351
+
352
+ This method clears the current storage path, storage name, search prefix,
353
+ storage content, and access credentials UUID, effectively resetting the
354
+ storage content to an uninitialized state.
355
+ """
356
+ self.storage_content.path = None
357
+ self.storage_content.storage = None
358
+ self.storage_content.search_prefix = None
359
+ self.storage_content.storage_content = None
360
+ self.storage_content.access_credentials_uuid = ""
361
+ self.storage_content.selected_files = set()
362
+ self.storage_content.selected_files_n = 0
363
+ self.uncheck_files_request(UncheckFilesRequest(keys=[]))
364
+
365
+ def refresh_storage_content(
366
+ self, access_credentials_uuid, storage_name, path, prefix=None
367
+ ):
368
+ """
369
+ Refreshes the storage content display with items from the specified storage path.
370
+
371
+ This method updates the storage content widget with items from the specified
372
+ storage path and provider. It handles retrieving the storage items using the
373
+ provider service and updating the UI accordingly.
374
+
375
+ Args:
376
+ access_credentials_uuid (str): UUID of the access credentials to use
377
+ storage_name (str): Name of the storage to display
378
+ path (str): Path within the storage to display
379
+ prefix (str, optional): Filter prefix for storage items. Defaults to None.
380
+
381
+ Note:
382
+ If an error occurs while retrieving storage items, a notification is shown
383
+ to the user and the storage content remains unchanged.
384
+ """
385
+ self.storage_content.path = path.strip("/") if path else path
386
+ self.storage_content.storage = storage_name
387
+ self.storage_content.access_credentials_uuid = access_credentials_uuid
388
+ self.storage_content.search_prefix = prefix or ""
389
+
390
+ provider_service = get_provider_service_by_access_uuid(
391
+ access_credentials_uuid, self.credentials_service
392
+ )
393
+
394
+ if not provider_service:
395
+ self.notify("Could not extract storage content", severity="error")
396
+ return
397
+ params = {"storage": storage_name, "path": path or "", "prefix": prefix or ""}
398
+ try:
399
+ self.storage_content.storage_content = provider_service.list_storage_items(**params) # type: ignore
400
+ except ListStorageItemsException as e:
401
+ self.notify(
402
+ f"""Could not extract storage content
403
+ {str(e)}""",
404
+ severity="error",
405
+ )
406
+
407
+ def _upload_file(
408
+ self,
409
+ access_credentials_uuid: str,
410
+ storage_name: str,
411
+ path: str,
412
+ source_path: Path,
413
+ ) -> None:
414
+ """
415
+ Uploads a file to the specified storage.
416
+
417
+ This method handles the upload of a file to a cloud storage provider.
418
+ It creates an upload key, gets the provider service, and pushes a
419
+ progress screen to handle the upload operation.
420
+
421
+ Args:
422
+ access_credentials_uuid (str): The UUID of the access credentials used for authentication.
423
+ storage_name (str): The name of the storage where the file will be uploaded.
424
+ path (str): The destination path within the storage.
425
+ source_path (Path): The local path of the file to be uploaded.
426
+ file_system_service (FileSystemService, optional): Service for file system operations.
427
+ Defaults to Provide[DiContainer.file_system_service].
428
+
429
+ Returns:
430
+ None
431
+
432
+ Note:
433
+ If the source_path is None or the provider service is not available,
434
+ the method will return early without performing any upload.
435
+ """
436
+ # Validate input parameters
437
+ if not source_path:
438
+ self.notify("No file selected for upload", severity="error")
439
+ return
440
+
441
+ # Get the provider service
442
+ provider_service = get_provider_service_by_access_uuid(
443
+ access_credentials_uuid, self.credentials_service
444
+ )
445
+ if not provider_service:
446
+ self.notify("Could not get provider service for upload", severity="error")
447
+ return
448
+
449
+ # Create upload key
450
+ upload_key = UploadKey(
451
+ display_name=source_path.name,
452
+ uuid=generate_uuid(),
453
+ path=source_path,
454
+ dest_path=str(source_path.name),
455
+ )
456
+
457
+ # Push the upload progress screen
458
+ self.push_screen(
459
+ StorageActionProgressScreen(
460
+ storage_name=storage_name,
461
+ provider_service=provider_service,
462
+ path=path,
463
+ keys=[upload_key],
464
+ action="upload",
465
+ ),
466
+ callback=lambda x: self.after_bulk_operation_callback(
467
+ access_credentials_uuid, storage_name, path
468
+ ),
469
+ )
@@ -0,0 +1,12 @@
1
+ from dataclasses import dataclass
2
+ from typing import List
3
+
4
+ from textual.message import Message
5
+
6
+
7
+ @dataclass
8
+ class DeleteRequest(Message):
9
+ storage_name: str
10
+ access_credentials_uuid: str
11
+ path: str
12
+ keys: List[str]
@@ -0,0 +1,12 @@
1
+ from dataclasses import dataclass
2
+ from typing import List
3
+
4
+ from textual.message import Message
5
+
6
+
7
+ @dataclass
8
+ class DownloadRequest(Message):
9
+ storage_name: str
10
+ access_credentials_uuid: str
11
+ path: str
12
+ keys: List[str]
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass
2
+
3
+ from textual.message import Message
4
+
5
+
6
+ @dataclass
7
+ class PreviewRequest(Message):
8
+ storage_name: str
9
+ access_credentials_uuid: str
10
+ path: str
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass
2
+
3
+ from textual.message import Message
4
+
5
+
6
+ @dataclass
7
+ class ResizingRuleMove(Message):
8
+ orientation: str
9
+ delta: int
10
+ previous_component_id: str
11
+ next_component_id: str
12
+
13
+
14
+ @dataclass
15
+ class ResizingRuleSelect(Message):
16
+ id: str
17
+
18
+
19
+ @dataclass
20
+ class ResizingRuleRelease(Message):
21
+ id: str
@@ -0,0 +1,11 @@
1
+ from dataclasses import dataclass
2
+
3
+ from textual.message import Message
4
+
5
+
6
+ @dataclass
7
+ class SelectStorageItem(Message):
8
+ name: str
9
+ path: str | None = None
10
+ access_credentials_uuid: str | None = None
11
+ prefix: str | None = None
@@ -0,0 +1,8 @@
1
+ from dataclasses import dataclass
2
+
3
+ from textual.message import Message
4
+
5
+
6
+ @dataclass
7
+ class UncheckFilesRequest(Message):
8
+ keys: list[str]
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass
2
+
3
+ from textual.message import Message
4
+
5
+
6
+ @dataclass
7
+ class UploadRequest(Message):
8
+ access_credentials_uuid: str
9
+ storage: str
10
+ path: str | None
File without changes