Skip to content

Commit

Permalink
FEATURE: remember last selected directories on windows
Browse files Browse the repository at this point in the history
Uses platformdirs python package
  • Loading branch information
amilcarlucas committed Apr 29, 2024
1 parent 04d5fe5 commit b2f8f0b
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install -U pymavlink mock coverage
python -m pip install -U platformdirs pymavlink mock coverage
pip install build
pip install -U .
Expand Down
86 changes: 86 additions & 0 deletions MethodicConfigurator/backend_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from re import compile as re_compile
from re import match as re_match
from re import escape as re_escape
from re import sub as re_sub

# from sys import exit as sys_exit
# from logging import debug as logging_debug
Expand All @@ -37,6 +38,8 @@

from zipfile import ZipFile

from platformdirs import user_config_dir

from annotate_params import BASE_URL, PARAM_DEFINITION_XML_FILE, Par
from annotate_params import get_xml_data
from annotate_params import create_doc_dict
Expand Down Expand Up @@ -518,6 +521,89 @@ def copy_fc_values_to_file(self, selected_file: str, params: Dict[str, float]):
logging_warning("Parameter %s not found in the current parameter file", param)
return ret

@staticmethod
def __get_settings_directory():
settings_directory = user_config_dir(".ardupilot_methodic_configurator", False, roaming=True, ensure_exists=True)

if not os_path.exists(settings_directory):
raise FileNotFoundError(f"The settings directory '{settings_directory}' does not exist.")
if not os_path.isdir(settings_directory):
raise NotADirectoryError(f"The path '{settings_directory}' is not a directory.")

return settings_directory

@staticmethod
def __get_settings_as_dict():
settings_path = os_path.join(LocalFilesystem.__get_settings_directory(), "settings.json")

settings = {}

try:
with open(settings_path, "r", encoding='utf-8') as settings_file:
settings = json_load(settings_file)
except FileNotFoundError:
# If the file does not exist, it will be created later
pass

if "Format version" not in settings:
settings["Format version"] = 1

if "directory_selection" not in settings:
settings["directory_selection"] = {}
return settings

@staticmethod
def __set_settings_from_dict(settings):
settings_path = os_path.join(LocalFilesystem.__get_settings_directory(), "settings.json")

with open(settings_path, "w", encoding='utf-8') as settings_file:
json_dump(settings, settings_file, indent=4)

@staticmethod
def store_recently_used_template_dirs(template_dir: str, new_base_dir: str):
settings = LocalFilesystem.__get_settings_as_dict()

# Regular expression pattern to match single backslashes
pattern = r"(?<!\\)\\(?!\\)"

# Replacement string
replacement = r"\\"

# Update the settings with the new values
settings["directory_selection"].update({
"template_dir": re_sub(pattern, replacement, template_dir),
"new_base_dir": re_sub(pattern, replacement, new_base_dir)
})

LocalFilesystem.__set_settings_from_dict(settings)

@staticmethod
def store_recently_used_vehicle_dir(vehicle_dir: str):
settings = LocalFilesystem.__get_settings_as_dict()

# Regular expression pattern to match single backslashes
pattern = r"(?<!\\)\\(?!\\)"

# Replacement string
replacement = r"\\"

# Update the settings with the new values
settings["directory_selection"].update({
"vehicle_dir": re_sub(pattern, replacement, vehicle_dir)
})

LocalFilesystem.__set_settings_from_dict(settings)

@staticmethod
def get_recently_used_dirs():
settings = LocalFilesystem.__get_settings_as_dict()

template_dir = settings["directory_selection"].get("template_dir", "")
new_base_dir = settings["directory_selection"].get("new_base_dir", "")
vehicle_dir = settings["directory_selection"].get("vehicle_dir", "")

return template_dir, new_base_dir, vehicle_dir

def write_last_written_filename(self, current_file: str):
try:
with open(os_path.join(self.vehicle_dir, 'last_written_filename.txt'), 'w', encoding='utf-8') as file:
Expand Down
9 changes: 6 additions & 3 deletions MethodicConfigurator/frontend_tkinter_directory_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def on_select_directory(self):
self.local_filesystem.re_init(self.directory, self.local_filesystem.vehicle_type)
files = list(self.local_filesystem.file_parameters.keys())
if files:
LocalFilesystem.store_recently_used_vehicle_dir(self.directory)
if hasattr(self.parent, 'file_selection_combobox'):
# Update the file selection combobox with the new files
self.parent.file_selection_combobox.set_entries_tupple(files, files[0])
Expand Down Expand Up @@ -185,10 +186,11 @@ def __init__(self, local_filesystem: LocalFilesystem):
self.introduction_label = tk.Label(self.root, text=introduction_text + \
"\nChoose one of the following two options:")
self.introduction_label.pack(expand=False, fill=tk.X, padx=6, pady=6)
self.create_option1_widgets(local_filesystem.vehicle_dir,
local_filesystem.vehicle_dir,
template_dir, new_base_dir, vehicle_dir = LocalFilesystem.get_recently_used_dirs()
self.create_option1_widgets(template_dir,
new_base_dir,
"MyVehicleName")
self.create_option2_widgets(local_filesystem.vehicle_dir)
self.create_option2_widgets(vehicle_dir)

# Bind the close_connection_and_quit function to the window close event
self.root.protocol("WM_DELETE_WINDOW", self.close_and_quit)
Expand Down Expand Up @@ -278,6 +280,7 @@ def create_new_vehicle_from_template(self):
self.local_filesystem.re_init(new_vehicle_dir, self.local_filesystem.vehicle_type)
files = list(self.local_filesystem.file_parameters.keys())
if files:
LocalFilesystem.store_recently_used_template_dirs(template_dir, new_base_dir)
self.root.destroy()
else:
# No intermediate parameter files were found in the source template directory
Expand Down
1 change: 1 addition & 0 deletions credits/CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ It directly uses:
| [webbrowser](https://docs.python.org/3/library/webbrowser.html) | [Python Software Foundation License](https://docs.python.org/3/license.html) |
| [pymavlink](https://github.com/ArduPilot/pymavlink) | [GNU Lesser General Public License v3.0](https://github.com/ArduPilot/pymavlink/blob/master/COPYING) |
| [ArduPilot tempcal_IMU.py](https://github.com/ArduPilot/ardupilot/blob/master/Tools/scripts/tempcal_IMU.py) | [](https://github.com/ArduPilot/ardupilot/blob/master/COPYING.txt) |
| [platformdirs](https://platformdirs.readthedocs.io/en/latest/index.html) | [MIT](https://github.com/platformdirs/platformdirs/blob/main/LICENSE) |
| [pyserial](https://pyserial.readthedocs.io/en/latest/pyserial.html) | [BSD License](https://github.com/pyserial/pyserial/blob/master/LICENSE.txt) |
| [Scrollable TK frame](https://gist.github.com/mp035/9f2027c3ef9172264532fcd6262f3b01) by Mark Pointing | [Mozilla Public License, v. 2.0](https://mozilla.org/MPL/2.0/) |
| [Python Tkinter ComboBox](https://dev.to/geraldew/python-tkinter-an-exercise-in-wrapping-the-combobox-ndb) by geraldew | [Mozilla Public License, v. 2.0](https://mozilla.org/MPL/2.0/) |
Expand Down
21 changes: 21 additions & 0 deletions credits/platformdirs-LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2010-202x The platformdirs developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
1 change: 1 addition & 0 deletions credits/update_credits_licenses.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
{"name": "pymavlink", "license_url": "https://raw.githubusercontent.com/ArduPilot/pymavlink/master/COPYING"},
{"name": "ArduPilot tempcal_IMU.py",
"license_url": "https://raw.githubusercontent.com/ArduPilot/ardupilot/master/COPYING.txt"},
{"name": "platformdirs", "license_url": "https://raw.githubusercontent.com/platformdirs/platformdirs/main/LICENSE"},
{"name": "pyserial", "license_url": "https://raw.githubusercontent.com/pyserial/pyserial/master/LICENSE.txt"},
{"name": "Scrollable_TK_frame", "license_url": "https://mozilla.org/MPL/2.0/"},
{"name": "Python_Tkinter_ComboBox", "license_url": "https://mozilla.org/MPL/2.0/"},
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def package_files(directory):
install_requires=[
'matplotlib',
'numpy',
'platformdirs',
'pymavlink',
'pyserial',
'pillow',
Expand Down
38 changes: 36 additions & 2 deletions windows/ardupilot_methodic_configurator.iss
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,26 @@ Source: "..\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.1-params\*.*"; D
Source: "..\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.0-DEV-params\*.*"; DestDir: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.0-DEV-params"; Flags: ignoreversion
Source: "..\windows\version.txt"; DestDir: "{commonappdata}\.ardupilot_methodic_configurator"; Flags: ignoreversion
Source: "..\windows\MethodicConfigurator.ico"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\settings_template.json"; DestDir: "{app}\_internal"; Flags: ignoreversion
Source: "..\MethodicConfigurator\ArduPilot_icon.png"; DestDir: "{app}\_internal"; Flags: ignoreversion
Source: "..\MethodicConfigurator\ArduPilot_logo.png"; DestDir: "{app}\_internal"; Flags: ignoreversion
Source: "..\MethodicConfigurator\file_documentation.json"; DestDir: "{app}\_internal"; Flags: ignoreversion
Source: "..\credits\*.*"; DestDir: "{app}\credits"; Flags: ignoreversion
Source: "set_permissions.bat"; DestDir: "{app}\_internal"; Flags: ignoreversion

[Dirs]
Name: "{userappdata}\.ardupilot_methodic_configurator\vehicles"; Flags: uninsneveruninstall

[Icons]
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator\vehicles"; Tasks: desktopicon; IconFilename: "{app}\MethodicConfigurator.ico"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator"; Tasks: desktopicon; IconFilename: "{app}\MethodicConfigurator.ico"
Name: "{group}\Documentation"; Filename: "https://github.com/ArduPilot/MethodicConfigurator/blob/master/USERMANUAL.md"
Name: "{group}\Vehicle Templates"; Filename: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter"
Name: "{group}\ArduPilot MethodicConfigurator Forum"; Filename: "https://discuss.ardupilot.org/t/new-ardupilot-methodic-configurator-gui/115038/"
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator\vehicles"; IconFilename: "{app}\MethodicConfigurator.ico"
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator"; IconFilename: "{app}\MethodicConfigurator.ico"

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
Filename: "{app}\_internal\set_permissions.bat"; WorkingDir: "{app}"; StatusMsg: "Setting file permissions..."; Flags: runhidden

[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
Expand Down Expand Up @@ -170,3 +173,34 @@ begin
RefreshEnvironment();
end;
end;
function ReplacePlaceholdersInFile(const FileName: string): Boolean;
var
FileContent: AnsiString;
UnicodeFileContent: String;
ProgData: String;
UserData: String;
begin
if LoadStringFromFile(FileName, FileContent) then
begin
UnicodeFileContent := String(FileContent)
ProgData := ExpandConstant('{commonappdata}')
UserData := ExpandConstant('{userappdata}')
StringChangeEx(ProgData, '\', '\\', True)
StringChangeEx(UserData, '\', '\\', True)
StringChangeEx(UnicodeFileContent, '{PROGRAM_DATA}', ProgData, True);
StringChangeEx(UnicodeFileContent, '{USER_DATA}', UserData, True);
Result := SaveStringToFile(FileName, AnsiString(UnicodeFileContent), False);
end
else
Result := False;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
ReplacePlaceholdersInFile(ExpandConstant('{app}\_internal\settings_template.json'));
RenameFile(ExpandConstant('{app}\_internal\settings_template.json'), ExpandConstant('{userappdata}\.ardupilot_methodic_configurator\settings.json'));
end;
end;
6 changes: 6 additions & 0 deletions windows/set_permissions.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@echo off
:: Change directory to where settings.json is located
cd "%APPDATA%\.ardupilot_methodic_configurator"

:: Grant full control to the current user
icacls settings.json /grant "%USERNAME%:(F)"
8 changes: 8 additions & 0 deletions windows/settings_template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Format version": 1,
"directory_selection": {
"template_dir": "{PROGRAM_DATA}\\.ardupilot_methodic_configurator\\vehicle_templates\\ArduCopter\\diatone_taycan_mxc\\4.5.1-params",
"new_base_dir": "{USER_DATA}\\.ardupilot_methodic_configurator\\vehicles",
"vehicle_dir": "{USER_DATA}\\.ardupilot_methodic_configurator\\vehicles"
}
}

0 comments on commit b2f8f0b

Please sign in to comment.