Пример #1
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)
Пример #2
0
 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)
Пример #3
0
    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
Пример #4
0
    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
Пример #5
0
    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
Пример #6
0
 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
Пример #7
0
 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)
Пример #8
0
 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)
Пример #9
0
 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)
Пример #10
0
    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()
Пример #11
0
    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()
Пример #12
0
 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.")
Пример #13
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)
Пример #14
0
    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()
Пример #15
0
    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
Пример #16
0
    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)
Пример #17
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)
Пример #18
0
 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)
Пример #19
0
 def filter_library(self, switch, _=""):
     self.library.filter_library(switch)
     if switch == self.header_installed:
         Config.set("installed_filter", switch.get_active())
Пример #20
0
 def filter_library(self, switch, _=""):
     self.library.filter_library(switch)
     Config.set("installed_filter", switch.get_active())
Пример #21
0
 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)