Merge pull request #6550 from duplicati/feature/add-power-mode-provider-selection

Add PowerMode provider selection
This commit is contained in:
Kenneth Skovhede
2025-10-20 08:25:29 +02:00
committed by GitHub
14 changed files with 528 additions and 63 deletions
@@ -0,0 +1,42 @@
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System;
namespace Duplicati.Library.Interface;
/// <summary>
/// Interface for a provider of power mode events
/// </summary>
public interface IPowerModeProvider : IDisposable
{
/// <summary>
/// Event that is triggered when the system is resuming from suspend
/// </summary>
Action? OnResume { get; set; }
/// <summary>
/// Event that is triggered when the system is suspending
/// </summary>
Action? OnSuspend { get; set; }
}
@@ -31,6 +31,7 @@ using Duplicati.Library.Utility;
using Duplicati.Library.AutoUpdater;
using Microsoft.Extensions.DependencyInjection;
using Duplicati.WebserverCore.Abstractions;
using Duplicati.Library.Snapshots;
#nullable enable
@@ -77,6 +78,7 @@ namespace Duplicati.Server.Database
public const string ADDITIONAL_REPORT_URL = "additional-report-url";
public const string BACKUP_LIST_SORT_ORDER = "backup-list-sort-order";
public const string DISABLE_API_EXTENSIONS = "disable-api-extensions";
public const string POWER_MODE_PROVIDER = "power-mode-provider";
}
private readonly Dictionary<string, string?> settings;
@@ -155,6 +157,7 @@ namespace Duplicati.Server.Database
provider?.GetRequiredService<EventPollNotify>()?.SignalServerSettingsUpdated();
// If throttle options were changed, update now
provider?.GetRequiredService<IQueueRunnerService>()?.GetCurrentTask()?.UpdateThrottleSpeeds(UploadSpeedLimit, DownloadSpeedLimit);
provider?.GetRequiredService<LiveControls>()?.UpdatePowerModeProvider();
}
// In case the usage reporter is enabled or disabled, refresh now
@@ -841,6 +844,24 @@ namespace Duplicati.Server.Database
}
}
public PowerModeProvider PowerModeProvider
{
get
{
var provider = settings[CONST.POWER_MODE_PROVIDER];
if (Enum.TryParse<PowerModeProvider>(provider, true, out var parsedProvider))
return parsedProvider;
return PowerModeProvider.Default;
}
set
{
lock (databaseConnection.m_lock)
settings[CONST.POWER_MODE_PROVIDER] = value == PowerModeProvider.Default ? null : value.ToString();
SaveSettings();
}
}
}
}
@@ -5,10 +5,6 @@
<Copyright>Copyright © 2025 Team Duplicati, MIT license</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Win32.SystemEvents" Version="9.0.6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\CommandLine\CLI\Duplicati.CommandLine.csproj" />
<ProjectReference Include="..\Common\Duplicati.Library.Common.csproj" />
+68 -53
View File
@@ -20,8 +20,9 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Runtime.Versioning;
using Duplicati.Library.Interface;
using Duplicati.Library.IO;
using Duplicati.Library.Snapshots;
using Duplicati.Server.Database;
namespace Duplicati.Server
@@ -131,6 +132,16 @@ namespace Duplicati.Server
/// </summary>
private readonly Connection m_connection;
/// <summary>
/// The power mode provider, if any
/// </summary>
private IPowerModeProvider m_powerModeProvider;
/// <summary>
/// The current power mode provider
/// </summary>
private PowerModeProvider m_currentPowerModeProvider = PowerModeProvider.None;
/// <summary>
/// Constructs a new instance of the LiveControl
/// </summary>
@@ -199,10 +210,30 @@ namespace Duplicati.Server
}
}
UpdatePowerModeProvider();
}
/// <summary>
/// Updates the current power mode provider, if changed
/// </summary>
public void UpdatePowerModeProvider()
{
try
{
if (OperatingSystem.IsWindows())
RegisterHibernateMonitor();
var newProvider = m_connection.ApplicationSettings.PowerModeProvider;
if (newProvider == m_currentPowerModeProvider)
return;
if (m_powerModeProvider != null)
System.Threading.Interlocked.Exchange(ref m_powerModeProvider, null)?.Dispose();
m_powerModeProvider = PowerModeUtility.GetPowerModeProvider(newProvider);
m_currentPowerModeProvider = newProvider;
if (m_powerModeProvider != null)
{
m_powerModeProvider.OnResume = OnResume;
m_powerModeProvider.OnSuspend = OnSuspend;
}
}
catch { }
}
@@ -362,71 +393,55 @@ namespace Duplicati.Server
public DateTime EstimatedPauseEnd { get { return m_waitTimeExpiration; } }
/// <summary>
/// Method for calling a Win32 API
/// Method called when the power mode provider signals suspend
/// </summary>
[SupportedOSPlatform("windows")]
private void RegisterHibernateMonitor()
private void OnSuspend()
{
Microsoft.Win32.SystemEvents.PowerModeChanged += new Microsoft.Win32.PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
//If we are running, register as being paused due to suspending
if (this.m_state == LiveControlState.Running)
{
this.SetPauseMode();
m_pausedForSuspend = true;
m_suspendMinimumPause = new DateTime(0, DateTimeKind.Utc);
}
else
{
if (m_waitTimeExpiration.Ticks != 0)
{
m_pausedForSuspend = true;
m_suspendMinimumPause = this.EstimatedPauseEnd;
ResetTimer(null);
}
}
}
/// <summary>
/// A monitor for detecting when the system hibernates or resumes
/// Method called when the power mode provider signals resume
/// </summary>
/// <param name="sender">Unused sender parameter</param>
/// <param name="_e">The event information</param>
[SupportedOSPlatform("windows")]
private void SystemEvents_PowerModeChanged(object sender, object _e)
private void OnResume()
{
Microsoft.Win32.PowerModeChangedEventArgs e = _e as Microsoft.Win32.PowerModeChangedEventArgs;
if (e == null)
return;
if (e.Mode == Microsoft.Win32.PowerModes.Suspend)
//If we have been been paused due to suspending, we un-pause now
if (m_pausedForSuspend)
{
//If we are running, register as being paused due to suspending
if (this.m_state == LiveControlState.Running)
long delayTicks = (m_suspendMinimumPause - DateTime.UtcNow).Ticks;
var appset = m_connection.ApplicationSettings;
if (!string.IsNullOrEmpty(appset.StartupDelayDuration) && appset.StartupDelayDuration != "0")
try { delayTicks = Math.Max(delayTicks, Library.Utility.Timeparser.ParseTimeSpan(appset.StartupDelayDuration).Ticks); }
catch { }
if (delayTicks > 0)
{
this.SetPauseMode();
m_pausedForSuspend = true;
m_suspendMinimumPause = new DateTime(0, DateTimeKind.Utc);
this.Pause(TimeSpan.FromTicks(delayTicks), true);
}
else
{
if (m_waitTimeExpiration.Ticks != 0)
{
m_pausedForSuspend = true;
m_suspendMinimumPause = this.EstimatedPauseEnd;
ResetTimer(null);
}
this.Resume();
}
}
else if (e.Mode == Microsoft.Win32.PowerModes.Resume)
{
//If we have been been paused due to suspending, we un-pause now
if (m_pausedForSuspend)
{
long delayTicks = (m_suspendMinimumPause - DateTime.UtcNow).Ticks;
var appset = m_connection.ApplicationSettings;
if (!string.IsNullOrEmpty(appset.StartupDelayDuration) && appset.StartupDelayDuration != "0")
try { delayTicks = Math.Max(delayTicks, Library.Utility.Timeparser.ParseTimeSpan(appset.StartupDelayDuration).Ticks); }
catch { }
if (delayTicks > 0)
{
this.Pause(TimeSpan.FromTicks(delayTicks), true);
}
else
{
this.Resume();
}
}
m_pausedForSuspend = false;
m_suspendMinimumPause = new DateTime(0, DateTimeKind.Utc);
}
m_pausedForSuspend = false;
m_suspendMinimumPause = new DateTime(0, DateTimeKind.Utc);
}
}
@@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.Win32.SystemEvents" Version="9.0.6" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,46 @@
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.Library.Snapshots;
/// <summary>
/// The power mode providers that are supported
/// </summary>
public enum PowerModeProvider
{
/// <summary>
/// The default power mode provider for the system
/// </summary>
Default,
/// <summary>
/// No power mode provider (ignores events)
/// </summary>
None,
/// <summary>
/// .NET power mode provider
/// </summary>
Net,
/// <summary>
/// Native based power mode provider
/// </summary>
Native
}
@@ -0,0 +1,56 @@
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System;
using Duplicati.Library.Interface;
namespace Duplicati.Library.Snapshots;
/// <summary>
/// Support class for managing power mode providers.
/// </summary>
public static class PowerModeUtility
{
/// <summary>
/// Loads and returns a power mode provider
/// </summary>
/// <param name="powerModeProvider">The power mode provider</param>
/// <returns>The <see cref="IDisposable"/> for the power mode provider</returns>
public static IPowerModeProvider? GetPowerModeProvider(PowerModeProvider powerModeProvider)
{
if (OperatingSystem.IsWindows())
return powerModeProvider switch
{
PowerModeProvider.Net =>
new Windows.WindowsPowerModeProvider(),
PowerModeProvider.Native or PowerModeProvider.Default =>
OperatingSystem.IsWindowsVersionAtLeast(8, 0) ?
Windows.WindowsShimLoader.NewPowerModeProvider()
: new Windows.WindowsPowerModeProvider(),
_ => null
};
return null;
}
}
@@ -0,0 +1,73 @@
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System;
using System.Runtime.Versioning;
using Duplicati.Library.Interface;
namespace Duplicati.Library.Snapshots.Windows;
/// <summary>
/// Implementation of powermode handler for Windows
/// </summary>
[SupportedOSPlatform("windows")]
public class WindowsPowerModeProvider : IPowerModeProvider
{
/// <inheritdoc />
public Action? OnResume { get; set; }
/// <inheritdoc />
public Action? OnSuspend { get; set; }
/// <summary>
/// Constructs a new power mode provider
/// </summary>
public WindowsPowerModeProvider()
{
Microsoft.Win32.SystemEvents.PowerModeChanged += new Microsoft.Win32.PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
}
/// <summary>
/// Handles the power mode events
/// </summary>
/// <param name="sender">The event sender</param>
/// <param name="e">The event args</param>
private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
{
switch (e.Mode)
{
case Microsoft.Win32.PowerModes.Suspend:
OnSuspend?.Invoke();
break;
case Microsoft.Win32.PowerModes.Resume:
OnResume?.Invoke();
break;
}
}
/// <inheritdoc />
public void Dispose()
{
Microsoft.Win32.SystemEvents.PowerModeChanged -= new Microsoft.Win32.PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
}
}
@@ -27,7 +27,6 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using Duplicati.Library.Interface;
using Duplicati.Library.Utility;
@@ -47,7 +46,7 @@ public static class WindowsShimLoader
/// Cache of types already loaded
/// </summary>
private static readonly Dictionary<string, Type> _loadedTypes = new Dictionary<string, Type>();
/// <summary>
/// Cached reference to the assembly we are loading from
/// </summary>
@@ -100,7 +99,7 @@ public static class WindowsShimLoader
var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
return path is null ? IntPtr.Zero : LoadUnmanagedDllFromPath(path);
}
}
}
/// <summary>
/// Loads a type using reflection
@@ -154,6 +153,13 @@ public static class WindowsShimLoader
public static IDisposable NewSeBackupPrivilegeScope()
=> LoadWithReflection<IDisposable>("SeBackupPrivilegeScope");
/// <summary>
/// Creates a new PowerModeProvider that can notify of suspend/resume events
/// </summary>
/// <returns>A new PowerModeProvider</returns>
public static IPowerModeProvider NewPowerModeProvider()
=> LoadWithReflection<IPowerModeProvider>("PowerManagementModule");
/// <summary>
/// Creates a new BackupDataStream for reading data with BackupRead
/// </summary>
@@ -11,11 +11,11 @@
<PackageReference Include="Vanara.PInvoke.VssApi" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.Kernel32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.Security" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
<PackageReference Include="Vanara.Security" Version="4.1.6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Interface\Duplicati.Library.Interface.csproj" />
<!-- <ProjectReference Include="..\Common\Duplicati.Library.Common.csproj" /> -->
</ItemGroup>
</Project>
@@ -0,0 +1,194 @@
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Duplicati.Library.Interface;
namespace Duplicati.Library.WindowsModules;
/// <summary>
/// Provides power management functionality for Windows using the powrprof callback API (Windows 8+).
/// </summary>
[SupportedOSPlatform("windows")]
public sealed class PowerManagementModule : IPowerModeProvider, IDisposable
{
/// <summary>
/// Registration handle returned from PowerRegisterSuspendResumeNotification.
/// </summary>
private IntPtr _registrationHandle = IntPtr.Zero;
/// <summary>
/// Keep a reference to the delegate to prevent it from being garbage collected.
/// </summary>
private DEVICE_NOTIFY_CALLBACK_ROUTINE? _callbackRef;
/// <inheritdoc />
public Action? OnResume { get; set; }
/// <inheritdoc />
public Action? OnSuspend { get; set; }
/// <summary>
/// Initializes a new instance. Required for reflection-based loading.
/// </summary>
public PowerManagementModule() : this(null)
{
}
/// <summary>
/// Initializes a new instance. The parameter is ignored in this implementation.
/// </summary>
/// <param name="_">Unused. Present for compatibility with previous constructor.</param>
public PowerManagementModule(Guid? _)
{
RegisterSuspendResumeCallback();
}
/// <summary>
/// Registers the suspend/resume callback using powrprof (Windows 8+).
/// </summary>
private void RegisterSuspendResumeCallback()
{
_callbackRef = new DEVICE_NOTIFY_CALLBACK_ROUTINE(SuspendResumeCallback);
var parameters = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
Callback = _callbackRef,
Context = IntPtr.Zero
};
// DEVICE_NOTIFY_CALLBACK delivers notifications via the provided delegate.
uint status = PowerRegisterSuspendResumeNotification(DEVICE_NOTIFY_CALLBACK, ref parameters, out _registrationHandle);
// If registration fails, we keep a no-op provider (no window fallback by design).
// STATUS_SUCCESS is 0.
if (status != STATUS_SUCCESS)
{
_registrationHandle = IntPtr.Zero;
}
}
/// <summary>
/// Constant indicating successful operation.
/// </summary>
private const uint STATUS_SUCCESS = 0;
/// <summary>
/// Callback invoked by the system for suspend/resume notifications.
/// </summary>
/// <param name="context">User-provided context (unused).</param>
/// <param name="type">Power event type (e.g., PBT_APMSUSPEND, PBT_APMRESUMEAUTOMATIC).</param>
/// <param name="setting">Additional info (unused).</param>
/// <returns>STATUS_SUCCESS (0) on success.</returns>
private uint SuspendResumeCallback(IntPtr context, uint type, IntPtr setting)
{
switch (type)
{
case PBT_APMSUSPEND:
OnSuspend?.Invoke();
break;
case PBT_APMRESUMEAUTOMATIC:
case PBT_APMRESUMESUSPEND:
OnResume?.Invoke();
break;
}
return STATUS_SUCCESS;
}
/// <inheritdoc />
public void Dispose()
{
if (_registrationHandle != IntPtr.Zero)
{
PowerUnregisterSuspendResumeNotification(_registrationHandle);
_registrationHandle = IntPtr.Zero;
}
_callbackRef = null;
}
/// <summary>
/// Power broadcast event for system suspend.
/// </summary>
private const uint PBT_APMSUSPEND = 0x0004;
/// <summary>
/// Power broadcast event for automatic resume from suspend.
/// </summary>
private const uint PBT_APMRESUMEAUTOMATIC = 0x0012;
/// <summary>
/// Power broadcast event for resume from suspend.
/// </summary>
private const uint PBT_APMRESUMESUSPEND = 0x0007;
/// <summary>
/// Flag indicating that the recipient is a callback routine.
/// </summary>
private const uint DEVICE_NOTIFY_CALLBACK = 2;
/// <summary>
/// Structure used to subscribe to suspend/resume notifications via callback.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
/// <summary>
/// The callback routine to receive notifications.
/// </summary>
public DEVICE_NOTIFY_CALLBACK_ROUTINE Callback;
/// <summary>
/// User-defined context passed to the callback.
/// </summary>
public IntPtr Context;
}
/// <summary>
/// Callback routine signature for device/power notifications.
/// Return STATUS_SUCCESS (0) on success.
/// </summary>
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate uint DEVICE_NOTIFY_CALLBACK_ROUTINE(IntPtr Context, uint Type, IntPtr Setting);
/// <summary>
/// Registers to receive power suspend/resume notifications via a callback.
/// </summary>
/// <param name="Flags">Must be DEVICE_NOTIFY_CALLBACK for callback delivery.</param>
/// <param name="Parameters">Callback and context parameters.</param>
/// <param name="Handle">Out registration handle.</param>
/// <returns>STATUS_SUCCESS (0) on success.</returns>
[DllImport("powrprof.dll", SetLastError = true)]
private static extern uint PowerRegisterSuspendResumeNotification(
uint Flags,
ref DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS Parameters,
out IntPtr Handle);
/// <summary>
/// Unregisters a previous suspend/resume notification registration.
/// </summary>
/// <param name="Handle">The registration handle.</param>
/// <returns>STATUS_SUCCESS (0) on success.</returns>
[DllImport("powrprof.dll", SetLastError = true)]
private static extern uint PowerUnregisterSuspendResumeNotification(IntPtr Handle);
}
+3 -2
View File
@@ -236,7 +236,7 @@ namespace Duplicati.Server
SetPurgeTempFilesTimer(connection, commandlineOptions);
LiveControl.StateChanged = (e) => { LiveControl_StateChanged(queueRunner, connection, eventPollNotify, e); };
LiveControl.StateChanged = (e) => { LiveControl_StateChanged(queueRunner, connection, eventPollNotify, scheduler, e); };
if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, PING_PONG_KEEPALIVE_OPTION))
{
@@ -857,7 +857,7 @@ namespace Duplicati.Server
/// This event handler updates the trayicon menu with the current state of the runner.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
private static void LiveControl_StateChanged(IQueueRunnerService queueRunnerService, Connection connection, EventPollNotify eventPollNotify, LiveControls.LiveControlEvent e)
private static void LiveControl_StateChanged(IQueueRunnerService queueRunnerService, Connection connection, EventPollNotify eventPollNotify, ISchedulerService schedulerService, LiveControls.LiveControlEvent e)
{
var appSettings = connection.ApplicationSettings;
switch (e.State)
@@ -873,6 +873,7 @@ namespace Duplicati.Server
{
queueRunnerService.Resume();
queueRunnerService.GetCurrentTask()?.Resume();
schedulerService?.Reschedule();
appSettings.PausedUntil = null;
break;
}
@@ -227,6 +227,10 @@ public sealed record SystemInfoDto
/// The new default OAuth URL for v2 authentication.
/// </summary>
public required string DefaultOAuthURLv2 { get; init; }
/// <summary>
/// The supported power mode providers
/// </summary>
public required IEnumerable<string> PowerModeProviders { get; init; }
/// <summary>
/// Represents a timezone.
@@ -21,6 +21,7 @@
using System.Globalization;
using Duplicati.Library.AutoUpdater;
using Duplicati.Library.Localization;
using Duplicati.Library.Snapshots;
using Duplicati.Library.Utility.Options;
using Duplicati.Server;
using Duplicati.Server.Database;
@@ -228,6 +229,11 @@ public class SystemInfoProvider(IApplicationSettings applicationSettings, Connec
/// The timezones available on the system
/// </summary>
public required IEnumerable<SystemInfoDto.TimeZoneDto> TimeZones { get; init; }
/// <summary>
/// The power mode providers supported
/// </summary>
public required string[] PowerModeProviders { get; init; }
}
/// <summary>
@@ -266,6 +272,9 @@ public class SystemInfoProvider(IApplicationSettings applicationSettings, Connec
SecretProviderModules = Server.Serializable.ServerSettings.SecretProviderModules,
UsingAlternateUpdateURLs = AutoUpdateSettings.UsesAlternateURLs,
LogLevels = Enum.GetNames(typeof(Library.Logging.LogMessageType)),
PowerModeProviders = OperatingSystem.IsWindows()
? [string.Empty, PowerModeProvider.None.ToString(), PowerModeProvider.Net.ToString(), PowerModeProvider.Native.ToString()]
: [string.Empty, PowerModeProvider.None.ToString()],
SpecialFolders = SpecialFolders.Nodes.Select(n => new Dto.SystemInfoDto.SpecialFolderDto { ID = n.id, Path = n.resolvedpath }).ToArray(),
SupportedLocales = Library.Localization.LocalizationService.SupportedCultures
.Select(x => new Dto.SystemInfoDto.LocaleDto
@@ -363,6 +372,7 @@ public class SystemInfoProvider(IApplicationSettings applicationSettings, Connec
TimeZones = systeminfo.TimeZones,
DefaultOAuthURL = AuthIdOptionsHelper.DUPLICATI_OAUTH_SERVICE,
DefaultOAuthURLv2 = AuthIdOptionsHelper.DUPLICATI_OAUTH_SERVICE_NEW,
PowerModeProviders = systeminfo.PowerModeProviders,
};
}
}