sortmeout 1.0.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.
- sortmeout/__init__.py +23 -0
- sortmeout/app.py +618 -0
- sortmeout/cli.py +550 -0
- sortmeout/config/__init__.py +11 -0
- sortmeout/config/manager.py +313 -0
- sortmeout/config/settings.py +201 -0
- sortmeout/core/__init__.py +21 -0
- sortmeout/core/action.py +889 -0
- sortmeout/core/condition.py +672 -0
- sortmeout/core/engine.py +421 -0
- sortmeout/core/rule.py +254 -0
- sortmeout/core/watcher.py +471 -0
- sortmeout/gui/__init__.py +10 -0
- sortmeout/gui/app.py +325 -0
- sortmeout/macos/__init__.py +19 -0
- sortmeout/macos/spotlight.py +337 -0
- sortmeout/macos/tags.py +308 -0
- sortmeout/macos/trash.py +449 -0
- sortmeout/utils/__init__.py +12 -0
- sortmeout/utils/file_info.py +363 -0
- sortmeout/utils/logger.py +214 -0
- sortmeout-1.0.0.dist-info/METADATA +302 -0
- sortmeout-1.0.0.dist-info/RECORD +27 -0
- sortmeout-1.0.0.dist-info/WHEEL +5 -0
- sortmeout-1.0.0.dist-info/entry_points.txt +3 -0
- sortmeout-1.0.0.dist-info/licenses/LICENSE +21 -0
- sortmeout-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration file management.
|
|
3
|
+
|
|
4
|
+
Handles reading and writing configuration files for SortMeOut.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
import appdirs
|
|
18
|
+
|
|
19
|
+
from sortmeout.config.settings import Settings
|
|
20
|
+
from sortmeout.utils.logger import get_logger
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
# Application identifiers
|
|
25
|
+
APP_NAME = "SortMeOut"
|
|
26
|
+
APP_AUTHOR = "SortMeOut"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_config_directory() -> Path:
|
|
30
|
+
"""Get the configuration directory path."""
|
|
31
|
+
config_dir = Path(appdirs.user_config_dir(APP_NAME, APP_AUTHOR))
|
|
32
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
return config_dir
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_data_directory() -> Path:
|
|
37
|
+
"""Get the data directory path."""
|
|
38
|
+
data_dir = Path(appdirs.user_data_dir(APP_NAME, APP_AUTHOR))
|
|
39
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
return data_dir
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ConfigManager:
|
|
44
|
+
"""
|
|
45
|
+
Manages configuration file operations.
|
|
46
|
+
|
|
47
|
+
Handles loading, saving, and migrating configuration files.
|
|
48
|
+
Supports both YAML and JSON formats.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, config_path: Optional[str] = None):
|
|
52
|
+
"""
|
|
53
|
+
Initialize configuration manager.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
config_path: Path to configuration file. If None, uses default.
|
|
57
|
+
"""
|
|
58
|
+
if config_path:
|
|
59
|
+
self.config_path = Path(config_path)
|
|
60
|
+
else:
|
|
61
|
+
self.config_path = get_config_directory() / "config.yaml"
|
|
62
|
+
|
|
63
|
+
self.config_dir = self.config_path.parent
|
|
64
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
|
|
66
|
+
# Backup directory
|
|
67
|
+
self.backup_dir = self.config_dir / "backups"
|
|
68
|
+
self.backup_dir.mkdir(exist_ok=True)
|
|
69
|
+
|
|
70
|
+
logger.debug("Config path: %s", self.config_path)
|
|
71
|
+
|
|
72
|
+
def load_config(self) -> Dict[str, Any]:
|
|
73
|
+
"""
|
|
74
|
+
Load configuration from file.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Configuration dictionary.
|
|
78
|
+
"""
|
|
79
|
+
if not self.config_path.exists():
|
|
80
|
+
logger.info("No config file found, using defaults")
|
|
81
|
+
return self._get_default_config()
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
with open(self.config_path, "r") as f:
|
|
85
|
+
if self.config_path.suffix in (".yaml", ".yml"):
|
|
86
|
+
config = yaml.safe_load(f) or {}
|
|
87
|
+
else:
|
|
88
|
+
config = json.load(f)
|
|
89
|
+
|
|
90
|
+
logger.info("Loaded config from: %s", self.config_path)
|
|
91
|
+
return config
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.error("Failed to load config: %s", e)
|
|
95
|
+
return self._get_default_config()
|
|
96
|
+
|
|
97
|
+
def save_config(self, config: Dict[str, Any]) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Save configuration to file.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
config: Configuration dictionary to save.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
True if save was successful.
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
# Create backup before saving
|
|
109
|
+
if self.config_path.exists():
|
|
110
|
+
self._create_backup()
|
|
111
|
+
|
|
112
|
+
# Add metadata
|
|
113
|
+
config["_metadata"] = {
|
|
114
|
+
"version": "1.0",
|
|
115
|
+
"updated_at": datetime.now().isoformat(),
|
|
116
|
+
"app_version": "0.1.0",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
with open(self.config_path, "w") as f:
|
|
120
|
+
if self.config_path.suffix in (".yaml", ".yml"):
|
|
121
|
+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
122
|
+
else:
|
|
123
|
+
json.dump(config, f, indent=2)
|
|
124
|
+
|
|
125
|
+
logger.info("Saved config to: %s", self.config_path)
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error("Failed to save config: %s", e)
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def load_settings(self) -> Settings:
|
|
133
|
+
"""
|
|
134
|
+
Load application settings.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Settings object.
|
|
138
|
+
"""
|
|
139
|
+
settings_path = self.config_dir / "settings.yaml"
|
|
140
|
+
|
|
141
|
+
if settings_path.exists():
|
|
142
|
+
try:
|
|
143
|
+
with open(settings_path, "r") as f:
|
|
144
|
+
data = yaml.safe_load(f) or {}
|
|
145
|
+
return Settings.from_dict(data)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error("Failed to load settings: %s", e)
|
|
148
|
+
|
|
149
|
+
return Settings()
|
|
150
|
+
|
|
151
|
+
def save_settings(self, settings: Settings) -> bool:
|
|
152
|
+
"""
|
|
153
|
+
Save application settings.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
settings: Settings object to save.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
True if save was successful.
|
|
160
|
+
"""
|
|
161
|
+
settings_path = self.config_dir / "settings.yaml"
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
with open(settings_path, "w") as f:
|
|
165
|
+
yaml.dump(settings.to_dict(), f, default_flow_style=False)
|
|
166
|
+
return True
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.error("Failed to save settings: %s", e)
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
def _get_default_config(self) -> Dict[str, Any]:
|
|
172
|
+
"""Get default configuration."""
|
|
173
|
+
return {
|
|
174
|
+
"folders": [],
|
|
175
|
+
"_metadata": {
|
|
176
|
+
"version": "1.0",
|
|
177
|
+
"created_at": datetime.now().isoformat(),
|
|
178
|
+
"app_version": "0.1.0",
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
def _create_backup(self) -> None:
|
|
183
|
+
"""Create a backup of the current config file."""
|
|
184
|
+
if not self.config_path.exists():
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
188
|
+
backup_name = f"config_backup_{timestamp}{self.config_path.suffix}"
|
|
189
|
+
backup_path = self.backup_dir / backup_name
|
|
190
|
+
|
|
191
|
+
shutil.copy2(self.config_path, backup_path)
|
|
192
|
+
logger.debug("Created config backup: %s", backup_path)
|
|
193
|
+
|
|
194
|
+
# Keep only last 10 backups
|
|
195
|
+
self._cleanup_old_backups()
|
|
196
|
+
|
|
197
|
+
def _cleanup_old_backups(self, keep: int = 10) -> None:
|
|
198
|
+
"""Remove old backup files."""
|
|
199
|
+
backups = sorted(self.backup_dir.glob("config_backup_*"))
|
|
200
|
+
|
|
201
|
+
if len(backups) > keep:
|
|
202
|
+
for backup in backups[:-keep]:
|
|
203
|
+
backup.unlink()
|
|
204
|
+
logger.debug("Removed old backup: %s", backup)
|
|
205
|
+
|
|
206
|
+
def restore_backup(self, backup_name: Optional[str] = None) -> bool:
|
|
207
|
+
"""
|
|
208
|
+
Restore configuration from a backup.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
backup_name: Name of backup file. If None, uses most recent.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
True if restore was successful.
|
|
215
|
+
"""
|
|
216
|
+
if backup_name:
|
|
217
|
+
backup_path = self.backup_dir / backup_name
|
|
218
|
+
else:
|
|
219
|
+
backups = sorted(self.backup_dir.glob("config_backup_*"))
|
|
220
|
+
if not backups:
|
|
221
|
+
logger.warning("No backups found")
|
|
222
|
+
return False
|
|
223
|
+
backup_path = backups[-1]
|
|
224
|
+
|
|
225
|
+
if not backup_path.exists():
|
|
226
|
+
logger.error("Backup not found: %s", backup_path)
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
shutil.copy2(backup_path, self.config_path)
|
|
231
|
+
logger.info("Restored config from: %s", backup_path)
|
|
232
|
+
return True
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.error("Failed to restore backup: %s", e)
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
def list_backups(self) -> list:
|
|
238
|
+
"""List available backup files."""
|
|
239
|
+
return sorted([b.name for b in self.backup_dir.glob("config_backup_*")])
|
|
240
|
+
|
|
241
|
+
def export_config(self, output_path: str, format: str = "yaml") -> bool:
|
|
242
|
+
"""
|
|
243
|
+
Export configuration to a file.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
output_path: Path to export to.
|
|
247
|
+
format: Export format (yaml or json).
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
True if export was successful.
|
|
251
|
+
"""
|
|
252
|
+
config = self.load_config()
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
with open(output_path, "w") as f:
|
|
256
|
+
if format == "yaml":
|
|
257
|
+
yaml.dump(config, f, default_flow_style=False)
|
|
258
|
+
else:
|
|
259
|
+
json.dump(config, f, indent=2)
|
|
260
|
+
|
|
261
|
+
logger.info("Exported config to: %s", output_path)
|
|
262
|
+
return True
|
|
263
|
+
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error("Failed to export config: %s", e)
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
def import_config(self, input_path: str, merge: bool = False) -> bool:
|
|
269
|
+
"""
|
|
270
|
+
Import configuration from a file.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
input_path: Path to import from.
|
|
274
|
+
merge: Merge with existing config instead of replacing.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
True if import was successful.
|
|
278
|
+
"""
|
|
279
|
+
try:
|
|
280
|
+
with open(input_path, "r") as f:
|
|
281
|
+
if input_path.endswith((".yaml", ".yml")):
|
|
282
|
+
imported = yaml.safe_load(f) or {}
|
|
283
|
+
else:
|
|
284
|
+
imported = json.load(f)
|
|
285
|
+
|
|
286
|
+
if merge:
|
|
287
|
+
current = self.load_config()
|
|
288
|
+
# Merge folders
|
|
289
|
+
current_folders = {f["path"]: f for f in current.get("folders", [])}
|
|
290
|
+
for folder in imported.get("folders", []):
|
|
291
|
+
current_folders[folder["path"]] = folder
|
|
292
|
+
current["folders"] = list(current_folders.values())
|
|
293
|
+
config = current
|
|
294
|
+
else:
|
|
295
|
+
config = imported
|
|
296
|
+
|
|
297
|
+
return self.save_config(config)
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.error("Failed to import config: %s", e)
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
def reset_config(self) -> bool:
|
|
304
|
+
"""
|
|
305
|
+
Reset configuration to defaults.
|
|
306
|
+
|
|
307
|
+
Creates a backup before resetting.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
True if reset was successful.
|
|
311
|
+
"""
|
|
312
|
+
self._create_backup()
|
|
313
|
+
return self.save_config(self._get_default_config())
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Application settings.
|
|
3
|
+
|
|
4
|
+
User-configurable settings for SortMeOut behavior.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class TrashSettings:
|
|
15
|
+
"""Settings for trash management."""
|
|
16
|
+
enabled: bool = False
|
|
17
|
+
max_age_days: int = 30
|
|
18
|
+
max_size_gb: float = 10.0
|
|
19
|
+
app_sweep_enabled: bool = True
|
|
20
|
+
app_sweep_prompt: bool = True # Ask before cleaning app files
|
|
21
|
+
|
|
22
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
23
|
+
return {
|
|
24
|
+
"enabled": self.enabled,
|
|
25
|
+
"max_age_days": self.max_age_days,
|
|
26
|
+
"max_size_gb": self.max_size_gb,
|
|
27
|
+
"app_sweep_enabled": self.app_sweep_enabled,
|
|
28
|
+
"app_sweep_prompt": self.app_sweep_prompt,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TrashSettings":
|
|
33
|
+
return cls(
|
|
34
|
+
enabled=data.get("enabled", False),
|
|
35
|
+
max_age_days=data.get("max_age_days", 30),
|
|
36
|
+
max_size_gb=data.get("max_size_gb", 10.0),
|
|
37
|
+
app_sweep_enabled=data.get("app_sweep_enabled", True),
|
|
38
|
+
app_sweep_prompt=data.get("app_sweep_prompt", True),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class NotificationSettings:
|
|
44
|
+
"""Settings for notifications."""
|
|
45
|
+
enabled: bool = True
|
|
46
|
+
show_rule_matches: bool = False
|
|
47
|
+
show_errors: bool = True
|
|
48
|
+
show_summary: bool = True
|
|
49
|
+
summary_interval_minutes: int = 60
|
|
50
|
+
sound_enabled: bool = True
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
53
|
+
return {
|
|
54
|
+
"enabled": self.enabled,
|
|
55
|
+
"show_rule_matches": self.show_rule_matches,
|
|
56
|
+
"show_errors": self.show_errors,
|
|
57
|
+
"show_summary": self.show_summary,
|
|
58
|
+
"summary_interval_minutes": self.summary_interval_minutes,
|
|
59
|
+
"sound_enabled": self.sound_enabled,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def from_dict(cls, data: Dict[str, Any]) -> "NotificationSettings":
|
|
64
|
+
return cls(
|
|
65
|
+
enabled=data.get("enabled", True),
|
|
66
|
+
show_rule_matches=data.get("show_rule_matches", False),
|
|
67
|
+
show_errors=data.get("show_errors", True),
|
|
68
|
+
show_summary=data.get("show_summary", True),
|
|
69
|
+
summary_interval_minutes=data.get("summary_interval_minutes", 60),
|
|
70
|
+
sound_enabled=data.get("sound_enabled", True),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class LoggingSettings:
|
|
76
|
+
"""Settings for logging."""
|
|
77
|
+
level: str = "INFO"
|
|
78
|
+
file_logging: bool = True
|
|
79
|
+
max_log_size_mb: int = 10
|
|
80
|
+
backup_count: int = 5
|
|
81
|
+
action_logging: bool = True
|
|
82
|
+
|
|
83
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
84
|
+
return {
|
|
85
|
+
"level": self.level,
|
|
86
|
+
"file_logging": self.file_logging,
|
|
87
|
+
"max_log_size_mb": self.max_log_size_mb,
|
|
88
|
+
"backup_count": self.backup_count,
|
|
89
|
+
"action_logging": self.action_logging,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def from_dict(cls, data: Dict[str, Any]) -> "LoggingSettings":
|
|
94
|
+
return cls(
|
|
95
|
+
level=data.get("level", "INFO"),
|
|
96
|
+
file_logging=data.get("file_logging", True),
|
|
97
|
+
max_log_size_mb=data.get("max_log_size_mb", 10),
|
|
98
|
+
backup_count=data.get("backup_count", 5),
|
|
99
|
+
action_logging=data.get("action_logging", True),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class WatcherSettings:
|
|
105
|
+
"""Settings for file watching."""
|
|
106
|
+
latency_seconds: float = 0.5
|
|
107
|
+
debounce_seconds: float = 0.5
|
|
108
|
+
ignore_hidden_files: bool = True
|
|
109
|
+
ignore_system_files: bool = True
|
|
110
|
+
custom_ignore_patterns: List[str] = field(default_factory=list)
|
|
111
|
+
|
|
112
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
113
|
+
return {
|
|
114
|
+
"latency_seconds": self.latency_seconds,
|
|
115
|
+
"debounce_seconds": self.debounce_seconds,
|
|
116
|
+
"ignore_hidden_files": self.ignore_hidden_files,
|
|
117
|
+
"ignore_system_files": self.ignore_system_files,
|
|
118
|
+
"custom_ignore_patterns": self.custom_ignore_patterns,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def from_dict(cls, data: Dict[str, Any]) -> "WatcherSettings":
|
|
123
|
+
return cls(
|
|
124
|
+
latency_seconds=data.get("latency_seconds", 0.5),
|
|
125
|
+
debounce_seconds=data.get("debounce_seconds", 0.5),
|
|
126
|
+
ignore_hidden_files=data.get("ignore_hidden_files", True),
|
|
127
|
+
ignore_system_files=data.get("ignore_system_files", True),
|
|
128
|
+
custom_ignore_patterns=data.get("custom_ignore_patterns", []),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class Settings:
|
|
134
|
+
"""
|
|
135
|
+
Main application settings.
|
|
136
|
+
|
|
137
|
+
Contains all user-configurable settings for SortMeOut.
|
|
138
|
+
"""
|
|
139
|
+
# General
|
|
140
|
+
start_at_login: bool = False
|
|
141
|
+
show_menu_bar_icon: bool = True
|
|
142
|
+
preview_mode: bool = False
|
|
143
|
+
confirm_destructive_actions: bool = True
|
|
144
|
+
|
|
145
|
+
# Subsystems
|
|
146
|
+
trash: TrashSettings = field(default_factory=TrashSettings)
|
|
147
|
+
notifications: NotificationSettings = field(default_factory=NotificationSettings)
|
|
148
|
+
logging: LoggingSettings = field(default_factory=LoggingSettings)
|
|
149
|
+
watcher: WatcherSettings = field(default_factory=WatcherSettings)
|
|
150
|
+
|
|
151
|
+
# UI preferences
|
|
152
|
+
theme: str = "system" # system, light, dark
|
|
153
|
+
language: str = "en"
|
|
154
|
+
show_preview_on_hover: bool = True
|
|
155
|
+
|
|
156
|
+
# Advanced
|
|
157
|
+
max_concurrent_actions: int = 5
|
|
158
|
+
action_timeout_seconds: int = 300
|
|
159
|
+
retry_failed_actions: bool = False
|
|
160
|
+
retry_count: int = 3
|
|
161
|
+
|
|
162
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
163
|
+
"""Convert settings to dictionary."""
|
|
164
|
+
return {
|
|
165
|
+
"start_at_login": self.start_at_login,
|
|
166
|
+
"show_menu_bar_icon": self.show_menu_bar_icon,
|
|
167
|
+
"preview_mode": self.preview_mode,
|
|
168
|
+
"confirm_destructive_actions": self.confirm_destructive_actions,
|
|
169
|
+
"trash": self.trash.to_dict(),
|
|
170
|
+
"notifications": self.notifications.to_dict(),
|
|
171
|
+
"logging": self.logging.to_dict(),
|
|
172
|
+
"watcher": self.watcher.to_dict(),
|
|
173
|
+
"theme": self.theme,
|
|
174
|
+
"language": self.language,
|
|
175
|
+
"show_preview_on_hover": self.show_preview_on_hover,
|
|
176
|
+
"max_concurrent_actions": self.max_concurrent_actions,
|
|
177
|
+
"action_timeout_seconds": self.action_timeout_seconds,
|
|
178
|
+
"retry_failed_actions": self.retry_failed_actions,
|
|
179
|
+
"retry_count": self.retry_count,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Settings":
|
|
184
|
+
"""Create settings from dictionary."""
|
|
185
|
+
return cls(
|
|
186
|
+
start_at_login=data.get("start_at_login", False),
|
|
187
|
+
show_menu_bar_icon=data.get("show_menu_bar_icon", True),
|
|
188
|
+
preview_mode=data.get("preview_mode", False),
|
|
189
|
+
confirm_destructive_actions=data.get("confirm_destructive_actions", True),
|
|
190
|
+
trash=TrashSettings.from_dict(data.get("trash", {})),
|
|
191
|
+
notifications=NotificationSettings.from_dict(data.get("notifications", {})),
|
|
192
|
+
logging=LoggingSettings.from_dict(data.get("logging", {})),
|
|
193
|
+
watcher=WatcherSettings.from_dict(data.get("watcher", {})),
|
|
194
|
+
theme=data.get("theme", "system"),
|
|
195
|
+
language=data.get("language", "en"),
|
|
196
|
+
show_preview_on_hover=data.get("show_preview_on_hover", True),
|
|
197
|
+
max_concurrent_actions=data.get("max_concurrent_actions", 5),
|
|
198
|
+
action_timeout_seconds=data.get("action_timeout_seconds", 300),
|
|
199
|
+
retry_failed_actions=data.get("retry_failed_actions", False),
|
|
200
|
+
retry_count=data.get("retry_count", 3),
|
|
201
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core module for SortMeOut.
|
|
3
|
+
|
|
4
|
+
Contains the fundamental building blocks of the file automation system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from sortmeout.core.rule import Rule
|
|
8
|
+
from sortmeout.core.condition import Condition, ConditionGroup
|
|
9
|
+
from sortmeout.core.action import Action
|
|
10
|
+
from sortmeout.core.watcher import FolderWatcher, WatcherManager
|
|
11
|
+
from sortmeout.core.engine import RuleEngine
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Rule",
|
|
15
|
+
"Condition",
|
|
16
|
+
"ConditionGroup",
|
|
17
|
+
"Action",
|
|
18
|
+
"FolderWatcher",
|
|
19
|
+
"WatcherManager",
|
|
20
|
+
"RuleEngine",
|
|
21
|
+
]
|