From 592134e8592497e966733575e0c8bc2b26de4401 Mon Sep 17 00:00:00 2001 From: tkashkin Date: Sun, 22 Jul 2018 17:53:23 +0300 Subject: [PATCH] Downloader rewrite: now it's possible to pause and cancel downloads Downloads can be resumed after interruption UI improvements Former-commit-id: e2f0c9e2a7cee9a862a025efe44028060bc5de1e --- data/GameHub.css | 31 ++- ...com.github.tkashkin.gamehub.appdata.xml.in | 7 + debian/changelog | 8 + meson.build | 2 +- po/POTFILES | 4 +- po/com.github.tkashkin.gamehub.pot | 164 ++++++++--- po/de.po | 177 +++++++++--- po/id.po | 176 +++++++++--- po/pl.po | 174 +++++++++--- po/pt_BR.po | 175 +++++++++--- po/ru.po | 174 +++++++++--- po/uk.po | 178 +++++++++--- src/data/Game.vala | 34 ++- src/data/sources/gog/GOGGame.vala | 40 ++- src/data/sources/humble/HumbleGame.vala | 37 ++- src/data/sources/steam/Steam.vala | 22 +- src/data/sources/steam/SteamGame.vala | 5 +- src/meson.build | 10 +- src/ui/dialogs/GameInstallDialog.vala | 4 +- src/ui/dialogs/SettingsDialog.vala | 38 ++- .../GameDetailsView/GameDetailsView.vala | 84 +++++- src/ui/views/GamesView/GameCard.vala | 10 +- .../GamesView/GameDownloadProgressView.vala | 70 ++++- src/ui/views/GamesView/GameListRow.vala | 1 + src/ui/views/GamesView/GamesView.vala | 186 +++++++++++-- src/ui/views/WelcomeView.vala | 11 +- src/utils/Downloader.vala | 247 ----------------- src/utils/FSUtils.vala | 4 +- src/utils/Settings.vala | 16 +- src/utils/Utils.vala | 9 +- src/utils/downloader/Downloader.vala | 154 +++++++++++ src/utils/downloader/SoupDownloader.vala | 261 ++++++++++++++++++ 32 files changed, 1880 insertions(+), 633 deletions(-) delete mode 100644 src/utils/Downloader.vala create mode 100644 src/utils/downloader/Downloader.vala create mode 100644 src/utils/downloader/SoupDownloader.vala diff --git a/data/GameHub.css b/data/GameHub.css index c8081579..3d62c58a 100644 --- a/data/GameHub.css +++ b/data/GameHub.css @@ -132,7 +132,7 @@ button label { border: none; border-color: transparent; - box-shadow: 0 0 0 1px alpha (#000, 0.05), 0 3px 3px alpha (#000, 0.22); + box-shadow: 0 0 0 1px alpha(#000, 0.05), 0 3px 3px alpha(#000, 0.22); border-radius: 4px; } list:not(:backdrop) row:selected:focus label.category-label @@ -143,4 +143,33 @@ list:not(:backdrop) row:selected:focus label.category-label list.installers-list { background: transparent; +} + +list.downloads-list row +{ + background: transparent; + border-bottom: 1px alpha(#333, 0.3) solid; + outline: none; +} +list.downloads-list row:last-child +{ + border-bottom: none; +} +list.downloads-list row:selected +{ + background: transparent; + outline: none; +} +list.downloads-list row:focus +{ + background: transparent; + outline: none; +} + +label.games-list-header +{ + background: alpha(#333, 0.1); + margin-top: -1px; + border-top: 1px alpha(#333, 0.3) solid; + border-bottom: 1px alpha(#333, 0.3) solid; } \ No newline at end of file diff --git a/data/com.github.tkashkin.gamehub.appdata.xml.in b/data/com.github.tkashkin.gamehub.appdata.xml.in index 6fbf7cf4..d41b0d78 100644 --- a/data/com.github.tkashkin.gamehub.appdata.xml.in +++ b/data/com.github.tkashkin.gamehub.appdata.xml.in @@ -41,6 +41,13 @@ + + +

Downloader rewrite: now it's possible to pause and cancel downloads

+

Downloads can be resumed after interruption

+

UI improvements

+
+

Yet another locale fix

diff --git a/debian/changelog b/debian/changelog index 1a2dd5d6..c65fb33c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +com.github.tkashkin.gamehub (0.6.0) xenial; urgency=low + + * Downloader rewrite: now it's possible to pause and cancel downloads + * Downloads can be resumed after interruption + * UI improvements + + -- tkashkin Sun, 22 Jul 2018 16:18:24 +0300 + com.github.tkashkin.gamehub (0.5.7) xenial; urgency=low * Yet another locale fix diff --git a/meson.build b/meson.build index 0b2417bc..99486f04 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('com.github.tkashkin.gamehub', 'vala', 'c', version: '0.5.7') +project('com.github.tkashkin.gamehub', 'vala', 'c', version: '0.6.0') i18n = import('i18n') gnome = import('gnome') diff --git a/po/POTFILES b/po/POTFILES index 288e8a23..54279a8e 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -42,4 +42,6 @@ src/utils/Utils.vala src/utils/FSUtils.vala src/utils/Parser.vala src/utils/Settings.vala -src/utils/Downloader.vala \ No newline at end of file + +src/utils/downloader/Downloader.vala +src/utils/downloader/SoupDownloader.vala \ No newline at end of file diff --git a/po/com.github.tkashkin.gamehub.pot b/po/com.github.tkashkin.gamehub.pot index 3386de87..c7c86302 100644 --- a/po/com.github.tkashkin.gamehub.pot +++ b/po/com.github.tkashkin.gamehub.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.github.tkashkin.gamehub\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-21 01:58+0300\n" +"POT-Creation-Date: 2018-07-22 17:49+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -42,21 +42,28 @@ msgstr "" msgid "Installing" msgstr "" -#: src/data/Game.vala:77 +#: src/data/Game.vala:77 src/utils/downloader/Downloader.vala:137 msgid "Download started" msgstr "" -#: src/data/Game.vala:78 -msgid "Download finished" +#: src/data/Game.vala:79 +msgid "Not installed" msgstr "" -#: src/data/Game.vala:81 -#, c-format -msgid "Downloading: %d%% (%s / %s)" +#: src/data/Game.vala:89 +msgid "Installed:" msgstr "" -#: src/data/Game.vala:83 -msgid "Not installed" +#: src/data/Game.vala:90 +msgid "Installing:" +msgstr "" + +#: src/data/Game.vala:91 +msgid "Downloading:" +msgstr "" + +#: src/data/Game.vala:93 +msgid "Not installed:" msgstr "" #: src/data/sources/steam/Steam.vala:17 @@ -70,7 +77,8 @@ msgid "" msgstr "" #: src/ui/dialogs/SettingsDialog.vala:13 src/ui/views/WelcomeView.vala:34 -#: src/ui/views/WelcomeView.vala:59 src/ui/views/GamesView/GamesView.vala:143 +#: src/ui/views/WelcomeView.vala:58 src/ui/views/GamesView/GamesView.vala:158 +#: src/ui/views/GamesView/GamesView.vala:355 msgid "Settings" msgstr "" @@ -86,10 +94,6 @@ msgstr "" msgid "Generate key" msgstr "" -#: src/ui/dialogs/SettingsDialog.vala:35 -msgid "Steam API key" -msgstr "" - #: src/ui/dialogs/SettingsDialog.vala:36 msgid "Steam installation directory" msgstr "" @@ -106,7 +110,7 @@ msgstr "" msgid "Humble Bundle games directory" msgstr "" -#: src/ui/dialogs/SettingsDialog.vala:49 +#: src/ui/dialogs/SettingsDialog.vala:50 msgid "Humble Bundle installers cache" msgstr "" @@ -115,15 +119,27 @@ msgstr "" msgid "Close" msgstr "" -#: src/ui/dialogs/SettingsDialog.vala:180 +#: src/ui/dialogs/SettingsDialog.vala:106 +msgid "Default" +msgstr "" + +#: src/ui/dialogs/SettingsDialog.vala:114 +msgid "Restore default API key" +msgstr "" + +#: src/ui/dialogs/SettingsDialog.vala:125 +msgid "Steam API key" +msgstr "" + +#: src/ui/dialogs/SettingsDialog.vala:215 msgid "Open" msgstr "" -#: src/ui/dialogs/SettingsDialog.vala:187 +#: src/ui/dialogs/SettingsDialog.vala:222 msgid "Clear" msgstr "" -#: src/ui/dialogs/SettingsDialog.vala:203 +#: src/ui/dialogs/SettingsDialog.vala:238 #, c-format msgid "%llu installer; %s" msgid_plural "%llu installers; %s" @@ -132,7 +148,7 @@ msgstr[1] "" #: src/ui/dialogs/GameInstallDialog.vala:31 #: src/ui/dialogs/GameInstallDialog.vala:131 -#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:202 msgid "Install" msgstr "" @@ -169,83 +185,155 @@ msgstr "" msgid "Skip" msgstr "" -#: src/ui/views/WelcomeView.vala:115 +#: src/ui/views/WelcomeView.vala:116 msgid "Ready" msgstr "" -#: src/ui/views/WelcomeView.vala:121 +#: src/ui/views/WelcomeView.vala:122 msgid "Authentication required" msgstr "" -#: src/ui/views/WelcomeView.vala:126 +#: src/ui/views/WelcomeView.vala:127 msgid "Authenticating..." msgstr "" -#: src/ui/views/WelcomeView.vala:137 +#: src/ui/views/WelcomeView.vala:138 #, c-format msgid "Install %s" msgstr "" -#: src/ui/views/WelcomeView.vala:138 +#: src/ui/views/WelcomeView.vala:139 msgid "Return to GameHub after installing" msgstr "" -#: src/ui/views/GamesView/GamesView.vala:92 +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "No games" +msgstr "" + +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "Get some games or enable some game sources in settings" +msgstr "" + +#: src/ui/views/GamesView/GamesView.vala:63 +msgid "Reload" +msgstr "" + +#: src/ui/views/GamesView/GamesView.vala:108 msgid "Grid view" msgstr "" -#: src/ui/views/GamesView/GamesView.vala:93 +#: src/ui/views/GamesView/GamesView.vala:109 msgid "List view" msgstr "" -#: src/ui/views/GamesView/GamesView.vala:107 +#: src/ui/views/GamesView/GamesView.vala:121 msgid "All games" msgstr "" -#: src/ui/views/GamesView/GamesView.vala:111 +#: src/ui/views/GamesView/GamesView.vala:125 #, c-format msgid "%s games" msgstr "" -#: src/ui/views/GamesView/GamesView.vala:117 +#: src/ui/views/GamesView/GamesView.vala:131 msgid "Downloads" msgstr "" -#: src/ui/views/GamesView/GamesView.vala:138 +#: src/ui/views/GamesView/GamesView.vala:153 msgid "Search" msgstr "" -#: src/ui/views/GamesView/GamesView.vala:194 +#: src/ui/views/GamesView/GamesView.vala:277 #, c-format msgid "%u game" msgid_plural "%u games" msgstr[0] "" msgstr[1] "" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:122 +#: src/ui/views/GamesView/GamesView.vala:281 +#, c-format +msgid "No %s games" +msgstr "" + +#: src/ui/views/GamesView/GamesView.vala:282 +msgid "Get some Linux-compatible games" +msgstr "" + +#: src/ui/views/GamesView/GamesView.vala:353 +msgid "" +"No games were loaded from Steam. Set your games list privacy to public or " +"use your own Steam API key in settings." +msgstr "" + +#: src/ui/views/GamesView/GamesView.vala:354 +msgid "Privacy" +msgstr "" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:59 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:129 +msgid "Pause download" +msgstr "" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:64 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:135 +msgid "Resume download" +msgstr "" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:69 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:141 +msgid "Cancel download" +msgstr "" + +#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 msgid "Description" msgstr "" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:163 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:203 msgid "Run" msgstr "" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:164 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:204 msgid "Open installation directory" msgstr "" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:165 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:205 msgid "Open store page" msgstr "" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:166 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:206 msgid "Uninstall" msgstr "" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:235 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:307 msgid "Language" msgstr "" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:238 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:310 msgid "Languages" msgstr "" + +#: src/utils/downloader/Downloader.vala:136 +msgid "Starting download" +msgstr "" + +#: src/utils/downloader/Downloader.vala:138 +msgid "Download finished" +msgstr "" + +#: src/utils/downloader/Downloader.vala:139 +msgid "Download failed" +msgstr "" + +#: src/utils/downloader/Downloader.vala:141 +#, c-format +msgid "Downloading: %d%% (%s / %s)" +msgstr "" + +#: src/utils/downloader/Downloader.vala:143 +#, c-format +msgid "Paused: %d%% (%s / %s)" +msgstr "" + +#: src/utils/downloader/Downloader.vala:145 +msgid "Download cancelled" +msgstr "" diff --git a/po/de.po b/po/de.po index b1e2a444..f7f4abb4 100644 --- a/po/de.po +++ b/po/de.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.github.tkashkin.gamehub\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-21 01:58+0300\n" +"POT-Creation-Date: 2018-07-22 17:49+0300\n" "PO-Revision-Date: 2018-07-14 00:51+0300\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -41,23 +41,30 @@ msgstr "Installiert" msgid "Installing" msgstr "Wird installiert" -#: src/data/Game.vala:77 +#: src/data/Game.vala:77 src/utils/downloader/Downloader.vala:137 msgid "Download started" msgstr "Download ist gestartet" -#: src/data/Game.vala:78 -msgid "Download finished" -msgstr "Download ist beendet" - -#: src/data/Game.vala:81 -#, c-format -msgid "Downloading: %d%% (%s / %s)" -msgstr "Herunterladen: %d%% (%s / %s)" - -#: src/data/Game.vala:83 +#: src/data/Game.vala:79 msgid "Not installed" msgstr "Nicht installiert" +#: src/data/Game.vala:89 +msgid "Installed:" +msgstr "Installiert:" + +#: src/data/Game.vala:90 +msgid "Installing:" +msgstr "Wird installiert:" + +#: src/data/Game.vala:91 +msgid "Downloading:" +msgstr "Wird heruntergeladen:" + +#: src/data/Game.vala:93 +msgid "Not installed:" +msgstr "Nicht installiert:" + #: src/data/sources/steam/Steam.vala:17 msgid "Your SteamID will be read from Steam configuration file" msgstr "Ihre SteamID wird von der Steam-Konfigurationsdatei gelesen" @@ -71,7 +78,8 @@ msgstr "" "Logge dich in dein Konto im Steam-Client ein und kehre zu GameHub zurück" #: src/ui/dialogs/SettingsDialog.vala:13 src/ui/views/WelcomeView.vala:34 -#: src/ui/views/WelcomeView.vala:59 src/ui/views/GamesView/GamesView.vala:143 +#: src/ui/views/WelcomeView.vala:58 src/ui/views/GamesView/GamesView.vala:158 +#: src/ui/views/GamesView/GamesView.vala:355 msgid "Settings" msgstr "Einstellungen" @@ -88,10 +96,6 @@ msgstr "" msgid "Generate key" msgstr "Schlüssel generieren" -#: src/ui/dialogs/SettingsDialog.vala:35 -msgid "Steam API key" -msgstr "Steam-API-Schlüssel" - #: src/ui/dialogs/SettingsDialog.vala:36 msgid "Steam installation directory" msgstr "Steam-Installationsordner" @@ -108,7 +112,7 @@ msgstr "GOG Installateure Cache" msgid "Humble Bundle games directory" msgstr "Humble Bundle Spieleordner" -#: src/ui/dialogs/SettingsDialog.vala:49 +#: src/ui/dialogs/SettingsDialog.vala:50 msgid "Humble Bundle installers cache" msgstr "Humble Bundle Installateure Cache" @@ -117,15 +121,28 @@ msgstr "Humble Bundle Installateure Cache" msgid "Close" msgstr "Schließen" -#: src/ui/dialogs/SettingsDialog.vala:180 +#: src/ui/dialogs/SettingsDialog.vala:106 +msgid "Default" +msgstr "Standard" + +#: src/ui/dialogs/SettingsDialog.vala:114 +msgid "Restore default API key" +msgstr "Wiederherstellen des Standard-API-Schlüssels" + +#: src/ui/dialogs/SettingsDialog.vala:125 +msgid "Steam API key" +msgstr "Steam-API-Schlüssel" + +#: src/ui/dialogs/SettingsDialog.vala:215 msgid "Open" msgstr "Öffnen" -#: src/ui/dialogs/SettingsDialog.vala:187 +#: src/ui/dialogs/SettingsDialog.vala:222 msgid "Clear" msgstr "Löschen" -#: src/ui/dialogs/SettingsDialog.vala:203 +#: src/ui/dialogs/SettingsDialog.vala:238 +#, c-format msgid "%llu installer; %s" msgid_plural "%llu installers; %s" msgstr[0] "%llu Installer; %s" @@ -133,7 +150,7 @@ msgstr[1] "%llu Installateure; %s" #: src/ui/dialogs/GameInstallDialog.vala:31 #: src/ui/dialogs/GameInstallDialog.vala:131 -#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:202 msgid "Install" msgstr "Installieren" @@ -142,6 +159,7 @@ msgid "Select game installer" msgstr "Wähle das Spiel-Installationsprogramm aus" #: src/ui/dialogs/GameInstallDialog.vala:92 +#, c-format msgid "Installer size: %s" msgstr "Größe des Installationsprogramms: %s" @@ -169,87 +187,164 @@ msgstr "Lass uns anfangen" msgid "Skip" msgstr "Überspringen" -#: src/ui/views/WelcomeView.vala:115 +#: src/ui/views/WelcomeView.vala:116 msgid "Ready" msgstr "Bereit" -#: src/ui/views/WelcomeView.vala:121 +#: src/ui/views/WelcomeView.vala:122 msgid "Authentication required" msgstr "Authentifizierung erforderlich" -#: src/ui/views/WelcomeView.vala:126 +#: src/ui/views/WelcomeView.vala:127 msgid "Authenticating..." msgstr "Authentifizieren..." -#: src/ui/views/WelcomeView.vala:137 +#: src/ui/views/WelcomeView.vala:138 #, c-format msgid "Install %s" msgstr "%s installieren" -#: src/ui/views/WelcomeView.vala:138 +#: src/ui/views/WelcomeView.vala:139 msgid "Return to GameHub after installing" msgstr "Zurück zu GameHub nach der Installation" -#: src/ui/views/GamesView/GamesView.vala:92 +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "No games" +msgstr "Keine Spiele" + +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "Get some games or enable some game sources in settings" +msgstr "" +"Holen Sie sich einige Spiele oder aktivieren Sie einige Spielquellen in den " +"Einstellungen" + +#: src/ui/views/GamesView/GamesView.vala:63 +msgid "Reload" +msgstr "Neu laden" + +#: src/ui/views/GamesView/GamesView.vala:108 msgid "Grid view" msgstr "Rasteransicht" -#: src/ui/views/GamesView/GamesView.vala:93 +#: src/ui/views/GamesView/GamesView.vala:109 msgid "List view" msgstr "Listenansicht" -#: src/ui/views/GamesView/GamesView.vala:107 +#: src/ui/views/GamesView/GamesView.vala:121 msgid "All games" msgstr "Alle Spiele" -#: src/ui/views/GamesView/GamesView.vala:111 +#: src/ui/views/GamesView/GamesView.vala:125 #, c-format msgid "%s games" msgstr "Spiele von %s" -#: src/ui/views/GamesView/GamesView.vala:117 +#: src/ui/views/GamesView/GamesView.vala:131 msgid "Downloads" msgstr "Downloads" -#: src/ui/views/GamesView/GamesView.vala:138 +#: src/ui/views/GamesView/GamesView.vala:153 msgid "Search" msgstr "Suche" -#: src/ui/views/GamesView/GamesView.vala:194 +#: src/ui/views/GamesView/GamesView.vala:277 #, c-format msgid "%u game" msgid_plural "%u games" msgstr[0] "%u Spiel" msgstr[1] "%u Spiele" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:122 +#: src/ui/views/GamesView/GamesView.vala:281 +#, c-format +msgid "No %s games" +msgstr "Keine Spiele von %s" + +#: src/ui/views/GamesView/GamesView.vala:282 +msgid "Get some Linux-compatible games" +msgstr "Holen Sie sich einige Linux-kompatible Spiele" + +#: src/ui/views/GamesView/GamesView.vala:353 +msgid "" +"No games were loaded from Steam. Set your games list privacy to public or " +"use your own Steam API key in settings." +msgstr "" +"Es wurden keine Spiele von Steam geladen. Richte deine Spieleliste auf die " +"Öffentlichkeit oder benutze deinen eigenen Steam-API-Schlüssel in den " +"Einstellungen." + +#: src/ui/views/GamesView/GamesView.vala:354 +msgid "Privacy" +msgstr "Datenschutz" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:59 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:129 +msgid "Pause download" +msgstr "Download pausieren" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:64 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:135 +msgid "Resume download" +msgstr "Download fortsetzen" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:69 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:141 +msgid "Cancel download" +msgstr "Download abbrechen" + +#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 msgid "Description" msgstr "Beschreibung" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:163 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:203 msgid "Run" msgstr "Ausführen" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:164 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:204 msgid "Open installation directory" msgstr "Installationsordner öffnen" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:165 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:205 msgid "Open store page" msgstr "Shopseite öffnen" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:166 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:206 msgid "Uninstall" msgstr "Deinstallieren" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:235 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:307 msgid "Language" msgstr "Sprache" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:238 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:310 msgid "Languages" msgstr "Sprachen" +#: src/utils/downloader/Downloader.vala:136 +msgid "Starting download" +msgstr "Download wird gestartet" + +#: src/utils/downloader/Downloader.vala:138 +msgid "Download finished" +msgstr "Download ist beendet" + +#: src/utils/downloader/Downloader.vala:139 +msgid "Download failed" +msgstr "Download ist fehlgeschlagen" + +#: src/utils/downloader/Downloader.vala:141 +#, c-format +msgid "Downloading: %d%% (%s / %s)" +msgstr "Herunterladen: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:143 +#, c-format +msgid "Paused: %d%% (%s / %s)" +msgstr "Pausiert: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:145 +msgid "Download cancelled" +msgstr "Download ist abgebrochen" + #~ msgid "Select main executable of the game" #~ msgstr "Wählen Sie die Hauptdatei des Spiels" diff --git a/po/id.po b/po/id.po index 9d6249d5..52ab9551 100644 --- a/po/id.po +++ b/po/id.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.github.tkashkin.gamehub\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-21 01:58+0300\n" +"POT-Creation-Date: 2018-07-22 17:49+0300\n" "PO-Revision-Date: 2018-07-14 22:01+0700\n" "Last-Translator: Kukuh Syafaat \n" "Language-Team: \n" @@ -42,23 +42,30 @@ msgstr "Terpasang" msgid "Installing" msgstr "Memasang" -#: src/data/Game.vala:77 +#: src/data/Game.vala:77 src/utils/downloader/Downloader.vala:137 msgid "Download started" msgstr "Unduhan dimulai" -#: src/data/Game.vala:78 -msgid "Download finished" -msgstr "Unduhan selesai" - -#: src/data/Game.vala:81 -#, c-format -msgid "Downloading: %d%% (%s / %s)" -msgstr "Mengunduh: %d%% (%s / %s)" - -#: src/data/Game.vala:83 +#: src/data/Game.vala:79 msgid "Not installed" msgstr "Tidak terpasang" +#: src/data/Game.vala:89 +msgid "Installed:" +msgstr "Terpasang:" + +#: src/data/Game.vala:90 +msgid "Installing:" +msgstr "Memasang:" + +#: src/data/Game.vala:91 +msgid "Downloading:" +msgstr "Unduh:" + +#: src/data/Game.vala:93 +msgid "Not installed:" +msgstr "Tidak terpasang:" + #: src/data/sources/steam/Steam.vala:17 msgid "Your SteamID will be read from Steam configuration file" msgstr "SteamID Anda akan dibaca dari berkas konfigurasi Steam" @@ -72,7 +79,8 @@ msgstr "" "Masuk ke akun Anda di klien Steam dan kembali ke GameHub" #: src/ui/dialogs/SettingsDialog.vala:13 src/ui/views/WelcomeView.vala:34 -#: src/ui/views/WelcomeView.vala:59 src/ui/views/GamesView/GamesView.vala:143 +#: src/ui/views/WelcomeView.vala:58 src/ui/views/GamesView/GamesView.vala:158 +#: src/ui/views/GamesView/GamesView.vala:355 msgid "Settings" msgstr "Pengaturan" @@ -88,10 +96,6 @@ msgstr "Kunci API Steam memiliki jumlah penggunaan terbatas per hari" msgid "Generate key" msgstr "Hasilkan kunci" -#: src/ui/dialogs/SettingsDialog.vala:35 -msgid "Steam API key" -msgstr "Kunci API Steam" - #: src/ui/dialogs/SettingsDialog.vala:36 msgid "Steam installation directory" msgstr "Direktori instalasi Steam" @@ -108,7 +112,7 @@ msgstr "Penginstal cache GOG" msgid "Humble Bundle games directory" msgstr "Direktori permainan Humble Bundle" -#: src/ui/dialogs/SettingsDialog.vala:49 +#: src/ui/dialogs/SettingsDialog.vala:50 msgid "Humble Bundle installers cache" msgstr "Penginstal cache Humble Bundle" @@ -117,22 +121,35 @@ msgstr "Penginstal cache Humble Bundle" msgid "Close" msgstr "Tutup" -#: src/ui/dialogs/SettingsDialog.vala:180 +#: src/ui/dialogs/SettingsDialog.vala:106 +msgid "Default" +msgstr "" + +#: src/ui/dialogs/SettingsDialog.vala:114 +msgid "Restore default API key" +msgstr "Kembalikan kunci API default" + +#: src/ui/dialogs/SettingsDialog.vala:125 +msgid "Steam API key" +msgstr "Kunci API Steam" + +#: src/ui/dialogs/SettingsDialog.vala:215 msgid "Open" msgstr "Buka" -#: src/ui/dialogs/SettingsDialog.vala:187 +#: src/ui/dialogs/SettingsDialog.vala:222 msgid "Clear" msgstr "Bersih" -#: src/ui/dialogs/SettingsDialog.vala:203 +#: src/ui/dialogs/SettingsDialog.vala:238 +#, c-format msgid "%llu installer; %s" msgid_plural "%llu installers; %s" msgstr[0] "%llu pemasang; %s" #: src/ui/dialogs/GameInstallDialog.vala:31 #: src/ui/dialogs/GameInstallDialog.vala:131 -#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:202 msgid "Install" msgstr "Pasang" @@ -141,6 +158,7 @@ msgid "Select game installer" msgstr "Pilih pemasang permainan" #: src/ui/dialogs/GameInstallDialog.vala:92 +#, c-format msgid "Installer size: %s" msgstr "Ukuran penginstal: %s" @@ -168,86 +186,162 @@ msgstr "Mari kita mulai" msgid "Skip" msgstr "Lewati" -#: src/ui/views/WelcomeView.vala:115 +#: src/ui/views/WelcomeView.vala:116 msgid "Ready" msgstr "Siap" -#: src/ui/views/WelcomeView.vala:121 +#: src/ui/views/WelcomeView.vala:122 msgid "Authentication required" msgstr "Diperlukan autentikasi" -#: src/ui/views/WelcomeView.vala:126 +#: src/ui/views/WelcomeView.vala:127 msgid "Authenticating..." msgstr "Mengautentikasi..." -#: src/ui/views/WelcomeView.vala:137 +#: src/ui/views/WelcomeView.vala:138 #, c-format msgid "Install %s" msgstr "Pasang %s" -#: src/ui/views/WelcomeView.vala:138 +#: src/ui/views/WelcomeView.vala:139 msgid "Return to GameHub after installing" msgstr "Kembali ke GameHub setelah memasang" -#: src/ui/views/GamesView/GamesView.vala:92 +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "No games" +msgstr "Tidak ada permainan" + +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "Get some games or enable some game sources in settings" +msgstr "" +"Dapatkan beberapa permainan atau aktifkan beberapa sumber permainan di " +"pengaturan" + +#: src/ui/views/GamesView/GamesView.vala:63 +msgid "Reload" +msgstr "Muat ulang" + +#: src/ui/views/GamesView/GamesView.vala:108 msgid "Grid view" msgstr "Tilikan kisi" -#: src/ui/views/GamesView/GamesView.vala:93 +#: src/ui/views/GamesView/GamesView.vala:109 msgid "List view" msgstr "Tilikan daftar" -#: src/ui/views/GamesView/GamesView.vala:107 +#: src/ui/views/GamesView/GamesView.vala:121 msgid "All games" msgstr "Semua permainan" -#: src/ui/views/GamesView/GamesView.vala:111 +#: src/ui/views/GamesView/GamesView.vala:125 #, c-format msgid "%s games" msgstr "%s permainan" -#: src/ui/views/GamesView/GamesView.vala:117 +#: src/ui/views/GamesView/GamesView.vala:131 msgid "Downloads" msgstr "Unduh" -#: src/ui/views/GamesView/GamesView.vala:138 +#: src/ui/views/GamesView/GamesView.vala:153 msgid "Search" msgstr "Cari" -#: src/ui/views/GamesView/GamesView.vala:194 +#: src/ui/views/GamesView/GamesView.vala:277 #, c-format msgid "%u game" msgid_plural "%u games" msgstr[0] "%u permainan" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:122 +#: src/ui/views/GamesView/GamesView.vala:281 +#, c-format +msgid "No %s games" +msgstr "Tidak ada %s permainan" + +#: src/ui/views/GamesView/GamesView.vala:282 +msgid "Get some Linux-compatible games" +msgstr "Dapatkan beberapa permainan Linux yang kompatibel" + +#: src/ui/views/GamesView/GamesView.vala:353 +msgid "" +"No games were loaded from Steam. Set your games list privacy to public or " +"use your own Steam API key in settings." +msgstr "" +"Tidak ada permainan yang dimuat dari Steam. Setel privasi daftar permainan " +"anda ke publik atau gunakan kunci API Steam anda sendiri dalam pengaturan." + +#: src/ui/views/GamesView/GamesView.vala:354 +msgid "Privacy" +msgstr "Privasi" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:59 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:129 +msgid "Pause download" +msgstr "Jeda unduhan" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:64 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:135 +msgid "Resume download" +msgstr "Lanjutkan unduhan" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:69 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:141 +msgid "Cancel download" +msgstr "Batalkan unduhan" + +#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 msgid "Description" msgstr "Deskripsi" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:163 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:203 msgid "Run" msgstr "Jalankan" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:164 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:204 msgid "Open installation directory" msgstr "Buka direktori pemasangan" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:165 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:205 msgid "Open store page" msgstr "Buka halaman toko" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:166 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:206 msgid "Uninstall" msgstr "Copot pemasangan" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:235 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:307 msgid "Language" msgstr "Bahasa" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:238 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:310 msgid "Languages" msgstr "Bahasa" +#: src/utils/downloader/Downloader.vala:136 +msgid "Starting download" +msgstr "Mulai mengunduh" + +#: src/utils/downloader/Downloader.vala:138 +msgid "Download finished" +msgstr "Unduhan selesai" + +#: src/utils/downloader/Downloader.vala:139 +msgid "Download failed" +msgstr "Unduhan gagal" + +#: src/utils/downloader/Downloader.vala:141 +#, c-format +msgid "Downloading: %d%% (%s / %s)" +msgstr "Mengunduh: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:143 +#, c-format +msgid "Paused: %d%% (%s / %s)" +msgstr "Dijeda: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:145 +msgid "Download cancelled" +msgstr "Unduhan dibatalkan" + #~ msgid "Select main executable of the game" #~ msgstr "Pilih main executable dari permainan" diff --git a/po/pl.po b/po/pl.po index 4179cb30..4894f106 100644 --- a/po/pl.po +++ b/po/pl.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.github.tkashkin.gamehub\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-21 01:58+0300\n" +"POT-Creation-Date: 2018-07-22 17:49+0300\n" "PO-Revision-Date: 2018-07-14 00:51+0300\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -42,23 +42,30 @@ msgstr "Zainstalowana" msgid "Installing" msgstr "Instalacja" -#: src/data/Game.vala:77 +#: src/data/Game.vala:77 src/utils/downloader/Downloader.vala:137 msgid "Download started" msgstr "Pobieranie rozpoczęte" -#: src/data/Game.vala:78 -msgid "Download finished" -msgstr "Pobieranie zakończone" - -#: src/data/Game.vala:81 -#, c-format -msgid "Downloading: %d%% (%s / %s)" -msgstr "Ściąganie: %d%% (%s / %s)" - -#: src/data/Game.vala:83 +#: src/data/Game.vala:79 msgid "Not installed" msgstr "Nie zainstalowana" +#: src/data/Game.vala:89 +msgid "Installed:" +msgstr "Zainstalowani:" + +#: src/data/Game.vala:90 +msgid "Installing:" +msgstr "Instalowanie:" + +#: src/data/Game.vala:91 +msgid "Downloading:" +msgstr "Ściąganie:" + +#: src/data/Game.vala:93 +msgid "Not installed:" +msgstr "Nie zainstalowani:" + #: src/data/sources/steam/Steam.vala:17 msgid "Your SteamID will be read from Steam configuration file" msgstr "Twój SteamID zostanie odczytany z pliku konfiguracyjnego Steam" @@ -72,7 +79,8 @@ msgstr "" "Zaloguj się do swojego konta w kliencie Steam i wróć do GameHub" #: src/ui/dialogs/SettingsDialog.vala:13 src/ui/views/WelcomeView.vala:34 -#: src/ui/views/WelcomeView.vala:59 src/ui/views/GamesView/GamesView.vala:143 +#: src/ui/views/WelcomeView.vala:58 src/ui/views/GamesView/GamesView.vala:158 +#: src/ui/views/GamesView/GamesView.vala:355 msgid "Settings" msgstr "Ustawienia" @@ -88,10 +96,6 @@ msgstr "Klucz Steam API mają ograniczoną liczbę zastosowań dziennie" msgid "Generate key" msgstr "Wygeneruj klucz" -#: src/ui/dialogs/SettingsDialog.vala:35 -msgid "Steam API key" -msgstr "Klucz Steam API" - #: src/ui/dialogs/SettingsDialog.vala:36 msgid "Steam installation directory" msgstr "Katalog instalacji Steam" @@ -108,7 +112,7 @@ msgstr "Pamięć podręczna instalatorów GOG" msgid "Humble Bundle games directory" msgstr "Katalog gier Humble Bundle" -#: src/ui/dialogs/SettingsDialog.vala:49 +#: src/ui/dialogs/SettingsDialog.vala:50 msgid "Humble Bundle installers cache" msgstr "Pamięć podręczna instalatorów Humble Bundle" @@ -117,15 +121,28 @@ msgstr "Pamięć podręczna instalatorów Humble Bundle" msgid "Close" msgstr "Zamknij" -#: src/ui/dialogs/SettingsDialog.vala:180 +#: src/ui/dialogs/SettingsDialog.vala:106 +msgid "Default" +msgstr "Domyślna" + +#: src/ui/dialogs/SettingsDialog.vala:114 +msgid "Restore default API key" +msgstr "Przywróć domyślny klucz API" + +#: src/ui/dialogs/SettingsDialog.vala:125 +msgid "Steam API key" +msgstr "Klucz Steam API" + +#: src/ui/dialogs/SettingsDialog.vala:215 msgid "Open" msgstr "Otwórz" -#: src/ui/dialogs/SettingsDialog.vala:187 +#: src/ui/dialogs/SettingsDialog.vala:222 msgid "Clear" msgstr "Oczyścić" -#: src/ui/dialogs/SettingsDialog.vala:203 +#: src/ui/dialogs/SettingsDialog.vala:238 +#, c-format msgid "%llu installer; %s" msgid_plural "%llu installers; %s" msgstr[0] "%llu instalator; %s" @@ -134,7 +151,7 @@ msgstr[2] "%llu instalatorów; %s" #: src/ui/dialogs/GameInstallDialog.vala:31 #: src/ui/dialogs/GameInstallDialog.vala:131 -#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:202 msgid "Install" msgstr "Instaluj" @@ -143,6 +160,7 @@ msgid "Select game installer" msgstr "Wybierz instalator gry" #: src/ui/dialogs/GameInstallDialog.vala:92 +#, c-format msgid "Installer size: %s" msgstr "Rozmiar instalatora: %s" @@ -170,53 +188,65 @@ msgstr "Zacznijmy" msgid "Skip" msgstr "Pominąć" -#: src/ui/views/WelcomeView.vala:115 +#: src/ui/views/WelcomeView.vala:116 msgid "Ready" msgstr "Gotowe" -#: src/ui/views/WelcomeView.vala:121 +#: src/ui/views/WelcomeView.vala:122 msgid "Authentication required" msgstr "Wymagane uwierzytelnienie" -#: src/ui/views/WelcomeView.vala:126 +#: src/ui/views/WelcomeView.vala:127 msgid "Authenticating..." msgstr "Uwierzytelnianie..." -#: src/ui/views/WelcomeView.vala:137 +#: src/ui/views/WelcomeView.vala:138 #, c-format msgid "Install %s" msgstr "Instaluj %s" -#: src/ui/views/WelcomeView.vala:138 +#: src/ui/views/WelcomeView.vala:139 msgid "Return to GameHub after installing" msgstr "Wróć do GameHub po instalacji" -#: src/ui/views/GamesView/GamesView.vala:92 +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "No games" +msgstr "Brak gier" + +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "Get some games or enable some game sources in settings" +msgstr "Pobierz gry lub włącz niektóre źródła gier w ustawieniach" + +#: src/ui/views/GamesView/GamesView.vala:63 +msgid "Reload" +msgstr "Przeładować" + +#: src/ui/views/GamesView/GamesView.vala:108 msgid "Grid view" msgstr "Widok siatki" -#: src/ui/views/GamesView/GamesView.vala:93 +#: src/ui/views/GamesView/GamesView.vala:109 msgid "List view" msgstr "Widok listy" -#: src/ui/views/GamesView/GamesView.vala:107 +#: src/ui/views/GamesView/GamesView.vala:121 msgid "All games" msgstr "Wszystkie gry" -#: src/ui/views/GamesView/GamesView.vala:111 +#: src/ui/views/GamesView/GamesView.vala:125 #, c-format msgid "%s games" msgstr "Gry z %s" -#: src/ui/views/GamesView/GamesView.vala:117 +#: src/ui/views/GamesView/GamesView.vala:131 msgid "Downloads" msgstr "Pliki do pobrania" -#: src/ui/views/GamesView/GamesView.vala:138 +#: src/ui/views/GamesView/GamesView.vala:153 msgid "Search" msgstr "Szukaj" -#: src/ui/views/GamesView/GamesView.vala:194 +#: src/ui/views/GamesView/GamesView.vala:277 #, c-format msgid "%u game" msgid_plural "%u games" @@ -224,34 +254,96 @@ msgstr[0] "%u gra" msgstr[1] "%u gry" msgstr[2] "%u gier" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:122 +#: src/ui/views/GamesView/GamesView.vala:281 +#, c-format +msgid "No %s games" +msgstr "Brak gier z %s" + +#: src/ui/views/GamesView/GamesView.vala:282 +msgid "Get some Linux-compatible games" +msgstr "Zdobądź gry kompatybilne z Linuksem" + +#: src/ui/views/GamesView/GamesView.vala:353 +msgid "" +"No games were loaded from Steam. Set your games list privacy to public or " +"use your own Steam API key in settings." +msgstr "" +"Żadne gry nie zostały załadowane ze Steam. Ustaw prywatność listy gier jako " +"publiczną lub użyj swojego własnego klucza Steam API w ustawieniach." + +#: src/ui/views/GamesView/GamesView.vala:354 +msgid "Privacy" +msgstr "Prywatność" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:59 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:129 +msgid "Pause download" +msgstr "Wstrzymaj pobieranie" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:64 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:135 +msgid "Resume download" +msgstr "Wznów pobieranie" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:69 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:141 +msgid "Cancel download" +msgstr "Anuluj pobieranie" + +#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 msgid "Description" msgstr "Opis" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:163 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:203 msgid "Run" msgstr "Uruchom" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:164 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:204 msgid "Open installation directory" msgstr "Otwórz katalog instalacyjny" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:165 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:205 msgid "Open store page" msgstr "Otwórz stronę sklepu" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:166 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:206 msgid "Uninstall" msgstr "Odinstaluj" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:235 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:307 msgid "Language" msgstr "Język" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:238 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:310 msgid "Languages" msgstr "Języki" +#: src/utils/downloader/Downloader.vala:136 +msgid "Starting download" +msgstr "Rozpoczynanie pobierania" + +#: src/utils/downloader/Downloader.vala:138 +msgid "Download finished" +msgstr "Pobieranie zakończone" + +#: src/utils/downloader/Downloader.vala:139 +msgid "Download failed" +msgstr "Pobieranie nie udane" + +#: src/utils/downloader/Downloader.vala:141 +#, c-format +msgid "Downloading: %d%% (%s / %s)" +msgstr "Ściąganie: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:143 +#, c-format +msgid "Paused: %d%% (%s / %s)" +msgstr "Wstrzymane: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:145 +msgid "Download cancelled" +msgstr "Pobieranie zostało anulowane" + #~ msgid "Select main executable of the game" #~ msgstr "Wybierz główny plik wykonywalny gry" diff --git a/po/pt_BR.po b/po/pt_BR.po index 308e8a0c..a54595a0 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.github.tkashkin.gamehub\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-21 01:58+0300\n" +"POT-Creation-Date: 2018-07-22 17:49+0300\n" "PO-Revision-Date: 2018-07-13 16:18+0300\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -41,23 +41,30 @@ msgstr "Instalado" msgid "Installing" msgstr "Instalando" -#: src/data/Game.vala:77 +#: src/data/Game.vala:77 src/utils/downloader/Downloader.vala:137 msgid "Download started" msgstr "Começou a baixar" -#: src/data/Game.vala:78 -msgid "Download finished" -msgstr "Jogo baixado com sucesso" - -#: src/data/Game.vala:81 -#, c-format -msgid "Downloading: %d%% (%s / %s)" -msgstr "Baixando: %d%% (%s / %s)" - -#: src/data/Game.vala:83 +#: src/data/Game.vala:79 msgid "Not installed" msgstr "Não instalado" +#: src/data/Game.vala:89 +msgid "Installed:" +msgstr "Instalado:" + +#: src/data/Game.vala:90 +msgid "Installing:" +msgstr "Instalando:" + +#: src/data/Game.vala:91 +msgid "Downloading:" +msgstr "Baixado:" + +#: src/data/Game.vala:93 +msgid "Not installed:" +msgstr "Não instalado:" + #: src/data/sources/steam/Steam.vala:17 msgid "Your SteamID will be read from Steam configuration file" msgstr "Seu SteamID será lido a partir do arquivo de configuração do Steam" @@ -71,7 +78,8 @@ msgstr "" "Entre na sua conta no cliente Steam e retorne ao GameHub" #: src/ui/dialogs/SettingsDialog.vala:13 src/ui/views/WelcomeView.vala:34 -#: src/ui/views/WelcomeView.vala:59 src/ui/views/GamesView/GamesView.vala:143 +#: src/ui/views/WelcomeView.vala:58 src/ui/views/GamesView/GamesView.vala:158 +#: src/ui/views/GamesView/GamesView.vala:355 msgid "Settings" msgstr "Configurações" @@ -88,10 +96,6 @@ msgstr "" msgid "Generate key" msgstr "Gerar chave" -#: src/ui/dialogs/SettingsDialog.vala:35 -msgid "Steam API key" -msgstr "Chave da API do Steam" - #: src/ui/dialogs/SettingsDialog.vala:36 msgid "Steam installation directory" msgstr "Diretório de instalação do Steam" @@ -108,7 +112,7 @@ msgstr "Cache de instaladores do GOG" msgid "Humble Bundle games directory" msgstr "Diretório de jogos Humble Bundle" -#: src/ui/dialogs/SettingsDialog.vala:49 +#: src/ui/dialogs/SettingsDialog.vala:50 msgid "Humble Bundle installers cache" msgstr "Cache de instaladores do Humble Bundle" @@ -117,15 +121,28 @@ msgstr "Cache de instaladores do Humble Bundle" msgid "Close" msgstr "Fechar" -#: src/ui/dialogs/SettingsDialog.vala:180 +#: src/ui/dialogs/SettingsDialog.vala:106 +msgid "Default" +msgstr "Padrão" + +#: src/ui/dialogs/SettingsDialog.vala:114 +msgid "Restore default API key" +msgstr "Restaurar chave de API padrão" + +#: src/ui/dialogs/SettingsDialog.vala:125 +msgid "Steam API key" +msgstr "Chave da API do Steam" + +#: src/ui/dialogs/SettingsDialog.vala:215 msgid "Open" msgstr "Abrir" -#: src/ui/dialogs/SettingsDialog.vala:187 +#: src/ui/dialogs/SettingsDialog.vala:222 msgid "Clear" msgstr "Limpar" -#: src/ui/dialogs/SettingsDialog.vala:203 +#: src/ui/dialogs/SettingsDialog.vala:238 +#, c-format msgid "%llu installer; %s" msgid_plural "%llu installers; %s" msgstr[0] "%llu instalador; %s" @@ -133,7 +150,7 @@ msgstr[1] "%llu instaladores; %s" #: src/ui/dialogs/GameInstallDialog.vala:31 #: src/ui/dialogs/GameInstallDialog.vala:131 -#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:202 msgid "Install" msgstr "Instalar" @@ -142,6 +159,7 @@ msgid "Select game installer" msgstr "Selecione o instalador do jogo" #: src/ui/dialogs/GameInstallDialog.vala:92 +#, c-format msgid "Installer size: %s" msgstr "Tamanho do instalador: %s" @@ -169,87 +187,162 @@ msgstr "Vamos começar" msgid "Skip" msgstr "Pular" -#: src/ui/views/WelcomeView.vala:115 +#: src/ui/views/WelcomeView.vala:116 msgid "Ready" msgstr "Pronto" -#: src/ui/views/WelcomeView.vala:121 +#: src/ui/views/WelcomeView.vala:122 msgid "Authentication required" msgstr "Autentificação requerida" -#: src/ui/views/WelcomeView.vala:126 +#: src/ui/views/WelcomeView.vala:127 msgid "Authenticating..." msgstr "Autenticando..." -#: src/ui/views/WelcomeView.vala:137 +#: src/ui/views/WelcomeView.vala:138 #, c-format msgid "Install %s" msgstr "Instalar %s" -#: src/ui/views/WelcomeView.vala:138 +#: src/ui/views/WelcomeView.vala:139 msgid "Return to GameHub after installing" msgstr "Retornar ao GameHub depois de instalar" -#: src/ui/views/GamesView/GamesView.vala:92 +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "No games" +msgstr "Sem jogos" + +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "Get some games or enable some game sources in settings" +msgstr "" +"Obtenha alguns jogos ou ative algumas fontes de jogos nas configurações" + +#: src/ui/views/GamesView/GamesView.vala:63 +msgid "Reload" +msgstr "Recarregar" + +#: src/ui/views/GamesView/GamesView.vala:108 msgid "Grid view" msgstr "Visualizar grade" -#: src/ui/views/GamesView/GamesView.vala:93 +#: src/ui/views/GamesView/GamesView.vala:109 msgid "List view" msgstr "Visualizar lista" -#: src/ui/views/GamesView/GamesView.vala:107 +#: src/ui/views/GamesView/GamesView.vala:121 msgid "All games" msgstr "Todos os jogos" -#: src/ui/views/GamesView/GamesView.vala:111 +#: src/ui/views/GamesView/GamesView.vala:125 #, c-format msgid "%s games" msgstr "%s jogos" -#: src/ui/views/GamesView/GamesView.vala:117 +#: src/ui/views/GamesView/GamesView.vala:131 msgid "Downloads" msgstr "Baixado" -#: src/ui/views/GamesView/GamesView.vala:138 +#: src/ui/views/GamesView/GamesView.vala:153 msgid "Search" msgstr "Pesquisar" -#: src/ui/views/GamesView/GamesView.vala:194 +#: src/ui/views/GamesView/GamesView.vala:277 #, c-format msgid "%u game" msgid_plural "%u games" msgstr[0] "%u jogo" msgstr[1] "%u jogos" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:122 +#: src/ui/views/GamesView/GamesView.vala:281 +#, c-format +msgid "No %s games" +msgstr "Nenhum jogo do %s" + +#: src/ui/views/GamesView/GamesView.vala:282 +msgid "Get some Linux-compatible games" +msgstr "Obtenha alguns jogos compatíveis com o Linux" + +#: src/ui/views/GamesView/GamesView.vala:353 +msgid "" +"No games were loaded from Steam. Set your games list privacy to public or " +"use your own Steam API key in settings." +msgstr "" +"Nenhum jogo foi carregado do Steam. Defina a privacidade da lista de jogos " +"como pública ou use sua própria chave de API do Steam nas configurações." + +#: src/ui/views/GamesView/GamesView.vala:354 +msgid "Privacy" +msgstr "Privacidade" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:59 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:129 +msgid "Pause download" +msgstr "Pausar o download" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:64 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:135 +msgid "Resume download" +msgstr "Retomar o download" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:69 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:141 +msgid "Cancel download" +msgstr "Cancelar o download" + +#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 msgid "Description" msgstr "Descrição" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:163 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:203 msgid "Run" msgstr "Correr" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:164 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:204 msgid "Open installation directory" msgstr "Abra o diretório de instalação" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:165 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:205 msgid "Open store page" msgstr "Abra a página da loja" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:166 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:206 msgid "Uninstall" msgstr "Desinstalar" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:235 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:307 msgid "Language" msgstr "Língua" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:238 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:310 msgid "Languages" msgstr "Línguas" +#: src/utils/downloader/Downloader.vala:136 +msgid "Starting download" +msgstr "Iniciando download" + +#: src/utils/downloader/Downloader.vala:138 +msgid "Download finished" +msgstr "Jogo baixado com sucesso" + +#: src/utils/downloader/Downloader.vala:139 +msgid "Download failed" +msgstr "Download falhou" + +#: src/utils/downloader/Downloader.vala:141 +#, c-format +msgid "Downloading: %d%% (%s / %s)" +msgstr "Baixando: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:143 +#, c-format +msgid "Paused: %d%% (%s / %s)" +msgstr "Pausado: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:145 +msgid "Download cancelled" +msgstr "Download cancelado" + #~ msgid "Select main executable of the game" #~ msgstr "Selecione o executável principal do jogo" diff --git a/po/ru.po b/po/ru.po index 077a59e8..35ed1662 100644 --- a/po/ru.po +++ b/po/ru.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.github.tkashkin.gamehub\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-21 01:58+0300\n" +"POT-Creation-Date: 2018-07-22 17:49+0300\n" "PO-Revision-Date: 2018-05-27 03:39+0300\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -42,23 +42,30 @@ msgstr "Установлена" msgid "Installing" msgstr "Установка" -#: src/data/Game.vala:77 +#: src/data/Game.vala:77 src/utils/downloader/Downloader.vala:137 msgid "Download started" msgstr "Загрузка начата" -#: src/data/Game.vala:78 -msgid "Download finished" -msgstr "Загрузка завершена" - -#: src/data/Game.vala:81 -#, c-format -msgid "Downloading: %d%% (%s / %s)" -msgstr "Загрузка: %d%% (%s / %s)" - -#: src/data/Game.vala:83 +#: src/data/Game.vala:79 msgid "Not installed" msgstr "Не установлена" +#: src/data/Game.vala:89 +msgid "Installed:" +msgstr "Установленые:" + +#: src/data/Game.vala:90 +msgid "Installing:" +msgstr "Установка:" + +#: src/data/Game.vala:91 +msgid "Downloading:" +msgstr "Загрузки:" + +#: src/data/Game.vala:93 +msgid "Not installed:" +msgstr "Не установленые:" + #: src/data/sources/steam/Steam.vala:17 msgid "Your SteamID will be read from Steam configuration file" msgstr "Ваш SteamID будет прочитан из файла конфигурации Steam" @@ -72,7 +79,8 @@ msgstr "" "Войдите в ваш аккаунт в клиенте Steam и вернитесь в GameHub" #: src/ui/dialogs/SettingsDialog.vala:13 src/ui/views/WelcomeView.vala:34 -#: src/ui/views/WelcomeView.vala:59 src/ui/views/GamesView/GamesView.vala:143 +#: src/ui/views/WelcomeView.vala:58 src/ui/views/GamesView/GamesView.vala:158 +#: src/ui/views/GamesView/GamesView.vala:355 msgid "Settings" msgstr "Настройки" @@ -88,10 +96,6 @@ msgstr "API-ключи Steam имеют ограничение на количе msgid "Generate key" msgstr "Сгенерировать ключ" -#: src/ui/dialogs/SettingsDialog.vala:35 -msgid "Steam API key" -msgstr "API-ключ Steam" - #: src/ui/dialogs/SettingsDialog.vala:36 msgid "Steam installation directory" msgstr "Папка установки Steam" @@ -108,7 +112,7 @@ msgstr "Кэш установщиков GOG" msgid "Humble Bundle games directory" msgstr "Папка игр Humble Bundle" -#: src/ui/dialogs/SettingsDialog.vala:49 +#: src/ui/dialogs/SettingsDialog.vala:50 msgid "Humble Bundle installers cache" msgstr "Кэш установщиков Humble Bundle" @@ -117,15 +121,28 @@ msgstr "Кэш установщиков Humble Bundle" msgid "Close" msgstr "Закрыть" -#: src/ui/dialogs/SettingsDialog.vala:180 +#: src/ui/dialogs/SettingsDialog.vala:106 +msgid "Default" +msgstr "По умолчанию" + +#: src/ui/dialogs/SettingsDialog.vala:114 +msgid "Restore default API key" +msgstr "Восстановить API-ключ по умолчанию" + +#: src/ui/dialogs/SettingsDialog.vala:125 +msgid "Steam API key" +msgstr "API-ключ Steam" + +#: src/ui/dialogs/SettingsDialog.vala:215 msgid "Open" msgstr "Открыть" -#: src/ui/dialogs/SettingsDialog.vala:187 +#: src/ui/dialogs/SettingsDialog.vala:222 msgid "Clear" msgstr "Очистить" -#: src/ui/dialogs/SettingsDialog.vala:203 +#: src/ui/dialogs/SettingsDialog.vala:238 +#, c-format msgid "%llu installer; %s" msgid_plural "%llu installers; %s" msgstr[0] "%llu установщик; %s" @@ -134,7 +151,7 @@ msgstr[2] "%llu установщиков; %s" #: src/ui/dialogs/GameInstallDialog.vala:31 #: src/ui/dialogs/GameInstallDialog.vala:131 -#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:202 msgid "Install" msgstr "Установить" @@ -143,6 +160,7 @@ msgid "Select game installer" msgstr "Выберите установочный файл игры" #: src/ui/dialogs/GameInstallDialog.vala:92 +#, c-format msgid "Installer size: %s" msgstr "Размер установщика: %s" @@ -170,53 +188,65 @@ msgstr "Давайте начнём" msgid "Skip" msgstr "Пропустить" -#: src/ui/views/WelcomeView.vala:115 +#: src/ui/views/WelcomeView.vala:116 msgid "Ready" msgstr "Готово" -#: src/ui/views/WelcomeView.vala:121 +#: src/ui/views/WelcomeView.vala:122 msgid "Authentication required" msgstr "Требуется авторизация" -#: src/ui/views/WelcomeView.vala:126 +#: src/ui/views/WelcomeView.vala:127 msgid "Authenticating..." msgstr "Авторизация..." -#: src/ui/views/WelcomeView.vala:137 +#: src/ui/views/WelcomeView.vala:138 #, c-format msgid "Install %s" msgstr "Установить %s" -#: src/ui/views/WelcomeView.vala:138 +#: src/ui/views/WelcomeView.vala:139 msgid "Return to GameHub after installing" msgstr "Вернитесь в GameHub после установки" -#: src/ui/views/GamesView/GamesView.vala:92 +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "No games" +msgstr "Нет игр" + +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "Get some games or enable some game sources in settings" +msgstr "Получите игры или включите источники в настройках" + +#: src/ui/views/GamesView/GamesView.vala:63 +msgid "Reload" +msgstr "Обновить" + +#: src/ui/views/GamesView/GamesView.vala:108 msgid "Grid view" msgstr "Сетка" -#: src/ui/views/GamesView/GamesView.vala:93 +#: src/ui/views/GamesView/GamesView.vala:109 msgid "List view" msgstr "Список" -#: src/ui/views/GamesView/GamesView.vala:107 +#: src/ui/views/GamesView/GamesView.vala:121 msgid "All games" msgstr "Все игры" -#: src/ui/views/GamesView/GamesView.vala:111 +#: src/ui/views/GamesView/GamesView.vala:125 #, c-format msgid "%s games" msgstr "Игры из %s" -#: src/ui/views/GamesView/GamesView.vala:117 +#: src/ui/views/GamesView/GamesView.vala:131 msgid "Downloads" msgstr "Загрузки" -#: src/ui/views/GamesView/GamesView.vala:138 +#: src/ui/views/GamesView/GamesView.vala:153 msgid "Search" msgstr "Поиск" -#: src/ui/views/GamesView/GamesView.vala:194 +#: src/ui/views/GamesView/GamesView.vala:277 #, c-format msgid "%u game" msgid_plural "%u games" @@ -224,34 +254,96 @@ msgstr[0] "%u игра" msgstr[1] "%u игры" msgstr[2] "%u игр" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:122 +#: src/ui/views/GamesView/GamesView.vala:281 +#, c-format +msgid "No %s games" +msgstr "Нет игр из %s" + +#: src/ui/views/GamesView/GamesView.vala:282 +msgid "Get some Linux-compatible games" +msgstr "Получите игры, совместимые с Linux" + +#: src/ui/views/GamesView/GamesView.vala:353 +msgid "" +"No games were loaded from Steam. Set your games list privacy to public or " +"use your own Steam API key in settings." +msgstr "" +"Нет игр, загруженных из Steam. Настройте список игр как публичный или " +"используйте свой API-ключ Steam в настройках." + +#: src/ui/views/GamesView/GamesView.vala:354 +msgid "Privacy" +msgstr "Приватность" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:59 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:129 +msgid "Pause download" +msgstr "Приостановить загрузку" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:64 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:135 +msgid "Resume download" +msgstr "Возобновить загрузку" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:69 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:141 +msgid "Cancel download" +msgstr "Отменить загрузку" + +#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 msgid "Description" msgstr "Описание" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:163 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:203 msgid "Run" msgstr "Запустить" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:164 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:204 msgid "Open installation directory" msgstr "Открыть папку установки" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:165 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:205 msgid "Open store page" msgstr "Открыть страницу в магазине" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:166 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:206 msgid "Uninstall" msgstr "Удалить" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:235 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:307 msgid "Language" msgstr "Язык" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:238 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:310 msgid "Languages" msgstr "Языки" +#: src/utils/downloader/Downloader.vala:136 +msgid "Starting download" +msgstr "Начало загрузки" + +#: src/utils/downloader/Downloader.vala:138 +msgid "Download finished" +msgstr "Загрузка завершена" + +#: src/utils/downloader/Downloader.vala:139 +msgid "Download failed" +msgstr "Ошибка загрузки" + +#: src/utils/downloader/Downloader.vala:141 +#, c-format +msgid "Downloading: %d%% (%s / %s)" +msgstr "Загрузка: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:143 +#, c-format +msgid "Paused: %d%% (%s / %s)" +msgstr "Приостановлено: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:145 +msgid "Download cancelled" +msgstr "Загрузка отменена" + #~ msgid "Select main executable of the game" #~ msgstr "Выберите исполняемый файл игры" diff --git a/po/uk.po b/po/uk.po index 10db8b04..6e47c48b 100644 --- a/po/uk.po +++ b/po/uk.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.github.tkashkin.gamehub\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-21 01:58+0300\n" +"POT-Creation-Date: 2018-07-22 17:06+0300\n" "PO-Revision-Date: 2018-07-14 00:51+0300\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -42,23 +42,30 @@ msgstr "Встановлено" msgid "Installing" msgstr "Встановлення" -#: src/data/Game.vala:77 +#: src/data/Game.vala:77 src/utils/downloader/Downloader.vala:137 msgid "Download started" msgstr "Завантаження розпочато" -#: src/data/Game.vala:78 -msgid "Download finished" -msgstr "Завантаження завершено" - -#: src/data/Game.vala:81 -#, c-format -msgid "Downloading: %d%% (%s / %s)" -msgstr "Завантаження: %d%% (%s / %s)" - -#: src/data/Game.vala:83 +#: src/data/Game.vala:79 msgid "Not installed" msgstr "Не встановлено" +#: src/data/Game.vala:89 +msgid "Installed:" +msgstr "Встановлено:" + +#: src/data/Game.vala:90 +msgid "Installing:" +msgstr "Встановлення:" + +#: src/data/Game.vala:91 +msgid "Downloading:" +msgstr "Завантаження:" + +#: src/data/Game.vala:93 +msgid "Not installed:" +msgstr "Не встановлено:" + #: src/data/sources/steam/Steam.vala:17 msgid "Your SteamID will be read from Steam configuration file" msgstr "Ваш SteamID буде прочитаним з файлу конфігурації Steam" @@ -72,7 +79,8 @@ msgstr "" "Увійдіть у свій обліковий запис у клієнті Steam і поверніться до GameHub" #: src/ui/dialogs/SettingsDialog.vala:13 src/ui/views/WelcomeView.vala:34 -#: src/ui/views/WelcomeView.vala:59 src/ui/views/GamesView/GamesView.vala:143 +#: src/ui/views/WelcomeView.vala:58 src/ui/views/GamesView/GamesView.vala:158 +#: src/ui/views/GamesView/GamesView.vala:355 msgid "Settings" msgstr "Налаштування" @@ -88,10 +96,6 @@ msgstr "Ключі API Steam мають обмежену кількість ви msgid "Generate key" msgstr "Створити ключ" -#: src/ui/dialogs/SettingsDialog.vala:35 -msgid "Steam API key" -msgstr "Ключ API Steam" - #: src/ui/dialogs/SettingsDialog.vala:36 msgid "Steam installation directory" msgstr "Каталог встановлення Steam" @@ -108,7 +112,7 @@ msgstr "Кеш інсталяторів GOG" msgid "Humble Bundle games directory" msgstr "Каталог ігор Humble Bundle" -#: src/ui/dialogs/SettingsDialog.vala:49 +#: src/ui/dialogs/SettingsDialog.vala:50 msgid "Humble Bundle installers cache" msgstr "Кеш інсталяторів Humble Bundle" @@ -117,16 +121,28 @@ msgstr "Кеш інсталяторів Humble Bundle" msgid "Close" msgstr "Закрити" -#: src/ui/dialogs/SettingsDialog.vala:180 +#: src/ui/dialogs/SettingsDialog.vala:106 +msgid "Default" +msgstr "За замовчуванням" + +#: src/ui/dialogs/SettingsDialog.vala:114 +msgid "Restore default API key" +msgstr "Відновити ключ API за замовчуванням" + +#: src/ui/dialogs/SettingsDialog.vala:125 +msgid "Steam API key" +msgstr "Ключ API Steam" + +#: src/ui/dialogs/SettingsDialog.vala:215 msgid "Open" msgstr "Відкрити" -#: src/ui/dialogs/SettingsDialog.vala:187 +#: src/ui/dialogs/SettingsDialog.vala:222 msgid "Clear" msgstr "Очистити" -#: src/ui/dialogs/SettingsDialog.vala:203 -#, fuzzy, c-format +#: src/ui/dialogs/SettingsDialog.vala:238 +#, c-format msgid "%llu installer; %s" msgid_plural "%llu installers; %s" msgstr[0] "%llu інсталятор; %s" @@ -135,7 +151,7 @@ msgstr[2] "%llu інсталяторів; %s" #: src/ui/dialogs/GameInstallDialog.vala:31 #: src/ui/dialogs/GameInstallDialog.vala:131 -#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:202 msgid "Install" msgstr "Встановити" @@ -144,9 +160,9 @@ msgid "Select game installer" msgstr "Виберіть інсталятора гри" #: src/ui/dialogs/GameInstallDialog.vala:92 -#, fuzzy, c-format +#, c-format msgid "Installer size: %s" -msgstr "Встановити %s" +msgstr "Розмір інсталятора: %s" #: src/ui/dialogs/GameInstallDialog.vala:124 msgid "Cancel" @@ -172,53 +188,65 @@ msgstr "Давайте розпочнемо" msgid "Skip" msgstr "Пропустити" -#: src/ui/views/WelcomeView.vala:115 +#: src/ui/views/WelcomeView.vala:116 msgid "Ready" msgstr "Готово" -#: src/ui/views/WelcomeView.vala:121 +#: src/ui/views/WelcomeView.vala:122 msgid "Authentication required" msgstr "Необхідна аутентифікація" -#: src/ui/views/WelcomeView.vala:126 +#: src/ui/views/WelcomeView.vala:127 msgid "Authenticating..." msgstr "Аутентифікація..." -#: src/ui/views/WelcomeView.vala:137 +#: src/ui/views/WelcomeView.vala:138 #, c-format msgid "Install %s" msgstr "Встановити %s" -#: src/ui/views/WelcomeView.vala:138 +#: src/ui/views/WelcomeView.vala:139 msgid "Return to GameHub after installing" msgstr "Поверніться до GameHub після встановлення" -#: src/ui/views/GamesView/GamesView.vala:92 +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "No games" +msgstr "Немає ігор" + +#: src/ui/views/GamesView/GamesView.vala:62 +msgid "Get some games or enable some game sources in settings" +msgstr "Отримайте ігри або увімкніть джерела ігор у налаштуваннях" + +#: src/ui/views/GamesView/GamesView.vala:63 +msgid "Reload" +msgstr "Перезавантажити" + +#: src/ui/views/GamesView/GamesView.vala:108 msgid "Grid view" msgstr "Сітка" -#: src/ui/views/GamesView/GamesView.vala:93 +#: src/ui/views/GamesView/GamesView.vala:109 msgid "List view" msgstr "Список" -#: src/ui/views/GamesView/GamesView.vala:107 +#: src/ui/views/GamesView/GamesView.vala:121 msgid "All games" msgstr "Всі ігри" -#: src/ui/views/GamesView/GamesView.vala:111 +#: src/ui/views/GamesView/GamesView.vala:125 #, c-format msgid "%s games" msgstr "Ігри з %s" -#: src/ui/views/GamesView/GamesView.vala:117 +#: src/ui/views/GamesView/GamesView.vala:131 msgid "Downloads" msgstr "Завантаження" -#: src/ui/views/GamesView/GamesView.vala:138 +#: src/ui/views/GamesView/GamesView.vala:153 msgid "Search" msgstr "Пошук" -#: src/ui/views/GamesView/GamesView.vala:194 +#: src/ui/views/GamesView/GamesView.vala:277 #, c-format msgid "%u game" msgid_plural "%u games" @@ -226,34 +254,96 @@ msgstr[0] "%u гра" msgstr[1] "%u гри" msgstr[2] "%u ігор" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:122 +#: src/ui/views/GamesView/GamesView.vala:281 +#, c-format +msgid "No %s games" +msgstr "Немає ігор з %s" + +#: src/ui/views/GamesView/GamesView.vala:282 +msgid "Get some Linux-compatible games" +msgstr "Отримайте сумісні з Linux ігри" + +#: src/ui/views/GamesView/GamesView.vala:353 +msgid "" +"No games were loaded from Steam. Set your games list privacy to public or " +"use your own Steam API key in settings." +msgstr "" +"Немає ігор, завантажених з Steam. Налаштуйте список ігор як публічний або " +"використовуйте свій API-ключ Steam в налаштуваннях." + +#: src/ui/views/GamesView/GamesView.vala:354 +msgid "Privacy" +msgstr "Конфіденційність" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:59 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:129 +msgid "Pause download" +msgstr "Призупинити завантаження" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:64 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:135 +msgid "Resume download" +msgstr "Відновити завантаження" + +#: src/ui/views/GamesView/GameDownloadProgressView.vala:69 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:141 +msgid "Cancel download" +msgstr "Скасувати завантаження" + +#: src/ui/views/GameDetailsView/GameDetailsView.vala:162 msgid "Description" msgstr "Опис" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:163 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:203 msgid "Run" msgstr "Запустити" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:164 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:204 msgid "Open installation directory" msgstr "Відкрити каталог встановлення" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:165 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:205 msgid "Open store page" msgstr "Відкрити сторінку магазину" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:166 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:206 msgid "Uninstall" msgstr "Видалити" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:235 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:307 msgid "Language" msgstr "Мова" -#: src/ui/views/GameDetailsView/GameDetailsView.vala:238 +#: src/ui/views/GameDetailsView/GameDetailsView.vala:310 msgid "Languages" msgstr "Мови" +#: src/utils/downloader/Downloader.vala:136 +msgid "Starting download" +msgstr "Початок завантаження" + +#: src/utils/downloader/Downloader.vala:138 +msgid "Download finished" +msgstr "Завантаження завершено" + +#: src/utils/downloader/Downloader.vala:139 +msgid "Download failed" +msgstr "Помилка завантаження" + +#: src/utils/downloader/Downloader.vala:141 +#, c-format +msgid "Downloading: %d%% (%s / %s)" +msgstr "Завантаження: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:143 +#, c-format +msgid "Paused: %d%% (%s / %s)" +msgstr "Призупинено: %d%% (%s / %s)" + +#: src/utils/downloader/Downloader.vala:145 +msgid "Download cancelled" +msgstr "Завантаження скасовано" + #~ msgid "Select main executable of the game" #~ msgstr "Виберіть основний виконуваний файл гри" diff --git a/src/data/Game.vala b/src/data/Game.vala index f0915759..3c9a5f4d 100644 --- a/src/data/Game.vala +++ b/src/data/Game.vala @@ -1,5 +1,7 @@ using Gtk; +using GameHub.Utils.Downloader; + namespace GameHub.Data { public abstract class Game @@ -22,7 +24,7 @@ namespace GameHub.Data public virtual bool is_installed(){ return false; } - public abstract async void install(Utils.DownloadProgress progress = (d, t) => {}); + public abstract async void install(); public abstract async void run(); public abstract async void uninstall(); @@ -56,14 +58,12 @@ namespace GameHub.Data { public Game.State state; - public int64 dl_bytes; - public int64 dl_bytes_total; + public Download? download; - public Status(Game.State state=Game.State.UNINSTALLED, int64 dl_bytes = -1, int64 dl_bytes_total = -1) + public Status(Game.State state=Game.State.UNINSTALLED, Download? download=null) { this.state = state; - this.dl_bytes = dl_bytes; - this.dl_bytes_total = dl_bytes_total; + this.download = download; } public string description @@ -74,20 +74,30 @@ namespace GameHub.Data { case Game.State.INSTALLED: return _("Installed"); case Game.State.INSTALLING: return _("Installing"); - case Game.State.DOWNLOAD_STARTED: return _("Download started"); - case Game.State.DOWNLOAD_FINISHED: return _("Download finished"); - case Game.State.DOWNLOADING: - var fraction = (double) dl_bytes / dl_bytes_total; - return _("Downloading: %d%% (%s / %s)").printf((int)(fraction * 100), format_size(dl_bytes), format_size(dl_bytes_total)); + case Game.State.DOWNLOADING: return download != null ? download.status.description : _("Download started"); } return _("Not installed"); } } + + public string header + { + owned get + { + switch(state) + { + case Game.State.INSTALLED: return _("Installed:"); + case Game.State.INSTALLING: return _("Installing:"); + case Game.State.DOWNLOADING: return _("Downloading:"); + } + return _("Not installed:"); + } + } } public enum State { - UNINSTALLED, INSTALLED, DOWNLOAD_STARTED, DOWNLOADING, DOWNLOAD_FINISHED, INSTALLING; + UNINSTALLED, INSTALLED, DOWNLOADING, INSTALLING; } } } diff --git a/src/data/sources/gog/GOGGame.vala b/src/data/sources/gog/GOGGame.vala index 998efef7..1adbafcd 100644 --- a/src/data/sources/gog/GOGGame.vala +++ b/src/data/sources/gog/GOGGame.vala @@ -62,7 +62,10 @@ namespace GameHub.Data.Sources.GOG public override bool is_installed() { - status = new Game.Status(executable.query_exists() ? Game.State.INSTALLED : Game.State.UNINSTALLED); + if(status.state != Game.State.DOWNLOADING) + { + status = new Game.Status(executable.query_exists() ? Game.State.INSTALLED : Game.State.UNINSTALLED); + } return executable.query_exists(); } @@ -105,10 +108,13 @@ namespace GameHub.Data.Sources.GOG GamesDB.get_instance().add_game(this); - status = new Game.Status(executable.query_exists() ? Game.State.INSTALLED : Game.State.UNINSTALLED); + if(status.state != Game.State.DOWNLOADING) + { + status = new Game.Status(executable.query_exists() ? Game.State.INSTALLED : Game.State.UNINSTALLED); + } } - public override async void install(DownloadProgress progress = (d, t) => {}) + public override async void install() { yield update_game_info(); @@ -132,27 +138,35 @@ namespace GameHub.Data.Sources.GOG var wnd = new GameHub.UI.Dialogs.GameInstallDialog(this, installers); - wnd.canceled.connect(() => Idle.add(install.callback)); + wnd.cancelled.connect(() => Idle.add(install.callback)); wnd.install.connect(installer => { root = Parser.parse_remote_json_file(installer.file, "GET", ((GOG) source).user_token); var link = root.get_object().get_string_member("downlink"); - var local = FSUtils.expand(FSUtils.Paths.GOG.Installers, "gog_" + id + "_" + installer.id + ".sh"); + var remote = File.new_for_uri(link); + var local = FSUtils.file(FSUtils.Paths.GOG.Installers, "gog_" + id + "_" + installer.id + ".sh"); FSUtils.mkdir(FSUtils.Paths.GOG.Games); FSUtils.mkdir(FSUtils.Paths.GOG.Installers); - status = new Game.Status(Game.State.DOWNLOAD_STARTED); + status = new Game.Status(Game.State.DOWNLOADING, null); + var ds_id = Downloader.get_instance().download_started.connect(dl => { + if(dl.remote != remote) return; + status = new Game.Status(Game.State.DOWNLOADING, dl); + dl.status_change.connect(s => { + status_change(status); + }); + }); - Downloader.get_instance().download.begin(File.new_for_uri(link), { local }, (d, t) => { - progress(d, t); - status = new Game.Status(Game.State.DOWNLOADING, d, t); - }, null, (obj, res) => { + Downloader.download.begin(remote, local, (obj, res) => { try { - var file = Downloader.get_instance().download.end(res).get_path(); - status = new Game.Status(Game.State.DOWNLOAD_FINISHED); + var file = Downloader.download.end(res).get_path(); + + Downloader.get_instance().disconnect(ds_id); + Utils.run({"chmod", "+x", file}); + status = new Game.Status(Game.State.INSTALLING); string[] cmd = {file, "--", "--i-agree-to-all-licenses", "--noreadme", "--nooptions", "--noprompt", @@ -163,10 +177,12 @@ namespace GameHub.Data.Sources.GOG Idle.add(install.callback); }); } + catch(IOError.CANCELLED e){} catch(Error e) { warning(e.message); } + status = new Game.Status(executable.query_exists() ? Game.State.INSTALLED : Game.State.UNINSTALLED); }); }); diff --git a/src/data/sources/humble/HumbleGame.vala b/src/data/sources/humble/HumbleGame.vala index 06d2c196..eb273267 100644 --- a/src/data/sources/humble/HumbleGame.vala +++ b/src/data/sources/humble/HumbleGame.vala @@ -78,11 +78,14 @@ namespace GameHub.Data.Sources.Humble public override bool is_installed() { - status = new Game.Status(executable.query_exists() ? Game.State.INSTALLED : Game.State.UNINSTALLED); + if(status.state != Game.State.DOWNLOADING) + { + status = new Game.Status(executable.query_exists() ? Game.State.INSTALLED : Game.State.UNINSTALLED); + } return executable.query_exists(); } - public override async void install(DownloadProgress progress = (d, t) => {}) + public override async void install() { var token = ((Humble) source).user_token; @@ -120,29 +123,37 @@ namespace GameHub.Data.Sources.Humble var wnd = new GameHub.UI.Dialogs.GameInstallDialog(this, installers); - wnd.canceled.connect(() => Idle.add(install.callback)); + wnd.cancelled.connect(() => Idle.add(install.callback)); wnd.install.connect(installer => { var link = installer.file; - var local = FSUtils.expand(FSUtils.Paths.Humble.Installers, "humble_" + installer.id); + var remote = File.new_for_uri(link); + var local = FSUtils.file(FSUtils.Paths.Humble.Installers, "humble_" + installer.id); FSUtils.mkdir(FSUtils.Paths.Humble.Games); FSUtils.mkdir(FSUtils.Paths.Humble.Installers); - status = new Game.Status(Game.State.DOWNLOAD_STARTED); + status = new Game.Status(Game.State.DOWNLOADING, null); + var ds_id = Downloader.get_instance().download_started.connect(dl => { + if(dl.remote != remote) return; + status = new Game.Status(Game.State.DOWNLOADING, dl); + dl.status_change.connect(s => { + status_change(status); + }); + }); - Downloader.get_instance().download.begin(File.new_for_uri(link), { local }, (d, t) => { - progress(d, t); - status = new Game.Status(Game.State.DOWNLOADING, d, t); - }, null, (obj, res) => { + Downloader.download.begin(remote, local, (obj, res) => { try { - var file = Downloader.get_instance().download.end(res); - status = new Game.Status(Game.State.DOWNLOAD_STARTED); + var file = Downloader.download.end(res); + + Downloader.get_instance().disconnect(ds_id); + var path = file.get_path(); - FSUtils.mkdir(install_dir.get_path()); Utils.run({"chmod", "+x", path}); + FSUtils.mkdir(install_dir.get_path()); + var info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE, FileQueryInfoFlags.NONE); var type = info.get_content_type(); @@ -205,9 +216,11 @@ namespace GameHub.Data.Sources.Humble catch(Error e){} choose_executable(); + status = new Game.Status(executable.query_exists() ? Game.State.INSTALLED : Game.State.UNINSTALLED); Idle.add(install.callback); }); } + catch(IOError.CANCELLED e){} catch(Error e) { warning(e.message); diff --git a/src/data/sources/steam/Steam.vala b/src/data/sources/steam/Steam.vala index bf3ab1d6..625a0b33 100644 --- a/src/data/sources/steam/Steam.vala +++ b/src/data/sources/steam/Steam.vala @@ -74,7 +74,7 @@ namespace GameHub.Data.Sources.Steam if(!is_authenticated_in_steam_client) { - yield Utils.run_async({"steam"}, null, false, false); + Utils.open_uri("steam://"); return false; } @@ -147,18 +147,22 @@ namespace GameHub.Data.Sources.Steam var url = @"https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=$(api_key)&steamid=$(user_id)&format=json&include_appinfo=1&include_played_free_games=1"; var root = yield Parser.parse_remote_json_file_async(url); - var json_games = Parser.json_object(root, {"response"}).get_array_member("games"); + var response = Parser.json_object(root, {"response"}); + var json_games = response != null && response.has_member("games") ? response.get_array_member("games") : null; - foreach(var g in json_games.get_elements()) + if(json_games != null) { - var game = new SteamGame(this, g.get_object()); - if(!games.contains(game) && yield game.is_for_linux()) + foreach(var g in json_games.get_elements()) { - games.add(game); - if(game_loaded != null) game_loaded(game); - GamesDB.get_instance().add_game(game); + var game = new SteamGame(this, g.get_object()); + if(!games.contains(game) && yield game.is_for_linux()) + { + games.add(game); + if(game_loaded != null) game_loaded(game); + GamesDB.get_instance().add_game(game); + } + games_count = games.size; } - games_count = games.size; } return games; diff --git a/src/data/sources/steam/SteamGame.vala b/src/data/sources/steam/SteamGame.vala index 413401ac..f35d2cd8 100644 --- a/src/data/sources/steam/SteamGame.vala +++ b/src/data/sources/steam/SteamGame.vala @@ -50,7 +50,8 @@ namespace GameHub.Data.Sources.Steam } var root = Parser.parse_json(custom_info); - description = Parser.json_object(root, {id, "data"}).get_string_member("detailed_description"); + var data = Parser.json_object(root, {id, "data"}); + description = data != null && data.has_member("detailed_description") ? data.get_string_member("detailed_description") : ""; if(_is_for_linux == true) GamesDB.get_instance().add_game(this); } @@ -108,7 +109,7 @@ namespace GameHub.Data.Sources.Steam return false; } - public override async void install(DownloadProgress progress = (d, t) => {}) + public override async void install() { yield run(); } diff --git a/src/meson.build b/src/meson.build index 583f6ab0..26f5658f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -19,8 +19,10 @@ deps = [ dependency('glib-2.0'), dependency('json-glib-1.0'), dependency('gee-0.8'), + dependency('sqlite3'), dependency('libsoup-2.4'), - dependency('sqlite3') + meson.get_compiler('vala').find_library('posix'), + meson.get_compiler('vala').find_library('linux') ] gtk322 = dependency('gtk+-3.0', version: '>=3.22', required: false) @@ -33,8 +35,6 @@ endif if get_option('use_ivy') add_global_arguments('-g', '-X', '-rdynamic', '-D', 'USE_IVY', language: 'vala') - deps += meson.get_compiler('vala').find_library('posix') - deps += meson.get_compiler('vala').find_library('linux') endif executable( @@ -82,7 +82,9 @@ executable( 'utils/FSUtils.vala', 'utils/Parser.vala', 'utils/Settings.vala', - 'utils/Downloader.vala', + + 'utils/downloader/Downloader.vala', + 'utils/downloader/SoupDownloader.vala', 'lib/ivy/Extractor.vala', 'lib/ivy/Frame.vala', diff --git a/src/ui/dialogs/GameInstallDialog.vala b/src/ui/dialogs/GameInstallDialog.vala index 1eecb211..6999bae0 100644 --- a/src/ui/dialogs/GameInstallDialog.vala +++ b/src/ui/dialogs/GameInstallDialog.vala @@ -16,7 +16,7 @@ namespace GameHub.UI.Dialogs public signal void import(); public signal void install(Game.Installer installer); - public signal void canceled(); + public signal void cancelled(); private Box content; private Label title_label; @@ -92,7 +92,7 @@ namespace GameHub.UI.Dialogs subtitle_label.label = _("Installer size: %s").printf(format_size(installers[0].file_size)); } - destroy.connect(() => { if(!is_finished) canceled(); }); + destroy.connect(() => { if(!is_finished) cancelled(); }); response.connect((source, response_id) => { switch(response_id) diff --git a/src/ui/dialogs/SettingsDialog.vala b/src/ui/dialogs/SettingsDialog.vala index 2a1f7bff..480e0008 100644 --- a/src/ui/dialogs/SettingsDialog.vala +++ b/src/ui/dialogs/SettingsDialog.vala @@ -31,8 +31,8 @@ namespace GameHub.UI.Dialogs add_separator(); add_header_with_checkbox("Steam", steam_auth.enabled, v => { steam_auth.enabled = v; }); - add_labeled_link(_("Steam API keys have limited number of uses per day"), _("Generate key"), "https://steamcommunity.com/dev/apikey"); - add_entry(_("Steam API key"), steam_auth.api_key, v => { steam_auth.api_key = v; }); + add_labeled_link(_("Steam API keys have limited number of uses per day"), _("Generate key"), "steam://openurl/https://steamcommunity.com/dev/apikey"); + add_steam_apikey_entry(); add_file_chooser(_("Steam installation directory"), FileChooserAction.SELECT_FOLDER, paths.steam_home, v => { paths.steam_home = v; }, false); add_separator(); @@ -98,6 +98,40 @@ namespace GameHub.UI.Dialogs add_widget(hbox); } + private void add_steam_apikey_entry() + { + var steam_auth = Settings.Auth.Steam.get_instance(); + + var entry = new Entry(); + entry.placeholder_text = _("Default"); + entry.max_length = 32; + if(steam_auth.api_key != steam_auth.schema.get_default_value("api-key").get_string()) + { + entry.text = steam_auth.api_key; + } + entry.primary_icon_name = "steam-symbolic"; + entry.secondary_icon_name = "edit-delete-symbolic"; + entry.secondary_icon_tooltip_text = _("Restore default API key"); + entry.set_size_request(280, -1); + + entry.notify["text"].connect(() => { steam_auth.api_key = entry.text; }); + entry.icon_press.connect((pos, e) => { + if(pos == EntryIconPosition.SECONDARY) + { + entry.text = ""; + } + }); + + var label = new Label(_("Steam API key")); + label.halign = Align.START; + label.hexpand = true; + + var hbox = new Box(Orientation.HORIZONTAL, 12); + hbox.add(label); + hbox.add(entry); + add_widget(hbox); + } + private void add_file_chooser(string text, FileChooserAction mode, string val, owned EntryAction action, bool create=true) { var chooser = new FileChooserButton(text, mode); diff --git a/src/ui/views/GameDetailsView/GameDetailsView.vala b/src/ui/views/GameDetailsView/GameDetailsView.vala index 6c0e73fb..f44964d9 100644 --- a/src/ui/views/GameDetailsView/GameDetailsView.vala +++ b/src/ui/views/GameDetailsView/GameDetailsView.vala @@ -48,6 +48,12 @@ namespace GameHub.UI.Views private AutoSizeImage icon; private Image src_icon; + private Downloader.Download? download; + + private Button action_pause; + private Button action_resume; + private Button action_cancel; + private ActionButton action_install; private ActionButton action_run; private ActionButton action_open_directory; @@ -110,9 +116,43 @@ namespace GameHub.UI.Views src_icon.opacity = 0.1; var title_vbox = new Box(Orientation.VERTICAL, 0); - - title_vbox.add(title); - title_vbox.add(status); + var vbox_labels = new Box(Orientation.VERTICAL, 0); + vbox_labels.hexpand = true; + + var hbox_inner = new Box(Orientation.HORIZONTAL, 8); + var hbox_actions = new Box(Orientation.HORIZONTAL, 0); + hbox_actions.vexpand = false; + hbox_actions.valign = Align.CENTER; + + action_pause = new Button.from_icon_name("media-playback-pause-symbolic"); + action_pause.set_size_request(36, 36); + action_pause.tooltip_text = _("Pause download"); + action_pause.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT); + action_pause.visible = false; + + action_resume = new Button.from_icon_name("media-playback-start-symbolic"); + action_resume.set_size_request(36, 36); + action_resume.tooltip_text = _("Resume download"); + action_resume.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT); + action_resume.visible = false; + + action_cancel = new Button.from_icon_name("process-stop-symbolic"); + action_cancel.set_size_request(36, 36); + action_cancel.tooltip_text = _("Cancel download"); + action_cancel.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT); + action_cancel.visible = false; + + vbox_labels.add(title); + vbox_labels.add(status); + + hbox_inner.add(vbox_labels); + hbox_inner.add(hbox_actions); + + hbox_actions.add(action_pause); + hbox_actions.add(action_resume); + hbox_actions.add(action_cancel); + + title_vbox.add(hbox_inner); title_vbox.add(download_progress); title_hbox.add(icon); @@ -164,6 +204,24 @@ namespace GameHub.UI.Views action_open_directory = add_action("folder", _("Open installation directory"), open_game_directory); action_open_store_page = add_action("web-browser", _("Open store page"), open_game_store_page); action_uninstall = add_action("edit-delete", _("Uninstall"), uninstall_game); + + action_cancel.clicked.connect(() => { + if(download != null) download.cancel(); + }); + + action_pause.clicked.connect(() => { + if(download != null && download is Downloader.PausableDownload) + { + ((Downloader.PausableDownload) download).pause(); + } + }); + + action_resume.clicked.connect(() => { + if(download != null && download is Downloader.PausableDownload) + { + ((Downloader.PausableDownload) download).resume(); + } + }); } private async void update_game() @@ -202,10 +260,24 @@ namespace GameHub.UI.Views _game_status_handler_id = _game.status_change.connect(s => { status.label = s.description; download_progress.hide(); - if(s.state == Game.State.DOWNLOADING) + if(s.state == Game.State.DOWNLOADING && s.download != null) { + download = s.download; + var ds = download.status.state; + download_progress.show(); - download_progress.fraction = (double) s.dl_bytes / s.dl_bytes_total; + download_progress.fraction = s.download.status.progress; + + action_cancel.visible = true; + action_cancel.sensitive = ds == Downloader.DownloadState.DOWNLOADING || ds == Downloader.DownloadState.PAUSED; + action_pause.visible = download is Downloader.PausableDownload && ds != Downloader.DownloadState.PAUSED; + action_resume.visible = download is Downloader.PausableDownload && ds == Downloader.DownloadState.PAUSED; + } + else + { + action_cancel.visible = false; + action_pause.visible = false; + action_resume.visible = false; } action_install.visible = s.state != Game.State.INSTALLED; action_install.sensitive = s.state == Game.State.UNINSTALLED; @@ -217,7 +289,7 @@ namespace GameHub.UI.Views _game.status_change(_game.status); custom_info.forall(w => custom_info.remove(w)); - if(_game is GameHub.Data.Sources.GOG.GOGGame) + if(_game is GameHub.Data.Sources.GOG.GOGGame && _game.custom_info.length > 0) { var root = Parser.parse_json(_game.custom_info).get_object(); diff --git a/src/ui/views/GamesView/GameCard.vala b/src/ui/views/GamesView/GameCard.vala index 27cd05b3..9793c609 100644 --- a/src/ui/views/GamesView/GameCard.vala +++ b/src/ui/views/GamesView/GameCard.vala @@ -101,9 +101,7 @@ namespace GameHub.UI.Views } else if(game.status.state == Game.State.UNINSTALLED) { - game.install.begin((d, t) => {}, (obj, res) => { - game.install.end(res); - }); + game.install.begin(); } break; @@ -146,10 +144,12 @@ namespace GameHub.UI.Views card.get_style_context().remove_class("installed"); card.get_style_context().add_class("downloading"); card.get_style_context().remove_class("installing"); - var fraction = (double) s.dl_bytes / s.dl_bytes_total; Allocation alloc; card.get_allocation(out alloc); - progress_bar.set_size_request((int) (fraction * alloc.width), 8); + if(s.download != null) + { + progress_bar.set_size_request((int) (s.download.status.progress * alloc.width), 8); + } break; case Game.State.INSTALLING: diff --git a/src/ui/views/GamesView/GameDownloadProgressView.vala b/src/ui/views/GamesView/GameDownloadProgressView.vala index a7a59fb5..86c54751 100644 --- a/src/ui/views/GamesView/GameDownloadProgressView.vala +++ b/src/ui/views/GamesView/GameDownloadProgressView.vala @@ -10,10 +10,15 @@ namespace GameHub.UI.Views public class GameDownloadProgressView: ListBoxRow { public Game game; + private Downloader.Download? download; private AutoSizeImage image; private ProgressBar progress_bar; + private Button action_pause; + private Button action_resume; + private Button action_cancel; + public GameDownloadProgressView(Game game) { this.game = game; @@ -22,7 +27,14 @@ namespace GameHub.UI.Views var hbox = new Box(Orientation.HORIZONTAL, 16); hbox.margin = 8; + var hbox_inner = new Box(Orientation.HORIZONTAL, 8); + var hbox_actions = new Box(Orientation.HORIZONTAL, 0); + hbox_actions.vexpand = false; + hbox_actions.valign = Align.CENTER; + var vbox = new Box(Orientation.VERTICAL, 0); + var vbox_labels = new Box(Orientation.VERTICAL, 0); + vbox_labels.hexpand = true; image = new AutoSizeImage(); image.set_constraint(48, 48, 1); @@ -42,9 +54,33 @@ namespace GameHub.UI.Views progress_bar.hexpand = true; progress_bar.fraction = 0d; progress_bar.get_style_context().add_class(Gtk.STYLE_CLASS_OSD); + + action_pause = new Button.from_icon_name("media-playback-pause-symbolic"); + action_pause.tooltip_text = _("Pause download"); + action_pause.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT); + action_pause.visible = false; + + action_resume = new Button.from_icon_name("media-playback-start-symbolic"); + action_resume.tooltip_text = _("Resume download"); + action_resume.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT); + action_resume.visible = false; - vbox.add(label); - vbox.add(state_label); + action_cancel = new Button.from_icon_name("process-stop-symbolic"); + action_cancel.tooltip_text = _("Cancel download"); + action_cancel.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT); + action_cancel.visible = false; + + vbox_labels.add(label); + vbox_labels.add(state_label); + + hbox_inner.add(vbox_labels); + hbox_inner.add(hbox_actions); + + hbox_actions.add(action_pause); + hbox_actions.add(action_resume); + hbox_actions.add(action_cancel); + + vbox.add(hbox_inner); vbox.add(progress_bar); hbox.add(vbox); @@ -53,9 +89,35 @@ namespace GameHub.UI.Views game.status_change.connect(s => { state_label.label = s.description; - if(s.state == Game.State.DOWNLOADING) + if(s.state == Game.State.DOWNLOADING && s.download != null) + { + download = s.download; + var ds = download.status.state; + + progress_bar.fraction = download.status.progress; + + action_cancel.visible = true; + action_cancel.sensitive = ds == Downloader.DownloadState.DOWNLOADING || ds == Downloader.DownloadState.PAUSED; + action_pause.visible = download is Downloader.PausableDownload && ds != Downloader.DownloadState.PAUSED; + action_resume.visible = download is Downloader.PausableDownload && ds == Downloader.DownloadState.PAUSED; + } + }); + + action_cancel.clicked.connect(() => { + if(download != null) download.cancel(); + }); + + action_pause.clicked.connect(() => { + if(download != null && download is Downloader.PausableDownload) + { + ((Downloader.PausableDownload) download).pause(); + } + }); + + action_resume.clicked.connect(() => { + if(download != null && download is Downloader.PausableDownload) { - progress_bar.fraction = (double) s.dl_bytes / s.dl_bytes_total; + ((Downloader.PausableDownload) download).resume(); } }); diff --git a/src/ui/views/GamesView/GameListRow.vala b/src/ui/views/GamesView/GameListRow.vala index 09a7ca91..8a14c8d8 100644 --- a/src/ui/views/GamesView/GameListRow.vala +++ b/src/ui/views/GamesView/GameListRow.vala @@ -47,6 +47,7 @@ namespace GameHub.UI.Views game.status_change.connect(s => { state_label.label = s.description; update_icon(); + changed(); }); game.status_change(game.status); diff --git a/src/ui/views/GamesView/GamesView.vala b/src/ui/views/GamesView/GamesView.vala index 31919ed1..b8d49cc2 100644 --- a/src/ui/views/GamesView/GamesView.vala +++ b/src/ui/views/GamesView/GamesView.vala @@ -1,8 +1,10 @@ using Gtk; +using GLib; using Gee; using Granite; using GameHub.Data; using GameHub.Utils; +using GameHub.Utils.Downloader; namespace GameHub.UI.Views { @@ -12,8 +14,12 @@ namespace GameHub.UI.Views private ArrayList sources = new ArrayList(); + private Box messages; + private Stack stack; + private Granite.Widgets.AlertView empty_alert; + private ScrolledWindow games_grid_scrolled; private FlowBox games_grid; @@ -36,12 +42,14 @@ namespace GameHub.UI.Views private ListBox downloads_list; private int downloads_count = 0; + private Settings.SavedState saved_state; + construct { instance = this; var ui_settings = Settings.UI.get_instance(); - var saved_state = Settings.SavedState.get_instance(); + saved_state = Settings.SavedState.get_instance(); foreach(var src in GameSources) { @@ -51,6 +59,9 @@ namespace GameHub.UI.Views stack = new Stack(); stack.transition_type = StackTransitionType.CROSSFADE; + empty_alert = new Granite.Widgets.AlertView(_("No games"), _("Get some games or enable some game sources in settings"), "dialog-warning"); + empty_alert.show_action(_("Reload")); + games_grid = new FlowBox(); games_grid.margin = 4; @@ -81,9 +92,14 @@ namespace GameHub.UI.Views games_list_paned.pack1(games_list_scrolled, false, false); games_list_paned.pack2(games_list_details, true, true); + stack.add(empty_alert); stack.add(games_grid_scrolled); stack.add(games_list_paned); - add(stack); + + messages = new Box(Orientation.VERTICAL, 0); + + attach(messages, 0, 0); + attach(stack, 0, 1); view = new Granite.Widgets.ModeButton(); view.halign = Align.CENTER; @@ -93,9 +109,7 @@ namespace GameHub.UI.Views add_view_button("view-list", _("List view")); view.mode_changed.connect(() => { - var tab = view.selected == 0 ? (Widget) games_grid_scrolled : (Widget) games_list_paned; - stack.set_visible_child(tab); - saved_state.games_view = view.selected == 0 ? Settings.GamesView.GRID : Settings.GamesView.LIST; + update_view(); }); titlebar.pack_start(view); @@ -115,9 +129,10 @@ namespace GameHub.UI.Views downloads = new MenuButton(); downloads.tooltip_text = _("Downloads"); - downloads.image = new Image.from_icon_name("folder-download", IconSize.LARGE_TOOLBAR); + downloads.image = new Image.from_icon_name("emblem-downloads", IconSize.LARGE_TOOLBAR); downloads_popover = new Popover(downloads); downloads_list = new ListBox(); + downloads_list.get_style_context().add_class("downloads-list"); var downloads_scrolled = new ScrolledWindow(null, null); #if GTK_3_22 @@ -162,6 +177,16 @@ namespace GameHub.UI.Views var item2 = row2 as GameListRow; if(item1 != null && item2 != null) { + var s1 = item1.game.status.state; + var s2 = item2.game.status.state; + + if(s1 == Game.State.DOWNLOADING && s2 != Game.State.DOWNLOADING) return -1; + if(s1 != Game.State.DOWNLOADING && s2 == Game.State.DOWNLOADING) return 1; + if(s1 == Game.State.INSTALLING && s2 != Game.State.INSTALLING) return -1; + if(s1 != Game.State.INSTALLING && s2 == Game.State.INSTALLING) return 1; + if(s1 == Game.State.INSTALLED && s2 != Game.State.INSTALLED) return -1; + if(s1 != Game.State.INSTALLED && s2 == Game.State.INSTALLED) return 1; + return item1.game.name.collate(item2.game.name); } return 0; @@ -177,6 +202,24 @@ namespace GameHub.UI.Views return games_filter(item.game); }); + games_list.set_header_func((row, prev) => { + var item = row as GameListRow; + var prev_item = prev as GameListRow; + var s = item.game.status.state; + var ps = prev_item != null ? prev_item.game.status.state : s; + + games_list.grab_focus(); + + if(prev_item != null && s == ps) row.set_header(null); + else + { + var label = new HeaderLabel(item.game.status.header); + label.get_style_context().add_class("games-list-header"); + label.set_size_request(1024, -1); // ugly hack + row.set_header(label); + } + }); + games_list.row_selected.connect(row => { var item = row as GameListRow; games_list_details.game = item != null ? item.game : null; @@ -186,12 +229,7 @@ namespace GameHub.UI.Views games_grid.invalidate_filter(); games_list.invalidate_filter(); - var f = filter.selected; - GameSource? src = null; - if(f > 0) src = sources[f - 1]; - var games = src == null ? games_grid.get_children().length() : src.games_count; - titlebar.title = "GameHub" + (src == null ? "" : "/" + src.name); - titlebar.subtitle = ngettext("%u game", "%u games", games).printf(games); + update_view(); Timeout.add(100, () => { games_list_select_first_visible_row(); return false; }); }); @@ -212,19 +250,74 @@ namespace GameHub.UI.Views games_grid_scrolled.show_all(); games_grid.show_all(); + empty_alert.action_activated.connect(() => load_games.begin()); + + stack.set_visible_child(empty_alert); + + view.opacity = 0; + view.sensitive = false; + filter.opacity = 0; + filter.sensitive = false; + search.opacity = 0; + search.sensitive = false; + downloads.opacity = 0; + + load_games.begin(); + } + + private void update_view() + { + show_games(); + + var f = filter.selected; + GameSource? src = null; + if(f > 0) src = sources[f - 1]; + var games = src == null ? games_grid.get_children().length() : src.games_count; + titlebar.title = "GameHub" + (src == null ? "" : "/" + src.name); + titlebar.subtitle = ngettext("%u game", "%u games", games).printf(games); + + if(src != null && src.games_count == 0) + { + empty_alert.title = _("No %s games").printf(src.name); + empty_alert.description = _("Get some Linux-compatible games"); + empty_alert.icon_name = src.icon + "-symbolic"; + stack.set_visible_child(empty_alert); + } + else + { + var tab = view.selected == 0 ? (Widget) games_grid_scrolled : (Widget) games_list_paned; + stack.set_visible_child(tab); + saved_state.games_view = view.selected == 0 ? Settings.GamesView.GRID : Settings.GamesView.LIST; + } + } + + private void show_games() + { + if(view.opacity != 0 || stack.visible_child != empty_alert) return; + view.set_active(saved_state.games_view == Settings.GamesView.LIST ? 1 : 0); stack.set_visible_child(saved_state.games_view == Settings.GamesView.LIST ? (Widget) games_list_paned : (Widget) games_grid_scrolled); - load_games.begin(); + view.opacity = 1; + view.sensitive = true; + filter.opacity = 1; + filter.sensitive = true; + search.opacity = 1; + search.sensitive = true; + downloads.opacity = 1; } private async void load_games() { + messages.get_children().foreach(c => messages.remove(c)); + foreach(var src in sources) { loading_sources++; spinner.active = loading_sources > 0; src.load_games.begin(g => { + update_view(); + games_grid.add(new GameCard(g)); games_list.add(new GameListRow(g)); games_grid.show_all(); @@ -233,15 +326,57 @@ namespace GameHub.UI.Views var pv = new GameDownloadProgressView(g); downloads_list.add(pv); g.status_change.connect(s => { - if(s.state == Game.State.DOWNLOAD_STARTED) downloads_count++; - else if(s.state == Game.State.DOWNLOAD_FINISHED) downloads_count--; - pv.visible = s.state == Game.State.DOWNLOADING || s.state == Game.State.DOWNLOAD_STARTED; + if(s.state == Game.State.INSTALLING) + { + downloads_count--; + } + else if(s.state == Game.State.DOWNLOADING) + { + if(s.download != null && (s.download.status.state == DownloadState.CANCELLED + || s.download.status.state == DownloadState.FAILED)) + downloads_count--; + else if(!pv.visible) downloads_count++; + } + pv.visible = s.state == Game.State.DOWNLOADING; + downloads_count = int.max(0, downloads_count); downloads.set_sensitive(downloads_count > 0); }); g.status_change(g.status); }, (obj, res) => { loading_sources--; spinner.active = loading_sources > 0; + + if(src.games_count == 0) + { + if(src is GameHub.Data.Sources.Steam.Steam) + { + var msg = message(_("No games were loaded from Steam. Set your games list privacy to public or use your own Steam API key in settings."), MessageType.WARNING); + msg.add_button(_("Privacy"), 1); + msg.add_button(_("Settings"), 2); + + msg.close.connect(() => { + msg.revealed = false; + Timeout.add(250, () => { messages.remove(msg); return false; }); + }); + + msg.response.connect(r => { + switch(r) + { + case 1: + Utils.open_uri("steam://openurl/https://steamcommunity.com/my/edit/settings"); + break; + + case 2: + settings.clicked(); + break; + + case ResponseType.CLOSE: + msg.close(); + break; + } + }); + } + } }); } @@ -275,8 +410,25 @@ namespace GameHub.UI.Views var row = games_list.get_selected_row() as GameListRow?; if(row == null || games_filter(row.game)) return; - row = games_list.get_row_at_y(1) as GameListRow?; + row = games_list.get_row_at_y(32) as GameListRow?; games_list.select_row(row); } + + private InfoBar message(string text, MessageType type=MessageType.OTHER) + { + var bar = new InfoBar(); + bar.message_type = type; + bar.revealed = false; + bar.show_close_button = true; + bar.get_content_area().add(new Label(text)); + + messages.add(bar); + + bar.show_all(); + + bar.revealed = true; + + return bar; + } } } diff --git a/src/ui/views/WelcomeView.vala b/src/ui/views/WelcomeView.vala index b10b496f..d0bb5e67 100644 --- a/src/ui/views/WelcomeView.vala +++ b/src/ui/views/WelcomeView.vala @@ -53,8 +53,7 @@ namespace GameHub.UI.Views skip_btn.clicked.connect(open_games_view); skip_btn.halign = Align.CENTER; skip_btn.valign = Align.CENTER; - skip_btn.set_sensitive(false); - + settings = new Button(); settings.tooltip_text = _("Settings"); settings.image = new Image.from_icon_name("open-menu", IconSize.LARGE_TOOLBAR); @@ -66,7 +65,9 @@ namespace GameHub.UI.Views titlebar.pack_end(skip_btn); settings.opacity = 0; + settings.sensitive = false; skip_btn.opacity = 0; + skip_btn.sensitive = false; foreach(var src in GameSources) { @@ -91,7 +92,7 @@ namespace GameHub.UI.Views if(is_updating) return; is_updating = true; - skip_btn.set_sensitive(false); + skip_btn.sensitive = false; var all_authenticated = true; int enabled_sources = 0; @@ -114,7 +115,7 @@ namespace GameHub.UI.Views { btn.description = _("Ready"); welcome.set_item_sensitivity(index, false); - skip_btn.set_sensitive(true); + skip_btn.sensitive = true; } else { @@ -149,6 +150,7 @@ namespace GameHub.UI.Views if(enabled_sources == 0) { settings.opacity = 0; + settings.sensitive = false; skip_btn.opacity = 0; stack.set_visible_child(empty_alert); empty_alert.show_all(); @@ -156,6 +158,7 @@ namespace GameHub.UI.Views else { settings.opacity = 1; + settings.sensitive = true; skip_btn.opacity = 1; stack.set_visible_child(welcome); welcome.show_all(); diff --git a/src/utils/Downloader.vala b/src/utils/Downloader.vala deleted file mode 100644 index 85c9b740..00000000 --- a/src/utils/Downloader.vala +++ /dev/null @@ -1,247 +0,0 @@ -using Gtk; -using Gdk; - -namespace GameHub.Utils -{ - public delegate void DownloadProgress(int64 downloaded, int64 total); - - public class Download - { - public string uri; - public File remote_file; - public File cached_file; - public DownloadProgress progress; - - public Download(File remote_file, File cached_file, owned DownloadProgress progress) - { - this.remote_file = remote_file; - this.uri = remote_file.get_uri(); - this.cached_file = cached_file; - this.progress = (owned) progress; - } - } - - public class Downloader: GLib.Object - { - private static Downloader downloader; - private Soup.Session session; - - private GLib.HashTable downloads; - - public signal void downloaded(Download download); - public signal void download_failed(Download download, GLib.Error error); - - public static string[] supported_schemes = { - "http", - "https", - }; - - public static Downloader get_instance() - { - if (downloader == null) - downloader = new Downloader(); - - return downloader; - } - - private Downloader() - { - downloads = new GLib.HashTable(str_hash, str_equal); - session = new Soup.Session(); - session.max_conns = 32; - session.max_conns_per_host = 16; - } - - public async File download(File remote_file, string[] cached_paths, DownloadProgress progress = (d, t) => {}, Cancellable? cancellable = null) throws GLib.Error - { - var uri = remote_file.get_uri(); - var download = downloads.get(uri); - var cached_path = cached_paths[0]; - - if(download != null) - return yield await_download(download, cached_path, (d, t) => progress(d, t)); - - var cached_file = get_cached_file(remote_file, cached_paths); - if(cached_file != null) - return cached_file; - - var tmp_path = cached_path + "~"; - var tmp_file = GLib.File.new_for_path(tmp_path); - debug("Downloading '%s'...", uri); - download = new Download(remote_file, tmp_file, (d, t) => progress(d, t)); - downloads.set(uri, download); - - try - { - if(remote_file.get_uri_scheme() in supported_schemes) - yield download_from_http(download, cancellable); - else - yield download_from_filesystem(download, cancellable); - } - catch(GLib.Error error) - { - download_failed(download, error); - throw error; - } - finally - { - downloads.remove(uri); - } - - cached_file = GLib.File.new_for_path(cached_path); - tmp_file.move(cached_file, FileCopyFlags.NONE, cancellable); - download.cached_file = cached_file; - - debug("Downloaded '%s' and it's now locally available at '%s'.", uri, cached_path); - downloaded(download); - - return cached_file; - } - - private async void download_from_http(Download download, Cancellable? cancellable = null) throws GLib.Error - { - var msg = new Soup.Message("GET", download.uri); - msg.response_body.set_accumulate(false); - - #if !FLATPAK - var address = msg.get_address(); - var connectable = new NetworkAddress(address.name, (uint16) address.port); - var network_monitor = NetworkMonitor.get_default(); - if(!(yield network_monitor.can_reach_async(connectable))) - throw new GLib.IOError.HOST_UNREACHABLE("Failed to reach host"); - #endif - - GLib.Error? err = null; - ulong cancelled_id = 0; - if(cancellable != null) - cancelled_id = cancellable.connect(() => { - err = new GLib.IOError.CANCELLED("Cancelled by cancellable."); - session.cancel_message(msg, Soup.Status.CANCELLED); - }); - - int64 total_num_bytes = 0; - msg.got_headers.connect(() => { - total_num_bytes = msg.response_headers.get_content_length(); - }); - - var cached_file_stream = yield download.cached_file.replace_async(null, false, FileCreateFlags.REPLACE_DESTINATION); - - int64 current_num_bytes = 0; - - msg.got_chunk.connect((msg, chunk) => { - if(session.would_redirect(msg)) - return; - - current_num_bytes += chunk.length; - try - { - cached_file_stream.write(chunk.data); - if(total_num_bytes > 0) - download.progress(current_num_bytes, total_num_bytes); - } - catch(GLib.Error e) - { - err = e; - session.cancel_message(msg, Soup.Status.CANCELLED); - } - }); - - session.queue_message(msg, (session, msg) => { - download_from_http.callback(); - }); - - yield; - - if(cancelled_id != 0) - cancellable.disconnect (cancelled_id); - - yield cached_file_stream.close_async(Priority.DEFAULT, cancellable); - - if(msg.status_code != Soup.Status.OK) - { - download.cached_file.delete(); - if(err == null) - err = new GLib.Error(Soup.http_error_quark(), (int) msg.status_code, msg.reason_phrase); - - throw err; - } - } - - private async File? await_download(Download download, string cached_path, owned DownloadProgress progress) throws GLib.Error - { - File downloaded_file = null; - GLib.Error download_error = null; - - SourceFunc callback = await_download.callback; - var downloaded_id = downloaded.connect((downloader, downloaded) => { - if (downloaded.uri != download.uri) - return; - - downloaded_file = downloaded.cached_file; - callback(); - }); - var downloaded_failed_id = download_failed.connect((downloader, failed_download, error) => { - if (failed_download.uri != download.uri) - return; - - download_error = error; - callback (); - }); - - debug("'%s' already being downloaded. Waiting for download to complete..", download.uri); - yield; - debug("Finished waiting for '%s' to download.", download.uri); - disconnect(downloaded_id); - disconnect(downloaded_failed_id); - - if(download_error != null) - throw download_error; - - File cached_file; - if(downloaded_file.get_path () != cached_path) - { - cached_file = File.new_for_path (cached_path); - yield downloaded_file.copy_async (cached_file, FileCopyFlags.OVERWRITE); - } - else - cached_file = downloaded_file; - - return cached_file; - } - - private async void download_from_filesystem(Download download, Cancellable? cancellable = null) throws GLib.Error - { - var src_file = download.remote_file; - var dest_file = download.cached_file; - - try - { - debug("Copying '%s' to '%s'..", src_file.get_path (), dest_file.get_path ()); - yield src_file.copy_async(dest_file, - FileCopyFlags.OVERWRITE, - Priority.DEFAULT, - cancellable, - (current, total) => { - download.progress(current, total); - }); - debug("Copied '%s' to '%s'.", src_file.get_path(), dest_file.get_path()); - } - catch(IOError.EXISTS error){} - } - - private File? get_cached_file(File remote_file, string[] cached_paths) - { - foreach(var path in cached_paths) - { - var cached_file = File.new_for_path(path); - if(cached_file.query_exists()) - { - debug("'%s' already available locally at '%s'. Not downloading.", remote_file.get_uri(), path); - return cached_file; - } - } - - return null; - } - } -} diff --git a/src/utils/FSUtils.vala b/src/utils/FSUtils.vala index c0c6b696..5d0da5d2 100644 --- a/src/utils/FSUtils.vala +++ b/src/utils/FSUtils.vala @@ -118,8 +118,8 @@ namespace GameHub.Utils mkdir(FSUtils.Paths.Cache.Home); mkdir(FSUtils.Paths.Cache.Images); - FSUtils.rm(FSUtils.Paths.GOG.Installers, "{*~,.goutputstream-*}"); - FSUtils.rm(FSUtils.Paths.Humble.Installers, "{*~,.goutputstream-*}"); + FSUtils.rm(FSUtils.Paths.GOG.Installers, ".goutputstream-*"); + FSUtils.rm(FSUtils.Paths.Humble.Installers, ".goutputstream-*"); } } } diff --git a/src/utils/Settings.vala b/src/utils/Settings.vala index c579136f..04de1e00 100644 --- a/src/utils/Settings.vala +++ b/src/utils/Settings.vala @@ -75,7 +75,21 @@ namespace GameHub.Settings { base(ProjectConfig.PROJECT_NAME + ".auth.steam"); } - + + protected override void verify(string key) + { + switch(key) + { + case "api-key": + if(api_key.length != 32) + { + schema.reset("api-key"); + } + break; + } + } + + private static Steam? instance; public static unowned Steam get_instance() { diff --git a/src/utils/Utils.vala b/src/utils/Utils.vala index f6e59610..3d5274e9 100644 --- a/src/utils/Utils.vala +++ b/src/utils/Utils.vala @@ -122,19 +122,24 @@ namespace GameHub.Utils yield; } + public static string md5(string s) + { + return Checksum.compute_for_string(ChecksumType.MD5, s, s.length); + } + public static async string? cache_image(string url, string prefix="remote") { var parts = url.split("?")[0].split("."); var ext = parts.length > 1 ? parts[parts.length - 1] : null; ext = ext != null && ext.length <= 6 ? "." + ext : null; - var hash = Checksum.compute_for_string(ChecksumType.MD5, url, url.length); + var hash = md5(url); var remote = File.new_for_uri(url); var cached = FSUtils.file(FSUtils.Paths.Cache.Images, @"$(prefix)_$(hash)$(ext)"); try { if(!cached.query_exists()) { - yield Downloader.get_instance().download(remote, { cached.get_path() }); + yield Downloader.download(remote, cached); } return cached.get_path(); } diff --git a/src/utils/downloader/Downloader.vala b/src/utils/downloader/Downloader.vala new file mode 100644 index 00000000..4b99baea --- /dev/null +++ b/src/utils/downloader/Downloader.vala @@ -0,0 +1,154 @@ +using GLib; + +namespace GameHub.Utils.Downloader +{ + public abstract class Downloader: Object + { + private static Downloader? downloader; + + public signal void download_started(Download download); + public signal void downloaded(Download download); + public signal void download_failed(Download download, Error error); + + public static Downloader? get_instance() + { + if(downloader == null) + { + downloader = new GameHub.Utils.Downloader.Soup.SoupDownloader(); + } + + return downloader; + } + + public abstract async File download(File remote, File local) throws Error; + public abstract Download? get_download(File remote); + } + + public static async File download(File remote, File local) throws Error + { + File result = local; + Error? error = null; + + new Thread("dl-thread-" + Utils.md5(remote.get_uri()), () => { + var downloader = Downloader.get_instance(); + if(downloader == null) + { + Idle.add(download.callback); + } + else + { + downloader.download.begin(remote, local, (obj, res) => { + try + { + result = downloader.download.end(res); + } + catch(Error e) + { + error = e; + } + Idle.add(download.callback); + }); + } + + return null; + }); + + yield; + + if(error != null) throw error; + + return result; + } + + public static Download? get_download(File remote) + { + var downloader = Downloader.get_instance(); + if(downloader == null) return null; + return downloader.get_download(remote); + } + + public static Downloader? get_instance() + { + return Downloader.get_instance(); + } + + public abstract class Download + { + public File remote; + public File local; + + protected DownloadStatus _status = new DownloadStatus(); + public signal void status_change(DownloadStatus status); + + public DownloadStatus status + { + get { return _status; } + set { _status = value; status_change(_status); } + } + + public Download(File remote, File local) + { + this.remote = remote; + this.local = local; + } + + public abstract void cancel(); + } + + public abstract class PausableDownload: Download + { + public PausableDownload(File remote, File local) + { + base(remote, local); + } + + public abstract void pause(); + public abstract void resume(); + } + + public class DownloadStatus + { + public DownloadState state; + + public int64 bytes_downloaded; + public int64 bytes_total; + + public DownloadStatus(DownloadState state=DownloadState.STARTING, int64 downloaded = -1, int64 total = -1) + { + this.state = state; + this.bytes_downloaded = downloaded; + this.bytes_total = total; + } + + public double progress + { + get { + return (double) bytes_downloaded / bytes_total; + } + } + + public string description + { + owned get + { + switch(state) + { + case DownloadState.STARTING: return _("Starting download"); + case DownloadState.STARTED: return _("Download started"); + case DownloadState.FINISHED: return _("Download finished"); + case DownloadState.FAILED: return _("Download failed"); + case DownloadState.DOWNLOADING: + return _("Downloading: %d%% (%s / %s)").printf((int)(progress * 100), format_size(bytes_downloaded), format_size(bytes_total)); + case DownloadState.PAUSED: + return _("Paused: %d%% (%s / %s)").printf((int)(progress * 100), format_size(bytes_downloaded), format_size(bytes_total)); + } + return _("Download cancelled"); + } + } + } + + public enum DownloadState + { + STARTING, STARTED, DOWNLOADING, FINISHED, PAUSED, CANCELLED, FAILED; + } +} \ No newline at end of file diff --git a/src/utils/downloader/SoupDownloader.vala b/src/utils/downloader/SoupDownloader.vala new file mode 100644 index 00000000..d4d0c9b9 --- /dev/null +++ b/src/utils/downloader/SoupDownloader.vala @@ -0,0 +1,261 @@ +using GLib; +using Soup; + +using GameHub.Utils.Downloader; + +namespace GameHub.Utils.Downloader.Soup +{ + public class SoupDownloader: Downloader + { + private Session session; + + private HashTable downloads; + + private static string[] supported_schemes = { "http", "https" }; + + public SoupDownloader() + { + downloads = new HashTable(str_hash, str_equal); + session = new Session(); + session.max_conns = 32; + session.max_conns_per_host = 16; + } + + public override Download? get_download(File remote) + { + return downloads.get(remote.get_uri()); + } + + public override async File download(File remote, File local) throws Error + { + var uri = remote.get_uri(); + SoupDownload download = downloads.get(uri); + + if(download != null) return yield await_download(download); + + if(local.query_exists()) + { + debug("[SoupDownloader] '%s' is already downloaded", uri); + return local; + } + + var tmp = File.new_for_path(local.get_path() + "~"); + + download = new SoupDownload(remote, tmp); + download.session = session; + downloads.set(uri, download); + + download_started(download); + + debug("[SoupDownloader] Downloading '%s'...", uri); + + try + { + if(remote.get_uri_scheme() in supported_schemes) + yield download_from_http(download); + else + yield download_from_filesystem(download); + } + catch(IOError.CANCELLED error) + { + download.status = new DownloadStatus(DownloadState.CANCELLED); + throw error; + } + catch(Error error) + { + download.status = new DownloadStatus(DownloadState.FAILED); + download_failed(download, error); + throw error; + } + finally + { + downloads.remove(uri); + } + + tmp.move(local, FileCopyFlags.NONE); + + debug("[SoupDownloader] Downloaded '%s'", uri); + + downloaded(download); + + return local; + } + + private async void download_from_http(SoupDownload download) throws Error + { + var msg = new Message("GET", download.remote.get_uri()); + msg.response_body.set_accumulate(false); + + download.session = session; + download.message = msg; + + #if !FLATPAK + var address = msg.get_address(); + var connectable = new NetworkAddress(address.name, (uint16) address.port); + var network_monitor = NetworkMonitor.get_default(); + if(!(yield network_monitor.can_reach_async(connectable))) + throw new IOError.HOST_UNREACHABLE("Failed to reach host"); + #endif + + GLib.Error? err = null; + + FileOutputStream? local_stream = null; + + int64 dl_bytes = 0; + int64 dl_bytes_total = 0; + + int64 resume_from = 0; + var resume_dl = false; + + if(download.local.get_basename().has_suffix("~") && download.local.query_exists()) + { + var info = yield download.local.query_info_async(FileAttribute.STANDARD_SIZE, FileQueryInfoFlags.NONE); + resume_from = info.get_size(); + if(resume_from > 0) + { + resume_dl = true; + msg.request_headers.set_range(resume_from, -1); + debug(@"[SoupDownloader] Download part found, size: $(resume_from)"); + } + } + + msg.got_headers.connect(() => { + dl_bytes_total = msg.response_headers.get_content_length(); + debug(@"[SoupDownloader] Content-Length: $(dl_bytes_total)"); + try + { + int64 rstart = -1, rend = -1; + if(resume_dl && msg.response_headers.get_content_range(out rstart, out rend, out dl_bytes_total)) + { + debug(@"[SoupDownloader] Content-Range is supported($(rstart)-$(rend)), resuming from $(resume_from)"); + debug(@"[SoupDownloader] Content-Length: $(dl_bytes_total)"); + dl_bytes = resume_from; + local_stream = download.local.append_to(FileCreateFlags.NONE); + } + else + { + local_stream = download.local.replace(null, false, FileCreateFlags.REPLACE_DESTINATION); + } + } + catch(Error e) + { + warning(e.message); + } + }); + + msg.got_chunk.connect((msg, chunk) => { + if(session.would_redirect(msg)) return; + + dl_bytes += chunk.length; + try + { + local_stream.write(chunk.data); + download.status = new DownloadStatus(DownloadState.DOWNLOADING, dl_bytes, dl_bytes_total); + } + catch(Error e) + { + err = e; + session.cancel_message(msg, Status.CANCELLED); + } + }); + + session.queue_message(msg, (session, msg) => { + download_from_http.callback(); + }); + + yield; + + yield local_stream.close_async(Priority.DEFAULT); + + if(msg.status_code != Status.OK && msg.status_code != Status.PARTIAL_CONTENT) + { + if(msg.status_code == Status.CANCELLED) + { + throw new IOError.CANCELLED("Download cancelled by user"); + } + + if(err == null) + err = new GLib.Error(http_error_quark(), (int) msg.status_code, msg.reason_phrase); + + throw err; + } + } + + private async File? await_download(SoupDownload download) throws Error + { + File downloaded_file = null; + Error download_error = null; + + SourceFunc callback = await_download.callback; + var downloaded_id = downloaded.connect((downloader, downloaded) => { + if(downloaded.remote.get_uri() != download.remote.get_uri()) return; + downloaded_file = downloaded.local; + callback(); + }); + var downloaded_failed_id = download_failed.connect((downloader, failed_download, error) => { + if(failed_download.remote.get_uri() != download.remote.get_uri()) return; + download_error = error; + callback(); + }); + + yield; + + disconnect(downloaded_id); + disconnect(downloaded_failed_id); + + if(download_error != null) throw download_error; + + return downloaded_file; + } + + private async void download_from_filesystem(SoupDownload download) throws GLib.Error + { + try + { + debug("[SoupDownloader] Copying '%s' to '%s'", download.remote.get_path(), download.local.get_path()); + yield download.remote.copy_async( + download.local, + FileCopyFlags.OVERWRITE, + Priority.DEFAULT, + null, + (current, total) => { download.status = new DownloadStatus(DownloadState.DOWNLOADING, current, total); }); + } + catch(IOError.EXISTS error){} + } + } + + public class SoupDownload: PausableDownload + { + public Session? session; + public Message? message; + + public SoupDownload(File remote, File local) + { + base(remote, local); + } + + public override void pause() + { + if(session != null && message != null && _status.state == DownloadState.DOWNLOADING) + { + session.pause_message(message); + _status.state = DownloadState.PAUSED; + status_change(_status); + } + } + public override void resume() + { + if(session != null && message != null && _status.state == DownloadState.PAUSED) + { + session.unpause_message(message); + } + } + public override void cancel() + { + if(session != null && message != null) + { + session.cancel_message(message, Status.CANCELLED); + } + } + } +}