Skip to content

Conversation

@StevenLares
Copy link

These commits address these tasks found in my comment under issue #12:

  • Convert windows file paths into platform agnostic paths.
  • Remove "exe" from executable file path names if the user is on Linux.

In addition, the final commit in this PR changes the download link to point to the latest release instead of a specific release.

The commit descriptions go into more detail on the exact changes made.

amarzot and others added 5 commits February 20, 2024 14:43
… name corresponding to user's OS.

This function is now used by other classes to store the executable names in member variables. Relevant code now refers to the member variables instead of the previously hardcoded exe names.

In addition, I adjusted download links to point to executables (with the platform agnostic exe names) although that will require building the launcher for Linux on each release.
@StevenLares
Copy link
Author

So I ran this on a windows machine, and I got an error regarding pathlib Path 😅 .
I'll investigate this when I have more time, don't merge this request. I modify the request later.

@Zedb0T
Copy link
Contributor

Zedb0T commented Feb 22, 2024

No worries! I can fixup/upload some linux built binary on one of the mods repos if youd like to have something to test end to end with.

@StevenLares
Copy link
Author

StevenLares commented Feb 22, 2024 via email

…nged platform.system() to sys.platform for consistency with the rest of the code
… never used.

launcherUtils.py now makes use of file path variables declared at the top, removes some file path hardcoding
… with autoupdater.py' on both Linux and Windows
@StevenLares StevenLares changed the title Convert windows file paths into platform agnostic paths; make executables have platform agnostic names [Linux Support] Convert windows file paths into platform agnostic paths; use agnostic executables and commands Feb 22, 2024
Copy link

@amarzot amarzot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a maintainer here, but wrote #56 so thought I'd leave some feedback. Feel free to disregard anything you see as out of scope for this PR.

Comment on lines +95 to +112
if sys.platform == "win32":
call = "TASKLIST", "/FI", "imagename eq %s" % process_name
try:
# use buildin check_output right away
output = subprocess.check_output(call).decode()
# check in last line for process name
last_line = output.strip().split("\r\n")[-1]
# because Fail message could be translated
return last_line.lower().startswith(process_name.lower())
except:
return False
else:
call = ["pgrep", "--list-name", "^" + process_name + "$"]
try:
output = subprocess.check_output(call).decode()
return len(output) > 1
except:
return False
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it is worth bringing in psutil for this functionality

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this behaves the same on both Windows and Linux, I think it is worth using it. However I think maintainers should decide if adding a dependency is ok: @Zedb0T

Comment on lines +117 to +120
if sys.platform == "win32":
os.system("taskkill /f /im " + process_name)
else:
os.system("pkill " + "^" + process_name + "$")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about psutil here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this behaves the same on both Windows and Linux, I think it is worth using it. However I think maintainers should decide if adding a dependency is ok: @Zedb0T

if sys.platform == "win32":
subprocess.check_call('mklink /J "%s" "%s"' % (link, target), shell=True)
else:
subprocess.check_call('ln -s "%s" "%s"' % (link, target), shell=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be able to use pathlib

Suggested change
subprocess.check_call('ln -s "%s" "%s"' % (link, target), shell=True)
link.symlink_to(target)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The windows version creates a directory junction.
The pathlib library does not have a junction command.

As far as I can tell, the overall code doesn't need a junction, and will function the same with a call to:

Path.symlink_to(target, target_is_directory=True)

The second parameter is used only on Windows, ignored everywhere else

While there is a function is_junction() in launcherUtils, I think this new implementation will work just the same on Windows

def is_junction(path: str) -> bool:
    try:
        return bool(os.readlink(path))
    except OSError:
        return False

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc there was a reason we went with junctions, I think the pathing somewhere in opengoal itself broke when using symlinks? might be misremembering this

Copy link
Collaborator

@dallmeyer dallmeyer Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see:
https://github.com/OpenGOAL-Mods/OG-ModLauncher/pull/37/files
https://github.com/OpenGOAL-Mods/OG-ModLauncher/pull/43/files

but I think the key issue was # symlink isodata for custom levels art group (goalc doesnt take -f flag)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the links.

I will leave the commands as-is, although I corrected the order of Linux's symlink parameters in bab64d0

if sys.platform == "win32":
subprocess.check_call('mklink /H "%s" "%s"' % (link, target), shell=True)
else:
subprocess.check_call('ln -s "%s" "%s"' % (link, target), shell=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here with link.symlink_to

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I'll add it after a decision on the other similar function is made

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth keeping it this way to be consistent with the other symlink function

Comment on lines 98 to 116
window['update_status'].update("Starting Update...")
try_remove_dir(AppdataPATH + "/temp")
if not os.path.exists(AppdataPATH + "/temp"):
os.makedirs(AppdataPATH + "/temp")
try_remove_dir(AppdataPATH/"temp")
if not os.path.exists(AppdataPATH/"temp"):
os.makedirs(AppdataPATH/"temp")

window['update_status'].update("Downloading update from " + latest_release_assets_url)
file = urllib.request.urlopen(latest_release_assets_url)
urllib.request.urlretrieve(latest_release_assets_url, AppdataPATH + "/temp/OpengoalModLauncher.exe", show_progress)
urllib.request.urlretrieve(latest_release_assets_url, AppdataPATH/"temp"/OpengoalModLauncher_exe, show_progress)
window['update_status'].update("Done downloading")

window['update_status'].update("Removing previous installation " + AppdataPATH)
try_remove_dir(AppdataPATH + "/data")
try_remove_file(AppdataPATH + "/gk.exe")
try_remove_file(AppdataPATH + "/goalc.exe")
try_remove_file(AppdataPATH + "/extractor.exe")
window['update_status'].update(f"Removing previous installation {AppdataPATH}")
try_remove_dir(AppdataPATH/"data")
try_remove_file(AppdataPATH/gk_exe)
try_remove_file(AppdataPATH/goalc_exe)
try_remove_file(AppdataPATH/extractor_exe)

window['update_status'].update("Extracting update")
temp_dir = AppdataPATH + "/temp"
try_remove_file(temp_dir + "/updateDATA.zip")
temp_dir = AppdataPATH/"temp"
try_remove_file(temp_dir/"updateDATA.zip")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe move temp_dir to the top for reuse, and change the os functions to temp_dir.exists() and temp_dir.mkdir()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See ca63a7d

Also, there are other instances where the code uses os.path and shutil functions where pathlib could be used, although for now I left them as is

Comment on lines +613 to +618
mod_name_settings_path = AppdataPATH / "OpenGOAL" / GAME / "settings" / (MOD_NAME + "-settings.gc")
if mod_name_settings_path.exists():
# just to be safe delete the migrated settings file if it already exists (shouldn't happen but prevents rename from failing below)
if exists(AppdataPATH + "\OpenGOAL" + "//" + GAME + "//" + "settings\\" + MOD_ID + "-settings.gc"):
os.remove(
AppdataPATH + "\OpenGOAL" + "//" + GAME + "//" + "settings\\" + MOD_ID + "-settings.gc"
)
mod_id_settings_path = AppdataPATH / "OpenGOAL" / GAME / "settings" / (MOD_ID + "-settings.gc")
if mod_id_settings_path.exists():
mod_id_settings_path.unlink()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using AppdataPATH here is an issue as it is derived from appdirs.user_data_dirs which points to ~/.local/share/ on linux when we should really be using ~/.config aka appdirs.user_config_dir. Both the data_dir and config_dir point to Appdata on windows, so that won't break.

See https://github.com/ActiveState/appdirs/blob/master/appdirs.py#L186-L189

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch!

Are there any other instances of the code that should also be modified?

Also, I am not sure if mods made from https://github.com/OpenGOAL-Mods/OG-Mod-Base would need to be updated to read their settings from .config on Linux.

from utils import launcherUtils

dirs = AppDirs(roaming=True)
AppdataPATH = Path(dirs.user_data_dir)/"OpenGOAL-UnofficialModLauncher"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs a rename as this is not the path to the AppData folder, but to the install directory. Also launcherUtils.py has this same variable, but it does point just to AppData

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See c44b200

…ved temp_dir earlier to top of code block. It is used instead of hardcoding
…ates, found in both check_for_updates() and download_newest_mod(). Also, I broke up the code into several reusable functions that are each responsible for one task only. Finally, I added a TODO that describes a future code improvement needed for Linux compatibility, which should be easier to implement and debug with this new code structure
@StevenLares
Copy link
Author

StevenLares commented Feb 24, 2024

Where this is at:

  • In 781524c I decompose some redundant code into separate functions. I also added a TODO regarding a future improvement needed once Linux executables are created for the opengoallauncher
  • Window buttons in the mainframe are broken on Linux for some reason. Haven't been able to debug yet
  • Open suggestions from this ticket
  • Any other task from what about making it cross-platform? #12 that doesn't seem to have a commit associated with it

@Zedb0T
Copy link
Contributor

Zedb0T commented Feb 26, 2024

Yeah great work here - sorry I've been slow to comment/review etc just been a very busy time for me but I do see the changes and want to get involved/help.

Hopefully soon I'll be able to look at this stuff in full.

If yall aren't already in the modding discord, it might good to join so we can all kind of make sure everyone's on the same page and make a plan for Linux support.

@StevenLares
Copy link
Author

@Zedb0T Hey no worries, I appreciate the help you have provided so far.

I will join the Discord later today and introduce myself. Can't wait to meet you guys there!

layoutUtils has one public method, generate(), along with several private helper methods.

My goals with this commit were to:
Separate layout code from the main code. This improves readability for both sets of code.
Break up the layout code into separate functions and variables so that it is easier to read and modify.
Assemble the layout elements so that they resemble the actual UI, as it is suggested in the pysimplegui docs. Read the returns for side_panel(), main_panel(), and main_panel()'s variable button_panel_with_mod_info for this idea in action.
… temp fix to accomodate large screens with high resolutions. Therefore, I added a TODO on high level next steps as that is out of scope for this pull request
… it results in a keyerror when the executable is run. I have added a default value in case the variable doesn't exist
@StevenLares
Copy link
Author

StevenLares commented Feb 29, 2024

These are my last commits on the launcher, for now.

I am open to responding to code review(s) when they're ready, but no rush.
From Discord, I understand there are higher priority items related to the OG-Mod-Base. At the moment, I don't have the time to contribute much on that front.

There are a few commits here that are slightly out of scope (like changing the dimensions of one of the columns, the introduction of layoutUtils.py, decomposing autoupdater.py into several reusable functions, and TODOs on possible improvements). No new python dependencies have been introduced to the project.

Of course, feel free to discard changes that you see fit. However, some of these changes make it easier for a new contributer to understand the code while improving maintainability for current maintainers.

Let me know if you have any questions.

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants