From 657e90c98b4c3c8a4581fd94bd1c41e843c26d19 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 21 Jan 2016 12:29:14 -0500 Subject: [PATCH] support system wake on recording schedule --- .../MediaBrowser.Controller.csproj | 1 + .../Power/IPowerManagement.cs | 13 ++++++ .../LiveTv/EmbyTV/EmbyTV.cs | 5 ++- .../LiveTv/EmbyTV/TimerManager.cs | 30 +++++++++++++- .../Native/BaseMonoApp.cs | 14 +++++++ .../ApplicationHost.cs | 2 + .../INativeApp.cs | 7 ++++ .../MediaBrowser.ServerApplication.csproj | 1 + .../Native/WindowsApp.cs | 6 +++ .../Native/WindowsPowerManagement.cs | 40 +++++++++++++++++++ 10 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 MediaBrowser.Controller/Power/IPowerManagement.cs create mode 100644 MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 774e51bd3..9c17c8d9b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -266,6 +266,7 @@ + diff --git a/MediaBrowser.Controller/Power/IPowerManagement.cs b/MediaBrowser.Controller/Power/IPowerManagement.cs new file mode 100644 index 000000000..faa289695 --- /dev/null +++ b/MediaBrowser.Controller/Power/IPowerManagement.cs @@ -0,0 +1,13 @@ +using System; + +namespace MediaBrowser.Controller.Power +{ + public interface IPowerManagement + { + /// + /// Schedules the wake. + /// + /// The UTC time. + void ScheduleWake(DateTime utcTime); + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index dbab7fef7..e78da617a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -28,6 +28,7 @@ using System.Threading; using System.Threading.Tasks; using CommonIO; using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Power; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { @@ -55,7 +56,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public static EmbyTV Current; - public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder) + public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement) { Current = this; @@ -75,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _recordingProvider = new ItemDataProvider(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "recordings"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)); _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); - _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers")); + _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger); _timerProvider.TimerFired += _timerProvider_TimerFired; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 80bb671fa..ca20379b6 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -8,19 +8,23 @@ using System.Collections.Concurrent; using System.Linq; using System.Threading; using CommonIO; -using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Power; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { public class TimerManager : ItemDataProvider { private readonly ConcurrentDictionary _timers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly IPowerManagement _powerManagement; + private readonly ILogger _logger; public event EventHandler> TimerFired; - public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath) + public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, IPowerManagement powerManagement, ILogger logger1) : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) { + _powerManagement = powerManagement; + _logger = logger1; } public void RestartTimers() @@ -58,6 +62,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow; timer.Change(timespan, TimeSpan.Zero); + ScheduleWake(item); } else { @@ -74,6 +79,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV base.Add(item); AddTimer(item); + ScheduleWake(item); } private void AddTimer(TimerInfo item) @@ -91,6 +97,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV StartTimer(item, timerLength); } + private void ScheduleWake(TimerInfo info) + { + var startDate = RecordingHelper.GetStartTime(info).AddMinutes(-5); + _logger.Info("Scheduling system wake timer at {0} (UTC)", startDate); + + try + { + _powerManagement.ScheduleWake(startDate); + _logger.Info("Scheduled system wake timer at {0} (UTC)", startDate); + } + catch (NotImplementedException) + { + + } + catch (Exception ex) + { + _logger.ErrorException("Error scheduling wake timer", ex); + } + } + public void StartTimer(TimerInfo item, TimeSpan length) { StopTimer(item); diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs index 31d4592c8..e57a651c0 100644 --- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; +using MediaBrowser.Controller.Power; namespace MediaBrowser.Server.Mono.Native { @@ -203,5 +204,18 @@ namespace MediaBrowser.Server.Mono.Native public string sysname = string.Empty; public string machine = string.Empty; } + + public IPowerManagement GetPowerManagement() + { + return new NullPowerManagement(); + } + } + + public class NullPowerManagement : IPowerManagement + { + public void ScheduleWake(DateTime utcTime) + { + throw new NotImplementedException(); + } } } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index fb11b8d65..00ba2d172 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -534,6 +534,8 @@ namespace MediaBrowser.Server.Startup.Common EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager); RegisterSingleInstance(EncodingManager); + RegisterSingleInstance(NativeApp.GetPowerManagement()); + var sharingRepo = new SharingRepository(LogManager, ApplicationPaths); await sharingRepo.Initialize().ConfigureAwait(false); RegisterSingleInstance(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this)); diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs index 597caf34c..75b38d0c4 100644 --- a/MediaBrowser.Server.Startup.Common/INativeApp.cs +++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs @@ -2,6 +2,7 @@ using MediaBrowser.Model.Logging; using System.Collections.Generic; using System.Reflection; +using MediaBrowser.Controller.Power; namespace MediaBrowser.Server.Startup.Common { @@ -90,5 +91,11 @@ namespace MediaBrowser.Server.Startup.Common /// Prevents the system stand by. /// void PreventSystemStandby(); + + /// + /// Gets the power management. + /// + /// IPowerManagement. + IPowerManagement GetPowerManagement(); } } diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index c62983192..80e56d6e1 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -120,6 +120,7 @@ + diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index ceab5379d..9ef9c7d50 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -6,6 +6,7 @@ using MediaBrowser.ServerApplication.Networking; using System.Collections.Generic; using System.Reflection; using CommonIO; +using MediaBrowser.Controller.Power; namespace MediaBrowser.ServerApplication.Native { @@ -117,5 +118,10 @@ namespace MediaBrowser.ServerApplication.Native { Standby.PreventSystemStandby(); } + + public IPowerManagement GetPowerManagement() + { + return new WindowsPowerManagement(); + } } } diff --git a/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs b/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs new file mode 100644 index 000000000..6bd78553b --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs @@ -0,0 +1,40 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Threading; +using MediaBrowser.Controller.Power; +using Microsoft.Win32.SafeHandles; + +namespace MediaBrowser.ServerApplication.Native +{ + public class WindowsPowerManagement : IPowerManagement + { + [DllImport("kernel32.dll")] + public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetWaitableTimer(SafeWaitHandle hTimer, [In] ref long pDueTime, int lPeriod, IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, bool fResume); + + public void ScheduleWake(DateTime utcTime) + { + long duetime = utcTime.ToFileTime(); + + using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, "MyWaitabletimer")) + { + if (SetWaitableTimer(handle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true)) + { + using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset)) + { + wh.SafeWaitHandle = handle; + wh.WaitOne(); + } + } + else + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + } + } +}