def __authenticate(self): url = None if Config.get("stay_logged_in"): token = Config.get("refresh_token") else: Config.unset("username") Config.unset("refresh_token") token = None # Make sure there is an internet connection if not self.api.can_connect(): return authenticated = self.api.authenticate(refresh_token=token, login_code=url) while not authenticated: login_url = self.api.get_login_url() redirect_url = self.api.get_redirect_url() login = Login(login_url=login_url, redirect_url=redirect_url, parent=self) response = login.run() login.hide() if response == Gtk.ResponseType.DELETE_EVENT: Gtk.main_quit() exit(0) if response == Gtk.ResponseType.NONE: result = login.get_result() authenticated = self.api.authenticate(refresh_token=token, login_code=result) Config.set("refresh_token", authenticated)
def __save_theme_choice(self) -> None: settings = Gtk.Settings.get_default() Config.set("use_dark_theme", self.switch_use_dark_theme.get_active()) if Config.get("use_dark_theme") is True: settings.set_property("gtk-application-prefer-dark-theme", True) else: settings.set_property("gtk-application-prefer-dark-theme", False)
def __request(self, url: str = None, params: dict = None) -> dict: # Refresh the token if needed if self.active_token_expiration_time < time.time(): print("Refreshing token") refresh_token = Config.get("refresh_token") Config.set("refresh_token", self.__refresh_token(refresh_token)) # Make the request headers = { 'Authorization': "Bearer {}".format(str(self.active_token)), } result = {} try: response = SESSION.get(url, headers=headers, params=params) if self.debug: print("Request: {}".format(url)) print("Return code: {}".format(response.status_code)) print("Response body: {}".format(response.text)) print("") if response.status_code < 300: result = response.json() except requests.exceptions.RequestException as e: print("Encountered exception while making HTTP request.") print("Request: {}".format(url)) print("Exception: {}".format(e)) print("") return result
def __save_install_dir_choice(self) -> bool: choice = self.button_file_chooser.get_filename() old_dir = Config.get("install_dir") if choice == old_dir: return True if not os.path.exists(choice): try: os.makedirs(choice, mode=0o755) except: return False else: write_test_file = os.path.join(choice, "write_test.txt") try: with open(write_test_file, "w") as file: file.write("test") file.close() os.remove(write_test_file) except: return False # Remove the old directory if it is empty try: os.rmdir(old_dir) except OSError: pass Config.set("install_dir", choice) return True
def __download(self, download_info, finish_func, cancel_to_state): download_success = True GLib.idle_add(self.update_to_state, self.state.QUEUED) Config.set("current_download", self.game.id) # Start the download for all files self.download_list = [] number_of_files = len(download_info['files']) total_file_size = 0 executable_path = None download_files = [] for key, file_info in enumerate(download_info['files']): try: download_url = self.api.get_real_download_link( file_info["downlink"]) except ValueError as e: print(e) GLib.idle_add(self.parent.parent.show_error, _("Download error"), _(str(e))) download_success = False break total_file_size += self.api.get_file_size(file_info["downlink"]) try: # Extract the filename from the download url (filename is between %2F and &token) filename = urllib.parse.unquote( re.search('%2F(((?!%2F).)*)&t', download_url).group(1)) except AttributeError: filename = "{}-{}.bin".format(self.game.get_stripped_name(), key) download_path = os.path.join(self.download_dir, filename) if key == 0: # If key = 0, denote the file as the executable's path executable_path = download_path md5sum = self.api.get_download_file_md5(file_info["downlink"]) if md5sum: self.game.md5sum[os.path.basename(download_path)] = md5sum download = Download( url=download_url, save_location=download_path, finish_func=finish_func if download_path == executable_path else None, progress_func=self.set_progress, cancel_func=lambda: self.__cancel(to_state=cancel_to_state), number=number_of_files - key, out_of_amount=number_of_files, game=self.game) download_files.insert(0, download) self.download_list.extend(download_files) if check_diskspace(total_file_size, Config.get("install_dir")): DownloadManager.download(download_files) ds_msg_title = "" ds_msg_text = "" else: ds_msg_title = "Download error" ds_msg_text = "Not enough disk space to install game." download_success = False if ds_msg_title: GLib.idle_add(self.parent.parent.show_error, _(ds_msg_title), _(ds_msg_text)) return download_success
def get_user_info(self) -> str: username = Config.get("username") if not username: url = "https://embed.gog.com/userData.json" response = self.__request(url) username = response["username"] Config.set("username", username) return username
def __save_view_choice(self) -> None: view_choice = self.combobox_view.get_active_iter() if view_choice is not None: model = self.combobox_view.get_model() view, _ = model[view_choice][:2] if view != Config.get("view"): self.parent.reset_library() Config.set("view", view)
def test_set(self, mock_isfile): mock_isfile.return_value = True config = JSON_DEFAULT_CONFIGURATION with patch("builtins.open", mock_open(read_data=config)): from minigalaxy.config import Config Config.set("lang", "pl") lang = Config.get("lang") exp = "pl" obs = lang self.assertEqual(exp, obs)
def __download_file(self) -> None: Config.set("current_download", self.game.id) GLib.idle_add(self.update_to_state, self.state.QUEUED) download_info = self.api.get_download_info(self.game) file_url = download_info["downlink"] self.download = Download(file_url, self.download_path, self.__install, progress_func=self.set_progress, cancel_func=self.__cancel_download) DownloadManager.download(self.download)
def save_pressed(self, button): self.__save_language_choice() Config.set("keep_installers", self.switch_keep_installers.get_active()) Config.set("stay_logged_in", self.switch_stay_logged_in.get_active()) Config.set("show_fps", self.switch_show_fps.get_active()) if self.switch_show_windows_games.get_active() != Config.get( "show_windows_games"): Config.set("show_windows_games", self.switch_show_windows_games.get_active()) self.parent.reset_library() # Only change the install_dir is it was actually changed if self.button_file_chooser.get_filename() != Config.get( "install_dir"): if self.__save_install_dir_choice(): DownloadManager.cancel_all_downloads() self.parent.reset_library() else: dialog = Gtk.MessageDialog( parent=self, modal=True, destroy_with_parent=True, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text=_("{} isn't a usable path").format( self.button_file_chooser.get_filename())) dialog.run() dialog.destroy() self.destroy()
def save_pressed(self, button): self.__save_language_choice() Config.set("keep_installers", self.switch_keep_installers.get_active()) Config.set("stay_logged_in", self.switch_stay_logged_in.get_active()) if self.switch_show_windows_games.get_active() != Config.get( "show_windows_games"): if self.switch_show_windows_games.get_active( ) and not shutil.which("wine"): self.parent.show_error( _("Wine wasn't found. Showing Windows games cannot be enabled." )) Config.set("show_windows_games", False) else: Config.set("show_windows_games", self.switch_show_windows_games.get_active()) self.parent.reset_library() # Only change the install_dir is it was actually changed if self.button_file_chooser.get_filename() != Config.get( "install_dir"): if self.__save_install_dir_choice(): DownloadManager.cancel_all_downloads() self.parent.reset_library() else: self.parent.show_error( _("{} isn't a usable path").format( self.button_file_chooser.get_filename())) self.destroy()
def __save_locale_choice(self) -> None: new_locale = self.combobox_program_language.get_active_iter() if new_locale is not None: model = self.combobox_program_language.get_model() locale_choice, _ = model[new_locale][:2] if locale_choice == '': default_locale = locale.getdefaultlocale()[0] locale.setlocale(locale.LC_ALL, (default_locale, 'UTF-8')) Config.set("locale", locale_choice) else: try: locale.setlocale(locale.LC_ALL, (locale_choice, 'UTF-8')) Config.set("locale", locale_choice) except locale.Error: self.parent.show_error("Failed to change program language. Make sure locale is generated on your system.")
def __download_file(self) -> None: GLib.idle_add(self.update_to_state, self.state.QUEUED) try: download_info = self.api.get_download_info(self.game) except NoDownloadLinkFound: if Config.get("current_download") == self.game.id: Config.unset("current_download") GLib.idle_add( self.parent.parent.show_error, _("Download error"), _("There was an error when trying to fetch the download link!") ) GLib.idle_add(self.update_to_state, self.state.DOWNLOADABLE) return Config.set("current_download", self.game.id) # Start the download for all files self.download = [] download_path = self.download_path finish_func = self.__install number_of_files = len(download_info['files']) for key, file_info in enumerate(download_info['files']): download_url = self.api.get_real_download_link( file_info["downlink"]) try: # Extract the filename from the download url (filename is between %2F and &token) download_path = os.path.join( self.download_dir, urllib.parse.unquote( re.search('%2F(((?!%2F).)*)&t', download_url).group(1))) if key == 0: # If key = 0, denote the file as the executable's path self.download_path = download_path except AttributeError: if key > 0: download_path = "{}-{}.bin".format(self.download_path, key) download = Download(url=download_url, save_location=download_path, finish_func=finish_func, progress_func=self.set_progress, cancel_func=self.__cancel_download, number=key + 1, out_of_amount=number_of_files) self.download.append(download) DownloadManager.download(self.download)
def __request(self, url: str = None, params: dict = None) -> dict: # Refresh the token if needed if self.active_token_expiration_time < time.time(): print("Refreshing token") refresh_token = Config.get("refresh_token") Config.set("refresh_token", self.__refresh_token(refresh_token)) # Make the request headers = { 'Authorization': "Bearer {}".format(str(self.active_token)), } response = SESSION.get(url, headers=headers, params=params) if self.debug: print("Request: {}".format(url)) print("Return code: {}".format(response.status_code)) print("Response body: {}".format(response.text)) print("") return response.json()
def __download(self, download_info, finish_func, cancel_to_state): download_success = True GLib.idle_add(self.update_to_state, self.state.QUEUED) Config.set("current_download", self.game.id) # Start the download for all files self.download = [] number_of_files = len(download_info['files']) for key, file_info in enumerate(download_info['files']): try: download_url = self.api.get_real_download_link( file_info["downlink"]) self.game.md5sum = self.api.get_download_file_md5( file_info["downlink"]) except ValueError as e: print(e) GLib.idle_add(self.parent.parent.show_error, _("Download error"), _(str(e))) download_success = False break try: # Extract the filename from the download url (filename is between %2F and &token) download_path = os.path.join( self.download_dir, urllib.parse.unquote( re.search('%2F(((?!%2F).)*)&t', download_url).group(1))) if key == 0: # If key = 0, denote the file as the executable's path self.download_path = download_path except AttributeError: if key > 0: download_path = "{}-{}.bin".format(self.download_path, key) download = Download( url=download_url, save_location=download_path, finish_func=finish_func, progress_func=self.set_progress, cancel_func=lambda: self.__cancel(to_state=cancel_to_state), number=key + 1, out_of_amount=number_of_files) self.download.append(download) DownloadManager.download(self.download) return download_success
def __download_file(self) -> None: Config.set("current_download", self.game.id) GLib.idle_add(self.update_to_state, self.state.QUEUED) download_info = self.api.get_download_info(self.game) # Start the download for all files self.download = [] download_path = self.download_path finish_func = self.__install for key, file_info in enumerate(download_info['files']): if key > 0: download_path = "{}-{}.bin".format(self.download_path, key) download = Download(url=self.api.get_real_download_link( file_info["downlink"]), save_location=download_path, finish_func=finish_func, progress_func=self.set_progress, cancel_func=self.__cancel_download, number=key + 1, out_of_amount=len(download_info['files'])) self.download.append(download) DownloadManager.download(self.download)
class Window(Gtk.ApplicationWindow): __gtype_name__ = "Window" HeaderBar = Gtk.Template.Child() header_sync = Gtk.Template.Child() header_installed = Gtk.Template.Child() header_search = Gtk.Template.Child() menu_about = Gtk.Template.Child() menu_preferences = Gtk.Template.Child() menu_logout = Gtk.Template.Child() library = Gtk.Template.Child() def __init__(self, name): Gtk.ApplicationWindow.__init__(self, title=name) self.config = Config() self.api = Api(self.config) self.show_installed_only = False self.search_string = "" self.offline = False # Set the icon icon = GdkPixbuf.Pixbuf.new_from_file(LOGO_IMAGE_PATH) self.set_default_icon_list([icon]) # Create the thumbnails directory if not os.path.exists(THUMBNAIL_DIR): os.makedirs(THUMBNAIL_DIR) # Check if the user unset to stay logged in Preference and refresh token if self.api.config.get("stay_logged_in") is None: self.config.set("stay_logged_in", True) elif not self.api.config.get("stay_logged_in"): self.config.unset("username") self.config.unset("refresh_token") # Interact with the API self.__authenticate() self.HeaderBar.set_subtitle(self.api.get_user_info()) self.sync_library() self.show_all() @Gtk.Template.Callback("on_header_sync_clicked") def sync_library(self, button=None): # Make a list with the game tiles which are already loaded current_tiles = [] for child in self.library.get_children(): tile = child.get_children()[0] current_tiles.append(tile) # Recheck online status if button and self.offline: self.__authenticate() add_tiles_thread = threading.Thread(target=self.__add_tiles, args=[current_tiles]) add_tiles_thread.daemon = True add_tiles_thread.start() @Gtk.Template.Callback("on_header_installed_state_set") def show_installed_only_triggered(self, switch, state): self.show_installed_only = state self.library.set_filter_func(self.__filter_library_func) @Gtk.Template.Callback("on_header_search_changed") def search(self, widget): self.search_string = widget.get_text() self.library.set_filter_func(self.__filter_library_func) @Gtk.Template.Callback("on_menu_preferences_clicked") def show_preferences(self, button): preferences_window = Preferences(self, self.config) preferences_window.run() preferences_window.destroy() @Gtk.Template.Callback("on_menu_about_clicked") def show_about(self, button): about_window = About(self) about_window.run() about_window.destroy() @Gtk.Template.Callback("on_menu_logout_clicked") def logout(self, button): # Unset everything which is specific to this user self.HeaderBar.set_subtitle("") self.config.unset("username") self.config.unset("refresh_token") self.hide() # Show the login screen self.__authenticate() self.HeaderBar.set_subtitle(self.api.get_user_info()) self.sync_library() self.show_all() def refresh_game_install_states(self, path_changed=False): installed_games = self.__get_installed_games() for child in self.library.get_children(): tile = child.get_children()[0] if path_changed: tile.install_dir = "" # Check if game isn't installed already for installed_game in installed_games: if installed_game["name"] == tile.game.name: tile.install_dir = installed_game["dir"] break tile.load_state() self.filter_library() def filter_library(self): self.library.set_filter_func(self.__filter_library_func) def __filter_library_func(self, child): tile = child.get_children()[0] if not tile.installed and self.offline: return False if tile.installed and self.show_installed_only or not self.show_installed_only: if self.search_string.lower() in str(tile).lower(): return True else: return False else: return False def sort_library(self): self.library.set_sort_func(self.__sort_library_func) def __sort_library_func(self, child1, child2): tile1 = child1.get_children()[0] tile2 = child2.get_children()[0] return tile2 < tile1 def __update_library_view(self): self.sort_library() self.filter_library() self.library.show_all() def __add_tiles(self, current_tiles): # Refresh games list from API games = [] if not self.offline: try: games = self.api.get_library() except: GLib.idle_add(self.__show_error, _("Failed to retrieve library"), _("Couldn't connect to GOG servers")) self.offline = True # Get installed games installed_games = self.__get_installed_games() # Only add games if they aren't already in the list. Otherwise just reload their state if not self.offline: for game in games: not_found = True for tile in current_tiles: if self.__games_match(tile.game.name, game.name): not_found = False tile.game = game break if not_found: # Check if game is already installed install_dir = "" for installed_game in installed_games: if installed_game["name"] == game.name: print("Found game: {}".format(game.name)) install_dir = installed_game["dir"] break # Create the game tile GLib.idle_add(self.__add_tile, game, install_dir) else: for game in installed_games: not_found = True for tile in current_tiles: if tile.game.name == game["name"]: not_found = False break if not_found: # Create the game tile GLib.idle_add(self.__add_tile, Game(game["name"], 0, ""), game["dir"]) GLib.idle_add(self.__update_library_view) def __show_error(self, text, subtext): dialog = Gtk.MessageDialog(message_type=Gtk.MessageType.ERROR, parent=self, modal=True, buttons=Gtk.ButtonsType.CLOSE, text=text) dialog.format_secondary_text(subtext) dialog.run() dialog.destroy() def __add_tile(self, game, install_dir): # Create the game tile gametile = GameTile( parent=self, game=game, api=self.api, install_dir=install_dir, ) gametile.load_state() # Update the library self.library.add(gametile) self.__update_library_view() def __get_installed_games(self) -> dict: games = [] directories = os.listdir(self.config.get("install_dir")) for directory in directories: full_path = os.path.join(self.config.get("install_dir"), directory) if not os.path.isdir(full_path): continue # Make sure the gameinfo file exists gameinfo = os.path.join(full_path, "gameinfo") if not os.path.isfile(gameinfo): continue with open(gameinfo, 'r') as file: name = file.readline() games.append({'name': name.strip(), 'dir': full_path}) file.close() return games def __clean_game_name(self, name): return re.sub('[^A-Za-z0-9]+', '', name).lower() def __games_match(self, name1, name2): name1_clean = self.__clean_game_name(name1) name2_clean = self.__clean_game_name(name2) return name1_clean == name2_clean """ The API remembers the authentication token and uses it The token is not valid for a long time """ def __authenticate(self): url = None token = self.config.get("refresh_token") # Make sure there is an internet connection, but only once if self.api.can_connect(): self.offline = False else: if token: self.offline = True dialog = Gtk.MessageDialog( message_type=Gtk.MessageType.ERROR, parent=self, modal=True, buttons=Gtk.ButtonsType.OK, text=_("Couldn't connect to GOG servers")) dialog.format_secondary_text( _("Minigalaxy is now running in offline mode")) dialog.run() dialog.destroy() return else: dialog = Gtk.MessageDialog( message_type=Gtk.MessageType.ERROR, parent=self, modal=True, buttons=Gtk.ButtonsType.CLOSE, text=_("Couldn't connect to GOG servers")) dialog.format_secondary_text( _("Try again with an active internet connection")) dialog.run() dialog.destroy() exit(1) return authenticated = self.api.authenticate(refresh_token=token, login_code=url) while not authenticated: login_url = self.api.get_login_url() redirect_url = self.api.get_redirect_url() login = Login(login_url=login_url, redirect_url=redirect_url, parent=self) response = login.run() login.hide() if response == Gtk.ResponseType.DELETE_EVENT: Gtk.main_quit() exit(0) if response == Gtk.ResponseType.NONE: result = login.get_result() authenticated = self.api.authenticate(refresh_token=token, login_code=result) self.config.set("refresh_token", authenticated)
def __save_language_choice(self) -> None: lang_choice = self.combobox_language.get_active_iter() if lang_choice is not None: model = self.combobox_language.get_model() lang, _ = model[lang_choice][:2] Config.set("lang", lang)
def filter_library(self, switch, _=""): self.library.filter_library(switch) if switch == self.header_installed: Config.set("installed_filter", switch.get_active())
def filter_library(self, switch, _=""): self.library.filter_library(switch) Config.set("installed_filter", switch.get_active())
def on_window_state_event(self, widget, event): if event.new_window_state & Gdk.WindowState.MAXIMIZED: Config.set("keep_window_maximized", True) else: Config.set("keep_window_maximized", False)