aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Common/Updates
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Common/Updates')
-rw-r--r--MediaBrowser.Common/Updates/GithubUpdater.cs278
-rw-r--r--MediaBrowser.Common/Updates/IInstallationManager.cs121
-rw-r--r--MediaBrowser.Common/Updates/InstallationEventArgs.cs11
-rw-r--r--MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs9
4 files changed, 419 insertions, 0 deletions
diff --git a/MediaBrowser.Common/Updates/GithubUpdater.cs b/MediaBrowser.Common/Updates/GithubUpdater.cs
new file mode 100644
index 000000000..4275799a9
--- /dev/null
+++ b/MediaBrowser.Common/Updates/GithubUpdater.cs
@@ -0,0 +1,278 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Updates;
+
+namespace MediaBrowser.Common.Updates
+{
+ public class GithubUpdater
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly IJsonSerializer _jsonSerializer;
+
+ public GithubUpdater(IHttpClient httpClient, IJsonSerializer jsonSerializer)
+ {
+ _httpClient = httpClient;
+ _jsonSerializer = jsonSerializer;
+ }
+
+ public async Task<CheckForUpdateResult> CheckForUpdateResult(string organzation, string repository, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename, TimeSpan cacheLength, CancellationToken cancellationToken)
+ {
+ var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
+
+ var options = new HttpRequestOptions
+ {
+ Url = url,
+ EnableKeepAlive = false,
+ CancellationToken = cancellationToken,
+ UserAgent = "Emby/3.0",
+ BufferContent = false
+ };
+
+ if (cacheLength.Ticks > 0)
+ {
+ options.CacheMode = CacheMode.Unconditional;
+ options.CacheLength = cacheLength;
+ }
+
+ using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
+ {
+ using (var stream = response.Content)
+ {
+ var obj = _jsonSerializer.DeserializeFromStream<RootObject[]>(stream);
+
+ return CheckForUpdateResult(obj, minVersion, updateLevel, assetFilename, packageName, targetFilename);
+ }
+ }
+ }
+
+ private CheckForUpdateResult CheckForUpdateResult(RootObject[] obj, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename)
+ {
+ if (updateLevel == PackageVersionClass.Release)
+ {
+ // Technically all we need to do is check that it's not pre-release
+ // But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
+ obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
+ }
+ else if (updateLevel == PackageVersionClass.Beta)
+ {
+ obj = obj.Where(i => i.prerelease && i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase)).ToArray();
+ }
+ else if (updateLevel == PackageVersionClass.Dev)
+ {
+ obj = obj.Where(i => !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) || i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
+ }
+
+ var availableUpdate = obj
+ .Select(i => CheckForUpdateResult(i, minVersion, assetFilename, packageName, targetFilename))
+ .Where(i => i != null)
+ .OrderByDescending(i => Version.Parse(i.AvailableVersion))
+ .FirstOrDefault();
+
+ return availableUpdate ?? new CheckForUpdateResult
+ {
+ IsUpdateAvailable = false
+ };
+ }
+
+ private bool MatchesUpdateLevel(RootObject i, PackageVersionClass updateLevel)
+ {
+ if (updateLevel == PackageVersionClass.Beta)
+ {
+ return i.prerelease && i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase);
+ }
+ if (updateLevel == PackageVersionClass.Dev)
+ {
+ return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) ||
+ i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Technically all we need to do is check that it's not pre-release
+ // But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
+ return !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) &&
+ !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
+ }
+
+ public async Task<List<RootObject>> GetLatestReleases(string organzation, string repository, string assetFilename, CancellationToken cancellationToken)
+ {
+ var list = new List<RootObject>();
+
+ var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
+
+ var options = new HttpRequestOptions
+ {
+ Url = url,
+ EnableKeepAlive = false,
+ CancellationToken = cancellationToken,
+ UserAgent = "Emby/3.0",
+ BufferContent = false
+ };
+
+ using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
+ {
+ using (var stream = response.Content)
+ {
+ var obj = _jsonSerializer.DeserializeFromStream<RootObject[]>(stream);
+
+ obj = obj.Where(i => (i.assets ?? new List<Asset>()).Any(a => IsAsset(a, assetFilename, i.tag_name))).ToArray();
+
+ list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Release)).OrderByDescending(GetVersion).Take(1));
+ list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Beta)).OrderByDescending(GetVersion).Take(1));
+ list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Dev)).OrderByDescending(GetVersion).Take(1));
+
+ return list;
+ }
+ }
+ }
+
+ public Version GetVersion(RootObject obj)
+ {
+ Version version;
+ if (!Version.TryParse(obj.tag_name, out version))
+ {
+ return new Version(1, 0);
+ }
+
+ return version;
+ }
+
+ private CheckForUpdateResult CheckForUpdateResult(RootObject obj, Version minVersion, string assetFilename, string packageName, string targetFilename)
+ {
+ Version version;
+ var versionString = obj.tag_name;
+ if (!Version.TryParse(versionString, out version))
+ {
+ return null;
+ }
+
+ if (version < minVersion)
+ {
+ return null;
+ }
+
+ var asset = (obj.assets ?? new List<Asset>()).FirstOrDefault(i => IsAsset(i, assetFilename, versionString));
+
+ if (asset == null)
+ {
+ return null;
+ }
+
+ return new CheckForUpdateResult
+ {
+ AvailableVersion = version.ToString(),
+ IsUpdateAvailable = version > minVersion,
+ Package = new PackageVersionInfo
+ {
+ classification = obj.prerelease ?
+ (obj.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase) ? PackageVersionClass.Dev : PackageVersionClass.Beta) :
+ PackageVersionClass.Release,
+ name = packageName,
+ sourceUrl = asset.browser_download_url,
+ targetFilename = targetFilename,
+ versionStr = version.ToString(),
+ requiredVersionStr = "1.0.0",
+ description = obj.body,
+ infoUrl = obj.html_url
+ }
+ };
+ }
+
+ private bool IsAsset(Asset asset, string assetFilename, string version)
+ {
+ var downloadFilename = Path.GetFileName(asset.browser_download_url) ?? string.Empty;
+
+ assetFilename = assetFilename.Replace("{version}", version);
+
+ if (downloadFilename.IndexOf(assetFilename, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return true;
+ }
+
+ return string.Equals(assetFilename, downloadFilename, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public class Uploader
+ {
+ public string login { get; set; }
+ public int id { get; set; }
+ public string avatar_url { get; set; }
+ public string gravatar_id { get; set; }
+ public string url { get; set; }
+ public string html_url { get; set; }
+ public string followers_url { get; set; }
+ public string following_url { get; set; }
+ public string gists_url { get; set; }
+ public string starred_url { get; set; }
+ public string subscriptions_url { get; set; }
+ public string organizations_url { get; set; }
+ public string repos_url { get; set; }
+ public string events_url { get; set; }
+ public string received_events_url { get; set; }
+ public string type { get; set; }
+ public bool site_admin { get; set; }
+ }
+
+ public class Asset
+ {
+ public string url { get; set; }
+ public int id { get; set; }
+ public string name { get; set; }
+ public object label { get; set; }
+ public Uploader uploader { get; set; }
+ public string content_type { get; set; }
+ public string state { get; set; }
+ public int size { get; set; }
+ public int download_count { get; set; }
+ public string created_at { get; set; }
+ public string updated_at { get; set; }
+ public string browser_download_url { get; set; }
+ }
+
+ public class Author
+ {
+ public string login { get; set; }
+ public int id { get; set; }
+ public string avatar_url { get; set; }
+ public string gravatar_id { get; set; }
+ public string url { get; set; }
+ public string html_url { get; set; }
+ public string followers_url { get; set; }
+ public string following_url { get; set; }
+ public string gists_url { get; set; }
+ public string starred_url { get; set; }
+ public string subscriptions_url { get; set; }
+ public string organizations_url { get; set; }
+ public string repos_url { get; set; }
+ public string events_url { get; set; }
+ public string received_events_url { get; set; }
+ public string type { get; set; }
+ public bool site_admin { get; set; }
+ }
+
+ public class RootObject
+ {
+ public string url { get; set; }
+ public string assets_url { get; set; }
+ public string upload_url { get; set; }
+ public string html_url { get; set; }
+ public int id { get; set; }
+ public string tag_name { get; set; }
+ public string target_commitish { get; set; }
+ public string name { get; set; }
+ public bool draft { get; set; }
+ public Author author { get; set; }
+ public bool prerelease { get; set; }
+ public string created_at { get; set; }
+ public string published_at { get; set; }
+ public List<Asset> assets { get; set; }
+ public string tarball_url { get; set; }
+ public string zipball_url { get; set; }
+ public string body { get; set; }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs
new file mode 100644
index 000000000..dab38b27c
--- /dev/null
+++ b/MediaBrowser.Common/Updates/IInstallationManager.cs
@@ -0,0 +1,121 @@
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Updates;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Common.Updates
+{
+ public interface IInstallationManager : IDisposable
+ {
+ event EventHandler<InstallationEventArgs> PackageInstalling;
+ event EventHandler<InstallationEventArgs> PackageInstallationCompleted;
+ event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed;
+ event EventHandler<InstallationEventArgs> PackageInstallationCancelled;
+
+ /// <summary>
+ /// The current installations
+ /// </summary>
+ List<Tuple<InstallationInfo, CancellationTokenSource>> CurrentInstallations { get; set; }
+
+ /// <summary>
+ /// The completed installations
+ /// </summary>
+ IEnumerable<InstallationInfo> CompletedInstallations { get; }
+
+ /// <summary>
+ /// Occurs when [plugin uninstalled].
+ /// </summary>
+ event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
+
+ /// <summary>
+ /// Occurs when [plugin updated].
+ /// </summary>
+ event EventHandler<GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>>> PluginUpdated;
+
+ /// <summary>
+ /// Occurs when [plugin updated].
+ /// </summary>
+ event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
+
+ /// <summary>
+ /// Gets all available packages.
+ /// </summary>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="withRegistration">if set to <c>true</c> [with registration].</param>
+ /// <param name="packageType">Type of the package.</param>
+ /// <param name="applicationVersion">The application version.</param>
+ /// <returns>Task{List{PackageInfo}}.</returns>
+ Task<List<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken,
+ bool withRegistration = true,
+ string packageType = null,
+ Version applicationVersion = null);
+
+ /// <summary>
+ /// Gets all available packages from a static resource.
+ /// </summary>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{List{PackageInfo}}.</returns>
+ Task<List<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the package.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="guid">The assembly guid</param>
+ /// <param name="classification">The classification.</param>
+ /// <param name="version">The version.</param>
+ /// <returns>Task{PackageVersionInfo}.</returns>
+ Task<PackageVersionInfo> GetPackage(string name, string guid, PackageVersionClass classification, Version version);
+
+ /// <summary>
+ /// Gets the latest compatible version.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="guid">The assembly guid</param>
+ /// <param name="currentServerVersion">The current server version.</param>
+ /// <param name="classification">The classification.</param>
+ /// <returns>Task{PackageVersionInfo}.</returns>
+ Task<PackageVersionInfo> GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release);
+
+ /// <summary>
+ /// Gets the latest compatible version.
+ /// </summary>
+ /// <param name="availablePackages">The available packages.</param>
+ /// <param name="name">The name.</param>
+ /// <param name="guid">The assembly guid</param>
+ /// <param name="currentServerVersion">The current server version.</param>
+ /// <param name="classification">The classification.</param>
+ /// <returns>PackageVersionInfo.</returns>
+ PackageVersionInfo GetLatestCompatibleVersion(IEnumerable<PackageInfo> availablePackages, string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release);
+
+ /// <summary>
+ /// Gets the available plugin updates.
+ /// </summary>
+ /// <param name="applicationVersion">The current server version.</param>
+ /// <param name="withAutoUpdateEnabled">if set to <c>true</c> [with auto update enabled].</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{IEnumerable{PackageVersionInfo}}.</returns>
+ Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(Version applicationVersion, bool withAutoUpdateEnabled, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Installs the package.
+ /// </summary>
+ /// <param name="package">The package.</param>
+ /// <param name="isPlugin">if set to <c>true</c> [is plugin].</param>
+ /// <param name="progress">The progress.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="System.ArgumentNullException">package</exception>
+ Task InstallPackage(PackageVersionInfo package, bool isPlugin, IProgress<double> progress, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Uninstalls a plugin
+ /// </summary>
+ /// <param name="plugin">The plugin.</param>
+ /// <exception cref="System.ArgumentException"></exception>
+ void UninstallPlugin(IPlugin plugin);
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs
new file mode 100644
index 000000000..9dc8ead83
--- /dev/null
+++ b/MediaBrowser.Common/Updates/InstallationEventArgs.cs
@@ -0,0 +1,11 @@
+using MediaBrowser.Model.Updates;
+
+namespace MediaBrowser.Common.Updates
+{
+ public class InstallationEventArgs
+ {
+ public InstallationInfo InstallationInfo { get; set; }
+
+ public PackageVersionInfo PackageVersionInfo { get; set; }
+ }
+}
diff --git a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs
new file mode 100644
index 000000000..69dc1ee98
--- /dev/null
+++ b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace MediaBrowser.Common.Updates
+{
+ public class InstallationFailedEventArgs : InstallationEventArgs
+ {
+ public Exception Exception { get; set; }
+ }
+} \ No newline at end of file