Local save is synchronous (data safe on disk before returning). Network upload (file, DAG, BOM) runs in a background QThread. New files: - silo_upload_queue.py: UploadTask dataclass + UploadWorker(QThread) with coalescing, cancel, shutdown, and Qt signals - silo_status_widget.py: SyncStatusWidget for status bar feedback Modified: - silo_commands.py: _extract_dag_data, _extract_bom_data, _enqueue_upload helpers; Silo_Save and Silo_Commit use async enqueue - silo_origin.py: saveDocument() saves locally then enqueues async upload - InitGui.py: upload queue init at 700ms, shutdown handler via atexit Thread safety: worker never accesses App.Document or Qt widgets; all data pre-extracted on main thread as plain dicts/strings. Queue uses threading.Lock; signals cross thread boundary via Qt.
62 lines
2.1 KiB
Python
62 lines
2.1 KiB
Python
"""Status bar widget for the Silo upload queue.
|
|
|
|
Shows sync progress in the main window status bar:
|
|
- Hidden when idle
|
|
- "Syncing PN-001..." during upload
|
|
- "Synced PN-001 r3" briefly on success
|
|
- "Sync failed: PN-001" in red on failure
|
|
"""
|
|
|
|
from PySide6.QtCore import QTimer
|
|
from PySide6.QtWidgets import QLabel
|
|
|
|
|
|
class SyncStatusWidget(QLabel):
|
|
"""Compact status bar indicator for background Silo uploads."""
|
|
|
|
_FADE_MS = 4000 # how long the success message stays visible
|
|
|
|
def __init__(self, queue, parent=None):
|
|
super().__init__(parent)
|
|
self.setVisible(False)
|
|
self._queue = queue
|
|
self._fade_timer = QTimer(self)
|
|
self._fade_timer.setSingleShot(True)
|
|
self._fade_timer.timeout.connect(self._hide)
|
|
|
|
queue.upload_started.connect(self._on_started)
|
|
queue.upload_finished.connect(self._on_finished)
|
|
queue.upload_failed.connect(self._on_failed)
|
|
|
|
# -- slots ---------------------------------------------------------------
|
|
|
|
def _on_started(self, doc_name: str, part_number: str) -> None:
|
|
pending = self._queue.pending_count
|
|
if pending > 0:
|
|
self.setText(f"Syncing {part_number} (+{pending} queued)...")
|
|
else:
|
|
self.setText(f"Syncing {part_number}...")
|
|
self.setStyleSheet("")
|
|
self._fade_timer.stop()
|
|
self.setVisible(True)
|
|
|
|
def _on_finished(self, doc_name: str, part_number: str, revision: int) -> None:
|
|
pending = self._queue.pending_count
|
|
if pending > 0:
|
|
self.setText(f"Synced {part_number} r{revision} ({pending} remaining)")
|
|
else:
|
|
self.setText(f"Synced {part_number} r{revision}")
|
|
self.setStyleSheet("")
|
|
self._fade_timer.start(self._FADE_MS)
|
|
|
|
def _on_failed(self, doc_name: str, part_number: str, error: str) -> None:
|
|
self.setText(f"Sync failed: {part_number}")
|
|
self.setToolTip(error)
|
|
self.setStyleSheet("color: #f38ba8;") # Catppuccin Mocha red
|
|
self._fade_timer.stop()
|
|
self.setVisible(True)
|
|
|
|
def _hide(self) -> None:
|
|
self.setVisible(False)
|
|
self.setToolTip("")
|