Files
duplicati/Duplicati/Library/RestAPI/EventPollNotify.cs
T
Kenneth Skovhede c8b9fb5c73 Websocket subscriptions
This PR adds the ability to subscribe to messages over a websocket.

To prevent polling data, the server can now push messages through the websocket, so the client can instantly update when new data is available.

The change is backwards compatible, still serving the status updates over the socket. If the client is providing the authentication token, it is auto-subscried to the legacy status message.

If the client is using the new authentication message, it needs to subscribe to get the status updates (and any other services it needs).

The event system has been extended to support new, and more accurate, events. This is the first step towards removing the general status update and the long-poll mechanism.

This also re-introduces the blocking marker, so the client can know if the operation is halted due to too many pending transfers.

Some endpoints have been moved to service implementations, to allow serving the exact same data from both endpoints and websocket.

This PR also updates the way the progress is handled, so that all transfers are returned to the client, and the transfer speeds for each transfer is calculated based on a small sample buffer, so the values are more accurate even if the program is paused during transfers.
2025-05-27 12:13:07 +02:00

139 lines
5.1 KiB
C#

// 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.Collections.Generic;
using Duplicati.Server.Serialization.Interface;
namespace Duplicati.Server
{
/// <summary>
/// This class handles synchronized waiting for events
/// </summary>
public class EventPollNotify
{
/// <summary>
/// The lock that grants exclusive access to control structures
/// </summary>
private readonly object m_lock = new object();
/// <summary>
/// The current eventID
/// </summary>
private long m_eventNo = 0;
/// <summary>
/// The list of subscribed waiting threads
/// </summary>
private readonly Queue<System.Threading.ManualResetEvent> m_waitQueue = new Queue<System.Threading.ManualResetEvent>();
/// <summary>
/// An eventhandler for subscribing to event updates without blocking
/// </summary>
public event EventHandler<EventArgs?>? NewEvent;
/// <summary>
/// An eventhandler for subscribing to progress updates without blocking
/// </summary>
public event EventHandler<Func<IProgressEventData>?>? ProgressUpdate;
/// <summary>
/// An eventhandler for subscribing to queue updates without blocking
/// </summary>
public event EventHandler<EventArgs?>? TaskQueueUpdate;
/// <summary>
/// An eventhandler for subscribing backup list updates without blocking
/// </summary>
public event EventHandler<EventArgs?>? BackupListUpdate;
/// <summary>
/// An eventhandler for subscribing to notifications updates without blocking
/// </summary>
public event EventHandler<EventArgs?>? NotificationsUpdated;
/// <summary>
/// An eventhandler for subscribing to server settings updates without blocking
/// </summary>
public event EventHandler? ServerSettingsUpdate;
/// <summary>
/// Gets the current event ID
/// </summary>
public long EventNo { get { return m_eventNo; } }
/// <summary>
/// Call to wait for an event that is newer than the current known event
/// </summary>
/// <param name="eventId">The last known event id</param>
/// <param name="milliseconds">The number of milliseconds to block</param>
/// <returns>The current event id</returns>
public long Wait(long eventId, int milliseconds)
{
System.Threading.ManualResetEvent mre;
lock (m_lock)
{
//If a newer event has already occured, return immediately
if (eventId != m_eventNo)
return m_eventNo;
//Otherwise register this thread as waiting
mre = new System.Threading.ManualResetEvent(false);
m_waitQueue.Enqueue(mre);
}
//Wait until we are signalled or the time has elapsed
mre.WaitOne(milliseconds, false);
return m_eventNo;
}
/// <summary>
/// Signals that an event has occurred and notifies all waiting threads
/// </summary>
public void SignalNewEvent()
{
lock (m_lock)
{
m_eventNo++;
while (m_waitQueue.Count > 0)
m_waitQueue.Dequeue().Set();
}
if (NewEvent != null)
NewEvent(this, null!);
}
public void SignalProgressUpdate(Func<IProgressEventData>? pg)
=> ProgressUpdate?.Invoke(this, pg);
public void SignalTaskQueueUpdate()
=> TaskQueueUpdate?.Invoke(this, null);
public void SignalServerSettingsUpdated()
=> ServerSettingsUpdate?.Invoke(this, null!);
public void SignalBackupListUpdate()
=> BackupListUpdate?.Invoke(this, null!);
public void SignalNotificationUpdate()
=> NotificationsUpdated?.Invoke(this, null!);
}
}