From 868a7ce9c8b50bd64c8b5ae33fec77abfd25ef7c Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Thu, 21 Feb 2013 23:23:06 -0500 Subject: isolated clickonce dependancies --- MediaBrowser.Api/EnvironmentService.cs | 1 + MediaBrowser.Api/MediaBrowser.Api.csproj | 9 +- MediaBrowser.Api/PackageService.cs | 13 +- .../ScheduledTasks/ScheduledTaskService.cs | 176 ++++++ .../ScheduledTasksWebSocketListener.cs | 49 ++ .../WebSocket/LogFileWebSocketListener.cs | 146 +++++ .../WebSocket/SystemInfoWebSocketListener.cs | 45 ++ MediaBrowser.ClickOnce/ApplicationUpdateCheck.cs | 106 ++++ MediaBrowser.ClickOnce/ApplicationUpdater.cs | 92 +++ MediaBrowser.ClickOnce/ClickOnceHelper.cs | 255 ++++++++ .../MediaBrowser.ClickOnce.csproj | 62 ++ MediaBrowser.ClickOnce/Properties/AssemblyInfo.cs | 36 ++ .../Api/Logging/LogFileWebSocketListener.cs | 146 ----- .../Api/ScheduledTasks/ScheduledTaskService.cs | 176 ------ .../ScheduledTasksWebSocketListener.cs | 49 -- .../Api/SystemInfoWebSocketListener.cs | 44 -- MediaBrowser.Common/Extensions/BaseExtensions.cs | 19 - MediaBrowser.Common/Kernel/BaseKernel.cs | 19 +- MediaBrowser.Common/Kernel/IApplicationHost.cs | 30 +- MediaBrowser.Common/Kernel/TcpManager.cs | 11 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 17 +- MediaBrowser.Common/Net/HttpServer.cs | 15 +- MediaBrowser.Common/Net/NetworkShares.cs | 644 --------------------- MediaBrowser.Common/Net/StaticResult.cs | 14 - MediaBrowser.Common/Resources/Images/Icon.ico | Bin 149571 -> 0 bytes .../ScheduledTasks/BaseScheduledTask.cs | 8 +- .../ScheduledTasks/IScheduledTask.cs | 2 +- .../ScheduledTasks/ScheduledTaskHelpers.cs | 2 +- .../ScheduledTasks/Tasks/DeleteCacheFileTask.cs | 8 +- .../ScheduledTasks/Tasks/DeleteLogFileTask.cs | 6 +- .../ScheduledTasks/Tasks/ReloadLoggerTask.cs | 4 +- .../ScheduledTasks/Tasks/SystemUpdateTask.cs | 42 +- .../Updates/ApplicationUpdateCheck.cs | 92 --- MediaBrowser.Common/Updates/ApplicationUpdater.cs | 93 --- MediaBrowser.Common/Updates/ClickOnceHelper.cs | 217 ------- .../Entities/CollectionFolder.cs | 2 +- MediaBrowser.Controller/Entities/Folder.cs | 18 +- MediaBrowser.Controller/Entities/User.cs | 6 +- MediaBrowser.Controller/IO/NetworkShares.cs | 644 +++++++++++++++++++++ MediaBrowser.Controller/Library/LibraryManager.cs | 14 +- .../MediaBrowser.Controller.csproj | 2 +- .../ScheduledTasks/ChapterImagesTask.cs | 4 +- .../ScheduledTasks/ImageCleanupTask.cs | 4 +- .../ScheduledTasks/PeopleValidationTask.cs | 2 +- .../ScheduledTasks/PluginUpdateTask.cs | 10 +- .../ScheduledTasks/RefreshMediaLibraryTask.cs | 4 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- MediaBrowser.Model/Tasks/TaskInfo.cs | 2 +- MediaBrowser.Model/Tasks/TaskProgress.cs | 22 - MediaBrowser.Model/Updates/CheckForUpdateResult.cs | 22 + .../ScheduledTasks/CurrentTrailerDownloadTask.cs | 8 +- .../MediaBrowser.Server.Uninstall.csproj | 6 +- MediaBrowser.Server.Uninstall/Program.cs | 2 +- MediaBrowser.ServerApplication/App.xaml.cs | 56 +- .../LibraryExplorer.xaml.cs | 2 +- .../MediaBrowser.ServerApplication.csproj | 5 +- .../MediaBrowser.UI.Uninstall.csproj | 6 +- MediaBrowser.UI.Uninstall/Program.cs | 2 +- MediaBrowser.UI.sln | 12 + MediaBrowser.UI/App.xaml.cs | 62 +- MediaBrowser.UI/Extensions/Extensions.cs | 26 + MediaBrowser.UI/MainWindow.xaml.cs | 1 + MediaBrowser.UI/MediaBrowser.UI.csproj | 6 +- .../Html/scripts/DashboardPage.js | 4 +- .../Html/scripts/ScheduledTasksPage.js | 3 +- MediaBrowser.sln | 16 + 66 files changed, 1942 insertions(+), 1681 deletions(-) create mode 100644 MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs create mode 100644 MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs create mode 100644 MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs create mode 100644 MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs create mode 100644 MediaBrowser.ClickOnce/ApplicationUpdateCheck.cs create mode 100644 MediaBrowser.ClickOnce/ApplicationUpdater.cs create mode 100644 MediaBrowser.ClickOnce/ClickOnceHelper.cs create mode 100644 MediaBrowser.ClickOnce/MediaBrowser.ClickOnce.csproj create mode 100644 MediaBrowser.ClickOnce/Properties/AssemblyInfo.cs delete mode 100644 MediaBrowser.Common/Api/Logging/LogFileWebSocketListener.cs delete mode 100644 MediaBrowser.Common/Api/ScheduledTasks/ScheduledTaskService.cs delete mode 100644 MediaBrowser.Common/Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs delete mode 100644 MediaBrowser.Common/Api/SystemInfoWebSocketListener.cs delete mode 100644 MediaBrowser.Common/Net/NetworkShares.cs delete mode 100644 MediaBrowser.Common/Net/StaticResult.cs delete mode 100644 MediaBrowser.Common/Resources/Images/Icon.ico delete mode 100644 MediaBrowser.Common/Updates/ApplicationUpdateCheck.cs delete mode 100644 MediaBrowser.Common/Updates/ApplicationUpdater.cs delete mode 100644 MediaBrowser.Common/Updates/ClickOnceHelper.cs create mode 100644 MediaBrowser.Controller/IO/NetworkShares.cs delete mode 100644 MediaBrowser.Model/Tasks/TaskProgress.cs create mode 100644 MediaBrowser.Model/Updates/CheckForUpdateResult.cs create mode 100644 MediaBrowser.UI/Extensions/Extensions.cs diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index e552c7f1d..db768d0e8 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; +using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using ServiceStack.ServiceHost; using System; diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 546cf4492..220101de5 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -66,14 +66,11 @@ - - - - + @@ -87,6 +84,8 @@ + + @@ -108,6 +107,8 @@ + + diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 64c5d6cbc..924c33937 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -1,8 +1,7 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Kernel; using MediaBrowser.Common.Net; -using MediaBrowser.Common.Updates; using MediaBrowser.Controller; -using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Updates; using ServiceStack.ServiceHost; using System; @@ -97,6 +96,12 @@ namespace MediaBrowser.Api [Export(typeof(IRestfulService))] public class PackageService : BaseRestService { + /// + /// Gets or sets the application host. + /// + /// The application host. + public IApplicationHost ApplicationHost { get; set; } + /// /// Gets the specified request. /// @@ -116,9 +121,9 @@ namespace MediaBrowser.Api else if (request.PackageType == PackageType.System || request.PackageType == PackageType.All) { - var updateCheckResult = new ApplicationUpdateCheck().CheckForApplicationUpdate(CancellationToken.None, new Progress { }).Result; + var updateCheckResult = ApplicationHost.CheckForApplicationUpdate(CancellationToken.None, new Progress { }).Result; - if (updateCheckResult.UpdateAvailable) + if (updateCheckResult.IsUpdateAvailable) { result.Add(new PackageVersionInfo { diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs new file mode 100644 index 000000000..bd12454a0 --- /dev/null +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -0,0 +1,176 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Common.Serialization; +using MediaBrowser.Model.Tasks; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using ServiceStack.Text.Controller; + +namespace MediaBrowser.Api.ScheduledTasks +{ + /// + /// Class GetScheduledTask + /// + [Route("/ScheduledTasks/{Id}", "GET")] + public class GetScheduledTask : IReturn + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class GetScheduledTasks + /// + [Route("/ScheduledTasks", "GET")] + public class GetScheduledTasks : IReturn> + { + + } + + /// + /// Class StartScheduledTask + /// + [Route("/ScheduledTasks/Running/{Id}", "POST")] + public class StartScheduledTask : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class StopScheduledTask + /// + [Route("/ScheduledTasks/Running/{Id}", "DELETE")] + public class StopScheduledTask : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + public Guid Id { get; set; } + } + + /// + /// Class UpdateScheduledTaskTriggers + /// + [Route("/ScheduledTasks/{Id}/Triggers", "POST")] + public class UpdateScheduledTaskTriggers : IRequiresRequestStream + { + /// + /// Gets or sets the task id. + /// + /// The task id. + public Guid Id { get; set; } + + /// + /// The raw Http Request Input Stream + /// + /// The request stream. + public Stream RequestStream { get; set; } + } + + /// + /// Class ScheduledTasksService + /// + [Export(typeof(IRestfulService))] + public class ScheduledTaskService : BaseRestService + { + /// + /// Gets the specified request. + /// + /// The request. + /// IEnumerable{TaskInfo}. + public object Get(GetScheduledTasks request) + { + var result = Kernel.ScheduledTasks.OrderBy(i => i.Name) + .Select(ScheduledTaskHelpers.GetTaskInfo).ToList(); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// IEnumerable{TaskInfo}. + public object Get(GetScheduledTask request) + { + var task = Kernel.ScheduledTasks.FirstOrDefault(i => i.Id == request.Id); + + if (task == null) + { + throw new ResourceNotFoundException("Task not found"); + } + + var result = ScheduledTaskHelpers.GetTaskInfo(task); + + return ToOptimizedResult(result); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(StartScheduledTask request) + { + var task = Kernel.ScheduledTasks.FirstOrDefault(i => i.Id == request.Id); + + if (task == null) + { + throw new ResourceNotFoundException("Task not found"); + } + + task.Execute(); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Delete(StopScheduledTask request) + { + var task = Kernel.ScheduledTasks.FirstOrDefault(i => i.Id == request.Id); + + if (task == null) + { + throw new ResourceNotFoundException("Task not found"); + } + + task.Cancel(); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(UpdateScheduledTaskTriggers request) + { + // We need to parse this manually because we told service stack not to with IRequiresRequestStream + // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs + var pathInfo = PathInfo.Parse(Request.PathInfo); + var id = new Guid(pathInfo.GetArgumentValue(1)); + + var task = Kernel.ScheduledTasks.FirstOrDefault(i => i.Id == id); + + if (task == null) + { + throw new ResourceNotFoundException("Task not found"); + } + + var triggerInfos = JsonSerializer.DeserializeFromStream(request.RequestStream); + + task.Triggers = triggerInfos.Select(t => ScheduledTaskHelpers.GetTrigger(t, Kernel)); + } + } +} diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs new file mode 100644 index 000000000..7fa47735e --- /dev/null +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs @@ -0,0 +1,49 @@ +using MediaBrowser.Common.Kernel; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.ScheduledTasks +{ + /// + /// Class ScheduledTasksWebSocketListener + /// + [Export(typeof(IWebSocketListener))] + public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener, object> + { + /// + /// Gets the name. + /// + /// The name. + protected override string Name + { + get { return "ScheduledTasksInfo"; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + [ImportingConstructor] + public ScheduledTasksWebSocketListener([Import("logger")] ILogger logger) + : base(logger) + { + + } + + /// + /// Gets the data to send. + /// + /// The state. + /// Task{IEnumerable{TaskInfo}}. + protected override Task> GetDataToSend(object state) + { + return Task.FromResult(Kernel.ScheduledTasks.OrderBy(i => i.Name) + .Select(ScheduledTaskHelpers.GetTaskInfo)); + } + } +} diff --git a/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs b/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs new file mode 100644 index 000000000..f725b0222 --- /dev/null +++ b/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs @@ -0,0 +1,146 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Kernel; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Api.WebSocket +{ + /// + /// Class ScheduledTasksWebSocketListener + /// + [Export(typeof(IWebSocketListener))] + public class LogFileWebSocketListener : BasePeriodicWebSocketListener, LogFileWebSocketState> + { + /// + /// Gets the name. + /// + /// The name. + protected override string Name + { + get { return "LogFile"; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + [ImportingConstructor] + public LogFileWebSocketListener([Import("logger")] ILogger logger) + : base(logger) + { + + } + + /// + /// Initializes the specified kernel. + /// + /// The kernel. + public override void Initialize(IKernel kernel) + { + base.Initialize(kernel); + + kernel.LoggerLoaded += kernel_LoggerLoaded; + } + + /// + /// Gets the data to send. + /// + /// The state. + /// IEnumerable{System.String}. + protected override async Task> GetDataToSend(LogFileWebSocketState state) + { + if (!string.Equals(Kernel.LogFilePath, state.LastLogFilePath)) + { + state.LastLogFilePath = Kernel.LogFilePath; + state.StartLine = 0; + } + + var lines = await GetLogLines(state.LastLogFilePath, state.StartLine).ConfigureAwait(false); + + state.StartLine += lines.Count; + + return lines; + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool dispose) + { + if (dispose) + { + Kernel.LoggerLoaded -= kernel_LoggerLoaded; + } + base.Dispose(dispose); + } + + /// + /// Handles the LoggerLoaded event of the kernel control. + /// + /// The source of the event. + /// The instance containing the event data. + void kernel_LoggerLoaded(object sender, EventArgs e) + { + // Reset the startline for each connection whenever the logger reloads + lock (ActiveConnections) + { + foreach (var connection in ActiveConnections) + { + connection.Item4.StartLine = 0; + } + } + } + + /// + /// Gets the log lines. + /// + /// The log file path. + /// The start line. + /// Task{IEnumerable{System.String}}. + internal static async Task> GetLogLines(string logFilePath, int startLine) + { + var lines = new List(); + + using (var fs = new FileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, StreamDefaults.DefaultFileStreamBufferSize, true)) + { + using (var reader = new StreamReader(fs)) + { + while (!reader.EndOfStream) + { + lines.Add(await reader.ReadLineAsync().ConfigureAwait(false)); + } + } + } + + if (startLine > 0) + { + lines = lines.Skip(startLine).ToList(); + } + + return lines; + } + } + + /// + /// Class LogFileWebSocketState + /// + public class LogFileWebSocketState + { + /// + /// Gets or sets the last log file path. + /// + /// The last log file path. + public string LastLogFilePath { get; set; } + /// + /// Gets or sets the start line. + /// + /// The start line. + public int StartLine { get; set; } + } +} diff --git a/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs new file mode 100644 index 000000000..51d9f2fcd --- /dev/null +++ b/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs @@ -0,0 +1,45 @@ +using MediaBrowser.Common.Kernel; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.WebSocket +{ + /// + /// Class SystemInfoWebSocketListener + /// + [Export(typeof(IWebSocketListener))] + public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener + { + /// + /// Gets the name. + /// + /// The name. + protected override string Name + { + get { return "SystemInfo"; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + [ImportingConstructor] + public SystemInfoWebSocketListener([Import("logger")] ILogger logger) + : base(logger) + { + + } + + /// + /// Gets the data to send. + /// + /// The state. + /// Task{SystemInfo}. + protected override Task GetDataToSend(object state) + { + return Task.FromResult(Kernel.GetSystemInfo()); + } + } +} diff --git a/MediaBrowser.ClickOnce/ApplicationUpdateCheck.cs b/MediaBrowser.ClickOnce/ApplicationUpdateCheck.cs new file mode 100644 index 000000000..72c42f586 --- /dev/null +++ b/MediaBrowser.ClickOnce/ApplicationUpdateCheck.cs @@ -0,0 +1,106 @@ +using MediaBrowser.Model.Updates; +using System; +using System.Deployment.Application; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.ClickOnce +{ + /// + /// Class ApplicationUpdateCheck + /// + public class ApplicationUpdateCheck + { + /// + /// The _task completion source + /// + private TaskCompletionSource _taskCompletionSource; + + /// + /// The _progress + /// + private IProgress _progress; + + /// + /// Checks for application update. + /// + /// The cancellation token. + /// The progress. + /// Task{CheckForUpdateCompletedEventArgs}. + /// Current deployment is not a ClickOnce deployment + public Task CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress progress) + { + if (!ApplicationDeployment.IsNetworkDeployed) + { + throw new InvalidOperationException("Current deployment is not network deployed."); + } + + _progress = progress; + + _taskCompletionSource = new TaskCompletionSource(); + + var deployment = ApplicationDeployment.CurrentDeployment; + + cancellationToken.Register(deployment.CheckForUpdateAsyncCancel); + + cancellationToken.ThrowIfCancellationRequested(); + + deployment.CheckForUpdateCompleted += deployment_CheckForUpdateCompleted; + deployment.CheckForUpdateProgressChanged += deployment_CheckForUpdateProgressChanged; + + deployment.CheckForUpdateAsync(); + + return _taskCompletionSource.Task; + } + + /// + /// To the result. + /// + /// The instance containing the event data. + /// CheckForUpdateResult. + private CheckForUpdateResult ToResult(CheckForUpdateCompletedEventArgs args) + { + return new CheckForUpdateResult + { + AvailableVersion = args.AvailableVersion, + IsUpdateAvailable = args.UpdateAvailable + }; + } + + /// + /// Handles the CheckForUpdateCompleted event of the deployment control. + /// + /// The source of the event. + /// The instance containing the event data. + void deployment_CheckForUpdateCompleted(object sender, CheckForUpdateCompletedEventArgs e) + { + var deployment = ApplicationDeployment.CurrentDeployment; + + deployment.CheckForUpdateCompleted -= deployment_CheckForUpdateCompleted; + deployment.CheckForUpdateProgressChanged -= deployment_CheckForUpdateProgressChanged; + + if (e.Error != null) + { + _taskCompletionSource.SetException(e.Error); + } + else if (e.Cancelled) + { + _taskCompletionSource.SetCanceled(); + } + else + { + _taskCompletionSource.SetResult(ToResult(e)); + } + } + + /// + /// Handles the CheckForUpdateProgressChanged event of the deployment control. + /// + /// The source of the event. + /// The instance containing the event data. + void deployment_CheckForUpdateProgressChanged(object sender, DeploymentProgressChangedEventArgs e) + { + _progress.Report(e.ProgressPercentage); + } + } +} diff --git a/MediaBrowser.ClickOnce/ApplicationUpdater.cs b/MediaBrowser.ClickOnce/ApplicationUpdater.cs new file mode 100644 index 000000000..29af406f1 --- /dev/null +++ b/MediaBrowser.ClickOnce/ApplicationUpdater.cs @@ -0,0 +1,92 @@ +using System; +using System.ComponentModel; +using System.Deployment.Application; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.ClickOnce +{ + /// + /// Class ApplicationUpdater + /// + public class ApplicationUpdater + { + /// + /// The _task completion source + /// + private TaskCompletionSource _taskCompletionSource; + + /// + /// The _progress + /// + private IProgress _progress; + + /// + /// Updates the application + /// + /// The cancellation token. + /// The progress. + /// Task{AsyncCompletedEventArgs}. + /// Current deployment is not network deployed. + public Task UpdateApplication(CancellationToken cancellationToken, IProgress progress) + { + if (!ApplicationDeployment.IsNetworkDeployed) + { + throw new InvalidOperationException("Current deployment is not network deployed."); + } + + _progress = progress; + + _taskCompletionSource = new TaskCompletionSource(); + + var deployment = ApplicationDeployment.CurrentDeployment; + + cancellationToken.Register(deployment.UpdateAsyncCancel); + + cancellationToken.ThrowIfCancellationRequested(); + + deployment.UpdateCompleted += deployment_UpdateCompleted; + deployment.UpdateProgressChanged += deployment_UpdateProgressChanged; + + deployment.UpdateAsync(); + + return _taskCompletionSource.Task; + } + + /// + /// Handles the UpdateCompleted event of the deployment control. + /// + /// The source of the event. + /// The instance containing the event data. + void deployment_UpdateCompleted(object sender, AsyncCompletedEventArgs e) + { + var deployment = ApplicationDeployment.CurrentDeployment; + + deployment.UpdateCompleted -= deployment_UpdateCompleted; + deployment.UpdateProgressChanged -= deployment_UpdateProgressChanged; + + if (e.Error != null) + { + _taskCompletionSource.SetException(e.Error); + } + else if (e.Cancelled) + { + _taskCompletionSource.SetCanceled(); + } + else + { + _taskCompletionSource.SetResult(e); + } + } + + /// + /// Handles the UpdateProgressChanged event of the deployment control. + /// + /// The source of the event. + /// The instance containing the event data. + void deployment_UpdateProgressChanged(object sender, DeploymentProgressChangedEventArgs e) + { + _progress.Report(e.ProgressPercentage); + } + } +} diff --git a/MediaBrowser.ClickOnce/ClickOnceHelper.cs b/MediaBrowser.ClickOnce/ClickOnceHelper.cs new file mode 100644 index 000000000..c86332ccf --- /dev/null +++ b/MediaBrowser.ClickOnce/ClickOnceHelper.cs @@ -0,0 +1,255 @@ +using System.Deployment.Application; +using Microsoft.Win32; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.AccessControl; + +namespace MediaBrowser.ClickOnce +{ + /// + /// Class ClickOnceHelper + /// + public class ClickOnceHelper + { + /// + /// The uninstall string + /// + private const string UninstallString = "UninstallString"; + /// + /// The display name key + /// + private const string DisplayNameKey = "DisplayName"; + /// + /// The uninstall string file + /// + private const string UninstallStringFile = "UninstallString.bat"; + /// + /// The appref extension + /// + private const string ApprefExtension = ".appref-ms"; + /// + /// The uninstall registry key + /// + private readonly RegistryKey UninstallRegistryKey; + + /// + /// Gets the location. + /// + /// The location. + private static string Location + { + get { return Assembly.GetExecutingAssembly().Location; } + } + + /// + /// Gets a value indicating whether this instance is network deployed. + /// + /// true if this instance is network deployed; otherwise, false. + public static bool IsNetworkDeployed + { + get { return ApplicationDeployment.IsNetworkDeployed; } + } + + /// + /// Gets the name of the publisher. + /// + /// The name of the publisher. + public string PublisherName { get; private set; } + /// + /// Gets the name of the product. + /// + /// The name of the product. + public string ProductName { get; private set; } + /// + /// Gets the uninstall file. + /// + /// The uninstall file. + public string UninstallFile { get; private set; } + /// + /// Gets the name of the suite. + /// + /// The name of the suite. + public string SuiteName { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the publisher. + /// Name of the product. + /// Name of the suite. + public ClickOnceHelper(string publisherName, string productName, string suiteName) + { + PublisherName = publisherName; + ProductName = productName; + SuiteName = suiteName; + + var publisherFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), PublisherName); + + if (!Directory.Exists(publisherFolder)) + { + Directory.CreateDirectory(publisherFolder); + } + + UninstallFile = Path.Combine(publisherFolder, UninstallStringFile); + + UninstallRegistryKey = GetUninstallRegistryKeyByProductName(ProductName); + } + + /// + /// Gets the shortcut path. + /// + /// System.String. + private string GetShortcutPath() + { + var allProgramsPath = Environment.GetFolderPath(Environment.SpecialFolder.Programs); + + var shortcutPath = Path.Combine(allProgramsPath, PublisherName); + + if (!string.IsNullOrEmpty(SuiteName)) + { + shortcutPath = Path.Combine(shortcutPath, SuiteName); + } + + return Path.Combine(shortcutPath, ProductName) + ApprefExtension; + } + + /// + /// Gets the startup shortcut path. + /// + /// System.String. + private string GetStartupShortcutPath() + { + var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup); + + return Path.Combine(startupPath, ProductName) + ApprefExtension; + } + + /// + /// Adds the shortcut to startup. + /// + public void AddShortcutToStartup() + { + var startupPath = GetStartupShortcutPath(); + + if (!File.Exists(startupPath)) + { + File.Copy(GetShortcutPath(), startupPath); + } + } + + /// + /// Removes the shortcut from startup. + /// + public void RemoveShortcutFromStartup() + { + var startupPath = GetStartupShortcutPath(); + + if (File.Exists(startupPath)) + { + File.Delete(startupPath); + } + } + + /// + /// Updates the uninstall parameters. + /// + /// Name of the uninstaller file. + public void UpdateUninstallParameters(string uninstallerFileName) + { + if (UninstallRegistryKey != null) + { + UpdateUninstallString(uninstallerFileName); + } + } + + /// + /// Gets the name of the uninstall registry key by product. + /// + /// Name of the product. + /// RegistryKey. + private RegistryKey GetUninstallRegistryKeyByProductName(string productName) + { + var subKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall"); + + if (subKey == null) + { + return null; + } + + return subKey.GetSubKeyNames() + .Select(name => subKey.OpenSubKey(name, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.QueryValues | RegistryRights.ReadKey | RegistryRights.SetValue)) + .Where(application => application != null) + .FirstOrDefault(application => application.GetValueNames().Where(appKey => appKey.Equals(DisplayNameKey)).Any(appKey => application.GetValue(appKey).Equals(productName))); + } + + /// + /// Updates the uninstall string. + /// + /// Name of the uninstaller file. + private void UpdateUninstallString(string uninstallerFileName) + { + var uninstallString = (string)UninstallRegistryKey.GetValue(UninstallString); + + if (!string.IsNullOrEmpty(UninstallFile) && uninstallString.StartsWith("rundll32.exe", StringComparison.OrdinalIgnoreCase)) + { + File.WriteAllText(UninstallFile, uninstallString); + } + + var str = String.Format("\"{0}\" uninstall", Path.Combine(Path.GetDirectoryName(Location), uninstallerFileName)); + + UninstallRegistryKey.SetValue(UninstallString, str); + } + + /// + /// Uninstalls this instance. + /// + public void Uninstall() + { + RemoveShortcutFromStartup(); + + var uninstallString = File.ReadAllText(UninstallFile); + + new Process + { + StartInfo = + { + Arguments = uninstallString.Substring(uninstallString.IndexOf(" ", StringComparison.OrdinalIgnoreCase) + 1), + FileName = uninstallString.Substring(0, uninstallString.IndexOf(" ", StringComparison.OrdinalIgnoreCase)), + UseShellExecute = false + } + + }.Start(); + } + + /// + /// Configures the click once startup. + /// + /// Name of the publisher. + /// Name of the product. + /// Name of the suite. + /// if set to true [run at startup]. + /// The uninstaller filename. + public static void ConfigureClickOnceStartupIfInstalled(string publisherName, string productName, string suiteName, bool runAtStartup, string uninstallerFilename) + { + if (!ApplicationDeployment.IsNetworkDeployed) + { + return; + } + + var clickOnceHelper = new ClickOnceHelper(publisherName, productName, suiteName); + + if (runAtStartup) + { + clickOnceHelper.UpdateUninstallParameters(uninstallerFilename); + clickOnceHelper.AddShortcutToStartup(); + } + else + { + clickOnceHelper.RemoveShortcutFromStartup(); + } + } + } +} diff --git a/MediaBrowser.ClickOnce/MediaBrowser.ClickOnce.csproj b/MediaBrowser.ClickOnce/MediaBrowser.ClickOnce.csproj new file mode 100644 index 000000000..00370bfcd --- /dev/null +++ b/MediaBrowser.ClickOnce/MediaBrowser.ClickOnce.csproj @@ -0,0 +1,62 @@ + + + + + Debug + AnyCPU + {CC96BF3E-0BDA-4809-BC4B-BB6D418F4A84} + Library + Properties + MediaBrowser.ClickOnce + MediaBrowser.ClickOnce + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + + + + + \ No newline at end of file diff --git a/MediaBrowser.ClickOnce/Properties/AssemblyInfo.cs b/MediaBrowser.ClickOnce/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..1faa44e01 --- /dev/null +++ b/MediaBrowser.ClickOnce/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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("MediaBrowser.ClickOnce")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MediaBrowser.ClickOnce")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[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("16e62c11-5009-4212-8c96-fd692479fc5d")] + +// 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")] diff --git a/MediaBrowser.Common/Api/Logging/LogFileWebSocketListener.cs b/MediaBrowser.Common/Api/Logging/LogFileWebSocketListener.cs deleted file mode 100644 index e873facb1..000000000 --- a/MediaBrowser.Common/Api/Logging/LogFileWebSocketListener.cs +++ /dev/null @@ -1,146 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Kernel; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.Common.Api.Logging -{ - /// - /// Class ScheduledTasksWebSocketListener - /// - [Export(typeof(IWebSocketListener))] - public class LogFileWebSocketListener : BasePeriodicWebSocketListener, LogFileWebSocketState> - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "LogFile"; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - [ImportingConstructor] - public LogFileWebSocketListener([Import("logger")] ILogger logger) - : base(logger) - { - - } - - /// - /// Initializes the specified kernel. - /// - /// The kernel. - public override void Initialize(IKernel kernel) - { - base.Initialize(kernel); - - kernel.LoggerLoaded += kernel_LoggerLoaded; - } - - /// - /// Gets the data to send. - /// - /// The state. - /// IEnumerable{System.String}. - protected override async Task> GetDataToSend(LogFileWebSocketState state) - { - if (!string.Equals(Kernel.LogFilePath, state.LastLogFilePath)) - { - state.LastLogFilePath = Kernel.LogFilePath; - state.StartLine = 0; - } - - var lines = await GetLogLines(state.LastLogFilePath, state.StartLine).ConfigureAwait(false); - - state.StartLine += lines.Count; - - return lines; - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool dispose) - { - if (dispose) - { - Kernel.LoggerLoaded -= kernel_LoggerLoaded; - } - base.Dispose(dispose); - } - - /// - /// Handles the LoggerLoaded event of the kernel control. - /// - /// The source of the event. - /// The instance containing the event data. - void kernel_LoggerLoaded(object sender, EventArgs e) - { - // Reset the startline for each connection whenever the logger reloads - lock (ActiveConnections) - { - foreach (var connection in ActiveConnections) - { - connection.Item4.StartLine = 0; - } - } - } - - /// - /// Gets the log lines. - /// - /// The log file path. - /// The start line. - /// Task{IEnumerable{System.String}}. - internal static async Task> GetLogLines(string logFilePath, int startLine) - { - var lines = new List(); - - using (var fs = new FileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, StreamDefaults.DefaultFileStreamBufferSize, true)) - { - using (var reader = new StreamReader(fs)) - { - while (!reader.EndOfStream) - { - lines.Add(await reader.ReadLineAsync().ConfigureAwait(false)); - } - } - } - - if (startLine > 0) - { - lines = lines.Skip(startLine).ToList(); - } - - return lines; - } - } - - /// - /// Class LogFileWebSocketState - /// - public class LogFileWebSocketState - { - /// - /// Gets or sets the last log file path. - /// - /// The last log file path. - public string LastLogFilePath { get; set; } - /// - /// Gets or sets the start line. - /// - /// The start line. - public int StartLine { get; set; } - } -} diff --git a/MediaBrowser.Common/Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Common/Api/ScheduledTasks/ScheduledTaskService.cs deleted file mode 100644 index 1df24dcd2..000000000 --- a/MediaBrowser.Common/Api/ScheduledTasks/ScheduledTaskService.cs +++ /dev/null @@ -1,176 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Common.Serialization; -using MediaBrowser.Model.Tasks; -using ServiceStack.ServiceHost; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.IO; -using System.Linq; -using ServiceStack.Text.Controller; - -namespace MediaBrowser.Common.Api.ScheduledTasks -{ - /// - /// Class GetScheduledTask - /// - [Route("/ScheduledTasks/{Id}", "GET")] - public class GetScheduledTask : IReturn - { - /// - /// Gets or sets the id. - /// - /// The id. - public Guid Id { get; set; } - } - - /// - /// Class GetScheduledTasks - /// - [Route("/ScheduledTasks", "GET")] - public class GetScheduledTasks : IReturn> - { - - } - - /// - /// Class StartScheduledTask - /// - [Route("/ScheduledTasks/Running/{Id}", "POST")] - public class StartScheduledTask : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - public Guid Id { get; set; } - } - - /// - /// Class StopScheduledTask - /// - [Route("/ScheduledTasks/Running/{Id}", "DELETE")] - public class StopScheduledTask : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - public Guid Id { get; set; } - } - - /// - /// Class UpdateScheduledTaskTriggers - /// - [Route("/ScheduledTasks/{Id}/Triggers", "POST")] - public class UpdateScheduledTaskTriggers : IRequiresRequestStream - { - /// - /// Gets or sets the task id. - /// - /// The task id. - public Guid Id { get; set; } - - /// - /// The raw Http Request Input Stream - /// - /// The request stream. - public Stream RequestStream { get; set; } - } - - /// - /// Class ScheduledTasksService - /// - [Export(typeof(IRestfulService))] - public class ScheduledTaskService : BaseRestService - { - /// - /// Gets the specified request. - /// - /// The request. - /// IEnumerable{TaskInfo}. - public object Get(GetScheduledTasks request) - { - var result = Kernel.ScheduledTasks.OrderBy(i => i.Name) - .Select(ScheduledTaskHelpers.GetTaskInfo).ToList(); - - return ToOptimizedResult(result); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// IEnumerable{TaskInfo}. - public object Get(GetScheduledTask request) - { - var task = Kernel.ScheduledTasks.FirstOrDefault(i => i.Id == request.Id); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - var result = ScheduledTaskHelpers.GetTaskInfo(task); - - return ToOptimizedResult(result); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(StartScheduledTask request) - { - var task = Kernel.ScheduledTasks.FirstOrDefault(i => i.Id == request.Id); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - task.Execute(); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Delete(StopScheduledTask request) - { - var task = Kernel.ScheduledTasks.FirstOrDefault(i => i.Id == request.Id); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - task.Cancel(); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(UpdateScheduledTaskTriggers request) - { - // We need to parse this manually because we told service stack not to with IRequiresRequestStream - // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs - var pathInfo = PathInfo.Parse(Request.PathInfo); - var id = new Guid(pathInfo.GetArgumentValue(1)); - - var task = Kernel.ScheduledTasks.FirstOrDefault(i => i.Id == id); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - var triggerInfos = JsonSerializer.DeserializeFromStream(request.RequestStream); - - task.Triggers = triggerInfos.Select(t => ScheduledTaskHelpers.GetTrigger(t, Kernel)); - } - } -} diff --git a/MediaBrowser.Common/Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Common/Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs deleted file mode 100644 index 7e4596d9d..000000000 --- a/MediaBrowser.Common/Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs +++ /dev/null @@ -1,49 +0,0 @@ -using MediaBrowser.Common.Kernel; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Tasks; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Api.ScheduledTasks -{ - /// - /// Class ScheduledTasksWebSocketListener - /// - [Export(typeof(IWebSocketListener))] - public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener, object> - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "ScheduledTasksInfo"; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - [ImportingConstructor] - public ScheduledTasksWebSocketListener([Import("logger")] ILogger logger) - : base(logger) - { - - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{IEnumerable{TaskInfo}}. - protected override Task> GetDataToSend(object state) - { - return Task.FromResult(Kernel.ScheduledTasks.OrderBy(i => i.Name) - .Select(ScheduledTaskHelpers.GetTaskInfo)); - } - } -} diff --git a/MediaBrowser.Common/Api/SystemInfoWebSocketListener.cs b/MediaBrowser.Common/Api/SystemInfoWebSocketListener.cs deleted file mode 100644 index a216937ed..000000000 --- a/MediaBrowser.Common/Api/SystemInfoWebSocketListener.cs +++ /dev/null @@ -1,44 +0,0 @@ -using MediaBrowser.Common.Kernel; -using MediaBrowser.Model.Logging; -using System.ComponentModel.Composition; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Api -{ - /// - /// Class SystemInfoWebSocketListener - /// - [Export(typeof(IWebSocketListener))] - public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "SystemInfo"; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - [ImportingConstructor] - public SystemInfoWebSocketListener([Import("logger")] ILogger logger) - : base(logger) - { - - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{SystemInfo}. - protected override Task GetDataToSend(object state) - { - return Task.FromResult(Kernel.GetSystemInfo()); - } - } -} diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs index e6b9d8ee6..6abe6d4e0 100644 --- a/MediaBrowser.Common/Extensions/BaseExtensions.cs +++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -using System.Windows.Threading; namespace MediaBrowser.Common.Extensions { @@ -46,24 +45,6 @@ namespace MediaBrowser.Common.Extensions return val.Split(new[] { separator }, options); } - /// - /// Invokes an action after a specified delay - /// - /// The dispatcher. - /// The action. - /// The delay ms. - public static void InvokeWithDelay(this Dispatcher dispatcher, Action action, long delayMs) - { - var timer = new DispatcherTimer(DispatcherPriority.Normal, dispatcher); - timer.Interval = TimeSpan.FromMilliseconds(delayMs); - timer.Tick += (sender, args) => - { - timer.Stop(); - action(); - }; - timer.Start(); - } - /// /// Provides a non-blocking method to start a process and wait asynchronously for it to exit /// diff --git a/MediaBrowser.Common/Kernel/BaseKernel.cs b/MediaBrowser.Common/Kernel/BaseKernel.cs index a4ac70749..d172d0a0d 100644 --- a/MediaBrowser.Common/Kernel/BaseKernel.cs +++ b/MediaBrowser.Common/Kernel/BaseKernel.cs @@ -13,7 +13,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; -using System.Deployment.Application; using System.Diagnostics; using System.IO; using System.Linq; @@ -155,12 +154,6 @@ namespace MediaBrowser.Common.Kernel /// true if this instance is first run; otherwise, false. public bool IsFirstRun { get; private set; } - /// - /// The version of the application to display - /// - /// The display version. - public string DisplayVersion { get { return ApplicationVersion.ToString(); } } - /// /// Gets or sets a value indicating whether this instance has changes that require the entire application to restart. /// @@ -325,7 +318,10 @@ namespace MediaBrowser.Common.Kernel /// Gets the log file path. /// /// The log file path. - public string LogFilePath { get; private set; } + public string LogFilePath + { + get { return ApplicationHost.LogFilePath; } + } /// /// Gets the logger. @@ -429,7 +425,7 @@ namespace MediaBrowser.Common.Kernel await ReloadComposableParts().ConfigureAwait(false); DisposeTcpManager(); - TcpManager = new TcpManager(this, Logger); + TcpManager = new TcpManager(ApplicationHost, this, Logger); } /// @@ -482,6 +478,7 @@ namespace MediaBrowser.Common.Kernel protected virtual void ComposeExportedValues(CompositionContainer container) { container.ComposeExportedValue("logger", Logger); + container.ComposeExportedValue("appHost", ApplicationHost); } /// @@ -729,8 +726,8 @@ namespace MediaBrowser.Common.Kernel return new SystemInfo { HasPendingRestart = HasPendingRestart, - Version = DisplayVersion, - IsNetworkDeployed = ApplicationDeployment.IsNetworkDeployed, + Version = ApplicationVersion.ToString(), + IsNetworkDeployed = ApplicationHost.CanSelfUpdate, WebSocketPortNumber = TcpManager.WebSocketPortNumber, SupportsNativeWebSocket = TcpManager.SupportsNativeWebSocket, FailedPluginAssemblies = FailedPluginAssemblies.ToArray() diff --git a/MediaBrowser.Common/Kernel/IApplicationHost.cs b/MediaBrowser.Common/Kernel/IApplicationHost.cs index c1b63c261..63c63eb3d 100644 --- a/MediaBrowser.Common/Kernel/IApplicationHost.cs +++ b/MediaBrowser.Common/Kernel/IApplicationHost.cs @@ -1,4 +1,8 @@ - +using MediaBrowser.Model.Updates; +using System; +using System.Threading; +using System.Threading.Tasks; + namespace MediaBrowser.Common.Kernel { /// @@ -15,5 +19,29 @@ namespace MediaBrowser.Common.Kernel /// Reloads the logger. /// void ReloadLogger(); + + /// + /// Gets the log file path. + /// + /// The log file path. + string LogFilePath { get; } + + /// + /// Gets or sets a value indicating whether this instance can self update. + /// + /// true if this instance can self update; otherwise, false. + bool CanSelfUpdate { get; } + + /// + /// Checks for update. + /// + /// Task{CheckForUpdateResult}. + Task CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress progress); + + /// + /// Updates the application. + /// + /// Task. + Task UpdateApplication(CancellationToken cancellationToken, IProgress progress); } } diff --git a/MediaBrowser.Common/Kernel/TcpManager.cs b/MediaBrowser.Common/Kernel/TcpManager.cs index 7fb624f73..086815cbf 100644 --- a/MediaBrowser.Common/Kernel/TcpManager.cs +++ b/MediaBrowser.Common/Kernel/TcpManager.cs @@ -64,6 +64,11 @@ namespace MediaBrowser.Common.Kernel /// The _logger /// private readonly ILogger _logger; + + /// + /// The _application host + /// + private readonly IApplicationHost _applicationHost; /// /// The _supports native web socket @@ -108,12 +113,14 @@ namespace MediaBrowser.Common.Kernel /// /// Initializes a new instance of the class. /// + /// The application host. /// The kernel. /// The logger. - public TcpManager(IKernel kernel, ILogger logger) + public TcpManager(IApplicationHost applicationHost, IKernel kernel, ILogger logger) : base(kernel) { _logger = logger; + _applicationHost = applicationHost; if (kernel.IsFirstRun) { @@ -182,7 +189,7 @@ namespace MediaBrowser.Common.Kernel try { - HttpServer = new HttpServer(Kernel.HttpServerUrlPrefix, "Media Browser", Kernel, _logger); + HttpServer = new HttpServer(Kernel.HttpServerUrlPrefix, "Media Browser", _applicationHost, Kernel, _logger); } catch (HttpListenerException ex) { diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index b806df856..c7b162dee 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -34,7 +34,8 @@ 4 - Resources\Images\Icon.ico + + @@ -94,7 +95,6 @@ - @@ -118,13 +118,8 @@ - - - - - @@ -156,8 +151,6 @@ - - @@ -193,9 +186,6 @@ - - - @@ -216,9 +206,6 @@ Designer - - - diff --git a/MediaBrowser.Common/Net/HttpServer.cs b/MediaBrowser.Common/Net/HttpServer.cs index 184b82c16..b09c95da0 100644 --- a/MediaBrowser.Common/Net/HttpServer.cs +++ b/MediaBrowser.Common/Net/HttpServer.cs @@ -45,6 +45,12 @@ namespace MediaBrowser.Common.Net /// The kernel. private IKernel Kernel { get; set; } + /// + /// Gets or sets the application host. + /// + /// The application host. + private IApplicationHost ApplicationHost { get; set; } + /// /// This subscribes to HttpListener requests and finds the appropriate BaseHandler to process it /// @@ -67,11 +73,12 @@ namespace MediaBrowser.Common.Net /// /// The URL. /// Name of the product. + /// The application host. /// The kernel. /// The logger. /// The default redirectpath. /// urlPrefix - public HttpServer(string urlPrefix, string serverName, IKernel kernel, ILogger logger, string defaultRedirectpath = null) + public HttpServer(string urlPrefix, string serverName, IApplicationHost applicationHost, IKernel kernel, ILogger logger, string defaultRedirectpath = null) : base() { if (string.IsNullOrEmpty(urlPrefix)) @@ -86,9 +93,14 @@ namespace MediaBrowser.Common.Net { throw new ArgumentNullException("logger"); } + if (applicationHost == null) + { + throw new ArgumentNullException("applicationHost"); + } DefaultRedirectPath = defaultRedirectpath; _logger = logger; + ApplicationHost = applicationHost; EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath = null; EndpointHostConfig.Instance.MetadataRedirectPath = "metadata"; @@ -144,6 +156,7 @@ namespace MediaBrowser.Common.Net container.Register(Kernel); container.Register(_logger); + container.Register(ApplicationHost); foreach (var service in Kernel.RestServices) { diff --git a/MediaBrowser.Common/Net/NetworkShares.cs b/MediaBrowser.Common/Net/NetworkShares.cs deleted file mode 100644 index 202865b4c..000000000 --- a/MediaBrowser.Common/Net/NetworkShares.cs +++ /dev/null @@ -1,644 +0,0 @@ -using System; -using System.IO; -using System.Collections; -using System.Runtime.InteropServices; - -namespace MediaBrowser.Common.Net -{ - /// - /// Type of share - /// - [Flags] - public enum ShareType - { - /// Disk share - Disk = 0, - /// Printer share - Printer = 1, - /// Device share - Device = 2, - /// IPC share - IPC = 3, - /// Special share - Special = -2147483648, // 0x80000000, - } - - #region Share - - /// - /// Information about a local share - /// - public class Share - { - #region Private data - - private string _server; - private string _netName; - private string _path; - private ShareType _shareType; - private string _remark; - - #endregion - - #region Constructor - - /// - /// Constructor - /// - /// - /// - public Share(string server, string netName, string path, ShareType shareType, string remark) - { - if (ShareType.Special == shareType && "IPC$" == netName) - { - shareType |= ShareType.IPC; - } - - _server = server; - _netName = netName; - _path = path; - _shareType = shareType; - _remark = remark; - } - - #endregion - - #region Properties - - /// - /// The name of the computer that this share belongs to - /// - public string Server - { - get { return _server; } - } - - /// - /// Share name - /// - public string NetName - { - get { return _netName; } - } - - /// - /// Local path - /// - public string Path - { - get { return _path; } - } - - /// - /// Share type - /// - public ShareType ShareType - { - get { return _shareType; } - } - - /// - /// Comment - /// - public string Remark - { - get { return _remark; } - } - - /// - /// Returns true if this is a file system share - /// - public bool IsFileSystem - { - get - { - // Shared device - if (0 != (_shareType & ShareType.Device)) return false; - // IPC share - if (0 != (_shareType & ShareType.IPC)) return false; - // Shared printer - if (0 != (_shareType & ShareType.Printer)) return false; - - // Standard disk share - if (0 == (_shareType & ShareType.Special)) return true; - - // Special disk share (e.g. C$) - if (ShareType.Special == _shareType && null != _netName && 0 != _netName.Length) - return true; - else - return false; - } - } - - /// - /// Get the root of a disk-based share - /// - public DirectoryInfo Root - { - get - { - if (IsFileSystem) - { - if (null == _server || 0 == _server.Length) - if (null == _path || 0 == _path.Length) - return new DirectoryInfo(ToString()); - else - return new DirectoryInfo(_path); - else - return new DirectoryInfo(ToString()); - } - else - return null; - } - } - - #endregion - - /// - /// Returns the path to this share - /// - /// - public override string ToString() - { - if (null == _server || 0 == _server.Length) - { - return string.Format(@"\\{0}\{1}", Environment.MachineName, _netName); - } - else - return string.Format(@"\\{0}\{1}", _server, _netName); - } - - /// - /// Returns true if this share matches the local path - /// - /// - /// - public bool MatchesPath(string path) - { - if (!IsFileSystem) return false; - if (null == path || 0 == path.Length) return true; - - return path.ToLower().StartsWith(_path.ToLower()); - } - } - - #endregion - - /// - /// A collection of shares - /// - public class ShareCollection : ReadOnlyCollectionBase - { - #region Platform - - /// - /// Is this an NT platform? - /// - protected static bool IsNT - { - get { return (PlatformID.Win32NT == Environment.OSVersion.Platform); } - } - - /// - /// Returns true if this is Windows 2000 or higher - /// - protected static bool IsW2KUp - { - get - { - OperatingSystem os = Environment.OSVersion; - if (PlatformID.Win32NT == os.Platform && os.Version.Major >= 5) - return true; - else - return false; - } - } - - #endregion - - #region Interop - - #region Constants - - /// Maximum path length - protected const int MAX_PATH = 260; - /// No error - protected const int NO_ERROR = 0; - /// Access denied - protected const int ERROR_ACCESS_DENIED = 5; - /// Access denied - protected const int ERROR_WRONG_LEVEL = 124; - /// More data available - protected const int ERROR_MORE_DATA = 234; - /// Not connected - protected const int ERROR_NOT_CONNECTED = 2250; - /// Level 1 - protected const int UNIVERSAL_NAME_INFO_LEVEL = 1; - /// Max extries (9x) - protected const int MAX_SI50_ENTRIES = 20; - - #endregion - - #region Structures - - /// Unc name - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - protected struct UNIVERSAL_NAME_INFO - { - [MarshalAs(UnmanagedType.LPTStr)] - public string lpUniversalName; - } - - /// Share information, NT, level 2 - /// - /// Requires admin rights to work. - /// - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - protected struct SHARE_INFO_2 - { - [MarshalAs(UnmanagedType.LPWStr)] - public string NetName; - public ShareType ShareType; - [MarshalAs(UnmanagedType.LPWStr)] - public string Remark; - public int Permissions; - public int MaxUsers; - public int CurrentUsers; - [MarshalAs(UnmanagedType.LPWStr)] - public string Path; - [MarshalAs(UnmanagedType.LPWStr)] - public string Password; - } - - /// Share information, NT, level 1 - /// - /// Fallback when no admin rights. - /// - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - protected struct SHARE_INFO_1 - { - [MarshalAs(UnmanagedType.LPWStr)] - public string NetName; - public ShareType ShareType; - [MarshalAs(UnmanagedType.LPWStr)] - public string Remark; - } - - /// Share information, Win9x - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] - protected struct SHARE_INFO_50 - { - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)] - public string NetName; - - public byte bShareType; - public ushort Flags; - - [MarshalAs(UnmanagedType.LPTStr)] - public string Remark; - [MarshalAs(UnmanagedType.LPTStr)] - public string Path; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)] - public string PasswordRW; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)] - public string PasswordRO; - - public ShareType ShareType - { - get { return (ShareType)((int)bShareType & 0x7F); } - } - } - - /// Share information level 1, Win9x - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] - protected struct SHARE_INFO_1_9x - { - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)] - public string NetName; - public byte Padding; - - public ushort bShareType; - - [MarshalAs(UnmanagedType.LPTStr)] - public string Remark; - - public ShareType ShareType - { - get { return (ShareType)((int)bShareType & 0x7FFF); } - } - } - - #endregion - - #region Functions - - /// Get a UNC name - [DllImport("mpr", CharSet = CharSet.Auto)] - protected static extern int WNetGetUniversalName(string lpLocalPath, - int dwInfoLevel, ref UNIVERSAL_NAME_INFO lpBuffer, ref int lpBufferSize); - - /// Get a UNC name - [DllImport("mpr", CharSet = CharSet.Auto)] - protected static extern int WNetGetUniversalName(string lpLocalPath, - int dwInfoLevel, IntPtr lpBuffer, ref int lpBufferSize); - - /// Enumerate shares (NT) - [DllImport("netapi32", CharSet = CharSet.Unicode)] - protected static extern int NetShareEnum(string lpServerName, int dwLevel, - out IntPtr lpBuffer, int dwPrefMaxLen, out int entriesRead, - out int totalEntries, ref int hResume); - - /// Enumerate shares (9x) - [DllImport("svrapi", CharSet = CharSet.Ansi)] - protected static extern int NetShareEnum( - [MarshalAs(UnmanagedType.LPTStr)] string lpServerName, int dwLevel, - IntPtr lpBuffer, ushort cbBuffer, out ushort entriesRead, - out ushort totalEntries); - - /// Free the buffer (NT) - [DllImport("netapi32")] - protected static extern int NetApiBufferFree(IntPtr lpBuffer); - - #endregion - - #region Enumerate shares - - /// - /// Enumerates the shares on Windows NT - /// - /// The server name - /// The ShareCollection - protected static void EnumerateSharesNT(string server, ShareCollection shares) - { - int level = 2; - int entriesRead, totalEntries, nRet, hResume = 0; - IntPtr pBuffer = IntPtr.Zero; - - try - { - nRet = NetShareEnum(server, level, out pBuffer, -1, - out entriesRead, out totalEntries, ref hResume); - - if (ERROR_ACCESS_DENIED == nRet) - { - //Need admin for level 2, drop to level 1 - level = 1; - nRet = NetShareEnum(server, level, out pBuffer, -1, - out entriesRead, out totalEntries, ref hResume); - } - - if (NO_ERROR == nRet && entriesRead > 0) - { - Type t = (2 == level) ? typeof(SHARE_INFO_2) : typeof(SHARE_INFO_1); - int offset = Marshal.SizeOf(t); - - for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += offset) - { - IntPtr pItem = new IntPtr(lpItem); - if (1 == level) - { - SHARE_INFO_1 si = (SHARE_INFO_1)Marshal.PtrToStructure(pItem, t); - shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark); - } - else - { - SHARE_INFO_2 si = (SHARE_INFO_2)Marshal.PtrToStructure(pItem, t); - shares.Add(si.NetName, si.Path, si.ShareType, si.Remark); - } - } - } - - } - finally - { - // Clean up buffer allocated by system - if (IntPtr.Zero != pBuffer) - NetApiBufferFree(pBuffer); - } - } - - /// - /// Enumerates the shares on Windows 9x - /// - /// The server name - /// The ShareCollection - protected static void EnumerateShares9x(string server, ShareCollection shares) - { - int level = 50; - int nRet = 0; - ushort entriesRead, totalEntries; - - Type t = typeof(SHARE_INFO_50); - int size = Marshal.SizeOf(t); - ushort cbBuffer = (ushort)(MAX_SI50_ENTRIES * size); - //On Win9x, must allocate buffer before calling API - IntPtr pBuffer = Marshal.AllocHGlobal(cbBuffer); - - try - { - nRet = NetShareEnum(server, level, pBuffer, cbBuffer, - out entriesRead, out totalEntries); - - if (ERROR_WRONG_LEVEL == nRet) - { - level = 1; - t = typeof(SHARE_INFO_1_9x); - size = Marshal.SizeOf(t); - - nRet = NetShareEnum(server, level, pBuffer, cbBuffer, - out entriesRead, out totalEntries); - } - - if (NO_ERROR == nRet || ERROR_MORE_DATA == nRet) - { - for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += size) - { - IntPtr pItem = new IntPtr(lpItem); - - if (1 == level) - { - SHARE_INFO_1_9x si = (SHARE_INFO_1_9x)Marshal.PtrToStructure(pItem, t); - shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark); - } - else - { - SHARE_INFO_50 si = (SHARE_INFO_50)Marshal.PtrToStructure(pItem, t); - shares.Add(si.NetName, si.Path, si.ShareType, si.Remark); - } - } - } - else - Console.WriteLine(nRet); - - } - finally - { - //Clean up buffer - Marshal.FreeHGlobal(pBuffer); - } - } - - /// - /// Enumerates the shares - /// - /// The server name - /// The ShareCollection - protected static void EnumerateShares(string server, ShareCollection shares) - { - if (null != server && 0 != server.Length && !IsW2KUp) - { - server = server.ToUpper(); - - // On NT4, 9x and Me, server has to start with "\\" - if (!('\\' == server[0] && '\\' == server[1])) - server = @"\\" + server; - } - - if (IsNT) - EnumerateSharesNT(server, shares); - else - EnumerateShares9x(server, shares); - } - - #endregion - - #endregion - - #region Static methods - - /// - /// Returns true if fileName is a valid local file-name of the form: - /// X:\, where X is a drive letter from A-Z - /// - /// The filename to check - /// - public static bool IsValidFilePath(string fileName) - { - if (null == fileName || 0 == fileName.Length) return false; - - char drive = char.ToUpper(fileName[0]); - if ('A' > drive || drive > 'Z') - return false; - - else if (Path.VolumeSeparatorChar != fileName[1]) - return false; - else if (Path.DirectorySeparatorChar != fileName[2]) - return false; - else - return true; - } - - #endregion - - /// The name of the server this collection represents - private string _server; - - #region Constructor - - /// - /// Default constructor - local machine - /// - public ShareCollection() - { - _server = string.Empty; - EnumerateShares(_server, this); - } - - /// - /// Constructor - /// - /// - public ShareCollection(string server) - { - _server = server; - EnumerateShares(_server, this); - } - - #endregion - - #region Add - - protected void Add(Share share) - { - InnerList.Add(share); - } - - protected void Add(string netName, string path, ShareType shareType, string remark) - { - InnerList.Add(new Share(_server, netName, path, shareType, remark)); - } - - #endregion - - #region Properties - - /// - /// Returns the name of the server this collection represents - /// - public string Server - { - get { return _server; } - } - - /// - /// Returns the at the specified index. - /// - public Share this[int index] - { - get { return (Share)InnerList[index]; } - } - - /// - /// Returns the which matches a given local path - /// - /// The path to match - public Share this[string path] - { - get - { - if (null == path || 0 == path.Length) return null; - - path = Path.GetFullPath(path); - if (!IsValidFilePath(path)) return null; - - Share match = null; - - for (int i = 0; i < InnerList.Count; i++) - { - Share s = (Share)InnerList[i]; - - if (s.IsFileSystem && s.MatchesPath(path)) - { - //Store first match - if (null == match) - match = s; - - // If this has a longer path, - // and this is a disk share or match is a special share, - // then this is a better match - else if (match.Path.Length < s.Path.Length) - { - if (ShareType.Disk == s.ShareType || ShareType.Disk != match.ShareType) - match = s; - } - } - } - - return match; - } - } - - #endregion - - /// - /// Copy this collection to an array - /// - /// - /// - public void CopyTo(Share[] array, int index) - { - InnerList.CopyTo(array, index); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Net/StaticResult.cs b/MediaBrowser.Common/Net/StaticResult.cs deleted file mode 100644 index 0dd6372cf..000000000 --- a/MediaBrowser.Common/Net/StaticResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net -{ - public class StaticResult - { - public Stream Stream { get; set; } - } -} diff --git a/MediaBrowser.Common/Resources/Images/Icon.ico b/MediaBrowser.Common/Resources/Images/Icon.ico deleted file mode 100644 index bea939de4..000000000 Binary files a/MediaBrowser.Common/Resources/Images/Icon.ico and /dev/null differ diff --git a/MediaBrowser.Common/ScheduledTasks/BaseScheduledTask.cs b/MediaBrowser.Common/ScheduledTasks/BaseScheduledTask.cs index 99dd7ab01..395c73a84 100644 --- a/MediaBrowser.Common/ScheduledTasks/BaseScheduledTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/BaseScheduledTask.cs @@ -171,7 +171,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// Gets the current progress. /// /// The current progress. - public TaskProgress CurrentProgress { get; private set; } + public double? CurrentProgress { get; private set; } /// /// The _triggers @@ -246,7 +246,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// The cancellation token. /// The progress. /// Task. - protected abstract Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress); + protected abstract Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress); /// /// Gets the name of the task @@ -355,7 +355,7 @@ namespace MediaBrowser.Common.ScheduledTasks Logger.Info("Executing {0}", Name); - var progress = new Progress(); + var progress = new Progress(); progress.ProgressChanged += progress_ProgressChanged; @@ -426,7 +426,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// The sender. /// The e. - void progress_ProgressChanged(object sender, TaskProgress e) + void progress_ProgressChanged(object sender, double e) { CurrentProgress = e; } diff --git a/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs b/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs index a3809c4b9..95d1edf63 100644 --- a/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// Gets the current progress. /// /// The current progress. - TaskProgress CurrentProgress { get; } + double? CurrentProgress { get; } /// /// Gets the name of the task diff --git a/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs b/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs index 34421ca1f..95c4c6a66 100644 --- a/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs +++ b/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Common.ScheduledTasks return new TaskInfo { Name = task.Name, - CurrentProgress = task.CurrentProgress, + CurrentProgressPercentage = task.CurrentProgress, State = task.State, Id = task.Id, LastExecutionResult = task.LastExecutionResult, diff --git a/MediaBrowser.Common/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/MediaBrowser.Common/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 98c6d672a..2a9bc4a0d 100644 --- a/MediaBrowser.Common/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks /// The cancellation token. /// The progress. /// Task. - protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) { return Task.Run(() => { @@ -51,7 +51,7 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks /// The directory. /// The min date modified. /// The progress. - private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress progress) + private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress progress) { var filesToDelete = new DirectoryInfo(directory).EnumerateFileSystemInfos("*", SearchOption.AllDirectories) .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && f.LastWriteTimeUtc < minDateModified) @@ -64,7 +64,7 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks double percent = index; percent /= filesToDelete.Count; - progress.Report(new TaskProgress { Description = file.FullName, PercentComplete = 100 * percent }); + progress.Report(100 * percent); cancellationToken.ThrowIfCancellationRequested(); @@ -73,7 +73,7 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks index++; } - progress.Report(new TaskProgress { PercentComplete = 100 }); + progress.Report(100); } /// diff --git a/MediaBrowser.Common/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/MediaBrowser.Common/ScheduledTasks/Tasks/DeleteLogFileTask.cs index afb21187c..a1068a263 100644 --- a/MediaBrowser.Common/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks /// The cancellation token. /// The progress. /// Task. - protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) { return Task.Run(() => { @@ -51,7 +51,7 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks double percent = index; percent /= filesToDelete.Count; - progress.Report(new TaskProgress { Description = file.FullName, PercentComplete = 100 * percent }); + progress.Report(100 * percent); cancellationToken.ThrowIfCancellationRequested(); @@ -60,7 +60,7 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks index++; } - progress.Report(new TaskProgress { PercentComplete = 100 }); + progress.Report(100); }); } diff --git a/MediaBrowser.Common/ScheduledTasks/Tasks/ReloadLoggerTask.cs b/MediaBrowser.Common/ScheduledTasks/Tasks/ReloadLoggerTask.cs index 24aaad57f..a4f06f205 100644 --- a/MediaBrowser.Common/ScheduledTasks/Tasks/ReloadLoggerTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/Tasks/ReloadLoggerTask.cs @@ -31,11 +31,11 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks /// The cancellation token. /// The progress. /// Task. - protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) { cancellationToken.ThrowIfCancellationRequested(); - progress.Report(new TaskProgress { PercentComplete = 0 }); + progress.Report(0); return Task.Run(() => Kernel.ReloadLogger()); } diff --git a/MediaBrowser.Common/ScheduledTasks/Tasks/SystemUpdateTask.cs b/MediaBrowser.Common/ScheduledTasks/Tasks/SystemUpdateTask.cs index a7e3f90a0..f9950424f 100644 --- a/MediaBrowser.Common/ScheduledTasks/Tasks/SystemUpdateTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/Tasks/SystemUpdateTask.cs @@ -1,10 +1,7 @@ using MediaBrowser.Common.Kernel; -using MediaBrowser.Common.Updates; -using MediaBrowser.Model.Tasks; using System; using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Deployment.Application; using System.Threading; using System.Threading.Tasks; @@ -16,6 +13,21 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks [Export(typeof(IScheduledTask))] public class SystemUpdateTask : BaseScheduledTask { + /// + /// The _app host + /// + private readonly IApplicationHost _appHost; + + /// + /// Initializes a new instance of the class. + /// + /// The app host. + [ImportingConstructor] + public SystemUpdateTask([Import("appHost")] IApplicationHost appHost) + { + _appHost = appHost; + } + /// /// Creates the triggers that define when the task will run /// @@ -37,26 +49,26 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks /// The cancellation token. /// The progress. /// Task. - protected override async Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + protected override async Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) { - if (!ApplicationDeployment.IsNetworkDeployed) return; + if (!_appHost.CanSelfUpdate) return; - EventHandler innerProgressHandler = (sender, e) => progress.Report(new TaskProgress { PercentComplete = e.PercentComplete * .1 }); + EventHandler innerProgressHandler = (sender, e) => progress.Report(e * .1); // Create a progress object for the update check - var innerProgress = new Progress(); + var innerProgress = new Progress(); innerProgress.ProgressChanged += innerProgressHandler; - var updateInfo = await new ApplicationUpdateCheck().CheckForApplicationUpdate(cancellationToken, innerProgress).ConfigureAwait(false); + var updateInfo = await _appHost.CheckForApplicationUpdate(cancellationToken, innerProgress).ConfigureAwait(false); // Release the event handler innerProgress.ProgressChanged -= innerProgressHandler; - progress.Report(new TaskProgress { PercentComplete = 10 }); + progress.Report(10); - if (!updateInfo.UpdateAvailable) + if (!updateInfo.IsUpdateAvailable) { - progress.Report(new TaskProgress { PercentComplete = 100 }); + progress.Report(100); return; } @@ -66,12 +78,12 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks { Logger.Info("Update Revision {0} available. Updating...", updateInfo.AvailableVersion); - innerProgressHandler = (sender, e) => progress.Report(new TaskProgress { PercentComplete = (e.PercentComplete * .9) + .1 }); + innerProgressHandler = (sender, e) => progress.Report((e * .9) + .1); - innerProgress = new Progress(); + innerProgress = new Progress(); innerProgress.ProgressChanged += innerProgressHandler; - await new ApplicationUpdater().UpdateApplication(cancellationToken, innerProgress).ConfigureAwait(false); + await _appHost.UpdateApplication(cancellationToken, innerProgress).ConfigureAwait(false); // Release the event handler innerProgress.ProgressChanged -= innerProgressHandler; @@ -83,7 +95,7 @@ namespace MediaBrowser.Common.ScheduledTasks.Tasks Logger.Info("A new version of Media Browser is available."); } - progress.Report(new TaskProgress { PercentComplete = 100 }); + progress.Report(100); } /// diff --git a/MediaBrowser.Common/Updates/ApplicationUpdateCheck.cs b/MediaBrowser.Common/Updates/ApplicationUpdateCheck.cs deleted file mode 100644 index 7501d7321..000000000 --- a/MediaBrowser.Common/Updates/ApplicationUpdateCheck.cs +++ /dev/null @@ -1,92 +0,0 @@ -using MediaBrowser.Model.Tasks; -using System; -using System.Deployment.Application; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Updates -{ - /// - /// Class ApplicationUpdateCheck - /// - public class ApplicationUpdateCheck - { - /// - /// The _task completion source - /// - private TaskCompletionSource _taskCompletionSource; - - /// - /// The _progress - /// - private IProgress _progress; - - /// - /// Checks for application update. - /// - /// The cancellation token. - /// The progress. - /// Task{CheckForUpdateCompletedEventArgs}. - /// Current deployment is not a ClickOnce deployment - public Task CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress progress) - { - if (!ApplicationDeployment.IsNetworkDeployed) - { - throw new InvalidOperationException("Current deployment is not network deployed."); - } - - _progress = progress; - - _taskCompletionSource = new TaskCompletionSource(); - - var deployment = ApplicationDeployment.CurrentDeployment; - - cancellationToken.Register(deployment.CheckForUpdateAsyncCancel); - - cancellationToken.ThrowIfCancellationRequested(); - - deployment.CheckForUpdateCompleted += deployment_CheckForUpdateCompleted; - deployment.CheckForUpdateProgressChanged += deployment_CheckForUpdateProgressChanged; - - deployment.CheckForUpdateAsync(); - - return _taskCompletionSource.Task; - } - - /// - /// Handles the CheckForUpdateCompleted event of the deployment control. - /// - /// The source of the event. - /// The instance containing the event data. - void deployment_CheckForUpdateCompleted(object sender, CheckForUpdateCompletedEventArgs e) - { - var deployment = ApplicationDeployment.CurrentDeployment; - - deployment.CheckForUpdateCompleted -= deployment_CheckForUpdateCompleted; - deployment.CheckForUpdateProgressChanged -= deployment_CheckForUpdateProgressChanged; - - if (e.Error != null) - { - _taskCompletionSource.SetException(e.Error); - } - else if (e.Cancelled) - { - _taskCompletionSource.SetCanceled(); - } - else - { - _taskCompletionSource.SetResult(e); - } - } - - /// - /// Handles the CheckForUpdateProgressChanged event of the deployment control. - /// - /// The source of the event. - /// The instance containing the event data. - void deployment_CheckForUpdateProgressChanged(object sender, DeploymentProgressChangedEventArgs e) - { - _progress.Report(new TaskProgress { PercentComplete = e.ProgressPercentage }); - } - } -} diff --git a/MediaBrowser.Common/Updates/ApplicationUpdater.cs b/MediaBrowser.Common/Updates/ApplicationUpdater.cs deleted file mode 100644 index d8ae87b37..000000000 --- a/MediaBrowser.Common/Updates/ApplicationUpdater.cs +++ /dev/null @@ -1,93 +0,0 @@ -using MediaBrowser.Model.Tasks; -using System; -using System.ComponentModel; -using System.Deployment.Application; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Updates -{ - /// - /// Class ApplicationUpdater - /// - public class ApplicationUpdater - { - /// - /// The _task completion source - /// - private TaskCompletionSource _taskCompletionSource; - - /// - /// The _progress - /// - private IProgress _progress; - - /// - /// Updates the application - /// - /// The cancellation token. - /// The progress. - /// Task{AsyncCompletedEventArgs}. - /// Current deployment is not network deployed. - public Task UpdateApplication(CancellationToken cancellationToken, IProgress progress) - { - if (!ApplicationDeployment.IsNetworkDeployed) - { - throw new InvalidOperationException("Current deployment is not network deployed."); - } - - _progress = progress; - - _taskCompletionSource = new TaskCompletionSource(); - - var deployment = ApplicationDeployment.CurrentDeployment; - - cancellationToken.Register(deployment.UpdateAsyncCancel); - - cancellationToken.ThrowIfCancellationRequested(); - - deployment.UpdateCompleted += deployment_UpdateCompleted; - deployment.UpdateProgressChanged += deployment_UpdateProgressChanged; - - deployment.UpdateAsync(); - - return _taskCompletionSource.Task; - } - - /// - /// Handles the UpdateCompleted event of the deployment control. - /// - /// The source of the event. - /// The instance containing the event data. - void deployment_UpdateCompleted(object sender, AsyncCompletedEventArgs e) - { - var deployment = ApplicationDeployment.CurrentDeployment; - - deployment.UpdateCompleted -= deployment_UpdateCompleted; - deployment.UpdateProgressChanged -= deployment_UpdateProgressChanged; - - if (e.Error != null) - { - _taskCompletionSource.SetException(e.Error); - } - else if (e.Cancelled) - { - _taskCompletionSource.SetCanceled(); - } - else - { - _taskCompletionSource.SetResult(e); - } - } - - /// - /// Handles the UpdateProgressChanged event of the deployment control. - /// - /// The source of the event. - /// The instance containing the event data. - void deployment_UpdateProgressChanged(object sender, DeploymentProgressChangedEventArgs e) - { - _progress.Report(new TaskProgress { PercentComplete = e.ProgressPercentage }); - } - } -} diff --git a/MediaBrowser.Common/Updates/ClickOnceHelper.cs b/MediaBrowser.Common/Updates/ClickOnceHelper.cs deleted file mode 100644 index 7dd0cf9c5..000000000 --- a/MediaBrowser.Common/Updates/ClickOnceHelper.cs +++ /dev/null @@ -1,217 +0,0 @@ -using Microsoft.Win32; -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security.AccessControl; - -namespace MediaBrowser.Common.Updates -{ - /// - /// Class ClickOnceHelper - /// - public class ClickOnceHelper - { - /// - /// The uninstall string - /// - private const string UninstallString = "UninstallString"; - /// - /// The display name key - /// - private const string DisplayNameKey = "DisplayName"; - /// - /// The uninstall string file - /// - private const string UninstallStringFile = "UninstallString.bat"; - /// - /// The appref extension - /// - private const string ApprefExtension = ".appref-ms"; - /// - /// The uninstall registry key - /// - private readonly RegistryKey UninstallRegistryKey; - - /// - /// Gets the location. - /// - /// The location. - private static string Location - { - get { return Assembly.GetExecutingAssembly().Location; } - } - - /// - /// Gets the name of the publisher. - /// - /// The name of the publisher. - public string PublisherName { get; private set; } - /// - /// Gets the name of the product. - /// - /// The name of the product. - public string ProductName { get; private set; } - /// - /// Gets the uninstall file. - /// - /// The uninstall file. - public string UninstallFile { get; private set; } - /// - /// Gets the name of the suite. - /// - /// The name of the suite. - public string SuiteName { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// Name of the publisher. - /// Name of the product. - /// Name of the suite. - public ClickOnceHelper(string publisherName, string productName, string suiteName) - { - PublisherName = publisherName; - ProductName = productName; - SuiteName = suiteName; - - var publisherFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), PublisherName); - - if (!Directory.Exists(publisherFolder)) - { - Directory.CreateDirectory(publisherFolder); - } - - UninstallFile = Path.Combine(publisherFolder, UninstallStringFile); - - UninstallRegistryKey = GetUninstallRegistryKeyByProductName(ProductName); - } - - /// - /// Gets the shortcut path. - /// - /// System.String. - private string GetShortcutPath() - { - var allProgramsPath = Environment.GetFolderPath(Environment.SpecialFolder.Programs); - - var shortcutPath = Path.Combine(allProgramsPath, PublisherName); - - if (!string.IsNullOrEmpty(SuiteName)) - { - shortcutPath = Path.Combine(shortcutPath, SuiteName); - } - - return Path.Combine(shortcutPath, ProductName) + ApprefExtension; - } - - /// - /// Gets the startup shortcut path. - /// - /// System.String. - private string GetStartupShortcutPath() - { - var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup); - - return Path.Combine(startupPath, ProductName) + ApprefExtension; - } - - /// - /// Adds the shortcut to startup. - /// - public void AddShortcutToStartup() - { - var startupPath = GetStartupShortcutPath(); - - if (!File.Exists(startupPath)) - { - File.Copy(GetShortcutPath(), startupPath); - } - } - - /// - /// Removes the shortcut from startup. - /// - public void RemoveShortcutFromStartup() - { - var startupPath = GetStartupShortcutPath(); - - if (File.Exists(startupPath)) - { - File.Delete(startupPath); - } - } - - /// - /// Updates the uninstall parameters. - /// - /// Name of the uninstaller file. - public void UpdateUninstallParameters(string uninstallerFileName) - { - if (UninstallRegistryKey != null) - { - UpdateUninstallString(uninstallerFileName); - } - } - - /// - /// Gets the name of the uninstall registry key by product. - /// - /// Name of the product. - /// RegistryKey. - private RegistryKey GetUninstallRegistryKeyByProductName(string productName) - { - var subKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall"); - - if (subKey == null) - { - return null; - } - - return subKey.GetSubKeyNames() - .Select(name => subKey.OpenSubKey(name, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.QueryValues | RegistryRights.ReadKey | RegistryRights.SetValue)) - .Where(application => application != null) - .FirstOrDefault(application => application.GetValueNames().Where(appKey => appKey.Equals(DisplayNameKey)).Any(appKey => application.GetValue(appKey).Equals(productName))); - } - - /// - /// Updates the uninstall string. - /// - /// Name of the uninstaller file. - private void UpdateUninstallString(string uninstallerFileName) - { - var uninstallString = (string)UninstallRegistryKey.GetValue(UninstallString); - - if (!string.IsNullOrEmpty(UninstallFile) && uninstallString.StartsWith("rundll32.exe", StringComparison.OrdinalIgnoreCase)) - { - File.WriteAllText(UninstallFile, uninstallString); - } - - var str = String.Format("\"{0}\" uninstall", Path.Combine(Path.GetDirectoryName(Location), uninstallerFileName)); - - UninstallRegistryKey.SetValue(UninstallString, str); - } - - /// - /// Uninstalls this instance. - /// - public void Uninstall() - { - RemoveShortcutFromStartup(); - - var uninstallString = File.ReadAllText(UninstallFile); - - new Process - { - StartInfo = - { - Arguments = uninstallString.Substring(uninstallString.IndexOf(" ", StringComparison.OrdinalIgnoreCase) + 1), - FileName = uninstallString.Substring(0, uninstallString.IndexOf(" ", StringComparison.OrdinalIgnoreCase)), - UseShellExecute = false - } - - }.Start(); - } - } -} diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 33e9f291d..fbcd2f589 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Entities /// The cancellation token. /// if set to true [recursive]. /// Task. - protected override Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool? recursive = null) + protected override Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool? recursive = null) { //we don't directly validate our children //but we do need to clear out the index cache... diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index f67934f5d..bcb0b26be 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -612,7 +612,7 @@ namespace MediaBrowser.Controller.Entities /// The cancellation token. /// if set to true [recursive]. /// Task. - public async Task ValidateChildren(IProgress progress, CancellationToken cancellationToken, bool? recursive = null) + public async Task ValidateChildren(IProgress progress, CancellationToken cancellationToken, bool? recursive = null) { cancellationToken.ThrowIfCancellationRequested(); @@ -664,7 +664,7 @@ namespace MediaBrowser.Controller.Entities /// The cancellation token. /// if set to true [recursive]. /// Task. - protected async virtual Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool? recursive = null) + protected async virtual Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool? recursive = null) { // Nothing to do here if (LocationType != LocationType.FileSystem) @@ -681,7 +681,7 @@ namespace MediaBrowser.Controller.Entities if (nonCachedChildren == null) return; //nothing to validate - progress.Report(new TaskProgress { PercentComplete = 5 }); + progress.Report(5); //build a dictionary of the current children we have now by Id so we can compare quickly and easily var currentChildren = ActualChildren.ToDictionary(i => i.Id); @@ -772,13 +772,13 @@ namespace MediaBrowser.Controller.Entities Kernel.Instance.LibraryManager.OnLibraryChanged(changedArgs); } - progress.Report(new TaskProgress { PercentComplete = 15 }); + progress.Report(15); cancellationToken.ThrowIfCancellationRequested(); await RefreshChildren(validChildren, progress, cancellationToken, recursive).ConfigureAwait(false); - progress.Report(new TaskProgress { PercentComplete = 100 }); + progress.Report(100); } /// @@ -789,7 +789,7 @@ namespace MediaBrowser.Controller.Entities /// The cancellation token. /// if set to true [recursive]. /// Task. - private Task RefreshChildren(IEnumerable> children, IProgress progress, CancellationToken cancellationToken, bool? recursive) + private Task RefreshChildren(IEnumerable> children, IProgress progress, CancellationToken cancellationToken, bool? recursive) { var numComplete = 0; @@ -824,7 +824,7 @@ namespace MediaBrowser.Controller.Entities { cancellationToken.ThrowIfCancellationRequested(); - await ((Folder)child).ValidateChildren(new Progress { }, cancellationToken, recursive: recursive).ConfigureAwait(false); + await ((Folder)child).ValidateChildren(new Progress { }, cancellationToken, recursive: recursive).ConfigureAwait(false); } lock (progress) @@ -834,7 +834,7 @@ namespace MediaBrowser.Controller.Entities double percent = numComplete; percent /= list.Count; - progress.Report(new TaskProgress { PercentComplete = (85 * percent) + 15 }); + progress.Report((85 * percent) + 15); } })); @@ -952,7 +952,7 @@ namespace MediaBrowser.Controller.Entities { await base.ChangedExternally().ConfigureAwait(false); - var progress = new Progress { }; + var progress = new Progress { }; await ValidateChildren(progress, CancellationToken.None).ConfigureAwait(false); } diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index ee9815e62..f27e8c689 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -205,7 +205,7 @@ namespace MediaBrowser.Controller.Entities /// The cancellation token. /// The progress. /// Task. - public async Task ValidateMediaLibrary(IProgress progress, CancellationToken cancellationToken) + public async Task ValidateMediaLibrary(IProgress progress, CancellationToken cancellationToken) { Logger.Info("Validating media library for {0}", Name); await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); @@ -221,7 +221,7 @@ namespace MediaBrowser.Controller.Entities /// The cancellation token. /// The progress. /// Task. - public async Task ValidateCollectionFolders(IProgress progress, CancellationToken cancellationToken) + public async Task ValidateCollectionFolders(IProgress progress, CancellationToken cancellationToken) { Logger.Info("Validating collection folders for {0}", Name); await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); @@ -279,7 +279,7 @@ namespace MediaBrowser.Controller.Entities RootFolder = null; // Kick off a task to validate the media library - Task.Run(() => ValidateMediaLibrary(new Progress { }, CancellationToken.None)); + Task.Run(() => ValidateMediaLibrary(new Progress { }, CancellationToken.None)); return RefreshMetadata(CancellationToken.None, forceSave: true, forceRefresh: true); } diff --git a/MediaBrowser.Controller/IO/NetworkShares.cs b/MediaBrowser.Controller/IO/NetworkShares.cs new file mode 100644 index 000000000..93edc6447 --- /dev/null +++ b/MediaBrowser.Controller/IO/NetworkShares.cs @@ -0,0 +1,644 @@ +using System; +using System.IO; +using System.Collections; +using System.Runtime.InteropServices; + +namespace MediaBrowser.Controller.IO +{ + /// + /// Type of share + /// + [Flags] + public enum ShareType + { + /// Disk share + Disk = 0, + /// Printer share + Printer = 1, + /// Device share + Device = 2, + /// IPC share + IPC = 3, + /// Special share + Special = -2147483648, // 0x80000000, + } + + #region Share + + /// + /// Information about a local share + /// + public class Share + { + #region Private data + + private string _server; + private string _netName; + private string _path; + private ShareType _shareType; + private string _remark; + + #endregion + + #region Constructor + + /// + /// Constructor + /// + /// + /// + public Share(string server, string netName, string path, ShareType shareType, string remark) + { + if (ShareType.Special == shareType && "IPC$" == netName) + { + shareType |= ShareType.IPC; + } + + _server = server; + _netName = netName; + _path = path; + _shareType = shareType; + _remark = remark; + } + + #endregion + + #region Properties + + /// + /// The name of the computer that this share belongs to + /// + public string Server + { + get { return _server; } + } + + /// + /// Share name + /// + public string NetName + { + get { return _netName; } + } + + /// + /// Local path + /// + public string Path + { + get { return _path; } + } + + /// + /// Share type + /// + public ShareType ShareType + { + get { return _shareType; } + } + + /// + /// Comment + /// + public string Remark + { + get { return _remark; } + } + + /// + /// Returns true if this is a file system share + /// + public bool IsFileSystem + { + get + { + // Shared device + if (0 != (_shareType & ShareType.Device)) return false; + // IPC share + if (0 != (_shareType & ShareType.IPC)) return false; + // Shared printer + if (0 != (_shareType & ShareType.Printer)) return false; + + // Standard disk share + if (0 == (_shareType & ShareType.Special)) return true; + + // Special disk share (e.g. C$) + if (ShareType.Special == _shareType && null != _netName && 0 != _netName.Length) + return true; + else + return false; + } + } + + /// + /// Get the root of a disk-based share + /// + public DirectoryInfo Root + { + get + { + if (IsFileSystem) + { + if (null == _server || 0 == _server.Length) + if (null == _path || 0 == _path.Length) + return new DirectoryInfo(ToString()); + else + return new DirectoryInfo(_path); + else + return new DirectoryInfo(ToString()); + } + else + return null; + } + } + + #endregion + + /// + /// Returns the path to this share + /// + /// + public override string ToString() + { + if (null == _server || 0 == _server.Length) + { + return string.Format(@"\\{0}\{1}", Environment.MachineName, _netName); + } + else + return string.Format(@"\\{0}\{1}", _server, _netName); + } + + /// + /// Returns true if this share matches the local path + /// + /// + /// + public bool MatchesPath(string path) + { + if (!IsFileSystem) return false; + if (null == path || 0 == path.Length) return true; + + return path.ToLower().StartsWith(_path.ToLower()); + } + } + + #endregion + + /// + /// A collection of shares + /// + public class ShareCollection : ReadOnlyCollectionBase + { + #region Platform + + /// + /// Is this an NT platform? + /// + protected static bool IsNT + { + get { return (PlatformID.Win32NT == Environment.OSVersion.Platform); } + } + + /// + /// Returns true if this is Windows 2000 or higher + /// + protected static bool IsW2KUp + { + get + { + OperatingSystem os = Environment.OSVersion; + if (PlatformID.Win32NT == os.Platform && os.Version.Major >= 5) + return true; + else + return false; + } + } + + #endregion + + #region Interop + + #region Constants + + /// Maximum path length + protected const int MAX_PATH = 260; + /// No error + protected const int NO_ERROR = 0; + /// Access denied + protected const int ERROR_ACCESS_DENIED = 5; + /// Access denied + protected const int ERROR_WRONG_LEVEL = 124; + /// More data available + protected const int ERROR_MORE_DATA = 234; + /// Not connected + protected const int ERROR_NOT_CONNECTED = 2250; + /// Level 1 + protected const int UNIVERSAL_NAME_INFO_LEVEL = 1; + /// Max extries (9x) + protected const int MAX_SI50_ENTRIES = 20; + + #endregion + + #region Structures + + /// Unc name + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + protected struct UNIVERSAL_NAME_INFO + { + [MarshalAs(UnmanagedType.LPTStr)] + public string lpUniversalName; + } + + /// Share information, NT, level 2 + /// + /// Requires admin rights to work. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + protected struct SHARE_INFO_2 + { + [MarshalAs(UnmanagedType.LPWStr)] + public string NetName; + public ShareType ShareType; + [MarshalAs(UnmanagedType.LPWStr)] + public string Remark; + public int Permissions; + public int MaxUsers; + public int CurrentUsers; + [MarshalAs(UnmanagedType.LPWStr)] + public string Path; + [MarshalAs(UnmanagedType.LPWStr)] + public string Password; + } + + /// Share information, NT, level 1 + /// + /// Fallback when no admin rights. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + protected struct SHARE_INFO_1 + { + [MarshalAs(UnmanagedType.LPWStr)] + public string NetName; + public ShareType ShareType; + [MarshalAs(UnmanagedType.LPWStr)] + public string Remark; + } + + /// Share information, Win9x + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + protected struct SHARE_INFO_50 + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)] + public string NetName; + + public byte bShareType; + public ushort Flags; + + [MarshalAs(UnmanagedType.LPTStr)] + public string Remark; + [MarshalAs(UnmanagedType.LPTStr)] + public string Path; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)] + public string PasswordRW; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)] + public string PasswordRO; + + public ShareType ShareType + { + get { return (ShareType)((int)bShareType & 0x7F); } + } + } + + /// Share information level 1, Win9x + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + protected struct SHARE_INFO_1_9x + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)] + public string NetName; + public byte Padding; + + public ushort bShareType; + + [MarshalAs(UnmanagedType.LPTStr)] + public string Remark; + + public ShareType ShareType + { + get { return (ShareType)((int)bShareType & 0x7FFF); } + } + } + + #endregion + + #region Functions + + /// Get a UNC name + [DllImport("mpr", CharSet = CharSet.Auto)] + protected static extern int WNetGetUniversalName(string lpLocalPath, + int dwInfoLevel, ref UNIVERSAL_NAME_INFO lpBuffer, ref int lpBufferSize); + + /// Get a UNC name + [DllImport("mpr", CharSet = CharSet.Auto)] + protected static extern int WNetGetUniversalName(string lpLocalPath, + int dwInfoLevel, IntPtr lpBuffer, ref int lpBufferSize); + + /// Enumerate shares (NT) + [DllImport("netapi32", CharSet = CharSet.Unicode)] + protected static extern int NetShareEnum(string lpServerName, int dwLevel, + out IntPtr lpBuffer, int dwPrefMaxLen, out int entriesRead, + out int totalEntries, ref int hResume); + + /// Enumerate shares (9x) + [DllImport("svrapi", CharSet = CharSet.Ansi)] + protected static extern int NetShareEnum( + [MarshalAs(UnmanagedType.LPTStr)] string lpServerName, int dwLevel, + IntPtr lpBuffer, ushort cbBuffer, out ushort entriesRead, + out ushort totalEntries); + + /// Free the buffer (NT) + [DllImport("netapi32")] + protected static extern int NetApiBufferFree(IntPtr lpBuffer); + + #endregion + + #region Enumerate shares + + /// + /// Enumerates the shares on Windows NT + /// + /// The server name + /// The ShareCollection + protected static void EnumerateSharesNT(string server, ShareCollection shares) + { + int level = 2; + int entriesRead, totalEntries, nRet, hResume = 0; + IntPtr pBuffer = IntPtr.Zero; + + try + { + nRet = NetShareEnum(server, level, out pBuffer, -1, + out entriesRead, out totalEntries, ref hResume); + + if (ERROR_ACCESS_DENIED == nRet) + { + //Need admin for level 2, drop to level 1 + level = 1; + nRet = NetShareEnum(server, level, out pBuffer, -1, + out entriesRead, out totalEntries, ref hResume); + } + + if (NO_ERROR == nRet && entriesRead > 0) + { + Type t = (2 == level) ? typeof(SHARE_INFO_2) : typeof(SHARE_INFO_1); + int offset = Marshal.SizeOf(t); + + for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += offset) + { + IntPtr pItem = new IntPtr(lpItem); + if (1 == level) + { + SHARE_INFO_1 si = (SHARE_INFO_1)Marshal.PtrToStructure(pItem, t); + shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark); + } + else + { + SHARE_INFO_2 si = (SHARE_INFO_2)Marshal.PtrToStructure(pItem, t); + shares.Add(si.NetName, si.Path, si.ShareType, si.Remark); + } + } + } + + } + finally + { + // Clean up buffer allocated by system + if (IntPtr.Zero != pBuffer) + NetApiBufferFree(pBuffer); + } + } + + /// + /// Enumerates the shares on Windows 9x + /// + /// The server name + /// The ShareCollection + protected static void EnumerateShares9x(string server, ShareCollection shares) + { + int level = 50; + int nRet = 0; + ushort entriesRead, totalEntries; + + Type t = typeof(SHARE_INFO_50); + int size = Marshal.SizeOf(t); + ushort cbBuffer = (ushort)(MAX_SI50_ENTRIES * size); + //On Win9x, must allocate buffer before calling API + IntPtr pBuffer = Marshal.AllocHGlobal(cbBuffer); + + try + { + nRet = NetShareEnum(server, level, pBuffer, cbBuffer, + out entriesRead, out totalEntries); + + if (ERROR_WRONG_LEVEL == nRet) + { + level = 1; + t = typeof(SHARE_INFO_1_9x); + size = Marshal.SizeOf(t); + + nRet = NetShareEnum(server, level, pBuffer, cbBuffer, + out entriesRead, out totalEntries); + } + + if (NO_ERROR == nRet || ERROR_MORE_DATA == nRet) + { + for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += size) + { + IntPtr pItem = new IntPtr(lpItem); + + if (1 == level) + { + SHARE_INFO_1_9x si = (SHARE_INFO_1_9x)Marshal.PtrToStructure(pItem, t); + shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark); + } + else + { + SHARE_INFO_50 si = (SHARE_INFO_50)Marshal.PtrToStructure(pItem, t); + shares.Add(si.NetName, si.Path, si.ShareType, si.Remark); + } + } + } + else + Console.WriteLine(nRet); + + } + finally + { + //Clean up buffer + Marshal.FreeHGlobal(pBuffer); + } + } + + /// + /// Enumerates the shares + /// + /// The server name + /// The ShareCollection + protected static void EnumerateShares(string server, ShareCollection shares) + { + if (null != server && 0 != server.Length && !IsW2KUp) + { + server = server.ToUpper(); + + // On NT4, 9x and Me, server has to start with "\\" + if (!('\\' == server[0] && '\\' == server[1])) + server = @"\\" + server; + } + + if (IsNT) + EnumerateSharesNT(server, shares); + else + EnumerateShares9x(server, shares); + } + + #endregion + + #endregion + + #region Static methods + + /// + /// Returns true if fileName is a valid local file-name of the form: + /// X:\, where X is a drive letter from A-Z + /// + /// The filename to check + /// + public static bool IsValidFilePath(string fileName) + { + if (null == fileName || 0 == fileName.Length) return false; + + char drive = char.ToUpper(fileName[0]); + if ('A' > drive || drive > 'Z') + return false; + + else if (Path.VolumeSeparatorChar != fileName[1]) + return false; + else if (Path.DirectorySeparatorChar != fileName[2]) + return false; + else + return true; + } + + #endregion + + /// The name of the server this collection represents + private string _server; + + #region Constructor + + /// + /// Default constructor - local machine + /// + public ShareCollection() + { + _server = string.Empty; + EnumerateShares(_server, this); + } + + /// + /// Constructor + /// + /// + public ShareCollection(string server) + { + _server = server; + EnumerateShares(_server, this); + } + + #endregion + + #region Add + + protected void Add(Share share) + { + InnerList.Add(share); + } + + protected void Add(string netName, string path, ShareType shareType, string remark) + { + InnerList.Add(new Share(_server, netName, path, shareType, remark)); + } + + #endregion + + #region Properties + + /// + /// Returns the name of the server this collection represents + /// + public string Server + { + get { return _server; } + } + + /// + /// Returns the at the specified index. + /// + public Share this[int index] + { + get { return (Share)InnerList[index]; } + } + + /// + /// Returns the which matches a given local path + /// + /// The path to match + public Share this[string path] + { + get + { + if (null == path || 0 == path.Length) return null; + + path = Path.GetFullPath(path); + if (!IsValidFilePath(path)) return null; + + Share match = null; + + for (int i = 0; i < InnerList.Count; i++) + { + Share s = (Share)InnerList[i]; + + if (s.IsFileSystem && s.MatchesPath(path)) + { + //Store first match + if (null == match) + match = s; + + // If this has a longer path, + // and this is a disk share or match is a special share, + // then this is a better match + else if (match.Path.Length < s.Path.Length) + { + if (ShareType.Disk == s.ShareType || ShareType.Disk != match.ShareType) + match = s; + } + } + } + + return match; + } + } + + #endregion + + /// + /// Copy this collection to an array + /// + /// + /// + public void CopyTo(Share[] array, int index) + { + InnerList.CopyTo(array, index); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Library/LibraryManager.cs b/MediaBrowser.Controller/Library/LibraryManager.cs index 95a11e8fe..4087f9ef8 100644 --- a/MediaBrowser.Controller/Library/LibraryManager.cs +++ b/MediaBrowser.Controller/Library/LibraryManager.cs @@ -369,7 +369,7 @@ namespace MediaBrowser.Controller.Library /// The cancellation token. /// The progress. /// Task. - internal async Task ValidatePeople(CancellationToken cancellationToken, IProgress progress) + internal async Task ValidatePeople(CancellationToken cancellationToken, IProgress progress) { // Clear the IBN cache ImagesByNameItemCache.Clear(); @@ -422,14 +422,14 @@ namespace MediaBrowser.Controller.Library double percent = numComplete; percent /= people.Count; - progress.Report(new TaskProgress { PercentComplete = 100 * percent }); + progress.Report(100 * percent); } })); } await Task.WhenAll(tasks).ConfigureAwait(false); - progress.Report(new TaskProgress { PercentComplete = 100 }); + progress.Report(100); _logger.Info("People validation complete"); } @@ -440,17 +440,17 @@ namespace MediaBrowser.Controller.Library /// The progress. /// The cancellation token. /// Task. - internal async Task ValidateMediaLibrary(IProgress progress, CancellationToken cancellationToken) + internal async Task ValidateMediaLibrary(IProgress progress, CancellationToken cancellationToken) { _logger.Info("Validating media library"); await Kernel.RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); // Start by just validating the children of the root, but go no further - await Kernel.RootFolder.ValidateChildren(new Progress { }, cancellationToken, recursive: false); + await Kernel.RootFolder.ValidateChildren(new Progress { }, cancellationToken, recursive: false); // Validate only the collection folders for each user, just to make them available as quickly as possible - var userCollectionFolderTasks = Kernel.Users.AsParallel().Select(user => user.ValidateCollectionFolders(new Progress { }, cancellationToken)); + var userCollectionFolderTasks = Kernel.Users.AsParallel().Select(user => user.ValidateCollectionFolders(new Progress { }, cancellationToken)); await Task.WhenAll(userCollectionFolderTasks).ConfigureAwait(false); // Now validate the entire media library @@ -458,7 +458,7 @@ namespace MediaBrowser.Controller.Library foreach (var user in Kernel.Users) { - await user.ValidateMediaLibrary(new Progress { }, cancellationToken).ConfigureAwait(false); + await user.ValidateMediaLibrary(new Progress { }, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index c2b11ae32..0bf3e6c1f 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -67,7 +67,6 @@ - @@ -106,6 +105,7 @@ + diff --git a/MediaBrowser.Controller/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Controller/ScheduledTasks/ChapterImagesTask.cs index 21f1bce5a..d3d30ed77 100644 --- a/MediaBrowser.Controller/ScheduledTasks/ChapterImagesTask.cs +++ b/MediaBrowser.Controller/ScheduledTasks/ChapterImagesTask.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Controller.ScheduledTasks /// The cancellation token. /// The progress. /// Task. - protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) { var videos = Kernel.RootFolder.RecursiveChildren.OfType