DavidoTek / ProtonUp-Qt

Install and manage GE-Proton, Luxtorpeda & more for Steam and Wine-GE & more for Lutris with this graphical user interface.
https://davidotek.github.io/protonup-qt
GNU General Public License v3.0
1.24k stars 40 forks source link

[Feature Request] Use a symlink to help auto update wine-ge #162

Open Etaash-mathamsetty opened 1 year ago

Etaash-mathamsetty commented 1 year ago

essentially this is how the feature works, so whenever the user opens protonup-qt and downloads a newer version of a runner (except proton, I tried that and it won't work with steam unfortunately), let's say wine-ge, we will symlink this newer version to wine-ge-latest (or something similar), then all the user has to do to keep wine-ge updated is just download a newer version of wine-ge. if the user chooses to make the game use wine-ge-latest in heroic or lutris, the symlink will just automatically point it to the newer version.

steps to do it manually:

  1. symlink latest wine-ge with name wine-ge-latest (use this name every time you perform the replacement), so for example wine-ge-proton7-34 will be symlinked to wine-ge-latest

an alternative method would be to rename the folders, but with that approach I can imagine things getting very messy, very fast since there is no way to determine the version number

sonic2kk commented 1 year ago

Possibly related / implementation detail for #117

DavidoTek commented 1 year ago

That should be doable.

How would a user choose to use "wine-ge-stable" symlink option? I think we have 3 options:

  1. Automatically create the symlink for every new version (no user option)
  2. Add a checkbox like Install option "Latest Wine-GE" to the launcher to automatically select the newest version to the install dialog or the settings
  3. Show the option Latest Wine-GE in the version drop down of the install dialog
    (Not sure if that is confusing)

Internally, it should be quite easy to do:

  1. Delete the old symlink if it exists
  2. Create a symlink to the newly installed version folder but replace the version in the name with latest
Etaash-mathamsetty commented 1 year ago

That should be doable.

How would a user choose to use "wine-ge-stable" symlink option? I think we have 3 options:

  1. Automatically create the symlink for every new version (no user option)
  2. Add a checkbox like Install option "Latest Wine-GE" to the launcher to automatically select the newest version to the install dialog or the settings
  3. Show the option Latest Wine-GE in the version drop down of the install dialog
    (Not sure if that is confusing)

Internally, it should be quite easy to do:

  1. Delete the old symlink if it exists
  2. Create a symlink to the newly installed version folder but replace the version in the name with latest

Personally I like having choice and so I think option 2 is the best

Etaash-mathamsetty commented 1 year ago

Any updates on this?

sonic2kk commented 1 year ago

Been fiddling with this, if I make some more progress I will hopefully get a PR up soon for at least Wine-GE. I'm trying to keep in mind potentially doing this for other compatibility tools, as well as "cleaning up".

I am currently investigating how to handle a scenario where, for example, the latest version of Wine-GE being removed. Ideally, if there is an older version available, the symlink would update to allow for this. If no older version is available, the symlink should be removed.

Perhaps the most straightforward is to have a generic helper method to update symlinks, though knowing when to call this could be problematic. It would probably be better if ctmods implemented an uninstall method, but that could be a hassle and probably a prerequisite to implementing this symlinking feature, rather than something to be done alongside it.

I am also not sure how we would handle this if a user manually removed the runner.

Just my thoughts and experiences with playing around with this :-)


EDIT: Got a branch with an initial attempt at implementing this up over here. I created a generic function for managing the symlink for Lutris runners, and right now I have only implemented it for Wine-GE. It will "update" the symlink if a newer release of lutris-GE is downloaded (which means on re-installing of a runner it will simply remove and re-create the symlink). It is currently missing:

sonic2kk commented 1 year ago

I took a look at some of the work required around the ctmods having their own remove method and I think it makes the most sense, but I am absolutely open to better suggestions/corrections of course :-)

How i see it is like this: Each ctmod would optionally implement this symlinking functionality, so each ctmod's remove method should be responsible for handling the cleanup to update the symlink, or removing it altogether. Having a remove method also means that the special case hack for SteamTinkerLaunch's uninstall could be cleaned up and held solely in the ctmod. This sounds nice to say but I am sure the refactor would be a bit involved (as these things tend to be), but this is only one other example of why implementing a remove method for each ctmod could be beneficial.

We could do a check something like this in the btn_remove_selcted_clicked method in MainWindow (I'll come back to the problem afterwards):

for ct in ctools_to_remove:
    # Not all ctmods may implement a remove, only those that have custom uninstall steps beyond a simple folder remove
    if hasattr(ct, 'remove') and callable(getattr(ct, 'remove')):
        ct.remove(ct.get_install_folder(), ct.get_install_dir())
    else:
        remove_ctool(ct.get_install_folder(), ct.get_install_dir())  # Existing uninstall behaviour

The problem is that ctools_to_remove simply holds a list of BasicCompatTool, so this doesn't work as it is currently described. Since this is a data structure class it's probably best to keep any kind of remove logic out of it, and stick to having it store read data.

A solution I thought of could be to somehow hold a reference to the corresponding ctmod class as a field inside of this BasicCompatTool class. For SteamTinkerLaunch entries it would point to the SteamTinkerLaunch ctmod class in ctmod_steamtinkerlaunch - though differentiating between git/nightly versions could be troublesome for tools that have nightly builds as there probably wouldn't be a good way to differentiate after installation...

I did think about possibly having a "stop-gap" util method that gets called from remove_ctool, where we have a hardcoded list of steps like if install_dir looks like wine-ge install_dir, find wine-ge symlink by name and update/remove it. I don't like this idea very much, and I don't know if there's a good way to "dynamically" set the symlink name since the name of each ctool could be so widely varied.


Those are my thoughts on how to proceed with the rest of this implementation and what I explored so far :-)

DavidoTek commented 1 year ago

the ctmods having their own remove method

Yeah, that would bring a lot of advantages. We would need to add some way for ctmods to detect their "own" compatibility tools. At this point we could could also add a function get_installed_versions to each ctmod instead of reling on the static get_installed_ctools.

This sounds nice to say but I am sure the refactor would be a bit involved

That will be a lot of work. I started on a refactor a while back, but I haven't gotten very far yet and it is also a few commits behind main.

We could do a check something like this in the btn_remove_selcted_clicked

Agreed

A solution I thought of could be to somehow hold a reference to the corresponding ctmod class as a field inside of this BasicCompatTool

Good idea. We could add a remove function and a reference to the ctmod to BasicCompatTool. Maybe like this:

class BasicCompatTool:
    def __init__ ( self, ... , ctinstaller: CtInstaller ):
        self.ctinstaller = ctinstaller
    def remove ( self ):
        self.ctinstaller.uninstall_tool(self)

The ctmod's uninstall_tool function could then read all needed information (launcher, install directory) from the BasicCompatTool. If each ctmod had a get_installed_versions function, it could store a reference to itself to each BasicCompatTool in the list.

I did think about possibly having a "stop-gap" util method [...] I don't like this idea very much, and I don't know if there's a good way to "dynamically" set the symlink name since the name of each ctool could be so widely varied.

Agreed. That's a bit like our current remove_steamtinkerlaunch function. It works, but makes the code less clean and more coupled.

sigboe commented 1 year ago

If a symlink doesn't work for proton in steam, what about a hardlink?

sonic2kk commented 3 months ago

Late reply but commenting because it came up again in another issue, and for completeness wanted to give a reply here:

If a symlink doesn't work for proton in steam, what about a hardlink?

A hardlink should indeed work. If you replace all of the files in a given, say, GE-Proton folder, this will actually use the updated files, so there's no caching on the Steam side. You can validate which Proton version is being used by inspecting a Proton log for a game, generated with PROTON_LOG=1 %command% in the game's launch option.