Flatpak source initial work

This commit is contained in:
kramo
2023-06-30 19:51:44 +02:00
parent 7a1e5e0968
commit fccf302c4b
12 changed files with 160 additions and 36 deletions

View File

@@ -61,6 +61,12 @@
<key name="legendary-location" type="s">
<default>"~/.config/legendary/"</default>
</key>
<key name="flatpak" type="b">
<default>false</default>
</key>
<key name="flatpak-location" type="s">
<default>"/var/lib/flatpak/exports"</default>
</key>
<key name="sgdb-key" type="s">
<default>""</default>
</key>

View File

@@ -16,7 +16,8 @@
"--filesystem=~/.var/app/net.lutris.Lutris/:ro",
"--filesystem=~/.var/app/com.heroicgameslauncher.hgl/config/heroic/:ro",
"--filesystem=~/.var/app/com.usebottles.bottles/data/bottles/:ro",
"--filesystem=~/.var/app/io.itch.itch/config/itch/:ro"
"--filesystem=~/.var/app/io.itch.itch/config/itch/:ro",
"--filesystem=/var/lib/flatpak:ro"
],
"cleanup" : [
"/include",
@@ -96,6 +97,20 @@
"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

@@ -41,13 +41,13 @@ class BottlesSourceIterator(SourceIterator):
data = self.source.data_location["library.yml"].read_text("utf-8")
library: dict = yaml.safe_load(data)
added_time = int(time())
for entry in library.values():
# Build game
values = {
"version": shared.SPEC_VERSION,
"source": self.source.id,
"added": int(time()),
"added": added_time,
"name": entry["name"],
"game_id": self.source.game_id_format.format(game_id=entry["id"]),
"executable": self.source.executable_format.format(

View File

@@ -0,0 +1,94 @@
# flatpak_source.py
#
# Copyright 2022-2023 kramo
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import re
from pathlib import Path
from time import time
from xdg import IconTheme
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
class FlatpakSourceIterator(SourceIterator):
source: "FlatpakSource"
def generator_builder(self) -> SourceIterationResult:
"""Generator method producing games"""
added_time = int(time())
IconTheme.icondirs.append("/var/lib/flatpak/exports/share/icons")
for entry in (self.source.data_location["applications"]).iterdir():
flatpak_id = entry.stem
with entry.open("r", encoding="utf-8") as open_file:
string = open_file.read()
desktop_values = {"Name": None, "Icon": None}
for key in desktop_values:
if regex := re.findall(f"{key}=(.*)\n", string):
desktop_values[key] = regex[0]
if not desktop_values["Name"]:
continue
values = {
"source": self.source.id,
"added": added_time,
"name": desktop_values["Name"],
"game_id": self.source.game_id_format.format(game_id=flatpak_id),
"executable": self.source.executable_format.format(
flatpak_id=flatpak_id
),
}
game = Game(values, allow_side_effects=False)
additional_data = {}
if icon_name := desktop_values["Icon"]:
if icon_path := IconTheme.getIconPath(icon_name):
additional_data = {"local_image_path": Path(icon_path)}
# Produce game
yield (game, additional_data)
class FlatpakSource(Source):
"""Generic Flatpak source"""
name = "Flatpak"
iterator_class = FlatpakSourceIterator
executable_format = "flatpak run {flatpak_id}"
available_on = set(("linux",))
data_location = Location(
schema_key="flatpak-location",
candidates=(
"/var/lib/flatpak/exports/",
shared.data_dir / "flatpak" / "exports",
),
paths={
"applications": (True, "share/applications"),
},
)

View File

@@ -68,7 +68,7 @@ class HeroicSourceIterator(SourceIterator):
}
def game_from_library_entry(
self, entry: HeroicLibraryEntry
self, entry: HeroicLibraryEntry, added_time: int
) -> SourceIterationResult:
"""Helper method used to build a Game from a Heroic library entry"""
@@ -81,9 +81,8 @@ class HeroicSourceIterator(SourceIterator):
runner = entry["runner"]
service = self.sub_sources[runner]["service"]
values = {
"version": shared.SPEC_VERSION,
"source": self.source.id,
"added": int(time()),
"added": added_time,
"name": entry["title"],
"developer": entry["developer"],
"game_id": self.source.game_id_format.format(
@@ -119,9 +118,12 @@ class HeroicSourceIterator(SourceIterator):
# Invalid library.json file, skip it
logging.warning("Couldn't open Heroic file: %s", str(file))
continue
added_time = int(time())
for entry in library:
try:
result = self.game_from_library_entry(entry)
result = self.game_from_library_entry(entry, added_time)
except KeyError:
# Skip invalid games
logging.warning("Invalid Heroic game skipped in %s", str(file))
@@ -130,7 +132,7 @@ class HeroicSourceIterator(SourceIterator):
class HeroicSource(URLExecutableSource):
"""Generic heroic games launcher source"""
"""Generic Heroic Games Launcher source"""
name = "Heroic"
iterator_class = HeroicSourceIterator
@@ -141,9 +143,8 @@ class HeroicSource(URLExecutableSource):
schema_key="heroic-location",
candidates=(
"~/.var/app/com.heroicgameslauncher.hgl/config/heroic/",
shared.config_dir / "heroic/",
"~/.config/heroic/",
shared.appdata_dir / "heroic/",
shared.config_dir / "heroic",
shared.appdata_dir / "heroic",
),
paths={
"config.json": (False, "config.json"),

View File

@@ -59,11 +59,12 @@ class ItchSourceIterator(SourceIterator):
connection = connect(db_path)
cursor = connection.execute(db_request)
added_time = int(time())
# Create games from the db results
for row in cursor:
values = {
"version": shared.SPEC_VERSION,
"added": int(time()),
"added": added_time,
"source": self.source.id,
"name": row[1],
"game_id": self.source.game_id_format.format(game_id=row[0]),
@@ -87,9 +88,8 @@ class ItchSource(URLExecutableSource):
schema_key="itch-location",
candidates=(
"~/.var/app/io.itch.itch/config/itch/",
shared.config_dir / "itch/",
"~/.config/itch/",
shared.appdata_dir / "itch/",
shared.config_dir / "itch",
shared.appdata_dir / "itch",
),
paths={"butler.db": (False, "db/butler.db")},
)

View File

@@ -32,7 +32,9 @@ from src.importer.sources.source import Source, SourceIterationResult, SourceIte
class LegendarySourceIterator(SourceIterator):
source: "LegendarySource"
def game_from_library_entry(self, entry: dict) -> SourceIterationResult:
def game_from_library_entry(
self, entry: dict, added_time: int
) -> SourceIterationResult:
# Skip non-games
if entry["is_dlc"]:
return None
@@ -40,8 +42,7 @@ class LegendarySourceIterator(SourceIterator):
# Build game
app_name = entry["app_name"]
values = {
"version": shared.SPEC_VERSION,
"added": int(time()),
"added": added_time,
"source": self.source.id,
"name": entry["title"],
"game_id": self.source.game_id_format.format(game_id=app_name),
@@ -72,10 +73,13 @@ class LegendarySourceIterator(SourceIterator):
except (JSONDecodeError, OSError):
logging.warning("Couldn't open Legendary file: %s", str(file))
return
added_time = int(time())
# Generate games from library
for entry in library.values():
try:
result = self.game_from_library_entry(entry)
result = self.game_from_library_entry(entry, added_time)
except KeyError as error:
# Skip invalid games
logging.warning(
@@ -93,10 +97,7 @@ class LegendarySource(Source):
iterator_class = LegendarySourceIterator
data_location: Location = Location(
schema_key="legendary-location",
candidates=(
shared.config_dir / "legendary/",
"~/.config/legendary",
),
candidates=(shared.config_dir / "legendary",),
paths={
"installed.json": (False, "installed.json"),
"metadata": (True, "metadata"),

View File

@@ -55,12 +55,13 @@ class LutrisSourceIterator(SourceIterator):
connection = connect(db_path)
cursor = connection.execute(request, params)
added_time = int(time())
# Create games from the DB results
for row in cursor:
# Create game
values = {
"version": shared.SPEC_VERSION,
"added": int(time()),
"added": added_time,
"hidden": row[4],
"name": row[1],
"source": f"{self.source.id}_{row[3]}",
@@ -83,7 +84,7 @@ class LutrisSourceIterator(SourceIterator):
class LutrisSource(URLExecutableSource):
"""Generic lutris source"""
"""Generic Lutris source"""
name = "Lutris"
iterator_class = LutrisSourceIterator
@@ -96,8 +97,7 @@ class LutrisSource(URLExecutableSource):
schema_key="lutris-location",
candidates=(
"~/.var/app/net.lutris.Lutris/data/lutris/",
shared.data_dir / "lutris/",
"~/.local/share/lutris/",
shared.data_dir / "lutris",
),
paths={
"pga.db": (False, "pga.db"),
@@ -108,8 +108,7 @@ class LutrisSource(URLExecutableSource):
schema_key="lutris-cache-location",
candidates=(
"~/.var/app/net.lutris.Lutris/cache/lutris/",
shared.cache_dir / "lutris/",
"~/.cache/lutris",
shared.cache_dir / "lutris",
),
paths={
"coverart": (True, "coverart"),

View File

@@ -87,7 +87,7 @@ class Source(Iterable):
@property
def game_id_format(self) -> str:
"""The string format used to construct game IDs"""
return self.name.lower() + "_{game_id}"
return self.id + "_{game_id}"
@property
def is_available(self):

View File

@@ -66,6 +66,9 @@ class SteamSourceIterator(SourceIterator):
"""Generator method producing games"""
appid_cache = set()
manifests = self.get_manifests()
added_time = int(time())
for manifest in manifests:
# Get metadata from manifest
steam = SteamFileHelper()
@@ -87,8 +90,7 @@ class SteamSourceIterator(SourceIterator):
# Build game from local data
values = {
"version": shared.SPEC_VERSION,
"added": int(time()),
"added": added_time,
"name": local_data["name"],
"source": self.source.id,
"game_id": self.source.game_id_format.format(game_id=appid),
@@ -117,8 +119,8 @@ class SteamSource(URLExecutableSource):
schema_key="steam-location",
candidates=(
"~/.var/app/com.valvesoftware.Steam/data/Steam/",
shared.data_dir / "Steam/",
"~/.steam/",
shared.data_dir / "Steam",
"~/.steam",
shared.programfiles32_dir / "Steam",
),
paths={

View File

@@ -34,6 +34,7 @@ from src.details_window import DetailsWindow
from src.game import Game
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
@@ -222,6 +223,9 @@ class CartridgesApplication(Adw.Application):
if shared.schema.get_boolean("bottles"):
importer.add_source(BottlesSource())
if not shared.schema.get_boolean("flatpak"):
importer.add_source(FlatpakSource())
if shared.schema.get_boolean("itch"):
importer.add_source(ItchSource())

View File

@@ -26,6 +26,7 @@ from gi.repository import Adw, Gio, GLib, Gtk
from src import shared
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
@@ -124,6 +125,7 @@ class PreferencesWindow(Adw.PreferencesWindow):
# Sources settings
for source_class in (
BottlesSource,
FlatpakSource,
HeroicSource,
ItchSource,
LegendarySource,