diff options
Diffstat (limited to 'Emby.Server.Implementations/ScheduledTasks')
5 files changed, 679 insertions, 0 deletions
diff --git a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs new file mode 100644 index 000000000..d75815847 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -0,0 +1,192 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks +{ + /// <summary> + /// Class ChapterImagesTask + /// </summary> + class ChapterImagesTask : IScheduledTask + { + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + private readonly IItemRepository _itemRepo; + + private readonly IApplicationPaths _appPaths; + + private readonly IEncodingManager _encodingManager; + private readonly IFileSystem _fileSystem; + + /// <summary> + /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class. + /// </summary> + public ChapterImagesTask(ILogManager logManager, ILibraryManager libraryManager, IItemRepository itemRepo, IApplicationPaths appPaths, IEncodingManager encodingManager, IFileSystem fileSystem) + { + _logger = logManager.GetLogger(GetType().Name); + _libraryManager = libraryManager; + _itemRepo = itemRepo; + _appPaths = appPaths; + _encodingManager = encodingManager; + _fileSystem = fileSystem; + } + + /// <summary> + /// Creates the triggers that define when the task will run + /// </summary> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + return new[] { + + new TaskTriggerInfo + { + Type = TaskTriggerInfo.TriggerDaily, + TimeOfDayTicks = TimeSpan.FromHours(1).Ticks, + MaxRuntimeMs = Convert.ToInt32(TimeSpan.FromHours(4).TotalMilliseconds) + } + }; + } + + public string Key + { + get { return "RefreshChapterImages"; } + } + + /// <summary> + /// Returns the task to be executed + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + var videos = _libraryManager.GetItemList(new InternalItemsQuery + { + MediaTypes = new[] { MediaType.Video }, + IsFolder = false, + Recursive = true + }) + .OfType<Video>() + .ToList(); + + var numComplete = 0; + + var failHistoryPath = Path.Combine(_appPaths.CachePath, "chapter-failures.txt"); + + List<string> previouslyFailedImages; + + try + { + previouslyFailedImages = _fileSystem.ReadAllText(failHistoryPath) + .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) + .ToList(); + } + catch (FileNotFoundException) + { + previouslyFailedImages = new List<string>(); + } + catch (IOException) + { + previouslyFailedImages = new List<string>(); + } + + foreach (var video in videos) + { + cancellationToken.ThrowIfCancellationRequested(); + + var key = video.Path + video.DateModified.Ticks; + + var extract = !previouslyFailedImages.Contains(key, StringComparer.OrdinalIgnoreCase); + + try + { + var chapters = _itemRepo.GetChapters(video.Id).ToList(); + + var success = await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions + { + SaveChapters = true, + ExtractImages = extract, + Video = video, + Chapters = chapters + + }, CancellationToken.None); + + if (!success) + { + previouslyFailedImages.Add(key); + + var parentPath = Path.GetDirectoryName(failHistoryPath); + + _fileSystem.CreateDirectory(parentPath); + + _fileSystem.WriteAllText(failHistoryPath, string.Join("|", previouslyFailedImages.ToArray())); + } + + numComplete++; + double percent = numComplete; + percent /= videos.Count; + + progress.Report(100 * percent); + } + catch (ObjectDisposedException) + { + break; + } + } + } + + /// <summary> + /// Gets the name of the task + /// </summary> + /// <value>The name.</value> + public string Name + { + get + { + return "Chapter image extraction"; + } + } + + /// <summary> + /// Gets the description. + /// </summary> + /// <value>The description.</value> + public string Description + { + get { return "Creates thumbnails for videos that have chapters."; } + } + + /// <summary> + /// Gets the category. + /// </summary> + /// <value>The category.</value> + public string Category + { + get + { + return "Library"; + } + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs new file mode 100644 index 000000000..51122226b --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs @@ -0,0 +1,94 @@ +using MediaBrowser.Controller.Library; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks +{ + /// <summary> + /// Class PeopleValidationTask + /// </summary> + public class PeopleValidationTask : IScheduledTask + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + private readonly IServerApplicationHost _appHost; + + /// <summary> + /// Initializes a new instance of the <see cref="PeopleValidationTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost) + { + _libraryManager = libraryManager; + _appHost = appHost; + } + + /// <summary> + /// Creates the triggers that define when the task will run + /// </summary> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + // Randomize the default start hour because this operation can really hammer internet metadata providers + var startHour = new Random(_appHost.SystemId.GetHashCode()).Next(0, 8); + + return new[] { + + // Every so often + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerDaily, TimeOfDayTicks = TimeSpan.FromHours(startHour).Ticks} + }; + } + + public string Key + { + get { return "RefreshPeople"; } + } + + /// <summary> + /// Returns the task to be executed + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + return _libraryManager.ValidatePeople(cancellationToken, progress); + } + + /// <summary> + /// Gets the name of the task + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return "Refresh people"; } + } + + /// <summary> + /// Gets the description. + /// </summary> + /// <value>The description.</value> + public string Description + { + get { return "Updates metadata for actors and directors in your media library."; } + } + + /// <summary> + /// Gets the category. + /// </summary> + /// <value>The category.</value> + public string Category + { + get + { + return "Library"; + } + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs new file mode 100644 index 000000000..e619b6864 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs @@ -0,0 +1,140 @@ +using MediaBrowser.Common; +using MediaBrowser.Common.Updates; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks +{ + /// <summary> + /// Plugin Update Task + /// </summary> + public class PluginUpdateTask : IScheduledTask + { + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + private readonly IInstallationManager _installationManager; + + private readonly IApplicationHost _appHost; + + public PluginUpdateTask(ILogger logger, IInstallationManager installationManager, IApplicationHost appHost) + { + _logger = logger; + _installationManager = installationManager; + _appHost = appHost; + } + + /// <summary> + /// Creates the triggers that define when the task will run + /// </summary> + /// <returns>IEnumerable{BaseTaskTrigger}.</returns> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + return new[] { + + // At startup + new TaskTriggerInfo {Type = TaskTriggerInfo.TriggerStartup}, + + // Every so often + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + }; + } + + public string Key + { + get { return "PluginUpdates"; } + } + + /// <summary> + /// Update installed plugins + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + progress.Report(0); + + var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, true, cancellationToken).ConfigureAwait(false)).ToList(); + + progress.Report(10); + + var numComplete = 0; + + // Create tasks for each one + var tasks = packagesToInstall.Select(i => Task.Run(async () => + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + await _installationManager.InstallPackage(i, true, new Progress<double>(), cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // InstallPackage has it's own inner cancellation token, so only throw this if it's ours + if (cancellationToken.IsCancellationRequested) + { + throw; + } + } + catch (HttpException ex) + { + _logger.ErrorException("Error downloading {0}", ex, i.name); + } + catch (IOException ex) + { + _logger.ErrorException("Error updating {0}", ex, i.name); + } + + // Update progress + lock (progress) + { + numComplete++; + double percent = numComplete; + percent /= packagesToInstall.Count; + + progress.Report(90 * percent + 10); + } + })); + + cancellationToken.ThrowIfCancellationRequested(); + + await Task.WhenAll(tasks).ConfigureAwait(false); + + progress.Report(100); + } + + /// <summary> + /// Gets the name of the task + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return "Check for plugin updates"; } + } + + /// <summary> + /// Gets the description. + /// </summary> + /// <value>The description.</value> + public string Description + { + get { return "Downloads and installs updates for plugins that are configured to update automatically."; } + } + + public string Category + { + get { return "Application"; } + } + } +}
\ No newline at end of file diff --git a/Emby.Server.Implementations/ScheduledTasks/RefreshIntrosTask.cs b/Emby.Server.Implementations/ScheduledTasks/RefreshIntrosTask.cs new file mode 100644 index 000000000..749233fa1 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/RefreshIntrosTask.cs @@ -0,0 +1,105 @@ +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.ScheduledTasks +{ + /// <summary> + /// Class RefreshIntrosTask + /// </summary> + public class RefreshIntrosTask : ILibraryPostScanTask + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + private readonly IFileSystem _fileSystem; + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshIntrosTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="fileSystem">The file system.</param> + public RefreshIntrosTask(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem) + { + _libraryManager = libraryManager; + _logger = logger; + _fileSystem = fileSystem; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var files = _libraryManager.GetAllIntroFiles().ToList(); + + var numComplete = 0; + + foreach (var file in files) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + await RefreshIntro(file, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error refreshing intro {0}", ex, file); + } + + numComplete++; + double percent = numComplete; + percent /= files.Count; + progress.Report(percent * 100); + } + } + + /// <summary> + /// Refreshes the intro. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task RefreshIntro(string path, CancellationToken cancellationToken) + { + var item = _libraryManager.ResolvePath(_fileSystem.GetFileSystemInfo(path)); + + if (item == null) + { + _logger.Error("Intro resolver returned null for {0}", path); + return; + } + + var dbItem = _libraryManager.GetItemById(item.Id); + + if (dbItem != null) + { + item = dbItem; + } + + // Force the save if it's a new item + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/SystemUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/SystemUpdateTask.cs new file mode 100644 index 000000000..28fd8b68c --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/SystemUpdateTask.cs @@ -0,0 +1,148 @@ +using MediaBrowser.Common; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks +{ + /// <summary> + /// Plugin Update Task + /// </summary> + public class SystemUpdateTask : IScheduledTask + { + /// <summary> + /// The _app host + /// </summary> + private readonly IApplicationHost _appHost; + + /// <summary> + /// Gets or sets the configuration manager. + /// </summary> + /// <value>The configuration manager.</value> + private IConfigurationManager ConfigurationManager { get; set; } + /// <summary> + /// Gets or sets the logger. + /// </summary> + /// <value>The logger.</value> + private ILogger Logger { get; set; } + + /// <summary> + /// Initializes a new instance of the <see cref="SystemUpdateTask" /> class. + /// </summary> + /// <param name="appHost">The app host.</param> + /// <param name="configurationManager">The configuration manager.</param> + /// <param name="logger">The logger.</param> + public SystemUpdateTask(IApplicationHost appHost, IConfigurationManager configurationManager, ILogger logger) + { + _appHost = appHost; + ConfigurationManager = configurationManager; + Logger = logger; + } + + /// <summary> + /// Creates the triggers that define when the task will run + /// </summary> + /// <returns>IEnumerable{BaseTaskTrigger}.</returns> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + return new[] { + + // At startup + new TaskTriggerInfo {Type = TaskTriggerInfo.TriggerStartup}, + + // Every so often + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + }; + } + + /// <summary> + /// Returns the task to be executed + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + EventHandler<double> innerProgressHandler = (sender, e) => progress.Report(e * .1); + + // Create a progress object for the update check + var innerProgress = new Progress<double>(); + innerProgress.ProgressChanged += innerProgressHandler; + + var updateInfo = await _appHost.CheckForApplicationUpdate(cancellationToken, innerProgress).ConfigureAwait(false); + + // Release the event handler + innerProgress.ProgressChanged -= innerProgressHandler; + + progress.Report(10); + + if (!updateInfo.IsUpdateAvailable) + { + Logger.Debug("No application update available."); + progress.Report(100); + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (!_appHost.CanSelfUpdate) return; + + if (ConfigurationManager.CommonConfiguration.EnableAutoUpdate) + { + Logger.Info("Update Revision {0} available. Updating...", updateInfo.AvailableVersion); + + innerProgressHandler = (sender, e) => progress.Report(e * .9 + .1); + + innerProgress = new Progress<double>(); + innerProgress.ProgressChanged += innerProgressHandler; + + await _appHost.UpdateApplication(updateInfo.Package, cancellationToken, innerProgress).ConfigureAwait(false); + + // Release the event handler + innerProgress.ProgressChanged -= innerProgressHandler; + } + else + { + Logger.Info("A new version of " + _appHost.Name + " is available."); + } + + progress.Report(100); + } + + /// <summary> + /// Gets the name of the task + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return "Check for application updates"; } + } + + /// <summary> + /// Gets the description. + /// </summary> + /// <value>The description.</value> + public string Description + { + get { return "Downloads and installs application updates."; } + } + + /// <summary> + /// Gets the category. + /// </summary> + /// <value>The category.</value> + public string Category + { + get { return "Application"; } + } + + public string Key + { + get { return "SystemUpdateTask"; } + } + } +} |
