334 lines
11 KiB
Python
334 lines
11 KiB
Python
# Description: Universal Import/Export tool for EU-Utility
|
|
# Author: LemonNexus
|
|
# Version: 1.0.0
|
|
|
|
"""
|
|
Universal Import/Export tool for EU-Utility.
|
|
Export and import all plugin data for backup and migration.
|
|
"""
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
QPushButton, QFileDialog, QProgressBar,
|
|
QListWidget, QListWidgetItem, QMessageBox, QComboBox
|
|
)
|
|
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
|
import json
|
|
import zipfile
|
|
import os
|
|
from datetime import datetime
|
|
from plugins.base_plugin import BasePlugin
|
|
|
|
|
|
class ExportImportWorker(QThread):
|
|
"""Background worker for export/import operations."""
|
|
progress = pyqtSignal(int)
|
|
status = pyqtSignal(str)
|
|
finished_signal = pyqtSignal(bool, str)
|
|
|
|
def __init__(self, operation, data_store, path, selected_plugins=None):
|
|
super().__init__()
|
|
self.operation = operation
|
|
self.data_store = data_store
|
|
self.path = path
|
|
self.selected_plugins = selected_plugins or []
|
|
|
|
def run(self):
|
|
try:
|
|
if self.operation == "export":
|
|
self._do_export()
|
|
else:
|
|
self._do_import()
|
|
except Exception as e:
|
|
self.finished_signal.emit(False, str(e))
|
|
|
|
def _do_export(self):
|
|
"""Export data to zip file."""
|
|
self.status.emit("Gathering plugin data...")
|
|
|
|
# Get all plugin data
|
|
all_data = {}
|
|
plugin_keys = self.data_store.get_all_keys("*")
|
|
|
|
total = len(plugin_keys)
|
|
for i, key in enumerate(plugin_keys):
|
|
# Check if filtered
|
|
plugin_name = key.split('.')[0] if '.' in key else key
|
|
if self.selected_plugins and plugin_name not in self.selected_plugins:
|
|
continue
|
|
|
|
data = self.data_store.load(key, None)
|
|
if data is not None:
|
|
all_data[key] = data
|
|
|
|
progress = int((i + 1) / total * 50)
|
|
self.progress.emit(progress)
|
|
|
|
self.status.emit("Creating export archive...")
|
|
|
|
# Create zip file
|
|
with zipfile.ZipFile(self.path, 'w', zipfile.ZIP_DEFLATED) as zf:
|
|
# Add metadata
|
|
metadata = {
|
|
'version': '2.0.0',
|
|
'exported_at': datetime.now().isoformat(),
|
|
'plugin_count': len(set(k.split('.')[0] for k in all_data.keys())),
|
|
}
|
|
zf.writestr('metadata.json', json.dumps(metadata, indent=2))
|
|
|
|
# Add data
|
|
zf.writestr('data.json', json.dumps(all_data, indent=2))
|
|
|
|
self.progress.emit(75)
|
|
|
|
self.progress.emit(100)
|
|
self.finished_signal.emit(True, f"Exported {len(all_data)} data items")
|
|
|
|
def _do_import(self):
|
|
"""Import data from zip file."""
|
|
self.status.emit("Reading archive...")
|
|
|
|
with zipfile.ZipFile(self.path, 'r') as zf:
|
|
# Verify metadata
|
|
if 'metadata.json' not in zf.namelist():
|
|
raise ValueError("Invalid export file: missing metadata")
|
|
|
|
metadata = json.loads(zf.read('metadata.json'))
|
|
self.status.emit(f"Importing from version {metadata.get('version', 'unknown')}...")
|
|
|
|
# Load data
|
|
data = json.loads(zf.read('data.json'))
|
|
self.progress.emit(25)
|
|
|
|
# Import
|
|
total = len(data)
|
|
for i, (key, value) in enumerate(data.items()):
|
|
# Check if filtered
|
|
plugin_name = key.split('.')[0] if '.' in key else key
|
|
if self.selected_plugins and plugin_name not in self.selected_plugins:
|
|
continue
|
|
|
|
self.data_store.save_raw(key, value)
|
|
progress = 25 + int((i + 1) / total * 75)
|
|
self.progress.emit(progress)
|
|
|
|
self.finished_signal.emit(True, f"Imported {len(data)} data items")
|
|
|
|
|
|
class ImportExportPlugin(BasePlugin):
|
|
"""Universal import/export tool for EU-Utility data."""
|
|
|
|
name = "Import/Export"
|
|
version = "1.0.0"
|
|
author = "LemonNexus"
|
|
description = "Backup and restore all plugin data"
|
|
icon = "archive"
|
|
|
|
def initialize(self):
|
|
"""Initialize plugin."""
|
|
self.worker = None
|
|
self.default_export_dir = os.path.expanduser("~/Documents/EU-Utility/Backups")
|
|
os.makedirs(self.default_export_dir, exist_ok=True)
|
|
|
|
def get_ui(self):
|
|
"""Create plugin UI."""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setSpacing(15)
|
|
|
|
# Title
|
|
title = QLabel("Import / Export Data")
|
|
title.setStyleSheet("font-size: 18px; font-weight: bold;")
|
|
layout.addWidget(title)
|
|
|
|
# Description
|
|
desc = QLabel("Backup and restore all your EU-Utility data")
|
|
desc.setStyleSheet("color: rgba(255,255,255,150);")
|
|
layout.addWidget(desc)
|
|
|
|
# Plugin selection
|
|
layout.addWidget(QLabel("Select plugins to export/import:"))
|
|
self.plugin_list = QListWidget()
|
|
self.plugin_list.setSelectionMode(QListWidget.SelectionMode.MultiSelection)
|
|
self._populate_plugin_list()
|
|
layout.addWidget(self.plugin_list)
|
|
|
|
# Select all/none buttons
|
|
select_layout = QHBoxLayout()
|
|
select_all_btn = QPushButton("Select All")
|
|
select_all_btn.clicked.connect(lambda: self.plugin_list.selectAll())
|
|
select_layout.addWidget(select_all_btn)
|
|
|
|
select_none_btn = QPushButton("Select None")
|
|
select_none_btn.clicked.connect(lambda: self.plugin_list.clearSelection())
|
|
select_layout.addWidget(select_none_btn)
|
|
layout.addLayout(select_layout)
|
|
|
|
# Progress
|
|
self.progress_bar = QProgressBar()
|
|
self.progress_bar.setVisible(False)
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
self.status_label = QLabel("")
|
|
self.status_label.setStyleSheet("color: #4a9eff;")
|
|
layout.addWidget(self.status_label)
|
|
|
|
# Export/Import buttons
|
|
btn_layout = QHBoxLayout()
|
|
|
|
export_btn = QPushButton("Export to File...")
|
|
export_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #4caf50;
|
|
color: white;
|
|
padding: 10px;
|
|
font-weight: bold;
|
|
}
|
|
""")
|
|
export_btn.clicked.connect(self._export)
|
|
btn_layout.addWidget(export_btn)
|
|
|
|
import_btn = QPushButton("Import from File...")
|
|
import_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #ff8c42;
|
|
color: white;
|
|
padding: 10px;
|
|
font-weight: bold;
|
|
}
|
|
""")
|
|
import_btn.clicked.connect(self._import)
|
|
btn_layout.addWidget(import_btn)
|
|
|
|
layout.addLayout(btn_layout)
|
|
|
|
# Info
|
|
info = QLabel(
|
|
"Exports include:\n"
|
|
"- All plugin settings\n"
|
|
"- Session history\n"
|
|
"- Price alerts\n"
|
|
"- Custom configurations\n\n"
|
|
"Files are saved as .zip archives with JSON data."
|
|
)
|
|
info.setStyleSheet("color: rgba(255,255,255,150); font-size: 11px;")
|
|
layout.addWidget(info)
|
|
|
|
layout.addStretch()
|
|
return widget
|
|
|
|
def _populate_plugin_list(self):
|
|
"""Populate the plugin list."""
|
|
# Get available plugins
|
|
plugins = [
|
|
"loot_tracker",
|
|
"skill_scanner",
|
|
"price_alerts",
|
|
"session_exporter",
|
|
"mining_helper",
|
|
"mission_tracker",
|
|
"codex_tracker",
|
|
"auction_tracker",
|
|
"settings"
|
|
]
|
|
|
|
for plugin in plugins:
|
|
item = QListWidgetItem(plugin.replace('_', ' ').title())
|
|
item.setData(Qt.ItemDataRole.UserRole, plugin)
|
|
self.plugin_list.addItem(item)
|
|
|
|
def _get_selected_plugins(self):
|
|
"""Get list of selected plugin names."""
|
|
selected = []
|
|
for item in self.plugin_list.selectedItems():
|
|
selected.append(item.data(Qt.ItemDataRole.UserRole))
|
|
return selected
|
|
|
|
def _export(self):
|
|
"""Export data to file."""
|
|
selected = self._get_selected_plugins()
|
|
if not selected:
|
|
QMessageBox.warning(self.get_ui(), "No Selection", "Please select at least one plugin to export.")
|
|
return
|
|
|
|
# Get save location
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
default_name = f"eu-utility-backup-{timestamp}.zip"
|
|
|
|
path, _ = QFileDialog.getSaveFileName(
|
|
self.get_ui(),
|
|
"Export Data",
|
|
os.path.join(self.default_export_dir, default_name),
|
|
"Zip Files (*.zip)"
|
|
)
|
|
|
|
if not path:
|
|
return
|
|
|
|
# Start export
|
|
self._start_worker("export", path, selected)
|
|
|
|
def _import(self):
|
|
"""Import data from file."""
|
|
selected = self._get_selected_plugins()
|
|
if not selected:
|
|
QMessageBox.warning(self.get_ui(), "No Selection", "Please select at least one plugin to import.")
|
|
return
|
|
|
|
# Confirm import
|
|
reply = QMessageBox.question(
|
|
self.get_ui(),
|
|
"Confirm Import",
|
|
"This will overwrite existing data for selected plugins. Continue?",
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
return
|
|
|
|
# Get file location
|
|
path, _ = QFileDialog.getOpenFileName(
|
|
self.get_ui(),
|
|
"Import Data",
|
|
self.default_export_dir,
|
|
"Zip Files (*.zip)"
|
|
)
|
|
|
|
if not path:
|
|
return
|
|
|
|
# Start import
|
|
self._start_worker("import", path, selected)
|
|
|
|
def _start_worker(self, operation, path, selected):
|
|
"""Start background worker."""
|
|
# Show progress
|
|
self.progress_bar.setVisible(True)
|
|
self.progress_bar.setValue(0)
|
|
|
|
# Get data store from API
|
|
data_store = self.api.services.get('data_store') if self.api else None
|
|
if not data_store:
|
|
QMessageBox.critical(self.get_ui(), "Error", "Data store not available")
|
|
return
|
|
|
|
# Create and start worker
|
|
self.worker = ExportImportWorker(operation, data_store, path, selected)
|
|
self.worker.progress.connect(self.progress_bar.setValue)
|
|
self.worker.status.connect(self.status_label.setText)
|
|
self.worker.finished_signal.connect(self._on_worker_finished)
|
|
self.worker.start()
|
|
|
|
def _on_worker_finished(self, success, message):
|
|
"""Handle worker completion."""
|
|
self.progress_bar.setVisible(False)
|
|
|
|
if success:
|
|
self.status_label.setText(f"✓ {message}")
|
|
self.status_label.setStyleSheet("color: #4caf50;")
|
|
QMessageBox.information(self.get_ui(), "Success", message)
|
|
else:
|
|
self.status_label.setText(f"✗ {message}")
|
|
self.status_label.setStyleSheet("color: #f44336;")
|
|
QMessageBox.critical(self.get_ui(), "Error", message)
|