diff --git a/portable_computing_toolkit_installer/resources/supported_tools.json b/portable_computing_toolkit_installer/resources/supported_tools.json index f765328..b5b7ec4 100644 --- a/portable_computing_toolkit_installer/resources/supported_tools.json +++ b/portable_computing_toolkit_installer/resources/supported_tools.json @@ -52,6 +52,7 @@ "category": "development.languages.c and c++", "homepage": "https://www.msys2.org", "icon url": "https://avatars1.githubusercontent.com/u/6759993?s=200&v=4", + "ntfs_required": true, "search": { "selector": "", "filename regex": "", diff --git a/portable_computing_toolkit_installer/ui/installer_wizard.py b/portable_computing_toolkit_installer/ui/installer_wizard.py index 214c751..7bd1b00 100755 --- a/portable_computing_toolkit_installer/ui/installer_wizard.py +++ b/portable_computing_toolkit_installer/ui/installer_wizard.py @@ -37,7 +37,7 @@ class InstallerWizard(QtWidgets.QWizard): self.intro.anchorClicked.connect(self._link_clicked) self.license.anchorClicked.connect(self._link_clicked) self.selection_menu.itemClicked.connect(self._enforce_dependencies) - self.install_location.setValidator(Location_Validator(parent=self)) + self.install_location.setValidator(Location_Validator(self)) self.location_page.registerField("Location*", self.install_location) self.browse_button.clicked.connect(self._select_location) self.selection_menu.itemClicked.connect(self._open_homepage) diff --git a/portable_computing_toolkit_installer/validators/location_validator.py b/portable_computing_toolkit_installer/validators/location_validator.py index 25836f5..40495dc 100644 --- a/portable_computing_toolkit_installer/validators/location_validator.py +++ b/portable_computing_toolkit_installer/validators/location_validator.py @@ -5,8 +5,18 @@ import pathlib class Location_Validator(QtGui.QValidator): - def __init__(self, parent=None): + def __init__(self, parent): super().__init__(parent) + """for some reason on my Linux system, selecting a directory validates the same location repatedly, + once for each subdir of the location. The only way to keep it from repeatedly validating and potentially + opening a warning window for each subdirectory in the location path was to track this and return the same + value that was returned the last time it was validated.""" + self._previous = {"location":"", "disposition":(QtGui.QValidator.Intermediate, "", 0,)} + self._ntfs_location_selected = False + + @property + def ntfs_location_selected(self): + return self._ntfs_location_selected def _onNTFSDrive(self, location): """Tests if location is a path on an NTFS-formatted drive. Returns True if path is on NTFS-formatted drive. @@ -14,54 +24,70 @@ class Location_Validator(QtGui.QValidator): Arguments: location {str} -- A string representing a folder path """ - match = [drive for drive in psutil.disk_partitions() if (drive.device == pathlib.Path(location).anchor and drive.fstype == "NTFS")] - if not match: - QtWidgets.QMessageBox( - icon=QtWidgets.QMessageBox.Critical, - parent=self.parent(), - text=f"{pathlib.Path(location).anchor} is not an NTFS-formatted volume. Please reformat your drive to NTFS or pick an install path on a different volume" - ).show() - return False - else: + parent_dirs = [str(parent_dir) for parent_dir in pathlib.Path(location).parents] + match = [drive for drive in psutil.disk_partitions() if (drive.mountpoint in parent_dirs and drive.fstype == "NTFS")] + if match: + self._ntfs_location_selected = True return True + else: + if QtWidgets.QMessageBox.question(self.parent(), "", f"{pathlib.Path(location)} is not an NTFS-formatted volume. Tools which require a NTFS filesystem to function will not be available. Continue anyway?") in (QtWidgets.QMessageBox.No, 0): + self._ntfs_location_selected = False + return False + else: + self._ntfs_location_selected = False + return True def validate(self, input_, pos): - """Called by Portable_CS_Toolkit_Installer via QtWidget.QLineEdit.TextChanged signal to validate installation location input of user + """Called by InstallerWizard via QtWidget.QLineEdit.TextChanged signal to validate installation location input of user Arguments: input_ {str} -- The text entered in the installation location QLineEdit widget pos {int} -- The current location of the cursor in the QLineEdit widget """ - if input_.endswith(r":"): - return ( - QtGui.QValidator.Intermediate, - input_, - pos, - ) # bugfix. _onNTFSDrive triggers when typing the : in a drive name e.g. C:\ - - # because this class method is invoked via signal, it cannot handle python exceptions - # testing writability in Python is difficult and involves actually writing a temporary file - # to the directory, however, if that fails and raises an exception, it happens in Qt/C++ world - # and the python interpreter can't catch it, the program just crashes without any output, - # therefore, we will use Qt to do checking - input_ = os.path.expandvars(input_) # expand any environment variables in input - location = QtCore.QFileInfo(input_) - if location.exists() and location.isDir(): - - if input_ and not self._onNTFSDrive(input_): - # clear the location, reset cursor, but allow continued edits - return (QtGui.QValidator.Intermediate, "", 0) - - test_file = QtCore.QTemporaryFile(input_ + r"\testXXXXXX.tmp") - - if test_file.open(QtCore.QIODevice.ReadWrite): - test_file.close() - test_file.remove() - return (QtGui.QValidator.Acceptable, input_, pos) - else: - return (QtGui.QValidator.Intermediate, input_, pos) - + if not input_.strip(): + self._previous["location"] = input_ + self._previous["disposition"] = (QtGui.QValidator.Intermediate, input_, pos,) + return (QtGui.QValidator.Intermediate, input_, pos,) + elif input_.strip() == self._previous["location"]: + #I really hate that I have to do this + return self._previous["disposition"] + elif input_.strip().endswith(r":"): + self._previous["location"] = input_ + self._previous["disposition"] = (QtGui.QValidator.Intermediate, input_, pos,) + # bugfix. _onNTFSDrive triggers when typing the : in a drive name e.g. C:\ + return (QtGui.QValidator.Intermediate, input_, pos,) else: - # I wanted to return invalid, but that would prevent input of anything not instantly acceptable - # or editing something acceptable to something else acceptable, passing through an invalid on the way - return (QtGui.QValidator.Intermediate, input_, pos) + # because this class method is invoked via signal, it cannot handle python exceptions + # testing writability in Python is difficult and involves actually writing a temporary file + # to the directory, however, if that fails and raises an exception, it happens in Qt/C++ world + # and the python interpreter can't catch it, the program just crashes without any output, + # therefore, we will use Qt to do checking + input_ = os.path.expandvars(input_.strip()) # expand any environment variables in input + location = QtCore.QFileInfo(input_) + if location.exists() and location.isDir(): + + if input_ and not self._onNTFSDrive(input_): + self._previous["location"] = input_ + self._previous["disposition"] = (QtGui.QValidator.Intermediate, "", 0) + # clear the location, reset cursor, but allow continued edits + return (QtGui.QValidator.Intermediate, "", 0) + + test_file = QtCore.QTemporaryFile(input_ + r"\testXXXXXX.tmp") + + if test_file.open(QtCore.QIODevice.ReadWrite): + test_file.close() + test_file.remove() + self._previous["location"] = input_ + self._previous["disposition"] = (QtGui.QValidator.Acceptable, input_, pos,) + return (QtGui.QValidator.Acceptable, input_, pos,) + else: + self._previous["location"] = input_ + self._previous["disposition"] = (QtGui.QValidator.Intermediate, input_, pos,) + return (QtGui.QValidator.Intermediate, input_, pos,) + + else: + self._previous["location"] = input_ + self._previous["disposition"] = (QtGui.QValidator.Intermediate, input_, pos,) + # I wanted to return invalid, but that would prevent input of anything not instantly acceptable + # or editing something acceptable to something else acceptable, passing through an invalid on the way + return (QtGui.QValidator.Intermediate, input_, pos,)