Release SyncVito 1.0.17
This commit is contained in:
1 parent
8218cfd9d5
commit
f83a85ed40
10 files changed
+425
-29
No files matched your search
+145
-2
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
@@ -13,6 +14,7 @@ from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QFileDialog,
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
QHeaderView,
|
||||
@@ -40,6 +42,7 @@ from syncvito.dialogs import TaskDialog
|
||||
from syncvito.icons import app_icon, status_icon
|
||||
from syncvito.models import AppConfig, SyncDirection, SyncTask, TaskRuntimeState, TriggerMode
|
||||
from syncvito.state_store import StateStore
|
||||
from syncvito.task_transfer import TaskTransferError, export_task, export_tasks, import_task, import_tasks
|
||||
from syncvito.theme import apply_theme
|
||||
from syncvito.update_controller import UpdateController
|
||||
from syncvito.update_dialog import UpdateDialog
|
||||
@@ -251,6 +254,15 @@ class MainWindow(QMainWindow):
|
||||
edit_button = QPushButton("Bearbeiten")
|
||||
delete_button = QPushButton("Löschen")
|
||||
delete_button.setProperty("danger", True)
|
||||
transfer_button = QPushButton("Import / Export")
|
||||
transfer_menu = QMenu(transfer_button)
|
||||
transfer_menu.addSection("Exportieren")
|
||||
transfer_menu.addAction("Ausgewählte Aufgabe exportieren …", self._export_selected_task)
|
||||
transfer_menu.addAction("Alle Aufgaben exportieren …", self._export_all_tasks)
|
||||
transfer_menu.addSection("Importieren")
|
||||
transfer_menu.addAction("Einzelne Aufgabe laden …", self._import_single_task)
|
||||
transfer_menu.addAction("Alle Aufgaben ersetzen …", self._import_all_tasks)
|
||||
transfer_button.setMenu(transfer_menu)
|
||||
self.sync_selected_button = QPushButton("Auswahl synchronisieren")
|
||||
add_button.clicked.connect(self._add_task)
|
||||
edit_button.clicked.connect(self._edit_task)
|
||||
@@ -259,6 +271,7 @@ class MainWindow(QMainWindow):
|
||||
toolbar.addWidget(add_button)
|
||||
toolbar.addWidget(edit_button)
|
||||
toolbar.addWidget(delete_button)
|
||||
toolbar.addWidget(transfer_button)
|
||||
toolbar.addStretch(1)
|
||||
toolbar.addWidget(self.sync_selected_button)
|
||||
layout.addLayout(toolbar)
|
||||
@@ -567,6 +580,135 @@ class MainWindow(QMainWindow):
|
||||
StateStore().remove(task.id)
|
||||
self._persist_tasks("Aufgabe gelöscht")
|
||||
|
||||
def _export_selected_task(self) -> None:
|
||||
task = self._selected_task()
|
||||
if task is None:
|
||||
QMessageBox.information(self, "Keine Auswahl", "Bitte zuerst eine Aufgabe auswählen.")
|
||||
return
|
||||
safe_name = re.sub(r'[<>:"/\\|?*\x00-\x1f]', "_", task.name).strip(" .")[:80] or "Aufgabe"
|
||||
suggested_path = Path.home() / f"SyncVito-{safe_name}.json"
|
||||
filename, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Aufgabe exportieren",
|
||||
str(suggested_path),
|
||||
"JSON-Dateien (*.json)",
|
||||
)
|
||||
if not filename:
|
||||
return
|
||||
export_path = self._json_export_path(filename)
|
||||
try:
|
||||
export_task(export_path, task)
|
||||
except TaskTransferError as exc:
|
||||
QMessageBox.critical(self, "Export fehlgeschlagen", str(exc))
|
||||
self._append_log("Aufgabe exportiert", "Fehler", str(exc))
|
||||
return
|
||||
self._append_log("Aufgabe exportiert", "Erfolg", f"{task.name}: {export_path}")
|
||||
QMessageBox.information(self, "Export abgeschlossen", f"Die Aufgabe wurde gespeichert unter:\n{export_path}")
|
||||
|
||||
def _export_all_tasks(self) -> None:
|
||||
suggested_path = Path.home() / "SyncVito-Aufgaben.json"
|
||||
filename, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Alle Aufgaben exportieren",
|
||||
str(suggested_path),
|
||||
"JSON-Dateien (*.json)",
|
||||
)
|
||||
if not filename:
|
||||
return
|
||||
export_path = self._json_export_path(filename)
|
||||
try:
|
||||
export_tasks(export_path, self.config.tasks)
|
||||
except TaskTransferError as exc:
|
||||
QMessageBox.critical(self, "Export fehlgeschlagen", str(exc))
|
||||
self._append_log("Alle Aufgaben exportiert", "Fehler", str(exc))
|
||||
return
|
||||
detail = f"{len(self.config.tasks)} Aufgaben: {export_path}"
|
||||
self._append_log("Alle Aufgaben exportiert", "Erfolg", detail)
|
||||
QMessageBox.information(self, "Export abgeschlossen", f"Alle Aufgaben wurden gespeichert unter:\n{export_path}")
|
||||
|
||||
def _import_single_task(self) -> None:
|
||||
filename, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Einzelne Aufgabe laden",
|
||||
str(Path.home()),
|
||||
"JSON-Dateien (*.json);;Alle Dateien (*)",
|
||||
)
|
||||
if not filename:
|
||||
return
|
||||
try:
|
||||
task = import_task(Path(filename))
|
||||
except TaskTransferError as exc:
|
||||
QMessageBox.critical(self, "Import fehlgeschlagen", str(exc))
|
||||
self._append_log("Aufgabe importiert", "Fehler", str(exc))
|
||||
return
|
||||
|
||||
self.config.tasks.append(task)
|
||||
if not self._persist_tasks("Aufgabe importiert"):
|
||||
self.config.tasks.remove(task)
|
||||
self._refresh_tasks()
|
||||
return
|
||||
QMessageBox.information(self, "Import abgeschlossen", f"„{task.name}“ wurde als neue Aufgabe hinzugefügt.")
|
||||
|
||||
def _import_all_tasks(self) -> None:
|
||||
if self._has_pending_sync_work():
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Synchronisation läuft",
|
||||
"Alle Aufgaben können erst ersetzt werden, wenn keine Synchronisation läuft oder wartet.",
|
||||
)
|
||||
return
|
||||
filename, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Alle Aufgaben laden",
|
||||
str(Path.home()),
|
||||
"JSON-Dateien (*.json);;Alle Dateien (*)",
|
||||
)
|
||||
if not filename:
|
||||
return
|
||||
try:
|
||||
imported_tasks = import_tasks(Path(filename))
|
||||
except TaskTransferError as exc:
|
||||
QMessageBox.critical(self, "Import fehlgeschlagen", str(exc))
|
||||
self._append_log("Alle Aufgaben importiert", "Fehler", str(exc))
|
||||
return
|
||||
|
||||
answer = QMessageBox.question(
|
||||
self,
|
||||
"Alle Aufgaben ersetzen",
|
||||
f"Die Datei enthält {len(imported_tasks)} Aufgaben. "
|
||||
f"Damit werden alle {len(self.config.tasks)} vorhandenen Aufgaben ersetzt. Fortfahren?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
if answer != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
if self._has_pending_sync_work():
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Synchronisation läuft",
|
||||
"Während des Imports wurde eine Synchronisation gestartet. Bitte den Import danach wiederholen.",
|
||||
)
|
||||
return
|
||||
|
||||
previous_tasks = self.config.tasks[:]
|
||||
previous_queue = self.config.settings.pending_queue[:]
|
||||
self.config.tasks[:] = imported_tasks
|
||||
self.config.settings.pending_queue = []
|
||||
if not self._persist_tasks("Alle Aufgaben importiert"):
|
||||
self.config.tasks[:] = previous_tasks
|
||||
self.config.settings.pending_queue = previous_queue
|
||||
self._refresh_tasks()
|
||||
return
|
||||
QMessageBox.information(self, "Import abgeschlossen", f"{len(imported_tasks)} Aufgaben wurden geladen.")
|
||||
|
||||
@staticmethod
|
||||
def _json_export_path(filename: str) -> Path:
|
||||
path = Path(filename)
|
||||
return path if path.suffix else path.with_suffix(".json")
|
||||
|
||||
def _has_pending_sync_work(self) -> bool:
|
||||
return self.coordinator.is_running or bool(self.coordinator.queued_task_ids)
|
||||
|
||||
def _sync_selected(self) -> None:
|
||||
task = self._selected_task()
|
||||
if task is None:
|
||||
@@ -576,16 +718,17 @@ class MainWindow(QMainWindow):
|
||||
self.coordinator.set_paused(False)
|
||||
self.coordinator.enqueue(task.id)
|
||||
|
||||
def _persist_tasks(self, message: str) -> None:
|
||||
def _persist_tasks(self, message: str) -> bool:
|
||||
try:
|
||||
self.config_store.save(self.config)
|
||||
self.coordinator.refresh_task_states()
|
||||
self.coordinator.rebuild_monitors()
|
||||
except ConfigError as exc:
|
||||
QMessageBox.critical(self, "Speicherfehler", str(exc))
|
||||
return
|
||||
return False
|
||||
self._refresh_tasks()
|
||||
self._append_log("Aufgaben", "Erfolg", message)
|
||||
return True
|
||||
|
||||
def _save_settings(self) -> None:
|
||||
settings = self.config.settings
|
||||
|
||||
Reference in new issue
Block a user