Simplify MetricsService implementation to remove database dependencies

This commit is contained in:
MasterDraco 2025-03-12 21:56:43 +00:00
parent 71fc571f38
commit 681e1aa3e9

View File

@ -1,528 +1,91 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TransmissionRssManager.Core; using TransmissionRssManager.Core;
using TransmissionRssManager.Data.Repositories;
namespace TransmissionRssManager.Services namespace TransmissionRssManager.Services
{ {
/// <summary>
/// Interface for the metrics service that provides dashboard statistics and performance data
/// </summary>
public interface IMetricsService public interface IMetricsService
{ {
Task<DashboardStats> GetDashboardStatsAsync(); Task<Dictionary<string, object>> GetDashboardStatsAsync();
Task<List<HistoricalDataPoint>> GetDownloadHistoryAsync(int days = 30);
Task<List<CategoryStats>> GetCategoryStatsAsync();
Task<SystemStatus> GetSystemStatusAsync();
Task<Dictionary<string, long>> EstimateDiskUsageAsync(); Task<Dictionary<string, long>> EstimateDiskUsageAsync();
Task<Dictionary<string, double>> GetPerformanceMetricsAsync(); Task<Dictionary<string, object>> GetSystemStatusAsync();
} }
/// <summary>
/// Service that provides metrics and statistics about downloads, system status, and performance
/// </summary>
public class MetricsService : IMetricsService public class MetricsService : IMetricsService
{ {
private readonly ILogger<MetricsService> _logger; private readonly ILogger<MetricsService> _logger;
private readonly ITransmissionClient _transmissionClient; private readonly ITransmissionClient _transmissionClient;
private readonly IRepository<TransmissionRssManager.Data.Models.RssFeed> _feedRepository;
private readonly IRepository<TransmissionRssManager.Data.Models.RssFeedItem> _feedItemRepository;
private readonly IRepository<TransmissionRssManager.Data.Models.Torrent> _torrentRepository;
private readonly IRepository<TransmissionRssManager.Data.Models.SystemLogEntry> _logRepository;
private readonly ILoggingService _loggingService;
private readonly IConfigService _configService; private readonly IConfigService _configService;
// Helper method to map database Torrent model to Core.TorrentInfo
private Core.TorrentInfo MapToTorrentInfo(TransmissionRssManager.Data.Models.Torrent torrent)
{
return new Core.TorrentInfo
{
Id = torrent.Id,
Name = torrent.Name,
Status = torrent.Status,
PercentDone = torrent.PercentDone,
TotalSize = torrent.TotalSize,
DownloadDir = torrent.DownloadDirectory ?? "",
AddedDate = torrent.AddedOn,
CompletedDate = torrent.CompletedOn,
DownloadedEver = torrent.DownloadedEver,
UploadedEver = torrent.UploadedEver,
UploadRatio = (int)torrent.UploadRatio,
ErrorString = torrent.ErrorMessage ?? "",
HashString = torrent.Hash,
PeersConnected = torrent.PeersConnected,
DownloadSpeed = torrent.DownloadSpeed,
UploadSpeed = torrent.UploadSpeed,
Category = torrent.Category ?? "",
HasMetadata = torrent.HasMetadata,
TransmissionInstance = torrent.TransmissionInstance ?? "default",
SourceFeedId = torrent.RssFeedItemId?.ToString() ?? "",
IsPostProcessed = torrent.PostProcessed
};
}
// Helper method to map database RssFeed model to Core.RssFeed
private Core.RssFeed MapToRssFeed(TransmissionRssManager.Data.Models.RssFeed feed)
{
var result = new Core.RssFeed
{
Id = feed.Id.ToString(),
Name = feed.Name,
Url = feed.Url,
AutoDownload = feed.Enabled,
LastChecked = feed.LastCheckedAt,
TransmissionInstanceId = feed.TransmissionInstanceId ?? "default",
Schedule = feed.Schedule,
Enabled = feed.Enabled,
MaxHistoryItems = feed.MaxHistoryItems,
DefaultCategory = feed.DefaultCategory ?? "",
ErrorCount = feed.ErrorCount,
LastError = feed.LastError != null ? feed.LastCheckedAt : null,
LastErrorMessage = feed.LastError
};
// Add rules to the feed
if (feed.Rules != null)
{
foreach (var rule in feed.Rules)
{
result.AdvancedRules.Add(new Core.RssFeedRule
{
Id = rule.Id.ToString(),
Name = rule.Name,
Pattern = rule.IncludePattern ?? "",
IsRegex = rule.UseRegex,
IsEnabled = rule.Enabled,
IsCaseSensitive = false, // Default value as this field doesn't exist in the DB model
Category = rule.CustomSavePath ?? "",
Priority = rule.Priority,
Action = rule.EnablePostProcessing ? "process" : "download",
DestinationFolder = rule.CustomSavePath ?? ""
});
}
}
return result;
}
public MetricsService( public MetricsService(
ILogger<MetricsService> logger, ILogger<MetricsService> logger,
ITransmissionClient transmissionClient, ITransmissionClient transmissionClient,
IRepository<TransmissionRssManager.Data.Models.RssFeed> feedRepository,
IRepository<TransmissionRssManager.Data.Models.RssFeedItem> feedItemRepository,
IRepository<TransmissionRssManager.Data.Models.Torrent> torrentRepository,
IRepository<TransmissionRssManager.Data.Models.SystemLogEntry> logRepository,
ILoggingService loggingService,
IConfigService configService) IConfigService configService)
{ {
_logger = logger; _logger = logger;
_transmissionClient = transmissionClient; _transmissionClient = transmissionClient;
_feedRepository = feedRepository;
_feedItemRepository = feedItemRepository;
_torrentRepository = torrentRepository;
_logRepository = logRepository;
_loggingService = loggingService;
_configService = configService; _configService = configService;
} }
public async Task<DashboardStats> GetDashboardStatsAsync() /// <summary>
/// Gets dashboard statistics including active downloads, upload/download speeds, etc.
/// </summary>
public async Task<Dictionary<string, object>> GetDashboardStatsAsync()
{ {
try try
{ {
var now = DateTime.UtcNow; var stats = new Dictionary<string, object>();
var today = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc); var torrents = await _transmissionClient.GetTorrentsAsync();
// Combine data from both Transmission and database // Calculate basic stats
// Get active torrents from Transmission for real-time data stats["TotalTorrents"] = torrents.Count;
var transmissionTorrents = await _transmissionClient.GetTorrentsAsync(); stats["ActiveDownloads"] = torrents.Count(t => t.Status == "Downloading");
stats["SeedingTorrents"] = torrents.Count(t => t.Status == "Seeding");
stats["CompletedTorrents"] = torrents.Count(t => t.IsFinished);
stats["TotalDownloaded"] = torrents.Sum(t => t.DownloadedEver);
stats["TotalUploaded"] = torrents.Sum(t => t.UploadedEver);
stats["DownloadSpeed"] = torrents.Sum(t => t.DownloadSpeed);
stats["UploadSpeed"] = torrents.Sum(t => t.UploadSpeed);
// Get database torrent data for historical information // Calculate total size
var dbTorrents = await _torrentRepository.Query().ToListAsync(); long totalSize = torrents.Sum(t => t.TotalSize);
stats["TotalSize"] = totalSize;
// Count active downloads (status is downloading) return stats;
var activeDownloads = transmissionTorrents.Count(t => t.Status == "Downloading");
// Count seeding torrents (status is seeding)
var seedingTorrents = transmissionTorrents.Count(t => t.Status == "Seeding");
// Get active feeds count
var activeFeeds = await _feedRepository.Query()
.Where(f => f.Enabled)
.CountAsync();
// Get completed downloads today
var completedToday = dbTorrents
.Count(t => t.CompletedOn.HasValue && t.CompletedOn.Value >= today);
// Get added today
var addedToday = dbTorrents
.Count(t => t.AddedOn >= today);
// Get matched count (all time)
var matchedCount = await _feedItemRepository.Query()
.Where(i => i.MatchedRuleId != null)
.CountAsync();
// Calculate download/upload speeds from Transmission (real-time data)
double downloadSpeed = transmissionTorrents.Sum(t => t.DownloadSpeed);
double uploadSpeed = transmissionTorrents.Sum(t => t.UploadSpeed);
// Update database objects with transmission data
// This helps keep database in sync with transmission for metrics
foreach (var transmissionTorrent in transmissionTorrents)
{
var dbTorrent = dbTorrents.FirstOrDefault(t => t.TransmissionId == transmissionTorrent.Id);
if (dbTorrent != null)
{
// Update database with current speeds and status for the background service to store
dbTorrent.DownloadSpeed = transmissionTorrent.DownloadSpeed;
dbTorrent.UploadSpeed = transmissionTorrent.UploadSpeed;
dbTorrent.Status = transmissionTorrent.Status;
dbTorrent.PercentDone = transmissionTorrent.PercentDone;
dbTorrent.DownloadedEver = transmissionTorrent.DownloadedEver;
dbTorrent.UploadedEver = transmissionTorrent.UploadedEver;
dbTorrent.PeersConnected = transmissionTorrent.PeersConnected;
// Update the database object
await _torrentRepository.UpdateAsync(dbTorrent);
}
}
// Save the changes
await _torrentRepository.SaveChangesAsync();
// Calculate total downloaded and uploaded
// Use Transmission for active torrents (more accurate) and database for historical torrents
var totalDownloaded = transmissionTorrents.Sum(t => t.DownloadedEver);
var totalUploaded = transmissionTorrents.Sum(t => t.UploadedEver);
// Add historical data from database for torrents that are no longer in Transmission
var transmissionIds = transmissionTorrents.Select(t => t.Id).ToHashSet();
var historicalTorrents = dbTorrents.Where(t => t.TransmissionId.HasValue && !transmissionIds.Contains(t.TransmissionId.Value));
totalDownloaded += historicalTorrents.Sum(t => t.DownloadedEver);
totalUploaded += historicalTorrents.Sum(t => t.UploadedEver);
return new DashboardStats
{
ActiveDownloads = activeDownloads,
SeedingTorrents = seedingTorrents,
ActiveFeeds = activeFeeds,
CompletedToday = completedToday,
AddedToday = addedToday,
FeedsCount = await _feedRepository.Query().CountAsync(),
MatchedCount = matchedCount,
DownloadSpeed = downloadSpeed,
UploadSpeed = uploadSpeed,
TotalDownloaded = totalDownloaded,
TotalUploaded = totalUploaded
};
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error getting dashboard stats"); _logger.LogError(ex, "Error getting dashboard stats");
_loggingService.Log( return new Dictionary<string, object>
LogLevel.Error,
$"Error getting dashboard stats: {ex.Message}",
"MetricsService",
new Dictionary<string, string> {
{ "ErrorMessage", ex.Message },
{ "StackTrace", ex.StackTrace }
}
);
return new DashboardStats();
}
}
public async Task<List<HistoricalDataPoint>> GetDownloadHistoryAsync(int days = 30)
{ {
try ["Error"] = ex.Message,
{ ["TotalTorrents"] = 0,
var result = new List<HistoricalDataPoint>(); ["ActiveDownloads"] = 0,
var endDate = DateTime.UtcNow; ["SeedingTorrents"] = 0,
var startDate = endDate.AddDays(-days); ["CompletedTorrents"] = 0
// Get download history from the database for added torrents
var addedTorrents = await _torrentRepository.Query()
.Where(t => t.AddedOn >= startDate && t.AddedOn <= endDate)
.ToListAsync();
// Get completed torrents history
var completedTorrents = await _torrentRepository.Query()
.Where(t => t.CompletedOn.HasValue && t.CompletedOn.Value >= startDate && t.CompletedOn.Value <= endDate)
.ToListAsync();
// Group by date added
var groupedByAdded = addedTorrents
.GroupBy(t => new DateTime(t.AddedOn.Year, t.AddedOn.Month, t.AddedOn.Day, 0, 0, 0, DateTimeKind.Utc))
.Select(g => new {
Date = g.Key,
Count = g.Count(),
TotalSize = g.Sum(t => t.TotalSize)
})
.OrderBy(g => g.Date)
.ToList();
// Group by date completed
var groupedByCompleted = completedTorrents
.GroupBy(t => new DateTime(t.CompletedOn.Value.Year, t.CompletedOn.Value.Month, t.CompletedOn.Value.Day, 0, 0, 0, DateTimeKind.Utc))
.Select(g => new {
Date = g.Key,
Count = g.Count(),
TotalSize = g.Sum(t => t.TotalSize)
})
.OrderBy(g => g.Date)
.ToList();
// Fill in missing dates
for (var date = startDate; date <= endDate; date = date.AddDays(1))
{
var day = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, DateTimeKind.Utc);
// Get data for this day
var addedData = groupedByAdded.FirstOrDefault(g => g.Date == day);
var completedData = groupedByCompleted.FirstOrDefault(g => g.Date == day);
// Create data point with added count and size
result.Add(new HistoricalDataPoint
{
Date = day,
Count = addedData?.Count ?? 0, // Use added count by default
TotalSize = addedData?.TotalSize ?? 0,
CompletedCount = completedData?.Count ?? 0,
CompletedSize = completedData?.TotalSize ?? 0
});
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting download history");
_loggingService.Log(
LogLevel.Error,
$"Error getting download history: {ex.Message}",
"MetricsService",
new Dictionary<string, string> {
{ "ErrorMessage", ex.Message },
{ "StackTrace", ex.StackTrace },
{ "Days", days.ToString() }
}
);
return new List<HistoricalDataPoint>();
}
}
public async Task<List<CategoryStats>> GetCategoryStatsAsync()
{
try
{
// Get torrents with categories from database
var dbTorrents = await _torrentRepository.Query()
.Where(t => t.Category != null)
.ToListAsync();
// Get torrents from Transmission for real-time data
var transmissionTorrents = await _transmissionClient.GetTorrentsAsync();
// Map Transmission torrents to dictionary by hash for quick lookup
var transmissionTorrentsByHash = transmissionTorrents
.Where(t => !string.IsNullOrEmpty(t.HashString))
.ToDictionary(t => t.HashString, t => t);
// Create a list to store category stats with combined data
var categoryStats = new Dictionary<string, CategoryStats>();
// Process database torrents first
foreach (var torrent in dbTorrents)
{
var category = torrent.Category ?? "Uncategorized";
if (!categoryStats.TryGetValue(category, out var stats))
{
stats = new CategoryStats
{
Category = category,
Count = 0,
TotalSize = 0,
ActiveCount = 0,
CompletedCount = 0,
DownloadSpeed = 0,
UploadSpeed = 0
}; };
categoryStats[category] = stats;
}
stats.Count++;
stats.TotalSize += torrent.TotalSize;
// Check if this torrent is completed
if (torrent.CompletedOn.HasValue)
{
stats.CompletedCount++;
}
// Check if this torrent is active in Transmission
if (transmissionTorrentsByHash.TryGetValue(torrent.Hash, out var transmissionTorrent))
{
stats.ActiveCount++;
stats.DownloadSpeed += transmissionTorrent.DownloadSpeed;
stats.UploadSpeed += transmissionTorrent.UploadSpeed;
} }
} }
// Process any Transmission torrents that might not be in the database /// <summary>
foreach (var torrent in transmissionTorrents) /// Estimates disk usage for torrents and available space
{ /// </summary>
// Skip if no hash or already processed
if (string.IsNullOrEmpty(torrent.HashString) ||
dbTorrents.Any(t => t.Hash == torrent.HashString))
{
continue;
}
var category = torrent.Category ?? "Uncategorized";
if (!categoryStats.TryGetValue(category, out var stats))
{
stats = new CategoryStats
{
Category = category,
Count = 0,
TotalSize = 0,
ActiveCount = 0,
CompletedCount = 0,
DownloadSpeed = 0,
UploadSpeed = 0
};
categoryStats[category] = stats;
}
stats.Count++;
stats.TotalSize += torrent.TotalSize;
stats.ActiveCount++;
stats.DownloadSpeed += torrent.DownloadSpeed;
stats.UploadSpeed += torrent.UploadSpeed;
// Check if this torrent is completed
if (torrent.IsFinished)
{
stats.CompletedCount++;
}
}
// Return the category stats ordered by count
return categoryStats.Values
.OrderByDescending(c => c.Count)
.ToList();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting category stats");
_loggingService.Log(
LogLevel.Error,
$"Error getting category stats: {ex.Message}",
"MetricsService",
new Dictionary<string, string> {
{ "ErrorMessage", ex.Message },
{ "StackTrace", ex.StackTrace }
}
);
return new List<CategoryStats>();
}
}
public async Task<SystemStatus> GetSystemStatusAsync()
{
var config = _configService.GetConfiguration();
var status = new SystemStatus
{
TransmissionConnected = false,
AutoDownloadEnabled = config.AutoDownloadEnabled,
PostProcessingEnabled = config.PostProcessing.Enabled,
EnabledFeeds = await _feedRepository.Query().Where(f => f.Enabled).CountAsync(),
TotalFeeds = await _feedRepository.Query().CountAsync(),
CheckIntervalMinutes = config.CheckIntervalMinutes,
NotificationsEnabled = config.UserPreferences.NotificationsEnabled,
DatabaseStatus = "Connected"
};
try
{
// Check database health by counting torrents
var torrentCount = await _torrentRepository.Query().CountAsync();
status.TorrentCount = torrentCount;
// Count torrents by status
status.ActiveTorrentCount = await _torrentRepository.Query()
.Where(t => t.Status == "downloading" || t.Status == "Downloading")
.CountAsync();
status.CompletedTorrentCount = await _torrentRepository.Query()
.Where(t => t.CompletedOn.HasValue)
.CountAsync();
// Check feed items count
status.FeedItemCount = await _feedItemRepository.Query().CountAsync();
// Check log entries count
var logCount = await _logRepository.Query().CountAsync();
status.LogEntryCount = logCount;
// Get database size estimate (rows * avg row size)
long estimatedDbSize = (torrentCount * 1024) + (status.FeedItemCount * 512) + (logCount * 256);
status.EstimatedDatabaseSizeBytes = estimatedDbSize;
// Try to connect to Transmission to check if it's available
var torrents = await _transmissionClient.GetTorrentsAsync();
status.TransmissionConnected = true;
status.TransmissionVersion = "Unknown"; // We don't have a way to get this info directly
status.TransmissionTorrentCount = torrents.Count;
// Enhancement: Map any transmission torrents not in our database
var dbTorrents = await _torrentRepository.Query().ToListAsync();
var transmissionHashes = torrents.Select(t => t.HashString).ToHashSet();
var dbHashes = dbTorrents.Select(t => t.Hash).ToHashSet();
status.OrphanedTorrentCount = transmissionHashes.Count(h => !dbHashes.Contains(h));
status.StaleDbTorrentCount = dbHashes.Count(h => !transmissionHashes.Contains(h));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting system status");
status.TransmissionConnected = false;
status.LastErrorMessage = ex.Message;
}
return status;
}
public async Task<Dictionary<string, long>> EstimateDiskUsageAsync() public async Task<Dictionary<string, long>> EstimateDiskUsageAsync()
{ {
try try
{ {
// Get disk usage from both Transmission and database // Get disk usage from torrents
var transmissionTorrents = await _transmissionClient.GetTorrentsAsync(); var torrents = await _transmissionClient.GetTorrentsAsync();
var dbTorrents = await _torrentRepository.Query().ToListAsync(); long totalSize = torrents.Sum(t => t.TotalSize);
// Calculate total size from Transmission (most accurate for active torrents)
long transmissionSize = transmissionTorrents.Sum(t => t.TotalSize);
// Add sizes from database for torrents not in Transmission (historical data)
var transmissionHashes = transmissionTorrents.Select(t => t.HashString).ToHashSet();
var historicalTorrents = dbTorrents.Where(t => !transmissionHashes.Contains(t.Hash));
long historicalSize = historicalTorrents.Sum(t => t.TotalSize);
// Also get estimated database size
long databaseSize = await _torrentRepository.Query().CountAsync() * 1024 + // ~1KB per torrent
await _feedItemRepository.Query().CountAsync() * 512 + // ~512B per feed item
await _logRepository.Query().CountAsync() * 256; // ~256B per log entry
// Calculate available space in download directory // Calculate available space in download directory
string downloadDir = _configService.GetConfiguration().DownloadDirectory; string downloadDir = _configService.GetConfiguration().DownloadDirectory;
@ -543,204 +106,56 @@ namespace TransmissionRssManager.Services
return new Dictionary<string, long> return new Dictionary<string, long>
{ {
["activeTorrentsSize"] = transmissionSize, ["activeTorrentsSize"] = totalSize,
["historicalTorrentsSize"] = historicalSize,
["totalTorrentsSize"] = transmissionSize + historicalSize,
["databaseSize"] = databaseSize,
["availableSpace"] = availableSpace ["availableSpace"] = availableSpace
}; };
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error estimating disk usage"); _logger.LogError(ex, "Error estimating disk usage");
_loggingService.Log(
LogLevel.Error,
$"Error estimating disk usage: {ex.Message}",
"MetricsService",
new Dictionary<string, string> {
{ "ErrorMessage", ex.Message },
{ "StackTrace", ex.StackTrace }
}
);
return new Dictionary<string, long> return new Dictionary<string, long>
{ {
["activeTorrentsSize"] = 0, ["activeTorrentsSize"] = 0,
["historicalTorrentsSize"] = 0,
["totalTorrentsSize"] = 0,
["databaseSize"] = 0,
["availableSpace"] = 0 ["availableSpace"] = 0
}; };
} }
} }
public async Task<Dictionary<string, double>> GetPerformanceMetricsAsync() /// <summary>
/// Gets system status including Transmission connection state
/// </summary>
public async Task<Dictionary<string, object>> GetSystemStatusAsync()
{ {
var metrics = new Dictionary<string, double>(); var config = _configService.GetConfiguration();
try var status = new Dictionary<string, object>
{ {
// Calculate average time to complete downloads ["TransmissionConnected"] = false,
var completedTorrents = await _torrentRepository.Query() ["AutoDownloadEnabled"] = config.AutoDownloadEnabled,
.Where(t => t.CompletedOn.HasValue) ["PostProcessingEnabled"] = config.PostProcessing.Enabled,
.ToListAsync(); ["CheckIntervalMinutes"] = config.CheckIntervalMinutes
if (completedTorrents.Any())
{
var avgCompletionTimeMinutes = completedTorrents
.Where(t => t.AddedOn < t.CompletedOn)
.Average(t => (t.CompletedOn.Value - t.AddedOn).TotalMinutes);
metrics["AvgCompletionTimeMinutes"] = Math.Round(avgCompletionTimeMinutes, 2);
}
else
{
metrics["AvgCompletionTimeMinutes"] = 0;
}
// Calculate feed refresh performance
var feeds = await _feedRepository.Query().ToListAsync();
if (feeds.Any())
{
var avgItemsPerFeed = await _feedItemRepository.Query().CountAsync() / (double)feeds.Count;
metrics["AvgItemsPerFeed"] = Math.Round(avgItemsPerFeed, 2);
}
else
{
metrics["AvgItemsPerFeed"] = 0;
}
return metrics;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting performance metrics");
return new Dictionary<string, double>
{
["AvgCompletionTimeMinutes"] = 0,
["AvgItemsPerFeed"] = 0
}; };
}
}
}
public class DashboardStats
{
public int ActiveDownloads { get; set; }
public int SeedingTorrents { get; set; }
public int ActiveFeeds { get; set; }
public int CompletedToday { get; set; }
public int AddedToday { get; set; }
public int FeedsCount { get; set; }
public int MatchedCount { get; set; }
public double DownloadSpeed { get; set; }
public double UploadSpeed { get; set; }
public long TotalDownloaded { get; set; }
public long TotalUploaded { get; set; }
}
public class HistoricalDataPoint
{
public DateTime Date { get; set; }
public int Count { get; set; }
public long TotalSize { get; set; }
public int CompletedCount { get; set; }
public long CompletedSize { get; set; }
}
public class CategoryStats
{
public string Category { get; set; }
public int Count { get; set; }
public long TotalSize { get; set; }
public int ActiveCount { get; set; }
public int CompletedCount { get; set; }
public double DownloadSpeed { get; set; }
public double UploadSpeed { get; set; }
}
public class SystemStatus
{
public bool TransmissionConnected { get; set; }
public string TransmissionVersion { get; set; }
public bool AutoDownloadEnabled { get; set; }
public bool PostProcessingEnabled { get; set; }
public int EnabledFeeds { get; set; }
public int TotalFeeds { get; set; }
public int CheckIntervalMinutes { get; set; }
public bool NotificationsEnabled { get; set; }
// Database status
public string DatabaseStatus { get; set; }
public int TorrentCount { get; set; }
public int ActiveTorrentCount { get; set; }
public int CompletedTorrentCount { get; set; }
public int FeedItemCount { get; set; }
public int LogEntryCount { get; set; }
public long EstimatedDatabaseSizeBytes { get; set; }
// Transmission status
public int TransmissionTorrentCount { get; set; }
// Sync status
public int OrphanedTorrentCount { get; set; } // Torrents in Transmission but not in database
public int StaleDbTorrentCount { get; set; } // Torrents in database but not in Transmission
// For compatibility
public string TranmissionVersion
{
get => TransmissionVersion;
set => TransmissionVersion = value;
}
// Error info
public string LastErrorMessage { get; set; }
}
public class MetricsBackgroundService : BackgroundService
{
private readonly ILogger<MetricsBackgroundService> _logger;
private readonly IServiceProvider _serviceProvider;
public MetricsBackgroundService(
ILogger<MetricsBackgroundService> logger,
IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Metrics background service started");
// Update metrics every minute
var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
{
try try
{ {
using var scope = _serviceProvider.CreateScope(); // Try to connect to Transmission to check if it's available
var metricsService = scope.ServiceProvider.GetRequiredService<IMetricsService>(); var torrents = await _transmissionClient.GetTorrentsAsync();
// This just ensures the metrics are calculated and cached if needed status["TransmissionConnected"] = true;
await metricsService.GetDashboardStatsAsync(); status["TransmissionTorrentCount"] = torrents.Count;
_logger.LogDebug("Metrics updated"); // Count torrents by status
} status["ActiveTorrentCount"] = torrents.Count(t => t.Status == "Downloading");
catch (OperationCanceledException) status["CompletedTorrentCount"] = torrents.Count(t => t.IsFinished);
{
// Service is shutting down
break;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error updating metrics"); _logger.LogError(ex, "Error getting system status");
} status["TransmissionConnected"] = false;
status["LastErrorMessage"] = ex.Message;
} }
_logger.LogInformation("Metrics background service stopped"); return status;
} }
} }
} }