fspachinko 0.0.2__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 (56) hide show
  1. fspachinko/__init__.py +6 -0
  2. fspachinko/_data/configs/fspachinko.json +60 -0
  3. fspachinko/_data/configs/logging.json +36 -0
  4. fspachinko/_data/icons/add_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  5. fspachinko/_data/icons/close_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  6. fspachinko/_data/icons/file_open_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  7. fspachinko/_data/icons/folder_open_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  8. fspachinko/_data/icons/icon.icns +0 -0
  9. fspachinko/_data/icons/icon.ico +0 -0
  10. fspachinko/_data/icons/play_arrow_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  11. fspachinko/_data/icons/remove_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  12. fspachinko/_data/icons/save_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  13. fspachinko/_data/icons/save_as_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  14. fspachinko/_data/icons/stop_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  15. fspachinko/_data/icons/sync_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg +1 -0
  16. fspachinko/_data/icons/windowIcon.png +0 -0
  17. fspachinko/cli/__init__.py +1 -0
  18. fspachinko/cli/__main__.py +19 -0
  19. fspachinko/cli/app.py +62 -0
  20. fspachinko/cli/observer.py +37 -0
  21. fspachinko/config/__init__.py +39 -0
  22. fspachinko/config/config.py +213 -0
  23. fspachinko/config/converter.py +163 -0
  24. fspachinko/config/schemas.py +96 -0
  25. fspachinko/core/__init__.py +20 -0
  26. fspachinko/core/builder.py +92 -0
  27. fspachinko/core/engine.py +129 -0
  28. fspachinko/core/quota.py +46 -0
  29. fspachinko/core/reporter.py +55 -0
  30. fspachinko/core/state.py +300 -0
  31. fspachinko/core/transfer.py +100 -0
  32. fspachinko/core/validator.py +70 -0
  33. fspachinko/core/walker.py +184 -0
  34. fspachinko/gui/__init__.py +1 -0
  35. fspachinko/gui/__main__.py +43 -0
  36. fspachinko/gui/actions.py +68 -0
  37. fspachinko/gui/centralwidget.py +70 -0
  38. fspachinko/gui/components.py +581 -0
  39. fspachinko/gui/mainwindow.py +153 -0
  40. fspachinko/gui/observer.py +54 -0
  41. fspachinko/gui/qthelpers.py +102 -0
  42. fspachinko/gui/settings.py +53 -0
  43. fspachinko/gui/uibuilder.py +127 -0
  44. fspachinko/gui/workers.py +56 -0
  45. fspachinko/utils/__init__.py +89 -0
  46. fspachinko/utils/constants.py +212 -0
  47. fspachinko/utils/helpers.py +143 -0
  48. fspachinko/utils/interfaces.py +35 -0
  49. fspachinko/utils/loggers.py +16 -0
  50. fspachinko/utils/paths.py +33 -0
  51. fspachinko/utils/timestamp.py +29 -0
  52. fspachinko-0.0.2.dist-info/METADATA +322 -0
  53. fspachinko-0.0.2.dist-info/RECORD +56 -0
  54. fspachinko-0.0.2.dist-info/WHEEL +4 -0
  55. fspachinko-0.0.2.dist-info/entry_points.txt +5 -0
  56. fspachinko-0.0.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,153 @@
1
+ """Main module."""
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING
5
+
6
+ from PySide6.QtCore import QSettings, Slot
7
+ from PySide6.QtWidgets import QFileDialog, QMainWindow, QStatusBar, QToolBar
8
+
9
+ from ..utils import GUIFileDialogFilter, GUILabel, GUIName, GUISettingsKey, GUITitle, get_stem_and_ext
10
+ from .actions import Actions
11
+ from .centralwidget import CentralWidget
12
+ from .qthelpers import set_qt_name
13
+ from .settings import ProfileManager
14
+
15
+ if TYPE_CHECKING:
16
+ from PySide6.QtGui import QCloseEvent
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class MainWindow(QMainWindow):
22
+ """Main application window."""
23
+
24
+ def __init__(self) -> None:
25
+ """Initialize the main window."""
26
+ super().__init__()
27
+ logger.debug("Initializing GUI")
28
+ self.central_widget = CentralWidget()
29
+ self.profiles = ProfileManager()
30
+ self.qsettings = QSettings()
31
+ self._actions = Actions()
32
+ self.setCentralWidget(self.central_widget)
33
+ self.init_connections()
34
+ self.init_menubar()
35
+ self.init_toolbar()
36
+ self.init_statusbar()
37
+ self.init_settings()
38
+
39
+ def init_connections(self) -> None:
40
+ """Initialize connections."""
41
+ self._actions.file.save.triggered.connect(self.save_profile)
42
+ self._actions.file.save_as.triggered.connect(self.save_profile_as_dialog)
43
+ self._actions.file.load.triggered.connect(self.open_profile_dialog)
44
+ self._actions.file.exit.triggered.connect(self.close)
45
+
46
+ self._actions.run.start.triggered.connect(self.central_widget.on_start)
47
+ self._actions.run.stop.triggered.connect(self.central_widget.on_stop)
48
+
49
+ def init_menubar(self) -> None:
50
+ """Initialize the menu bar."""
51
+ menubar = self.menuBar()
52
+ set_qt_name(menubar, GUIName.MENUBAR)
53
+
54
+ file_menu = menubar.addMenu(GUILabel.FILEMENU)
55
+ set_qt_name(file_menu, GUIName.FILEMENU)
56
+
57
+ file_menu.addAction(self._actions.file.save)
58
+ file_menu.addAction(self._actions.file.save_as)
59
+ file_menu.addAction(self._actions.file.load)
60
+ file_menu.addSeparator()
61
+ file_menu.addAction(self._actions.file.autosave)
62
+ file_menu.addSeparator()
63
+ file_menu.addAction(self._actions.file.exit)
64
+
65
+ run_menu = menubar.addMenu(GUILabel.RUNMENU)
66
+ set_qt_name(run_menu, GUIName.RUNMENU)
67
+
68
+ run_menu.addAction(self._actions.run.start)
69
+ run_menu.addAction(self._actions.run.stop)
70
+
71
+ def init_toolbar(self) -> None:
72
+ """Initialize the toolbar."""
73
+ toolbar = QToolBar(GUIName.TOOLBAR)
74
+ set_qt_name(toolbar, GUIName.TOOLBAR)
75
+
76
+ toolbar.addAction(self._actions.file.save)
77
+ toolbar.addAction(self._actions.file.save_as)
78
+ toolbar.addAction(self._actions.file.load)
79
+ toolbar.addAction(self._actions.file.autosave)
80
+ toolbar.addSeparator()
81
+ toolbar.addAction(self._actions.run.start)
82
+ toolbar.addAction(self._actions.run.stop)
83
+ toolbar.addSeparator()
84
+ toolbar.addAction(self._actions.file.exit)
85
+
86
+ self.addToolBar(toolbar)
87
+
88
+ def init_statusbar(self) -> None:
89
+ """Initialize the status bar."""
90
+ statusbar = QStatusBar(self, sizeGripEnabled=True)
91
+ set_qt_name(statusbar, GUIName.STATUSBAR)
92
+
93
+ self.setStatusBar(statusbar)
94
+
95
+ def init_settings(self) -> None:
96
+ """Initialize GUI settings manager."""
97
+ self.restoreGeometry(self.qsettings.value(GUISettingsKey.GEOMETRY))
98
+ self.restoreState(self.qsettings.value(GUISettingsKey.STATE))
99
+ self.profiles.set_current(str(self.qsettings.value(GUISettingsKey.PROFILE, "")))
100
+ self.profiles.open_profile(self)
101
+ self.reset_window_title()
102
+
103
+ @Slot()
104
+ def save_profile(self) -> None:
105
+ """Save the current GUI profile."""
106
+ self.profiles.save_profile(self)
107
+
108
+ @Slot()
109
+ def save_profile_as_dialog(self) -> None:
110
+ """Save a GUI profile via dialog."""
111
+ filename, _ = QFileDialog.getSaveFileName(
112
+ self,
113
+ GUITitle.SAVE_PROFILE,
114
+ self.profiles.get_current_profile_parent(),
115
+ GUIFileDialogFilter.JSON,
116
+ )
117
+ if filename:
118
+ self.profiles.set_current(filename)
119
+ self.save_profile()
120
+ self.reset_window_title()
121
+
122
+ @Slot()
123
+ def open_profile_dialog(self) -> None:
124
+ """Load a GUI profile via dialog."""
125
+ filename, _ = QFileDialog.getOpenFileName(
126
+ self,
127
+ GUITitle.OPEN_PROFILE,
128
+ self.profiles.get_current_profile_parent(),
129
+ GUIFileDialogFilter.JSON,
130
+ )
131
+ if filename:
132
+ self.profiles.set_current(filename)
133
+ self.profiles.open_profile(self)
134
+ self.reset_window_title()
135
+
136
+ @Slot()
137
+ def reset_window_title(self) -> None:
138
+ """Update the window title based on the current profile."""
139
+ profile_stem, _ = get_stem_and_ext(self.profiles.current_profile)
140
+ self.setWindowTitle(f"{profile_stem} - {GUITitle.WINDOW}")
141
+
142
+ def save_settings(self) -> None:
143
+ """Save GUI settings on close."""
144
+ self.qsettings.setValue(GUISettingsKey.GEOMETRY, self.saveGeometry())
145
+ self.qsettings.setValue(GUISettingsKey.STATE, self.saveState())
146
+ self.qsettings.setValue(GUISettingsKey.PROFILE, self.profiles.current_profile)
147
+
148
+ def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
149
+ """Handle window close event."""
150
+ self.save_settings()
151
+ if self._actions.file.autosave.isChecked():
152
+ self.save_profile()
153
+ super().closeEvent(event)
@@ -0,0 +1,54 @@
1
+ """Observer for GUI."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from PySide6.QtCore import QObject, Signal
6
+
7
+ from ..utils import Observer
8
+
9
+
10
+ class WorkerSignals(QObject):
11
+ """Qt worker signals."""
12
+
13
+ progress_total = Signal(int)
14
+ count_total = Signal()
15
+ progress = Signal(int)
16
+ finished = Signal()
17
+ log = Signal(str)
18
+ time = Signal()
19
+ count = Signal(int)
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class GuiObserver(Observer):
24
+ """GUI observer."""
25
+
26
+ signals: WorkerSignals
27
+
28
+ def on_progress_total(self, maximum: int) -> None:
29
+ """Emit total progress signal."""
30
+ self.signals.progress_total.emit(maximum)
31
+
32
+ def on_count_total(self) -> None:
33
+ """Emit total count signal."""
34
+ self.signals.count_total.emit()
35
+
36
+ def on_progress(self, maximum: int) -> None:
37
+ """Emit progress signal."""
38
+ self.signals.progress.emit(maximum)
39
+
40
+ def on_finished(self) -> None:
41
+ """Emit finished signal."""
42
+ self.signals.finished.emit()
43
+
44
+ def on_log(self, msg: str) -> None:
45
+ """Emit log message signal."""
46
+ self.signals.log.emit(msg)
47
+
48
+ def on_time(self) -> None:
49
+ """Emit time update signal."""
50
+ self.signals.time.emit()
51
+
52
+ def on_count(self, count: int) -> None:
53
+ """Emit count update signal."""
54
+ self.signals.count.emit(count)
@@ -0,0 +1,102 @@
1
+ """Helper functions for Qt GUI elements."""
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from PySide6.QtWidgets import (
6
+ QCheckBox,
7
+ QComboBox,
8
+ QDoubleSpinBox,
9
+ QGroupBox,
10
+ QLineEdit,
11
+ QRadioButton,
12
+ QSpinBox,
13
+ QWidget,
14
+ )
15
+
16
+ from ..utils import strtobool
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Iterator
20
+
21
+ from PySide6.QtCore import QObject
22
+ from PySide6.QtGui import QAction
23
+
24
+
25
+ def get_qt_classname(obj: QObject) -> str:
26
+ """Get the class name of a QObject."""
27
+ return str(obj.metaObject().className())
28
+
29
+
30
+ def set_qt_name(w: QWidget | QAction, name: str) -> None:
31
+ """Initialize a widget with a given object name."""
32
+ w.setObjectName(name)
33
+
34
+
35
+ def set_qt_tips(w: QWidget | QAction, tooltip: str, statustip: str = "") -> None:
36
+ """Set the tooltip and status tip for a widget."""
37
+ if not statustip:
38
+ statustip = f"{tooltip} | ({get_qt_classname(w)})"
39
+
40
+ w.setToolTip(tooltip)
41
+ w.setStatusTip(statustip)
42
+
43
+
44
+ def get_widget_value(widget: QWidget) -> Any:
45
+ """Retrieve the value of a widget based on its type.
46
+
47
+ Args:
48
+ widget (QWidget): The widget to retrieve the value from.
49
+
50
+ Returns:
51
+ Any: The value of the widget, or None if not applicable.
52
+
53
+ """
54
+ match widget:
55
+ case QLineEdit():
56
+ return widget.text()
57
+ case QComboBox():
58
+ return widget.currentIndex()
59
+ case QSpinBox() | QDoubleSpinBox():
60
+ return widget.value()
61
+ case QGroupBox() if not widget.isCheckable():
62
+ return None
63
+ case QCheckBox() | QRadioButton() | QGroupBox():
64
+ return widget.isChecked()
65
+ case _:
66
+ return None
67
+
68
+
69
+ def set_widget_value(widget: QWidget, val: Any) -> None:
70
+ """Set the value of a widget based on its type.
71
+
72
+ Args:
73
+ widget (QWidget): The widget to set the value for.
74
+ val (Any): The value to set.
75
+
76
+ """
77
+ match widget:
78
+ case QLineEdit():
79
+ widget.setText(val)
80
+ case QComboBox():
81
+ try:
82
+ index = int(val)
83
+ if 0 <= index < widget.count():
84
+ widget.setCurrentIndex(index)
85
+ except (ValueError, TypeError):
86
+ pass
87
+ case QSpinBox():
88
+ widget.setValue(int(val))
89
+ case QDoubleSpinBox():
90
+ widget.setValue(float(val))
91
+ case QCheckBox() | QRadioButton() | QGroupBox():
92
+ state = strtobool(val=val)
93
+ widget.setChecked(state)
94
+ case _:
95
+ return
96
+
97
+
98
+ def iter_custom_widget(w: QWidget) -> Iterator[tuple[str, QWidget]]:
99
+ """Iterate over valid child widgets."""
100
+ for child in w.findChildren(QWidget):
101
+ if (key := child.objectName()) and not key.startswith("qt_"):
102
+ yield key, child
@@ -0,0 +1,53 @@
1
+ """Settings handling for GUI."""
2
+
3
+ import os
4
+ from collections.abc import Sequence
5
+ from dataclasses import dataclass, field
6
+
7
+ from PySide6.QtWidgets import QComboBox, QWidget
8
+
9
+ from ..utils import Paths, load_json, save_json
10
+ from .qthelpers import get_widget_value, iter_custom_widget, set_widget_value
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class ProfileManager:
15
+ """Class for managing GUI profiles."""
16
+
17
+ current_profile: str = field(init=False)
18
+
19
+ def set_current(self, profile: str) -> None:
20
+ """Set the current profile name."""
21
+ self.current_profile = Paths.profile(profile)
22
+
23
+ def save_profile(self, parent: QWidget) -> None:
24
+ """Recursively save settings for all child widgets."""
25
+ data = {}
26
+ for key, child in iter_custom_widget(parent):
27
+ if (val := get_widget_value(child)) is None:
28
+ continue
29
+
30
+ data[key] = val
31
+ if isinstance(child, QComboBox):
32
+ items = [child.itemText(i) for i in range(child.count())]
33
+ data[f"{key}_items"] = items
34
+
35
+ save_json(self.current_profile, data)
36
+
37
+ def open_profile(self, parent: QWidget) -> None:
38
+ """Recursively load settings for all child widgets."""
39
+ data = load_json(self.current_profile)
40
+
41
+ for key, child in iter_custom_widget(parent):
42
+ if isinstance(child, QComboBox):
43
+ items = data.get(f"{key}_items")
44
+ if isinstance(items, Sequence):
45
+ child.clear()
46
+ child.addItems([str(i) for i in items])
47
+
48
+ if (val := data.get(key)) is not None:
49
+ set_widget_value(child, val)
50
+
51
+ def get_current_profile_parent(self) -> str:
52
+ """Get the parent directory of the current profile."""
53
+ return os.path.dirname(self.current_profile)
@@ -0,0 +1,127 @@
1
+ """Main module."""
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget
6
+
7
+ from ..config import ConfigModel
8
+ from .components import (
9
+ DestPathSelectorWidget,
10
+ DurationFilterWidget,
11
+ ExtensionsFilterWidget,
12
+ FileCountWidget,
13
+ FilenameWidget,
14
+ FolderCreatorWidget,
15
+ FolderSizeLimitWidget,
16
+ KeywordsFilterWidget,
17
+ ListIncludeExcludeFilterWidget,
18
+ LoggingWidget,
19
+ MinMaxFilterWidget,
20
+ OptionsWidget,
21
+ PathSelectorWidget,
22
+ ProgressWidget,
23
+ RootPathSelectorWidget,
24
+ SizeFilterWidget,
25
+ SizeLimitWidget,
26
+ TotalSizeLimitWidget,
27
+ TransferModeWidget,
28
+ )
29
+
30
+
31
+ @dataclass(slots=True)
32
+ class UIBuilder:
33
+ """Main Widget Builder."""
34
+
35
+ root: PathSelectorWidget = field(default_factory=RootPathSelectorWidget)
36
+ dest: PathSelectorWidget = field(default_factory=DestPathSelectorWidget)
37
+ filecount: FileCountWidget = field(default_factory=FileCountWidget)
38
+ folders: FolderCreatorWidget = field(default_factory=FolderCreatorWidget)
39
+ filename: FilenameWidget = field(default_factory=FilenameWidget)
40
+ transfermode: TransferModeWidget = field(default_factory=TransferModeWidget)
41
+ keywords: ListIncludeExcludeFilterWidget = field(default_factory=KeywordsFilterWidget)
42
+ extensions: ListIncludeExcludeFilterWidget = field(default_factory=ExtensionsFilterWidget)
43
+ filesize: MinMaxFilterWidget = field(default_factory=SizeFilterWidget)
44
+ duration: MinMaxFilterWidget = field(default_factory=DurationFilterWidget)
45
+ folder_size_limit: SizeLimitWidget = field(default_factory=FolderSizeLimitWidget)
46
+ total_size_limit: SizeLimitWidget = field(default_factory=TotalSizeLimitWidget)
47
+ options: OptionsWidget = field(default_factory=OptionsWidget)
48
+ progress: ProgressWidget = field(default_factory=ProgressWidget)
49
+ logging: LoggingWidget = field(default_factory=LoggingWidget)
50
+
51
+ def build_layout(self) -> QVBoxLayout:
52
+ """Set up the main UI layouts."""
53
+ path_widget = QWidget()
54
+ path_layout = QVBoxLayout(path_widget)
55
+ path_layout.addWidget(self.root)
56
+ path_layout.addWidget(self.dest)
57
+
58
+ # Left widget
59
+ output_widget = QWidget()
60
+ output_layout = QHBoxLayout(output_widget)
61
+ output_layout.addWidget(self.filecount)
62
+ output_layout.addWidget(self.transfermode)
63
+ output_layout.addWidget(self.folders)
64
+ output_layout.addWidget(self.filename)
65
+
66
+ filter_widget = QWidget()
67
+ filter_layout = QVBoxLayout(filter_widget)
68
+ filter_layout.addWidget(self.keywords)
69
+ filter_layout.addWidget(self.extensions)
70
+
71
+ left_widget = QWidget()
72
+ left_layout = QVBoxLayout(left_widget)
73
+ left_layout.addWidget(output_widget)
74
+ left_layout.addWidget(filter_widget)
75
+
76
+ # Right widget
77
+ size_widget = QWidget()
78
+ size_layout = QHBoxLayout(size_widget)
79
+ size_layout.addWidget(self.filesize)
80
+ size_layout.addWidget(self.duration)
81
+
82
+ size_limit_widget = QWidget()
83
+ size_limit_layout = QHBoxLayout(size_limit_widget)
84
+ size_limit_layout.addWidget(self.folder_size_limit)
85
+ size_limit_layout.addWidget(self.total_size_limit)
86
+
87
+ options_widget = QWidget()
88
+ options_layout = QHBoxLayout(options_widget)
89
+ options_layout.addWidget(self.options)
90
+
91
+ right_widget = QWidget()
92
+ right_layout = QVBoxLayout(right_widget)
93
+ right_layout.addWidget(size_widget)
94
+ right_layout.addWidget(size_limit_widget)
95
+ right_layout.addWidget(options_widget)
96
+
97
+ # Body widget
98
+ body_widget = QWidget()
99
+ body_layout = QHBoxLayout(body_widget)
100
+ body_layout.addWidget(left_widget)
101
+ body_layout.addWidget(right_widget)
102
+
103
+ # Assemble Main Layout
104
+ main_layout = QVBoxLayout()
105
+ main_layout.addWidget(path_widget)
106
+ main_layout.addWidget(body_widget)
107
+ main_layout.addWidget(self.logging)
108
+ main_layout.addWidget(self.progress)
109
+ return main_layout
110
+
111
+ def get_config(self) -> ConfigModel:
112
+ """Get the current configuration from all widgets."""
113
+ return ConfigModel(
114
+ root=self.root.get_config(),
115
+ dest=self.dest.get_config(),
116
+ filecount=self.filecount.get_config(),
117
+ folder=self.folders.get_config(),
118
+ filename=self.filename.get_config(),
119
+ transfermode=self.transfermode.get_config(),
120
+ keyword=self.keywords.get_config(),
121
+ extension=self.extensions.get_config(),
122
+ filesize=self.filesize.get_config(),
123
+ duration=self.duration.get_config(),
124
+ folder_size_limit=self.folder_size_limit.get_config(),
125
+ total_size_limit=self.total_size_limit.get_config(),
126
+ options=self.options.get_config(),
127
+ )
@@ -0,0 +1,56 @@
1
+ """Workers for GUI."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ from PySide6.QtCore import QThread, Slot
7
+
8
+ from ..core import build_engine
9
+ from .observer import GuiObserver, WorkerSignals
10
+
11
+ if TYPE_CHECKING:
12
+ from ..config import ConfigModel
13
+ from ..core import Engine
14
+
15
+
16
+ @dataclass(slots=True)
17
+ class MainWorker:
18
+ """Worker for running process."""
19
+
20
+ signals: WorkerSignals
21
+ engine: Engine
22
+
23
+ @classmethod
24
+ def from_config(cls, config: ConfigModel) -> MainWorker:
25
+ """Post-initialization tasks."""
26
+ signals = WorkerSignals()
27
+ observer = GuiObserver(signals)
28
+ engine = build_engine(config)
29
+ engine.set_observer(observer)
30
+ return cls(signals, engine)
31
+
32
+ def run(self) -> None:
33
+ """Run the process."""
34
+ self.engine.start()
35
+
36
+ def stop(self) -> None:
37
+ """Stop the process."""
38
+ self.engine.request_stop()
39
+
40
+
41
+ class MainThread(QThread):
42
+ """Worker thread for running process."""
43
+
44
+ def __init__(self, worker: MainWorker) -> None:
45
+ """Initialize the worker thread."""
46
+ super().__init__()
47
+ self.worker = worker
48
+
49
+ @Slot()
50
+ def run(self) -> None:
51
+ """Run the process."""
52
+ self.worker.run()
53
+
54
+ def stop(self) -> None:
55
+ """Stop the process."""
56
+ self.worker.stop()
@@ -0,0 +1,89 @@
1
+ """Utilities package."""
2
+
3
+ from .constants import (
4
+ DURATION_CMD,
5
+ FALSE_STRS,
6
+ INVALID_FILENAME_CHARS,
7
+ PERCENTAGE_100,
8
+ SIZE_MAP,
9
+ TIME_MAP,
10
+ TRUE_STRS,
11
+ WALKER_CACHE_LIMIT,
12
+ AppSetting,
13
+ BytesIn,
14
+ ByteUnit,
15
+ DefaultPath,
16
+ FileError,
17
+ FilenameTemplate,
18
+ FilenameTemplateMapKey,
19
+ GUIFileDialogFilter,
20
+ GUILabel,
21
+ GUIName,
22
+ GUISettingsKey,
23
+ GUITitle,
24
+ IconFilename,
25
+ ReStrFmt,
26
+ StateStatus,
27
+ TimeUnit,
28
+ TransferMode,
29
+ )
30
+ from .helpers import (
31
+ SafeDict,
32
+ are_paths_equal,
33
+ calc_unique_path_name,
34
+ convert_byte_to_human_readable_size,
35
+ convert_string_to_list,
36
+ get_duration,
37
+ get_stem_and_ext,
38
+ load_json,
39
+ remove_directory,
40
+ save_json,
41
+ strtobool,
42
+ )
43
+ from .interfaces import Observer
44
+ from .loggers import initialize_logging
45
+ from .paths import Paths
46
+ from .timestamp import DateTimeStamp
47
+
48
+ __all__ = [
49
+ "DURATION_CMD",
50
+ "FALSE_STRS",
51
+ "INVALID_FILENAME_CHARS",
52
+ "PERCENTAGE_100",
53
+ "SIZE_MAP",
54
+ "TIME_MAP",
55
+ "TRUE_STRS",
56
+ "WALKER_CACHE_LIMIT",
57
+ "AppSetting",
58
+ "ByteUnit",
59
+ "BytesIn",
60
+ "DateTimeStamp",
61
+ "DefaultPath",
62
+ "FileError",
63
+ "FilenameTemplate",
64
+ "FilenameTemplateMapKey",
65
+ "GUIFileDialogFilter",
66
+ "GUILabel",
67
+ "GUIName",
68
+ "GUISettingsKey",
69
+ "GUITitle",
70
+ "IconFilename",
71
+ "Observer",
72
+ "Paths",
73
+ "ReStrFmt",
74
+ "SafeDict",
75
+ "StateStatus",
76
+ "TimeUnit",
77
+ "TransferMode",
78
+ "are_paths_equal",
79
+ "calc_unique_path_name",
80
+ "convert_byte_to_human_readable_size",
81
+ "convert_string_to_list",
82
+ "get_duration",
83
+ "get_stem_and_ext",
84
+ "initialize_logging",
85
+ "load_json",
86
+ "remove_directory",
87
+ "save_json",
88
+ "strtobool",
89
+ ]