diff --git a/data/icons/icons.gresource.xml b/data/icons/icons.gresource.xml index bf13ca6a..334ba166 100644 --- a/data/icons/icons.gresource.xml +++ b/data/icons/icons.gresource.xml @@ -3,6 +3,7 @@ symbolic/sources/sources-all.svg symbolic/sources/steam.svg + symbolic/sources/epicgames.svg symbolic/sources/gog.svg symbolic/sources/humble.svg symbolic/sources/humble-trove.svg diff --git a/data/icons/symbolic/sources/epicgames.svg b/data/icons/symbolic/sources/epicgames.svg new file mode 100644 index 00000000..f618e2a2 --- /dev/null +++ b/data/icons/symbolic/sources/epicgames.svg @@ -0,0 +1,30 @@ + +image/svg+xml \ No newline at end of file diff --git a/src/app.vala b/src/app.vala index 9bb6a90d..f9b67a06 100644 --- a/src/app.vala +++ b/src/app.vala @@ -23,6 +23,7 @@ using Gee; using GameHub.Data; using GameHub.Data.DB; using GameHub.Data.Sources.Steam; +using GameHub.Data.Sources.EpicGames; using GameHub.Data.Sources.GOG; using GameHub.Data.Sources.Humble; using GameHub.Data.Sources.Itch; @@ -141,7 +142,7 @@ namespace GameHub ImageCache.init(); Database.create(); - GameSources = { new Steam(), new GOG(), new Humble(), new Trove(), new Itch(), new User() }; + GameSources = { new Steam(), new EpicGames(), new GOG(), new Humble(), new Trove(), new Itch(), new User() }; Providers.ImageProviders = { new Providers.Images.Steam(), new Providers.Images.SteamGridDB(), new Providers.Images.JinxSGVI() }; Providers.DataProviders = { new Providers.Data.IGDB() }; diff --git a/src/data/GameSource.vala b/src/data/GameSource.vala index 25b51e4e..c50abf81 100644 --- a/src/data/GameSource.vala +++ b/src/data/GameSource.vala @@ -21,6 +21,7 @@ using Gee; using GameHub.Utils; using GameHub.Data.Sources.Steam; using GameHub.Data.Sources.GOG; +using GameHub.Data.Sources.EpicGames; namespace GameHub.Data { diff --git a/src/data/db/tables/Games.vala b/src/data/db/tables/Games.vala index c7fcbda5..9423f20d 100644 --- a/src/data/db/tables/Games.vala +++ b/src/data/db/tables/Games.vala @@ -22,6 +22,7 @@ using Sqlite; using GameHub.Utils; using GameHub.Data.Sources.Steam; +using GameHub.Data.Sources.EpicGames; using GameHub.Data.Sources.GOG; using GameHub.Data.Sources.Humble; using GameHub.Data.Sources.Itch; @@ -311,6 +312,10 @@ namespace GameHub.Data.DB.Tables { g = new SteamGame.from_db((Steam) s, st); } + else if(s is EpicGames) + { + g = new EpicGamesGame.from_db((EpicGames) s, st); + } else if(s is GOG) { g = new GOGGame.from_db((GOG) s, st); @@ -396,6 +401,10 @@ namespace GameHub.Data.DB.Tables { g = new SteamGame.from_db((Steam) s, st); } + else if(s is EpicGames) + { + g = new EpicGamesGame.from_db((EpicGames) s, st); + } else if(s is GOG) { g = new GOGGame.from_db((GOG) s, st); diff --git a/src/data/db/tables/IGDBData.vala b/src/data/db/tables/IGDBData.vala index 6c151437..bc53146e 100644 --- a/src/data/db/tables/IGDBData.vala +++ b/src/data/db/tables/IGDBData.vala @@ -24,6 +24,7 @@ using GameHub.Utils; using GameHub.Data.Sources.Steam; using GameHub.Data.Sources.GOG; using GameHub.Data.Sources.Humble; +using GameHub.Data.Sources.EpicGames; namespace GameHub.Data.DB.Tables { diff --git a/src/data/db/tables/Tags.vala b/src/data/db/tables/Tags.vala index dfcad4a0..5803e420 100644 --- a/src/data/db/tables/Tags.vala +++ b/src/data/db/tables/Tags.vala @@ -22,6 +22,7 @@ using Sqlite; using GameHub.Utils; using GameHub.Data.Sources.Steam; +using GameHub.Data.Sources.EpicGames; using GameHub.Data.Sources.GOG; using GameHub.Data.Sources.Humble; diff --git a/src/data/sources/epicgames/EpicGames.vala b/src/data/sources/epicgames/EpicGames.vala new file mode 100644 index 00000000..622b3fc8 --- /dev/null +++ b/src/data/sources/epicgames/EpicGames.vala @@ -0,0 +1,157 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin +Copyright (C) 2020 Adam Jordanek + +GameHub 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. + +GameHub 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 GameHub. If not, see . +*/ + +using Gee; +using GameHub.Data.DB; +using GameHub.Utils; + +namespace GameHub.Data.Sources.EpicGames +{ + public class EpicGames: GameSource + { + public static EpicGames instance; + + public override string id { get { return "epicgames"; } } + public override string name { get { return "EpicGames"; } } + public override string icon { get { return "source-epicgames-symbolic"; } } + + private Regex regex = /\*\s*([^(]*)\s\(App\sname:\s([a-zA-Z0-9]+),\sversion:\s([^)]*)\)/; + + private bool enable = true; + public override bool enabled + { + get { return enable; } + set { enable = value; } + } + + + public LegendaryWrapper? legendary_wrapper { get; private set; } + + public string? user_id { get; protected set; } + public string? user_name { get; protected set; } + + public EpicGames() + { + instance = this; + legendary_wrapper = new LegendaryWrapper(); + } + + public override bool is_installed(bool refresh) + { + debug("[EpicGames] is_installed: NOT IMPLEMENTED"); + return true; + } + + public override async bool install() + { + debug("[EpicGames] install: NOT IMPLEMENTED"); + return true; + } + + public override async bool authenticate() + { + debug("[EpicGames] authenticate: NOT IMPLEMENTED"); + return true; + } + + public override bool is_authenticated() + { + debug("[EpicGames] is_authenticated: NOT IMPLEMENTED"); + return true; + } + + public override bool can_authenticate_automatically() + { + debug("[EpicGames] can_authenticate_automatically: NOT IMPLEMENTED"); + return true; + } + + public async bool refresh_token() + { + debug("[EpicGames] refresh_token: NOT IMPLEMENTED"); + return true; + } + + private ArrayList _games = new ArrayList(Game.is_equal); + + public override ArrayList games { get { return _games; } } + + public override async ArrayList load_games(Utils.FutureResult2? game_loaded=null, Utils.Future? cache_loaded=null) + { + if(_games.size > 0) + { + return _games; + } + + debug("[EpicGames] Load games"); + + Utils.thread("EpicGamesLoading", () => { + _games.clear(); + + games_count = 0; + + var cached = Tables.Games.get_all(this); + if(cached.size > 0) + { + foreach(var g in cached) + { + if(!Settings.UI.Behavior.instance.merge_games || !Tables.Merges.is_game_merged(g)) + { + _games.add(g); + if(game_loaded != null) + { + game_loaded(g, true); + } + } + games_count++; + } + } + + if(cache_loaded != null) + { + cache_loaded(); + } + + var games = legendary_wrapper.getGames(); + foreach(var game in games) + { + var g = new EpicGamesGame(this, game.name, game.id); + bool is_new_game = !_games.contains(g); + if(is_new_game) { + g.save(); + if(game_loaded != null) + { + game_loaded(g, true); + } + _games.add(g); + games_count++; + } else { + var index = _games.index_of(g); + _games.get(index).update_status(); + } + } + Idle.add(load_games.callback); + }); + yield; + return _games; + } + + + } +} diff --git a/src/data/sources/epicgames/EpicGamesGame.vala b/src/data/sources/epicgames/EpicGamesGame.vala new file mode 100644 index 00000000..b204128b --- /dev/null +++ b/src/data/sources/epicgames/EpicGamesGame.vala @@ -0,0 +1,172 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin +Copyright (C) 2020 Adam Jordanek + +GameHub 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. + +GameHub 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 GameHub. If not, see . +*/ + +using Gee; +using GameHub.Data.DB; +using GameHub.Utils; + +using GameHub.Utils.Downloader; + +namespace GameHub.Data.Sources.EpicGames +{ + public class EpicGamesGame: Game + { + public ArrayList? installers { get; protected set; default = new ArrayList(); } + + public EpicGamesGame(EpicGames src, string nameP, string idP) + { + source = src; + name = nameP; + id = idP; + icon = ""; + image = src.legendary_wrapper.get_image(id); + platforms.add(Platform.LINUX); + + install_dir = null; + executable_path = "$game_dir/start.sh"; + work_dir_path = "$game_dir"; + info_detailed = @"{}"; + + mount_overlays.begin(); + update_status(); + } + + public override void update_status() + { + var state = Game.State.UNINSTALLED; + if (((EpicGames)source).legendary_wrapper.is_installed(id)) { + state = Game.State.INSTALLED; + debug ("New installed game: \tname = %s\t", name); + } else { + + debug ("New not installed game: \tname = %s\t", name); + } + + if(state == Game.State.INSTALLED) + { + remove_tag(Tables.Tags.BUILTIN_UNINSTALLED); + add_tag(Tables.Tags.BUILTIN_INSTALLED); + } + else + { + add_tag(Tables.Tags.BUILTIN_UNINSTALLED); + remove_tag(Tables.Tags.BUILTIN_INSTALLED); + } + status = new Game.Status(state, this); + } + + public EpicGamesGame.from_db(EpicGames src, Sqlite.Statement s) + { + source = src; + id = Tables.Games.ID.get(s); + name = Tables.Games.NAME.get(s); + info = Tables.Games.INFO.get(s); + info_detailed = Tables.Games.INFO_DETAILED.get(s); + icon = Tables.Games.ICON.get(s); + image = src.legendary_wrapper.get_image(id);//Tables.Games.IMAGE.get(s); + install_dir = Tables.Games.INSTALL_PATH.get(s) != null ? FSUtils.file(Tables.Games.INSTALL_PATH.get(s)) : null; + executable_path = Tables.Games.EXECUTABLE.get(s); + work_dir_path = Tables.Games.WORK_DIR.get(s); + compat_tool = Tables.Games.COMPAT_TOOL.get(s); + compat_tool_settings = Tables.Games.COMPAT_TOOL_SETTINGS.get(s); + arguments = Tables.Games.ARGUMENTS.get(s); + last_launch = Tables.Games.LAST_LAUNCH.get_int64(s); + playtime_source = Tables.Games.PLAYTIME_SOURCE.get_int64(s); + playtime_tracked = Tables.Games.PLAYTIME_TRACKED.get_int64(s); + image_vertical = Tables.Games.IMAGE_VERTICAL.get(s); + + platforms.clear(); + var pls = Tables.Games.PLATFORMS.get(s).split(","); + foreach(var pl in pls) + { + foreach(var p in Platform.PLATFORMS) + { + if(pl == p.id()) + { + platforms.add(p); + break; + } + } + } + installers.add(new EpicGamesGame.EpicGamesInstaller(this, id)); + update_status(); + } + + public override async void install(Runnable.Installer.InstallMode install_mode=Runnable.Installer.InstallMode.INTERACTIVE) + { + new GameHub.UI.Dialogs.InstallDialog(this, installers, install_mode, install.callback); + yield; + update_status(); + } + public override async void uninstall() + { + ((EpicGames)source).legendary_wrapper.uninstall(id); + update_status(); + } + + public override async void run() + { + ((EpicGames)source).legendary_wrapper.run(id); + + } + + public class EpicGamesInstaller: Runnable.Installer + { + public EpicGamesGame game; + public override string name { owned get { return "TEST"; } } + + public EpicGamesInstaller(EpicGamesGame game, string id) + { + this.game = game; + id = id; + platform = Platform.CURRENT; + } + + public override async void install(Runnable runnable, CompatTool? tool=null) + { + EpicGamesGame? game = null; + if(runnable is EpicGamesGame) + { + game = runnable as EpicGamesGame; + } + + EpicGames epic = (EpicGames)(game.source); + + Utils.thread("EpicGamesGame.Installer", () => { + game.status = new Game.Status(Game.State.DOWNLOADING, game, null); + + + epic.legendary_wrapper.install(game.id); + Idle.add(install.callback); + }); + yield; + + if(game != null) game.status = new Game.Status(Game.State.INSTALLED, game, null); + + runnable.update_status(); + + debug("install"); + } + } + } + + + + +} diff --git a/src/data/sources/epicgames/LegendaryWrapper.vala b/src/data/sources/epicgames/LegendaryWrapper.vala new file mode 100644 index 00000000..82db64c2 --- /dev/null +++ b/src/data/sources/epicgames/LegendaryWrapper.vala @@ -0,0 +1,119 @@ +using Gee; + +namespace GameHub.Data.Sources.EpicGames +{ + public struct LegendaryGame { + string name; + string id; + string version; + } + + public class LegendaryWrapper + { + private Regex regex = /\*\s*([^(]*)\s\(App\sname:\s([a-zA-Z0-9]+),\sversion:\s([^)]*)\)/; + + public LegendaryWrapper() + { + } + + public ArrayList getGames() { + var result = new ArrayList(); + + string? line = null; + MatchInfo info; + var output = new DataInputStream(new Subprocess.newv ({"legendary", "list-games"}, STDOUT_PIPE).get_stdout_pipe ()); + + while ((line = output.read_line()) != null) { + if (regex.match (line, 0, out info)) { + LegendaryGame? g = {info.fetch (1), info.fetch (2), info.fetch (3)}; + result.add(g); + } + } + return result; + } + + public string get_image(string id) + { + string res = ""; + var file = File.new_for_path(Environment.get_home_dir () + "/.config/legendary/metadata/"+id+".json"); + + if (file.query_exists ()) { + var dis = new DataInputStream (file.read ()); + string line; + + while ((line = dis.read_line (null)) != null) { + res += line; + } + var parser = new Json.Parser (); + parser.load_from_data (res); + var root_object = parser.get_root().get_object(); + + var metadata = root_object.get_object_member ("metadata"); + var keyImages = metadata.get_array_member ("keyImages"); + var img = keyImages.get_object_element (0).get_string_member ("url"); + return img; + } + return ""; + } + + public void install(string id) + { + // FIXME: It can be done much better + var process = new Subprocess.newv ({"legendary", "download", id}, STDOUT_PIPE | STDIN_PIPE); + var input = new DataOutputStream(process.get_stdin_pipe ()); + var output = new DataInputStream(process.get_stdout_pipe ()); + string? line = null; + input.put_string("y\n"); + while ((line = output.read_line()) != null) { + debug("[EpicGames] %s", line); + } + refresh_installed = true; + } + + public void uninstall(string id) + { + // FIXME: It can be done much better + var process = new Subprocess.newv ({"legendary", "uninstall", id}, STDOUT_PIPE | STDIN_PIPE); + var input = new DataOutputStream(process.get_stdin_pipe ()); + var output = new DataInputStream(process.get_stdout_pipe ()); + string? line = null; + input.put_string("y\n"); + while ((line = output.read_line()) != null) { + debug("[EpicGames] %s", line); + } + refresh_installed = true; + } + + public void run(string id) { + // FIXME: not good idea + new Subprocess.newv ({"legendary", "launch", id}, STDOUT_PIPE); + } + + private bool refresh_installed = true; + private ArrayList _installed = new ArrayList(); + + public bool is_installed(string id) + { + if(refresh_installed) { + build_installed_list(); + refresh_installed = false; + } + return _installed.contains(id); + } + + + private void build_installed_list() + { + var installed_output = new DataInputStream(new Subprocess.newv ({"legendary", "list-installed"}, STDOUT_PIPE).get_stdout_pipe ()); + _installed.clear(); + string? line = null; + MatchInfo info; + while ((line = installed_output.read_line()) != null) { + if (regex.match (line, 0, out info)) { + _installed.add(info.fetch(2)); + } + } + } + + } +} \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 2acdeac9..23bc6db2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -45,6 +45,10 @@ sources = [ 'data/sources/steam/Steam.vala', 'data/sources/steam/SteamGame.vala', + 'data/sources/epicgames/EpicGames.vala', + 'data/sources/epicgames/EpicGamesGame.vala', + 'data/sources/epicgames/LegendaryWrapper.vala', + 'data/sources/gog/GOG.vala', 'data/sources/gog/GOGGame.vala', @@ -104,6 +108,7 @@ sources = [ 'ui/dialogs/SettingsDialog/pages/general/Collection.vala', 'ui/dialogs/SettingsDialog/pages/general/Tweaks.vala', 'ui/dialogs/SettingsDialog/pages/sources/Steam.vala', + 'ui/dialogs/SettingsDialog/pages/sources/EpicGames.vala', 'ui/dialogs/SettingsDialog/pages/sources/GOG.vala', 'ui/dialogs/SettingsDialog/pages/sources/Humble.vala', 'ui/dialogs/SettingsDialog/pages/sources/Itch.vala', diff --git a/src/ui/dialogs/SettingsDialog/SettingsDialog.vala b/src/ui/dialogs/SettingsDialog/SettingsDialog.vala index 2c15973f..b71cf692 100644 --- a/src/ui/dialogs/SettingsDialog/SettingsDialog.vala +++ b/src/ui/dialogs/SettingsDialog/SettingsDialog.vala @@ -89,6 +89,7 @@ namespace GameHub.UI.Dialogs.SettingsDialog #endif add_page("sources/steam", new Pages.Sources.Steam(this)); + add_page("sources/epicgames", new Pages.Sources.EpicGames(this)); add_page("sources/gog", new Pages.Sources.GOG(this)); add_page("sources/humble", new Pages.Sources.Humble(this)); add_page("sources/itch", new Pages.Sources.Itch(this)); diff --git a/src/ui/dialogs/SettingsDialog/pages/sources/EpicGames.vala b/src/ui/dialogs/SettingsDialog/pages/sources/EpicGames.vala new file mode 100644 index 00000000..d7539d66 --- /dev/null +++ b/src/ui/dialogs/SettingsDialog/pages/sources/EpicGames.vala @@ -0,0 +1,52 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub 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. + +GameHub 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 GameHub. If not, see . +*/ + +using Gtk; + +using GameHub.Utils; +using GameHub.UI.Widgets; + +namespace GameHub.UI.Dialogs.SettingsDialog.Pages.Sources +{ + public class EpicGames: SettingsDialogPage + { + public EpicGames(SettingsDialog dlg) + { + Object( + dialog: dlg, + title: "EpicGames", + description: _("Disabled"), + icon_name: "source-epicgames-symbolic", + activatable: true + ); + status = description; + } + + construct + { + var paths = FSUtils.Paths.Settings.instance; + + update(); + } + + private void update() + { + } + + } +} diff --git a/src/ui/views/WelcomeView.vala b/src/ui/views/WelcomeView.vala index 1029a4ab..e1271bb6 100644 --- a/src/ui/views/WelcomeView.vala +++ b/src/ui/views/WelcomeView.vala @@ -96,6 +96,7 @@ namespace GameHub.UI.Views foreach(var src in GameSources) { + debug("[GS] %s", src.icon); welcome.append(src.icon, src.name, ""); } @@ -126,12 +127,11 @@ namespace GameHub.UI.Views var src = GameSources[index]; var btn = welcome.get_button_from_index(index); - welcome.set_item_visible(index, !(src is Sources.Humble.Trove) && !(src is Sources.User.User) && src.enabled); if(src is Sources.Humble.Trove || !src.enabled) continue; enabled_sources++; - + debug("[GS] %s %s", src.name, src.is_installed(true).to_string()); if(src.is_installed(true)) { btn.title = src.name;