Logging Enhancements (#1521)
* Recreated Kavita Logging with Serilog instead of Default. This needs to be move out of the appsettings now, to allow auto updater to patch. * Refactored the code to be completely configured via Code rather than appsettings.json. This is a required step for Auto Updating. * Added in the ability to send logs directly to the UI only for users on the log route. Stopping implementation as Alerts page will handle the rest of the implementation. * Fixed up the backup service to not rely on Config from appsettings.json * Tweaked the Logging levels available * Moved everything over to File-scoped namespaces * Moved everything over to File-scoped namespaces * Code cleanup, removed an old migration and changed so debug logging doesn't print sensitive db data * Removed dead code
This commit is contained in:
parent
9f715cc35f
commit
d1a14f7e68
212 changed files with 16599 additions and 16834 deletions
|
|
@ -56,4 +56,5 @@ public class EventHub : IEventHub
|
|||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
await _messageHub.Clients.User(user.UserName).SendAsync(method, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
58
API/SignalR/LogHub.cs
Normal file
58
API/SignalR/LogHub.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using API.Extensions;
|
||||
using API.SignalR.Presence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace API.SignalR;
|
||||
|
||||
public interface ILogHub : Serilog.Sinks.AspNetCore.SignalR.Interfaces.IHub
|
||||
{
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
public class LogHub : Hub<ILogHub>
|
||||
{
|
||||
private readonly IEventHub _eventHub;
|
||||
private readonly IPresenceTracker _tracker;
|
||||
|
||||
public LogHub(IEventHub eventHub, IPresenceTracker tracker)
|
||||
{
|
||||
_eventHub = eventHub;
|
||||
_tracker = tracker;
|
||||
}
|
||||
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
await _tracker.UserConnected(Context.User.GetUsername(), Context.ConnectionId);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
await _tracker.UserDisconnected(Context.User.GetUsername(), Context.ConnectionId);
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
|
||||
public async Task SendLogAsString(string message)
|
||||
{
|
||||
await _eventHub.SendMessageAsync("LogString", new SignalRMessage()
|
||||
{
|
||||
Body = message,
|
||||
EventType = "LogString",
|
||||
Name = "LogString",
|
||||
}, true);
|
||||
}
|
||||
|
||||
public async Task SendLogAsObject(object messageObject)
|
||||
{
|
||||
await _eventHub.SendMessageAsync("LogObject", new SignalRMessage()
|
||||
{
|
||||
Body = messageObject,
|
||||
EventType = "LogString",
|
||||
Name = "LogString",
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,476 +6,475 @@ using API.DTOs.Update;
|
|||
using API.Entities;
|
||||
using API.Extensions;
|
||||
|
||||
namespace API.SignalR
|
||||
namespace API.SignalR;
|
||||
|
||||
public static class MessageFactoryEntityTypes
|
||||
{
|
||||
public static class MessageFactoryEntityTypes
|
||||
public const string Series = "series";
|
||||
public const string Volume = "volume";
|
||||
public const string Chapter = "chapter";
|
||||
public const string CollectionTag = "collection";
|
||||
public const string ReadingList = "readingList";
|
||||
}
|
||||
public static class MessageFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// An update is available for the Kavita instance
|
||||
/// </summary>
|
||||
public const string UpdateAvailable = "UpdateAvailable";
|
||||
/// <summary>
|
||||
/// Used to tell when a scan series completes. This also informs UI to update series metadata
|
||||
/// </summary>
|
||||
public const string ScanSeries = "ScanSeries";
|
||||
/// <summary>
|
||||
/// Event sent out during Refresh Metadata for progress tracking
|
||||
/// </summary>
|
||||
private const string CoverUpdateProgress = "CoverUpdateProgress";
|
||||
/// <summary>
|
||||
/// Series is added to server
|
||||
/// </summary>
|
||||
public const string SeriesAdded = "SeriesAdded";
|
||||
/// <summary>
|
||||
/// Series is removed from server
|
||||
/// </summary>
|
||||
public const string SeriesRemoved = "SeriesRemoved";
|
||||
/// <summary>
|
||||
/// When a user is connects/disconnects from server
|
||||
/// </summary>
|
||||
public const string OnlineUsers = "OnlineUsers";
|
||||
/// <summary>
|
||||
/// When a series is added to a collection
|
||||
/// </summary>
|
||||
public const string SeriesAddedToCollection = "SeriesAddedToCollection";
|
||||
/// <summary>
|
||||
/// Event sent out during backing up the database
|
||||
/// </summary>
|
||||
private const string BackupDatabaseProgress = "BackupDatabaseProgress";
|
||||
/// <summary>
|
||||
/// Event sent out during cleaning up temp and cache folders
|
||||
/// </summary>
|
||||
private const string CleanupProgress = "CleanupProgress";
|
||||
/// <summary>
|
||||
/// Event sent out during downloading of files
|
||||
/// </summary>
|
||||
private const string DownloadProgress = "DownloadProgress";
|
||||
/// <summary>
|
||||
/// A cover was updated
|
||||
/// </summary>
|
||||
public const string CoverUpdate = "CoverUpdate";
|
||||
/// <summary>
|
||||
/// A custom site theme was removed or added
|
||||
/// </summary>
|
||||
private const string SiteThemeProgress = "SiteThemeProgress";
|
||||
/// <summary>
|
||||
/// A custom book theme was removed or added
|
||||
/// </summary>
|
||||
private const string BookThemeProgress = "BookThemeProgress";
|
||||
/// <summary>
|
||||
/// A type of event that has progress (determinate or indeterminate).
|
||||
/// The underlying event will have a name to give details on how to handle.
|
||||
/// </summary>
|
||||
/// <remarks>This is not an Event Name, it is used as the method only</remarks>
|
||||
public const string NotificationProgress = "NotificationProgress";
|
||||
/// <summary>
|
||||
/// Event sent out when Scan Loop is parsing a file
|
||||
/// </summary>
|
||||
private const string FileScanProgress = "FileScanProgress";
|
||||
/// <summary>
|
||||
/// A generic error that can occur in background processing
|
||||
/// </summary>
|
||||
public const string Error = "Error";
|
||||
/// <summary>
|
||||
/// When DB updates are occuring during a library/series scan
|
||||
/// </summary>
|
||||
private const string ScanProgress = "ScanProgress";
|
||||
/// <summary>
|
||||
/// When a library is created/deleted in the Server
|
||||
/// </summary>
|
||||
public const string LibraryModified = "LibraryModified";
|
||||
/// <summary>
|
||||
/// A user's progress was modified
|
||||
/// </summary>
|
||||
public const string UserProgressUpdate = "UserProgressUpdate";
|
||||
/// <summary>
|
||||
/// A user's account or preferences were updated and UI needs to refresh to stay in sync
|
||||
/// </summary>
|
||||
public const string UserUpdate = "UserUpdate";
|
||||
/// <summary>
|
||||
/// When bulk bookmarks are being converted
|
||||
/// </summary>
|
||||
private const string ConvertBookmarksProgress = "ConvertBookmarksProgress";
|
||||
/// <summary>
|
||||
/// When files are being scanned to calculate word count
|
||||
/// </summary>
|
||||
private const string WordCountAnalyzerProgress = "WordCountAnalyzerProgress";
|
||||
/// <summary>
|
||||
/// A generic message that can occur in background processing to inform user, but no direct action is needed
|
||||
/// </summary>
|
||||
public const string Info = "Info";
|
||||
|
||||
|
||||
public static SignalRMessage ScanSeriesEvent(int libraryId, int seriesId, string seriesName)
|
||||
{
|
||||
public const string Series = "series";
|
||||
public const string Volume = "volume";
|
||||
public const string Chapter = "chapter";
|
||||
public const string CollectionTag = "collection";
|
||||
public const string ReadingList = "readingList";
|
||||
return new SignalRMessage()
|
||||
{
|
||||
Name = ScanSeries,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
LibraryId = libraryId,
|
||||
SeriesId = seriesId,
|
||||
SeriesName = seriesName
|
||||
}
|
||||
};
|
||||
}
|
||||
public static class MessageFactory
|
||||
|
||||
public static SignalRMessage SeriesAddedEvent(int seriesId, string seriesName, int libraryId)
|
||||
{
|
||||
/// <summary>
|
||||
/// An update is available for the Kavita instance
|
||||
/// </summary>
|
||||
public const string UpdateAvailable = "UpdateAvailable";
|
||||
/// <summary>
|
||||
/// Used to tell when a scan series completes. This also informs UI to update series metadata
|
||||
/// </summary>
|
||||
public const string ScanSeries = "ScanSeries";
|
||||
/// <summary>
|
||||
/// Event sent out during Refresh Metadata for progress tracking
|
||||
/// </summary>
|
||||
private const string CoverUpdateProgress = "CoverUpdateProgress";
|
||||
/// <summary>
|
||||
/// Series is added to server
|
||||
/// </summary>
|
||||
public const string SeriesAdded = "SeriesAdded";
|
||||
/// <summary>
|
||||
/// Series is removed from server
|
||||
/// </summary>
|
||||
public const string SeriesRemoved = "SeriesRemoved";
|
||||
/// <summary>
|
||||
/// When a user is connects/disconnects from server
|
||||
/// </summary>
|
||||
public const string OnlineUsers = "OnlineUsers";
|
||||
/// <summary>
|
||||
/// When a series is added to a collection
|
||||
/// </summary>
|
||||
public const string SeriesAddedToCollection = "SeriesAddedToCollection";
|
||||
/// <summary>
|
||||
/// Event sent out during backing up the database
|
||||
/// </summary>
|
||||
private const string BackupDatabaseProgress = "BackupDatabaseProgress";
|
||||
/// <summary>
|
||||
/// Event sent out during cleaning up temp and cache folders
|
||||
/// </summary>
|
||||
private const string CleanupProgress = "CleanupProgress";
|
||||
/// <summary>
|
||||
/// Event sent out during downloading of files
|
||||
/// </summary>
|
||||
private const string DownloadProgress = "DownloadProgress";
|
||||
/// <summary>
|
||||
/// A cover was updated
|
||||
/// </summary>
|
||||
public const string CoverUpdate = "CoverUpdate";
|
||||
/// <summary>
|
||||
/// A custom site theme was removed or added
|
||||
/// </summary>
|
||||
private const string SiteThemeProgress = "SiteThemeProgress";
|
||||
/// <summary>
|
||||
/// A custom book theme was removed or added
|
||||
/// </summary>
|
||||
private const string BookThemeProgress = "BookThemeProgress";
|
||||
/// <summary>
|
||||
/// A type of event that has progress (determinate or indeterminate).
|
||||
/// The underlying event will have a name to give details on how to handle.
|
||||
/// </summary>
|
||||
/// <remarks>This is not an Event Name, it is used as the method only</remarks>
|
||||
public const string NotificationProgress = "NotificationProgress";
|
||||
/// <summary>
|
||||
/// Event sent out when Scan Loop is parsing a file
|
||||
/// </summary>
|
||||
private const string FileScanProgress = "FileScanProgress";
|
||||
/// <summary>
|
||||
/// A generic error that can occur in background processing
|
||||
/// </summary>
|
||||
public const string Error = "Error";
|
||||
/// <summary>
|
||||
/// When DB updates are occuring during a library/series scan
|
||||
/// </summary>
|
||||
private const string ScanProgress = "ScanProgress";
|
||||
/// <summary>
|
||||
/// When a library is created/deleted in the Server
|
||||
/// </summary>
|
||||
public const string LibraryModified = "LibraryModified";
|
||||
/// <summary>
|
||||
/// A user's progress was modified
|
||||
/// </summary>
|
||||
public const string UserProgressUpdate = "UserProgressUpdate";
|
||||
/// <summary>
|
||||
/// A user's account or preferences were updated and UI needs to refresh to stay in sync
|
||||
/// </summary>
|
||||
public const string UserUpdate = "UserUpdate";
|
||||
/// <summary>
|
||||
/// When bulk bookmarks are being converted
|
||||
/// </summary>
|
||||
private const string ConvertBookmarksProgress = "ConvertBookmarksProgress";
|
||||
/// <summary>
|
||||
/// When files are being scanned to calculate word count
|
||||
/// </summary>
|
||||
private const string WordCountAnalyzerProgress = "WordCountAnalyzerProgress";
|
||||
/// <summary>
|
||||
/// A generic message that can occur in background processing to inform user, but no direct action is needed
|
||||
/// </summary>
|
||||
public const string Info = "Info";
|
||||
|
||||
|
||||
public static SignalRMessage ScanSeriesEvent(int libraryId, int seriesId, string seriesName)
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = SeriesAdded,
|
||||
Body = new
|
||||
{
|
||||
Name = ScanSeries,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
LibraryId = libraryId,
|
||||
SeriesId = seriesId,
|
||||
SeriesName = seriesName
|
||||
}
|
||||
};
|
||||
}
|
||||
SeriesId = seriesId,
|
||||
SeriesName = seriesName,
|
||||
LibraryId = libraryId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage SeriesAddedEvent(int seriesId, string seriesName, int libraryId)
|
||||
public static SignalRMessage SeriesRemovedEvent(int seriesId, string seriesName, int libraryId)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = SeriesRemoved,
|
||||
Body = new
|
||||
{
|
||||
Name = SeriesAdded,
|
||||
Body = new
|
||||
{
|
||||
SeriesId = seriesId,
|
||||
SeriesName = seriesName,
|
||||
LibraryId = libraryId
|
||||
}
|
||||
};
|
||||
}
|
||||
SeriesId = seriesId,
|
||||
SeriesName = seriesName,
|
||||
LibraryId = libraryId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage SeriesRemovedEvent(int seriesId, string seriesName, int libraryId)
|
||||
|
||||
public static SignalRMessage WordCountAnalyzerProgressEvent(int libraryId, float progress, string eventType, string subtitle = "")
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = WordCountAnalyzerProgress,
|
||||
Title = "Analyzing Word count",
|
||||
SubTitle = subtitle,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
Name = SeriesRemoved,
|
||||
Body = new
|
||||
{
|
||||
SeriesId = seriesId,
|
||||
SeriesName = seriesName,
|
||||
LibraryId = libraryId
|
||||
}
|
||||
};
|
||||
}
|
||||
LibraryId = libraryId,
|
||||
Progress = progress,
|
||||
EventTime = DateTime.Now
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static SignalRMessage WordCountAnalyzerProgressEvent(int libraryId, float progress, string eventType, string subtitle = "")
|
||||
public static SignalRMessage CoverUpdateProgressEvent(int libraryId, float progress, string eventType, string subtitle = "")
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = CoverUpdateProgress,
|
||||
Title = "Refreshing Covers",
|
||||
SubTitle = subtitle,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
Name = WordCountAnalyzerProgress,
|
||||
Title = "Analyzing Word count",
|
||||
SubTitle = subtitle,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
LibraryId = libraryId,
|
||||
Progress = progress,
|
||||
EventTime = DateTime.Now
|
||||
}
|
||||
};
|
||||
}
|
||||
LibraryId = libraryId,
|
||||
Progress = progress,
|
||||
EventTime = DateTime.Now
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage CoverUpdateProgressEvent(int libraryId, float progress, string eventType, string subtitle = "")
|
||||
public static SignalRMessage BackupDatabaseProgressEvent(float progress, string subtitle = "")
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = BackupDatabaseProgress,
|
||||
Title = "Backing up Database",
|
||||
SubTitle = subtitle,
|
||||
EventType = progress switch
|
||||
{
|
||||
Name = CoverUpdateProgress,
|
||||
Title = "Refreshing Covers",
|
||||
SubTitle = subtitle,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
LibraryId = libraryId,
|
||||
Progress = progress,
|
||||
EventTime = DateTime.Now
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage BackupDatabaseProgressEvent(float progress, string subtitle = "")
|
||||
0f => "started",
|
||||
1f => "ended",
|
||||
_ => "updated"
|
||||
},
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
Progress = progress
|
||||
}
|
||||
};
|
||||
}
|
||||
public static SignalRMessage CleanupProgressEvent(float progress, string subtitle = "")
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = CleanupProgress,
|
||||
Title = "Performing Cleanup",
|
||||
SubTitle = subtitle,
|
||||
EventType = progress switch
|
||||
{
|
||||
Name = BackupDatabaseProgress,
|
||||
Title = "Backing up Database",
|
||||
SubTitle = subtitle,
|
||||
EventType = progress switch
|
||||
{
|
||||
0f => "started",
|
||||
1f => "ended",
|
||||
_ => "updated"
|
||||
},
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
Progress = progress
|
||||
}
|
||||
};
|
||||
}
|
||||
public static SignalRMessage CleanupProgressEvent(float progress, string subtitle = "")
|
||||
0f => "started",
|
||||
1f => "ended",
|
||||
_ => "updated"
|
||||
},
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
Progress = progress
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static SignalRMessage UpdateVersionEvent(UpdateNotificationDto update)
|
||||
{
|
||||
return new SignalRMessage
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
Name = CleanupProgress,
|
||||
Title = "Performing Cleanup",
|
||||
SubTitle = subtitle,
|
||||
EventType = progress switch
|
||||
{
|
||||
0f => "started",
|
||||
1f => "ended",
|
||||
_ => "updated"
|
||||
},
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
Progress = progress
|
||||
}
|
||||
};
|
||||
}
|
||||
Name = UpdateAvailable,
|
||||
Title = "Update Available",
|
||||
SubTitle = update.UpdateTitle,
|
||||
EventType = ProgressEventType.Single,
|
||||
Progress = ProgressType.None,
|
||||
Body = update
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static SignalRMessage UpdateVersionEvent(UpdateNotificationDto update)
|
||||
public static SignalRMessage SeriesAddedToCollectionEvent(int tagId, int seriesId)
|
||||
{
|
||||
return new SignalRMessage
|
||||
{
|
||||
return new SignalRMessage
|
||||
Name = SeriesAddedToCollection,
|
||||
Progress = ProgressType.None,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
Name = UpdateAvailable,
|
||||
Title = "Update Available",
|
||||
SubTitle = update.UpdateTitle,
|
||||
EventType = ProgressEventType.Single,
|
||||
Progress = ProgressType.None,
|
||||
Body = update
|
||||
};
|
||||
}
|
||||
TagId = tagId,
|
||||
SeriesId = seriesId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage SeriesAddedToCollectionEvent(int tagId, int seriesId)
|
||||
|
||||
public static SignalRMessage ErrorEvent(string title, string subtitle)
|
||||
{
|
||||
return new SignalRMessage
|
||||
{
|
||||
return new SignalRMessage
|
||||
Name = Error,
|
||||
Title = title,
|
||||
SubTitle = subtitle,
|
||||
Progress = ProgressType.None,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
Name = SeriesAddedToCollection,
|
||||
Progress = ProgressType.None,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
TagId = tagId,
|
||||
SeriesId = seriesId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static SignalRMessage ErrorEvent(string title, string subtitle)
|
||||
{
|
||||
return new SignalRMessage
|
||||
{
|
||||
Name = Error,
|
||||
Title = title,
|
||||
SubTitle = subtitle,
|
||||
Progress = ProgressType.None,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
Title = title,
|
||||
SubTitle = subtitle,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage InfoEvent(string title, string subtitle)
|
||||
public static SignalRMessage InfoEvent(string title, string subtitle)
|
||||
{
|
||||
return new SignalRMessage
|
||||
{
|
||||
return new SignalRMessage
|
||||
Name = Info,
|
||||
Title = title,
|
||||
SubTitle = subtitle,
|
||||
Progress = ProgressType.None,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
Name = Info,
|
||||
Title = title,
|
||||
SubTitle = subtitle,
|
||||
Progress = ProgressType.None,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
Title = title,
|
||||
SubTitle = subtitle,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage LibraryModifiedEvent(int libraryId, string action)
|
||||
public static SignalRMessage LibraryModifiedEvent(int libraryId, string action)
|
||||
{
|
||||
return new SignalRMessage
|
||||
{
|
||||
return new SignalRMessage
|
||||
Name = LibraryModified,
|
||||
Title = "Library modified",
|
||||
Progress = ProgressType.None,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
Name = LibraryModified,
|
||||
Title = "Library modified",
|
||||
Progress = ProgressType.None,
|
||||
EventType = ProgressEventType.Single,
|
||||
Body = new
|
||||
{
|
||||
LibrayId = libraryId,
|
||||
Action = action,
|
||||
}
|
||||
};
|
||||
}
|
||||
LibrayId = libraryId,
|
||||
Action = action,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage DownloadProgressEvent(string username, string downloadName, float progress, string eventType = "updated")
|
||||
public static SignalRMessage DownloadProgressEvent(string username, string downloadName, float progress, string eventType = "updated")
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = DownloadProgress,
|
||||
Title = $"Downloading {downloadName}",
|
||||
SubTitle = $"Preparing {username.SentenceCase()} the download of {downloadName}",
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
Name = DownloadProgress,
|
||||
Title = $"Downloading {downloadName}",
|
||||
SubTitle = $"Preparing {username.SentenceCase()} the download of {downloadName}",
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
UserName = username,
|
||||
DownloadName = downloadName,
|
||||
Progress = progress
|
||||
}
|
||||
};
|
||||
}
|
||||
UserName = username,
|
||||
DownloadName = downloadName,
|
||||
Progress = progress
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a file being scanned by Kavita for processing and grouping
|
||||
/// </summary>
|
||||
/// <remarks>Does not have a progress as it's unknown how many files there are. Instead sends -1 to represent indeterminate</remarks>
|
||||
/// <param name="folderPath"></param>
|
||||
/// <param name="libraryName"></param>
|
||||
/// <param name="eventType"></param>
|
||||
/// <returns></returns>
|
||||
public static SignalRMessage FileScanProgressEvent(string folderPath, string libraryName, string eventType)
|
||||
/// <summary>
|
||||
/// Represents a file being scanned by Kavita for processing and grouping
|
||||
/// </summary>
|
||||
/// <remarks>Does not have a progress as it's unknown how many files there are. Instead sends -1 to represent indeterminate</remarks>
|
||||
/// <param name="folderPath"></param>
|
||||
/// <param name="libraryName"></param>
|
||||
/// <param name="eventType"></param>
|
||||
/// <returns></returns>
|
||||
public static SignalRMessage FileScanProgressEvent(string folderPath, string libraryName, string eventType)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = FileScanProgress,
|
||||
Title = $"Scanning {libraryName}",
|
||||
SubTitle = folderPath,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Indeterminate,
|
||||
Body = new
|
||||
{
|
||||
Name = FileScanProgress,
|
||||
Title = $"Scanning {libraryName}",
|
||||
SubTitle = folderPath,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Indeterminate,
|
||||
Body = new
|
||||
{
|
||||
Title = $"Scanning {libraryName}",
|
||||
Subtitle = folderPath,
|
||||
Filename = folderPath,
|
||||
EventTime = DateTime.Now,
|
||||
}
|
||||
};
|
||||
}
|
||||
Subtitle = folderPath,
|
||||
Filename = folderPath,
|
||||
EventTime = DateTime.Now,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This informs the UI with details about what is being processed by the Scanner
|
||||
/// </summary>
|
||||
/// <param name="libraryName"></param>
|
||||
/// <param name="eventType"></param>
|
||||
/// <param name="seriesName"></param>
|
||||
/// <returns></returns>
|
||||
public static SignalRMessage LibraryScanProgressEvent(string libraryName, string eventType, string seriesName = "")
|
||||
/// <summary>
|
||||
/// This informs the UI with details about what is being processed by the Scanner
|
||||
/// </summary>
|
||||
/// <param name="libraryName"></param>
|
||||
/// <param name="eventType"></param>
|
||||
/// <param name="seriesName"></param>
|
||||
/// <returns></returns>
|
||||
public static SignalRMessage LibraryScanProgressEvent(string libraryName, string eventType, string seriesName = "")
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
Name = ScanProgress,
|
||||
Title = $"Processing {seriesName}",
|
||||
SubTitle = seriesName,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Indeterminate,
|
||||
Body = null
|
||||
};
|
||||
}
|
||||
Name = ScanProgress,
|
||||
Title = $"Processing {seriesName}",
|
||||
SubTitle = seriesName,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Indeterminate,
|
||||
Body = null
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage CoverUpdateEvent(int id, string entityType)
|
||||
public static SignalRMessage CoverUpdateEvent(int id, string entityType)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = CoverUpdate,
|
||||
Title = "Updating Cover",
|
||||
Progress = ProgressType.None,
|
||||
Body = new
|
||||
{
|
||||
Name = CoverUpdate,
|
||||
Title = "Updating Cover",
|
||||
Progress = ProgressType.None,
|
||||
Body = new
|
||||
{
|
||||
Id = id,
|
||||
EntityType = entityType,
|
||||
}
|
||||
};
|
||||
}
|
||||
Id = id,
|
||||
EntityType = entityType,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage UserProgressUpdateEvent(int userId, string username, int seriesId, int volumeId, int chapterId, int pagesRead)
|
||||
public static SignalRMessage UserProgressUpdateEvent(int userId, string username, int seriesId, int volumeId, int chapterId, int pagesRead)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = UserProgressUpdate,
|
||||
Title = "Updating User Progress",
|
||||
Progress = ProgressType.None,
|
||||
Body = new
|
||||
{
|
||||
Name = UserProgressUpdate,
|
||||
Title = "Updating User Progress",
|
||||
Progress = ProgressType.None,
|
||||
Body = new
|
||||
{
|
||||
UserId = userId,
|
||||
Username = username,
|
||||
SeriesId = seriesId,
|
||||
VolumeId = volumeId,
|
||||
ChapterId = chapterId,
|
||||
PagesRead = pagesRead,
|
||||
}
|
||||
};
|
||||
}
|
||||
UserId = userId,
|
||||
Username = username,
|
||||
SeriesId = seriesId,
|
||||
VolumeId = volumeId,
|
||||
ChapterId = chapterId,
|
||||
PagesRead = pagesRead,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage SiteThemeProgressEvent(string subtitle, string themeName, string eventType)
|
||||
public static SignalRMessage SiteThemeProgressEvent(string subtitle, string themeName, string eventType)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = SiteThemeProgress,
|
||||
Title = "Scanning Site Theme",
|
||||
SubTitle = subtitle,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Indeterminate,
|
||||
Body = new
|
||||
{
|
||||
Name = SiteThemeProgress,
|
||||
Title = "Scanning Site Theme",
|
||||
SubTitle = subtitle,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Indeterminate,
|
||||
Body = new
|
||||
{
|
||||
ThemeName = themeName,
|
||||
}
|
||||
};
|
||||
}
|
||||
ThemeName = themeName,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage BookThemeProgressEvent(string subtitle, string themeName, string eventType)
|
||||
public static SignalRMessage BookThemeProgressEvent(string subtitle, string themeName, string eventType)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = BookThemeProgress,
|
||||
Title = "Scanning Book Theme",
|
||||
SubTitle = subtitle,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Indeterminate,
|
||||
Body = new
|
||||
{
|
||||
Name = BookThemeProgress,
|
||||
Title = "Scanning Book Theme",
|
||||
SubTitle = subtitle,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Indeterminate,
|
||||
Body = new
|
||||
{
|
||||
ThemeName = themeName,
|
||||
}
|
||||
};
|
||||
}
|
||||
ThemeName = themeName,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage UserUpdateEvent(int userId, string userName)
|
||||
public static SignalRMessage UserUpdateEvent(int userId, string userName)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = UserUpdate,
|
||||
Title = "User Update",
|
||||
Progress = ProgressType.None,
|
||||
Body = new
|
||||
{
|
||||
Name = UserUpdate,
|
||||
Title = "User Update",
|
||||
Progress = ProgressType.None,
|
||||
Body = new
|
||||
{
|
||||
UserId = userId,
|
||||
UserName = userName
|
||||
}
|
||||
};
|
||||
}
|
||||
UserId = userId,
|
||||
UserName = userName
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage ConvertBookmarksProgressEvent(float progress, string eventType)
|
||||
public static SignalRMessage ConvertBookmarksProgressEvent(float progress, string eventType)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
return new SignalRMessage()
|
||||
Name = ConvertBookmarksProgress,
|
||||
Title = "Converting Bookmarks to WebP",
|
||||
SubTitle = string.Empty,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
Name = ConvertBookmarksProgress,
|
||||
Title = "Converting Bookmarks to WebP",
|
||||
SubTitle = string.Empty,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Determinate,
|
||||
Body = new
|
||||
{
|
||||
Progress = progress,
|
||||
EventTime = DateTime.Now
|
||||
}
|
||||
};
|
||||
}
|
||||
Progress = progress,
|
||||
EventTime = DateTime.Now
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,45 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Extensions;
|
||||
using API.SignalR.Presence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace API.SignalR
|
||||
namespace API.SignalR;
|
||||
|
||||
/// <summary>
|
||||
/// Generic hub for sending messages to UI
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class MessageHub : Hub
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic hub for sending messages to UI
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class MessageHub : Hub
|
||||
private readonly IPresenceTracker _tracker;
|
||||
|
||||
public MessageHub(IPresenceTracker tracker)
|
||||
{
|
||||
private readonly IPresenceTracker _tracker;
|
||||
_tracker = tracker;
|
||||
}
|
||||
|
||||
public MessageHub(IPresenceTracker tracker)
|
||||
{
|
||||
_tracker = tracker;
|
||||
}
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
await _tracker.UserConnected(Context.User.GetUsername(), Context.ConnectionId);
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
await _tracker.UserConnected(Context.User.GetUsername(), Context.ConnectionId);
|
||||
|
||||
var currentUsers = await PresenceTracker.GetOnlineUsers();
|
||||
await Clients.All.SendAsync(MessageFactory.OnlineUsers, currentUsers);
|
||||
var currentUsers = await PresenceTracker.GetOnlineUsers();
|
||||
await Clients.All.SendAsync(MessageFactory.OnlineUsers, currentUsers);
|
||||
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
await _tracker.UserDisconnected(Context.User.GetUsername(), Context.ConnectionId);
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
await _tracker.UserDisconnected(Context.User.GetUsername(), Context.ConnectionId);
|
||||
|
||||
var currentUsers = await PresenceTracker.GetOnlineUsers();
|
||||
await Clients.All.SendAsync(MessageFactory.OnlineUsers, currentUsers);
|
||||
var currentUsers = await PresenceTracker.GetOnlineUsers();
|
||||
await Clients.All.SendAsync(MessageFactory.OnlineUsers, currentUsers);
|
||||
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,112 +4,111 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
|
||||
namespace API.SignalR.Presence
|
||||
namespace API.SignalR.Presence;
|
||||
|
||||
public interface IPresenceTracker
|
||||
{
|
||||
public interface IPresenceTracker
|
||||
{
|
||||
Task UserConnected(string username, string connectionId);
|
||||
Task UserDisconnected(string username, string connectionId);
|
||||
Task<string[]> GetOnlineAdmins();
|
||||
Task<List<string>> GetConnectionsForUser(string username);
|
||||
Task UserConnected(string username, string connectionId);
|
||||
Task UserDisconnected(string username, string connectionId);
|
||||
Task<string[]> GetOnlineAdmins();
|
||||
Task<List<string>> GetConnectionsForUser(string username);
|
||||
|
||||
}
|
||||
|
||||
internal class ConnectionDetail
|
||||
{
|
||||
public List<string> ConnectionIds { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
}
|
||||
|
||||
// TODO: This can respond to UserRoleUpdate events to handle online users
|
||||
/// <summary>
|
||||
/// This is a singleton service for tracking what users have a SignalR connection and their difference connectionIds
|
||||
/// </summary>
|
||||
public class PresenceTracker : IPresenceTracker
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private static readonly Dictionary<string, ConnectionDetail> OnlineUsers = new Dictionary<string, ConnectionDetail>();
|
||||
|
||||
public PresenceTracker(IUnitOfWork unitOfWork)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
internal class ConnectionDetail
|
||||
public async Task UserConnected(string username, string connectionId)
|
||||
{
|
||||
public List<string> ConnectionIds { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username);
|
||||
if (user == null) return;
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
|
||||
lock (OnlineUsers)
|
||||
{
|
||||
if (OnlineUsers.ContainsKey(username))
|
||||
{
|
||||
OnlineUsers[username].ConnectionIds.Add(connectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnlineUsers.Add(username, new ConnectionDetail()
|
||||
{
|
||||
ConnectionIds = new List<string>() {connectionId},
|
||||
IsAdmin = isAdmin
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last active for the user
|
||||
user.LastActive = DateTime.Now;
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
// TODO: This can respond to UserRoleUpdate events to handle online users
|
||||
/// <summary>
|
||||
/// This is a singleton service for tracking what users have a SignalR connection and their difference connectionIds
|
||||
/// </summary>
|
||||
public class PresenceTracker : IPresenceTracker
|
||||
public Task UserDisconnected(string username, string connectionId)
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private static readonly Dictionary<string, ConnectionDetail> OnlineUsers = new Dictionary<string, ConnectionDetail>();
|
||||
|
||||
public PresenceTracker(IUnitOfWork unitOfWork)
|
||||
lock (OnlineUsers)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
if (!OnlineUsers.ContainsKey(username)) return Task.CompletedTask;
|
||||
|
||||
public async Task UserConnected(string username, string connectionId)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username);
|
||||
if (user == null) return;
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
|
||||
lock (OnlineUsers)
|
||||
OnlineUsers[username].ConnectionIds.Remove(connectionId);
|
||||
|
||||
if (OnlineUsers[username].ConnectionIds.Count == 0)
|
||||
{
|
||||
if (OnlineUsers.ContainsKey(username))
|
||||
{
|
||||
OnlineUsers[username].ConnectionIds.Add(connectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnlineUsers.Add(username, new ConnectionDetail()
|
||||
{
|
||||
ConnectionIds = new List<string>() {connectionId},
|
||||
IsAdmin = isAdmin
|
||||
});
|
||||
}
|
||||
OnlineUsers.Remove(username);
|
||||
}
|
||||
|
||||
// Update the last active for the user
|
||||
user.LastActive = DateTime.Now;
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task UserDisconnected(string username, string connectionId)
|
||||
public static Task<string[]> GetOnlineUsers()
|
||||
{
|
||||
string[] onlineUsers;
|
||||
lock (OnlineUsers)
|
||||
{
|
||||
lock (OnlineUsers)
|
||||
{
|
||||
if (!OnlineUsers.ContainsKey(username)) return Task.CompletedTask;
|
||||
|
||||
OnlineUsers[username].ConnectionIds.Remove(connectionId);
|
||||
|
||||
if (OnlineUsers[username].ConnectionIds.Count == 0)
|
||||
{
|
||||
OnlineUsers.Remove(username);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
onlineUsers = OnlineUsers.OrderBy(k => k.Key).Select(k => k.Key).ToArray();
|
||||
}
|
||||
|
||||
public static Task<string[]> GetOnlineUsers()
|
||||
return Task.FromResult(onlineUsers);
|
||||
}
|
||||
|
||||
public Task<string[]> GetOnlineAdmins()
|
||||
{
|
||||
// TODO: This might end in stale data, we want to get the online users, query against DB to check if they are admins then return
|
||||
string[] onlineUsers;
|
||||
lock (OnlineUsers)
|
||||
{
|
||||
string[] onlineUsers;
|
||||
lock (OnlineUsers)
|
||||
{
|
||||
onlineUsers = OnlineUsers.OrderBy(k => k.Key).Select(k => k.Key).ToArray();
|
||||
}
|
||||
|
||||
return Task.FromResult(onlineUsers);
|
||||
onlineUsers = OnlineUsers.Where(pair => pair.Value.IsAdmin).OrderBy(k => k.Key).Select(k => k.Key).ToArray();
|
||||
}
|
||||
|
||||
public Task<string[]> GetOnlineAdmins()
|
||||
|
||||
return Task.FromResult(onlineUsers);
|
||||
}
|
||||
|
||||
public Task<List<string>> GetConnectionsForUser(string username)
|
||||
{
|
||||
List<string> connectionIds;
|
||||
lock (OnlineUsers)
|
||||
{
|
||||
// TODO: This might end in stale data, we want to get the online users, query against DB to check if they are admins then return
|
||||
string[] onlineUsers;
|
||||
lock (OnlineUsers)
|
||||
{
|
||||
onlineUsers = OnlineUsers.Where(pair => pair.Value.IsAdmin).OrderBy(k => k.Key).Select(k => k.Key).ToArray();
|
||||
}
|
||||
|
||||
|
||||
return Task.FromResult(onlineUsers);
|
||||
connectionIds = OnlineUsers.GetValueOrDefault(username)?.ConnectionIds;
|
||||
}
|
||||
|
||||
public Task<List<string>> GetConnectionsForUser(string username)
|
||||
{
|
||||
List<string> connectionIds;
|
||||
lock (OnlineUsers)
|
||||
{
|
||||
connectionIds = OnlineUsers.GetValueOrDefault(username)?.ConnectionIds;
|
||||
}
|
||||
|
||||
return Task.FromResult(connectionIds ?? new List<string>());
|
||||
}
|
||||
return Task.FromResult(connectionIds ?? new List<string>());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,38 @@
|
|||
using System;
|
||||
|
||||
namespace API.SignalR
|
||||
namespace API.SignalR;
|
||||
|
||||
/// <summary>
|
||||
/// Payload for SignalR messages to Frontend
|
||||
/// </summary>
|
||||
public class SignalRMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Payload for SignalR messages to Frontend
|
||||
/// Body of the event type
|
||||
/// </summary>
|
||||
public class SignalRMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Body of the event type
|
||||
/// </summary>
|
||||
public object Body { get; set; }
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// User friendly Title of the Event
|
||||
/// </summary>
|
||||
/// <example>Scanning Manga</example>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// User friendly subtitle. Should have extra info
|
||||
/// </summary>
|
||||
/// <example>C:/manga/Accel World V01.cbz</example>
|
||||
public string SubTitle { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Represents what this represents. started | updated | ended | single
|
||||
/// <see cref="ProgressEventType"/>
|
||||
/// </summary>
|
||||
public string EventType { get; set; } = ProgressEventType.Updated;
|
||||
/// <summary>
|
||||
/// How should progress be represented. If Determinate, the Body MUST have a Progress float on it.
|
||||
/// </summary>
|
||||
public string Progress { get; set; } = ProgressType.None;
|
||||
/// <summary>
|
||||
/// When event took place
|
||||
/// </summary>
|
||||
public readonly DateTime EventTime = DateTime.Now;
|
||||
}
|
||||
public object Body { get; set; }
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// User friendly Title of the Event
|
||||
/// </summary>
|
||||
/// <example>Scanning Manga</example>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// User friendly subtitle. Should have extra info
|
||||
/// </summary>
|
||||
/// <example>C:/manga/Accel World V01.cbz</example>
|
||||
public string SubTitle { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Represents what this represents. started | updated | ended | single
|
||||
/// <see cref="ProgressEventType"/>
|
||||
/// </summary>
|
||||
public string EventType { get; set; } = ProgressEventType.Updated;
|
||||
/// <summary>
|
||||
/// How should progress be represented. If Determinate, the Body MUST have a Progress float on it.
|
||||
/// </summary>
|
||||
public string Progress { get; set; } = ProgressType.None;
|
||||
/// <summary>
|
||||
/// When event took place
|
||||
/// </summary>
|
||||
public readonly DateTime EventTime = DateTime.Now;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue