fix(paths): centralize file:// URL handling to fix broken save-dir paths
CI / preflight (push) Waiting to run
CI / preflight (push) Waiting to run
This commit is contained in:
@@ -43,7 +43,7 @@ Popup {
|
||||
id: saveDirDialog
|
||||
title: "Choose a folder to save data"
|
||||
onAccepted: {
|
||||
var dir = decodeURIComponent(String(selectedFolder).replace(/^file:\/\//, ""))
|
||||
var dir = String(selectedFolder)
|
||||
deviceController.setSaveDataDir(dir)
|
||||
file_browser.setCurrentDirectory(dir)
|
||||
root.finished()
|
||||
|
||||
@@ -171,10 +171,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
function cleanFolderUrl(url) {
|
||||
return decodeURIComponent(String(url).replace(/^file:\/\//, ""))
|
||||
}
|
||||
|
||||
function _doDetect() {
|
||||
root.memoryRead = false
|
||||
root.waferDetected = false
|
||||
@@ -187,7 +183,7 @@ Rectangle {
|
||||
id: saveDirDialog
|
||||
title: "Choose a folder to save Data"
|
||||
onAccepted: {
|
||||
var dir = root.cleanFolderUrl(selectedFolder)
|
||||
var dir = String(selectedFolder)
|
||||
deviceController.setSaveDataDir(dir)
|
||||
file_browser.setCurrentDirectory(dir)
|
||||
root._doDetect()
|
||||
|
||||
@@ -113,9 +113,6 @@ ColumnLayout {
|
||||
readonly property bool isBusy: deviceController.connectionStatus.endsWith("...")
|
||||
readonly property string portName: deviceController.selectedPort || "—"
|
||||
|
||||
function cleanFolderUrl(url) {
|
||||
return decodeURIComponent(String(url).replace(/^file:\/\//, ""));
|
||||
}
|
||||
function currentFamilyCode() {
|
||||
var info = deviceController.lastWaferInfo;
|
||||
return info && info.length > 0 ? (info[0] || "") : "";
|
||||
@@ -128,7 +125,7 @@ ColumnLayout {
|
||||
id: saveDirDialog
|
||||
title: "Choose a folder to save."
|
||||
onAccepted: {
|
||||
var dir = root.cleanFolderUrl(selectedFolder);
|
||||
var dir = String(selectedFolder);
|
||||
deviceController.setSaveDataDir(dir);
|
||||
file_browser.setCurrentDirectory(dir);
|
||||
root.parseAndSavePendingRead();
|
||||
@@ -141,7 +138,7 @@ ColumnLayout {
|
||||
id: dirOnlyDialog
|
||||
title: "Choose a folder to save Data"
|
||||
onAccepted: {
|
||||
var dir = root.cleanFolderUrl(selectedFolder);
|
||||
var dir = String(selectedFolder);
|
||||
deviceController.setSaveDataDir(dir);
|
||||
file_browser.setCurrentDirectory(dir);
|
||||
}
|
||||
|
||||
@@ -260,14 +260,14 @@ ColumnLayout {
|
||||
id: importFileDialog
|
||||
title: "Select Data File To Import"
|
||||
nameFilters: ["CSV files (*.csv)"]
|
||||
onAccepted: file_browser.importZWaferCsv(String(selectedFile).replace(/^file:\/\//, ""))
|
||||
onAccepted: file_browser.importZWaferCsv(String(selectedFile))
|
||||
}
|
||||
|
||||
FolderDialog {
|
||||
id: batchExportDirDialog
|
||||
title: "Export Wafer Maps To…"
|
||||
onAccepted: {
|
||||
var dir = String(selectedFolder).replace(/^file:\/\//, "");
|
||||
var dir = String(selectedFolder);
|
||||
var paths = file_browser.files.map(function(row) { return row.fileName; });
|
||||
batchExportController.start(paths, dir);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import threading
|
||||
|
||||
from PySide6.QtCore import Property, QObject, Qt, Signal, Slot
|
||||
|
||||
from pygui.backend.utils import slot_error_boundary
|
||||
from pygui.backend.utils import slot_error_boundary, to_local_path
|
||||
from pygui.backend.wafer.batch_export import BatchExportResult, export_batch
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -33,7 +33,9 @@ class BatchExportController(QObject):
|
||||
self._busy = True
|
||||
self.busyChanged.emit()
|
||||
threading.Thread(
|
||||
target=self._worker, args=(list(file_paths), output_dir), daemon=True
|
||||
target=self._worker,
|
||||
args=(list(file_paths), str(to_local_path(output_dir))),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
def _worker(self, file_paths: list[str], output_dir: str) -> None:
|
||||
|
||||
@@ -15,7 +15,7 @@ from PySide6.QtCore import Property, QObject, Qt, Signal, Slot
|
||||
|
||||
from pygui.backend.data.local_settings import LocalSettings
|
||||
from pygui.backend.models.data_model import TemperatureTableModel
|
||||
from pygui.backend.utils import slot_error_boundary
|
||||
from pygui.backend.utils import slot_error_boundary, to_local_path
|
||||
from pygui.backend.visualization.graph_view import GraphView
|
||||
from pygui.serialcomm.data_parser import (
|
||||
convert_to_debug_temperatures,
|
||||
@@ -82,7 +82,8 @@ class DeviceController(QObject):
|
||||
self._operation_in_progress = False
|
||||
self._activity_log: list[str] = []
|
||||
self._raw_bytes: Optional[bytes] = None
|
||||
self._save_data_dir: str = getattr(settings, 'save_data_dir', "") or str(Path(self._data_dir) / "csv")
|
||||
saved_dir = getattr(settings, 'save_data_dir', "")
|
||||
self._save_data_dir: str = str(to_local_path(saved_dir)) if saved_dir else str(Path(self._data_dir) / "csv")
|
||||
self._last_wafer_info: dict[str, Any] = {}
|
||||
self._wafer_detected: bool = False
|
||||
self._last_csv_path: str = ""
|
||||
@@ -141,8 +142,8 @@ class DeviceController(QObject):
|
||||
@slot_error_boundary
|
||||
def setSaveDataDir(self, path: str) -> None:
|
||||
"""Set the directory for saving parsed CSV data files."""
|
||||
self._save_data_dir = path
|
||||
self._append_log(f"Save data dir set to: {path}")
|
||||
self._save_data_dir = str(to_local_path(path))
|
||||
self._append_log(f"Save data dir set to: {self._save_data_dir}")
|
||||
self._save_status()
|
||||
|
||||
@Slot(result=str)
|
||||
|
||||
@@ -10,6 +10,7 @@ from PySide6.QtWidgets import QFileDialog, QMessageBox
|
||||
|
||||
from pygui.backend.data.constants import default_data_dir
|
||||
from pygui.backend.data.csv_file_metadata import CSVFileMetadata
|
||||
from pygui.backend.utils import to_local_path
|
||||
from pygui.backend.wafer.zwafer_parser import ZWaferParser
|
||||
|
||||
|
||||
@@ -45,7 +46,7 @@ class FileBrowser(QObject):
|
||||
Used to keep this directory in sync with DeviceController.saveDataDir
|
||||
when the user picks a save folder elsewhere in the app.
|
||||
"""
|
||||
self._set_current_directory(Path(path))
|
||||
self._set_current_directory(to_local_path(path))
|
||||
self._refresh_files(show_empty_message=False)
|
||||
|
||||
@Slot()
|
||||
@@ -124,7 +125,8 @@ class FileBrowser(QObject):
|
||||
date still gets a deterministic (if degenerate) filename, so the
|
||||
duplicate-import guard below still fires on a second attempt.
|
||||
"""
|
||||
source_path = Path(file_path)
|
||||
source_path = to_local_path(file_path)
|
||||
file_path = str(source_path)
|
||||
parser_data, _ = self._parser.parse(file_path)
|
||||
if parser_data is None:
|
||||
self.importFinished.emit("", f"Failed to parse CSV:\n{source_path.name}")
|
||||
|
||||
@@ -5,7 +5,7 @@ import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Property, QObject, QUrl, Signal, Slot
|
||||
from PySide6.QtCore import Property, QObject, Signal, Slot
|
||||
|
||||
from pygui.backend.license.license_manager import (
|
||||
LICENSE_FILENAME_RE,
|
||||
@@ -13,25 +13,13 @@ from pygui.backend.license.license_manager import (
|
||||
parse_license_blob,
|
||||
read_license_files,
|
||||
)
|
||||
from pygui.backend.utils import to_local_path as _to_local_path
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TRIAL_DAYS = 30
|
||||
|
||||
|
||||
def _to_local_path(path_or_url: str) -> Path:
|
||||
"""Accept both plain paths and file:// URLs (QML FileDialog gives URLs).
|
||||
|
||||
Uses QUrl.toLocalFile() rather than urlparse — a manual
|
||||
urlparse().path strip leaves a leading slash before a Windows drive
|
||||
letter (e.g. "/C:/Users/...", not a valid absolute path), which
|
||||
QUrl's platform-aware conversion handles correctly.
|
||||
"""
|
||||
if path_or_url.startswith("file:"):
|
||||
return Path(QUrl(path_or_url).toLocalFile())
|
||||
return Path(path_or_url)
|
||||
|
||||
|
||||
# ===== License Model =====
|
||||
class LicenseModel(QObject):
|
||||
"""Context property backing the About dialog license grid.
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
import logging
|
||||
import re
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QUrl
|
||||
|
||||
_MANGLED_DRIVE_RE = re.compile(r"^/([A-Za-z]:)")
|
||||
|
||||
|
||||
def to_local_path(path_or_url: str) -> Path:
|
||||
"""Accept plain paths, file:// URLs, and already-mangled "/C:/..." strings
|
||||
(persisted by old buggy code before it stripped file:// by hand).
|
||||
|
||||
Uses QUrl.toLocalFile() rather than a manual string strip — stripping
|
||||
"file://" by hand leaves a leading slash before a Windows drive letter
|
||||
(e.g. "/C:/Users/...", not a valid absolute path), which QUrl's
|
||||
platform-aware conversion handles correctly.
|
||||
"""
|
||||
if path_or_url.startswith("file:"):
|
||||
return Path(QUrl(path_or_url).toLocalFile())
|
||||
# ponytail: repair values saved by the old regex-stripping QML code
|
||||
return Path(_MANGLED_DRIVE_RE.sub(r"\1", path_or_url))
|
||||
|
||||
|
||||
def slot_error_boundary(func):
|
||||
|
||||
Reference in New Issue
Block a user