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