def __download_file(self, download): self.prepare_location(download.save_location) download_max_attempts = 5 download_attempt = 0 result = False while download_attempt < download_max_attempts: try: start_point, download_mode = self.get_start_point_and_download_mode( download) result = self.download_operation(download, start_point, download_mode) break except ConnectionError as e: print(e) download_attempt += 1 # Successful downloads if result: if download.number == download.out_of_amount: finish_thread = threading.Thread(target=download.finish) finish_thread.start() if self.__queue.empty(): Config.unset("current_download") # Unsuccessful downloads and cancels else: self.__cancel = False download.cancel() self.__current_download = None os.remove(download.save_location)
def __init__(self, name="Minigalaxy"): Gtk.ApplicationWindow.__init__(self, title=name) self.api = Api() self.search_string = "" self.offline = False # Set library self.library = Library(self, self.api) self.window_library.add(self.library) self.header_installed.set_active(Config.get("installed_filter")) # Set the icon icon = GdkPixbuf.Pixbuf.new_from_file(LOGO_IMAGE_PATH) self.set_default_icon_list([icon]) # Show the window if Config.get("keep_window_maximized"): self.maximize() self.show_all() # Create the thumbnails directory if not os.path.exists(THUMBNAIL_DIR): os.makedirs(THUMBNAIL_DIR, mode=0o755) # Interact with the API self.__authenticate() self.HeaderBar.set_subtitle(self.api.get_user_info()) self.sync_library()
def __get_installed_games(self) -> List[Game]: games = [] directories = os.listdir(Config.get("install_dir")) for directory in directories: full_path = os.path.join(Config.get("install_dir"), directory) # Only scan directories 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().strip() version = file.readline().strip() version_dev = file.readline().strip() language = file.readline().strip() game_id = file.readline().strip() if not game_id: game_id = 0 else: game_id = int(game_id) games.append( Game(name=name, game_id=game_id, install_dir=full_path)) return games
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()
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 __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 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 remove_installer(game, installer): error_message = "" installer_directory = os.path.dirname(installer) if not os.path.isdir(installer_directory): error_message = "No installer directory is present: {}".format( installer_directory) return error_message if Config.get("keep_installers"): keep_dir = os.path.join(Config.get("install_dir"), "installer") keep_dir2 = os.path.join(keep_dir, game.get_install_directory_name()) if keep_dir2 == installer_directory: # We are using the keep installer already return error_message if not compare_directories(installer_directory, keep_dir2): shutil.rmtree(keep_dir2, ignore_errors=True) try: shutil.move(installer_directory, keep_dir2) except Exception as e: error_message = str(e) else: os.remove(installer) return error_message
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 __init__(self, parent): Gtk.Dialog.__init__(self, title=_("Preferences"), parent=parent, modal=True) self.parent = parent self.__set_language_list() self.button_file_chooser.set_filename(Config.get("install_dir")) self.switch_keep_installers.set_active(Config.get("keep_installers")) self.switch_stay_logged_in.set_active(Config.get("stay_logged_in")) self.switch_show_fps.set_active(Config.get("show_fps")) self.switch_show_windows_games.set_active( Config.get("show_windows_games")) # Set tooltip for keep installers label installer_dir = os.path.join(self.button_file_chooser.get_filename(), "installer") self.label_keep_installers.set_tooltip_text( _("Keep installers after downloading a game.\nInstallers are stored in: {}" ).format(installer_dir)) # Only allow showing Windows games if wine is available if not shutil.which("wine"): self.switch_show_windows_games.set_sensitive(False) self.switch_show_windows_games.set_tooltip_text( _("Install Wine to enable this feature"))
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 __get_installed_games(self) -> List[Game]: # Make sure the install directory exists library_dir = Config.get("install_dir") if not os.path.exists(library_dir): os.makedirs(library_dir, mode=0o755) directories = os.listdir(library_dir) games = [] for directory in directories: full_path = os.path.join(Config.get("install_dir"), directory) # Only scan directories if not os.path.isdir(full_path): continue # Make sure the gameinfo file exists gameinfo = os.path.join(full_path, "gameinfo") if os.path.isfile(gameinfo): with open(gameinfo, 'r') as file: name = file.readline().strip() version = file.readline().strip() # noqa: F841 version_dev = file.readline().strip() # noqa: F841 language = file.readline().strip() # noqa: F841 game_id = file.readline().strip() if not game_id: game_id = 0 else: game_id = int(game_id) games.append( Game(name=name, game_id=game_id, install_dir=full_path)) else: games.extend(get_installed_windows_games(full_path)) return games
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_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 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 __set_fps_display(): # Enable FPS Counter for Nvidia or AMD (Mesa) users if Config.get("show_fps"): os.environ["__GL_SHOW_GRAPHICS_OSD"] = "1" # For Nvidia users os.environ["GALLIUM_HUD"] = "simple,fps" # For AMDGPU users elif Config.get("show_fps") is False: os.environ["__GL_SHOW_GRAPHICS_OSD"] = "0" # For Nvidia users os.environ["GALLIUM_HUD"] = ""
def __set_fps_display(): # Enable FPS Counter for Nvidia or AMD (Mesa) users if Config.get("show_fps"): os.environ["__GL_SHOW_GRAPHICS_OSD"] = "1" # For Nvidia users + OpenGL/Vulkan games os.environ["GALLIUM_HUD"] = "simple,fps" # For AMDGPU users + OpenGL games os.environ["VK_INSTANCE_LAYERS"] = "VK_LAYER_MESA_overlay" # For AMDGPU users + Vulkan games elif Config.get("show_fps") is False: os.environ["__GL_SHOW_GRAPHICS_OSD"] = "0" os.environ["GALLIUM_HUD"] = "" os.environ["VK_INSTANCE_LAYERS"] = ""
def test_unset(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.unset("lang") lang = Config.get("lang") exp = None 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 install_game(game, installer, parent_window=None) -> None: if not os.path.exists(installer): GLib.idle_add(__show_installation_error, game, _("{} failed to download.").format(installer), parent_window) raise FileNotFoundError("The installer {} does not exist".format(installer)) if not __verify_installer_integrity(installer): GLib.idle_add(__show_installation_error, game, _("{} was corrupted. Please download it again.").format(installer), parent_window) os.remove(installer) raise FileNotFoundError("The installer {} was corrupted".format(installer)) # Make a temporary empty directory for extracting the installer temp_dir = os.path.join(CACHE_DIR, "extract/{}".format(game.id)) if os.path.exists(temp_dir): shutil.rmtree(temp_dir, ignore_errors=True) os.makedirs(temp_dir) # Extract the installer try: with zipfile.ZipFile(installer) as archive: for member in archive.namelist(): file = archive.getinfo(member) archive.extract(file, temp_dir) # Set permissions attr = file.external_attr >> 16 os.chmod(os.path.join(temp_dir, member), attr) except zipfile.BadZipFile as e: GLib.idle_add(__show_installation_error, game, _("{} could not be unzipped.").format(installer), parent_window) raise e # Make sure the install directory exists library_dir = Config.get("install_dir") if not os.path.exists(library_dir): os.makedirs(library_dir) # Copy the game files into the correct directory shutil.move(os.path.join(temp_dir, "data/noarch"), game.install_dir) shutil.copyfile( os.path.join(THUMBNAIL_DIR, "{}.jpg".format(game.id)), os.path.join(game.install_dir, "thumbnail.jpg"), ) # Remove the temporary directory shutil.rmtree(temp_dir, ignore_errors=True) if Config.get("keep_installers"): keep_dir = os.path.join(Config.get("install_dir"), "installer") if not os.path.exists(keep_dir): os.makedirs(keep_dir) try: os.rename(installer, os.path.join(keep_dir, "{}.sh".format(game.name))) except Exception as ex: print("Encountered error while copying {} to {}. Got error: {}".format(installer, keep_dir, ex)) else: os.remove(installer)
def get_download_info(self): try: download_info = self.api.get_download_info(self.game) result = True except NoDownloadLinkFound as e: print(e) 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!\n{}".format(e))) download_info = False result = False return result, download_info
def logout(self, button): # Unset everything which is specific to this user self.HeaderBar.set_subtitle("") Config.unset("username") 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 __init__(self, name="Minigalaxy"): current_locale = Config.get("locale") default_locale = locale.getdefaultlocale()[0] if current_locale == '': locale.setlocale(locale.LC_ALL, (default_locale, 'UTF-8')) else: try: locale.setlocale(locale.LC_ALL, (current_locale, 'UTF-8')) except NameError: locale.setlocale(locale.LC_ALL, (default_locale, 'UTF-8')) Gtk.ApplicationWindow.__init__(self, title=name) self.api = Api() self.search_string = "" self.offline = False # Set library self.library = Library(self, self.api) self.window_library.add(self.library) self.header_installed.set_active(Config.get("installed_filter")) # Set the icon icon = GdkPixbuf.Pixbuf.new_from_file(LOGO_IMAGE_PATH) self.set_default_icon_list([icon]) # Set theme settings = Gtk.Settings.get_default() 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) # Show the window if Config.get("keep_window_maximized"): self.maximize() self.show_all() self.make_directories() # Interact with the API self.offline = not self.api.can_connect() if not self.offline: try: self.__authenticate() self.HeaderBar.set_subtitle(self.api.get_user_info()) except Exception as e: print( "Starting in offline mode, after receiving exception: {}". format(e)) self.offline = True self.sync_library()
def __init__(self, parent, game): current_locale = Config.get("locale") default_locale = locale.getdefaultlocale()[0] if current_locale == '': locale.setlocale(locale.LC_ALL, (default_locale, 'UTF-8')) else: try: locale.setlocale(locale.LC_ALL, (current_locale, 'UTF-8')) except NameError: locale.setlocale(locale.LC_ALL, (default_locale, 'UTF-8')) Gtk.Frame.__init__(self) Gtk.StyleContext.add_provider(self.button.get_style_context(), CSS_PROVIDER, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) self.parent = parent self.game = game self.api = parent.api self.offline = parent.offline self.progress_bar = None self.thumbnail_set = False self.download_list = [] self.dlc_dict = {} self.current_state = self.state.DOWNLOADABLE self.image.set_tooltip_text(self.game.name) self.game_label.set_label(self.game.name) # Set folder for download installer self.download_dir = os.path.join( CACHE_DIR, "download", self.game.get_install_directory_name()) # Set folder if user wants to keep installer (disabled by default) self.keep_dir = os.path.join(Config.get("install_dir"), "installer") self.keep_path = os.path.join(self.keep_dir, self.game.get_install_directory_name()) if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR, mode=0o755) self.reload_state() load_thumbnail_thread = threading.Thread(target=self.load_thumbnail) load_thumbnail_thread.start() # Start download if Minigalaxy was closed while downloading this game self.resume_download_if_expected() # Icon for Windows games if self.game.platform == "windows": self.image.set_tooltip_text("{} (Wine)".format(self.game.name)) self.wine_icon.set_from_file(ICON_WINE_PATH) self.wine_icon.show()
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 __download_file(self, download): # Make sure the directory exists save_directory = os.path.dirname(download.save_location) if not os.path.isdir(save_directory): os.makedirs(save_directory, mode=0o755) # Fail if the file already exists if os.path.isdir(download.save_location): raise IsADirectoryError("{} is a directory".format(download.save_location)) # Resume the previous download if possible start_point = 0 download_mode = 'wb' if os.path.isfile(download.save_location): if self.__is_same_download_as_before(download): print("Resuming download {}".format(download.save_location)) download_mode = 'ab' start_point = os.stat(download.save_location).st_size else: os.remove(download.save_location) # Download the file resume_header = {'Range': 'bytes={}-'.format(start_point)} download_request = SESSION.get(download.url, headers=resume_header, stream=True) downloaded_size = start_point file_size = int(download_request.headers.get('content-length')) if downloaded_size < file_size: with open(download.save_location, download_mode) as save_file: for chunk in download_request.iter_content(chunk_size=DOWNLOAD_CHUNK_SIZE): # Pause if needed while self.__paused: time.sleep(0.1) save_file.write(chunk) downloaded_size += len(chunk) if self.__cancel: self.__cancel = False save_file.close() download.cancel() self.__current_download = None return if file_size > 0: progress = int(downloaded_size / file_size * 100) download.set_progress(progress) save_file.close() if download.number == download.out_of_amount: finish_thread = threading.Thread(target=download.finish) finish_thread.start() if self.__queue.empty(): Config.unset("current_download")
def remove_installer(game, installer): error_message = "" if Config.get("keep_installers"): keep_dir = os.path.join(Config.get("install_dir"), "installer") keep_dir2 = os.path.join(keep_dir, game.get_install_directory_name()) keep_file = os.path.join(keep_dir2, os.path.basename(installer)) if not os.path.isdir(keep_dir2): os.makedirs(keep_dir2) shutil.move(installer, keep_file) installer_directory = os.path.dirname(installer) if os.path.isdir(installer_directory): shutil.rmtree(installer_directory, ignore_errors=True) else: error_message = "No installer directory is present: {}".format(installer_directory) return error_message
def __init__(self, parent): Gtk.Dialog.__init__(self, title=_("Preferences"), parent=parent, modal=True) self.parent = parent self.__set_language_list() self.button_file_chooser.set_filename(Config.get("install_dir")) self.switch_keep_installers.set_active(Config.get("keep_installers")) self.switch_stay_logged_in.set_active(Config.get("stay_logged_in")) self.switch_show_fps.set_active(Config.get("show_fps")) self.switch_show_windows_games.set_active(Config.get("show_windows_games")) # Set tooltip for keep installers label installer_dir = os.path.join(self.button_file_chooser.get_filename(), "installer") self.label_keep_installers.set_tooltip_text( _("Keep installers after downloading a game.\nInstallers are stored in: {}").format(installer_dir) )
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 get_download_info(self, game: Game, operating_system="linux", dlc_installers="") -> dict: if dlc_installers: installers = dlc_installers else: response = self.get_info(game) installers = response["downloads"]["installers"] possible_downloads = [] for installer in installers: if installer["os"] == operating_system: possible_downloads.append(installer) if not possible_downloads: if operating_system == "linux": return self.get_download_info(game, "windows") else: raise NoDownloadLinkFound( "Error: {} with id {} couldn't be installed".format( game.name, game.id)) download_info = possible_downloads[0] for installer in possible_downloads: if installer['language'] == Config.get("lang"): download_info = installer break if installer['language'] == "en": download_info = installer # Return last entry in possible_downloads. This will either be English or the first langauge in the list # This is just a backup, if the preferred language has been found, this part won't execute return download_info