From da1d7f64d6fa4dba8822cbfcafebd1aa98d362c5 Mon Sep 17 00:00:00 2001 From: erri120 Date: Thu, 13 Aug 2020 18:16:51 +0200 Subject: [PATCH] Created ExtensionUpdater --- DLSiteMetadata/DLSiteMetadata.csproj | 6 +- DLSiteMetadata/extension.yaml | 5 +- ExtensionUpdater/DTO.cs | 41 +++++ ExtensionUpdater/ExtensionUpdater.csproj | 84 +++++++++ ExtensionUpdater/ExtensionUpdaterPlugin.cs | 192 ++++++++++++++++++++ ExtensionUpdater/GitHub.cs | 138 ++++++++++++++ ExtensionUpdater/Properties/AssemblyInfo.cs | 35 ++++ ExtensionUpdater/YamlUtils.cs | 33 ++++ ExtensionUpdater/extension.yaml | 9 + ExtensionUpdater/packages.config | 6 + F95ZoneMetadata/F95ZoneMetadata.csproj | 6 +- F95ZoneMetadata/extension.yaml | 5 +- JastusaMetadata/JastusaMetadata.csproj | 2 +- JastusaMetadata/extension.yaml | 5 +- Playnite.Extensions.sln | 6 + VNDBMetadata/VNDBMetadata.csproj | 6 +- VNDBMetadata/extension.yaml | 5 +- scripts/xcopy-files.bat | 2 +- 18 files changed, 571 insertions(+), 15 deletions(-) create mode 100644 ExtensionUpdater/DTO.cs create mode 100644 ExtensionUpdater/ExtensionUpdater.csproj create mode 100644 ExtensionUpdater/ExtensionUpdaterPlugin.cs create mode 100644 ExtensionUpdater/GitHub.cs create mode 100644 ExtensionUpdater/Properties/AssemblyInfo.cs create mode 100644 ExtensionUpdater/YamlUtils.cs create mode 100644 ExtensionUpdater/extension.yaml create mode 100644 ExtensionUpdater/packages.config diff --git a/DLSiteMetadata/DLSiteMetadata.csproj b/DLSiteMetadata/DLSiteMetadata.csproj index ec0cfaa..edb3c22 100644 --- a/DLSiteMetadata/DLSiteMetadata.csproj +++ b/DLSiteMetadata/DLSiteMetadata.csproj @@ -61,9 +61,9 @@ - - PreserveNewest - + + Always + diff --git a/DLSiteMetadata/extension.yaml b/DLSiteMetadata/extension.yaml index 67a6a81..1f8e5c3 100644 --- a/DLSiteMetadata/extension.yaml +++ b/DLSiteMetadata/extension.yaml @@ -3,4 +3,7 @@ Author: erri120 Version: 1.4.0 Module: DLSiteMetadata.dll Type: MetadataProvider -Icon: icon.png \ No newline at end of file +Icon: icon.png +UpdaterConfig: + GitHubUser: erri120 + GitHubRepo: Playnite.Extensions \ No newline at end of file diff --git a/ExtensionUpdater/DTO.cs b/ExtensionUpdater/DTO.cs new file mode 100644 index 0000000..18b2c7e --- /dev/null +++ b/ExtensionUpdater/DTO.cs @@ -0,0 +1,41 @@ +// /* +// Copyright (C) 2020 erri120 +// +// 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 . +// */ + +using System; + +namespace ExtensionUpdater +{ + [Serializable] + public class PlayniteExtensionConfig + { + public string Name { get; set; } + public string Author { get; set; } + public string Version { get; set; } + public string Module { get; set; } + public string Type { get; set; } + public string Icon { get; set; } + + public UpdaterConfig UpdaterConfig { get; set; } + } + + [Serializable] + public class UpdaterConfig + { + public string GitHubUser { get; set; } + public string GitHubRepo { get; set; } + } +} \ No newline at end of file diff --git a/ExtensionUpdater/ExtensionUpdater.csproj b/ExtensionUpdater/ExtensionUpdater.csproj new file mode 100644 index 0000000..8a63ec4 --- /dev/null +++ b/ExtensionUpdater/ExtensionUpdater.csproj @@ -0,0 +1,84 @@ + + + + + Debug + AnyCPU + {AC48FFD8-57E6-47FF-B728-270E3F0B1F37} + Library + Properties + ExtensionUpdater + ExtensionUpdater + v4.6.2 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\PlayniteSDK.5.2.0\lib\net462\Playnite.SDK.dll + True + + + + + + + + + ..\packages\YamlDotNet.8.1.2\lib\net45\YamlDotNet.dll + True + + + + + + + + + + + + + + + Always + + + + + {5ac34e06-706a-4706-84d4-510aa345d7fc} + Extensions.Common + + + + + + diff --git a/ExtensionUpdater/ExtensionUpdaterPlugin.cs b/ExtensionUpdater/ExtensionUpdaterPlugin.cs new file mode 100644 index 0000000..63b48a5 --- /dev/null +++ b/ExtensionUpdater/ExtensionUpdaterPlugin.cs @@ -0,0 +1,192 @@ +// /* +// Copyright (C) 2020 erri120 +// +// 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 . +// */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Extensions.Common; +using Playnite.SDK; +using Playnite.SDK.Plugins; + +namespace ExtensionUpdater +{ + public class ExtensionUpdaterPlugin : Plugin + { + private readonly IPlayniteAPI _playniteAPI; + private readonly ILogger _logger; + private readonly string _extensionsDirectory; + private readonly CancellationTokenSource _source; + private readonly CancellationToken _token; + + public ExtensionUpdaterPlugin(IPlayniteAPI playniteAPI) : base(playniteAPI) + { + _playniteAPI = playniteAPI; + _logger = playniteAPI.CreateLogger(); + + var applicationPath = _playniteAPI.Paths.ApplicationPath; + var extensionsPath = Path.Combine(applicationPath, "Extensions"); + if (!Directory.Exists(extensionsPath)) + { + _logger.Error($"Directory {extensionsPath} does not exist!"); + } + else + { + _extensionsDirectory = extensionsPath; + } + + _source = new CancellationTokenSource(); + _token = _source.Token; + } + + public override Guid Id { get; } = Guid.Parse("f4a74d1b-44c0-4ea5-a935-2b994a638236"); + + public override void Dispose() + { + _source.Dispose(); + base.Dispose(); + } + + public override void OnApplicationStopped() + { + _source.Cancel(); + } + + public override void OnApplicationStarted() + { + Task.Run(() => + { + if (_extensionsDirectory == null) + { + return; + } + + if (_playniteAPI.ApplicationInfo.InOfflineMode) + { + _logger.Info("Application is in offline mode, skipping extension update check"); + return; + } + + IEnumerable> configs = Directory + .EnumerateFiles(_extensionsDirectory, "*.yaml", SearchOption.AllDirectories) + .Select(file => + { + try + { + var config = YamlUtils.FromYaml(file); + return config?.UpdaterConfig == null ? null : config; + } + catch (Exception e) + { + _logger.Error(e, $"Exception while trying to deserialize {file}!\n"); + } + + return null; + }).NotNull().GroupBy(config => + { + var repo = $"{config.UpdaterConfig.GitHubUser}/{config.UpdaterConfig.GitHubRepo}"; + return repo; + }); + + configs.Do(async group => + { + var repo = group.Key; + _logger.Info($"Found: {repo}"); + + List releases = + await GitHub.GetGitHubReleases(repo); + + if (releases == null || releases.Count == 0) + { + _logger.Error($"Found no releases for {repo}"); + return; + } + + var latest = releases[0]; + var sLatestVersion = latest.tag_name; + + if (sLatestVersion[0] == 'v') + { + sLatestVersion = sLatestVersion.Substring(1); + } + + var canParseLatest = Version.TryParse(sLatestVersion, out var latestVersion); + List> shouldUpdate = group.Select(config => + { + var canParseCurrent = Version.TryParse(config.Version, out var currentVersion); + + if (canParseCurrent) + { + if (canParseLatest) + { + if (currentVersion < latestVersion) + { + _logger.Info($"There is a new version for {config.Name}: {latestVersion}"); + return new Tuple(config, true); + } + + if (currentVersion != latestVersion) + return new Tuple(config, false); + + _logger.Info($"{config.Name} is up-to-date"); + return new Tuple(config, false); + } + + if (config.Version.Equals(sLatestVersion, StringComparison.OrdinalIgnoreCase)) + { + _logger.Info($"{config.Name} is up-to-date"); + return new Tuple(config, false); + } + + _logger.Info($"There is a new version for {config.Name}: {sLatestVersion}"); + return new Tuple(config, true); + } + + if (config.Version.Equals(sLatestVersion, StringComparison.OrdinalIgnoreCase)) + { + _logger.Info($"{config.Name} is up-to-date"); + return new Tuple(config, false); + } + + _logger.Info($"There is a new version for {config.Name}: {sLatestVersion}"); + return new Tuple(config, true); + }).ToList(); + + if (shouldUpdate.Any(x => x.Item2)) + { + var extensionsNameString = shouldUpdate.Select(x => x.Item1.Name) + .Aggregate((x, y) => $"{x},{y}"); + var message = $"The following Extension(s) can be updated: {extensionsNameString}. Click this message to download the release."; + + _playniteAPI.Notifications.Add(new NotificationMessage(repo, message, NotificationType.Info, + () => + { + Process.Start(latest.html_url); + })); + } + else + { + _logger.Info($"No Update available for {repo}"); + } + }); + }, _token); + } + } +} \ No newline at end of file diff --git a/ExtensionUpdater/GitHub.cs b/ExtensionUpdater/GitHub.cs new file mode 100644 index 0000000..094bd98 --- /dev/null +++ b/ExtensionUpdater/GitHub.cs @@ -0,0 +1,138 @@ +// /* +// Copyright (C) 2020 erri120 +// +// 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 . +// */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Extensions.Common; + +namespace ExtensionUpdater +{ + [Serializable] + public class GitHubRelease + { + public string name; + public string tag_name; + public string html_url; + public List assets; + } + + [Serializable] + public class GitHubReleaseAsset + { + public string browser_download_url; + public string name; + public long size; + } + + public class GitHubClient + { + private readonly HttpClient _client; + private readonly HttpClientHandler _handler; + + public GitHubClient() + { + _handler = new HttpClientHandler + { + AllowAutoRedirect = true, + }; + _client = new HttpClient(_handler); + } + + public async Task GetStringAsync(string url) + { + var result = await GetAsync(url, HttpCompletionOption.ResponseContentRead); + + if (result.Content == null) + throw new Exception($"Content for {url} is null!"); + + return await result.Content.ReadAsStringAsync(); + } + + public async Task GetAsync(string url, HttpCompletionOption responseHeadersRead = HttpCompletionOption.ResponseHeadersRead) + { + var msg = new HttpRequestMessage(HttpMethod.Get, url); + msg.Headers.Add("user-agent", "erri120.ExtensionUpdaterPlugin"); + + var result = await _client.SendAsync(msg, responseHeadersRead); + try + { + result.EnsureSuccessStatusCode(); + } + catch (HttpRequestException requestException) + { + throw new Exception($"HttpRequestException trying to access {url}", requestException); + } + + return result; + } + } + + public static class GitHub + { + private static readonly GitHubClient Client; + + static GitHub() + { + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; + Client = new GitHubClient(); + } + + public static async Task> GetGitHubReleases(string repo) + { + //https://api.github.com/repos/bbepis/XUnity.AutoTranslator/releases + var url = $"https://api.github.com/repos/{repo}/releases"; + + var result = await Client.GetStringAsync(url); + List releases; + + try + { + releases = result.FromJson>(); + } + catch (Exception e) + { + throw new Exception($"Unable to serialize content from {url}\n {result}", e); + } + + return releases; + } + + public static async Task DownloadGitHubReleaseAsset(GitHubReleaseAsset asset, string output) + { + if(File.Exists(output)) + File.Delete(output); + + if(asset.browser_download_url == null) + throw new Exception($"URL for asset {asset.name} is null!"); + + var result = await Client.GetAsync(asset.browser_download_url, HttpCompletionOption.ResponseContentRead); + long? contentLength = result.Content.Headers.ContentLength; + if(contentLength == null) + throw new Exception($"Content length for {asset.browser_download_url} ({asset.name}) is null!"); + + using (var fs = File.OpenWrite(output)) + { + await result.Content.CopyToAsync(fs); + result.Content.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/ExtensionUpdater/Properties/AssemblyInfo.cs b/ExtensionUpdater/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9e6f7d2 --- /dev/null +++ b/ExtensionUpdater/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ExtensionUpdater")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ExtensionUpdater")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("AC48FFD8-57E6-47FF-B728-270E3F0B1F37")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/ExtensionUpdater/YamlUtils.cs b/ExtensionUpdater/YamlUtils.cs new file mode 100644 index 0000000..10a26d9 --- /dev/null +++ b/ExtensionUpdater/YamlUtils.cs @@ -0,0 +1,33 @@ +// /* +// Copyright (C) 2020 erri120 +// +// 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 . +// */ + +using System.IO; +using YamlDotNet.Serialization; + +namespace ExtensionUpdater +{ + public static class YamlUtils + { + public static T FromYaml(string file) + { + var contents = File.ReadAllText(file); + var deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build(); + var res = deserializer.Deserialize(contents); + return res; + } + } +} \ No newline at end of file diff --git a/ExtensionUpdater/extension.yaml b/ExtensionUpdater/extension.yaml new file mode 100644 index 0000000..58dbdd5 --- /dev/null +++ b/ExtensionUpdater/extension.yaml @@ -0,0 +1,9 @@ +Name: Extension Updater +Author: erri120 +Version: 1.4.0 +Module: ExtensionUpdater.dll +Type: GenericPlugin +Icon: icon.png +UpdaterConfig: + GitHubUser: erri120 + GitHubRepo: Playnite.Extensions \ No newline at end of file diff --git a/ExtensionUpdater/packages.config b/ExtensionUpdater/packages.config new file mode 100644 index 0000000..040a43c --- /dev/null +++ b/ExtensionUpdater/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/F95ZoneMetadata/F95ZoneMetadata.csproj b/F95ZoneMetadata/F95ZoneMetadata.csproj index 6b3b676..d20a22c 100644 --- a/F95ZoneMetadata/F95ZoneMetadata.csproj +++ b/F95ZoneMetadata/F95ZoneMetadata.csproj @@ -56,9 +56,9 @@ - - PreserveNewest - + + Always + diff --git a/F95ZoneMetadata/extension.yaml b/F95ZoneMetadata/extension.yaml index e553b7e..14abb9d 100644 --- a/F95ZoneMetadata/extension.yaml +++ b/F95ZoneMetadata/extension.yaml @@ -3,4 +3,7 @@ Author: erri120 Version: 1.4.0 Module: F95ZoneMetadata.dll Type: MetadataProvider -Icon: icon.png \ No newline at end of file +Icon: icon.png +UpdaterConfig: + GitHubUser: erri120 + GitHubRepo: Playnite.Extensions \ No newline at end of file diff --git a/JastusaMetadata/JastusaMetadata.csproj b/JastusaMetadata/JastusaMetadata.csproj index 9fd74c7..73c2e6a 100644 --- a/JastusaMetadata/JastusaMetadata.csproj +++ b/JastusaMetadata/JastusaMetadata.csproj @@ -73,7 +73,7 @@ - PreserveNewest + Always PreserveNewest diff --git a/JastusaMetadata/extension.yaml b/JastusaMetadata/extension.yaml index feea59e..7c43de7 100644 --- a/JastusaMetadata/extension.yaml +++ b/JastusaMetadata/extension.yaml @@ -3,4 +3,7 @@ Author: erri120 Version: 1.4.0 Module: JastusaMetadata.dll Type: MetadataProvider -Icon: icon.png \ No newline at end of file +Icon: icon.png +UpdaterConfig: + GitHubUser: erri120 + GitHubRepo: Playnite.Extensions \ No newline at end of file diff --git a/Playnite.Extensions.sln b/Playnite.Extensions.sln index a4e2f5e..8bd0d8d 100644 --- a/Playnite.Extensions.sln +++ b/Playnite.Extensions.sln @@ -26,6 +26,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnityAutoTranslatorEmulato EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JastusaMetadata", "JastusaMetadata\JastusaMetadata.csproj", "{124FF354-ADB7-4B0F-9DC0-B3C6FDF41017}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionUpdater", "ExtensionUpdater\ExtensionUpdater.csproj", "{AC48FFD8-57E6-47FF-B728-270E3F0B1F37}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,6 +66,10 @@ Global {124FF354-ADB7-4B0F-9DC0-B3C6FDF41017}.Debug|Any CPU.Build.0 = Debug|Any CPU {124FF354-ADB7-4B0F-9DC0-B3C6FDF41017}.Release|Any CPU.ActiveCfg = Release|Any CPU {124FF354-ADB7-4B0F-9DC0-B3C6FDF41017}.Release|Any CPU.Build.0 = Release|Any CPU + {AC48FFD8-57E6-47FF-B728-270E3F0B1F37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC48FFD8-57E6-47FF-B728-270E3F0B1F37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC48FFD8-57E6-47FF-B728-270E3F0B1F37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC48FFD8-57E6-47FF-B728-270E3F0B1F37}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/VNDBMetadata/VNDBMetadata.csproj b/VNDBMetadata/VNDBMetadata.csproj index c45765f..c7ef0c1 100644 --- a/VNDBMetadata/VNDBMetadata.csproj +++ b/VNDBMetadata/VNDBMetadata.csproj @@ -58,9 +58,9 @@ - - PreserveNewest - + + Always + diff --git a/VNDBMetadata/extension.yaml b/VNDBMetadata/extension.yaml index f1fe846..daf2fae 100644 --- a/VNDBMetadata/extension.yaml +++ b/VNDBMetadata/extension.yaml @@ -3,4 +3,7 @@ Author: erri120 Version: 1.4.0 Module: VNDBMetadata.dll Type: MetadataProvider -Icon: icon.png \ No newline at end of file +Icon: icon.png +UpdaterConfig: + GitHubUser: erri120 + GitHubRepo: Playnite.Extensions \ No newline at end of file diff --git a/scripts/xcopy-files.bat b/scripts/xcopy-files.bat index e41780e..864ef6f 100644 --- a/scripts/xcopy-files.bat +++ b/scripts/xcopy-files.bat @@ -7,7 +7,7 @@ echo %outputFolder% set len=6 set obj[0]=DLSiteMetadata -set obj[1]=ExtensionsUpdater +set obj[1]=ExtensionUpdater set obj[2]=F95ZoneMetadata set obj[3]=JastusaMetadata set obj[4]=VNDBMetadata