Exemple #1
0
 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)
Exemple #2
0
    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)
Exemple #4
0
    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()
Exemple #5
0
 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")
Exemple #7
0
    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)
Exemple #8
0
 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")
Exemple #9
0
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)