From cfb4a33b6eb05db67406010299dfb883de9b60ed Mon Sep 17 00:00:00 2001 From: sr55 Date: Fri, 5 Jul 2019 23:11:04 +0100 Subject: [PATCH] WinGui: Build out code for active monitoring of storage and battery power on the system. (Similar to what the LinUI does) - Automatic pause on "Low" or "Critical" battery alarms. The % level is set in Windows power settings. Automatic Resume when AC returns, if it was paused by an alarm. - Automatic encode pause when destination drive drops below 2GB. (May make this a preference set later) - Behaviour of pause queue on low disk space with a user defined level in preferences is unchanged. #2109 #2181 --- win/CS/HandBrakeWPF/HandBrakeWPF.csproj | 2 + .../Properties/Resources.Designer.cs | 36 +++++ win/CS/HandBrakeWPF/Properties/Resources.resx | 12 ++ .../Services/Encode/Interfaces/IEncode.cs | 6 + .../HandBrakeWPF/Services/Encode/LibEncode.cs | 11 ++ .../Services/Interfaces/ISystemService.cs | 16 ++ .../Services/Queue/QueueService.cs | 2 +- win/CS/HandBrakeWPF/Services/SystemService.cs | 143 ++++++++++++++++++ .../HandBrakeWPF/Startup/AppBootstrapper.cs | 3 +- win/CS/HandBrakeWPF/UserSettingConstants.cs | 3 +- win/CS/HandBrakeWPF/Utilities/Win32.cs | 43 ++++++ .../HandBrakeWPF/ViewModels/MainViewModel.cs | 30 +++- .../ViewModels/OptionsViewModel.cs | 4 +- .../HandBrakeWPF/ViewModels/QueueViewModel.cs | 2 +- win/CS/HandBrakeWPF/defaultsettings.xml | 8 + 15 files changed, 308 insertions(+), 13 deletions(-) create mode 100644 win/CS/HandBrakeWPF/Services/Interfaces/ISystemService.cs create mode 100644 win/CS/HandBrakeWPF/Services/SystemService.cs diff --git a/win/CS/HandBrakeWPF/HandBrakeWPF.csproj b/win/CS/HandBrakeWPF/HandBrakeWPF.csproj index 232e4f3d9..11f9682df 100644 --- a/win/CS/HandBrakeWPF/HandBrakeWPF.csproj +++ b/win/CS/HandBrakeWPF/HandBrakeWPF.csproj @@ -218,6 +218,7 @@ + @@ -253,6 +254,7 @@ + diff --git a/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs b/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs index 892c484be..9ad37148d 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs +++ b/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs @@ -5191,6 +5191,42 @@ namespace HandBrakeWPF.Properties { } } + /// + /// Looks up a localized string similar to AC Mains power detected. Resuming encode... ({0} %). + /// + public static string SystemService_ACMains { + get { + return ResourceManager.GetString("SystemService_ACMains", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to System Battery Critical! ({0} %). + /// + public static string SystemService_CriticalBattery { + get { + return ResourceManager.GetString("SystemService_CriticalBattery", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to System Battery Low! ({0} %). Your encode has been paused to protect the system. System sleep is set to allowed!. + /// + public static string SystemService_LowBatteryLog { + get { + return ResourceManager.GetString("SystemService_LowBatteryLog", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remaining drive storage has dropped below {0} GB on the destination drive. Pausing encode.... + /// + public static string SystemService_LowDiskSpaceLog { + get { + return ResourceManager.GetString("SystemService_LowDiskSpaceLog", resourceCulture); + } + } + /// /// Looks up a localized string similar to {1}%, Pass {2} of {3} ///Remaining Time: {4}. diff --git a/win/CS/HandBrakeWPF/Properties/Resources.resx b/win/CS/HandBrakeWPF/Properties/Resources.resx index c850971d0..e707d01e7 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.resx @@ -2020,4 +2020,16 @@ Where supported, any user presets will have been imported. GB + + AC Mains power detected. Resuming encode... ({0} %) + + + System Battery Critical! ({0} %) + + + System Battery Low! ({0} %). Your encode has been paused to protect the system. System sleep is set to allowed! + + + Remaining drive storage has dropped below {0} GB on the destination drive. Pausing encode... + \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/Services/Encode/Interfaces/IEncode.cs b/win/CS/HandBrakeWPF/Services/Encode/Interfaces/IEncode.cs index 489f46925..871f31615 100644 --- a/win/CS/HandBrakeWPF/Services/Encode/Interfaces/IEncode.cs +++ b/win/CS/HandBrakeWPF/Services/Encode/Interfaces/IEncode.cs @@ -94,5 +94,11 @@ namespace HandBrakeWPF.Services.Encode.Interfaces /// Kill the process /// void Stop(); + + + /// + /// Get a copy of the Active job + /// + EncodeTask GetActiveJob(); } } \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs b/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs index 3b9e01280..f3ff75fcc 100644 --- a/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs +++ b/win/CS/HandBrakeWPF/Services/Encode/LibEncode.cs @@ -169,6 +169,17 @@ namespace HandBrakeWPF.Services.Encode } } + public EncodeTask GetActiveJob() + { + if (this.currentTask != null) + { + EncodeTask task = new EncodeTask(this.currentTask); // Decouple our current copy. + return task; + } + + return null; + } + #region HandBrakeInstance Event Handlers. /// diff --git a/win/CS/HandBrakeWPF/Services/Interfaces/ISystemService.cs b/win/CS/HandBrakeWPF/Services/Interfaces/ISystemService.cs new file mode 100644 index 000000000..79461a8ec --- /dev/null +++ b/win/CS/HandBrakeWPF/Services/Interfaces/ISystemService.cs @@ -0,0 +1,16 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// +// +// Defines +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.Services.Interfaces +{ + public interface ISystemService + { + void Start(); + } +} \ No newline at end of file diff --git a/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs b/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs index 3d8af90b3..90c9d7ded 100644 --- a/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs +++ b/win/CS/HandBrakeWPF/Services/Queue/QueueService.cs @@ -485,7 +485,7 @@ namespace HandBrakeWPF.Services.Queue QueueTask job = this.GetNextJobForProcessing(); if (job != null) { - if (this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspace) && !DriveUtilities.HasMinimumDiskSpace(job.Task.Destination, this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspaceLevel))) + if (this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspace) && !DriveUtilities.HasMinimumDiskSpace(job.Task.Destination, this.userSettingService.GetUserSetting(UserSettingConstants.PauseQueueOnLowDiskspaceLevel))) { LogService.GetLogger().LogMessage(Resources.PauseOnLowDiskspace, LogMessageType.ScanOrEncode, LogLevel.Info); job.Status = QueueItemStatus.Waiting; diff --git a/win/CS/HandBrakeWPF/Services/SystemService.cs b/win/CS/HandBrakeWPF/Services/SystemService.cs new file mode 100644 index 000000000..4bbc89f94 --- /dev/null +++ b/win/CS/HandBrakeWPF/Services/SystemService.cs @@ -0,0 +1,143 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// +// +// Monitor the system health for common problems that will directly impact encodes. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrakeWPF.Services +{ + using System; + using System.Runtime.CompilerServices; + using System.Timers; + + using HandBrakeWPF.Properties; + using HandBrakeWPF.Services.Encode.Interfaces; + using HandBrakeWPF.Services.Encode.Model; + using HandBrakeWPF.Services.Interfaces; + using HandBrakeWPF.Services.Logging; + using HandBrakeWPF.Services.Logging.Interfaces; + using HandBrakeWPF.Services.Logging.Model; + using HandBrakeWPF.Utilities; + + using Ookii.Dialogs.Wpf; + + public class SystemService : ISystemService + { + private readonly IUserSettingService userSettingService; + private readonly IEncode encodeService; + private readonly ILog log = LogService.GetLogger(); + private Timer pollTimer; + + private bool criticalStateHit = false; + private bool lowStateHit = false; + private bool lowPowerPause = false; + private bool storageLowPause = false; + + public SystemService(IUserSettingService userSettingService, IEncode encodeService) + { + this.userSettingService = userSettingService; + this.encodeService = encodeService; + } + + public void Start() + { + if (this.pollTimer == null) + { + this.pollTimer = new Timer(); + this.pollTimer.Interval = 10000; // Check every 10 seconds. + this.pollTimer.Elapsed += (o, e) => + { + this.CheckSystem(); + }; + + this.pollTimer.Start(); + } + } + + private void CheckSystem() + { + this.PowerCheck(); + this.StorageCheck(); + } + + private void StorageCheck() + { + string directory = this.encodeService.GetActiveJob()?.Destination; + if (!string.IsNullOrEmpty(directory) && this.encodeService.IsEncoding) + { + long lowLevel = this.userSettingService.GetUserSetting(UserSettingConstants.PauseEncodeOnLowDiskspaceLevel); + if (!this.storageLowPause && this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspace) && !DriveUtilities.HasMinimumDiskSpace(directory, lowLevel)) + { + LogService.GetLogger().LogMessage( + string.Format( + Resources.SystemService_LowDiskSpaceLog, + lowLevel / 1000 / 1000 / 1000), + LogMessageType.Application, + LogLevel.Info); + this.encodeService.Pause(); + this.storageLowPause = true; + } + } + } + + private void PowerCheck() + { + Win32.PowerState state = Win32.PowerState.GetPowerState(); + + if (state == null || state.BatteryFlag == Win32.BatteryFlag.NoSystemBattery || state.BatteryFlag == Win32.BatteryFlag.Unknown) + { + return; // Only run if we have a battery. + } + + if (state.ACLineStatus == Win32.ACLineStatus.Offline && state.BatteryFlag == Win32.BatteryFlag.Low && !this.lowStateHit) + { + if (this.encodeService.IsEncoding && !this.encodeService.IsPasued) + { + this.lowPowerPause = true; + this.encodeService.Pause(); + } + + Win32.AllowSleep(); + + this.ServiceLogMessage(string.Format(Resources.SystemService_LowBatteryLog, state.BatteryLifePercent)); + this.lowStateHit = true; + } + + if (state.ACLineStatus == Win32.ACLineStatus.Offline && state.BatteryFlag == Win32.BatteryFlag.Critical && !this.criticalStateHit) + { + if (this.encodeService.IsEncoding && !this.encodeService.IsPasued) + { + this.lowPowerPause = true; + this.encodeService.Pause(); // In case we missed the low state! + } + + Win32.AllowSleep(); + + this.ServiceLogMessage(string.Format(Resources.SystemService_CriticalBattery, state.BatteryLifePercent)); + this.criticalStateHit = true; + } + + // Reset the flags when we start charging. + if (state.ACLineStatus == Win32.ACLineStatus.Online && state.BatteryFlag >= Win32.BatteryFlag.Low) + { + if (this.lowPowerPause && this.encodeService.IsPasued) + { + this.encodeService.Resume(); + this.ServiceLogMessage(string.Format(Resources.SystemService_ACMains, state.BatteryLifePercent)); + } + + this.lowPowerPause = false; + this.criticalStateHit = false; + this.lowStateHit = false; + } + } + + private void ServiceLogMessage(string message) + { + this.log.LogMessage(string.Format("{0}# {1}{0}", Environment.NewLine, message), LogMessageType.Application, LogLevel.Info); + } + } +} diff --git a/win/CS/HandBrakeWPF/Startup/AppBootstrapper.cs b/win/CS/HandBrakeWPF/Startup/AppBootstrapper.cs index 4f1f6c159..69be73360 100644 --- a/win/CS/HandBrakeWPF/Startup/AppBootstrapper.cs +++ b/win/CS/HandBrakeWPF/Startup/AppBootstrapper.cs @@ -79,7 +79,8 @@ namespace HandBrakeWPF.Startup this.container.Singleton(); this.container.Singleton(); this.container.Singleton(); - + this.container.Singleton(); + // Tab Components this.container.Singleton(); this.container.Singleton(); diff --git a/win/CS/HandBrakeWPF/UserSettingConstants.cs b/win/CS/HandBrakeWPF/UserSettingConstants.cs index 1f6ed72db..c5c9d5ad7 100644 --- a/win/CS/HandBrakeWPF/UserSettingConstants.cs +++ b/win/CS/HandBrakeWPF/UserSettingConstants.cs @@ -36,7 +36,8 @@ namespace HandBrakeWPF public const string SendFileToArgs = "SendFileToArgs"; public const string PreventSleep = "PreventSleep"; public const string PauseOnLowDiskspace = "PauseOnLowDiskspace"; - public const string PauseOnLowDiskspaceLevel = "LowDiskSpaceWarningLevelInBytes"; + public const string PauseQueueOnLowDiskspaceLevel = "LowDiskSpaceWarningLevelInBytes"; + public const string PauseEncodeOnLowDiskspaceLevel = "LowDiskSpaceEncodePauseLevelInBytes"; public const string RemovePunctuation = "RemovePunctuation"; public const string ShowPresetPanel = "ShowPresetPanelOption"; public const string ResetWhenDoneAction = "ResetWhenDoneAction"; diff --git a/win/CS/HandBrakeWPF/Utilities/Win32.cs b/win/CS/HandBrakeWPF/Utilities/Win32.cs index 12154331a..f999e7628 100644 --- a/win/CS/HandBrakeWPF/Utilities/Win32.cs +++ b/win/CS/HandBrakeWPF/Utilities/Win32.cs @@ -195,5 +195,48 @@ namespace HandBrakeWPF.Utilities { executor = marshaller; } + + [StructLayout(LayoutKind.Sequential)] + public class PowerState + { + public ACLineStatus ACLineStatus; + public BatteryFlag BatteryFlag; + public Byte BatteryLifePercent; + public Byte SystemStatusFlag; + public Int32 BatteryLifeTime; + public Int32 BatteryFullLifeTime; + + public static PowerState GetPowerState() + { + PowerState state = new PowerState(); + if (GetSystemPowerStatusRef(state)) + { + return state; + } + + return null; + } + + [DllImport("Kernel32", EntryPoint = "GetSystemPowerStatus")] + private static extern bool GetSystemPowerStatusRef(PowerState sps); + } + + public enum ACLineStatus : byte + { + Offline = 0, + Online = 1, + Unknown = 255 + } + + public enum BatteryFlag : byte + { + High = 1, + Low = 2, + Critical = 4, + Charging = 8, + NoSystemBattery = 128, + Unknown = 255 + } } } + diff --git a/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs index c1cd1aafb..731a2e090 100644 --- a/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs @@ -110,12 +110,26 @@ namespace HandBrakeWPF.ViewModels /// The viewmodel for HandBrakes main window. /// /// whenDoneService must be a serivce here! - public MainViewModel(IUserSettingService userSettingService, IScan scanService, IPresetService presetService, - IErrorService errorService, IUpdateService updateService, - IPrePostActionService whenDoneService, IWindowManager windowManager, IPictureSettingsViewModel pictureSettingsViewModel, IVideoViewModel videoViewModel, ISummaryViewModel summaryViewModel, - IFiltersViewModel filtersViewModel, IAudioViewModel audioViewModel, ISubtitlesViewModel subtitlesViewModel, - IChaptersViewModel chaptersViewModel, IStaticPreviewViewModel staticPreviewViewModel, - IQueueViewModel queueViewModel, IMetaDataViewModel metaDataViewModel, INotifyIconService notifyIconService) + public MainViewModel( + IUserSettingService userSettingService, + IScan scanService, + IPresetService presetService, + IErrorService errorService, + IUpdateService updateService, + IPrePostActionService whenDoneService, + IWindowManager windowManager, + IPictureSettingsViewModel pictureSettingsViewModel, + IVideoViewModel videoViewModel, + ISummaryViewModel summaryViewModel, + IFiltersViewModel filtersViewModel, + IAudioViewModel audioViewModel, + ISubtitlesViewModel subtitlesViewModel, + IChaptersViewModel chaptersViewModel, + IStaticPreviewViewModel staticPreviewViewModel, + IQueueViewModel queueViewModel, + IMetaDataViewModel metaDataViewModel, + INotifyIconService notifyIconService, + ISystemService systemService) : base(userSettingService) { this.scanService = scanService; @@ -184,6 +198,8 @@ namespace HandBrakeWPF.ViewModels // Setup Commands this.QueueCommand = new QueueCommands(this.QueueViewModel); + // Monitor the system. + systemService.Start(); } #region View Model Properties @@ -1377,7 +1393,7 @@ namespace HandBrakeWPF.ViewModels if (!DriveUtilities.HasMinimumDiskSpace( this.Destination, - this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspaceLevel))) + this.userSettingService.GetUserSetting(UserSettingConstants.PauseQueueOnLowDiskspaceLevel))) { return new AddQueueError(Resources.Main_LowDiskspace, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); } diff --git a/win/CS/HandBrakeWPF/ViewModels/OptionsViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/OptionsViewModel.cs index 4375e2a08..b8d9b89f2 100644 --- a/win/CS/HandBrakeWPF/ViewModels/OptionsViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/OptionsViewModel.cs @@ -1582,7 +1582,7 @@ namespace HandBrakeWPF.ViewModels this.PreventSleep = userSettingService.GetUserSetting(UserSettingConstants.PreventSleep); this.PauseOnLowDiskspace = userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspace); - this.PauseOnLowDiskspaceLevel = this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspaceLevel); + this.PauseOnLowDiskspaceLevel = this.userSettingService.GetUserSetting(UserSettingConstants.PauseQueueOnLowDiskspaceLevel); // Log Verbosity Level this.logVerbosityOptions.Clear(); @@ -1723,7 +1723,7 @@ namespace HandBrakeWPF.ViewModels this.userSettingService.SetUserSetting(UserSettingConstants.ProcessPriority, this.SelectedPriority); this.userSettingService.SetUserSetting(UserSettingConstants.PreventSleep, this.PreventSleep); this.userSettingService.SetUserSetting(UserSettingConstants.PauseOnLowDiskspace, this.PauseOnLowDiskspace); - this.userSettingService.SetUserSetting(UserSettingConstants.PauseOnLowDiskspaceLevel, this.PauseOnLowDiskspaceLevel); + this.userSettingService.SetUserSetting(UserSettingConstants.PauseQueueOnLowDiskspaceLevel, this.PauseOnLowDiskspaceLevel); this.userSettingService.SetUserSetting(UserSettingConstants.Verbosity, this.SelectedVerbosity); this.userSettingService.SetUserSetting(UserSettingConstants.SaveLogWithVideo, this.CopyLogToEncodeDirectory); this.userSettingService.SetUserSetting(UserSettingConstants.SaveLogToCopyDirectory, this.CopyLogToSepcficedLocation); diff --git a/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs index 362f10ec6..f82ec9621 100644 --- a/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/QueueViewModel.cs @@ -487,7 +487,7 @@ namespace HandBrakeWPF.ViewModels var firstOrDefault = this.QueueTasks.FirstOrDefault(s => s.Status == QueueItemStatus.Waiting); if (firstOrDefault != null && !DriveUtilities.HasMinimumDiskSpace(firstOrDefault.Task.Destination, - this.userSettingService.GetUserSetting(UserSettingConstants.PauseOnLowDiskspaceLevel))) + this.userSettingService.GetUserSetting(UserSettingConstants.PauseQueueOnLowDiskspaceLevel))) { this.errorService.ShowMessageBox(Resources.Main_LowDiskspace, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); return; diff --git a/win/CS/HandBrakeWPF/defaultsettings.xml b/win/CS/HandBrakeWPF/defaultsettings.xml index 91ecbb494..08ef76557 100644 --- a/win/CS/HandBrakeWPF/defaultsettings.xml +++ b/win/CS/HandBrakeWPF/defaultsettings.xml @@ -408,6 +408,14 @@ 10000000000 + + + LowDiskSpaceEncodePauseLevelInBytes + + + 2000000000 + + ForcePresetReset -- 2.40.0