Merge pull request #6550 from duplicati/feature/add-power-mode-provider-selection
Add PowerMode provider selection
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user