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 __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 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 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 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 __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 __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 prevent_resume_on_startup(self): download_id = Config.get("current_download") if download_id and download_id == self.game.id: Config.unset("current_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)