Toplu indirme ve Eklenti Güncellemesi
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationIcon>hdm_bridge.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/DownloadManager.BrowserBridge/hdm_bridge.ico
Normal file
BIN
src/DownloadManager.BrowserBridge/hdm_bridge.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 217 KiB |
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
113565b263fcdda4a4665ee4bb805cc1b4b99e5585e061be924810699276bedd
|
||||
@@ -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 =
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
5fd99a422a78009cff8ca33e1127d7f18d84d9f6b01faca46a390be79a246ed9
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
55a289d435d94f5304dfb112e526c2c5fd4f8dced6fb87cc465459bf16bab373
|
||||
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.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.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
c41154c222d4c9aaf2ac34116ff46b068de5b4633ca984a833d7394a42fc99da
|
||||
f0f25ac1863349e31e231b7ddaa3d27d9e23bc3cc9323375791cbf31e36329ff
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"
|
||||
|
||||
@@ -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.
Binary file not shown.
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.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.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
ca6e1ab4507e84385d05f14541ed3151a50c58ff919b0dc872c1409753ebe984
|
||||
eb1425e8d7f5855caa9e9266e09a3f32bbe7a7b2ce13f06d61c757365565f828
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
38
src/DownloadManager.WPF/Helpers/DialogHelper.cs
Normal file
38
src/DownloadManager.WPF/Helpers/DialogHelper.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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("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")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
15b715facae8161371d168a1fc5a82bac9e7d3bc1e94fbaab03eaf9f99e1dfc4
|
||||
32463e5e28a863779906dcfe49a1890e4ff2e70459ec498a9ce269130c426e03
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
1581cd8303614e8729279b1784d87a0d2107ccbd3e116182e147b66ee67f7f74
|
||||
1d57680cc43827eda09ec10bdfa781db844bf5e57f23efe30b5e47096a22f495
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user