Toplu indirme ve Eklenti Güncellemesi
This commit is contained in:
@@ -15,4 +15,30 @@ public class CategoryRepository : ICategoryRepository
|
||||
|
||||
public async Task<DownloadCategory?> GetByIdAsync(int id)
|
||||
=> await _db.Categories.FindAsync(id);
|
||||
|
||||
public async Task AddAsync(DownloadCategory category)
|
||||
{
|
||||
_db.Categories.Add(category);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(DownloadCategory category)
|
||||
{
|
||||
var existing = await _db.Categories.FindAsync(category.Id);
|
||||
if (existing != null)
|
||||
{
|
||||
_db.Entry(existing).CurrentValues.SetValues(category);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var category = await _db.Categories.FindAsync(id);
|
||||
if (category != null)
|
||||
{
|
||||
_db.Categories.Remove(category);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ public interface ICategoryRepository
|
||||
{
|
||||
Task<IEnumerable<DownloadCategory>> GetAllAsync();
|
||||
Task<DownloadCategory?> GetByIdAsync(int id);
|
||||
Task AddAsync(DownloadCategory category);
|
||||
Task UpdateAsync(DownloadCategory category);
|
||||
Task DeleteAsync(int id);
|
||||
}
|
||||
|
||||
public interface ISettingsRepository
|
||||
|
||||
@@ -52,6 +52,25 @@ public class DownloadEngine
|
||||
|
||||
private async Task ProcessDownloadAsync(DownloadItem item, CancellationToken ct)
|
||||
{
|
||||
// Kuyruktan çıktıktan sonra güncel durumu veritabanından kontrol et
|
||||
using (var scope = _serviceProvider.CreateScope())
|
||||
{
|
||||
var repo = scope.ServiceProvider.GetRequiredService<Data.Repositories.IDownloadRepository>();
|
||||
var latest = await repo.GetByIdAsync(item.Id);
|
||||
|
||||
if (latest == null || latest.Status == DownloadStatus.Paused)
|
||||
{
|
||||
Serilog.Log.Information("İndirme {Id} duraklatılmış veya artık mevcut değil, işlem iptal edildi.", item.Id);
|
||||
_queue.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// Eğer veritabanındaki durum farklıysa (örneğin başka bir yerden güncellendiyse) nesneyi güncelle
|
||||
item.Status = latest.Status;
|
||||
item.SavePath = latest.SavePath;
|
||||
item.FileName = latest.FileName;
|
||||
}
|
||||
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
_activeDownloads[item.Id] = cts;
|
||||
|
||||
@@ -73,13 +92,6 @@ public class DownloadEngine
|
||||
item.DownloadedBytes = 0; // Yeni indirme
|
||||
}
|
||||
|
||||
// DURAKLATILMIŞSA İŞLEME (Kuyruktan yeni çıktıysa ve o sırada durdurulduysa)
|
||||
if (item.Status == DownloadStatus.Paused)
|
||||
{
|
||||
Serilog.Log.Information("İndirme {Id} duraklatıldı, atlanıyor.", item.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
item.Status = DownloadStatus.Downloading;
|
||||
|
||||
// Notify UI that we are starting
|
||||
|
||||
@@ -4,7 +4,7 @@ public class EngineOptions
|
||||
{
|
||||
public int MaxSegments { get; set; } = 8;
|
||||
public long MinSegmentBytes { get; set; } = 512 * 1024; // 512 KB
|
||||
public int MaxRetries { get; set; } = 5;
|
||||
public int MaxRetries { get; set; } = 30;
|
||||
public int RetryDelayMs { get; set; } = 2000;
|
||||
public int ConnectionTimeoutSeconds { get; set; } = 30;
|
||||
public long SpeedLimitBytesPerSec { get; set; } = 0; // 0 = limitsiz
|
||||
|
||||
@@ -128,21 +128,29 @@ public class SegmentedDownloader : IDownloader
|
||||
private async Task DownloadSegmentAsync(DownloadItem item, DownloadSegment segment, string tempDir, IProgress<DownloadProgressEvent>? progress, CancellationToken ct)
|
||||
{
|
||||
int retryCount = 0;
|
||||
while (true)
|
||||
Interlocked.Increment(ref _activeSegmentCount);
|
||||
try
|
||||
{
|
||||
try
|
||||
while (true)
|
||||
{
|
||||
await PerformSegmentDownloadAsync(item, segment, tempDir, progress, ct);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex) when (retryCount < _options.MaxRetries && !ct.IsCancellationRequested)
|
||||
{
|
||||
retryCount++;
|
||||
Serilog.Log.Warning("Parça {Index} indirilemedi (Deneme {Count}/{Max}): {Error}",
|
||||
segment.Index, retryCount, _options.MaxRetries, ex.Message);
|
||||
await Task.Delay(_options.RetryDelayMs * retryCount, ct);
|
||||
try
|
||||
{
|
||||
await PerformSegmentDownloadAsync(item, segment, tempDir, progress, ct);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex) when (retryCount < _options.MaxRetries && !ct.IsCancellationRequested)
|
||||
{
|
||||
retryCount++;
|
||||
Serilog.Log.Warning("Parça {Index} indirilemedi (Deneme {Count}/{Max}): {Error}",
|
||||
segment.Index, retryCount, _options.MaxRetries, ex.Message);
|
||||
await Task.Delay((_options.RetryDelayMs * retryCount) + Random.Shared.Next(1000), ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Decrement(ref _activeSegmentCount);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PerformSegmentDownloadAsync(DownloadItem item, DownloadSegment segment, string tempDir, IProgress<DownloadProgressEvent>? progress, CancellationToken ct)
|
||||
@@ -168,9 +176,15 @@ public class SegmentedDownloader : IDownloader
|
||||
|
||||
var buffer = new byte[8192];
|
||||
int read;
|
||||
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length, ct)) > 0)
|
||||
while (true)
|
||||
{
|
||||
await fileStream.WriteAsync(buffer, 0, read, ct);
|
||||
// Read with timeout to prevent hanging on network loss
|
||||
read = await stream.ReadAsync(buffer, ct).AsTask()
|
||||
.WaitAsync(TimeSpan.FromSeconds(_options.ConnectionTimeoutSeconds), ct);
|
||||
|
||||
if (read == 0) break;
|
||||
|
||||
await fileStream.WriteAsync(buffer.AsMemory(0, read), ct);
|
||||
segment.Downloaded += read;
|
||||
|
||||
lock (_progressLock)
|
||||
@@ -186,6 +200,8 @@ public class SegmentedDownloader : IDownloader
|
||||
segment.Status = SegmentStatus.Completed;
|
||||
}
|
||||
|
||||
private int _activeSegmentCount = 0;
|
||||
|
||||
private void ReportProgress(DownloadItem item, IProgress<DownloadProgressEvent>? progress)
|
||||
{
|
||||
if ((DateTime.UtcNow - _lastReport).TotalMilliseconds < 250) return;
|
||||
@@ -203,7 +219,8 @@ public class SegmentedDownloader : IDownloader
|
||||
ProgressPercent = percent,
|
||||
SpeedBytesPerSec = _speedCalculator.GetBytesPerSecond(),
|
||||
EstimatedRemaining = _speedCalculator.EstimateRemaining(item.TotalSize - downloaded),
|
||||
Status = DownloadStatus.Downloading
|
||||
Status = DownloadStatus.Downloading,
|
||||
ActiveSegments = _activeSegmentCount
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public class SingleDownloader : IDownloader
|
||||
retryCount++;
|
||||
Serilog.Log.Warning("Tekil indirme başarısız (Deneme {Count}/{Max}): {Error}",
|
||||
retryCount, _options.MaxRetries, ex.Message);
|
||||
await Task.Delay(_options.RetryDelayMs * retryCount, ct);
|
||||
await Task.Delay((_options.RetryDelayMs * retryCount) + Random.Shared.Next(1000), ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,9 +87,15 @@ public class SingleDownloader : IDownloader
|
||||
|
||||
var buffer = new byte[8192];
|
||||
int read;
|
||||
while ((read = await contentStream.ReadAsync(buffer, 0, buffer.Length, ct)) > 0)
|
||||
while (true)
|
||||
{
|
||||
await fileStream.WriteAsync(buffer, 0, read, ct);
|
||||
// Read with timeout to prevent hanging on network loss
|
||||
read = await contentStream.ReadAsync(buffer, ct).AsTask()
|
||||
.WaitAsync(TimeSpan.FromSeconds(_options.ConnectionTimeoutSeconds), ct);
|
||||
|
||||
if (read <= 0) break;
|
||||
|
||||
await fileStream.WriteAsync(buffer.AsMemory(0, read), ct);
|
||||
item.DownloadedBytes += read;
|
||||
_speedCalculator.AddSample(read);
|
||||
|
||||
|
||||
@@ -12,4 +12,5 @@ public class DownloadProgressEvent
|
||||
public long SpeedBytesPerSec { get; set; }
|
||||
public TimeSpan? EstimatedRemaining { get; set; }
|
||||
public DownloadStatus Status { get; set; }
|
||||
public int ActiveSegments { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using DownloadManager.Core.Data.Repositories;
|
||||
using DownloadManager.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Services;
|
||||
@@ -8,6 +11,10 @@ namespace DownloadManager.Core.Services;
|
||||
public interface ICategoryService
|
||||
{
|
||||
Task<IEnumerable<DownloadCategory>> GetCategoriesAsync();
|
||||
Task AddCategoryAsync(DownloadCategory category);
|
||||
Task UpdateCategoryAsync(DownloadCategory category);
|
||||
Task DeleteCategoryAsync(int id);
|
||||
Task<int> GetCategoryIdByExtensionAsync(string fileName);
|
||||
}
|
||||
|
||||
public class CategoryService : ICategoryService
|
||||
@@ -17,4 +24,34 @@ public class CategoryService : ICategoryService
|
||||
|
||||
public Task<IEnumerable<DownloadCategory>> GetCategoriesAsync()
|
||||
=> _repository.GetAllAsync();
|
||||
|
||||
public Task AddCategoryAsync(DownloadCategory category)
|
||||
=> _repository.AddAsync(category);
|
||||
|
||||
public Task UpdateCategoryAsync(DownloadCategory category)
|
||||
=> _repository.UpdateAsync(category);
|
||||
|
||||
public Task DeleteCategoryAsync(int id)
|
||||
=> _repository.DeleteAsync(id);
|
||||
|
||||
public async Task<int> GetCategoryIdByExtensionAsync(string fileName)
|
||||
{
|
||||
var ext = Path.GetExtension(fileName).TrimStart('.').ToLower();
|
||||
var categories = await _repository.GetAllAsync();
|
||||
|
||||
foreach (var cat in categories)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cat.Extensions)) continue;
|
||||
|
||||
var extensions = cat.Extensions.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim().ToLower());
|
||||
if (extensions.Contains(ext))
|
||||
{
|
||||
return cat.Id;
|
||||
}
|
||||
}
|
||||
|
||||
var other = categories.FirstOrDefault(c => c.Name == "Other" || c.Name == "Diğer");
|
||||
return other?.Id ?? 6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,15 @@ public class DownloadService : IDownloadService
|
||||
await _repository.UpdateAsync(item);
|
||||
}
|
||||
|
||||
public Task PauseDownloadAsync(Guid id)
|
||||
public async Task PauseDownloadAsync(Guid id)
|
||||
{
|
||||
var item = await _repository.GetByIdAsync(id);
|
||||
if (item != null)
|
||||
{
|
||||
item.Status = Enums.DownloadStatus.Paused;
|
||||
await _repository.UpdateAsync(item);
|
||||
}
|
||||
_engine.CancelDownload(id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task ResumeDownloadAsync(Guid id)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -8,15 +9,13 @@ namespace DownloadManager.Core.Services;
|
||||
|
||||
public class SchedulerWatchdog
|
||||
{
|
||||
private readonly IScheduleService _scheduleService;
|
||||
private readonly IDownloadService _downloadService;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private Timer? _timer;
|
||||
private bool _isProcessing;
|
||||
|
||||
public SchedulerWatchdog(IScheduleService scheduleService, IDownloadService downloadService)
|
||||
public SchedulerWatchdog(IServiceProvider serviceProvider)
|
||||
{
|
||||
_scheduleService = scheduleService;
|
||||
_downloadService = downloadService;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
@@ -39,7 +38,10 @@ public class SchedulerWatchdog
|
||||
|
||||
try
|
||||
{
|
||||
var jobs = await _scheduleService.GetJobsAsync();
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var scheduleService = scope.ServiceProvider.GetRequiredService<IScheduleService>();
|
||||
|
||||
var jobs = await scheduleService.GetJobsAsync();
|
||||
var activeJob = jobs.FirstOrDefault(j => j.IsActive);
|
||||
|
||||
if (activeJob == null) return;
|
||||
@@ -49,9 +51,6 @@ public class SchedulerWatchdog
|
||||
|
||||
// Gün kontrolü
|
||||
int dayIndex = (int)now.DayOfWeek; // 0=Pazar, 1=Pazartesi...
|
||||
// Model Pt=0, Pz=6 şeklinde varsayılmıştı (1111111)
|
||||
// .NET DayOfWeek: Sunday=0, Monday=1...
|
||||
// Dönüştürelim: Pt=0 için
|
||||
int adjustedIndex = dayIndex == 0 ? 6 : dayIndex - 1;
|
||||
|
||||
if (activeJob.DaysOfWeek[adjustedIndex] == '0') return;
|
||||
@@ -60,10 +59,9 @@ public class SchedulerWatchdog
|
||||
if (activeJob.StartTime.HasValue)
|
||||
{
|
||||
var startTime = activeJob.StartTime.Value.TimeOfDay;
|
||||
// Başlangıç vaktinden sonraki 1 dakika içindeysek başlat (sürekli tetiklenmemesi için)
|
||||
if (currentTime >= startTime && currentTime < startTime.Add(TimeSpan.FromMinutes(1)))
|
||||
{
|
||||
await StartScheduledDownloads();
|
||||
await StartScheduledDownloads(scope.ServiceProvider.GetRequiredService<IDownloadService>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +71,7 @@ public class SchedulerWatchdog
|
||||
var endTime = activeJob.EndTime.Value.TimeOfDay;
|
||||
if (currentTime >= endTime && currentTime < endTime.Add(TimeSpan.FromMinutes(1)))
|
||||
{
|
||||
await StopAllDownloads();
|
||||
await StopAllDownloads(scope.ServiceProvider.GetRequiredService<IDownloadService>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,27 +85,27 @@ public class SchedulerWatchdog
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartScheduledDownloads()
|
||||
private async Task StartScheduledDownloads(IDownloadService downloadService)
|
||||
{
|
||||
Serilog.Log.Information("Zamanlayıcı: İndirmeler başlatılıyor...");
|
||||
var downloads = await _downloadService.GetAllDownloadsAsync();
|
||||
var downloads = await downloadService.GetAllDownloadsAsync();
|
||||
var pending = downloads.Where(d => d.Status == Enums.DownloadStatus.Paused || d.Status == Enums.DownloadStatus.Pending);
|
||||
|
||||
foreach (var item in pending)
|
||||
{
|
||||
await _downloadService.ResumeDownloadAsync(item.Id);
|
||||
await downloadService.ResumeDownloadAsync(item.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StopAllDownloads()
|
||||
private async Task StopAllDownloads(IDownloadService downloadService)
|
||||
{
|
||||
Serilog.Log.Information("Zamanlayıcı: İndirmeler durduruluyor...");
|
||||
var downloads = await _downloadService.GetAllDownloadsAsync();
|
||||
var downloads = await downloadService.GetAllDownloadsAsync();
|
||||
var active = downloads.Where(d => d.Status == Enums.DownloadStatus.Downloading || d.Status == Enums.DownloadStatus.Queued);
|
||||
|
||||
foreach (var item in active)
|
||||
{
|
||||
await _downloadService.PauseDownloadAsync(item.Id);
|
||||
await downloadService.PauseDownloadAsync(item.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("DownloadManager.Core")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+81faa715a4eb11a2bf648f52891306b46ea7c256")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+561a953b1f10b7ec16bef81fdcd4030ad9d5f1e4")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("DownloadManager.Core")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("DownloadManager.Core")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
57eee8222b0d77ae6e394591882c8d91daaa59ea38bf4b29e8d5d32072290d1d
|
||||
355e174528e4d22ee7c251d97b3ad390b2cefbca24bdd6f2d576299a7fce3925
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
@@ -14,10 +13,10 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("DownloadManager.Core")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+81faa715a4eb11a2bf648f52891306b46ea7c256")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+561a953b1f10b7ec16bef81fdcd4030ad9d5f1e4")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("DownloadManager.Core")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("DownloadManager.Core")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
// MSBuild WriteCodeFragment sınıfı tarafından oluşturuldu.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
86922a9eccfa1e5736aa220690f23d77712e45c8681c90dab58e4b60acfd7921
|
||||
62c36dfd3abfff411d19bf2d9f22e0bd666e57c1af0629fcae7d4ef188fe9e95
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user