Add one-click installation script and fix MetricsService
This commit is contained in:
parent
f804ca51d3
commit
96f95f228f
53
README.md
53
README.md
@ -9,44 +9,51 @@ A C# application for managing RSS feeds and automatically downloading torrents v
|
||||
- Manage Transmission torrents through a user-friendly web interface
|
||||
- Post-processing of completed downloads (extract archives, organize media files)
|
||||
- Responsive web UI for desktop and mobile use
|
||||
- Database integration for historical data and statistics
|
||||
- Dashboard with metrics and download history
|
||||
|
||||
## Requirements
|
||||
|
||||
- .NET 7.0 or higher
|
||||
- Linux OS (Debian, Ubuntu, or derivatives)
|
||||
- Transmission BitTorrent client (with remote access enabled)
|
||||
- Linux OS (tested on Ubuntu, Debian, Fedora, Arch)
|
||||
- Dependencies: unzip, p7zip, unrar (for post-processing)
|
||||
|
||||
## Installation
|
||||
## One-Click Installation
|
||||
|
||||
### Automatic Installation
|
||||
|
||||
Run the installer script:
|
||||
Install Transmission RSS Manager with a single command:
|
||||
|
||||
```bash
|
||||
curl -sSL https://raw.githubusercontent.com/yourusername/transmission-rss-manager/main/install-script.sh | bash
|
||||
wget -O - https://git.powerdata.dk/masterdraco/Torrent-Manager/raw/branch/main/TransmissionRssManager/install.sh | sudo bash
|
||||
```
|
||||
|
||||
Or if you've cloned the repository:
|
||||
This command:
|
||||
1. Downloads the installer script
|
||||
2. Installs all dependencies (.NET SDK, PostgreSQL, etc.)
|
||||
3. Sets up the database
|
||||
4. Builds and configures the application
|
||||
5. Creates and starts the system service
|
||||
|
||||
After installation, access the web interface at: http://localhost:5000
|
||||
|
||||
## Alternative Installation Methods
|
||||
|
||||
If you prefer to examine the install script before running it, you can:
|
||||
|
||||
```bash
|
||||
./src/Infrastructure/install-script.sh
|
||||
# Download the install script
|
||||
wget https://git.powerdata.dk/masterdraco/Torrent-Manager/raw/branch/main/TransmissionRssManager/install.sh
|
||||
|
||||
# Review it
|
||||
less install.sh
|
||||
|
||||
# Run it
|
||||
sudo bash install.sh
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
Or if you've already cloned the repository:
|
||||
|
||||
1. Install .NET 7.0 SDK from [Microsoft's website](https://dotnet.microsoft.com/download)
|
||||
2. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/yourusername/transmission-rss-manager.git
|
||||
cd transmission-rss-manager
|
||||
```
|
||||
3. Build and run the application:
|
||||
```bash
|
||||
dotnet build -c Release
|
||||
dotnet run
|
||||
```
|
||||
4. Open a web browser and navigate to: `http://localhost:5000`
|
||||
```bash
|
||||
sudo bash install.sh
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
|
220
install.sh
Executable file
220
install.sh
Executable file
@ -0,0 +1,220 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Transmission RSS Manager One-Click Installer
|
||||
# This script downloads and installs Transmission RSS Manager with all dependencies
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Print section header
|
||||
print_section() {
|
||||
echo -e "\n${GREEN}===== $1 =====${NC}"
|
||||
}
|
||||
|
||||
# Error handling
|
||||
set -e
|
||||
trap 'echo -e "${RED}An error occurred. Installation failed.${NC}"; exit 1' ERR
|
||||
|
||||
print_section "Transmission RSS Manager Installer"
|
||||
echo -e "This script will install Transmission RSS Manager and all required components."
|
||||
|
||||
# Check if running as root (sudo)
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${YELLOW}Please run this script with sudo:${NC}"
|
||||
echo -e "${YELLOW}sudo bash install.sh${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect Linux distribution
|
||||
if [ -f /etc/os-release ]; then
|
||||
. /etc/os-release
|
||||
DISTRO=$ID
|
||||
else
|
||||
echo -e "${RED}Cannot detect Linux distribution. This script supports Debian, Ubuntu, and their derivatives.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_section "Installing Dependencies"
|
||||
|
||||
# Update package lists
|
||||
echo "Updating package lists..."
|
||||
apt-get update
|
||||
|
||||
# Install basic dependencies
|
||||
echo "Installing required packages..."
|
||||
apt-get install -y wget curl unzip git
|
||||
|
||||
# Install .NET SDK
|
||||
print_section "Installing .NET SDK"
|
||||
if ! command -v dotnet &> /dev/null; then
|
||||
echo "Installing .NET SDK 7.0..."
|
||||
|
||||
# Add Microsoft package repository
|
||||
wget -O packages-microsoft-prod.deb https://packages.microsoft.com/config/$DISTRO/$VERSION_ID/packages-microsoft-prod.deb
|
||||
dpkg -i packages-microsoft-prod.deb
|
||||
rm packages-microsoft-prod.deb
|
||||
|
||||
# Install .NET SDK
|
||||
apt-get update
|
||||
apt-get install -y apt-transport-https
|
||||
apt-get update
|
||||
apt-get install -y dotnet-sdk-7.0
|
||||
else
|
||||
echo ".NET SDK is already installed."
|
||||
fi
|
||||
|
||||
# Verify .NET installation
|
||||
dotnet --version
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}.NET SDK installation failed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install PostgreSQL
|
||||
print_section "Installing PostgreSQL"
|
||||
if ! command -v psql &> /dev/null; then
|
||||
echo "Installing PostgreSQL..."
|
||||
apt-get install -y postgresql postgresql-contrib
|
||||
else
|
||||
echo "PostgreSQL is already installed."
|
||||
fi
|
||||
|
||||
# Start PostgreSQL service
|
||||
systemctl start postgresql
|
||||
systemctl enable postgresql
|
||||
|
||||
# Install Entity Framework Core tools
|
||||
print_section "Installing EF Core tools"
|
||||
if ! su - postgres -c "dotnet tool list -g" | grep "dotnet-ef" > /dev/null; then
|
||||
echo "Installing Entity Framework Core tools..."
|
||||
su - postgres -c "dotnet tool install --global dotnet-ef --version 7.0.15"
|
||||
else
|
||||
echo "Entity Framework Core tools are already installed."
|
||||
fi
|
||||
|
||||
# Create installation directory
|
||||
print_section "Setting up application"
|
||||
INSTALL_DIR="/opt/transmission-rss-manager"
|
||||
mkdir -p $INSTALL_DIR
|
||||
|
||||
# Download or clone the application
|
||||
if [ ! -d "$INSTALL_DIR/.git" ]; then
|
||||
echo "Downloading application files..."
|
||||
# Clone the repository
|
||||
git clone https://git.powerdata.dk/masterdraco/Torrent-Manager.git $INSTALL_DIR
|
||||
else
|
||||
echo "Updating existing installation..."
|
||||
cd $INSTALL_DIR
|
||||
git pull
|
||||
fi
|
||||
|
||||
# Setup database
|
||||
print_section "Setting up database"
|
||||
DB_NAME="torrentmanager"
|
||||
DB_USER="torrentmanager"
|
||||
DB_PASSWORD=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16)
|
||||
|
||||
# Check if database exists
|
||||
DB_EXISTS=$(su - postgres -c "psql -tAc \"SELECT 1 FROM pg_database WHERE datname='$DB_NAME'\"")
|
||||
if [ "$DB_EXISTS" != "1" ]; then
|
||||
echo "Creating database and user..."
|
||||
# Create database and user
|
||||
su - postgres -c "psql -c \"CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';\""
|
||||
su - postgres -c "psql -c \"CREATE DATABASE $DB_NAME OWNER $DB_USER;\""
|
||||
su - postgres -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;\""
|
||||
else
|
||||
echo "Database already exists."
|
||||
fi
|
||||
|
||||
# Save connection string
|
||||
CONFIG_DIR="/etc/transmission-rss-manager"
|
||||
mkdir -p $CONFIG_DIR
|
||||
echo '{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=localhost;Database='$DB_NAME';Username='$DB_USER';Password='$DB_PASSWORD'"
|
||||
}
|
||||
}' > "$CONFIG_DIR/appsettings.json"
|
||||
|
||||
# Set proper permissions
|
||||
chown -R postgres:postgres "$CONFIG_DIR"
|
||||
chmod 750 "$CONFIG_DIR"
|
||||
chmod 640 "$CONFIG_DIR/appsettings.json"
|
||||
|
||||
# Build and deploy the application
|
||||
print_section "Building application"
|
||||
# Check if TransmissionRssManager directory exists directly or as a subdirectory
|
||||
if [ -d "$INSTALL_DIR/TransmissionRssManager" ]; then
|
||||
PROJECT_DIR="$INSTALL_DIR/TransmissionRssManager"
|
||||
elif [ -f "$INSTALL_DIR/TransmissionRssManager.csproj" ]; then
|
||||
PROJECT_DIR="$INSTALL_DIR"
|
||||
else
|
||||
# Look for the .csproj file
|
||||
PROJECT_DIR=$(find $INSTALL_DIR -name "*.csproj" -exec dirname {} \; | head -n 1)
|
||||
fi
|
||||
|
||||
echo "Building project from: $PROJECT_DIR"
|
||||
cd "$PROJECT_DIR"
|
||||
dotnet restore
|
||||
dotnet build -c Release
|
||||
dotnet publish -c Release -o $INSTALL_DIR/publish
|
||||
|
||||
# Copy configuration
|
||||
cp "$CONFIG_DIR/appsettings.json" "$INSTALL_DIR/publish/appsettings.json"
|
||||
|
||||
# Run migrations
|
||||
print_section "Running database migrations"
|
||||
cd "$PROJECT_DIR"
|
||||
dotnet ef database update
|
||||
|
||||
# Create systemd service
|
||||
print_section "Creating systemd service"
|
||||
# Find the main application DLL
|
||||
APP_DLL=$(find $INSTALL_DIR/publish -name "*.dll" | head -n 1)
|
||||
APP_NAME=$(basename "$APP_DLL" .dll)
|
||||
|
||||
echo "[Unit]
|
||||
Description=Transmission RSS Manager
|
||||
After=network.target postgresql.service
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=$INSTALL_DIR/publish
|
||||
ExecStart=/usr/bin/dotnet $APP_DLL
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
SyslogIdentifier=transmission-rss-manager
|
||||
User=postgres
|
||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target" > /etc/systemd/system/transmission-rss-manager.service
|
||||
|
||||
# Reload systemd, enable and start service
|
||||
systemctl daemon-reload
|
||||
systemctl enable transmission-rss-manager
|
||||
systemctl start transmission-rss-manager
|
||||
|
||||
# Create shortcut
|
||||
print_section "Creating application shortcut"
|
||||
echo "[Desktop Entry]
|
||||
Name=Transmission RSS Manager
|
||||
Comment=RSS Feed Manager for Transmission BitTorrent Client
|
||||
Exec=xdg-open http://localhost:5000
|
||||
Icon=transmission
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Network;P2P;" > /usr/share/applications/transmission-rss-manager.desktop
|
||||
|
||||
# Installation complete
|
||||
print_section "Installation Complete!"
|
||||
echo -e "${GREEN}Transmission RSS Manager has been successfully installed!${NC}"
|
||||
echo -e "Web interface: ${YELLOW}http://localhost:5000${NC}"
|
||||
echo -e "Database username: ${YELLOW}$DB_USER${NC}"
|
||||
echo -e "Database password: ${YELLOW}$DB_PASSWORD${NC}"
|
||||
echo -e "Configuration file: ${YELLOW}$CONFIG_DIR/appsettings.json${NC}"
|
||||
echo -e "Application files: ${YELLOW}$INSTALL_DIR${NC}"
|
||||
echo -e "\nTo check service status: ${YELLOW}systemctl status transmission-rss-manager${NC}"
|
||||
echo -e "View logs: ${YELLOW}journalctl -u transmission-rss-manager${NC}"
|
746
src/Services/MetricsService.cs
Normal file
746
src/Services/MetricsService.cs
Normal file
@ -0,0 +1,746 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TransmissionRssManager.Core;
|
||||
using TransmissionRssManager.Data.Repositories;
|
||||
|
||||
namespace TransmissionRssManager.Services
|
||||
{
|
||||
public interface IMetricsService
|
||||
{
|
||||
Task<DashboardStats> GetDashboardStatsAsync();
|
||||
Task<List<HistoricalDataPoint>> GetDownloadHistoryAsync(int days = 30);
|
||||
Task<List<CategoryStats>> GetCategoryStatsAsync();
|
||||
Task<SystemStatus> GetSystemStatusAsync();
|
||||
Task<Dictionary<string, long>> EstimateDiskUsageAsync();
|
||||
Task<Dictionary<string, double>> GetPerformanceMetricsAsync();
|
||||
}
|
||||
|
||||
public class MetricsService : IMetricsService
|
||||
{
|
||||
private readonly ILogger<MetricsService> _logger;
|
||||
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;
|
||||
|
||||
// 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(
|
||||
ILogger<MetricsService> logger,
|
||||
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)
|
||||
{
|
||||
_logger = logger;
|
||||
_transmissionClient = transmissionClient;
|
||||
_feedRepository = feedRepository;
|
||||
_feedItemRepository = feedItemRepository;
|
||||
_torrentRepository = torrentRepository;
|
||||
_logRepository = logRepository;
|
||||
_loggingService = loggingService;
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
public async Task<DashboardStats> GetDashboardStatsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var today = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
// Combine data from both Transmission and database
|
||||
// Get active torrents from Transmission for real-time data
|
||||
var transmissionTorrents = await _transmissionClient.GetTorrentsAsync();
|
||||
|
||||
// Get database torrent data for historical information
|
||||
var dbTorrents = await _torrentRepository.Query().ToListAsync();
|
||||
|
||||
// Count active downloads (status is downloading)
|
||||
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)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting dashboard stats");
|
||||
_loggingService.Log(
|
||||
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
|
||||
{
|
||||
var result = new List<HistoricalDataPoint>();
|
||||
var endDate = DateTime.UtcNow;
|
||||
var startDate = endDate.AddDays(-days);
|
||||
|
||||
// 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
|
||||
foreach (var torrent in transmissionTorrents)
|
||||
{
|
||||
// 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()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get disk usage from both Transmission and database
|
||||
var transmissionTorrents = await _transmissionClient.GetTorrentsAsync();
|
||||
var dbTorrents = await _torrentRepository.Query().ToListAsync();
|
||||
|
||||
// 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
|
||||
string downloadDir = _configService.GetConfiguration().DownloadDirectory;
|
||||
long availableSpace = 0;
|
||||
|
||||
if (!string.IsNullOrEmpty(downloadDir) && System.IO.Directory.Exists(downloadDir))
|
||||
{
|
||||
try
|
||||
{
|
||||
var driveInfo = new System.IO.DriveInfo(System.IO.Path.GetPathRoot(downloadDir));
|
||||
availableSpace = driveInfo.AvailableFreeSpace;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"Error getting available disk space for {downloadDir}");
|
||||
}
|
||||
}
|
||||
|
||||
return new Dictionary<string, long>
|
||||
{
|
||||
["activeTorrentsSize"] = transmissionSize,
|
||||
["historicalTorrentsSize"] = historicalSize,
|
||||
["totalTorrentsSize"] = transmissionSize + historicalSize,
|
||||
["databaseSize"] = databaseSize,
|
||||
["availableSpace"] = availableSpace
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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>
|
||||
{
|
||||
["activeTorrentsSize"] = 0,
|
||||
["historicalTorrentsSize"] = 0,
|
||||
["totalTorrentsSize"] = 0,
|
||||
["databaseSize"] = 0,
|
||||
["availableSpace"] = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, double>> GetPerformanceMetricsAsync()
|
||||
{
|
||||
var metrics = new Dictionary<string, double>();
|
||||
|
||||
try
|
||||
{
|
||||
// Calculate average time to complete downloads
|
||||
var completedTorrents = await _torrentRepository.Query()
|
||||
.Where(t => t.CompletedOn.HasValue)
|
||||
.ToListAsync();
|
||||
|
||||
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
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var metricsService = scope.ServiceProvider.GetRequiredService<IMetricsService>();
|
||||
|
||||
// This just ensures the metrics are calculated and cached if needed
|
||||
await metricsService.GetDashboardStatsAsync();
|
||||
|
||||
_logger.LogDebug("Metrics updated");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Service is shutting down
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating metrics");
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Metrics background service stopped");
|
||||
}
|
||||
}
|
||||
}
|
192
test-installation.sh
Executable file
192
test-installation.sh
Executable file
@ -0,0 +1,192 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Print section header
|
||||
print_section() {
|
||||
echo -e "\n${GREEN}====== $1 ======${NC}"
|
||||
}
|
||||
|
||||
# Error handling
|
||||
set -e
|
||||
trap 'echo -e "${RED}An error occurred. Test failed.${NC}"; exit 1' ERR
|
||||
|
||||
# Setup test environment
|
||||
print_section "Setting up test environment"
|
||||
CURRENT_DIR=$(pwd)
|
||||
TEST_DIR="$CURRENT_DIR/test-install"
|
||||
mkdir -p "$TEST_DIR"
|
||||
CONFIG_DIR="$TEST_DIR/config"
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
# Copy necessary files
|
||||
print_section "Copying files"
|
||||
mkdir -p "$TEST_DIR"
|
||||
# Copy only essential files (not the test directory itself)
|
||||
find "$CURRENT_DIR" -maxdepth 1 -not -path "*test-install*" -not -path "$CURRENT_DIR" -exec cp -r {} "$TEST_DIR/" \;
|
||||
cd "$TEST_DIR"
|
||||
|
||||
# Check .NET SDK installation
|
||||
print_section "Checking .NET SDK"
|
||||
dotnet --version
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}.NET SDK is not installed or not in PATH. Please install .NET SDK 7.0.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the application
|
||||
print_section "Building application"
|
||||
dotnet build -c Release
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Build failed. See errors above.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}Build successful.${NC}"
|
||||
|
||||
# Create database configuration
|
||||
print_section "Creating database configuration"
|
||||
echo '{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=localhost;Database=torrentmanager_test;Username=postgres;Password=postgres"
|
||||
}
|
||||
}' > "$CONFIG_DIR/appsettings.json"
|
||||
|
||||
cp "$CONFIG_DIR/appsettings.json" "$TEST_DIR/appsettings.json"
|
||||
echo -e "${GREEN}Database configuration created at $CONFIG_DIR/appsettings.json${NC}"
|
||||
|
||||
# Check if PostgreSQL is running
|
||||
print_section "Checking PostgreSQL"
|
||||
if command -v pg_isready >/dev/null 2>&1; then
|
||||
pg_isready
|
||||
PG_READY=$?
|
||||
else
|
||||
echo -e "${YELLOW}PostgreSQL client tools not found.${NC}"
|
||||
PG_READY=1
|
||||
fi
|
||||
|
||||
if [ $PG_READY -ne 0 ]; then
|
||||
echo -e "${YELLOW}PostgreSQL is not running or not installed.${NC}"
|
||||
echo -e "${YELLOW}This test expects PostgreSQL to be available with a 'postgres' user.${NC}"
|
||||
echo -e "${YELLOW}Either install PostgreSQL or modify the connection string in appsettings.json.${NC}"
|
||||
|
||||
# Continue anyway for testing other aspects
|
||||
echo -e "${YELLOW}Continuing test without database functionality.${NC}"
|
||||
|
||||
# Update appsettings.json to use SQLite instead
|
||||
echo '{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Data Source=torrentmanager_test.db"
|
||||
}
|
||||
}' > "$TEST_DIR/appsettings.json"
|
||||
|
||||
echo -e "${YELLOW}Using SQLite database instead.${NC}"
|
||||
|
||||
# Install Entity Framework Core SQLite provider
|
||||
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 7.0.17
|
||||
|
||||
# Update DatabaseProvider to use SQLite
|
||||
if [ -f "$TEST_DIR/src/Data/TorrentManagerContextFactory.cs" ]; then
|
||||
# Replace Npgsql with SQLite in DbContext factory
|
||||
sed -i 's/UseNpgsql/UseSqlite/g' "$TEST_DIR/src/Data/TorrentManagerContextFactory.cs"
|
||||
echo -e "${YELLOW}Modified database provider to use SQLite.${NC}"
|
||||
fi
|
||||
else
|
||||
# Create test database
|
||||
echo -e "${GREEN}Creating test database...${NC}"
|
||||
psql -U postgres -c "DROP DATABASE IF EXISTS torrentmanager_test;" || true
|
||||
psql -U postgres -c "CREATE DATABASE torrentmanager_test;"
|
||||
fi
|
||||
|
||||
# Install Entity Framework CLI if needed
|
||||
if ! dotnet tool list -g | grep "dotnet-ef" > /dev/null; then
|
||||
echo -e "${GREEN}Installing Entity Framework Core tools...${NC}"
|
||||
dotnet tool install --global dotnet-ef --version 7.0.15
|
||||
fi
|
||||
|
||||
# Apply migrations
|
||||
print_section "Applying database migrations"
|
||||
dotnet ef database update || true
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}Migrations applied successfully.${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Failed to apply migrations, but continuing test.${NC}"
|
||||
fi
|
||||
|
||||
# Test run the application (with timeout)
|
||||
print_section "Testing application startup"
|
||||
timeout 30s dotnet run --urls=http://localhost:5555 &
|
||||
APP_PID=$!
|
||||
|
||||
# Wait for the app to start
|
||||
echo -e "${GREEN}Waiting for application to start...${NC}"
|
||||
sleep 5
|
||||
|
||||
# Try to access the API
|
||||
print_section "Testing API endpoints"
|
||||
MAX_ATTEMPTS=30
|
||||
ATTEMPT=0
|
||||
API_STATUS=1
|
||||
|
||||
echo -e "${GREEN}Waiting for API to become available...${NC}"
|
||||
while [ $ATTEMPT -lt $MAX_ATTEMPTS ] && [ $API_STATUS -ne 0 ]; do
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -s http://localhost:5555/swagger/index.html > /dev/null
|
||||
API_STATUS=$?
|
||||
else
|
||||
# Try using wget if curl is not available
|
||||
if command -v wget >/dev/null 2>&1; then
|
||||
wget -q --spider http://localhost:5555/swagger/index.html
|
||||
API_STATUS=$?
|
||||
else
|
||||
echo -e "${YELLOW}Neither curl nor wget found. Cannot test API.${NC}"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $API_STATUS -ne 0 ]; then
|
||||
echo -n "."
|
||||
sleep 1
|
||||
ATTEMPT=$((ATTEMPT+1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "" # New line after progress dots
|
||||
|
||||
# Kill the app
|
||||
kill $APP_PID 2>/dev/null || true
|
||||
|
||||
if [ $API_STATUS -eq 0 ]; then
|
||||
echo -e "${GREEN}API successfully responded.${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}API did not respond within the timeout period.${NC}"
|
||||
echo -e "${YELLOW}This may be normal if database migrations are slow or there are other startup delays.${NC}"
|
||||
fi
|
||||
|
||||
# Check file permissions and structure
|
||||
print_section "Checking build outputs"
|
||||
if [ -f "$TEST_DIR/bin/Release/net7.0/TransmissionRssManager.dll" ]; then
|
||||
echo -e "${GREEN}Application was built successfully.${NC}"
|
||||
else
|
||||
echo -e "${RED}Missing application binaries.${NC}"
|
||||
fi
|
||||
|
||||
# Clean up test resources
|
||||
print_section "Test completed"
|
||||
if [ "$1" != "--keep" ]; then
|
||||
echo -e "${GREEN}Cleaning up test resources...${NC}"
|
||||
# Drop test database if PostgreSQL is available
|
||||
pg_isready > /dev/null && psql -U postgres -c "DROP DATABASE IF EXISTS torrentmanager_test;" || true
|
||||
# Remove test directory
|
||||
rm -rf "$TEST_DIR"
|
||||
echo -e "${GREEN}Test directory removed.${NC}"
|
||||
else
|
||||
echo -e "${GREEN}Test resources kept as requested.${NC}"
|
||||
echo -e "${GREEN}Test directory: $TEST_DIR${NC}"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Installation test completed.${NC}"
|
Loading…
x
Reference in New Issue
Block a user