Compare commits
24 Commits
dolphin-so
...
v2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca73023bc9 | ||
|
|
b8cd1fd741 | ||
|
|
26e8d6d2c6 | ||
|
|
7bcb113a33 | ||
|
|
fbf076660d | ||
|
|
b1992a9466 | ||
|
|
da777d3605 | ||
|
|
4587720934 | ||
|
|
270fa2092c | ||
|
|
82dddd1c5c | ||
|
|
bb4870e99d | ||
|
|
190b446de5 | ||
|
|
52b6c47c8d | ||
|
|
0df123975c | ||
|
|
7f576d1bd3 | ||
|
|
30152cd10a | ||
|
|
2acdedf033 | ||
|
|
a399113ff9 | ||
|
|
8839db272b | ||
|
|
a0bfca01d6 | ||
|
|
0601fd5ebb | ||
|
|
15da65fccf | ||
|
|
00ff297867 | ||
|
|
fbb2ccec57 |
@@ -174,6 +174,15 @@ template $PreferencesWindow : Adw.PreferencesWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Import Amazon Games");
|
||||
activatable-widget: heroic_import_amazon_switch;
|
||||
|
||||
Switch heroic_import_amazon_switch {
|
||||
valign: center;
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Import Sideloaded Games");
|
||||
activatable-widget: heroic_import_sideload_switch;
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<key name="heroic-import-gog" type="b">
|
||||
<default>true</default>
|
||||
</key>
|
||||
<key name="heroic-import-amazon" type="b">
|
||||
<default>true</default>
|
||||
</key>
|
||||
<key name="heroic-import-sideload" type="b">
|
||||
<default>true</default>
|
||||
</key>
|
||||
|
||||
@@ -44,10 +44,11 @@
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1" />
|
||||
<releases>
|
||||
<release version="2.0.6" date="2023-07-21">
|
||||
<release version="2.1" date="2023-07-25">
|
||||
<description translatable="no">
|
||||
<ul>
|
||||
<li>Fixes an issue with Steam mods not importing properly</li>
|
||||
<li>Added support for Amazon Games in the Heroic importer</li>
|
||||
<li>Translations since 2.0</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"--filesystem=~/.var/app/com.valvesoftware.Steam/data/Steam/:ro",
|
||||
"--filesystem=~/.var/app/net.lutris.Lutris/:ro",
|
||||
"--filesystem=~/.var/app/com.heroicgameslauncher.hgl/config/heroic/:ro",
|
||||
"--filesystem=~/.var/app/com.heroicgameslauncher.hgl/config/legendary/:ro",
|
||||
"--filesystem=~/.var/app/com.usebottles.bottles/data/bottles/:ro",
|
||||
"--filesystem=~/.var/app/io.itch.itch/config/itch/:ro",
|
||||
"--filesystem=/var/lib/flatpak:ro"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('cartridges',
|
||||
version: '2.0.6',
|
||||
version: '2.1',
|
||||
meson_version: '>= 0.59.0',
|
||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||
)
|
||||
|
||||
@@ -19,3 +19,4 @@ pl
|
||||
sv
|
||||
tr
|
||||
el
|
||||
cs
|
||||
|
||||
553
po/cs.po
Normal file
553
po/cs.po
Normal file
@@ -0,0 +1,553 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR kramo
|
||||
# This file is distributed under the same license as the Cartridges package.
|
||||
# foo expert <deferred_water346@simplelogin.com>, 2023.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Cartridges\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-07-05 14:36+0200\n"
|
||||
"PO-Revision-Date: 2023-07-24 13:05+0000\n"
|
||||
"Last-Translator: foo expert <deferred_water346@simplelogin.com>\n"
|
||||
"Language-Team: Czech <https://hosted.weblate.org/projects/cartridges/"
|
||||
"cartridges/cs/>\n"
|
||||
"Language: cs\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 5.0-dev\n"
|
||||
|
||||
#: data/hu.kramo.Cartridges.desktop.in:3
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:6 data/gtk/window.blp:47
|
||||
#: src/main.py:162
|
||||
msgid "Cartridges"
|
||||
msgstr "Kazety"
|
||||
|
||||
#: data/hu.kramo.Cartridges.desktop.in:4
|
||||
msgid "Game Launcher"
|
||||
msgstr "Spouštěč her"
|
||||
|
||||
#: data/hu.kramo.Cartridges.desktop.in:5
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:7
|
||||
msgid "Launch all your games"
|
||||
msgstr "Spusťte všechny vaše hry"
|
||||
|
||||
#: data/hu.kramo.Cartridges.desktop.in:11
|
||||
msgid "gaming;launcher;steam;lutris;heroic;bottles;itch;"
|
||||
msgstr "hraní;spouštěč;steam;lutris;heroic;láhve;itch;"
|
||||
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:9
|
||||
msgid ""
|
||||
"Cartridges is a simple game launcher for all of your games. It has support "
|
||||
"for importing games from Steam, Lutris, Heroic and more with no login "
|
||||
"necessary. You can sort and hide games or download cover art from "
|
||||
"SteamGridDB."
|
||||
msgstr ""
|
||||
"Kazety jsou jednoduchý spouštěč pro všechny vaše hry. Podporuje importovaní "
|
||||
"her ze služeb Steam, Lutris, Heroic a dalších bez nutnosti přihlášení. Hry "
|
||||
"můžete třídit a skrývat nebo stahovat obálky ze služby SteamGridDB."
|
||||
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:30
|
||||
msgid "Library"
|
||||
msgstr "Knihovna"
|
||||
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:34 src/details_window.py:67
|
||||
msgid "Edit Game Details"
|
||||
msgstr "Upravit podrobnosti o hře"
|
||||
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:38 data/gtk/window.blp:71
|
||||
msgid "Game Details"
|
||||
msgstr "Podrobnosti o hře"
|
||||
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:42 data/gtk/window.blp:416
|
||||
#: src/details_window.py:239
|
||||
msgid "Preferences"
|
||||
msgstr "Předvolby"
|
||||
|
||||
#: data/gtk/details-window.blp:25
|
||||
msgid "Cancel"
|
||||
msgstr "Zrušit"
|
||||
|
||||
#: data/gtk/details-window.blp:57
|
||||
msgid "New Cover"
|
||||
msgstr "Nový obal"
|
||||
|
||||
#: data/gtk/details-window.blp:75
|
||||
msgid "Delete Cover"
|
||||
msgstr "Odstranit obal"
|
||||
|
||||
#: data/gtk/details-window.blp:101 data/gtk/details-window.blp:106
|
||||
#: data/gtk/game.blp:80
|
||||
msgid "Title"
|
||||
msgstr "Název"
|
||||
|
||||
#: data/gtk/details-window.blp:102
|
||||
msgid "The title of the game"
|
||||
msgstr "Název hry"
|
||||
|
||||
#: data/gtk/details-window.blp:112 data/gtk/details-window.blp:117
|
||||
msgid "Developer"
|
||||
msgstr "Vývojář"
|
||||
|
||||
#: data/gtk/details-window.blp:113
|
||||
msgid "The developer or publisher (optional)"
|
||||
msgstr "Vývojář nebo vydavatel (nepovinné)"
|
||||
|
||||
#: data/gtk/details-window.blp:123 data/gtk/details-window.blp:155
|
||||
msgid "Executable"
|
||||
msgstr "Spustitelný soubor"
|
||||
|
||||
#: data/gtk/details-window.blp:124
|
||||
msgid "File to open or command to run when launching the game"
|
||||
msgstr "Soubor nebo příkaz pro spuštění hry"
|
||||
|
||||
#: data/gtk/details-window.blp:130
|
||||
msgid "More Info"
|
||||
msgstr "Více informací"
|
||||
|
||||
#: data/gtk/game.blp:102 data/gtk/game.blp:121 data/gtk/window.blp:195
|
||||
msgid "Edit"
|
||||
msgstr "Upravit"
|
||||
|
||||
#: data/gtk/game.blp:107 src/window.py:171
|
||||
msgid "Hide"
|
||||
msgstr "Skrýt"
|
||||
|
||||
#: data/gtk/game.blp:112 data/gtk/game.blp:131 data/gtk/preferences.blp:56
|
||||
#: data/gtk/window.blp:209
|
||||
msgid "Remove"
|
||||
msgstr "Odstranit"
|
||||
|
||||
#: data/gtk/game.blp:126 src/window.py:173
|
||||
msgid "Unhide"
|
||||
msgstr "Odkrýt"
|
||||
|
||||
#: data/gtk/help-overlay.blp:11 data/gtk/preferences.blp:9
|
||||
msgid "General"
|
||||
msgstr "Obecné"
|
||||
|
||||
#: data/gtk/help-overlay.blp:14
|
||||
msgid "Quit"
|
||||
msgstr "Ukončit"
|
||||
|
||||
#: data/gtk/help-overlay.blp:19 data/gtk/window.blp:217 data/gtk/window.blp:257
|
||||
#: data/gtk/window.blp:323
|
||||
msgid "Search"
|
||||
msgstr "Vyhledávání"
|
||||
|
||||
#: data/gtk/help-overlay.blp:24
|
||||
msgid "Show preferences"
|
||||
msgstr "Zobrazit předvolby"
|
||||
|
||||
#: data/gtk/help-overlay.blp:29
|
||||
msgid "Shortcuts"
|
||||
msgstr "Zkratky"
|
||||
|
||||
#: data/gtk/help-overlay.blp:34 src/game.py:102 src/preferences.py:112
|
||||
msgid "Undo"
|
||||
msgstr "Zpět"
|
||||
|
||||
#: data/gtk/help-overlay.blp:39
|
||||
msgid "Open menu"
|
||||
msgstr "Otevřít nabídku"
|
||||
|
||||
#: data/gtk/help-overlay.blp:45
|
||||
msgid "Games"
|
||||
msgstr "Hry"
|
||||
|
||||
#: data/gtk/help-overlay.blp:48
|
||||
msgid "Add new game"
|
||||
msgstr "Přidat novou hru"
|
||||
|
||||
#: data/gtk/help-overlay.blp:53
|
||||
msgid "Import games"
|
||||
msgstr "Importovat hry"
|
||||
|
||||
#: data/gtk/help-overlay.blp:58
|
||||
msgid "Show hidden games"
|
||||
msgstr "Zobrazit skryté hry"
|
||||
|
||||
#: data/gtk/help-overlay.blp:63
|
||||
msgid "Remove game"
|
||||
msgstr "Odstranit hru"
|
||||
|
||||
#: data/gtk/preferences.blp:13 data/gtk/preferences.blp:268
|
||||
msgid "Behavior"
|
||||
msgstr "Chování"
|
||||
|
||||
#: data/gtk/preferences.blp:16
|
||||
msgid "Exit After Launching Games"
|
||||
msgstr "Ukončit po spuštění her"
|
||||
|
||||
#: data/gtk/preferences.blp:25
|
||||
msgid "Cover Image Launches Game"
|
||||
msgstr "Obrázek na obálce spouští hru"
|
||||
|
||||
#: data/gtk/preferences.blp:26
|
||||
msgid "Swaps the behavior of the cover image and the play button"
|
||||
msgstr "Vymění chování obrázku na obálce a tlačítka pro přehrávání"
|
||||
|
||||
#: data/gtk/preferences.blp:36 src/details_window.py:81
|
||||
msgid "Images"
|
||||
msgstr "Obrázky"
|
||||
|
||||
#: data/gtk/preferences.blp:39
|
||||
msgid "High Quality Images"
|
||||
msgstr "Vysoce kvalitní obrázky"
|
||||
|
||||
#: data/gtk/preferences.blp:40
|
||||
msgid "Save game covers losslessly at the cost of storage"
|
||||
msgstr "Ukládat obaly her bezztrátově na úkor většího místa na disku"
|
||||
|
||||
#: data/gtk/preferences.blp:50
|
||||
msgid "Danger Zone"
|
||||
msgstr "Nebezpečná zóna"
|
||||
|
||||
#: data/gtk/preferences.blp:53
|
||||
msgid "Remove All Games"
|
||||
msgstr "Odstranit všechny hry"
|
||||
|
||||
#: data/gtk/preferences.blp:85 data/gtk/window.blp:27 data/gtk/window.blp:442
|
||||
msgid "Import"
|
||||
msgstr "Import"
|
||||
|
||||
#: data/gtk/preferences.blp:89
|
||||
msgid "Sources"
|
||||
msgstr "Zdroje"
|
||||
|
||||
#: data/gtk/preferences.blp:92
|
||||
msgid "Steam"
|
||||
msgstr "Steam"
|
||||
|
||||
#: data/gtk/preferences.blp:96 data/gtk/preferences.blp:110
|
||||
#: data/gtk/preferences.blp:151 data/gtk/preferences.blp:192
|
||||
#: data/gtk/preferences.blp:206 data/gtk/preferences.blp:220
|
||||
#: data/gtk/preferences.blp:234
|
||||
msgid "Install Location"
|
||||
msgstr "Umístění instalace"
|
||||
|
||||
#: data/gtk/preferences.blp:106
|
||||
msgid "Lutris"
|
||||
msgstr "Lutris"
|
||||
|
||||
#: data/gtk/preferences.blp:119
|
||||
msgid "Cache Location"
|
||||
msgstr "Umístění dočasných souborů"
|
||||
|
||||
#: data/gtk/preferences.blp:128
|
||||
msgid "Import Steam Games"
|
||||
msgstr "Importovat Steam hry"
|
||||
|
||||
#: data/gtk/preferences.blp:137
|
||||
msgid "Import Flatpak Games"
|
||||
msgstr "Importovat Flatpak hry"
|
||||
|
||||
#: data/gtk/preferences.blp:147
|
||||
msgid "Heroic"
|
||||
msgstr "Heroic"
|
||||
|
||||
#: data/gtk/preferences.blp:160
|
||||
msgid "Import Epic Games"
|
||||
msgstr "Importovat Epic Games hry"
|
||||
|
||||
#: data/gtk/preferences.blp:169
|
||||
msgid "Import GOG Games"
|
||||
msgstr "Importovat GOG hry"
|
||||
|
||||
#: data/gtk/preferences.blp:178
|
||||
msgid "Import Sideloaded Games"
|
||||
msgstr "Importovat ručně načtené hry"
|
||||
|
||||
#: data/gtk/preferences.blp:188
|
||||
msgid "Bottles"
|
||||
msgstr "Láhve"
|
||||
|
||||
#: data/gtk/preferences.blp:202
|
||||
msgid "itch"
|
||||
msgstr "itch"
|
||||
|
||||
#: data/gtk/preferences.blp:216
|
||||
msgid "Legendary"
|
||||
msgstr "Legendary"
|
||||
|
||||
#: data/gtk/preferences.blp:230
|
||||
msgid "Flatpak"
|
||||
msgstr "Flatpak"
|
||||
|
||||
#: data/gtk/preferences.blp:243
|
||||
msgid "Import Game Launchers"
|
||||
msgstr "Importovat spouštěče her"
|
||||
|
||||
#: data/gtk/preferences.blp:256
|
||||
msgid "SteamGridDB"
|
||||
msgstr "SteamGridDB"
|
||||
|
||||
#: data/gtk/preferences.blp:260
|
||||
msgid "Authentication"
|
||||
msgstr "Ověření"
|
||||
|
||||
#: data/gtk/preferences.blp:263
|
||||
msgid "API Key"
|
||||
msgstr "Klíč API"
|
||||
|
||||
#: data/gtk/preferences.blp:271
|
||||
msgid "Use SteamGridDB"
|
||||
msgstr "Používat SteamGridDB"
|
||||
|
||||
#: data/gtk/preferences.blp:272
|
||||
msgid "Download images when adding or importing games"
|
||||
msgstr "Stahovat obrázky při přidávání nebo importování her"
|
||||
|
||||
#: data/gtk/preferences.blp:281
|
||||
msgid "Prefer Over Official Images"
|
||||
msgstr "Upřednostnit před oficiálními obrázky"
|
||||
|
||||
#: data/gtk/preferences.blp:290
|
||||
msgid "Prefer Animated Images"
|
||||
msgstr "Upřednostnit animované obrázky"
|
||||
|
||||
#: data/gtk/window.blp:6 data/gtk/window.blp:14
|
||||
msgid "No Games Found"
|
||||
msgstr "Nebyly nalezeny žádné hry"
|
||||
|
||||
#: data/gtk/window.blp:7 data/gtk/window.blp:15
|
||||
msgid "Try a different search."
|
||||
msgstr "Zkuste hledat něco jiného."
|
||||
|
||||
#: data/gtk/window.blp:21
|
||||
msgid "No Games"
|
||||
msgstr "Žádné hry"
|
||||
|
||||
#: data/gtk/window.blp:22
|
||||
msgid "Use the + button to add games."
|
||||
msgstr "Tlačítkem + můžete přidávat hry."
|
||||
|
||||
#: data/gtk/window.blp:40
|
||||
msgid "No Hidden Games"
|
||||
msgstr "Žádné skryté hry"
|
||||
|
||||
#: data/gtk/window.blp:41
|
||||
msgid "Games you hide will appear here."
|
||||
msgstr "Hry, které skryjete, se zobrazí zde."
|
||||
|
||||
#: data/gtk/window.blp:64 data/gtk/window.blp:304
|
||||
msgid "Back"
|
||||
msgstr "Zpět"
|
||||
|
||||
#: data/gtk/window.blp:121
|
||||
msgid "Game Title"
|
||||
msgstr "Název hry"
|
||||
|
||||
#: data/gtk/window.blp:176
|
||||
msgid "Play"
|
||||
msgstr "Hrát"
|
||||
|
||||
#: data/gtk/window.blp:243 data/gtk/window.blp:435
|
||||
msgid "Add Game"
|
||||
msgstr "Přidat hru"
|
||||
|
||||
#: data/gtk/window.blp:250 data/gtk/window.blp:316
|
||||
msgid "Main Menu"
|
||||
msgstr "Hlavní nabídka"
|
||||
|
||||
#: data/gtk/window.blp:311
|
||||
msgid "Hidden Games"
|
||||
msgstr "Skryté hry"
|
||||
|
||||
#: data/gtk/window.blp:374
|
||||
msgid "Sort"
|
||||
msgstr "Třídit"
|
||||
|
||||
#: data/gtk/window.blp:377
|
||||
msgid "A-Z"
|
||||
msgstr "A-Ž"
|
||||
|
||||
#: data/gtk/window.blp:383
|
||||
msgid "Z-A"
|
||||
msgstr "Ž-A"
|
||||
|
||||
#: data/gtk/window.blp:389
|
||||
msgid "Newest"
|
||||
msgstr "Nejnovější"
|
||||
|
||||
#: data/gtk/window.blp:395
|
||||
msgid "Oldest"
|
||||
msgstr "Nejstarší"
|
||||
|
||||
#: data/gtk/window.blp:401
|
||||
msgid "Last Played"
|
||||
msgstr "Naposledy hráno"
|
||||
|
||||
#: data/gtk/window.blp:408
|
||||
msgid "Show Hidden"
|
||||
msgstr "Zobrazit Skryté"
|
||||
|
||||
#: data/gtk/window.blp:421
|
||||
msgid "Keyboard Shortcuts"
|
||||
msgstr "Klávesové zkratky"
|
||||
|
||||
#: data/gtk/window.blp:426
|
||||
msgid "About Cartridges"
|
||||
msgstr "O Kazetách"
|
||||
|
||||
#. Translators: Replace this with your name for it to show up in the about window
|
||||
#: src/main.py:180
|
||||
msgid "translator_credits"
|
||||
msgstr "ooo.i.love.foo"
|
||||
|
||||
#. The variable is the date when the game was added
|
||||
#: src/window.py:194
|
||||
msgid "Added: {}"
|
||||
msgstr "Přidáno: {}"
|
||||
|
||||
#: src/window.py:197
|
||||
msgid "Never"
|
||||
msgstr "Nikdy"
|
||||
|
||||
#. The variable is the date when the game was last played
|
||||
#: src/window.py:201
|
||||
msgid "Last played: {}"
|
||||
msgstr "Naposledy hráno: {}"
|
||||
|
||||
#: src/details_window.py:72
|
||||
msgid "Apply"
|
||||
msgstr "Použít"
|
||||
|
||||
#: src/details_window.py:78
|
||||
msgid "Add New Game"
|
||||
msgstr "Přidat novou hru"
|
||||
|
||||
#: src/details_window.py:79
|
||||
msgid "Confirm"
|
||||
msgstr "Potvrdit"
|
||||
|
||||
#. Translate this string as you would translate "file"
|
||||
#: src/details_window.py:92
|
||||
msgid "file.txt"
|
||||
msgstr "soubor.txt"
|
||||
|
||||
#. As in software
|
||||
#: src/details_window.py:94
|
||||
msgid "program"
|
||||
msgstr "program"
|
||||
|
||||
#. Translate this string as you would translate "path to {}"
|
||||
#: src/details_window.py:99 src/details_window.py:101
|
||||
msgid "C:\\path\\to\\{}"
|
||||
msgstr "C:\\cesta\\k\\{}"
|
||||
|
||||
#. Translate this string as you would translate "path to {}"
|
||||
#: src/details_window.py:105 src/details_window.py:107
|
||||
msgid "/path/to/{}"
|
||||
msgstr "/cesta/k/{}"
|
||||
|
||||
#: src/details_window.py:112
|
||||
msgid ""
|
||||
"To launch the executable \"{}\", use the command:\n"
|
||||
"\n"
|
||||
"<tt>\"{}\"</tt>\n"
|
||||
"\n"
|
||||
"To open the file \"{}\" with the default application, use:\n"
|
||||
"\n"
|
||||
"<tt>{} \"{}\"</tt>\n"
|
||||
"\n"
|
||||
"If the path contains spaces, make sure to wrap it in double quotes!"
|
||||
msgstr ""
|
||||
"Chcete-li spustit spustitelný soubor \"{}\", použijte příkaz:\n"
|
||||
"\n"
|
||||
"<tt>\"{}\"</tt>\n"
|
||||
"\n"
|
||||
"Chcete-li otevřít soubor \"{}\" pomocí výchozí aplikace, použijte příkaz:\n"
|
||||
"\n"
|
||||
"<tt>{} \"{}\"</tt>\n"
|
||||
"\n"
|
||||
"Pokud cesta obsahuje mezery, nezapomeňte ji zabalit do dvojitých uvozovek!"
|
||||
|
||||
#: src/details_window.py:147 src/details_window.py:153
|
||||
msgid "Couldn't Add Game"
|
||||
msgstr "Nelze přidat hru"
|
||||
|
||||
#: src/details_window.py:147 src/details_window.py:181
|
||||
msgid "Game title cannot be empty."
|
||||
msgstr "Název hry nemůže být prázdný."
|
||||
|
||||
#: src/details_window.py:153 src/details_window.py:189
|
||||
msgid "Executable cannot be empty."
|
||||
msgstr "Spustitelný soubor nemůže být prázdný."
|
||||
|
||||
#: src/details_window.py:180 src/details_window.py:188
|
||||
msgid "Couldn't Apply Preferences"
|
||||
msgstr "Nelze použít předvolby"
|
||||
|
||||
#. The variable is the title of the game
|
||||
#: src/game.py:138
|
||||
msgid "{} launched"
|
||||
msgstr "{} spuštěno"
|
||||
|
||||
#. The variable is the title of the game
|
||||
#: src/game.py:152
|
||||
msgid "{} hidden"
|
||||
msgstr "{} skryto"
|
||||
|
||||
#: src/game.py:152
|
||||
msgid "{} unhidden"
|
||||
msgstr "{} odkryto"
|
||||
|
||||
#: src/game.py:169
|
||||
msgid "{} removed"
|
||||
msgstr "{} odstraněno"
|
||||
|
||||
#: src/preferences.py:111
|
||||
msgid "All games removed"
|
||||
msgstr "Všechny hry odstraněny"
|
||||
|
||||
#: src/preferences.py:159
|
||||
msgid ""
|
||||
"An API key is required to use SteamGridDB. You can generate one {}here{}."
|
||||
msgstr ""
|
||||
"K používání služby SteamGridDB je vyžadován klíč API. Můžete si ho "
|
||||
"vygenerovat {}zde{}."
|
||||
|
||||
#: src/preferences.py:284
|
||||
msgid "Installation Not Found"
|
||||
msgstr "Instalace nebyla nalezena"
|
||||
|
||||
#: src/preferences.py:286
|
||||
msgid "Select a valid directory."
|
||||
msgstr "Vyberte platný adresář."
|
||||
|
||||
#: src/preferences.py:348
|
||||
msgid "Invalid Directory"
|
||||
msgstr "Neplatný adresář"
|
||||
|
||||
#. The variable is the name of the source
|
||||
#: src/preferences.py:352
|
||||
msgid "Select the {} cache directory."
|
||||
msgstr "Vyberte adresář {} mezipaměti."
|
||||
|
||||
#. The variable is the name of the source
|
||||
#: src/preferences.py:355
|
||||
msgid "Select the {} configuration directory."
|
||||
msgstr "Vyberte konfigurační adresář {}."
|
||||
|
||||
#. The variable is the name of the source
|
||||
#: src/preferences.py:358
|
||||
msgid "Select the {} data directory."
|
||||
msgstr "Vyberte datový adresář {}."
|
||||
|
||||
#: src/preferences.py:364
|
||||
msgid "Set Location"
|
||||
msgstr "Nastavit umístění"
|
||||
|
||||
#: src/utils/create_dialog.py:25
|
||||
msgid "Dismiss"
|
||||
msgstr "Zahodit"
|
||||
|
||||
#: src/store/managers/sgdb_manager.py:47
|
||||
msgid "Couldn't Authenticate SteamGridDB"
|
||||
msgstr "Nelze ověřit SteamGridDB"
|
||||
|
||||
#: src/store/managers/sgdb_manager.py:48
|
||||
msgid "Verify your API key in preferences"
|
||||
msgstr "Ověřte váš klíč API v předvolbách"
|
||||
10
po/fr.po
10
po/fr.po
@@ -12,8 +12,8 @@ msgstr ""
|
||||
"Project-Id-Version: cartridges\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-07-05 14:36+0200\n"
|
||||
"PO-Revision-Date: 2023-07-05 13:13+0000\n"
|
||||
"Last-Translator: Geoffrey Coulaud <geoffrey.coulaud+github@gmail.com>\n"
|
||||
"PO-Revision-Date: 2023-07-24 13:05+0000\n"
|
||||
"Last-Translator: rene-coty <irenee.thirion@e.email>\n"
|
||||
"Language-Team: French <https://hosted.weblate.org/projects/cartridges/"
|
||||
"cartridges/fr/>\n"
|
||||
"Language: fr\n"
|
||||
@@ -27,7 +27,7 @@ msgstr ""
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:6 data/gtk/window.blp:47
|
||||
#: src/main.py:162
|
||||
msgid "Cartridges"
|
||||
msgstr "Cartridges"
|
||||
msgstr "Cartouches"
|
||||
|
||||
#: data/hu.kramo.Cartridges.desktop.in:4
|
||||
msgid "Game Launcher"
|
||||
@@ -49,7 +49,7 @@ msgid ""
|
||||
"necessary. You can sort and hide games or download cover art from "
|
||||
"SteamGridDB."
|
||||
msgstr ""
|
||||
"Cartridges est un lanceur de jeux simple pour tous vos jeux. Il prend en "
|
||||
"Cartouches est un lanceur de jeux simple pour tous vos jeux. Il prend en "
|
||||
"charge l’importation des jeux depuis Steam, Lutris, Heroic et d’autres "
|
||||
"encore, sans nécessiter de connexion. Vous pouvez trier et masquer les jeux "
|
||||
"ou télécharger la pochette depuis SteamGridDB."
|
||||
@@ -398,7 +398,7 @@ msgstr "Raccourcis clavier"
|
||||
|
||||
#: data/gtk/window.blp:426
|
||||
msgid "About Cartridges"
|
||||
msgstr "À propos de Cartridges"
|
||||
msgstr "À propos de Cartouches"
|
||||
|
||||
#. Translators: Replace this with your name for it to show up in the about window
|
||||
#: src/main.py:180
|
||||
|
||||
9
po/it.po
9
po/it.po
@@ -4,13 +4,14 @@
|
||||
# Alessandro Iepure <alessandro.iepure@gmail.com>, 2023.
|
||||
# albanobattistella <albano_battistella@hotmail.com>, 2023.
|
||||
# kramo <contact@kramo.hu>, 2023.
|
||||
# Giasko <dibiame@hotmail.it>, 2023.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: cartridges\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-07-05 14:36+0200\n"
|
||||
"PO-Revision-Date: 2023-07-08 14:52+0000\n"
|
||||
"Last-Translator: Alessandro Iepure <alessandro.iepure@gmail.com>\n"
|
||||
"PO-Revision-Date: 2023-07-21 12:16+0000\n"
|
||||
"Last-Translator: Giasko <dibiame@hotmail.it>\n"
|
||||
"Language-Team: Italian <https://hosted.weblate.org/projects/cartridges/"
|
||||
"cartridges/it/>\n"
|
||||
"Language: it\n"
|
||||
@@ -132,7 +133,7 @@ msgstr "Generale"
|
||||
|
||||
#: data/gtk/help-overlay.blp:14
|
||||
msgid "Quit"
|
||||
msgstr "Chiudi"
|
||||
msgstr "Esci"
|
||||
|
||||
#: data/gtk/help-overlay.blp:19 data/gtk/window.blp:217 data/gtk/window.blp:257
|
||||
#: data/gtk/window.blp:323
|
||||
@@ -273,7 +274,7 @@ msgstr "itch"
|
||||
|
||||
#: data/gtk/preferences.blp:216
|
||||
msgid "Legendary"
|
||||
msgstr "Leggendario"
|
||||
msgstr "Legendary"
|
||||
|
||||
#: data/gtk/preferences.blp:230
|
||||
msgid "Flatpak"
|
||||
|
||||
35
po/pl.po
35
po/pl.po
@@ -4,13 +4,14 @@
|
||||
# Artur Wróblewski <krypalkora1984@gmail.com>, 2023.
|
||||
# Kshyso <Kshysio@protonmail.com>, 2023.
|
||||
# Eryk Michalak <gnu.ewm@protonmail.com>, 2023.
|
||||
# Michaks <fexwex3@gmail.com>, 2023.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Cartridges\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-07-05 14:36+0200\n"
|
||||
"PO-Revision-Date: 2023-07-14 15:51+0000\n"
|
||||
"Last-Translator: Eryk Michalak <gnu.ewm@protonmail.com>\n"
|
||||
"PO-Revision-Date: 2023-07-24 13:05+0000\n"
|
||||
"Last-Translator: Michaks <fexwex3@gmail.com>\n"
|
||||
"Language-Team: Polish <https://hosted.weblate.org/projects/cartridges/"
|
||||
"cartridges/pl/>\n"
|
||||
"Language: pl\n"
|
||||
@@ -25,7 +26,7 @@ msgstr ""
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:6 data/gtk/window.blp:47
|
||||
#: src/main.py:162
|
||||
msgid "Cartridges"
|
||||
msgstr "Cartridges"
|
||||
msgstr "Kartridże"
|
||||
|
||||
#: data/hu.kramo.Cartridges.desktop.in:4
|
||||
msgid "Game Launcher"
|
||||
@@ -57,16 +58,16 @@ msgstr "Biblioteka"
|
||||
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:34 src/details_window.py:67
|
||||
msgid "Edit Game Details"
|
||||
msgstr "Edytuj detale gry"
|
||||
msgstr "Edycja szczegółów gry"
|
||||
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:38 data/gtk/window.blp:71
|
||||
msgid "Game Details"
|
||||
msgstr "Detale gry"
|
||||
msgstr "Szczegóły gry"
|
||||
|
||||
#: data/hu.kramo.Cartridges.metainfo.xml.in:42 data/gtk/window.blp:416
|
||||
#: src/details_window.py:239
|
||||
msgid "Preferences"
|
||||
msgstr "Ustawienia"
|
||||
msgstr "Preferencje"
|
||||
|
||||
#: data/gtk/details-window.blp:25
|
||||
msgid "Cancel"
|
||||
@@ -74,11 +75,11 @@ msgstr "Anuluj"
|
||||
|
||||
#: data/gtk/details-window.blp:57
|
||||
msgid "New Cover"
|
||||
msgstr "Nowa Okładka"
|
||||
msgstr "Nowa okładka"
|
||||
|
||||
#: data/gtk/details-window.blp:75
|
||||
msgid "Delete Cover"
|
||||
msgstr "Usuń Okładkę"
|
||||
msgstr "Usuń osłonę"
|
||||
|
||||
#: data/gtk/details-window.blp:101 data/gtk/details-window.blp:106
|
||||
#: data/gtk/game.blp:80
|
||||
@@ -87,7 +88,7 @@ msgstr "Tytuł"
|
||||
|
||||
#: data/gtk/details-window.blp:102
|
||||
msgid "The title of the game"
|
||||
msgstr "Tytuł gry"
|
||||
msgstr "Tytuł Gry"
|
||||
|
||||
#: data/gtk/details-window.blp:112 data/gtk/details-window.blp:117
|
||||
msgid "Developer"
|
||||
@@ -150,7 +151,7 @@ msgstr "Skróty"
|
||||
|
||||
#: data/gtk/help-overlay.blp:34 src/game.py:102 src/preferences.py:112
|
||||
msgid "Undo"
|
||||
msgstr "Cofnij"
|
||||
msgstr "Wróć"
|
||||
|
||||
#: data/gtk/help-overlay.blp:39
|
||||
msgid "Open menu"
|
||||
@@ -162,7 +163,7 @@ msgstr "Gry"
|
||||
|
||||
#: data/gtk/help-overlay.blp:48
|
||||
msgid "Add new game"
|
||||
msgstr "Dodaj nową grę"
|
||||
msgstr "Dodaj nową gre"
|
||||
|
||||
#: data/gtk/help-overlay.blp:53
|
||||
msgid "Import games"
|
||||
@@ -186,7 +187,7 @@ msgstr "Wyjdź po uruchomieniu gry"
|
||||
|
||||
#: data/gtk/preferences.blp:25
|
||||
msgid "Cover Image Launches Game"
|
||||
msgstr "Obraz okładki startera gier"
|
||||
msgstr "Obraz okładki uruchamia grę"
|
||||
|
||||
#: data/gtk/preferences.blp:26
|
||||
msgid "Swaps the behavior of the cover image and the play button"
|
||||
@@ -337,7 +338,7 @@ msgstr "Gry, które ukryjesz, pojawią się tutaj."
|
||||
|
||||
#: data/gtk/window.blp:64 data/gtk/window.blp:304
|
||||
msgid "Back"
|
||||
msgstr "Cofnij"
|
||||
msgstr "Powrót"
|
||||
|
||||
#: data/gtk/window.blp:121
|
||||
msgid "Game Title"
|
||||
@@ -345,7 +346,7 @@ msgstr "Tytuł gry"
|
||||
|
||||
#: data/gtk/window.blp:176
|
||||
msgid "Play"
|
||||
msgstr "Uruchom"
|
||||
msgstr "Graj"
|
||||
|
||||
#: data/gtk/window.blp:243 data/gtk/window.blp:435
|
||||
msgid "Add Game"
|
||||
@@ -393,7 +394,7 @@ msgstr "Skróty klawiaturowe"
|
||||
|
||||
#: data/gtk/window.blp:426
|
||||
msgid "About Cartridges"
|
||||
msgstr "O Cartridges"
|
||||
msgstr "O Kartridżach"
|
||||
|
||||
#. Translators: Replace this with your name for it to show up in the about window
|
||||
#: src/main.py:180
|
||||
@@ -420,7 +421,7 @@ msgstr "Zastosuj"
|
||||
|
||||
#: src/details_window.py:78
|
||||
msgid "Add New Game"
|
||||
msgstr "Dodaj nową grę"
|
||||
msgstr "Dodaj nową Grę"
|
||||
|
||||
#: src/details_window.py:79
|
||||
msgid "Confirm"
|
||||
@@ -542,7 +543,7 @@ msgstr "Wybierz katalog z danymi {}."
|
||||
|
||||
#: src/preferences.py:364
|
||||
msgid "Set Location"
|
||||
msgstr "Ustaw lokacje"
|
||||
msgstr "Ustaw położenie"
|
||||
|
||||
#: src/utils/create_dialog.py:25
|
||||
msgid "Dismiss"
|
||||
|
||||
@@ -26,17 +26,13 @@ import yaml
|
||||
from src import shared
|
||||
from src.game import Game
|
||||
from src.importer.sources.location import Location
|
||||
from src.importer.sources.source import (
|
||||
SourceIterationResult,
|
||||
SourceIterator,
|
||||
URLExecutableSource,
|
||||
)
|
||||
from src.importer.sources.source import SourceIterable, URLExecutableSource
|
||||
|
||||
|
||||
class BottlesSourceIterator(SourceIterator):
|
||||
class BottlesSourceIterable(SourceIterable):
|
||||
source: "BottlesSource"
|
||||
|
||||
def generator_builder(self) -> SourceIterationResult:
|
||||
def __iter__(self):
|
||||
"""Generator method producing games"""
|
||||
|
||||
data = self.source.data_location["library.yml"].read_text("utf-8")
|
||||
@@ -84,7 +80,7 @@ class BottlesSource(URLExecutableSource):
|
||||
"""Generic Bottles source"""
|
||||
|
||||
name = _("Bottles")
|
||||
iterator_class = BottlesSourceIterator
|
||||
iterable_class = BottlesSourceIterable
|
||||
url_format = 'bottles:run/"{bottle_name}"/"{game_name}"'
|
||||
available_on = {"linux"}
|
||||
|
||||
|
||||
@@ -25,13 +25,13 @@ from gi.repository import GLib, Gtk
|
||||
from src import shared
|
||||
from src.game import Game
|
||||
from src.importer.sources.location import Location
|
||||
from src.importer.sources.source import Source, SourceIterationResult, SourceIterator
|
||||
from src.importer.sources.source import Source, SourceIterable
|
||||
|
||||
|
||||
class FlatpakSourceIterator(SourceIterator):
|
||||
class FlatpakSourceIterable(SourceIterable):
|
||||
source: "FlatpakSource"
|
||||
|
||||
def generator_builder(self) -> SourceIterationResult:
|
||||
def __iter__(self):
|
||||
"""Generator method producing games"""
|
||||
|
||||
added_time = int(time())
|
||||
@@ -115,7 +115,7 @@ class FlatpakSource(Source):
|
||||
"""Generic Flatpak source"""
|
||||
|
||||
name = _("Flatpak")
|
||||
iterator_class = FlatpakSourceIterator
|
||||
iterable_class = FlatpakSourceIterable
|
||||
executable_format = "flatpak run {flatpak_id}"
|
||||
available_on = {"linux"}
|
||||
|
||||
|
||||
@@ -20,21 +20,47 @@
|
||||
|
||||
import json
|
||||
import logging
|
||||
from abc import abstractmethod
|
||||
from hashlib import sha256
|
||||
from json import JSONDecodeError
|
||||
from pathlib import Path
|
||||
from time import time
|
||||
from typing import Optional, TypedDict
|
||||
from typing import Iterable, Optional, TypedDict
|
||||
from functools import cached_property
|
||||
|
||||
from src import shared
|
||||
from src.game import Game
|
||||
from src.importer.sources.location import Location
|
||||
from src.importer.sources.source import (
|
||||
URLExecutableSource,
|
||||
SourceIterable,
|
||||
SourceIterationResult,
|
||||
SourceIterator,
|
||||
URLExecutableSource,
|
||||
)
|
||||
|
||||
|
||||
def path_json_load(path: Path):
|
||||
"""
|
||||
Load JSON from the file at the given path
|
||||
|
||||
:raises OSError: if the file can't be opened
|
||||
:raises JSONDecodeError: if the file isn't valid JSON
|
||||
"""
|
||||
with path.open("r", encoding="utf-8") as open_file:
|
||||
return json.load(open_file)
|
||||
|
||||
|
||||
class InvalidLibraryFileError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidInstalledFileError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidStoreFileError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HeroicLibraryEntry(TypedDict):
|
||||
app_name: str
|
||||
installed: Optional[bool]
|
||||
@@ -44,115 +70,304 @@ class HeroicLibraryEntry(TypedDict):
|
||||
art_square: str
|
||||
|
||||
|
||||
class HeroicSubSource(TypedDict):
|
||||
service: str
|
||||
path: tuple[str]
|
||||
class SubSourceIterable(Iterable):
|
||||
"""Class representing a Heroic sub-source"""
|
||||
|
||||
|
||||
class HeroicSourceIterator(SourceIterator):
|
||||
source: "HeroicSource"
|
||||
source_iterable: "HeroicSourceIterable"
|
||||
name: str
|
||||
service: str
|
||||
image_uri_params: str = ""
|
||||
relative_library_path: Path
|
||||
library_json_entries_key: str = "library"
|
||||
|
||||
sub_sources: dict[str, HeroicSubSource] = {
|
||||
"sideload": {
|
||||
"service": "sideload",
|
||||
"path": ("sideload_apps", "library.json"),
|
||||
},
|
||||
"legendary": {
|
||||
"service": "epic",
|
||||
"path": ("store_cache", "legendary_library.json"),
|
||||
},
|
||||
"gog": {
|
||||
"service": "gog",
|
||||
"path": ("store_cache", "gog_library.json"),
|
||||
},
|
||||
}
|
||||
def __init__(self, source, source_iterable) -> None:
|
||||
self.source = source
|
||||
self.source_iterable = source_iterable
|
||||
|
||||
def game_from_library_entry(
|
||||
@cached_property
|
||||
def library_path(self) -> Path:
|
||||
path = self.source.config_location.root / self.relative_library_path
|
||||
logging.debug("Using Heroic %s library.json path %s", self.name, path)
|
||||
return path
|
||||
|
||||
def process_library_entry(
|
||||
self, entry: HeroicLibraryEntry, added_time: int
|
||||
) -> SourceIterationResult:
|
||||
"""Helper method used to build a Game from a Heroic library entry"""
|
||||
"""Build a Game from a Heroic library entry"""
|
||||
|
||||
# Skip games that are not installed
|
||||
if not entry["is_installed"]:
|
||||
return None
|
||||
app_name = entry["app_name"]
|
||||
|
||||
# Build game
|
||||
app_name = entry["app_name"]
|
||||
runner = entry["runner"]
|
||||
service = self.sub_sources[runner]["service"]
|
||||
values = {
|
||||
"source": f"{self.source.id}_{service}",
|
||||
"source": f"{self.source.id}_{self.service}",
|
||||
"added": added_time,
|
||||
"name": entry["title"],
|
||||
"developer": entry.get("developer", None),
|
||||
"game_id": self.source.game_id_format.format(
|
||||
service=service, game_id=app_name
|
||||
service=self.service, game_id=app_name
|
||||
),
|
||||
"executable": self.source.executable_format.format(app_name=app_name),
|
||||
"hidden": self.source_iterable.is_hidden(app_name),
|
||||
}
|
||||
game = Game(values)
|
||||
|
||||
# Get the image path from the heroic cache
|
||||
# Filenames are derived from the URL that heroic used to get the file
|
||||
uri: str = entry["art_square"]
|
||||
if service == "epic":
|
||||
uri += "?h=400&resize=1&w=300"
|
||||
uri: str = entry["art_square"] + self.image_uri_params
|
||||
digest = sha256(uri.encode()).hexdigest()
|
||||
image_path = self.source.config_location.root / "images-cache" / digest
|
||||
additional_data = {"local_image_path": image_path}
|
||||
|
||||
return (game, additional_data)
|
||||
|
||||
def generator_builder(self) -> SourceIterationResult:
|
||||
"""Generator method producing games from all the Heroic sub-sources"""
|
||||
|
||||
for sub_source_name, sub_source in self.sub_sources.items():
|
||||
# Skip disabled sub-sources
|
||||
if not shared.schema.get_boolean("heroic-import-" + sub_source["service"]):
|
||||
continue
|
||||
# Load games from JSON
|
||||
file = self.source.config_location.root.joinpath(*sub_source["path"])
|
||||
try:
|
||||
contents = json.load(file.open())
|
||||
key = "library" if sub_source_name == "legendary" else "games"
|
||||
library = contents[key]
|
||||
except (JSONDecodeError, OSError, KeyError):
|
||||
# Invalid library.json file, skip it
|
||||
logging.warning("Couldn't open Heroic file: %s", str(file))
|
||||
continue
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Iterate through the games with a generator
|
||||
:raises InvalidLibraryFileError: on initial call if the library file is bad
|
||||
"""
|
||||
added_time = int(time())
|
||||
|
||||
for entry in library:
|
||||
try:
|
||||
result = self.game_from_library_entry(entry, added_time)
|
||||
iterator = iter(
|
||||
path_json_load(self.library_path)[self.library_json_entries_key]
|
||||
)
|
||||
except (OSError, JSONDecodeError, TypeError, KeyError) as error:
|
||||
raise InvalidLibraryFileError(
|
||||
f"Invalid {self.library_path.name}"
|
||||
) from error
|
||||
for entry in iterator:
|
||||
try:
|
||||
yield self.process_library_entry(entry, added_time)
|
||||
except KeyError as error:
|
||||
# Skip invalid games
|
||||
logging.warning(
|
||||
"Invalid Heroic game skipped in %s", str(file), exc_info=error
|
||||
"Skipped invalid %s game %s",
|
||||
self.name,
|
||||
entry.get("app_name", "UNKNOWN"),
|
||||
exc_info=error,
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
class StoreSubSourceIterable(SubSourceIterable):
|
||||
"""
|
||||
Class representing a "store" sub source.
|
||||
Games can be installed or not, this class does the check accordingly.
|
||||
"""
|
||||
|
||||
relative_installed_path: Path
|
||||
installed_app_names: set[str]
|
||||
|
||||
@cached_property
|
||||
def installed_path(self) -> Path:
|
||||
path = self.source.config_location.root / self.relative_installed_path
|
||||
logging.debug("Using Heroic %s installed.json path %s", self.name, path)
|
||||
return path
|
||||
|
||||
@abstractmethod
|
||||
def get_installed_app_names(self) -> set[str]:
|
||||
"""
|
||||
Get the sub source's installed app names as a set.
|
||||
|
||||
:raises InvalidInstalledFileError: if the installed file data cannot be read
|
||||
Whenever possible, `__cause__` is set with the original exception
|
||||
"""
|
||||
|
||||
def is_installed(self, app_name: str) -> bool:
|
||||
return app_name in self.installed_app_names
|
||||
|
||||
def process_library_entry(self, entry, added_time):
|
||||
# Skip games that are not installed
|
||||
app_name = entry["app_name"]
|
||||
if not self.is_installed(app_name):
|
||||
logging.warning(
|
||||
"Skipped %s game %s (%s): not installed",
|
||||
self.service,
|
||||
entry["title"],
|
||||
app_name,
|
||||
)
|
||||
return None
|
||||
# Process entry as normal
|
||||
return super().process_library_entry(entry, added_time)
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Iterate through the installed games with a generator
|
||||
:raises InvalidLibraryFileError: on initial call if the library file is bad
|
||||
:raises InvalidInstalledFileError: on initial call if the installed file is bad
|
||||
"""
|
||||
self.installed_app_names = self.get_installed_app_names()
|
||||
yield from super().__iter__()
|
||||
|
||||
|
||||
class SideloadIterable(SubSourceIterable):
|
||||
name = "sideload"
|
||||
service = "sideload"
|
||||
relative_library_path = Path("sideload_apps") / "library.json"
|
||||
library_json_entries_key = "games"
|
||||
|
||||
|
||||
class LegendaryIterable(StoreSubSourceIterable):
|
||||
name = "legendary"
|
||||
service = "epic"
|
||||
image_uri_params = "?h=400&resize=1&w=300"
|
||||
relative_library_path = Path("store_cache") / "legendary_library.json"
|
||||
|
||||
# relative_installed_path = (
|
||||
# Path("legendary") / "legendaryConfig" / "legendary" / "installed.json"
|
||||
# )
|
||||
|
||||
@cached_property
|
||||
def installed_path(self) -> Path:
|
||||
"""
|
||||
Get the right path depending on the Heroic version
|
||||
|
||||
TODO after heroic 2.9 has been out for a while
|
||||
We should use the commented out relative_installed_path
|
||||
and remove this property override.
|
||||
"""
|
||||
|
||||
heroic_config_path = self.source.config_location.root
|
||||
# Heroic >= 2.9
|
||||
if (path := heroic_config_path / "legendaryConfig").is_dir():
|
||||
logging.debug("Using Heroic >= 2.9 legendary file")
|
||||
# Heroic <= 2.8
|
||||
elif heroic_config_path.is_relative_to(shared.flatpak_dir):
|
||||
# Heroic flatpak
|
||||
path = shared.flatpak_dir / "com.heroicgameslauncher.hgl" / "config"
|
||||
logging.debug("Using Heroic flatpak <= 2.8 legendary file")
|
||||
else:
|
||||
# Heroic native
|
||||
logging.debug("Using Heroic native <= 2.8 legendary file")
|
||||
path = Path.home() / ".config"
|
||||
|
||||
path = path / "legendary" / "installed.json"
|
||||
logging.debug("Using Heroic %s installed.json path %s", self.name, path)
|
||||
return path
|
||||
|
||||
def get_installed_app_names(self):
|
||||
try:
|
||||
return set(path_json_load(self.installed_path).keys())
|
||||
except (OSError, JSONDecodeError, AttributeError) as error:
|
||||
raise InvalidInstalledFileError(
|
||||
f"Invalid {self.installed_path.name}"
|
||||
) from error
|
||||
|
||||
|
||||
class GogIterable(StoreSubSourceIterable):
|
||||
name = "gog"
|
||||
service = "gog"
|
||||
library_json_entries_key = "games"
|
||||
relative_library_path = Path("store_cache") / "gog_library.json"
|
||||
relative_installed_path = Path("gog_store") / "installed.json"
|
||||
|
||||
def get_installed_app_names(self):
|
||||
try:
|
||||
return {
|
||||
app_name
|
||||
for entry in path_json_load(self.installed_path)["installed"]
|
||||
if (app_name := entry.get("appName")) is not None
|
||||
}
|
||||
except (OSError, JSONDecodeError, KeyError, AttributeError) as error:
|
||||
raise InvalidInstalledFileError(
|
||||
f"Invalid {self.installed_path.name}"
|
||||
) from error
|
||||
|
||||
|
||||
class NileIterable(StoreSubSourceIterable):
|
||||
name = "nile"
|
||||
service = "amazon"
|
||||
relative_library_path = Path("store_cache") / "nile_library.json"
|
||||
relative_installed_path = Path("nile_config") / "nile" / "installed.json"
|
||||
|
||||
def get_installed_app_names(self):
|
||||
try:
|
||||
installed_json = path_json_load(self.installed_path)
|
||||
return {
|
||||
app_name
|
||||
for entry in installed_json
|
||||
if (app_name := entry.get("id")) is not None
|
||||
}
|
||||
except (OSError, JSONDecodeError, AttributeError) as error:
|
||||
raise InvalidInstalledFileError(
|
||||
f"Invalid {self.installed_path.name}"
|
||||
) from error
|
||||
|
||||
|
||||
class HeroicSourceIterable(SourceIterable):
|
||||
source: "HeroicSource"
|
||||
|
||||
hidden_app_names: set[str] = set()
|
||||
|
||||
def is_hidden(self, app_name: str) -> bool:
|
||||
return app_name in self.hidden_app_names
|
||||
|
||||
def get_hidden_app_names(self) -> set[str]:
|
||||
"""Get the hidden app names from store/config.json
|
||||
|
||||
:raises InvalidStoreFileError: if the store is invalid for some reason
|
||||
"""
|
||||
|
||||
try:
|
||||
store = path_json_load(self.source.config_location["store_config.json"])
|
||||
self.hidden_app_names = {
|
||||
app_name
|
||||
for game in store["games"]["hidden"]
|
||||
if (app_name := game.get("appName")) is not None
|
||||
}
|
||||
except KeyError:
|
||||
logging.warning('No ["games"]["hidden"] key in Heroic store file')
|
||||
except (OSError, JSONDecodeError, TypeError) as error:
|
||||
logging.error("Invalid Heroic store file", exc_info=error)
|
||||
raise InvalidStoreFileError() from error
|
||||
|
||||
def __iter__(self):
|
||||
"""Generator method producing games from all the Heroic sub-sources"""
|
||||
|
||||
self.get_hidden_app_names()
|
||||
|
||||
# Get games from the sub sources
|
||||
for sub_source_class in (
|
||||
SideloadIterable,
|
||||
LegendaryIterable,
|
||||
GogIterable,
|
||||
NileIterable,
|
||||
):
|
||||
sub_source = sub_source_class(self.source, self)
|
||||
|
||||
if not shared.schema.get_boolean("heroic-import-" + sub_source.service):
|
||||
logging.debug("Skipping Heroic %s: disabled", sub_source.service)
|
||||
continue
|
||||
try:
|
||||
sub_source_iterable = iter(sub_source)
|
||||
yield from sub_source_iterable
|
||||
except (InvalidLibraryFileError, InvalidInstalledFileError) as error:
|
||||
logging.error(
|
||||
"Skipping bad Heroic sub-source %s",
|
||||
sub_source.service,
|
||||
exc_info=error,
|
||||
)
|
||||
continue
|
||||
yield result
|
||||
|
||||
|
||||
class HeroicSource(URLExecutableSource):
|
||||
"""Generic Heroic Games Launcher source"""
|
||||
|
||||
name = _("Heroic")
|
||||
iterator_class = HeroicSourceIterator
|
||||
iterable_class = HeroicSourceIterable
|
||||
url_format = "heroic://launch/{app_name}"
|
||||
available_on = {"linux", "win32"}
|
||||
|
||||
config_location = Location(
|
||||
schema_key="heroic-location",
|
||||
candidates=(
|
||||
shared.flatpak_dir / "com.heroicgameslauncher.hgl" / "config" / "heroic",
|
||||
shared.config_dir / "heroic",
|
||||
shared.home / ".config" / "heroic",
|
||||
shared.flatpak_dir / "com.heroicgameslauncher.hgl" / "config" / "heroic",
|
||||
shared.appdata_dir / "heroic",
|
||||
),
|
||||
paths={
|
||||
"config.json": (False, "config.json"),
|
||||
"store_config.json": (False, Path("store") / "config.json"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -25,18 +25,14 @@ from time import time
|
||||
from src import shared
|
||||
from src.game import Game
|
||||
from src.importer.sources.location import Location
|
||||
from src.importer.sources.source import (
|
||||
SourceIterationResult,
|
||||
SourceIterator,
|
||||
URLExecutableSource,
|
||||
)
|
||||
from src.importer.sources.source import SourceIterable, URLExecutableSource
|
||||
from src.utils.sqlite import copy_db
|
||||
|
||||
|
||||
class ItchSourceIterator(SourceIterator):
|
||||
class ItchSourceIterable(SourceIterable):
|
||||
source: "ItchSource"
|
||||
|
||||
def generator_builder(self) -> SourceIterationResult:
|
||||
def __iter__(self):
|
||||
"""Generator method producing games"""
|
||||
|
||||
# Query the database
|
||||
@@ -80,7 +76,7 @@ class ItchSourceIterator(SourceIterator):
|
||||
|
||||
class ItchSource(URLExecutableSource):
|
||||
name = _("itch")
|
||||
iterator_class = ItchSourceIterator
|
||||
iterable_class = ItchSourceIterable
|
||||
url_format = "itch://caves/{cave_id}/launch"
|
||||
available_on = {"linux", "win32"}
|
||||
|
||||
|
||||
@@ -21,15 +21,14 @@ import json
|
||||
import logging
|
||||
from json import JSONDecodeError
|
||||
from time import time
|
||||
from typing import Generator
|
||||
|
||||
from src import shared
|
||||
from src.game import Game
|
||||
from src.importer.sources.location import Location
|
||||
from src.importer.sources.source import Source, SourceIterationResult, SourceIterator
|
||||
from src.importer.sources.source import Source, SourceIterationResult, SourceIterable
|
||||
|
||||
|
||||
class LegendarySourceIterator(SourceIterator):
|
||||
class LegendarySourceIterable(SourceIterable):
|
||||
source: "LegendarySource"
|
||||
|
||||
def game_from_library_entry(
|
||||
@@ -65,7 +64,7 @@ class LegendarySourceIterator(SourceIterator):
|
||||
game = Game(values)
|
||||
return (game, data)
|
||||
|
||||
def generator_builder(self) -> Generator[SourceIterationResult, None, None]:
|
||||
def __iter__(self):
|
||||
# Open library
|
||||
file = self.source.config_location["installed.json"]
|
||||
try:
|
||||
@@ -94,7 +93,7 @@ class LegendarySource(Source):
|
||||
executable_format = "legendary launch {app_name}"
|
||||
available_on = {"linux"}
|
||||
|
||||
iterator_class = LegendarySourceIterator
|
||||
iterable_class = LegendarySourceIterable
|
||||
config_location: Location = Location(
|
||||
schema_key="legendary-location",
|
||||
candidates=(
|
||||
|
||||
@@ -24,18 +24,14 @@ from time import time
|
||||
from src import shared
|
||||
from src.game import Game
|
||||
from src.importer.sources.location import Location
|
||||
from src.importer.sources.source import (
|
||||
SourceIterationResult,
|
||||
SourceIterator,
|
||||
URLExecutableSource,
|
||||
)
|
||||
from src.importer.sources.source import SourceIterable, URLExecutableSource
|
||||
from src.utils.sqlite import copy_db
|
||||
|
||||
|
||||
class LutrisSourceIterator(SourceIterator):
|
||||
class LutrisSourceIterable(SourceIterable):
|
||||
source: "LutrisSource"
|
||||
|
||||
def generator_builder(self) -> SourceIterationResult:
|
||||
def __iter__(self):
|
||||
"""Generator method producing games"""
|
||||
|
||||
# Query the database
|
||||
@@ -91,7 +87,7 @@ class LutrisSource(URLExecutableSource):
|
||||
"""Generic Lutris source"""
|
||||
|
||||
name = _("Lutris")
|
||||
iterator_class = LutrisSourceIterator
|
||||
iterable_class = LutrisSourceIterable
|
||||
url_format = "lutris:rungameid/{game_id}"
|
||||
available_on = {"linux"}
|
||||
|
||||
|
||||
@@ -29,25 +29,16 @@ from src.importer.sources.location import Location
|
||||
SourceIterationResult = None | Game | tuple[Game, tuple[Any]]
|
||||
|
||||
|
||||
class SourceIterator(Iterator):
|
||||
class SourceIterable(Iterable):
|
||||
"""Data producer for a source of games"""
|
||||
|
||||
source: "Source" = None
|
||||
generator: Generator = None
|
||||
|
||||
def __init__(self, source: "Source") -> None:
|
||||
super().__init__()
|
||||
self.source = source
|
||||
self.generator = self.generator_builder()
|
||||
|
||||
def __iter__(self) -> "SourceIterator":
|
||||
return self
|
||||
|
||||
def __next__(self) -> SourceIterationResult:
|
||||
return next(self.generator)
|
||||
|
||||
@abstractmethod
|
||||
def generator_builder(self) -> Generator[SourceIterationResult, None, None]:
|
||||
def __iter__(self) -> Generator[SourceIterationResult, None, None]:
|
||||
"""
|
||||
Method that returns a generator that produces games
|
||||
* Should be implemented as a generator method
|
||||
@@ -66,7 +57,7 @@ class Source(Iterable):
|
||||
data_location: Optional[Location] = None
|
||||
cache_location: Optional[Location] = None
|
||||
config_location: Optional[Location] = None
|
||||
iterator_class: type[SourceIterator]
|
||||
iterable_class: type[SourceIterable]
|
||||
|
||||
@property
|
||||
def full_name(self) -> str:
|
||||
@@ -98,7 +89,7 @@ class Source(Iterable):
|
||||
def executable_format(self) -> str:
|
||||
"""The executable format used to construct game executables"""
|
||||
|
||||
def __iter__(self) -> SourceIterator:
|
||||
def __iter__(self) -> Generator[SourceIterationResult, None, None]:
|
||||
"""
|
||||
Get an iterator for the source
|
||||
:raises UnresolvableLocationError: Not iterable if any of the locations are unresolvable
|
||||
@@ -108,7 +99,7 @@ class Source(Iterable):
|
||||
if location is None:
|
||||
continue
|
||||
location.resolve()
|
||||
return self.iterator_class(self)
|
||||
return iter(self.iterable_class(self))
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
from time import time
|
||||
@@ -25,16 +26,12 @@ from typing import Iterable
|
||||
|
||||
from src import shared
|
||||
from src.game import Game
|
||||
from src.importer.sources.source import (
|
||||
SourceIterationResult,
|
||||
SourceIterator,
|
||||
URLExecutableSource,
|
||||
)
|
||||
from src.utils.steam import SteamFileHelper, SteamInvalidManifestError
|
||||
from src.importer.sources.location import Location
|
||||
from src.importer.sources.source import SourceIterable, URLExecutableSource
|
||||
from src.utils.steam import SteamFileHelper, SteamInvalidManifestError
|
||||
|
||||
|
||||
class SteamSourceIterator(SourceIterator):
|
||||
class SteamSourceIterable(SourceIterable):
|
||||
source: "SteamSource"
|
||||
|
||||
def get_manifest_dirs(self) -> Iterable[Path]:
|
||||
@@ -62,7 +59,7 @@ class SteamSourceIterator(SourceIterator):
|
||||
)
|
||||
return manifests
|
||||
|
||||
def generator_builder(self) -> SourceIterationResult:
|
||||
def __iter__(self):
|
||||
"""Generator method producing games"""
|
||||
appid_cache = set()
|
||||
manifests = self.get_manifests()
|
||||
@@ -74,17 +71,20 @@ class SteamSourceIterator(SourceIterator):
|
||||
steam = SteamFileHelper()
|
||||
try:
|
||||
local_data = steam.get_manifest_data(manifest)
|
||||
except (OSError, SteamInvalidManifestError):
|
||||
except (OSError, SteamInvalidManifestError) as error:
|
||||
logging.debug("Couldn't load appmanifest %s", manifest, exc_info=error)
|
||||
continue
|
||||
|
||||
# Skip non installed games
|
||||
installed_mask = 4
|
||||
if not int(local_data["stateflags"]) & installed_mask:
|
||||
logging.debug("Skipped %s: not installed", manifest)
|
||||
continue
|
||||
|
||||
# Skip duplicate appids
|
||||
appid = local_data["appid"]
|
||||
if appid in appid_cache:
|
||||
logging.debug("Skipped %s: appid already seen during import", manifest)
|
||||
continue
|
||||
appid_cache.add(appid)
|
||||
|
||||
@@ -112,7 +112,7 @@ class SteamSourceIterator(SourceIterator):
|
||||
class SteamSource(URLExecutableSource):
|
||||
name = _("Steam")
|
||||
available_on = {"linux", "win32"}
|
||||
iterator_class = SteamSourceIterator
|
||||
iterable_class = SteamSourceIterable
|
||||
url_format = "steam://rungameid/{game_id}"
|
||||
|
||||
data_location = Location(
|
||||
|
||||
@@ -73,7 +73,7 @@ def setup_logging():
|
||||
"PIL": {
|
||||
"handlers": ["lib_console_handler", "file_handler"],
|
||||
"propagate": False,
|
||||
"level": "NOTSET",
|
||||
"level": "WARNING",
|
||||
},
|
||||
"urllib3": {
|
||||
"handlers": ["lib_console_handler", "file_handler"],
|
||||
|
||||
@@ -68,6 +68,7 @@ class PreferencesWindow(Adw.PreferencesWindow):
|
||||
heroic_config_file_chooser_button = Gtk.Template.Child()
|
||||
heroic_import_epic_switch = Gtk.Template.Child()
|
||||
heroic_import_gog_switch = Gtk.Template.Child()
|
||||
heroic_import_amazon_switch = Gtk.Template.Child()
|
||||
heroic_import_sideload_switch = Gtk.Template.Child()
|
||||
|
||||
bottles_expander_row = Gtk.Template.Child()
|
||||
@@ -181,6 +182,7 @@ class PreferencesWindow(Adw.PreferencesWindow):
|
||||
"lutris-import-flatpak",
|
||||
"heroic-import-epic",
|
||||
"heroic-import-gog",
|
||||
"heroic-import-amazon",
|
||||
"heroic-import-sideload",
|
||||
"flatpak-import-launchers",
|
||||
"sgdb",
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import logging
|
||||
|
||||
from gi.repository import GdkPixbuf
|
||||
|
||||
from src import shared
|
||||
@@ -35,6 +37,7 @@ class LocalCoverManager(Manager):
|
||||
def manager_logic(self, game: Game, additional_data: dict) -> None:
|
||||
if image_path := additional_data.get("local_image_path"):
|
||||
if not image_path.is_file():
|
||||
logging.error("Local image path is not a file: %s", image_path)
|
||||
return
|
||||
save_cover(game.game_id, resize_cover(image_path))
|
||||
elif icon_path := additional_data.get("local_icon_path"):
|
||||
|
||||
Reference in New Issue
Block a user