1414import re as _re
1515import requests as _requests
1616import tkinter as _tk
17- import subprocess as _subprocess
1817import sys as _sys
1918import webbrowser as _webbrowser
2019from dataclasses import dataclass as _dataclass
@@ -104,6 +103,35 @@ def _is_mac() -> bool:
104103_SYSTEM_TEXT_COLOR = "systemTextColor" if _is_mac () else "black"
105104
106105
106+ def _get_latest_version_from_github () -> _Optional [_Version ]:
107+ """
108+ Fetch releases from GitHub and return the newest version, or None on error.
109+ """
110+ url = "https://api.github.com/repos/sdatkinson/neural-amp-modeler/releases"
111+ try :
112+ response = _requests .get (url )
113+ except _requests .exceptions .ConnectionError :
114+ print ("WARNING: Failed to reach the server to check for updates" )
115+ return None
116+ if response .status_code != 200 :
117+ print (f"Failed to fetch releases. Status code: { response .status_code } " )
118+ return None
119+ releases = response .json ()
120+ latest_version = None
121+ if releases :
122+ for release in releases :
123+ tag = release ["tag_name" ]
124+ if not tag .startswith ("v" ):
125+ print (f"Found invalid version { tag } " )
126+ else :
127+ this_version = _Version .from_string (tag [1 :])
128+ if latest_version is None or this_version > latest_version :
129+ latest_version = this_version
130+ else :
131+ print ("No releases found for this repository." )
132+ return latest_version
133+
134+
107135@_dataclass
108136class AdvancedOptions (object ):
109137 """
@@ -392,14 +420,58 @@ def __init__(
392420 self ._no .pack (side = _tk .RIGHT )
393421
394422
423+ class _UpdateAvailableModal (object ):
424+ """
425+ Modal shown when a new version is available. Message, "Do not show this again"
426+ checkbox, and Close button. Does not offer an in-GUI upgrade; instructs user to
427+ run pip install --upgrade.
428+ """
429+
430+ def __init__ (self , resume_main : _Callable [[], None ], version : str ):
431+ self ._root = _tk .Toplevel ()
432+ self ._root .title ("Update available" )
433+ msg = (
434+ f"neural-amp-modeler v{ version } is now available. To upgrade, run "
435+ "pip install --upgrade neural-amp-modeler in your terminal."
436+ )
437+ self ._label = _tk .Label (
438+ self ._root ,
439+ text = msg ,
440+ justify = _tk .LEFT ,
441+ wraplength = 400 ,
442+ )
443+ self ._label .pack (padx = 10 , pady = 10 )
444+ self ._never_show_var = _tk .BooleanVar (value = False )
445+ self ._checkbox = _tk .Checkbutton (
446+ self ._root ,
447+ text = "Do not show this again" ,
448+ variable = self ._never_show_var ,
449+ )
450+ self ._checkbox .pack (anchor = "w" , padx = 10 , pady = 5 )
451+ self ._close_btn = _tk .Button (
452+ self ._root ,
453+ text = "Close" ,
454+ width = _BUTTON_WIDTH ,
455+ height = _BUTTON_HEIGHT ,
456+ command = self ._on_close ,
457+ )
458+ self ._close_btn .pack (pady = 10 )
459+ self ._resume_main = resume_main
460+
461+ def _on_close (self ):
462+ if self ._never_show_var .get ():
463+ _settings .set_update_settings (never_show_again = True )
464+ self ._resume_main ()
465+ self ._root .destroy ()
466+
467+
395468class _GUIWidgets (_Enum ):
396469 INPUT_PATH = "input_path"
397470 OUTPUT_PATH = "output_path"
398471 TRAINING_DESTINATION = "training_destination"
399472 METADATA = "metadata"
400473 ADVANCED_OPTIONS = "advanced_options"
401474 TRAIN = "train"
402- UPDATE = "update"
403475
404476
405477@_dataclass
@@ -468,9 +540,7 @@ def __init__(self):
468540 # Last frames: advanced options & train in the SE corner:
469541 self ._frame_advanced_options = _tk .Frame (self ._root )
470542 self ._frame_train = _tk .Frame (self ._root )
471- self ._frame_update = _tk .Frame (self ._root )
472543 # Pack must be in reverse order
473- self ._frame_update .pack (side = _tk .BOTTOM , anchor = "e" )
474544 self ._frame_train .pack (side = _tk .BOTTOM , anchor = "e" )
475545 self ._frame_advanced_options .pack (side = _tk .BOTTOM , anchor = "e" )
476546
@@ -505,7 +575,7 @@ def __init__(self):
505575 )
506576 self ._widgets [_GUIWidgets .TRAIN ].pack ()
507577
508- self ._pack_update_button_if_update_is_available ()
578+ self ._show_update_modal_if_update_available ()
509579
510580 self ._check_button_states ()
511581
@@ -601,78 +671,15 @@ def _open_metadata(self):
601671
602672 self ._wait_while_func (lambda resume : UserMetadataGUI (resume , self ))
603673
604- def _pack_update_button (self , version_from : _Version , version_to : _Version ):
605- """
606- Pack a button that a user can click to update
607- """
608-
609- def update_nam ():
610- result = _subprocess .run (
611- [
612- f"{ _sys .executable } " ,
613- "-m" ,
614- "pip" ,
615- "install" ,
616- "--upgrade" ,
617- "neural-amp-modeler" ,
618- ]
619- )
620- if result .returncode == 0 :
621- self ._wait_while_func (
622- (lambda resume , * args , ** kwargs : _OkModal (resume , * args , ** kwargs )),
623- "Update complete! Restart NAM for changes to take effect." ,
624- )
625- else :
626- self ._wait_while_func (
627- (lambda resume , * args , ** kwargs : _OkModal (resume , * args , ** kwargs )),
628- "Update failed! See logs." ,
629- )
630-
631- self ._widgets [_GUIWidgets .UPDATE ] = _tk .Button (
632- self ._frame_update ,
633- text = f"Update ({ str (version_from )} -> { str (version_to )} )" ,
634- width = _BUTTON_WIDTH ,
635- height = _BUTTON_HEIGHT ,
636- command = update_nam ,
637- )
638- self ._widgets [_GUIWidgets .UPDATE ].pack ()
639-
640- def _pack_update_button_if_update_is_available (self ):
674+ def _show_update_modal_if_update_available (self ):
641675 class UpdateInfo (_NamedTuple ):
642676 available : bool
643677 current_version : _Version
644678 new_version : _Optional [_Version ]
645679
646680 def get_info () -> UpdateInfo :
647- # TODO error handling
648- url = f"https://api.github.com/repos/sdatkinson/neural-amp-modeler/releases"
649681 current_version = _get_current_version ()
650- try :
651- response = _requests .get (url )
652- except _requests .exceptions .ConnectionError :
653- print ("WARNING: Failed to reach the server to check for updates" )
654- return UpdateInfo (
655- available = False , current_version = current_version , new_version = None
656- )
657- if response .status_code != 200 :
658- print (f"Failed to fetch releases. Status code: { response .status_code } " )
659- return UpdateInfo (
660- available = False , current_version = current_version , new_version = None
661- )
662- else :
663- releases = response .json ()
664- latest_version = None
665- if releases :
666- for release in releases :
667- tag = release ["tag_name" ]
668- if not tag .startswith ("v" ):
669- print (f"Found invalid version { tag } " )
670- else :
671- this_version = _Version .from_string (tag [1 :])
672- if latest_version is None or this_version > latest_version :
673- latest_version = this_version
674- else :
675- print ("No releases found for this repository." )
682+ latest_version = _get_latest_version_from_github ()
676683 update_available = (
677684 latest_version is not None and latest_version > current_version
678685 )
@@ -683,9 +690,30 @@ def get_info() -> UpdateInfo:
683690 )
684691
685692 update_info = get_info ()
686- if update_info .available :
687- self ._pack_update_button (
688- update_info .current_version , update_info .new_version
693+ if not update_info .available or update_info .new_version is None : # No news
694+ return
695+ # Now figure out what we've seen in the past
696+ update_settings = _settings .get_update_settings ()
697+
698+ settings_version = (
699+ _Version .from_string (update_settings ["newest_available_version" ])
700+ if update_settings ["newest_available_version" ] is not None
701+ else None
702+ )
703+ # Different new version since we last checked
704+ if settings_version is None or update_info .new_version > settings_version :
705+ _settings .set_update_settings (
706+ newest_available_version = str (update_info .new_version ),
707+ never_show_again = False ,
708+ )
709+ update_settings = _settings .get_update_settings ()
710+ if update_settings ["never_show_again" ]:
711+ return
712+ else :
713+ self ._wait_while_func (
714+ lambda resume : _UpdateAvailableModal (
715+ resume , str (update_info .new_version )
716+ ),
689717 )
690718
691719 def _resume (self ):
0 commit comments