tenseleyflow/wulftp / 8901475

Browse files

some 'thread safety'

Authored by espadonne
SHA
89014751d098942a8cda31b3531a3194459ac54f
Parents
a92f02f
Tree
7fc8df9

1 changed file

StatusFile+-
M src/wulftp/wulftp.py 49 21
src/wulftp/wulftp.pymodified
@@ -1,20 +1,40 @@
11
 import os
22
 import sys
3
-import threading
43
 
54
 import paramiko
65
 import qtawesome as qta
76
 from dotenv import load_dotenv
87
 
98
 from PyQt6.QtCore import Qt, QSize
9
+from PyQt6.QtCore import pyqtSignal, QRunnable, QThreadPool
1010
 from PyQt6.QtWidgets import (
1111
     QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
1212
     QLineEdit, QToolButton, QPushButton, QListWidget, QFileDialog, QMessageBox, QFormLayout,
1313
 )
14
-
1514
 from .sftp_backend import SFTPBackend
1615
 
1716
 
17
+class UploadRunnable(QRunnable):
18
+    """
19
+    QRunnable for performing SFTP upload in a thread pool.
20
+    Emits finish or error signals on completion.
21
+    """
22
+    def __init__(self, backend, local_path, remote_path, finish_signal, error_signal):
23
+        super().__init__()
24
+        self.backend = backend
25
+        self.local_path = local_path
26
+        self.remote_path = remote_path
27
+        self.finish_signal = finish_signal
28
+        self.error_signal = error_signal
29
+
30
+    def run(self):
31
+        try:
32
+            self.backend.upload(self.local_path, self.remote_path)
33
+            self.finish_signal.emit()
34
+        except Exception as e:
35
+            self.error_signal.emit(str(e))
36
+
37
+
1838
 # fetch default host/port from .env
1939
 load_dotenv()
2040
 DEFAULT_HOST = os.getenv('WULFTP_HOST', '')
@@ -22,12 +42,21 @@ DEFAULT_PORT = int(os.getenv('WULFTP_PORT', '22'))
2242
 
2343
 
2444
 class WulFTPClient(QMainWindow):
45
+    # signals for thread-safe UI updates
46
+    remote_refreshed = pyqtSignal()
47
+    upload_error = pyqtSignal(str)
48
+
2549
     def __init__(self):
2650
         super().__init__()
2751
         self.backend = SFTPBackend()
2852
         self.sftp = None
2953
         self.init_ui()
3054
 
55
+        # setup thread pool and connect signals
56
+        self.threadpool = QThreadPool()
57
+        self.remote_refreshed.connect(self.refresh_remote)
58
+        self.upload_error.connect(self.show_error)
59
+
3160
     def init_ui(self):
3261
         """build and arrange ui elements."""
3362
         self.setWindowTitle('wulFTP POC')
@@ -152,16 +181,15 @@ class WulFTPClient(QMainWindow):
152181
             local_path = url.toLocalFile()
153182
             filename = os.path.basename(local_path)
154183
             remote_file = os.path.join(self.remote_path, filename)
155
-
156
-            def task(lp=local_path, rf=remote_file):
157
-                try:
158
-                    self.backend.upload(lp, rf)
159
-                    self.refresh_remote()
160
-                except Exception as e:
161
-                    self.show_error(str(e))
162
-
163
-            threading.Thread(target=task, daemon=True).start()
164
-
184
+            # schedule upload in thread pool
185
+            runnable = UploadRunnable(
186
+                self.backend,
187
+                local_path,
188
+                remote_file,
189
+                self.remote_refreshed,
190
+                self.upload_error
191
+            )
192
+            self.threadpool.start(runnable)
165193
         event.acceptProposedAction()
166194
 
167195
     def browse_key(self):
@@ -210,21 +238,21 @@ class WulFTPClient(QMainWindow):
210238
 
211239
     def browse_and_upload(self):
212240
         """open file dialog and upload the selected file to remote."""
213
-
214241
         path, _ = QFileDialog.getOpenFileName(self, 'select file to upload')
215242
         if not path:
216243
             return
217244
         filename = os.path.basename(path)
218245
         remote_file = os.path.join(self.remote_path, filename)
219246
 
220
-        def task():
221
-            try:
222
-                self.backend.upload(path, remote_file)
223
-                self.refresh_remote()
224
-            except Exception as e:
225
-                self.show_error(str(e))
226
-
227
-        threading.Thread(target=task, daemon=True).start()
247
+        # schedule file upload in thread pool
248
+        runnable = UploadRunnable(
249
+            self.backend,
250
+            path,
251
+            remote_file,
252
+            self.remote_refreshed,
253
+            self.upload_error
254
+        )
255
+        self.threadpool.start(runnable)
228256
 
229257
 
230258
 def main():