Toplu indirme ve Eklenti Güncellemesi

This commit is contained in:
hOLOlu
2026-05-09 11:33:53 +03:00
parent 729879c693
commit 75bd47c052
349 changed files with 5292 additions and 401 deletions

View File

@@ -5,6 +5,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ApplicationIcon>hdm_bridge.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>

View File

@@ -61,32 +61,55 @@ class Program
static async Task SendToMainAppAsync(BridgeMessage msg)
{
string logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "DownloadManager", "logs");
if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir);
string logPath = Path.Combine(logDir, "bridge.log");
try
{
// Ana uygulamanın çalışıp çalışmadığını kontrol et
var processes = System.Diagnostics.Process.GetProcessesByName("hDM");
if (processes.Length == 0)
{
// Uygulama çalışmıyorsa başlat (Yolu bulmaya çalışalım)
string appPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "hDM.exe");
// Uygulama çalışmıyorsa başlat
// Önce kök dizine (proje kökü) çıkıp bin/Debug veya bin/Release kontrolü yapalım
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string appPath = Path.Combine(baseDir, "hDM.exe");
// Geliştirme ortamı için alternatif yollar
// Geliştirme ortamı için alternatifler
if (!File.Exists(appPath))
appPath = @"D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.WPF\bin\Release\net8.0-windows\hDM.exe";
{
// Bridge'den WPF bin klasörüne çıkmaya çalış
var di = new DirectoryInfo(baseDir);
// D:\...\DownloadManager\src\DownloadManager.BrowserBridge\bin\Release\net8.0
// Buradan 4 seviye yukarı çıkarsak src'ye ulaşırız
if (di.Parent?.Parent?.Parent?.Parent != null)
{
string wpfBin = Path.Combine(di.Parent.Parent.Parent.Parent.FullName, "src", "DownloadManager.WPF", "bin", "Debug", "net8.0-windows10.0.19041.0", "hDM.exe");
if (File.Exists(wpfBin)) appPath = wpfBin;
else
{
wpfBin = Path.Combine(di.Parent.Parent.Parent.Parent.FullName, "src", "DownloadManager.WPF", "bin", "Release", "net8.0-windows10.0.19041.0", "hDM.exe");
if (File.Exists(wpfBin)) appPath = wpfBin;
}
}
}
if (File.Exists(appPath))
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(appPath) { UseShellExecute = true });
// Uygulamanın açılması ve pipe'ı başlatması için biraz bekleyelim
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(appPath) { UseShellExecute = true, WorkingDirectory = Path.GetDirectoryName(appPath) });
await Task.Delay(3000);
}
else
{
File.AppendAllText(logPath, $"{DateTime.Now}: Uygulama bulunamadı: {appPath}\n");
}
}
using var pipe = new NamedPipeClientStream(".", "DownloadManagerPipe",
PipeDirection.Out, PipeOptions.Asynchronous);
// Timeout'u 10 saniyeye çıkaralım (Uygulama yeni açılıyorsa gerekebilir)
await pipe.ConnectAsync(10000);
await pipe.ConnectAsync(5000);
var json = JsonConvert.SerializeObject(msg);
var data = Encoding.UTF8.GetBytes(json);
@@ -96,7 +119,7 @@ class Program
}
catch (Exception ex)
{
File.AppendAllText("bridge_error.log", $"{DateTime.Now}: Bağlantı hatası ({msg.Url}): {ex.Message}\n");
File.AppendAllText(logPath, $"{DateTime.Now}: Bağlantı hatası ({msg.Url}): {ex.Message}\n");
}
}
}

View File

@@ -0,0 +1,41 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"DownloadManager.BrowserBridge/1.0.0": {
"dependencies": {
"Newtonsoft.Json": "13.0.4"
},
"runtime": {
"DownloadManager.BrowserBridge.dll": {}
}
},
"Newtonsoft.Json/13.0.4": {
"runtime": {
"lib/net6.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.4.30916"
}
}
}
}
},
"libraries": {
"DownloadManager.BrowserBridge/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Newtonsoft.Json/13.0.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A==",
"path": "newtonsoft.json/13.0.4",
"hashPath": "newtonsoft.json.13.0.4.nupkg.sha512"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
"configProperties": {
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View File

@@ -4,6 +4,7 @@
"path": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\bin\\Release\\net8.0\\DownloadManager.BrowserBridge.exe",
"type": "stdio",
"allowed_origins": [
"chrome-extension://gnohncemfbplcagkfdhedbkfaogcoobi/"
"chrome-extension://dnpggiajimpjhghjppekgpejplkcbejg",
"chrome-extension://gnohncemfbplcagkfdhedbkfaogcoobi"
]
}

View File

@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("DownloadManager.BrowserBridge")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+561a953b1f10b7ec16bef81fdcd4030ad9d5f1e4")]
[assembly: System.Reflection.AssemblyProductAttribute("DownloadManager.BrowserBridge")]
[assembly: System.Reflection.AssemblyTitleAttribute("DownloadManager.BrowserBridge")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// MSBuild WriteCodeFragment sınıfı tarafından oluşturuldu.

View File

@@ -0,0 +1 @@
113565b263fcdda4a4665ee4bb805cc1b4b99e5585e061be924810699276bedd

View File

@@ -0,0 +1,15 @@
is_global = true
build_property.TargetFramework = net8.0
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = DownloadManager.BrowserBridge
build_property.ProjectDir = D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 8.0
build_property.EnableCodeStyleSeverity =

View File

@@ -0,0 +1 @@
5fd99a422a78009cff8ca33e1127d7f18d84d9f6b01faca46a390be79a246ed9

View File

@@ -0,0 +1,17 @@
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Debug\net8.0\DownloadManager.BrowserBridge.exe
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Debug\net8.0\DownloadManager.BrowserBridge.deps.json
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Debug\net8.0\DownloadManager.BrowserBridge.runtimeconfig.json
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Debug\net8.0\DownloadManager.BrowserBridge.dll
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Debug\net8.0\DownloadManager.BrowserBridge.pdb
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Debug\net8.0\Newtonsoft.Json.dll
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\DownloadManager.BrowserBridge.csproj.AssemblyReference.cache
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\DownloadManager.BrowserBridge.GeneratedMSBuildEditorConfig.editorconfig
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\DownloadManager.BrowserBridge.AssemblyInfoInputs.cache
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\DownloadManager.BrowserBridge.AssemblyInfo.cs
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\DownloadManager.BrowserBridge.csproj.CoreCompileInputs.cache
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\Download.A73557B5.Up2Date
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\DownloadManager.BrowserBridge.dll
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\refint\DownloadManager.BrowserBridge.dll
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\DownloadManager.BrowserBridge.pdb
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\DownloadManager.BrowserBridge.genruntimeconfig.cache
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Debug\net8.0\ref\DownloadManager.BrowserBridge.dll

View File

@@ -0,0 +1 @@
55a289d435d94f5304dfb112e526c2c5fd4f8dced6fb87cc465459bf16bab373

View File

@@ -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.BrowserBridge")]
[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.BrowserBridge")]
[assembly: System.Reflection.AssemblyTitleAttribute("DownloadManager.BrowserBridge")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
// MSBuild WriteCodeFragment sınıfı tarafından oluşturuldu.

View File

@@ -1 +1 @@
c41154c222d4c9aaf2ac34116ff46b068de5b4633ca984a833d7394a42fc99da
f0f25ac1863349e31e231b7ddaa3d27d9e23bc3cc9323375791cbf31e36329ff

View File

@@ -1,7 +1,9 @@
Windows Registry Editor Version 5.00
; Google Chrome için Kayıt
[HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.downloadmanager.bridge]
@="D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\manifest.json"
; Microsoft Edge için Kayıt
[HKEY_CURRENT_USER\Software\Microsoft\Edge\NativeMessagingHosts\com.downloadmanager.bridge]
@="D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\manifest.json"

View File

@@ -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();
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
});
}

View File

@@ -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);

View File

@@ -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; }
}

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -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")]

View File

@@ -1 +1 @@
57eee8222b0d77ae6e394591882c8d91daaa59ea38bf4b29e8d5d32072290d1d
355e174528e4d22ee7c251d97b3ad390b2cefbca24bdd6f2d576299a7fce3925

View File

@@ -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.

View File

@@ -1 +1 @@
86922a9eccfa1e5736aa220690f23d77712e45c8681c90dab58e4b60acfd7921
62c36dfd3abfff411d19bf2d9f22e0bd666e57c1af0629fcae7d4ef188fe9e95

View File

@@ -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.TestConsole")]
[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.TestConsole")]
[assembly: System.Reflection.AssemblyTitleAttribute("DownloadManager.TestConsole")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
// MSBuild WriteCodeFragment sınıfı tarafından oluşturuldu.

View File

@@ -1 +1 @@
ca6e1ab4507e84385d05f14541ed3151a50c58ff919b0dc872c1409753ebe984
eb1425e8d7f5855caa9e9266e09a3f32bbe7a7b2ce13f06d61c757365565f828

View File

@@ -7,8 +7,10 @@ using DownloadManager.Core.Models;
using DownloadManager.Core.Protocols;
using DownloadManager.Core.Queue;
using DownloadManager.Core.Services;
using DownloadManager.WPF.Helpers;
using DownloadManager.WPF.ViewModels;
using DownloadManager.WPF.Views;
// ... (rest of imports should be fine)
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -29,6 +31,7 @@ public partial class App : System.Windows.Application
{
private IHost _host;
private CancellationTokenSource _pipeCts = new();
private static Mutex? _mutex;
private static string GetExceptionDetails(Exception? ex)
{
@@ -80,19 +83,19 @@ public partial class App : System.Windows.Application
// Veritabanı
var dbPath = Path.Combine(GetAppDataPath(), "downloadmanager.db");
services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlite($"Data Source={dbPath}"), ServiceLifetime.Singleton);
opt.UseSqlite($"Data Source={dbPath}"), ServiceLifetime.Transient);
// Repositories
services.AddSingleton<IDownloadRepository, DownloadRepository>();
services.AddSingleton<ICategoryRepository, CategoryRepository>();
services.AddSingleton<ISettingsRepository, SettingsRepository>();
services.AddSingleton<IScheduleRepository, ScheduleRepository>();
services.AddTransient<IDownloadRepository, DownloadRepository>();
services.AddTransient<ICategoryRepository, CategoryRepository>();
services.AddTransient<ISettingsRepository, SettingsRepository>();
services.AddTransient<IScheduleRepository, ScheduleRepository>();
// Services
services.AddSingleton<IDownloadService, DownloadService>();
services.AddSingleton<ICategoryService, CategoryService>();
services.AddSingleton<ISettingsService, SettingsService>();
services.AddSingleton<IScheduleService, ScheduleService>();
services.AddTransient<IDownloadService, DownloadService>();
services.AddTransient<ICategoryService, CategoryService>();
services.AddTransient<ISettingsService, SettingsService>();
services.AddTransient<IScheduleService, ScheduleService>();
services.AddSingleton<SchedulerWatchdog>();
// Altyapı ve Motor
@@ -137,6 +140,14 @@ public partial class App : System.Windows.Application
protected override async void OnStartup(StartupEventArgs e)
{
_mutex = new Mutex(true, "hDM_SingleInstance_Mutex", out bool createdNew);
if (!createdNew)
{
// Zaten bir örnek çalışıyor
System.Windows.Application.Current.Shutdown();
return;
}
base.OnStartup(e);
try
@@ -164,13 +175,13 @@ public partial class App : System.Windows.Application
var mainWindow = _host.Services.GetRequiredService<MainWindow>();
var mainVm = _host.Services.GetRequiredService<MainViewModel>();
var watchdog = _host.Services.GetRequiredService<SchedulerWatchdog>();
mainWindow.DataContext = mainVm;
var categoryService = _host.Services.GetRequiredService<ICategoryService>();
mainWindow.DataContext = mainVm;
await Helpers.WindowStateHelper.LoadStateAsync(mainWindow, settings);
// Browser Bridge için Named Pipe Sunucusunu Başlat
_ = StartPipeServerAsync(downloadService, protocol, settings, mainVm);
_ = StartPipeServerAsync(downloadService, protocol, settings, mainVm, categoryService);
// Zamanlayıcıyı Başlat
watchdog.Start();
@@ -206,7 +217,7 @@ public partial class App : System.Windows.Application
System.Windows.Application.Current.Shutdown();
}
private async Task StartPipeServerAsync(IDownloadService service, IDownloadProtocol protocol, ISettingsService settings, MainViewModel mainVm)
private async Task StartPipeServerAsync(IDownloadService service, IDownloadProtocol protocol, ISettingsService settings, MainViewModel mainVm, ICategoryService categoryService)
{
Log.Information("Named Pipe sunucusu dinlemeye başladı...");
while (!_pipeCts.Token.IsCancellationRequested)
@@ -249,20 +260,19 @@ public partial class App : System.Windows.Application
var grabber = _host.Services.GetRequiredService<SiteGrabber>();
var grabberWin = new Views.Dialogs.GrabberWindow(grabber, url) { Owner = win };
if (grabberWin.ShowDialog() == true)
if (await grabberWin.ShowDialogAsyncSafe() == true)
{
// Sonuçları ekle (MainViewModel'deki OpenGrabberCommand mantığıyla aynı)
var selected = grabberWin.SelectedResults;
var defaultPath = await settings.GetAsync("DefaultSavePath");
foreach (var res in selected)
{
var item = new DownloadItem { Url = res.Url, FileName = res.FileName, SavePath = Path.Combine(defaultPath ?? "", res.FileName), Status = DownloadStatus.Queued };
var catName = Helpers.FileCategoryHelper.GetCategoryByExtension(item.FileName);
item.CategoryId = catName switch { "Software"=>1,"Document"=>2,"Audio"=>3,"Video"=>4,"Image"=>5,_=>6 };
var item = new DownloadItem { Url = res.Url, FileName = res.FileName, SavePath = Path.Combine(defaultPath ?? "", res.FileName), Status = DownloadStatus.Paused };
item.CategoryId = await categoryService.GetCategoryIdByExtensionAsync(item.FileName);
await service.AddDownloadAsync(item);
mainVm.AddDownloadItemViewModel(new DownloadItemViewModel {
Id = item.Id, FileName = item.FileName, Url = item.Url,
Status = DownloadStatus.Queued, FormattedSize = "Bilinmiyor",
Status = DownloadStatus.Paused, FormattedSize = "Bilinmiyor",
CreatedAt = item.CreatedAt,
LastActivityAt = item.LastActivityAt
});
@@ -279,43 +289,46 @@ public partial class App : System.Windows.Application
defaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");
}
// "Ekle" penceresini otomatik aç
var dialog = new Views.Dialogs.AddDownloadDialog(protocol, defaultPath, url, suggestedName);
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
try
{
try
var item = await protocol.GetFileInfoAsync(url, CancellationToken.None);
if (!string.IsNullOrWhiteSpace(suggestedName))
{
var item = await protocol.GetFileInfoAsync(dialog.Urls, CancellationToken.None);
// Kullanıcı pencerede ismi değiştirdiyse onu kullan
if (!string.IsNullOrWhiteSpace(dialog.FileName))
{
item.FileName = dialog.FileName;
}
item.SavePath = Path.Combine(dialog.SavePath, item.FileName);
// Kategori ata
var catName = Helpers.FileCategoryHelper.GetCategoryByExtension(item.FileName);
item.CategoryId = catName switch { "Software"=>1,"Document"=>2,"Audio"=>3,"Video"=>4,"Image"=>5,_=>6 };
await service.AddDownloadAsync(item);
mainVm.AddDownloadItemViewModel(new DownloadItemViewModel {
Id = item.Id, FileName = item.FileName, Url = item.Url,
Status = DownloadStatus.Queued,
FormattedSize = mainVm.FormatSizeInternal(item.TotalSize),
CreatedAt = item.CreatedAt,
LastActivityAt = item.LastActivityAt
});
item.FileName = suggestedName;
}
catch (Exception ex)
var targetPath = Path.Combine(defaultPath, item.FileName);
// Çakışma kontrolü (dosya adını eşsiz yap)
var counter = 1;
var dir = Path.GetDirectoryName(targetPath) ?? "";
var fn = Path.GetFileNameWithoutExtension(targetPath);
var ext = Path.GetExtension(targetPath);
while (File.Exists(targetPath))
{
Log.Error(ex, "Tarayıcıdan gelen URL eklenirken hata");
System.Windows.MessageBox.Show($"Tarayıcıdan gelen indirme eklenemedi.\n\nURL: {url}\n\nHata: {ex.Message}", "Bağlantı Hatası", MessageBoxButton.OK, MessageBoxImage.Error);
targetPath = Path.Combine(dir, $"{fn} ({counter++}){ext}");
}
item.FileName = Path.GetFileName(targetPath);
item.SavePath = targetPath;
// Kategori ata
item.CategoryId = await categoryService.GetCategoryIdByExtensionAsync(item.FileName);
await service.AddDownloadAsync(item);
mainVm.AddDownloadItemViewModel(new DownloadItemViewModel {
Id = item.Id, FileName = item.FileName, Url = item.Url,
Status = DownloadStatus.Paused,
FormattedSize = mainVm.FormatSizeInternal(item.TotalSize),
CreatedAt = item.CreatedAt,
LastActivityAt = item.LastActivityAt
});
}
catch (Exception ex)
{
Log.Error(ex, "Tarayıcıdan gelen URL otomatik eklenirken hata: {Url}", url);
}
}
});
@@ -345,6 +358,9 @@ public partial class App : System.Windows.Application
}
}
_mutex?.ReleaseMutex();
_mutex?.Dispose();
base.OnExit(e);
}
}

View File

@@ -0,0 +1,38 @@
using ModernWpf.Controls;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace DownloadManager.WPF.Helpers;
public static class DialogHelper
{
private static readonly SemaphoreSlim _dialogSemaphore = new(1, 1);
public static async Task<ContentDialogResult> ShowAsyncSafe(this ContentDialog dialog)
{
await _dialogSemaphore.WaitAsync();
try
{
return await dialog.ShowAsync();
}
finally
{
await Task.Delay(200); // ModernWpf'nin iç durumunu (activeDialog) temizlemesi için zaman tanı
_dialogSemaphore.Release();
}
}
public static async Task<bool?> ShowDialogAsyncSafe(this System.Windows.Window window)
{
await _dialogSemaphore.WaitAsync();
try
{
return window.ShowDialog();
}
finally
{
_dialogSemaphore.Release();
}
}
}

View File

@@ -15,6 +15,7 @@ public partial class DownloadItemViewModel : ObservableObject
[ObservableProperty] private string _formattedSize = "—";
[ObservableProperty] private double _progressPercent;
[ObservableProperty] private string _speed = "—";
[ObservableProperty] private long _rawSpeed;
[ObservableProperty] private string _timeRemaining = "—";
[ObservableProperty] private DownloadStatus _status;
[ObservableProperty] private int _categoryId;
@@ -39,6 +40,7 @@ public partial class DownloadItemViewModel : ObservableObject
System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
{
ProgressPercent = e.ProgressPercent;
RawSpeed = e.SpeedBytesPerSec;
Speed = FormatSpeed(e.SpeedBytesPerSec);
TimeRemaining = FormatRemaining(e.EstimatedRemaining);
Status = e.Status;

View File

@@ -8,6 +8,7 @@ using DownloadManager.Core.Models;
using DownloadManager.Core.Protocols;
using DownloadManager.Core.Queue;
using DownloadManager.Core.Services;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -18,6 +19,7 @@ using Newtonsoft.Json;
using WpfApp = System.Windows.Application;
using WpfMsg = System.Windows.MessageBox;
using DownloadManager.WPF.Views;
using DownloadManager.WPF.Helpers;
namespace DownloadManager.WPF.ViewModels;
@@ -31,13 +33,25 @@ public partial class MainViewModel : ObservableObject
private readonly EngineOptions _engineOptions;
private readonly ISettingsService _settingsService;
private readonly IScheduleService _scheduleService;
private readonly ICategoryService _categoryService;
private readonly SiteGrabber _grabber;
private readonly IServiceProvider _serviceProvider;
[ObservableProperty] private ObservableCollection<DownloadItemViewModel> _downloads = new();
private readonly List<DownloadItemViewModel> _allDownloads = new();
[ObservableProperty] private ObservableCollection<DownloadCategory> _categories = new();
[ObservableProperty] private DownloadItemViewModel? _selectedDownload;
[ObservableProperty] private DownloadCategory? _selectedCategory;
[ObservableProperty] private string _totalSpeed = "Toplam: 0 B/s";
[ObservableProperty] private int _activeCount;
[ObservableProperty] private int _completedCount;
[ObservableProperty] private int _errorCount;
[ObservableProperty] private int _pausedCount;
[ObservableProperty] private int _queuedCount;
[ObservableProperty] private int _totalCount;
[ObservableProperty] private string _activeSegmentsTotal = "0/0";
[ObservableProperty] private string _diskFreeSpace = string.Empty;
[ObservableProperty] private System.Windows.Media.PointCollection _speedPoints = new();
@@ -55,6 +69,22 @@ public partial class MainViewModel : ObservableObject
ResumeDownloadCommand.NotifyCanExecuteChanged();
}
partial void OnSelectedCategoryChanged(DownloadCategory? value)
{
ApplyCategoryFilter();
}
private void ApplyCategoryFilter()
{
var list = SelectedCategory == null || SelectedCategory.Id == 0
? _allDownloads.ToList()
: _allDownloads.Where(x => x.CategoryId == SelectedCategory.Id).ToList();
Downloads.Clear();
foreach(var item in list) Downloads.Add(item);
SortDownloads(SortColumn, IsSortAscending);
}
public MainViewModel(
IDownloadService downloadService,
IDownloadProtocol protocol,
@@ -64,7 +94,9 @@ public partial class MainViewModel : ObservableObject
EngineOptions engineOptions,
ISettingsService settingsService,
IScheduleService scheduleService,
SiteGrabber grabber)
ICategoryService categoryService,
SiteGrabber grabber,
IServiceProvider serviceProvider)
{
_downloadService = downloadService;
_protocol = protocol;
@@ -74,7 +106,9 @@ public partial class MainViewModel : ObservableObject
_engineOptions = engineOptions;
_settingsService = settingsService;
_scheduleService = scheduleService;
_categoryService = categoryService;
_grabber = grabber;
_serviceProvider = serviceProvider;
_engine.ProgressChanged += OnEngineProgressChanged;
@@ -139,15 +173,26 @@ public partial class MainViewModel : ObservableObject
}
}
var cats = await _categoryService.GetCategoriesAsync();
WpfApp.Current.Dispatcher.Invoke(() =>
{
Downloads.Clear();
Categories.Clear();
// Add a virtual 'All' category
Categories.Add(new DownloadCategory { Id = 0, Name = "Tümü" });
foreach (var cat in cats)
{
Categories.Add(cat);
}
SelectedCategory = Categories.FirstOrDefault();
_allDownloads.Clear();
foreach (var item in items)
{
var downloaded = Math.Min(item.DownloadedBytes, item.TotalSize);
var progress = item.TotalSize > 0 ? (double)downloaded / item.TotalSize * 100 : 0;
Downloads.Add(new DownloadItemViewModel
_allDownloads.Add(new DownloadItemViewModel
{
Id = item.Id,
FileName = item.FileName,
@@ -155,11 +200,13 @@ public partial class MainViewModel : ObservableObject
Status = item.Status,
ProgressPercent = Math.Min(100, progress),
FormattedSize = FormatSize(item.TotalSize),
CategoryId = item.CategoryId,
CreatedAt = item.CreatedAt,
LastActivityAt = item.LastActivityAt
});
}
SortDownloads(SortColumn, IsSortAscending);
ApplyCategoryFilter();
UpdateGlobalStatsInternal();
});
}
@@ -193,12 +240,18 @@ public partial class MainViewModel : ObservableObject
{
WpfApp.Current.Dispatcher.Invoke(() =>
{
var vm = Downloads.FirstOrDefault(x => x.Id == e.Id);
var vm = _allDownloads.FirstOrDefault(x => x.Id == e.Id);
if (vm != null)
{
vm.ApplyProgress(e);
vm.LastActivityAt = DateTime.Now;
// Aktif segment sayısını güncelle
if (e.Status == DownloadStatus.Downloading)
_activeSegmentCounts[e.Id] = e.ActiveSegments;
else
_activeSegmentCounts.Remove(e.Id);
if (e.Status == DownloadStatus.Completed)
{
vm.LastActivityAt = DateTime.Now;
@@ -209,13 +262,15 @@ public partial class MainViewModel : ObservableObject
{
try
{
var downloads = await _downloadService.GetAllDownloadsAsync();
using var scope = _serviceProvider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IDownloadService>();
var downloads = await service.GetAllDownloadsAsync();
var item = downloads.FirstOrDefault(x => x.Id == vm.Id);
if (item != null)
{
item.LastActivityAt = vm.LastActivityAt;
item.Status = DownloadStatus.Completed;
await _downloadService.UpdateDownloadAsync(item);
await service.UpdateDownloadAsync(item);
}
}
catch { /* Sessizce yut */ }
@@ -226,43 +281,34 @@ public partial class MainViewModel : ObservableObject
});
}
private readonly Dictionary<Guid, int> _activeSegmentCounts = new();
private void UpdateGlobalStatsInternal()
{
var activeItems = Downloads.Where(x => x.Status == DownloadStatus.Downloading).ToList();
ActiveCount = activeItems.Count;
var allItems = _allDownloads.ToList();
TotalCount = allItems.Count;
ActiveCount = allItems.Count(x => x.Status == DownloadStatus.Downloading);
CompletedCount = allItems.Count(x => x.Status == DownloadStatus.Completed);
PausedCount = allItems.Count(x => x.Status == DownloadStatus.Paused);
ErrorCount = allItems.Count(x => x.Status == DownloadStatus.Error);
QueuedCount = allItems.Count(x => x.Status == DownloadStatus.Queued);
var activeItems = _allDownloads.Where(x => x.Status == DownloadStatus.Downloading).ToList();
double totalMbps = 0;
foreach (var item in activeItems)
{
if (string.IsNullOrEmpty(item.Speed) || item.Speed == "—") continue;
var parts = item.Speed.Split(' ');
if (parts.Length < 2) continue;
if (double.TryParse(parts[0], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double val))
{
if (parts[1].StartsWith("MB")) totalMbps += val;
else if (parts[1].StartsWith("KB")) totalMbps += val / 1024;
}
}
TotalSpeed = $"Toplam: {totalMbps:F1} MB/s";
int totalActiveSegments = _activeSegmentCounts.Values.Sum();
int maxPossibleSegments = activeItems.Count * _engineOptions.MaxSegments;
ActiveSegmentsTotal = $"{totalActiveSegments}/{maxPossibleSegments}";
long totalBytesPerSec = activeItems.Sum(x => x.RawSpeed);
TotalSpeed = $"Toplam: {FormatSize(totalBytesPerSec)}/s";
_ = UpdateDiskSpaceAsync();
}
private void UpdateSpeedChart()
{
var activeItems = Downloads.Where(x => x.Status == DownloadStatus.Downloading).ToList();
var activeItems = _allDownloads.Where(x => x.Status == DownloadStatus.Downloading).ToList();
double totalMbps = 0;
foreach (var item in activeItems)
{
if (string.IsNullOrEmpty(item.Speed) || item.Speed == "—") continue;
var parts = item.Speed.Split(' ');
if (parts.Length < 2) continue;
if (double.TryParse(parts[0], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double val))
{
if (parts[1].StartsWith("MB")) totalMbps += val;
else if (parts[1].StartsWith("KB")) totalMbps += val / 1024;
}
}
double totalMbps = activeItems.Sum(x => (double)x.RawSpeed / (1024 * 1024));
_speedHistory.Add(totalMbps);
if (_speedHistory.Count > MaxChartPoints) _speedHistory.RemoveAt(0);
@@ -272,12 +318,11 @@ public partial class MainViewModel : ObservableObject
{
double maxSpeed = _speedHistory.Max();
if (maxSpeed < 0.1) maxSpeed = 0.1;
//var w = ((System.Windows.Controls.Panel)Application.Current.MainWindow.Content).ActualWidth;
//var w = bannerbg.ActualWidth
int w = (int)System.Windows.SystemParameters.PrimaryScreenWidth;
double width = w; // 900; // Grafik genişliği
double height = 40; // Grafik yüksekliği
double width = w;
double height = 40;
double stepX = width / (MaxChartPoints - 1);
for (int i = 0; i < _speedHistory.Count; i++)
@@ -315,8 +360,9 @@ public partial class MainViewModel : ObservableObject
{
WpfApp.Current.Dispatcher.Invoke(() =>
{
Downloads.Add(vm);
SortDownloads(SortColumn, IsSortAscending);
_allDownloads.Add(vm);
ApplyCategoryFilter();
UpdateGlobalStatsInternal();
});
}
@@ -340,7 +386,7 @@ public partial class MainViewModel : ObservableObject
var defaultPath = await _settingsService.GetAsync("DefaultSavePath");
var dialog = new Views.Dialogs.AddDownloadDialog(_protocol, defaultPath, clipboardUrl);
var result = await dialog.ShowAsync();
var result = await dialog.ShowAsyncSafe();
if (result == ModernWpf.Controls.ContentDialogResult.Primary)
{
@@ -349,7 +395,7 @@ public partial class MainViewModel : ObservableObject
{
try
{
if (Downloads.Any(x => x.Url == url))
if (_allDownloads.Any(x => x.Url == url))
{
var msgResult = WpfMsg.Show("Bu URL zaten listede mevcut. Tekrar eklemek istiyor musunuz?", "Mükerrer Kayıt", System.Windows.MessageBoxButton.YesNo);
if (msgResult == System.Windows.MessageBoxResult.No) continue;
@@ -378,7 +424,7 @@ public partial class MainViewModel : ObservableObject
var vm = new DownloadItemViewModel
{
Id = item.Id, FileName = item.FileName, Url = item.Url,
Status = DownloadStatus.Queued, FormattedSize = FormatSize(item.TotalSize),
Status = DownloadStatus.Paused, FormattedSize = FormatSize(item.TotalSize),
CategoryId = item.CategoryId,
CreatedAt = item.CreatedAt,
LastActivityAt = item.LastActivityAt
@@ -386,10 +432,11 @@ public partial class MainViewModel : ObservableObject
WpfApp.Current.Dispatcher.Invoke(() =>
{
Downloads.Add(vm);
// Mevcut sıralamayı tekrar uygula
SortDownloads(SortColumn, IsSortAscending);
_allDownloads.Add(vm);
ApplyCategoryFilter();
UpdateGlobalStatsInternal();
});
item.Status = DownloadStatus.Paused;
await _downloadService.AddDownloadAsync(item);
}
catch (Exception ex)
@@ -411,6 +458,30 @@ public partial class MainViewModel : ObservableObject
return filePath;
}
[RelayCommand]
private async Task ResumePending()
{
var pendingItems = _allDownloads.Where(x => x.Status == DownloadStatus.Paused || x.Status == DownloadStatus.Error).ToList();
foreach (var vm in pendingItems)
{
vm.Status = DownloadStatus.Queued;
await _downloadService.ResumeDownloadAsync(vm.Id);
}
UpdateGlobalStatsInternal();
}
[RelayCommand]
private async Task PauseAll()
{
var activeItems = _allDownloads.Where(x => x.Status == DownloadStatus.Downloading || x.Status == DownloadStatus.Queued).ToList();
foreach (var vm in activeItems)
{
await _downloadService.PauseDownloadAsync(vm.Id);
vm.Status = DownloadStatus.Paused;
}
UpdateGlobalStatsInternal();
}
[RelayCommand(CanExecute = nameof(CanPause))]
private async Task PauseDownload(DownloadItemViewModel? vm)
{
@@ -419,6 +490,7 @@ public partial class MainViewModel : ObservableObject
{
await _downloadService.PauseDownloadAsync(target.Id);
target.Status = DownloadStatus.Paused;
UpdateGlobalStatsInternal();
PauseDownloadCommand.NotifyCanExecuteChanged();
ResumeDownloadCommand.NotifyCanExecuteChanged();
}
@@ -438,6 +510,7 @@ public partial class MainViewModel : ObservableObject
{
target.Status = DownloadStatus.Queued;
await _downloadService.ResumeDownloadAsync(target.Id);
UpdateGlobalStatsInternal();
PauseDownloadCommand.NotifyCanExecuteChanged();
ResumeDownloadCommand.NotifyCanExecuteChanged();
}
@@ -455,8 +528,7 @@ public partial class MainViewModel : ObservableObject
if (vm == null) return;
try
{
var downloads = await _downloadService.GetAllDownloadsAsync();
var item = downloads.FirstOrDefault(x => x.Id == vm.Id);
var item = (await _downloadService.GetAllDownloadsAsync()).FirstOrDefault(x => x.Id == vm.Id);
if (item != null && File.Exists(item.SavePath))
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(item.SavePath) { UseShellExecute = true });
else
@@ -473,8 +545,7 @@ public partial class MainViewModel : ObservableObject
if (vm == null) return;
try
{
var downloads = await _downloadService.GetAllDownloadsAsync();
var item = downloads.FirstOrDefault(x => x.Id == vm.Id);
var item = (await _downloadService.GetAllDownloadsAsync()).FirstOrDefault(x => x.Id == vm.Id);
if (item != null && File.Exists(item.SavePath))
System.Diagnostics.Process.Start("rundll32.exe", $"shell32.dll,OpenAs_RunDLL {item.SavePath}");
}
@@ -487,8 +558,7 @@ public partial class MainViewModel : ObservableObject
if (vm == null) return;
try
{
var downloads = await _downloadService.GetAllDownloadsAsync();
var item = downloads.FirstOrDefault(x => x.Id == vm.Id);
var item = (await _downloadService.GetAllDownloadsAsync()).FirstOrDefault(x => x.Id == vm.Id);
if (item != null)
{
if (File.Exists(item.SavePath))
@@ -508,7 +578,7 @@ public partial class MainViewModel : ObservableObject
var item = (await _downloadService.GetAllDownloadsAsync()).FirstOrDefault(x => x.Id == vm.Id);
if (item == null) return;
var dialog = new Views.Dialogs.RenameDialog(item.SavePath);
var result = await dialog.ShowAsync();
var result = await dialog.ShowAsyncSafe();
if (result == ModernWpf.Controls.ContentDialogResult.Primary)
{
string newPath = Path.Combine(dialog.NewPath, dialog.NewFileName);
@@ -534,6 +604,7 @@ public partial class MainViewModel : ObservableObject
await _downloadService.UpdateDownloadAsync(item);
vm.ProgressPercent = 0; vm.Status = DownloadStatus.Queued;
_queue.Enqueue(item);
UpdateGlobalStatsInternal();
}
[RelayCommand]
@@ -558,13 +629,13 @@ public partial class MainViewModel : ObservableObject
{
try
{
// Önce arkaplan işlemini bitir
await _downloadService.DeleteDownloadAsync(target.Id);
// UI listesinden güvenli bir şekilde çıkar
WpfApp.Current.Dispatcher.Invoke(() =>
{
Downloads.Remove(target);
_allDownloads.Remove(target);
ApplyCategoryFilter();
UpdateGlobalStatsInternal();
});
}
catch (Exception ex)
@@ -591,7 +662,9 @@ public partial class MainViewModel : ObservableObject
WpfApp.Current.Dispatcher.Invoke(() =>
{
Downloads.Remove(target);
_allDownloads.Remove(target);
ApplyCategoryFilter();
UpdateGlobalStatsInternal();
});
if (item != null && File.Exists(item.SavePath))
@@ -622,7 +695,7 @@ public partial class MainViewModel : ObservableObject
if (item != null)
{
var dialog = new Views.Dialogs.PropertiesDialog(item, target.FormattedSize);
await dialog.ShowAsync();
await dialog.ShowAsyncSafe();
}
}
catch (Exception ex)
@@ -634,8 +707,8 @@ public partial class MainViewModel : ObservableObject
[RelayCommand]
private async Task OpenSettings()
{
var dialog = new Views.Dialogs.SettingsDialog(_settingsService);
var result = await dialog.ShowAsync();
var dialog = new Views.Dialogs.SettingsDialog(_settingsService, _categoryService);
var result = await dialog.ShowAsyncSafe();
if (result == ModernWpf.Controls.ContentDialogResult.Primary)
{
dialog.SaveSettings();
@@ -661,7 +734,7 @@ public partial class MainViewModel : ObservableObject
private async Task OpenScheduler()
{
var dialog = new Views.Dialogs.SchedulerDialog(_scheduleService);
var result = await dialog.ShowAsync();
var result = await dialog.ShowAsyncSafe();
if (result == ModernWpf.Controls.ContentDialogResult.Primary) { dialog.SaveJob(); }
}
@@ -673,25 +746,27 @@ public partial class MainViewModel : ObservableObject
Owner = WpfApp.Current.MainWindow
};
if (dialog.ShowDialog() == true)
if (await dialog.ShowDialogAsyncSafe() == true)
{
var selected = dialog.SelectedResults;
var defaultPath = await _settingsService.GetAsync("DefaultSavePath");
foreach (var res in selected)
{
var item = new DownloadItem { Url = res.Url, FileName = res.FileName, SavePath = Path.Combine(defaultPath ?? "", res.FileName), Status = DownloadStatus.Queued };
var catName = Helpers.FileCategoryHelper.GetCategoryByExtension(item.FileName);
item.CategoryId = catName switch { "Software"=>1,"Document"=>2,"Audio"=>3,"Video"=>4,"Image"=>5,_=>6 };
var item = new DownloadItem { Url = res.Url, FileName = res.FileName, SavePath = Path.Combine(defaultPath ?? "", res.FileName), Status = DownloadStatus.Paused };
item.CategoryId = await _categoryService.GetCategoryIdByExtensionAsync(item.FileName);
await _downloadService.AddDownloadAsync(item);
WpfApp.Current.Dispatcher.Invoke(() => {
Downloads.Add(new DownloadItemViewModel {
var vm = new DownloadItemViewModel {
Id = item.Id, FileName = item.FileName, Url = item.Url,
Status = DownloadStatus.Queued, FormattedSize = "Bilinmiyor",
Status = DownloadStatus.Paused, FormattedSize = "Bilinmiyor",
CreatedAt = item.CreatedAt,
LastActivityAt = item.LastActivityAt
});
};
_allDownloads.Add(vm);
});
}
ApplyCategoryFilter();
UpdateGlobalStatsInternal();
}
}

View File

@@ -3,12 +3,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.modernwpf.com/2019">
<ListBox BorderThickness="0,0,1,0" BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}">
<ListBoxItem Content="Tümü" Tag="All" IsSelected="True"/>
<ListBoxItem Content="Yazılım" Tag="Software"/>
<ListBoxItem Content="Belge" Tag="Document"/>
<ListBoxItem Content="Ses" Tag="Audio"/>
<ListBoxItem Content="Video" Tag="Video"/>
<ListBoxItem Content="Görsel" Tag="Image"/>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Margin="4,2"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>

View File

@@ -5,8 +5,20 @@
<StatusBar>
<StatusBarItem>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Aktif: " FontWeight="SemiBold"/>
<TextBlock Text="{Binding ActiveCount}"/>
<TextBlock Text="Listedeki Toplam: " FontWeight="SemiBold"/>
<TextBlock Text="{Binding TotalCount}" Margin="0,0,10,0"/>
<TextBlock Text="Tamamlanan: " Foreground="Green" FontWeight="SemiBold"/>
<TextBlock Text="{Binding CompletedCount}" Margin="0,0,10,0"/>
<TextBlock Text="Aktif: " Foreground="Blue" FontWeight="SemiBold"/>
<TextBlock Text="{Binding ActiveCount}" Margin="0,0,10,0"/>
<TextBlock Text="Duraklatıldı: " Foreground="Orange" FontWeight="SemiBold"/>
<TextBlock Text="{Binding PausedCount}" Margin="0,0,10,0"/>
<TextBlock Text="Hata: " Foreground="Red" FontWeight="SemiBold"/>
<TextBlock Text="{Binding ErrorCount}"/>
</StackPanel>
</StatusBarItem>
<Separator/>

View File

@@ -0,0 +1,47 @@
<ui:ContentDialog
x:Class="DownloadManager.WPF.Views.Dialogs.CategoryManagerDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.modernwpf.com/2019"
Title="Kategori Yöneticisi"
CloseButtonText="Kapat">
<Grid Width="450" Height="400">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="CategoryList" Grid.Row="0" Margin="0,0,0,10" SelectionChanged="CategoryList_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Kategori Adı:" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,10,10"/>
<TextBox x:Name="NameBox" Grid.Row="0" Grid.Column="1" Margin="0,0,0,10"/>
<TextBlock Text="Uzantılar (Virgülle ayırın):" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,10,10"/>
<TextBox x:Name="ExtensionsBox" Grid.Row="1" Grid.Column="1" Margin="0,0,0,10"/>
<ui:SimpleStackPanel Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
<Button Content="Yeni Ekle" Click="AddButton_Click"/>
<Button x:Name="UpdateBtn" Content="Güncelle" Click="UpdateButton_Click" IsEnabled="False"/>
<Button x:Name="DeleteBtn" Content="Sil" Click="DeleteButton_Click" IsEnabled="False"/>
</ui:SimpleStackPanel>
</Grid>
</Grid>
</ui:ContentDialog>

View File

@@ -0,0 +1,86 @@
using ModernWpf.Controls;
using DownloadManager.Core.Services;
using DownloadManager.Core.Models;
using System.Windows;
using System.Linq;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
namespace DownloadManager.WPF.Views.Dialogs;
public partial class CategoryManagerDialog : ContentDialog
{
private readonly ICategoryService _categoryService;
private ObservableCollection<DownloadCategory> _categories = new();
public CategoryManagerDialog(ICategoryService categoryService)
{
InitializeComponent();
_categoryService = categoryService;
CategoryList.ItemsSource = _categories;
_ = LoadCategoriesAsync();
}
private async Task LoadCategoriesAsync()
{
_categories.Clear();
var items = await _categoryService.GetCategoriesAsync();
foreach (var item in items)
{
_categories.Add(item);
}
}
private void CategoryList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (CategoryList.SelectedItem is DownloadCategory selected)
{
NameBox.Text = selected.Name;
ExtensionsBox.Text = selected.Extensions;
UpdateBtn.IsEnabled = true;
DeleteBtn.IsEnabled = true;
}
else
{
NameBox.Text = "";
ExtensionsBox.Text = "";
UpdateBtn.IsEnabled = false;
DeleteBtn.IsEnabled = false;
}
}
private async void AddButton_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(NameBox.Text)) return;
var cat = new DownloadCategory
{
Name = NameBox.Text,
Extensions = ExtensionsBox.Text,
SavePath = "" // Varsayılanı boş bırakıyoruz
};
await _categoryService.AddCategoryAsync(cat);
await LoadCategoriesAsync();
CategoryList.SelectedItem = _categories.FirstOrDefault(c => c.Name == cat.Name);
}
private async void UpdateButton_Click(object sender, RoutedEventArgs e)
{
if (CategoryList.SelectedItem is DownloadCategory selected && !string.IsNullOrWhiteSpace(NameBox.Text))
{
selected.Name = NameBox.Text;
selected.Extensions = ExtensionsBox.Text;
await _categoryService.UpdateCategoryAsync(selected);
await LoadCategoriesAsync();
CategoryList.SelectedItem = _categories.FirstOrDefault(c => c.Id == selected.Id);
}
}
private async void DeleteButton_Click(object sender, RoutedEventArgs e)
{
if (CategoryList.SelectedItem is DownloadCategory selected)
{
await _categoryService.DeleteCategoryAsync(selected.Id);
await LoadCategoriesAsync();
}
}
}

View File

@@ -34,8 +34,8 @@
</ui:SimpleStackPanel>
</GroupBox>
<!-- Görünüm -->
<GroupBox Header="Görünüm">
<!-- Görünüm ve Kategoriler -->
<GroupBox Header="Görünüm ve Kategoriler">
<ui:SimpleStackPanel Spacing="10" Margin="5">
<CheckBox x:Name="ShowCategoryPanelCheck" Content="Kategori Panelini Göster" IsChecked="True" Margin="0,0,0,10"/>
@@ -53,6 +53,8 @@
</Grid>
<Button Content="Sütun Genişliklerini Sıfırla" HorizontalAlignment="Left" Margin="0,10,0,0" Click="ResetColumns_Click"/>
<Button Content="Kategorileri ve Dosya Türlerini Yönet" HorizontalAlignment="Left" Margin="0,10,0,0" Click="ManageCategories_Click"/>
</ui:SimpleStackPanel>
</GroupBox>

View File

@@ -4,17 +4,20 @@ using System.IO;
using System;
using System.Windows.Controls;
using System.Linq;
using DownloadManager.WPF.Helpers;
namespace DownloadManager.WPF.Views.Dialogs;
public partial class SettingsDialog : ContentDialog
{
private readonly ISettingsService _settingsService;
private readonly ICategoryService _categoryService;
public SettingsDialog(ISettingsService settingsService)
public SettingsDialog(ISettingsService settingsService, ICategoryService categoryService)
{
InitializeComponent();
_settingsService = settingsService;
_categoryService = categoryService;
LoadSettings();
}
@@ -46,10 +49,10 @@ public partial class SettingsDialog : ContentDialog
if (ThemeCombo.SelectedItem is ComboBoxItem selectedTheme)
{
await _settingsService.SetAsync("AppTheme", selectedTheme.Tag.ToString()!);
await _settingsService.SetAsync("AppTheme", selectedTheme.Tag?.ToString() ?? "System");
}
await _settingsService.SetAsync("IsCategoryPanelVisible", ShowCategoryPanelCheck.IsChecked.ToString());
await _settingsService.SetAsync("IsCategoryPanelVisible", (ShowCategoryPanelCheck.IsChecked ?? true).ToString());
}
private void BrowseButton_Click(object sender, System.Windows.RoutedEventArgs e)
@@ -73,4 +76,12 @@ public partial class SettingsDialog : ContentDialog
mw.ResetColumnWidths();
}
}
private async void ManageCategories_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.Hide();
await Task.Delay(200); // Semaphorenun serbest kalması ve UI'ın güncellenmesi için kısa bir bekleme
var dialog = new CategoryManagerDialog(_categoryService);
await dialog.ShowAsyncSafe();
}
}

View File

@@ -62,6 +62,9 @@
<MenuItem Header="İndirmeyi Durdur / Duraklat" Command="{Binding PauseDownloadCommand}" CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
<MenuItem Header="İndirmeye Devam Et" Command="{Binding ResumeDownloadCommand}" CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
<Separator/>
<MenuItem Header="Bekleyenleri Başlat" Command="{Binding ResumePendingCommand}"/>
<MenuItem Header="Tümünü Duraklat" Command="{Binding PauseAllCommand}"/>
<Separator/>
<MenuItem Header="Kaldır" Command="{Binding RemoveCommand}" CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
<MenuItem Header="Dosyanın özelliklerini görüntüle" Command="{Binding ViewPropertiesCommand}" CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
@@ -110,19 +113,50 @@
<Image Source="pack://application:,,,/Resources/Down_nb-02.png" HorizontalAlignment="Left" Margin="20,5" Width="40" Height="40"/>
<TextBlock Text="hOLOlu Download Manager" VerticalAlignment="Center" HorizontalAlignment="Left"
Foreground="White" FontSize="20" FontWeight="Bold" Opacity="0.9" Margin="65,0,0,0" Height="30" Width="266">
<TextBlock.BitmapEffect>
<DropShadowBitmapEffect Color="Black" Direction="320" ShadowDepth="2" Opacity=".9" Softness=".2" />
</TextBlock.BitmapEffect>
</TextBlock>
<TextBlock.BitmapEffect>
<DropShadowBitmapEffect Color="Black" Direction="320" ShadowDepth="2" Opacity=".9" Softness=".2" />
</TextBlock.BitmapEffect>
</TextBlock>
<!-- Canlı Hız Grafiği -->
<Polyline Points="{Binding SpeedPoints}" Stroke="White" StrokeThickness="2"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,20,5"
Opacity="0.6" Stretch="None">
Opacity="0.4" Stretch="None">
<Polyline.Effect>
<DropShadowEffect BlurRadius="5" ShadowDepth="0" Color="White" Opacity="0.5"/>
</Polyline.Effect>
</Polyline>
<!-- Global İstatistikler -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,30,0">
<StackPanel VerticalAlignment="Center" Margin="0,0,20,0">
<TextBlock Text="Toplam Hız" Foreground="Yellow" FontSize="11" Opacity="0.8" HorizontalAlignment="Right" FontWeight="Bold">
<TextBlock.Effect>
<DropShadowEffect BlurRadius="1" ShadowDepth="1" Opacity="0.8"/>
</TextBlock.Effect>
</TextBlock>
<TextBlock Text="{Binding TotalSpeed}" Foreground="White" FontSize="22" FontWeight="Bold" HorizontalAlignment="Right">
<TextBlock.Effect>
<DropShadowEffect BlurRadius="4" ShadowDepth="1" Opacity="0.5"/>
</TextBlock.Effect>
</TextBlock>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="Aktif Görev" Foreground="GreenYellow" FontSize="11" Opacity="0.8" HorizontalAlignment="Right" FontWeight="Bold">
<TextBlock.Effect>
<DropShadowEffect BlurRadius="1" ShadowDepth="1" Opacity="0.8"/>
</TextBlock.Effect>
</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="{Binding ActiveCount}" Foreground="White" FontSize="18" FontWeight="SemiBold">
<TextBlock.Effect>
<DropShadowEffect BlurRadius="1" ShadowDepth="1" Opacity="0.5"/>
</TextBlock.Effect>
</TextBlock>
<TextBlock Text="{Binding ActiveSegmentsTotal, StringFormat=' ({0})'}" Foreground="White" FontSize="12" Opacity="0.8" VerticalAlignment="Bottom" Margin="5,0,0,2"/>
</StackPanel>
</StackPanel>
</StackPanel>
<Canvas>
<Line X1="0" Y1="10" X2="2000" Y2="10" Stroke="WhiteSmoke" StrokeThickness="0.5"
StrokeDashArray="1,15"/>
@@ -143,6 +177,10 @@
<!-- CommandBar -->
<ui:CommandBar Grid.Row="1" Grid.ColumnSpan="2">
<ui:AppBarButton Icon="Add" Label="Ekle" Command="{Binding AddDownloadCommand}"/>
<ui:AppBarSeparator/>
<ui:AppBarButton Icon="Play" Label="Bekleyenleri Başlat" Command="{Binding ResumePendingCommand}"/>
<ui:AppBarButton Icon="Pause" Label="Tümünü Duraklat" Command="{Binding PauseAllCommand}"/>
<ui:AppBarSeparator/>
<ui:AppBarButton Icon="Pause" Label="Duraklat" Command="{Binding PauseDownloadCommand}" CommandParameter="{Binding SelectedDownload}"/>
<ui:AppBarButton Icon="Play" Label="Devam Et" Command="{Binding ResumeDownloadCommand}" CommandParameter="{Binding SelectedDownload}"/>
<ui:AppBarSeparator/>

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("hDM")]
[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("hDM")]
[assembly: System.Reflection.AssemblyTitleAttribute("hDM")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
15b715facae8161371d168a1fc5a82bac9e7d3bc1e94fbaab03eaf9f99e1dfc4
32463e5e28a863779906dcfe49a1890e4ff2e70459ec498a9ce269130c426e03

View File

@@ -1 +1 @@
1581cd8303614e8729279b1784d87a0d2107ccbd3e116182e147b66ee67f7f74
1d57680cc43827eda09ec10bdfa781db844bf5e57f23efe30b5e47096a22f495

View File

@@ -266,3 +266,5 @@ D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.WPF\obj\Debug\net8.0-wi
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.WPF\obj\Debug\net8.0-windows10.0.19041.0\hDM.pdb
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.WPF\obj\Debug\net8.0-windows10.0.19041.0\DownloadManager.WPF.genruntimeconfig.cache
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.WPF\obj\Debug\net8.0-windows10.0.19041.0\ref\hDM.dll
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.WPF\obj\Debug\net8.0-windows10.0.19041.0\Views\Dialogs\CategoryManagerDialog.baml
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.WPF\obj\Debug\net8.0-windows10.0.19041.0\Views\Dialogs\CategoryManagerDialog.g.cs

Some files were not shown because too many files have changed in this diff Show More