Compare commits

...

40 Commits

Author SHA1 Message Date
GeoffreyCoulaud
927275cda9 🚧 Prototype import undo v2
bad:
- Not optimised at all
- copies a lot of unnecessary things
- not reliable

good:
- works, a little

---

For it to work more than "a little", we need to fix that bug:
- Import
- Remove a game
- Import (game comes back)
- Undo Import (game gone)
- Import
- Undo (everything gone)
2023-07-03 17:48:32 +02:00
GeoffreyCoulaud
df9f7dbca8 Revert "🚧 Very unfinished initial work on import undo"
This reverts commit d252b6b97d.
2023-07-03 14:00:23 +02:00
GeoffreyCoulaud
6c6fd29faa Merge branch 'main' into add-import-undo 2023-07-03 13:58:22 +02:00
kramo
e320e58ffc Fix Appstream ID 2023-07-03 12:43:33 +02:00
kramo
c9a1104b44 Revert test 2023-07-02 14:46:54 +02:00
kramo
ee5740c21c Test Actions 2023-07-02 14:46:37 +02:00
kramo
1498326f45 Exit differently 2023-07-02 13:03:34 +02:00
kramo
69fc7e1b03 Remove test module 2023-07-02 12:55:19 +02:00
kramo
8a3397bef5 This should work 2023-07-02 12:54:49 +02:00
kramo
d87048ee64 Test worked 2023-07-02 12:26:56 +02:00
kramo
3d6601238d Actions Test 2023-07-02 12:21:55 +02:00
kramo
a7393ba9b9 Rename Windows Action 2023-07-02 01:25:08 +02:00
kramo
c2f429a29c Fix Flatpak action caching 2023-07-02 00:57:54 +02:00
kramo
8fadcc8524 oops 2023-07-02 00:53:20 +02:00
kramo
c544901519 I'm one with the cloud ☁️ (#130) 2023-07-02 00:51:28 +02:00
GeoffreyCoulaud
d252b6b97d 🚧 Very unfinished initial work on import undo 2023-07-02 00:08:00 +02:00
kramo
7756f75bb9 Test Release Action 2023-07-01 20:18:26 +02:00
Geoffrey Coulaud
fabd9828f6 Merge pull request #129 from kra-mo/online-managers-improvements
Retry all online managers on ConnectionError
2023-07-01 18:52:26 +02:00
GeoffreyCoulaud
29e022327b Retry all online managers on ConnectionError 2023-07-01 18:51:50 +02:00
Geoffrey Coulaud
49fb3705e4 Merge pull request #128 from kra-mo/windows-logging-fix
Windows fixes
2023-07-01 18:29:47 +02:00
GeoffreyCoulaud
0c6cfc14f0 Fix hiding unavailable source rows 2023-07-01 18:27:11 +02:00
GeoffreyCoulaud
75a5255806 Sepcified utf-8 for lzma log backups 2023-07-01 18:14:10 +02:00
Geoffrey Coulaud
dd1dd2b7e5 Merge pull request #127 from kra-mo/sources-paths-fix
Sources paths fix
2023-07-01 17:10:47 +02:00
GeoffreyCoulaud
4a204442b5 Using new shared paths in sources 2023-07-01 15:53:52 +02:00
GeoffreyCoulaud
fb0c47c1f1 Re added hardcoded candidates for linux
- Real fix would be to get the XDG_*_DIR
2023-07-01 15:17:12 +02:00
GeoffreyCoulaud
3af968fee7 Simplified source available on 2023-07-01 15:10:40 +02:00
kramo
9fbb45cfa5 Fix Steam API developers 2023-07-01 14:30:26 +02:00
kramo
bbfe478ac3 Fix Legendary location labels 2023-07-01 13:58:47 +02:00
kramo
339ec1c20a Update translations 2023-07-01 13:48:30 +02:00
kramo
97e40e0a80 Fix Legendary, warning for missing locations 2023-07-01 13:44:14 +02:00
kramo
95d47815ab Add translation comments 2023-07-01 11:36:45 +02:00
kramo
772bf30468 Fix translations 2023-07-01 11:35:17 +02:00
kramo
7311015549 Fix translations 2023-07-01 11:30:44 +02:00
kramo
e7d2f58416 Merge pull request #126 from kra-mo/kra-mo-patch-1
Update README.md
2023-07-01 11:23:46 +02:00
kramo
a040b058d2 Update README.md 2023-07-01 11:23:38 +02:00
kramo
7be20c64bd Update translations 2023-07-01 11:15:55 +02:00
kramo
036e5814f3 Fix Flatpak custom paths 2023-07-01 11:11:16 +02:00
kramo
e46c9b6a30 Fix Flatpak desktop entry search 2023-07-01 10:54:48 +02:00
kramo
495755f278 More reliance on GLib 2023-07-01 10:44:15 +02:00
kramo
1f25bed842 Move away from PyXDG 2023-07-01 10:05:16 +02:00
28 changed files with 428 additions and 255 deletions

View File

@@ -2,20 +2,40 @@ on:
push: push:
branches: [main] branches: [main]
pull_request: pull_request:
name: "Build for Windows" name: CI
concurrency:
group: release-${{ github.sha }}
jobs: jobs:
flatpak:
name: Flatpak
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-44
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Flatpak Builder
uses: flatpak/flatpak-github-actions/flatpak-builder@v6.1
with:
bundle: hu.kramo.Cartridges.Devel.flatpak
manifest-path: flatpak/hu.kramo.Cartridges.Devel.json
windows: windows:
name: "Build" name: Windows
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: MSYS2
- name: Setup MSYS2
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
with: with:
msystem: UCRT64 msystem: UCRT64
update: true update: true
install: mingw-w64-ucrt-x86_64-gtk4 mingw-w64-ucrt-x86_64-libadwaita mingw-w64-ucrt-x86_64-python-gobject mingw-w64-ucrt-x86_64-python-yaml mingw-w64-ucrt-x86_64-python-requests mingw-w64-ucrt-x86_64-python-pillow mingw-w64-ucrt-x86_64-desktop-file-utils mingw-w64-ucrt-x86_64-ca-certificates mingw-w64-ucrt-x86_64-meson git install: mingw-w64-ucrt-x86_64-gtk4 mingw-w64-ucrt-x86_64-libadwaita mingw-w64-ucrt-x86_64-python-gobject mingw-w64-ucrt-x86_64-python-yaml mingw-w64-ucrt-x86_64-python-requests mingw-w64-ucrt-x86_64-python-pillow mingw-w64-ucrt-x86_64-desktop-file-utils mingw-w64-ucrt-x86_64-ca-certificates mingw-w64-ucrt-x86_64-meson git
- name: Compile - name: Compile
shell: msys2 {0} shell: msys2 {0}
run: | run: |
@@ -23,10 +43,18 @@ jobs:
ninja -C _build install ninja -C _build install
pacman --noconfirm -Rs mingw-w64-ucrt-x86_64-desktop-file-utils mingw-w64-ucrt-x86_64-meson git pacman --noconfirm -Rs mingw-w64-ucrt-x86_64-desktop-file-utils mingw-w64-ucrt-x86_64-meson git
find /ucrt64/share/locale/ -type f ! -name "*cartridges.mo" -delete find /ucrt64/share/locale/ -type f ! -name "*cartridges.mo" -delete
- name: "Inno Setup"
- name: Test
shell: msys2 {0}
run: |
set +e
timeout 2 cartridges; [ "$?" -eq "124" ]
- name: Inno Setup
run: iscc ".\_build\Cartridges.iss" run: iscc ".\_build\Cartridges.iss"
- name: "Upload Artifact"
- name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: "Installer" name: Windows Installer
path: "_build/Output/Cartridges Setup.exe" path: _build/Output/Cartridges Setup.exe

View File

@@ -1,19 +0,0 @@
on:
push:
branches: [main]
pull_request:
name: CI
jobs:
flatpak:
name: "Flatpak"
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-44
options: --privileged
steps:
- uses: actions/checkout@v3
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: hu.kramo.Cartridges.Devel.flatpak
manifest-path: flatpak/hu.kramo.Cartridges.Devel.json
cache-key: flatpak-builder-${{ github.sha }}

45
.github/workflows/publish-release.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
on:
push:
tags:
"*"
name: Publish Release
concurrency:
group: release-${{ github.sha }}
jobs:
publish-release:
name: Publish Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download workflow artifact
uses: dawidd6/action-download-artifact@v2.27.0
with:
workflow: ci.yml
commit: ${{ github.sha }}
- name: Get release notes
shell: python
run: |
import re, textwrap
open_file = open("./data/hu.kramo.Cartridges.metainfo.xml.in", "r", encoding="utf-8")
string = open_file.read()
open_file.close()
string = re.findall("<release.*>\s*<description.*>\n([\s\S]*?)\s*</description>\s*<\/release>", string)[0]
string = textwrap.dedent(string)
open_file = open("release_notes", "w", encoding="utf-8")
open_file.write(string)
open_file.close()
- name: Get tag name
id: get_tag_name
run: echo tag_name=${GITHUB_REF#refs/tags/} >> $GITHUB_OUTPUT
- name: Publish release
uses: softprops/action-gh-release@v0.1.15
with:
files: Windows Installer/Cartridges Setup.exe
fail_on_unmatched_files: true
tag_name: ${{ steps.get_tag_name.outputs.tag_name }}
body_path: release_notes

View File

@@ -2,9 +2,9 @@
ignore=importers ignore=importers
[MESSAGES CONTROL] [MESSAGES CONTROL]
disable=raw-checker-failed, disable=raw-checker-failed,
bad-inline-option, bad-inline-option,
locally-disabled, locally-disabled,

View File

@@ -23,7 +23,7 @@ The project can be translated on [Weblate](https://hosted.weblate.org/engage/car
## For Windows ## For Windows
1. Install [MSYS2](https://www.msys2.org/). 1. Install [MSYS2](https://www.msys2.org/).
2. From the MSYS2 shell, install the required dependencies listed [here](https://github.com/kra-mo/cartridges/blob/main/.github/workflows/windows.yml). 2. From the MSYS2 shell, install the required dependencies listed [here](https://github.com/kra-mo/cartridges/blob/main/.github/workflows/ci.yml).
3. Build it via Meson. 3. Build it via Meson.
## Meson ## Meson

View File

@@ -10,16 +10,13 @@
[![Flathub][flathub-image]][flathub-url] [![Flathub][flathub-image]][flathub-url]
[![Build status][github-actions-image]][github-actions-url] [![Build status][github-actions-image]][github-actions-url]
[![Translation Status][weblate-image]][weblate-url] [![Translation Status][weblate-image]][weblate-url]
[![License][license-image]][license-url]
[![Code style][code-style-image]][code-style-url] [![Code style][code-style-image]][code-style-url]
[![Discord][discord-image]][discord-url] [![Discord][discord-image]][discord-url]
[circle-url]: https://circle.gnome.org [circle-url]: https://circle.gnome.org
[circle-image]: https://circle.gnome.org/assets/button/badge.svg [circle-image]: https://circle.gnome.org/assets/button/badge.svg
[github-actions-url]: https://github.com/kra-mo/cartridges [github-actions-url]: https://github.com/kra-mo/cartridges
[github-actions-image]: https://github.com/kra-mo/cartridges/actions/workflows/flatpak-builder.yml/badge.svg [github-actions-image]: https://github.com/kra-mo/cartridges/actions/workflows/ci.yml/badge.svg
[license-url]: https://github.com/kra-mo/cartridges/blob/main/LICENSE
[license-image]: https://img.shields.io/github/license/kra-mo/cartridges
[code-style-url]: https://github.com/psf/black [code-style-url]: https://github.com/psf/black
[code-style-image]: https://img.shields.io/badge/code%20style-black-000000?style=flat [code-style-image]: https://img.shields.io/badge/code%20style-black-000000?style=flat
[weblate-url]: https://hosted.weblate.org/engage/cartridges/ [weblate-url]: https://hosted.weblate.org/engage/cartridges/
@@ -39,8 +36,14 @@ Cartridges is a simple game launcher written in Python using GTK4 and Libadwaita
## Features ## Features
- Manually adding and editing games - Manually adding and editing games
- Importing games from Steam, Lutris, Heroic, Bottles and itch - Importing games from various sources:
- Support for multiple Steam install locations - Steam
- Lutris
- Heroic
- Bottles
- itch
- Legendary
- Flatpak
- Hiding games - Hiding games
- Searching and sorting by title, date added and last played - Searching and sorting by title, date added and last played
- Automatically downloading cover art from [SteamGridDB](https://www.steamgriddb.com/) - Automatically downloading cover art from [SteamGridDB](https://www.steamgriddb.com/)
@@ -86,4 +89,4 @@ Thanks to [Weblate](https://weblate.org/) for hosting our translations!
The project follows the [GNOME Code of Conduct](https://wiki.gnome.org/Foundation/CodeOfConduct). The project follows the [GNOME Code of Conduct](https://wiki.gnome.org/Foundation/CodeOfConduct).
See [CODE_OF_CONDUCT.md](https://github.com/kra-mo/cartridges/blob/main/CODE_OF_CONDUCT.md). See [CODE_OF_CONDUCT.md](https://github.com/kra-mo/cartridges/blob/main/CODE_OF_CONDUCT.md).

View File

@@ -68,7 +68,7 @@
<default>true</default> <default>true</default>
</key> </key>
<key name="flatpak-location" type="s"> <key name="flatpak-location" type="s">
<default>"/var/lib/flatpak/exports/"</default> <default>"/var/lib/flatpak/"</default>
</key> </key>
<key name="flatpak-import-launchers" type="b"> <key name="flatpak-import-launchers" type="b">
<default>false</default> <default>false</default>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application"> <component type="desktop-application">
<id>@APP_ID@.desktop</id> <id>@APP_ID@</id>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-or-later</project_license> <project_license>GPL-3.0-or-later</project_license>
<name>Cartridges</name> <name>Cartridges</name>

View File

@@ -97,20 +97,6 @@
"sha256": "bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1" "sha256": "bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"
} }
] ]
},
{
"name": "python3-pyxdg",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pyxdg\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/e5/8d/cf41b66a8110670e3ad03dab9b759704eeed07fa96e90fdc0357b2ba70e2/pyxdg-0.28-py2.py3-none-any.whl",
"sha256": "bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab"
}
]
} }
] ]
}, },

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Cartridges\n" "Project-Id-Version: Cartridges\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-26 22:22+0200\n" "POT-Creation-Date: 2023-07-01 13:45+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -19,7 +19,7 @@ msgstr ""
#: data/hu.kramo.Cartridges.desktop.in:3 #: data/hu.kramo.Cartridges.desktop.in:3
#: data/hu.kramo.Cartridges.metainfo.xml.in:6 data/gtk/window.blp:47 #: data/hu.kramo.Cartridges.metainfo.xml.in:6 data/gtk/window.blp:47
#: src/main.py:153 #: src/main.py:162
msgid "Cartridges" msgid "Cartridges"
msgstr "" msgstr ""
@@ -57,7 +57,7 @@ msgid "Game Details"
msgstr "" msgstr ""
#: data/hu.kramo.Cartridges.metainfo.xml.in:42 data/gtk/window.blp:416 #: data/hu.kramo.Cartridges.metainfo.xml.in:42 data/gtk/window.blp:416
#: src/details_window.py:239 #: src/details_window.py:238
msgid "Preferences" msgid "Preferences"
msgstr "" msgstr ""
@@ -106,7 +106,7 @@ msgstr ""
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
#: data/gtk/game.blp:107 src/window.py:169 #: data/gtk/game.blp:107 src/window.py:171
msgid "Hide" msgid "Hide"
msgstr "" msgstr ""
@@ -115,7 +115,7 @@ msgstr ""
msgid "Remove" msgid "Remove"
msgstr "" msgstr ""
#: data/gtk/game.blp:126 src/window.py:171 #: data/gtk/game.blp:126 src/window.py:173
msgid "Unhide" msgid "Unhide"
msgstr "" msgstr ""
@@ -140,7 +140,7 @@ msgstr ""
msgid "Shortcuts" msgid "Shortcuts"
msgstr "" msgstr ""
#: data/gtk/help-overlay.blp:34 src/game.py:105 src/preferences.py:103 #: data/gtk/help-overlay.blp:34 src/game.py:102 src/preferences.py:112
msgid "Undo" msgid "Undo"
msgstr "" msgstr ""
@@ -168,7 +168,7 @@ msgstr ""
msgid "Remove game" msgid "Remove game"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:13 data/gtk/preferences.blp:236 #: data/gtk/preferences.blp:13 data/gtk/preferences.blp:268
msgid "Behavior" msgid "Behavior"
msgstr "" msgstr ""
@@ -217,8 +217,9 @@ msgid "Steam"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:96 data/gtk/preferences.blp:110 #: data/gtk/preferences.blp:96 data/gtk/preferences.blp:110
#: data/gtk/preferences.blp:142 data/gtk/preferences.blp:183 #: data/gtk/preferences.blp:151 data/gtk/preferences.blp:192
#: data/gtk/preferences.blp:197 data/gtk/preferences.blp:211 #: data/gtk/preferences.blp:206 data/gtk/preferences.blp:220
#: data/gtk/preferences.blp:234
msgid "Install Location" msgid "Install Location"
msgstr "" msgstr ""
@@ -234,59 +235,71 @@ msgstr ""
msgid "Import Steam Games" msgid "Import Steam Games"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:138 #: data/gtk/preferences.blp:137
msgid "Import Flatpak Games"
msgstr ""
#: data/gtk/preferences.blp:147
msgid "Heroic" msgid "Heroic"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:151 #: data/gtk/preferences.blp:160
msgid "Import Epic Games" msgid "Import Epic Games"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:160 #: data/gtk/preferences.blp:169
msgid "Import GOG Games" msgid "Import GOG Games"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:169 #: data/gtk/preferences.blp:178
msgid "Import Sideloaded Games" msgid "Import Sideloaded Games"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:179 #: data/gtk/preferences.blp:188
msgid "Bottles" msgid "Bottles"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:193 #: data/gtk/preferences.blp:202
msgid "itch" msgid "itch"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:207 #: data/gtk/preferences.blp:216
msgid "Legendary" msgid "Legendary"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:224 #: data/gtk/preferences.blp:230
msgid "Flatpak"
msgstr ""
#: data/gtk/preferences.blp:243
msgid "Import Game Launchers"
msgstr ""
#: data/gtk/preferences.blp:256
msgid "SteamGridDB" msgid "SteamGridDB"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:228 #: data/gtk/preferences.blp:260
msgid "Authentication" msgid "Authentication"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:231 #: data/gtk/preferences.blp:263
msgid "API Key" msgid "API Key"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:239 #: data/gtk/preferences.blp:271
msgid "Use SteamGridDB" msgid "Use SteamGridDB"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:240 #: data/gtk/preferences.blp:272
msgid "Download images when adding or importing games" msgid "Download images when adding or importing games"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:249 #: data/gtk/preferences.blp:281
msgid "Prefer Over Official Images" msgid "Prefer Over Official Images"
msgstr "" msgstr ""
#: data/gtk/preferences.blp:258 #: data/gtk/preferences.blp:290
msgid "Prefer Animated Images" msgid "Prefer Animated Images"
msgstr "" msgstr ""
@@ -375,21 +388,21 @@ msgid "About Cartridges"
msgstr "" msgstr ""
#. Translators: Replace this with your name for it to show up in the about window #. Translators: Replace this with your name for it to show up in the about window
#: src/main.py:171 #: src/main.py:180
msgid "translator_credits" msgid "translator_credits"
msgstr "" msgstr ""
#. The variable is the date when the game was added #. The variable is the date when the game was added
#: src/window.py:192 #: src/window.py:194
msgid "Added: {}" msgid "Added: {}"
msgstr "" msgstr ""
#: src/window.py:195 #: src/window.py:197
msgid "Never" msgid "Never"
msgstr "" msgstr ""
#. The variable is the date when the game was last played #. The variable is the date when the game was last played
#: src/window.py:199 #: src/window.py:201
msgid "Last played: {}" msgid "Last played: {}"
msgstr "" msgstr ""
@@ -442,46 +455,68 @@ msgstr ""
msgid "Couldn't Add Game" msgid "Couldn't Add Game"
msgstr "" msgstr ""
#: src/details_window.py:146 src/details_window.py:181 #: src/details_window.py:146 src/details_window.py:180
msgid "Game title cannot be empty." msgid "Game title cannot be empty."
msgstr "" msgstr ""
#: src/details_window.py:152 src/details_window.py:189 #: src/details_window.py:152 src/details_window.py:188
msgid "Executable cannot be empty." msgid "Executable cannot be empty."
msgstr "" msgstr ""
#: src/details_window.py:180 src/details_window.py:188 #: src/details_window.py:179 src/details_window.py:187
msgid "Couldn't Apply Preferences" msgid "Couldn't Apply Preferences"
msgstr "" msgstr ""
#. The variable is the title of the game #. The variable is the title of the game
#: src/game.py:141 #: src/game.py:138
msgid "{} launched" msgid "{} launched"
msgstr "" msgstr ""
#. The variable is the title of the game #. The variable is the title of the game
#: src/game.py:154 #: src/game.py:151
msgid "{} hidden" msgid "{} hidden"
msgstr "" msgstr ""
#: src/game.py:154 #: src/game.py:151
msgid "{} unhidden" msgid "{} unhidden"
msgstr "" msgstr ""
#: src/game.py:171 #: src/game.py:168
msgid "{} removed" msgid "{} removed"
msgstr "" msgstr ""
#: src/preferences.py:102 #: src/preferences.py:111
msgid "All games removed" msgid "All games removed"
msgstr "" msgstr ""
#: src/preferences.py:149 #: src/preferences.py:159
msgid "" msgid ""
"An API key is required to use SteamGridDB. You can generate one {}here{}." "An API key is required to use SteamGridDB. You can generate one {}here{}."
msgstr "" msgstr ""
#: src/preferences.py:289 #: src/preferences.py:284
msgid "Installation Not Found"
msgstr ""
#: src/preferences.py:286
msgid "Select a valid directory."
msgstr ""
#: src/preferences.py:349 src/preferences.py:353
msgid "Invalid Directory"
msgstr ""
#. The variable is the name of the source
#: src/preferences.py:351
msgid "Select the {} cache directory."
msgstr ""
#. The variable is the name of the source
#: src/preferences.py:355
msgid "Select the {} installation directory."
msgstr ""
#: src/preferences.py:361
msgid "Set Location" msgid "Set Location"
msgstr "" msgstr ""

View File

@@ -114,7 +114,7 @@ class DetailsWindow(Adw.Window):
self.exec_info_label.set_label(exec_info_text) self.exec_info_label.set_label(exec_info_text)
def clear_info_selection(*_args): def clear_info_selection(*_args):
self.exec_info_label.select_region(0, 0) self.exec_info_label.select_region(-1, -1)
self.exec_info_popover.connect("show", clear_info_selection) self.exec_info_popover.connect("show", clear_info_selection)

View File

@@ -18,7 +18,9 @@
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import json
import logging import logging
from shutil import copytree, rmtree
from gi.repository import Adw, GLib, Gtk from gi.repository import Adw, GLib, Gtk
@@ -26,8 +28,15 @@ from src import shared
from src.errors.error_producer import ErrorProducer from src.errors.error_producer import ErrorProducer
from src.errors.friendly_error import FriendlyError from src.errors.friendly_error import FriendlyError
from src.game import Game from src.game import Game
from src.importer.sources.bottles_source import BottlesSource
from src.importer.sources.flatpak_source import FlatpakSource
from src.importer.sources.heroic_source import HeroicSource
from src.importer.sources.itch_source import ItchSource
from src.importer.sources.legendary_source import LegendarySource
from src.importer.sources.location import UnresolvableLocationError from src.importer.sources.location import UnresolvableLocationError
from src.importer.sources.lutris_source import LutrisSource
from src.importer.sources.source import Source from src.importer.sources.source import Source
from src.importer.sources.steam_source import SteamSource
from src.store.managers.async_manager import AsyncManager from src.store.managers.async_manager import AsyncManager
from src.store.pipeline import Pipeline from src.store.pipeline import Pipeline
from src.utils.task import Task from src.utils.task import Task
@@ -53,6 +62,20 @@ class Importer(ErrorProducer):
super().__init__() super().__init__()
self.game_pipelines = set() self.game_pipelines = set()
self.sources = set() self.sources = set()
if shared.schema.get_boolean("lutris"):
self.sources.add(LutrisSource())
if shared.schema.get_boolean("steam"):
self.sources.add(SteamSource())
if shared.schema.get_boolean("heroic"):
self.sources.add(HeroicSource())
if shared.schema.get_boolean("bottles"):
self.sources.add(BottlesSource())
if shared.schema.get_boolean("flatpak"):
self.sources.add(FlatpakSource())
if shared.schema.get_boolean("itch"):
self.sources.add(ItchSource())
if shared.schema.get_boolean("legendary"):
self.sources.add(LegendarySource())
@property @property
def n_games_added(self): def n_games_added(self):
@@ -85,12 +108,47 @@ class Importer(ErrorProducer):
and len(self.game_pipelines) == self.n_pipelines_done and len(self.game_pipelines) == self.n_pipelines_done
) )
def add_source(self, source): def load_games_from_disk(self):
self.sources.add(source) """Load the games from disk"""
if shared.games_dir.is_dir():
for game_file in shared.games_dir.iterdir():
data = json.load(game_file.open())
game = Game(data)
shared.store.add_game(game, {"skip_save": True})
game.update()
def delete_backup(self):
"""Delete a a previously made backup"""
rmtree(shared.backup_games_dir, ignore_errors=True)
rmtree(shared.backup_covers_dir, ignore_errors=True)
def create_backup(self):
"""Make a games and covers backup"""
self.delete_backup()
copytree(shared.games_dir, shared.backup_games_dir)
copytree(shared.covers_dir, shared.backup_covers_dir)
def restore_backup(self):
"""Restore a previously made backup"""
# Remove games from the store and UI
for game in shared.store.games.values():
game.update_values({"removed": True})
game.update()
# Restore the disk backup
rmtree(shared.games_dir, ignore_errors=True)
rmtree(shared.covers_dir, ignore_errors=True)
shared.backup_games_dir.rename(shared.games_dir)
shared.backup_covers_dir.rename(shared.covers_dir)
# Reload games from disk
self.load_games_from_disk()
def run(self): def run(self):
"""Use several Gio.Task to import games from added sources""" """Use several Gio.Task to import games from added sources"""
self.create_backup()
self.create_dialog() self.create_dialog()
# Collect all errors and reset the cancellables for the managers # Collect all errors and reset the cancellables for the managers
@@ -288,13 +346,17 @@ class Importer(ErrorProducer):
"open_preferences", "open_preferences",
"import", "import",
) )
else:
elif self.n_games_added == 1: toast.set_button_label(_("Undo"))
toast.set_title(_("1 game imported")) toast.connect(
"button-clicked", self.dialog_response_callback, "undo_import"
elif self.n_games_added > 1: )
# The variable is the number of games toast.set_title(
toast.set_title(_("{} games imported").format(self.n_games_added)) _("1 game imported")
if self.n_games_added == 1
# The variable is the number of games
else _("{} games imported").format(self.n_games_added)
)
shared.win.toast_overlay.add_toast(toast) shared.win.toast_overlay.add_toast(toast)
return toast return toast
@@ -315,5 +377,7 @@ class Importer(ErrorProducer):
self.open_preferences(*args) self.open_preferences(*args)
elif response == "open_preferences_import": elif response == "open_preferences_import":
self.open_preferences(*args).connect("close-request", self.timeout_toast) self.open_preferences(*args).connect("close-request", self.timeout_toast)
elif response == "undo_import":
self.restore_backup()
else: else:
self.timeout_toast() self.timeout_toast()

View File

@@ -86,13 +86,14 @@ class BottlesSource(URLExecutableSource):
name = "Bottles" name = "Bottles"
iterator_class = BottlesSourceIterator iterator_class = BottlesSourceIterator
url_format = 'bottles:run/"{bottle_name}"/"{game_name}"' url_format = 'bottles:run/"{bottle_name}"/"{game_name}"'
available_on = set(("linux",)) available_on = {"linux"}
data_location = Location( data_location = Location(
schema_key="bottles-location", schema_key="bottles-location",
candidates=( candidates=(
"~/.var/app/com.usebottles.bottles/data/bottles/", shared.flatpak_dir / "com.usebottles.bottles" / "data" / "bottles",
shared.data_dir / "bottles/", shared.data_dir / "bottles/",
shared.home / ".local" / "share" / "bottles",
), ),
paths={ paths={
"library.yml": (False, "library.yml"), "library.yml": (False, "library.yml"),

View File

@@ -17,11 +17,10 @@
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import re
from pathlib import Path from pathlib import Path
from time import time from time import time
import subprocess
from xdg import IconTheme from gi.repository import GLib, Gtk
from src import shared from src import shared
from src.game import Game from src.game import Game
@@ -37,64 +36,48 @@ class FlatpakSourceIterator(SourceIterator):
added_time = int(time()) added_time = int(time())
IconTheme.icondirs.append(self.source.data_location["icons"]) icon_theme = Gtk.IconTheme.new()
icon_theme.add_search_path(str(self.source.data_location["icons"]))
try: blacklist = (
process = subprocess.run( {"hu.kramo.Cartridges"}
("flatpak-spawn", "--host", "flatpak", "list", "--columns=application"), if shared.schema.get_boolean("flatpak-import-launchers")
capture_output=True, else {
encoding="utf-8", "hu.kramo.Cartridges",
check=True, "com.valvesoftware.Steam",
) "net.lutris.Lutris",
flatpak_ids = process.stdout.split("\n") "com.heroicgameslauncher.hgl",
"com.usebottles.Bottles",
to_remove = ( "io.itch.itch",
{"hu.kramo.Cartridges"} }
if shared.schema.get_boolean("flatpak-import-launchers") )
else {
"hu.kramo.Cartridges",
"com.valvesoftware.Steam",
"net.lutris.Lutris",
"com.heroicgameslauncher.hgl",
"com.usebottles.Bottles",
"io.itch.itch",
}
)
for item in to_remove:
if item in flatpak_ids:
flatpak_ids.remove(item)
except subprocess.CalledProcessError:
return
for entry in (self.source.data_location["applications"]).iterdir(): for entry in (self.source.data_location["applications"]).iterdir():
flatpak_id = entry.stem if entry.suffix != ".desktop":
if flatpak_id not in flatpak_ids:
continue continue
with entry.open("r", encoding="utf-8") as open_file: keyfile = GLib.KeyFile.new()
string = open_file.read()
desktop_values = {"Name": None, "Icon": None, "Categories": None} try:
for key in desktop_values: keyfile.load_from_file(str(entry), 0)
if regex := re.findall(f"{key}=(.*)\n", string):
desktop_values[key] = regex[0]
if not desktop_values["Name"]: if "Game" not in keyfile.get_string_list("Desktop Entry", "Categories"):
continue continue
if not desktop_values["Categories"]: if (
continue flatpak_id := keyfile.get_string("Desktop Entry", "X-Flatpak")
) in blacklist or flatpak_id != entry.stem:
continue
if not "Game" in desktop_values["Categories"].split(";"): name = keyfile.get_string("Desktop Entry", "Name")
except GLib.GError:
continue continue
values = { values = {
"source": self.source.id, "source": self.source.id,
"added": added_time, "added": added_time,
"name": desktop_values["Name"], "name": name,
"game_id": self.source.game_id_format.format(game_id=flatpak_id), "game_id": self.source.game_id_format.format(game_id=flatpak_id),
"executable": self.source.executable_format.format( "executable": self.source.executable_format.format(
flatpak_id=flatpak_id flatpak_id=flatpak_id
@@ -103,11 +86,25 @@ class FlatpakSourceIterator(SourceIterator):
game = Game(values) game = Game(values)
additional_data = {} additional_data = {}
if icon_name := desktop_values["Icon"]:
if icon_path := IconTheme.getIconPath(icon_name, 512): try:
if (
icon_path := icon_theme.lookup_icon(
keyfile.get_string("Desktop Entry", "Icon"),
None,
512,
1,
shared.win.get_direction(),
0,
)
.get_file()
.get_path()
):
additional_data = {"local_icon_path": Path(icon_path)} additional_data = {"local_icon_path": Path(icon_path)}
else: else:
pass pass
except GLib.GError:
pass
# Produce game # Produce game
yield (game, additional_data) yield (game, additional_data)
@@ -119,16 +116,16 @@ class FlatpakSource(Source):
name = "Flatpak" name = "Flatpak"
iterator_class = FlatpakSourceIterator iterator_class = FlatpakSourceIterator
executable_format = "flatpak run {flatpak_id}" executable_format = "flatpak run {flatpak_id}"
available_on = set(("linux",)) available_on = {"linux"}
data_location = Location( data_location = Location(
schema_key="flatpak-location", schema_key="flatpak-location",
candidates=( candidates=(
"/var/lib/flatpak/exports/", "/var/lib/flatpak/",
shared.data_dir / "flatpak" / "exports", shared.data_dir / "flatpak",
), ),
paths={ paths={
"applications": (True, "share/applications"), "applications": (True, "exports/share/applications"),
"icons": (True, "share/icons"), "icons": (True, "exports/share/icons"),
}, },
) )

View File

@@ -137,13 +137,14 @@ class HeroicSource(URLExecutableSource):
name = "Heroic" name = "Heroic"
iterator_class = HeroicSourceIterator iterator_class = HeroicSourceIterator
url_format = "heroic://launch/{app_name}" url_format = "heroic://launch/{app_name}"
available_on = set(("linux", "win32")) available_on = {"linux", "win32"}
config_location = Location( config_location = Location(
schema_key="heroic-location", schema_key="heroic-location",
candidates=( candidates=(
"~/.var/app/com.heroicgameslauncher.hgl/config/heroic/", shared.flatpak_dir / "com.heroicgameslauncher.hgl" / "config" / "heroic",
shared.config_dir / "heroic", shared.config_dir / "heroic",
shared.home / ".config" / "heroic",
shared.appdata_dir / "heroic", shared.appdata_dir / "heroic",
), ),
paths={ paths={

View File

@@ -82,13 +82,14 @@ class ItchSource(URLExecutableSource):
name = "Itch" name = "Itch"
iterator_class = ItchSourceIterator iterator_class = ItchSourceIterator
url_format = "itch://caves/{cave_id}/launch" url_format = "itch://caves/{cave_id}/launch"
available_on = set(("linux", "win32")) available_on = {"linux", "win32"}
config_location = Location( config_location = Location(
schema_key="itch-location", schema_key="itch-location",
candidates=( candidates=(
"~/.var/app/io.itch.itch/config/itch/", shared.flatpak_dir / "io.itch.itch" / "config" / "itch",
shared.config_dir / "itch", shared.config_dir / "itch",
shared.home / ".config" / "itch",
shared.appdata_dir / "itch", shared.appdata_dir / "itch",
), ),
paths={"butler.db": (False, "db/butler.db")}, paths={"butler.db": (False, "db/butler.db")},

View File

@@ -92,12 +92,15 @@ class LegendarySourceIterator(SourceIterator):
class LegendarySource(Source): class LegendarySource(Source):
name = "Legendary" name = "Legendary"
executable_format = "legendary launch {app_name}" executable_format = "legendary launch {app_name}"
available_on = set(("linux", "win32")) available_on = {"linux", "win32"}
iterator_class = LegendarySourceIterator iterator_class = LegendarySourceIterator
data_location: Location = Location( config_location: Location = Location(
schema_key="legendary-location", schema_key="legendary-location",
candidates=(shared.config_dir / "legendary",), candidates=(
shared.config_dir / "legendary",
shared.home / ".config" / "legendary",
),
paths={ paths={
"installed.json": (False, "installed.json"), "installed.json": (False, "installed.json"),
"metadata": (True, "metadata"), "metadata": (True, "metadata"),

View File

@@ -93,15 +93,16 @@ class LutrisSource(URLExecutableSource):
name = "Lutris" name = "Lutris"
iterator_class = LutrisSourceIterator iterator_class = LutrisSourceIterator
url_format = "lutris:rungameid/{game_id}" url_format = "lutris:rungameid/{game_id}"
available_on = set(("linux",)) available_on = {"linux"}
# FIXME possible bug: location picks ~/.var... and cache_lcoation picks ~/.local... # FIXME possible bug: location picks ~/.var... and cache_lcoation picks ~/.local...
data_location = Location( data_location = Location(
schema_key="lutris-location", schema_key="lutris-location",
candidates=( candidates=(
"~/.var/app/net.lutris.Lutris/data/lutris/", shared.flatpak_dir / "net.lutris.Lutris" / "data" / "lutris",
shared.data_dir / "lutris", shared.data_dir / "lutris",
shared.home / ".local" / "share" / "lutris",
), ),
paths={ paths={
"pga.db": (False, "pga.db"), "pga.db": (False, "pga.db"),
@@ -111,8 +112,9 @@ class LutrisSource(URLExecutableSource):
cache_location = Location( cache_location = Location(
schema_key="lutris-cache-location", schema_key="lutris-cache-location",
candidates=( candidates=(
"~/.var/app/net.lutris.Lutris/cache/lutris/", shared.flatpak_dir / "net.lutris.Lutris" / "cache" / "lutris",
shared.cache_dir / "lutris", shared.cache_dir / "lutris",
shared.home / ".cache" / "lutris",
), ),
paths={ paths={
"coverart": (True, "coverart"), "coverart": (True, "coverart"),

View File

@@ -111,16 +111,16 @@ class SteamSourceIterator(SourceIterator):
class SteamSource(URLExecutableSource): class SteamSource(URLExecutableSource):
name = "Steam" name = "Steam"
available_on = set(("linux", "win32")) available_on = {"linux", "win32"}
iterator_class = SteamSourceIterator iterator_class = SteamSourceIterator
url_format = "steam://rungameid/{game_id}" url_format = "steam://rungameid/{game_id}"
data_location = Location( data_location = Location(
schema_key="steam-location", schema_key="steam-location",
candidates=( candidates=(
"~/.var/app/com.valvesoftware.Steam/data/Steam/", shared.flatpak_dir / "com.valvesoftware.Steam" / "data" / "Steam",
shared.data_dir / "Steam", shared.data_dir / "Steam",
"~/.steam", shared.home / ".steam",
shared.programfiles32_dir / "Steam", shared.programfiles32_dir / "Steam",
), ),
paths={ paths={

View File

@@ -88,16 +88,20 @@ class SessionFileHandler(StreamHandler):
# If uncompressed, compress # If uncompressed, compress
if not path.name.endswith(".xz"): if not path.name.endswith(".xz"):
new_path = path.with_suffix(path.suffix + ".xz") compressed_path = path.with_suffix(path.suffix + ".xz")
with ( with (
lzma.open( lzma.open(
new_path, "wt", format=FORMAT_XZ, preset=PRESET_DEFAULT compressed_path,
"wt",
format=FORMAT_XZ,
preset=PRESET_DEFAULT,
encoding="utf-8",
) as lzma_file, ) as lzma_file,
open(path, "r", encoding="utf-8") as original_file, open(path, "r", encoding="utf-8") as original_file,
): ):
lzma_file.write(original_file.read()) lzma_file.write(original_file.read())
path.unlink() path.unlink()
path = new_path path = compressed_path
# Rename with new number suffix # Rename with new number suffix
new_number = self.get_path_number(path) + 1 new_number = self.get_path_number(path) + 1

View File

@@ -17,7 +17,6 @@
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import json
import lzma import lzma
import sys import sys
@@ -31,15 +30,7 @@ from gi.repository import Adw, Gio, GLib, Gtk
from src import shared from src import shared
from src.details_window import DetailsWindow from src.details_window import DetailsWindow
from src.game import Game
from src.importer.importer import Importer from src.importer.importer import Importer
from src.importer.sources.bottles_source import BottlesSource
from src.importer.sources.flatpak_source import FlatpakSource
from src.importer.sources.heroic_source import HeroicSource
from src.importer.sources.itch_source import ItchSource
from src.importer.sources.legendary_source import LegendarySource
from src.importer.sources.lutris_source import LutrisSource
from src.importer.sources.steam_source import SteamSource
from src.logging.setup import log_system_info, setup_logging from src.logging.setup import log_system_info, setup_logging
from src.preferences import PreferencesWindow from src.preferences import PreferencesWindow
from src.store.managers.display_manager import DisplayManager from src.store.managers.display_manager import DisplayManager
@@ -83,17 +74,16 @@ class CartridgesApplication(Adw.Application):
"is-maximized", self.win, "maximized", Gio.SettingsBindFlags.DEFAULT "is-maximized", self.win, "maximized", Gio.SettingsBindFlags.DEFAULT
) )
# Load games from disk # Add managers to the store for game imports
shared.store.add_manager(FileManager(), False)
shared.store.add_manager(DisplayManager()) shared.store.add_manager(DisplayManager())
self.load_games_from_disk() shared.store.add_manager(FileManager())
# Add rest of the managers for game imports
shared.store.add_manager(LocalCoverManager()) shared.store.add_manager(LocalCoverManager())
shared.store.add_manager(SteamAPIManager()) shared.store.add_manager(SteamAPIManager())
shared.store.add_manager(OnlineCoverManager()) shared.store.add_manager(OnlineCoverManager())
shared.store.add_manager(SGDBManager()) shared.store.add_manager(SGDBManager())
shared.store.enable_manager_in_pipelines(FileManager)
# Load games from disk
Importer().load_games_from_disk()
# Create actions # Create actions
self.create_actions( self.create_actions(
@@ -134,13 +124,6 @@ class CartridgesApplication(Adw.Application):
self.win.present() self.win.present()
def load_games_from_disk(self):
if shared.games_dir.is_dir():
for game_file in shared.games_dir.iterdir():
data = json.load(game_file.open())
game = Game(data)
shared.store.add_game(game, {"skip_save": True})
def on_about_action(self, *_args): def on_about_action(self, *_args):
# Get the debug info from the log files # Get the debug info from the log files
debug_str = "" debug_str = ""
@@ -207,30 +190,7 @@ class CartridgesApplication(Adw.Application):
DetailsWindow() DetailsWindow()
def on_import_action(self, *_args): def on_import_action(self, *_args):
importer = Importer() Importer().run()
if shared.schema.get_boolean("lutris"):
importer.add_source(LutrisSource())
if shared.schema.get_boolean("steam"):
importer.add_source(SteamSource())
if shared.schema.get_boolean("heroic"):
importer.add_source(HeroicSource())
if shared.schema.get_boolean("bottles"):
importer.add_source(BottlesSource())
if shared.schema.get_boolean("flatpak"):
importer.add_source(FlatpakSource())
if shared.schema.get_boolean("itch"):
importer.add_source(ItchSource())
if shared.schema.get_boolean("legendary"):
importer.add_source(LegendarySource())
importer.run()
def on_remove_game_action(self, *_args): def on_remove_game_action(self, *_args):
self.win.active_game.remove_game() self.win.active_game.remove_game()

View File

@@ -30,6 +30,7 @@ from src.importer.sources.flatpak_source import FlatpakSource
from src.importer.sources.heroic_source import HeroicSource from src.importer.sources.heroic_source import HeroicSource
from src.importer.sources.itch_source import ItchSource from src.importer.sources.itch_source import ItchSource
from src.importer.sources.legendary_source import LegendarySource from src.importer.sources.legendary_source import LegendarySource
from src.importer.sources.location import UnresolvableLocationError
from src.importer.sources.lutris_source import LutrisSource from src.importer.sources.lutris_source import LutrisSource
from src.importer.sources.source import Source from src.importer.sources.source import Source
from src.importer.sources.steam_source import SteamSource from src.importer.sources.steam_source import SteamSource
@@ -99,6 +100,7 @@ class PreferencesWindow(Adw.PreferencesWindow):
remove_all_games_button = Gtk.Template.Child() remove_all_games_button = Gtk.Template.Child()
removed_games = set() removed_games = set()
warning_menu_buttons = {}
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -141,7 +143,7 @@ class PreferencesWindow(Adw.PreferencesWindow):
source = source_class() source = source_class()
if not source.is_available: if not source.is_available:
expander_row = getattr(self, f"{source.id}_expander_row") expander_row = getattr(self, f"{source.id}_expander_row")
expander_row.remove() expander_row.set_visible(False)
else: else:
self.init_source_row(source) self.init_source_row(source)
@@ -251,15 +253,65 @@ class PreferencesWindow(Adw.PreferencesWindow):
if not action_row: if not action_row:
continue continue
# Historically "location" meant data or config, so the key stays shared
infix = "-cache" if location == "cache" else "" infix = "-cache" if location == "cache" else ""
key = f"{source.id}{infix}-location" key = f"{source.id}{infix}-location"
path = Path(shared.schema.get_string(key)).expanduser() path = Path(shared.schema.get_string(key)).expanduser()
# Remove the path if the dir is picked via the Flatpak portal # Remove the path prefix if picked via Flatpak portal
subtitle = re.sub("/run/user/\\d*/doc/.*/", "", str(path)) subtitle = re.sub("/run/user/\\d*/doc/.*/", "", str(path))
action_row.set_subtitle(subtitle) action_row.set_subtitle(subtitle)
def resolve_locations(self, source):
"""Resolve locations and add a warning if location cannot be found"""
def clear_warning_selection(_widget, label):
label.select_region(-1, -1)
for location_name in ("data", "config", "cache"):
action_row = getattr(self, f"{source.id}_{location_name}_action_row", None)
if not action_row:
continue
try:
getattr(source, f"{location_name}_location", None).resolve()
except UnresolvableLocationError:
popover = Gtk.Popover(
child=(
label := Gtk.Label(
label=(
'<span rise="12pt"><b><big>'
+ _("Installation Not Found")
+ "</big></b></span>\n"
+ _("Select a valid directory.")
),
use_markup=True,
wrap=True,
max_width_chars=50,
halign=Gtk.Align.CENTER,
valign=Gtk.Align.CENTER,
justify=Gtk.Justification.CENTER,
margin_top=9,
margin_bottom=9,
margin_start=12,
margin_end=12,
selectable=True,
)
)
)
popover.connect("show", clear_warning_selection, label)
menu_button = Gtk.MenuButton(
icon_name="dialog-warning-symbolic",
valign=Gtk.Align.CENTER,
popover=popover,
)
menu_button.add_css_class("warning")
action_row.add_prefix(menu_button)
self.warning_menu_buttons[source.id] = menu_button
def init_source_row(self, source: Source): def init_source_row(self, source: Source):
"""Initialize a preference row for a source class""" """Initialize a preference row for a source class"""
@@ -281,20 +333,30 @@ class PreferencesWindow(Adw.PreferencesWindow):
shared.schema.set_string(key, value) shared.schema.set_string(key, value)
# Update the row # Update the row
self.update_source_action_row_paths(source) self.update_source_action_row_paths(source)
if self.warning_menu_buttons.get(source.id):
action_row = getattr(
self, f"{source.id}_{location_name}_action_row", None
)
action_row.remove(self.warning_menu_buttons[source.id])
self.warning_menu_buttons.pop(source.id)
logging.debug("User-set value for schema key %s: %s", key, value) logging.debug("User-set value for schema key %s: %s", key, value)
# Bad picked location, inform user # Bad picked location, inform user
else: else:
if location_name == "cache": if location_name == "cache":
title = "Cache directory not found" title = _("Invalid Directory")
subtitle_format = "Select the {} cache directory." # The variable is the name of the source
subtitle_format = _("Select the {} cache directory.")
else: else:
title = "Installation directory not found" title = _("Invalid Directory")
subtitle_format = "Select the {} installation directory." # The variable is the name of the source
subtitle_format = _("Select the {} installation directory.")
dialog = create_dialog( dialog = create_dialog(
self, self,
_(title), title,
_(subtitle_format).format(source.name), subtitle_format.format(source.name),
"choose_folder", "choose_folder",
_("Set Location"), _("Set Location"),
) )
@@ -321,4 +383,5 @@ class PreferencesWindow(Adw.PreferencesWindow):
button.connect("clicked", self.choose_folder, set_dir, location) button.connect("clicked", self.choose_folder, set_dir, location)
# Set the source row subtitles # Set the source row subtitles
self.resolve_locations(source)
self.update_source_action_row_paths(source) self.update_source_action_row_paths(source)

View File

@@ -20,7 +20,7 @@
import os import os
from pathlib import Path from pathlib import Path
from gi.repository import Gdk, Gio from gi.repository import Gdk, Gio, GLib
APP_ID = "@APP_ID@" APP_ID = "@APP_ID@"
VERSION = "@VERSION@" VERSION = "@VERSION@"
@@ -31,25 +31,19 @@ SPEC_VERSION = 1.5 # The version of the game_id.json spec
schema = Gio.Settings.new(APP_ID) schema = Gio.Settings.new(APP_ID)
state_schema = Gio.Settings.new(APP_ID + ".State") state_schema = Gio.Settings.new(APP_ID + ".State")
data_dir = ( home = Path.home()
Path(os.getenv("XDG_DATA_HOME")) data_dir = Path(GLib.get_user_data_dir())
if "XDG_DATA_HOME" in os.environ config_dir = Path(GLib.get_user_config_dir())
else Path.home() / ".local" / "share" cache_dir = Path(GLib.get_user_cache_dir())
) flatpak_dir = home / ".var" / "app"
config_dir = (
Path(os.getenv("XDG_CONFIG_HOME"))
if "XDG_CONFIG_HOME" in os.environ
else Path.home() / ".config"
)
cache_dir = (
Path(os.getenv("XDG_CACHE_HOME"))
if "XDG_CACHE_HOME" in os.environ
else Path.home() / ".cache"
)
games_dir = data_dir / "cartridges" / "games" games_dir = data_dir / "cartridges" / "games"
covers_dir = data_dir / "cartridges" / "covers" covers_dir = data_dir / "cartridges" / "covers"
backup_dir = cache_dir / "cartridges" / "backup"
backup_games_dir = backup_dir / games_dir.name
backup_covers_dir = backup_dir / covers_dir.name
appdata_dir = Path(os.getenv("appdata") or "C:\\Users\\Default\\AppData\\Roaming") appdata_dir = Path(os.getenv("appdata") or "C:\\Users\\Default\\AppData\\Roaming")
programfiles32_dir = Path(os.getenv("programfiles(x86)") or "C:\\Program Files (x86)") programfiles32_dir = Path(os.getenv("programfiles(x86)") or "C:\\Program Files (x86)")

View File

@@ -17,6 +17,9 @@
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import logging
from src import shared
from src.game import Game from src.game import Game
from src.game_cover import GameCover from src.game_cover import GameCover
from src.store.managers.manager import Manager from src.store.managers.manager import Manager
@@ -46,27 +49,28 @@ class DisplayManager(Manager):
"notify::visible", game.toggle_play, None "notify::visible", game.toggle_play, None
) )
game.menu_button.get_popover().connect( game.menu_button.get_popover().connect(
"notify::visible", game.win.set_active_game, game "notify::visible", shared.win.set_active_game, game
) )
if game.game_id in game.win.game_covers: if game.game_id in shared.win.game_covers:
game.game_cover = game.win.game_covers[game.game_id] game.game_cover = shared.win.game_covers[game.game_id]
game.game_cover.add_picture(game.cover) game.game_cover.add_picture(game.cover)
else: else:
game.game_cover = GameCover({game.cover}, game.get_cover_path()) game.game_cover = GameCover({game.cover}, game.get_cover_path())
game.win.game_covers[game.game_id] = game.game_cover shared.win.game_covers[game.game_id] = game.game_cover
if ( if (
game.win.stack.get_visible_child() == game.win.details_view shared.win.stack.get_visible_child() == shared.win.details_view
and game.win.active_game == game and shared.win.active_game == game
): ):
game.win.show_details_view(game) shared.win.show_details_view(game)
if not game.removed and not game.blacklisted: if not game.removed and not game.blacklisted:
logging.debug("Adding %s (%s) to the UI", game.name, game.game_id)
if game.hidden: if game.hidden:
game.win.hidden_library.append(game) shared.win.hidden_library.append(game)
else: else:
game.win.library.append(game) shared.win.library.append(game)
game.get_parent().set_focusable(False) game.get_parent().set_focusable(False)
game.win.set_library_child() shared.win.set_library_child()

View File

@@ -110,6 +110,7 @@ class Manager(ErrorProducer):
except Exception as error: # pylint: disable=broad-exception-caught except Exception as error: # pylint: disable=broad-exception-caught
handle_error(error) handle_error(error)
logging.debug("Running %s for %s (%s)", self.name, game.name, game.game_id)
try_manager_logic() try_manager_logic()
def process_game( def process_game(

View File

@@ -36,7 +36,7 @@ class OnlineCoverManager(Manager):
"""Manager that downloads game covers from URLs""" """Manager that downloads game covers from URLs"""
run_after = (LocalCoverManager,) run_after = (LocalCoverManager,)
retryable_on = (HTTPError, SSLError) retryable_on = (HTTPError, SSLError, ConnectionError)
def save_composited_cover( def save_composited_cover(
self, self,

View File

@@ -32,7 +32,7 @@ from src.utils.steam import (
class SteamAPIManager(AsyncManager): class SteamAPIManager(AsyncManager):
"""Manager in charge of completing a game's data from the Steam API""" """Manager in charge of completing a game's data from the Steam API"""
retryable_on = (HTTPError, SSLError) retryable_on = (HTTPError, SSLError, ConnectionError)
steam_api_helper: SteamAPIHelper = None steam_api_helper: SteamAPIHelper = None
steam_rate_limiter: SteamRateLimiter = None steam_rate_limiter: SteamRateLimiter = None

View File

@@ -57,7 +57,7 @@ class SteamManifestData(TypedDict):
class SteamAPIData(TypedDict): class SteamAPIData(TypedDict):
"""Dict returned by SteamAPIHelper.get_api_data""" """Dict returned by SteamAPIHelper.get_api_data"""
developers: str developer: str
class SteamRateLimiter(RateLimiter): class SteamRateLimiter(RateLimiter):
@@ -148,5 +148,5 @@ class SteamAPIHelper:
raise SteamNotAGameError() raise SteamNotAGameError()
# Return API values we're interested in # Return API values we're interested in
values = SteamAPIData(developers=", ".join(data["data"]["developers"])) values = SteamAPIData(developer=", ".join(data["data"]["developers"]))
return values return values