ilk commit
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
102
src/DownloadManager.BrowserBridge/Program.cs
Normal file
102
src/DownloadManager.BrowserBridge/Program.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace DownloadManager.BrowserBridge;
|
||||
|
||||
public class BridgeMessage
|
||||
{
|
||||
[JsonProperty("action")]
|
||||
public string Action { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("filename")]
|
||||
public string? FileName { get; set; }
|
||||
|
||||
[JsonProperty("referrer")]
|
||||
public string? Referrer { get; set; }
|
||||
}
|
||||
|
||||
class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var stdin = Console.OpenStandardInput();
|
||||
var stdout = Console.OpenStandardOutput();
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Chrome protokolü: 4 byte (little-endian uint32) = mesaj uzunluğu
|
||||
var lenBuf = new byte[4];
|
||||
int read = await stdin.ReadAsync(lenBuf, 0, 4);
|
||||
if (read < 4) break;
|
||||
|
||||
var length = BitConverter.ToUInt32(lenBuf, 0);
|
||||
var msgBuf = new byte[length];
|
||||
|
||||
int msgRead = 0;
|
||||
while (msgRead < length)
|
||||
{
|
||||
int r = await stdin.ReadAsync(msgBuf, msgRead, (int)length - msgRead);
|
||||
if (r <= 0) break;
|
||||
msgRead += r;
|
||||
}
|
||||
|
||||
var json = Encoding.UTF8.GetString(msgBuf);
|
||||
var msg = JsonConvert.DeserializeObject<BridgeMessage>(json);
|
||||
|
||||
if (msg != null)
|
||||
{
|
||||
await SendToMainAppAsync(msg);
|
||||
// Mesaj gönderildikten sonra çık (Native messaging her mesaj için yeni process açabiliyor)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async Task SendToMainAppAsync(BridgeMessage msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Ana uygulamanın çalışıp çalışmadığını kontrol et
|
||||
var processes = System.Diagnostics.Process.GetProcessesByName("DownloadManager.WPF");
|
||||
if (processes.Length == 0)
|
||||
{
|
||||
// Uygulama çalışmıyorsa başlat (Yolu bulmaya çalışalım)
|
||||
string appPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DownloadManager.WPF.exe");
|
||||
|
||||
// Geliştirme ortamı için alternatif yollar
|
||||
if (!File.Exists(appPath))
|
||||
appPath = @"D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.WPF\bin\Release\net8.0-windows\DownloadManager.WPF.exe";
|
||||
|
||||
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
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
var json = JsonConvert.SerializeObject(msg);
|
||||
var data = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
await pipe.WriteAsync(data);
|
||||
await pipe.FlushAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
File.AppendAllText("bridge_error.log", $"{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,13 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
"configProperties": {
|
||||
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
2/05/2026 08:52:46: The operation has timed out.
|
||||
2/05/2026 08:57:51: The operation has timed out.
|
||||
2/05/2026 08:59:05: The operation has timed out.
|
||||
2/05/2026 08:59:27: The operation has timed out.
|
||||
2/05/2026 08:59:40: The operation has timed out.
|
||||
2/05/2026 09:00:01: The operation has timed out.
|
||||
2/05/2026 09:03:51: The operation has timed out.
|
||||
2/05/2026 09:12:38: Bağlantı hatası (https://ftp.linux.org.tr/ubuntu-releases/24.04/ubuntu-24.04-desktop-amd64.iso): The operation has timed out.
|
||||
9
src/DownloadManager.BrowserBridge/manifest - Yedek.json
Normal file
9
src/DownloadManager.BrowserBridge/manifest - Yedek.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "com.downloadmanager.bridge",
|
||||
"description": "Download Manager Native Messaging Bridge",
|
||||
"path": "DownloadManager.BrowserBridge.exe",
|
||||
"type": "stdio",
|
||||
"allowed_origins": [
|
||||
"chrome-extension://[EXTENSION_ID_BURAYA_GELECEK]/"
|
||||
]
|
||||
}
|
||||
9
src/DownloadManager.BrowserBridge/manifest.json
Normal file
9
src/DownloadManager.BrowserBridge/manifest.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "com.downloadmanager.bridge",
|
||||
"description": "hOLOlu Download Manager Native Messaging Bridge",
|
||||
"path": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\bin\\Release\\net8.0\\DownloadManager.BrowserBridge.exe",
|
||||
"type": "stdio",
|
||||
"allowed_origins": [
|
||||
"chrome-extension://gnohncemfbplcagkfdhedbkfaogcoobi/"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
@@ -0,0 +1,23 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <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.
|
||||
// </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")]
|
||||
[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.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
000d6a996873011518a2be58f132c59662b6ff352ca8842e6f1fa1c31b20e4f1
|
||||
@@ -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 =
|
||||
@@ -0,0 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using global::System;
|
||||
global using global::System.Collections.Generic;
|
||||
global using global::System.IO;
|
||||
global using global::System.Linq;
|
||||
global using global::System.Net.Http;
|
||||
global using global::System.Threading;
|
||||
global using global::System.Threading.Tasks;
|
||||
Binary file not shown.
Binary file not shown.
BIN
src/DownloadManager.BrowserBridge/obj/Debug/net8.0/apphost.exe
Normal file
BIN
src/DownloadManager.BrowserBridge/obj/Debug/net8.0/apphost.exe
Normal file
Binary file not shown.
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\DownloadManager.BrowserBridge.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\DownloadManager.BrowserBridge.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\DownloadManager.BrowserBridge.csproj",
|
||||
"projectName": "DownloadManager.BrowserBridge",
|
||||
"projectPath": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\DownloadManager.BrowserBridge.csproj",
|
||||
"packagesPath": "C:\\Users\\hOLOlu\\.nuget\\packages\\",
|
||||
"outputPath": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files\\DevExpress 22.2\\Components\\Offline Packages",
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages",
|
||||
"C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\hOLOlu\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\DevExpress 22.2.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"C:\\Program Files\\DevExpress 22.2\\Components\\System\\Components\\Packages": {},
|
||||
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "9.0.300"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": {
|
||||
"target": "Package",
|
||||
"version": "[13.*, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.313/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\hOLOlu\.nuget\packages\;C:\Program Files\DevExpress 22.2\Components\Offline Packages;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages;C:\Program Files\dotnet\sdk\NuGetFallbackFolder</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.3</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\hOLOlu\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Program Files\DevExpress 22.2\Components\Offline Packages\" />
|
||||
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||
<SourceRoot Include="C:\Program Files\dotnet\sdk\NuGetFallbackFolder\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
@@ -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("Release")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
||||
[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 @@
|
||||
89d0fd93dd3e67ad8186e0c515a8d65ebbbc94026391ff8e88db296a467ee7a3
|
||||
@@ -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 =
|
||||
@@ -0,0 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using global::System;
|
||||
global using global::System.Collections.Generic;
|
||||
global using global::System.IO;
|
||||
global using global::System.Linq;
|
||||
global using global::System.Net.Http;
|
||||
global using global::System.Threading;
|
||||
global using global::System.Threading.Tasks;
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
9a48f8efc256d5e9f4c6bcb351f545b3f8de09f2663f420d4e646e2dde80a99c
|
||||
@@ -0,0 +1,17 @@
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Release\net8.0\DownloadManager.BrowserBridge.exe
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Release\net8.0\DownloadManager.BrowserBridge.deps.json
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Release\net8.0\DownloadManager.BrowserBridge.runtimeconfig.json
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Release\net8.0\DownloadManager.BrowserBridge.dll
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Release\net8.0\DownloadManager.BrowserBridge.pdb
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Release\net8.0\Newtonsoft.Json.dll
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\DownloadManager.BrowserBridge.csproj.AssemblyReference.cache
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\DownloadManager.BrowserBridge.GeneratedMSBuildEditorConfig.editorconfig
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\DownloadManager.BrowserBridge.AssemblyInfoInputs.cache
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\DownloadManager.BrowserBridge.AssemblyInfo.cs
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\DownloadManager.BrowserBridge.csproj.CoreCompileInputs.cache
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\Download.A73557B5.Up2Date
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\DownloadManager.BrowserBridge.dll
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\refint\DownloadManager.BrowserBridge.dll
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\DownloadManager.BrowserBridge.pdb
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\DownloadManager.BrowserBridge.genruntimeconfig.cache
|
||||
D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\obj\Release\net8.0\ref\DownloadManager.BrowserBridge.dll
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
f69caf1b77b5ac1742efde949e911c38081902650a9646ddb4fb0df3c6723dd0
|
||||
Binary file not shown.
BIN
src/DownloadManager.BrowserBridge/obj/Release/net8.0/apphost.exe
Normal file
BIN
src/DownloadManager.BrowserBridge/obj/Release/net8.0/apphost.exe
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
139
src/DownloadManager.BrowserBridge/obj/project.assets.json
Normal file
139
src/DownloadManager.BrowserBridge/obj/project.assets.json
Normal file
@@ -0,0 +1,139 @@
|
||||
{
|
||||
"version": 3,
|
||||
"targets": {
|
||||
"net8.0": {
|
||||
"Newtonsoft.Json/13.0.4": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"lib/net6.0/Newtonsoft.Json.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/Newtonsoft.Json.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"Newtonsoft.Json/13.0.4": {
|
||||
"sha512": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A==",
|
||||
"type": "package",
|
||||
"path": "newtonsoft.json/13.0.4",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"LICENSE.md",
|
||||
"README.md",
|
||||
"lib/net20/Newtonsoft.Json.dll",
|
||||
"lib/net20/Newtonsoft.Json.xml",
|
||||
"lib/net35/Newtonsoft.Json.dll",
|
||||
"lib/net35/Newtonsoft.Json.xml",
|
||||
"lib/net40/Newtonsoft.Json.dll",
|
||||
"lib/net40/Newtonsoft.Json.xml",
|
||||
"lib/net45/Newtonsoft.Json.dll",
|
||||
"lib/net45/Newtonsoft.Json.xml",
|
||||
"lib/net6.0/Newtonsoft.Json.dll",
|
||||
"lib/net6.0/Newtonsoft.Json.xml",
|
||||
"lib/netstandard1.0/Newtonsoft.Json.dll",
|
||||
"lib/netstandard1.0/Newtonsoft.Json.xml",
|
||||
"lib/netstandard1.3/Newtonsoft.Json.dll",
|
||||
"lib/netstandard1.3/Newtonsoft.Json.xml",
|
||||
"lib/netstandard2.0/Newtonsoft.Json.dll",
|
||||
"lib/netstandard2.0/Newtonsoft.Json.xml",
|
||||
"newtonsoft.json.13.0.4.nupkg.sha512",
|
||||
"newtonsoft.json.nuspec",
|
||||
"packageIcon.png"
|
||||
]
|
||||
}
|
||||
},
|
||||
"projectFileDependencyGroups": {
|
||||
"net8.0": [
|
||||
"Newtonsoft.Json >= 13.*"
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
"C:\\Users\\hOLOlu\\.nuget\\packages\\": {},
|
||||
"C:\\Program Files\\DevExpress 22.2\\Components\\Offline Packages": {},
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {},
|
||||
"C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\DownloadManager.BrowserBridge.csproj",
|
||||
"projectName": "DownloadManager.BrowserBridge",
|
||||
"projectPath": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\DownloadManager.BrowserBridge.csproj",
|
||||
"packagesPath": "C:\\Users\\hOLOlu\\.nuget\\packages\\",
|
||||
"outputPath": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files\\DevExpress 22.2\\Components\\Offline Packages",
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages",
|
||||
"C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\hOLOlu\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\DevExpress 22.2.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"C:\\Program Files\\DevExpress 22.2\\Components\\System\\Components\\Packages": {},
|
||||
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "9.0.300"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": {
|
||||
"target": "Package",
|
||||
"version": "[13.*, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.313/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/DownloadManager.BrowserBridge/obj/project.nuget.cache
Normal file
10
src/DownloadManager.BrowserBridge/obj/project.nuget.cache
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "B+3YjV9jaKo=",
|
||||
"success": true,
|
||||
"projectFilePath": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\DownloadManager.BrowserBridge.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\hOLOlu\\.nuget\\packages\\newtonsoft.json\\13.0.4\\newtonsoft.json.13.0.4.nupkg.sha512"
|
||||
],
|
||||
"logs": []
|
||||
}
|
||||
7
src/DownloadManager.BrowserBridge/register_bridge.reg
Normal file
7
src/DownloadManager.BrowserBridge/register_bridge.reg
Normal file
@@ -0,0 +1,7 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.downloadmanager.bridge]
|
||||
@="D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\manifest.json"
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Microsoft\Edge\NativeMessagingHosts\com.downloadmanager.bridge]
|
||||
@="D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.BrowserBridge\\manifest.json"
|
||||
6
src/DownloadManager.Core/Class1.cs
Normal file
6
src/DownloadManager.Core/Class1.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace DownloadManager.Core;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
31
src/DownloadManager.Core/Data/AppDbContext.cs
Normal file
31
src/DownloadManager.Core/Data/AppDbContext.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DownloadManager.Core.Data;
|
||||
|
||||
public class AppDbContext : DbContext
|
||||
{
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<DownloadItem> Downloads { get; set; } = null!;
|
||||
public DbSet<DownloadSegment> Segments { get; set; } = null!;
|
||||
public DbSet<DownloadCategory> Categories { get; set; } = null!;
|
||||
public DbSet<GrabberProject> GrabberProjects { get; set; } = null!;
|
||||
public DbSet<ScheduleJob> ScheduleJobs { get; set; } = null!;
|
||||
public DbSet<AppSetting> Settings { get; set; } = null!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder b)
|
||||
{
|
||||
// Seed — varsayılan kategoriler
|
||||
b.Entity<DownloadCategory>().HasData(
|
||||
new DownloadCategory { Id = 1, Name = "Yazılım", SavePath = @"%USERPROFILE%\Downloads\Yazılım", Extensions = "exe,msi,dmg" },
|
||||
new DownloadCategory { Id = 2, Name = "Belge", SavePath = @"%USERPROFILE%\Downloads\Belgeler", Extensions = "pdf,docx,xlsx,pptx" },
|
||||
new DownloadCategory { Id = 3, Name = "Ses", SavePath = @"%USERPROFILE%\Downloads\Ses", Extensions = "mp3,flac,wav,aac" },
|
||||
new DownloadCategory { Id = 4, Name = "Video", SavePath = @"%USERPROFILE%\Downloads\Video", Extensions = "mp4,mkv,avi,mov" },
|
||||
new DownloadCategory { Id = 5, Name = "Görsel", SavePath = @"%USERPROFILE%\Downloads\Görseller",Extensions = "jpg,jpeg,png,gif,webp" },
|
||||
new DownloadCategory { Id = 6, Name = "Diğer", SavePath = @"%USERPROFILE%\Downloads", Extensions = "" }
|
||||
);
|
||||
}
|
||||
}
|
||||
15
src/DownloadManager.Core/Data/AppDbContextFactory.cs
Normal file
15
src/DownloadManager.Core/Data/AppDbContextFactory.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace DownloadManager.Core.Data;
|
||||
|
||||
public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
|
||||
{
|
||||
public AppDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
|
||||
optionsBuilder.UseSqlite("Data Source=downloadmanager.db");
|
||||
|
||||
return new AppDbContext(optionsBuilder.Options);
|
||||
}
|
||||
}
|
||||
264
src/DownloadManager.Core/Data/Migrations/20260501194147_InitialCreate.Designer.cs
generated
Normal file
264
src/DownloadManager.Core/Data/Migrations/20260501194147_InitialCreate.Designer.cs
generated
Normal file
@@ -0,0 +1,264 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DownloadManager.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DownloadManager.Core.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20260501194147_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.26");
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.AppSetting", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Extensions")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SavePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Extensions = "exe,msi,dmg",
|
||||
Name = "Yazılım",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Yazılım"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 2,
|
||||
Extensions = "pdf,docx,xlsx,pptx",
|
||||
Name = "Belge",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Belgeler"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 3,
|
||||
Extensions = "mp3,flac,wav,aac",
|
||||
Name = "Ses",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Ses"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 4,
|
||||
Extensions = "mp4,mkv,avi,mov",
|
||||
Name = "Video",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Video"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 5,
|
||||
Extensions = "jpg,jpeg,png,gif,webp",
|
||||
Name = "Görsel",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Görseller"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 6,
|
||||
Extensions = "",
|
||||
Name = "Diğer",
|
||||
SavePath = "%USERPROFILE%\\Downloads"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("CompletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("DownloadedBytes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ETag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Referrer")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SavePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SegmentCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Sha256Checksum")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("StartedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("TotalSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserAgent")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Downloads");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadSegment", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("DownloadItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("Downloaded")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("EndByte")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("StartByte")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TempFilePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DownloadItemId");
|
||||
|
||||
b.ToTable("Segments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.GrabberProject", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("GrabberProjects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.ScheduleJob", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ScheduleJobs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadItem", b =>
|
||||
{
|
||||
b.HasOne("DownloadManager.Core.Models.DownloadCategory", "Category")
|
||||
.WithMany()
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadSegment", b =>
|
||||
{
|
||||
b.HasOne("DownloadManager.Core.Models.DownloadItem", null)
|
||||
.WithMany("Segments")
|
||||
.HasForeignKey("DownloadItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadItem", b =>
|
||||
{
|
||||
b.Navigation("Segments");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace DownloadManager.Core.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Categories",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
SavePath = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Extensions = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Categories", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GrabberProjects",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GrabberProjects", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ScheduleJobs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ScheduleJobs", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Settings",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Value = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Settings", x => x.Key);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Downloads",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
Url = table.Column<string>(type: "TEXT", nullable: false),
|
||||
FileName = table.Column<string>(type: "TEXT", nullable: false),
|
||||
SavePath = table.Column<string>(type: "TEXT", nullable: false),
|
||||
CategoryId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Status = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
TotalSize = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
DownloadedBytes = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
SegmentCount = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Priority = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Referrer = table.Column<string>(type: "TEXT", nullable: true),
|
||||
UserAgent = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Username = table.Column<string>(type: "TEXT", nullable: true),
|
||||
PasswordHash = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ETag = table.Column<string>(type: "TEXT", nullable: true),
|
||||
LastModified = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ErrorMessage = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Sha256Checksum = table.Column<string>(type: "TEXT", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
StartedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
CompletedAt = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Downloads", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Downloads_Categories_CategoryId",
|
||||
column: x => x.CategoryId,
|
||||
principalTable: "Categories",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Segments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
DownloadItemId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
Index = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
StartByte = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
EndByte = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
Downloaded = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
Status = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
TempFilePath = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Segments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Segments_Downloads_DownloadItemId",
|
||||
column: x => x.DownloadItemId,
|
||||
principalTable: "Downloads",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Categories",
|
||||
columns: new[] { "Id", "Extensions", "Name", "SavePath" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ 1, "exe,msi,dmg", "Yazılım", "%USERPROFILE%\\Downloads\\Yazılım" },
|
||||
{ 2, "pdf,docx,xlsx,pptx", "Belge", "%USERPROFILE%\\Downloads\\Belgeler" },
|
||||
{ 3, "mp3,flac,wav,aac", "Ses", "%USERPROFILE%\\Downloads\\Ses" },
|
||||
{ 4, "mp4,mkv,avi,mov", "Video", "%USERPROFILE%\\Downloads\\Video" },
|
||||
{ 5, "jpg,jpeg,png,gif,webp", "Görsel", "%USERPROFILE%\\Downloads\\Görseller" },
|
||||
{ 6, "", "Diğer", "%USERPROFILE%\\Downloads" }
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Downloads_CategoryId",
|
||||
table: "Downloads",
|
||||
column: "CategoryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Segments_DownloadItemId",
|
||||
table: "Segments",
|
||||
column: "DownloadItemId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "GrabberProjects");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ScheduleJobs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Segments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Settings");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Downloads");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Categories");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DownloadManager.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DownloadManager.Core.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
partial class AppDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.26");
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.AppSetting", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Extensions")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SavePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Extensions = "exe,msi,dmg",
|
||||
Name = "Yazılım",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Yazılım"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 2,
|
||||
Extensions = "pdf,docx,xlsx,pptx",
|
||||
Name = "Belge",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Belgeler"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 3,
|
||||
Extensions = "mp3,flac,wav,aac",
|
||||
Name = "Ses",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Ses"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 4,
|
||||
Extensions = "mp4,mkv,avi,mov",
|
||||
Name = "Video",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Video"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 5,
|
||||
Extensions = "jpg,jpeg,png,gif,webp",
|
||||
Name = "Görsel",
|
||||
SavePath = "%USERPROFILE%\\Downloads\\Görseller"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 6,
|
||||
Extensions = "",
|
||||
Name = "Diğer",
|
||||
SavePath = "%USERPROFILE%\\Downloads"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("CompletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("DownloadedBytes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ETag")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Referrer")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SavePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SegmentCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Sha256Checksum")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("StartedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("TotalSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserAgent")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Downloads");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadSegment", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("DownloadItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("Downloaded")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("EndByte")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Index")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("StartByte")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TempFilePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DownloadItemId");
|
||||
|
||||
b.ToTable("Segments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.GrabberProject", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("GrabberProjects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.ScheduleJob", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ScheduleJobs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadItem", b =>
|
||||
{
|
||||
b.HasOne("DownloadManager.Core.Models.DownloadCategory", "Category")
|
||||
.WithMany()
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadSegment", b =>
|
||||
{
|
||||
b.HasOne("DownloadManager.Core.Models.DownloadItem", null)
|
||||
.WithMany("Segments")
|
||||
.HasForeignKey("DownloadItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DownloadManager.Core.Models.DownloadItem", b =>
|
||||
{
|
||||
b.Navigation("Segments");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Data.Repositories;
|
||||
|
||||
public class CategoryRepository : ICategoryRepository
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
public CategoryRepository(AppDbContext db) => _db = db;
|
||||
|
||||
public async Task<IEnumerable<DownloadCategory>> GetAllAsync()
|
||||
=> await _db.Categories.ToListAsync();
|
||||
|
||||
public async Task<DownloadCategory?> GetByIdAsync(int id)
|
||||
=> await _db.Categories.FindAsync(id);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Data.Repositories;
|
||||
|
||||
public class DownloadRepository : IDownloadRepository
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
public DownloadRepository(AppDbContext db) => _db = db;
|
||||
|
||||
public async Task<DownloadItem?> GetByIdAsync(Guid id)
|
||||
=> await _db.Downloads.Include(d => d.Segments).FirstOrDefaultAsync(d => d.Id == id);
|
||||
|
||||
public async Task<IEnumerable<DownloadItem>> GetAllAsync()
|
||||
=> await _db.Downloads.Include(d => d.Category).ToListAsync();
|
||||
|
||||
public async Task AddAsync(DownloadItem item)
|
||||
{
|
||||
await _db.Downloads.AddAsync(item);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(DownloadItem item)
|
||||
{
|
||||
_db.Downloads.Update(item);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Guid id)
|
||||
{
|
||||
var item = await _db.Downloads.FindAsync(id);
|
||||
if (item != null)
|
||||
{
|
||||
_db.Downloads.Remove(item);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/DownloadManager.Core/Data/Repositories/IRepositories.cs
Normal file
27
src/DownloadManager.Core/Data/Repositories/IRepositories.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Data.Repositories;
|
||||
|
||||
public interface IDownloadRepository
|
||||
{
|
||||
Task<DownloadItem?> GetByIdAsync(Guid id);
|
||||
Task<IEnumerable<DownloadItem>> GetAllAsync();
|
||||
Task AddAsync(DownloadItem item);
|
||||
Task UpdateAsync(DownloadItem item);
|
||||
Task DeleteAsync(Guid id);
|
||||
}
|
||||
|
||||
public interface ICategoryRepository
|
||||
{
|
||||
Task<IEnumerable<DownloadCategory>> GetAllAsync();
|
||||
Task<DownloadCategory?> GetByIdAsync(int id);
|
||||
}
|
||||
|
||||
public interface ISettingsRepository
|
||||
{
|
||||
Task<string?> GetValueAsync(string key);
|
||||
Task SetValueAsync(string key, string value);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Data.Repositories;
|
||||
|
||||
public interface IScheduleRepository
|
||||
{
|
||||
Task<IEnumerable<ScheduleJob>> GetAllAsync();
|
||||
Task AddAsync(ScheduleJob job);
|
||||
Task UpdateAsync(ScheduleJob job);
|
||||
Task DeleteAsync(int id);
|
||||
}
|
||||
|
||||
public class ScheduleRepository : IScheduleRepository
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
public ScheduleRepository(AppDbContext db) => _db = db;
|
||||
|
||||
public async Task<IEnumerable<ScheduleJob>> GetAllAsync() => await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync(_db.ScheduleJobs);
|
||||
|
||||
public async Task AddAsync(ScheduleJob job)
|
||||
{
|
||||
await _db.ScheduleJobs.AddAsync(job);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(ScheduleJob job)
|
||||
{
|
||||
_db.ScheduleJobs.Update(job);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var job = await _db.ScheduleJobs.FindAsync(id);
|
||||
if (job != null)
|
||||
{
|
||||
_db.ScheduleJobs.Remove(job);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Data.Repositories;
|
||||
|
||||
public class SettingsRepository : ISettingsRepository
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
public SettingsRepository(AppDbContext db) => _db = db;
|
||||
|
||||
public async Task<string?> GetValueAsync(string key)
|
||||
{
|
||||
var setting = await _db.Settings.FindAsync(key);
|
||||
return setting?.Value;
|
||||
}
|
||||
|
||||
public async Task SetValueAsync(string key, string value)
|
||||
{
|
||||
var setting = await _db.Settings.FindAsync(key);
|
||||
if (setting == null)
|
||||
{
|
||||
await _db.Settings.AddAsync(new AppSetting { Key = key, Value = value });
|
||||
}
|
||||
else
|
||||
{
|
||||
setting.Value = value;
|
||||
}
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
26
src/DownloadManager.Core/DownloadManager.Core.csproj
Normal file
26
src/DownloadManager.Core/DownloadManager.Core.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.*">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.*" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.*" />
|
||||
<PackageReference Include="Serilog" Version="3.*" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.*" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.*" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
182
src/DownloadManager.Core/Engine/DownloadEngine.cs
Normal file
182
src/DownloadManager.Core/Engine/DownloadEngine.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using DownloadManager.Core.Enums;
|
||||
using DownloadManager.Core.Events;
|
||||
using DownloadManager.Core.Models;
|
||||
using DownloadManager.Core.Protocols;
|
||||
using DownloadManager.Core.Queue;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Engine;
|
||||
|
||||
public class DownloadEngine
|
||||
{
|
||||
private readonly DownloadQueue _queue;
|
||||
private readonly IDownloadProtocol _protocol;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ConcurrentDictionary<Guid, CancellationTokenSource> _activeDownloads = new();
|
||||
|
||||
public event EventHandler<DownloadProgressEvent>? ProgressChanged;
|
||||
|
||||
public DownloadEngine(DownloadQueue queue, IDownloadProtocol protocol, IServiceProvider serviceProvider)
|
||||
{
|
||||
_queue = queue;
|
||||
_protocol = protocol;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task StartQueueAsync(CancellationToken ct)
|
||||
{
|
||||
Serilog.Log.Information("DownloadEngine loop started.");
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await _queue.DequeueAsync(ct);
|
||||
if (item == null) continue;
|
||||
|
||||
Serilog.Log.Information("Item dequeued for processing: {Url} (ID: {Id})", item.Url, item.Id);
|
||||
_ = Task.Run(() => ProcessDownloadAsync(item, ct), ct);
|
||||
}
|
||||
catch (OperationCanceledException) { break; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Error in DownloadEngine queue loop");
|
||||
await Task.Delay(1000, ct);
|
||||
}
|
||||
}
|
||||
Serilog.Log.Information("DownloadEngine loop stopped.");
|
||||
}
|
||||
|
||||
private async Task ProcessDownloadAsync(DownloadItem item, CancellationToken ct)
|
||||
{
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
_activeDownloads[item.Id] = cts;
|
||||
|
||||
var progress = new Progress<DownloadProgressEvent>(e => ProgressChanged?.Invoke(this, e));
|
||||
|
||||
try
|
||||
{
|
||||
Serilog.Log.Information("Processing download: {FileName} (ID: {Id})", item.FileName, item.Id);
|
||||
|
||||
// Eğer dosya boyutu bilinmiyorsa (ilk kez ekleniyor), bilgilerini çek
|
||||
if (item.TotalSize <= 0)
|
||||
{
|
||||
Serilog.Log.Information("Fetching file info for {Url}", item.Url);
|
||||
var info = await _protocol.GetFileInfoAsync(item.Url, cts.Token);
|
||||
item.TotalSize = info.TotalSize;
|
||||
item.ETag = info.ETag;
|
||||
item.LastModified = info.LastModified;
|
||||
if (string.IsNullOrEmpty(item.FileName)) item.FileName = info.FileName;
|
||||
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("Download {Id} is paused, skipping.", item.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
item.Status = DownloadStatus.Downloading;
|
||||
|
||||
// Notify UI that we are starting
|
||||
ProgressChanged?.Invoke(this, new DownloadProgressEvent
|
||||
{
|
||||
Id = item.Id,
|
||||
Status = DownloadStatus.Downloading,
|
||||
DownloadedBytes = item.DownloadedBytes,
|
||||
TotalBytes = item.TotalSize,
|
||||
ProgressPercent = item.TotalSize > 0 ? (double)Math.Min(item.DownloadedBytes, item.TotalSize) / item.TotalSize * 100 : 0
|
||||
});
|
||||
|
||||
IDownloader downloader;
|
||||
if (item.TotalSize > 0)
|
||||
{
|
||||
downloader = _serviceProvider.GetRequiredService<SegmentedDownloader>();
|
||||
Serilog.Log.Information("Using SegmentedDownloader for {Id}. Current progress: {Downloaded}/{Total}",
|
||||
item.Id, item.DownloadedBytes, item.TotalSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
downloader = _serviceProvider.GetRequiredService<SingleDownloader>();
|
||||
Serilog.Log.Information("Using SingleDownloader (unknown size) for {Id}. Current progress: {Downloaded}",
|
||||
item.Id, item.DownloadedBytes);
|
||||
}
|
||||
|
||||
await downloader.DownloadAsync(item, progress, cts.Token);
|
||||
|
||||
// Eğer boyut bilinmiyorduysa (dynamic), indirilen boyutu toplam boyut yap
|
||||
if (item.TotalSize <= 0) item.TotalSize = item.DownloadedBytes;
|
||||
|
||||
item.Status = DownloadStatus.Completed;
|
||||
item.CompletedAt = DateTime.UtcNow;
|
||||
|
||||
ProgressChanged?.Invoke(this, new DownloadProgressEvent
|
||||
{
|
||||
Id = item.Id,
|
||||
Status = DownloadStatus.Completed,
|
||||
ProgressPercent = 100
|
||||
});
|
||||
Serilog.Log.Information("Download completed: {Id}", item.Id);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Serilog.Log.Information("Download paused/cancelled: {Id}", item.Id);
|
||||
item.Status = DownloadStatus.Paused;
|
||||
ProgressChanged?.Invoke(this, CreateProgressEvent(item, DownloadStatus.Paused));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Error processing download: {Id}", item.Id);
|
||||
item.Status = DownloadStatus.Error;
|
||||
item.ErrorMessage = ex.Message;
|
||||
ProgressChanged?.Invoke(this, CreateProgressEvent(item, DownloadStatus.Error));
|
||||
}
|
||||
finally
|
||||
{
|
||||
await SaveItemStateAsync(item);
|
||||
_activeDownloads.TryRemove(item.Id, out _);
|
||||
_queue.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private DownloadProgressEvent CreateProgressEvent(DownloadItem item, DownloadStatus status)
|
||||
{
|
||||
var downloaded = Math.Min(item.DownloadedBytes, item.TotalSize);
|
||||
return new DownloadProgressEvent
|
||||
{
|
||||
Id = item.Id,
|
||||
Status = status,
|
||||
DownloadedBytes = downloaded,
|
||||
TotalBytes = item.TotalSize,
|
||||
ProgressPercent = item.TotalSize > 0 ? (double)downloaded / item.TotalSize * 100 : 0
|
||||
};
|
||||
}
|
||||
|
||||
private async Task SaveItemStateAsync(DownloadItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var repo = scope.ServiceProvider.GetRequiredService<Data.Repositories.IDownloadRepository>();
|
||||
await repo.UpdateAsync(item);
|
||||
Serilog.Log.Information("Saved download state to DB for {Id}: {Status}, {Downloaded} bytes",
|
||||
item.Id, item.Status, item.DownloadedBytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Failed to save download state to DB for {Id}", item.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelDownload(Guid id)
|
||||
{
|
||||
if (_activeDownloads.TryRemove(id, out var cts))
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/DownloadManager.Core/Engine/EngineOptions.cs
Normal file
12
src/DownloadManager.Core/Engine/EngineOptions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace DownloadManager.Core.Engine;
|
||||
|
||||
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 RetryDelayMs { get; set; } = 2000;
|
||||
public int ConnectionTimeoutSeconds { get; set; } = 30;
|
||||
public long SpeedLimitBytesPerSec { get; set; } = 0; // 0 = limitsiz
|
||||
public string UserAgent { get; set; } = "DownloadManager/1.0 (.NET 8)";
|
||||
}
|
||||
12
src/DownloadManager.Core/Engine/IDownloader.cs
Normal file
12
src/DownloadManager.Core/Engine/IDownloader.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using DownloadManager.Core.Events;
|
||||
using DownloadManager.Core.Models;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Engine;
|
||||
|
||||
public interface IDownloader
|
||||
{
|
||||
Task DownloadAsync(DownloadItem item, IProgress<DownloadProgressEvent>? progress, CancellationToken ct);
|
||||
}
|
||||
84
src/DownloadManager.Core/Engine/ResumeManager.cs
Normal file
84
src/DownloadManager.Core/Engine/ResumeManager.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Engine;
|
||||
|
||||
public class DownloadItemState
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Url { get; set; } = string.Empty;
|
||||
public string SavePath { get; set; } = string.Empty;
|
||||
public long TotalSize { get; set; }
|
||||
public string? ETag { get; set; }
|
||||
public string? LastModified { get; set; }
|
||||
public List<DownloadSegment> Segments { get; set; } = new();
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime SavedAt { get; set; }
|
||||
}
|
||||
|
||||
public class ResumeManager
|
||||
{
|
||||
private readonly string _basePath;
|
||||
|
||||
public ResumeManager()
|
||||
{
|
||||
_basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "DownloadManager", "temp");
|
||||
if (!Directory.Exists(_basePath))
|
||||
Directory.CreateDirectory(_basePath);
|
||||
}
|
||||
|
||||
public async Task<DownloadItemState?> LoadAsync(Guid id)
|
||||
{
|
||||
var path = GetMetaPath(id);
|
||||
if (!File.Exists(path)) return null;
|
||||
|
||||
var json = await File.ReadAllTextAsync(path);
|
||||
return JsonConvert.DeserializeObject<DownloadItemState>(json);
|
||||
}
|
||||
|
||||
public async Task<DownloadItemState> LoadOrCreateAsync(DownloadItem item, EngineOptions options)
|
||||
{
|
||||
var state = await LoadAsync(item.Id);
|
||||
if (state != null)
|
||||
{
|
||||
// ETag veya LastModified değişmişse → içerik değişmiş, baştan başla.
|
||||
// Bu kontrolü burada yapabiliriz.
|
||||
return state;
|
||||
}
|
||||
|
||||
state = new DownloadItemState
|
||||
{
|
||||
Id = item.Id,
|
||||
Url = item.Url,
|
||||
SavePath = item.SavePath,
|
||||
TotalSize = item.TotalSize,
|
||||
ETag = item.ETag,
|
||||
LastModified = item.LastModified,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public async Task SaveAsync(DownloadItem item, DownloadItemState state)
|
||||
{
|
||||
var path = GetMetaPath(item.Id);
|
||||
state.SavedAt = DateTime.UtcNow;
|
||||
var json = JsonConvert.SerializeObject(state, Formatting.Indented);
|
||||
await File.WriteAllTextAsync(path, json);
|
||||
}
|
||||
|
||||
public Task DeleteAsync(Guid id)
|
||||
{
|
||||
var path = GetMetaPath(id);
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private string GetMetaPath(Guid id) => Path.Combine(_basePath, $"{id}.meta");
|
||||
}
|
||||
197
src/DownloadManager.Core/Engine/SegmentedDownloader.cs
Normal file
197
src/DownloadManager.Core/Engine/SegmentedDownloader.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using DownloadManager.Core.Enums;
|
||||
using DownloadManager.Core.Events;
|
||||
using DownloadManager.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Engine;
|
||||
|
||||
public class SegmentedDownloader : IDownloader
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ResumeManager _resumeManager;
|
||||
private readonly SpeedCalculator _speedCalculator;
|
||||
private readonly EngineOptions _options;
|
||||
private readonly object _progressLock = new();
|
||||
private DateTime _lastReport = DateTime.MinValue;
|
||||
|
||||
public SegmentedDownloader(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ResumeManager resumeManager,
|
||||
SpeedCalculator speedCalculator,
|
||||
EngineOptions options)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_resumeManager = resumeManager;
|
||||
_speedCalculator = speedCalculator;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public async Task DownloadAsync(DownloadItem item, IProgress<DownloadProgressEvent>? progress, CancellationToken ct)
|
||||
{
|
||||
Console.WriteLine($"[SegmentedDownloader] Starting download for {item.Id}");
|
||||
var state = await _resumeManager.LoadOrCreateAsync(item, _options);
|
||||
|
||||
if (state.Segments.Count == 0)
|
||||
{
|
||||
Console.WriteLine($"[SegmentedDownloader] Initializing segments for {item.Id}");
|
||||
InitializeSegments(state, _options);
|
||||
}
|
||||
|
||||
var tempDir = EnsureTempDirectory(item.Id);
|
||||
var activeTasks = new ConcurrentDictionary<int, Task>();
|
||||
|
||||
Console.WriteLine($"[SegmentedDownloader] Starting {state.Segments.Count(s => s.Status != SegmentStatus.Completed)} segments");
|
||||
foreach (var seg in state.Segments.Where(s => s.Status != SegmentStatus.Completed))
|
||||
{
|
||||
activeTasks[seg.Index] = DownloadSegmentAsync(item, seg, tempDir, progress, ct);
|
||||
}
|
||||
|
||||
while (activeTasks.Count > 0)
|
||||
{
|
||||
var done = await Task.WhenAny(activeTasks.Values);
|
||||
await done; // Önemli: Task'ı await ederek cancellation veya hataların fırlatılmasını sağlıyoruz
|
||||
|
||||
var completedTask = activeTasks.FirstOrDefault(x => x.Value == done);
|
||||
activeTasks.TryRemove(completedTask.Key, out _);
|
||||
|
||||
Console.WriteLine($"[SegmentedDownloader] Segment {completedTask.Key} completed. {activeTasks.Count} remaining.");
|
||||
|
||||
await _resumeManager.SaveAsync(item, state);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
Console.WriteLine($"[SegmentedDownloader] Merging segments for {item.Id}");
|
||||
await MergeSegmentsAsync(state.Segments, item.SavePath);
|
||||
await _resumeManager.DeleteAsync(item.Id);
|
||||
Directory.Delete(tempDir, true);
|
||||
Console.WriteLine($"[SegmentedDownloader] Download finished for {item.Id}");
|
||||
}
|
||||
|
||||
private void InitializeSegments(DownloadItemState state, EngineOptions options)
|
||||
{
|
||||
if (state.TotalSize <= 0)
|
||||
{
|
||||
// Safety fallback: if TotalSize is somehow 0 here, create a single "segment"
|
||||
// but normally DownloadEngine should have picked SingleDownloader.
|
||||
state.Segments.Add(new DownloadSegment
|
||||
{
|
||||
Index = 0,
|
||||
StartByte = 0,
|
||||
EndByte = 0,
|
||||
Downloaded = 0,
|
||||
Status = SegmentStatus.Pending,
|
||||
DownloadItemId = state.Id
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
long segmentSize = state.TotalSize / options.MaxSegments;
|
||||
if (segmentSize == 0) segmentSize = state.TotalSize; // Small file case
|
||||
|
||||
for (int i = 0; i < options.MaxSegments; i++)
|
||||
{
|
||||
long start = i * segmentSize;
|
||||
if (start >= state.TotalSize) break; // Don't create empty segments for very small files
|
||||
|
||||
long end = (i == options.MaxSegments - 1) ? state.TotalSize - 1 : (i + 1) * segmentSize - 1;
|
||||
if (end >= state.TotalSize) end = state.TotalSize - 1;
|
||||
|
||||
state.Segments.Add(new DownloadSegment
|
||||
{
|
||||
Index = i,
|
||||
StartByte = start,
|
||||
EndByte = end,
|
||||
Downloaded = 0,
|
||||
Status = SegmentStatus.Pending,
|
||||
DownloadItemId = state.Id
|
||||
});
|
||||
|
||||
if (end == state.TotalSize - 1) break; // Last segment reached
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadSegmentAsync(DownloadItem item, DownloadSegment segment, string tempDir, IProgress<DownloadProgressEvent>? progress, CancellationToken ct)
|
||||
{
|
||||
segment.Status = SegmentStatus.Downloading;
|
||||
segment.TempFilePath = Path.Combine(tempDir, $"seg_{segment.Index}.tmp");
|
||||
|
||||
using var client = _httpClientFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", _options.UserAgent);
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, item.Url);
|
||||
request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(segment.StartByte + segment.Downloaded, segment.EndByte);
|
||||
|
||||
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
using var stream = await response.Content.ReadAsStreamAsync(ct);
|
||||
using var fileStream = new FileStream(segment.TempFilePath, segment.Downloaded > 0 ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
|
||||
|
||||
var buffer = new byte[8192];
|
||||
int read;
|
||||
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length, ct)) > 0)
|
||||
{
|
||||
await fileStream.WriteAsync(buffer, 0, read, ct);
|
||||
segment.Downloaded += read;
|
||||
|
||||
lock (_progressLock)
|
||||
{
|
||||
item.DownloadedBytes += read;
|
||||
ReportProgress(item, progress);
|
||||
}
|
||||
|
||||
_speedCalculator.AddSample(read);
|
||||
}
|
||||
|
||||
segment.Status = SegmentStatus.Completed;
|
||||
}
|
||||
|
||||
private void ReportProgress(DownloadItem item, IProgress<DownloadProgressEvent>? progress)
|
||||
{
|
||||
if ((DateTime.UtcNow - _lastReport).TotalMilliseconds < 250) return;
|
||||
_lastReport = DateTime.UtcNow;
|
||||
|
||||
var downloaded = Math.Min(item.DownloadedBytes, item.TotalSize);
|
||||
var percent = item.TotalSize > 0 ? (double)downloaded / item.TotalSize * 100 : 0;
|
||||
if (percent > 100) percent = 100;
|
||||
|
||||
progress?.Report(new DownloadProgressEvent
|
||||
{
|
||||
Id = item.Id,
|
||||
DownloadedBytes = downloaded,
|
||||
TotalBytes = item.TotalSize,
|
||||
ProgressPercent = percent,
|
||||
SpeedBytesPerSec = _speedCalculator.GetBytesPerSecond(),
|
||||
EstimatedRemaining = _speedCalculator.EstimateRemaining(item.TotalSize - downloaded),
|
||||
Status = DownloadStatus.Downloading
|
||||
});
|
||||
}
|
||||
|
||||
private string EnsureTempDirectory(Guid id)
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), "DownloadManager", id.ToString());
|
||||
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private async Task MergeSegmentsAsync(List<DownloadSegment> segments, string savePath)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(savePath);
|
||||
if (directory != null && !Directory.Exists(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
using var outputStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
|
||||
foreach (var seg in segments.OrderBy(s => s.Index))
|
||||
{
|
||||
using var inputStream = new FileStream(seg.TempFilePath, FileMode.Open, FileAccess.Read);
|
||||
await inputStream.CopyToAsync(outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/DownloadManager.Core/Engine/SingleDownloader.cs
Normal file
58
src/DownloadManager.Core/Engine/SingleDownloader.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using DownloadManager.Core.Enums;
|
||||
using DownloadManager.Core.Events;
|
||||
using DownloadManager.Core.Models;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Engine;
|
||||
|
||||
public class SingleDownloader : IDownloader
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly SpeedCalculator _speedCalculator;
|
||||
|
||||
public SingleDownloader(IHttpClientFactory httpClientFactory, SpeedCalculator speedCalculator)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_speedCalculator = speedCalculator;
|
||||
}
|
||||
|
||||
public async Task DownloadAsync(DownloadItem item, IProgress<DownloadProgressEvent>? progress, CancellationToken ct)
|
||||
{
|
||||
using var client = _httpClientFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "DownloadManager/1.0 (.NET 8)");
|
||||
|
||||
using var response = await client.GetAsync(item.Url, HttpCompletionOption.ResponseHeadersRead, ct);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var directory = Path.GetDirectoryName(item.SavePath);
|
||||
if (directory != null && !Directory.Exists(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
using var contentStream = await response.Content.ReadAsStreamAsync(ct);
|
||||
using var fileStream = new FileStream(item.SavePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
|
||||
|
||||
var buffer = new byte[8192];
|
||||
int read;
|
||||
while ((read = await contentStream.ReadAsync(buffer, 0, buffer.Length, ct)) > 0)
|
||||
{
|
||||
await fileStream.WriteAsync(buffer, 0, read, ct);
|
||||
item.DownloadedBytes += read;
|
||||
_speedCalculator.AddSample(read);
|
||||
|
||||
progress?.Report(new DownloadProgressEvent
|
||||
{
|
||||
Id = item.Id,
|
||||
DownloadedBytes = item.DownloadedBytes,
|
||||
TotalBytes = item.TotalSize,
|
||||
ProgressPercent = item.TotalSize > 0 ? (double)item.DownloadedBytes / item.TotalSize * 100 : 0,
|
||||
SpeedBytesPerSec = _speedCalculator.GetBytesPerSecond(),
|
||||
EstimatedRemaining = _speedCalculator.EstimateRemaining(item.TotalSize - item.DownloadedBytes),
|
||||
Status = DownloadStatus.Downloading
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/DownloadManager.Core/Engine/SpeedCalculator.cs
Normal file
41
src/DownloadManager.Core/Engine/SpeedCalculator.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace DownloadManager.Core.Engine;
|
||||
|
||||
public class SpeedCalculator
|
||||
{
|
||||
// Son 3 saniyenin kayan pencere ortalaması — anlık dalgalanmaları bastırır
|
||||
private readonly Queue<(DateTime Time, long Bytes)> _samples = new();
|
||||
private const double WindowSeconds = 3.0;
|
||||
|
||||
public void AddSample(long newBytes)
|
||||
{
|
||||
lock (_samples)
|
||||
{
|
||||
_samples.Enqueue((DateTime.UtcNow, newBytes));
|
||||
while (_samples.TryPeek(out var old) &&
|
||||
(DateTime.UtcNow - old.Time).TotalSeconds > WindowSeconds)
|
||||
_samples.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Anlık hız (byte/saniye)</summary>
|
||||
public long GetBytesPerSecond()
|
||||
{
|
||||
lock (_samples)
|
||||
{
|
||||
if (_samples.Count < 2) return 0;
|
||||
var elapsed = (DateTime.UtcNow - _samples.Peek().Time).TotalSeconds;
|
||||
return elapsed > 0 ? (long)(_samples.Sum(s => s.Bytes) / elapsed) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Kalan süre tahmini</summary>
|
||||
public TimeSpan? EstimateRemaining(long remainingBytes)
|
||||
{
|
||||
var speed = GetBytesPerSecond();
|
||||
return speed > 0 ? TimeSpan.FromSeconds(remainingBytes / (double)speed) : null;
|
||||
}
|
||||
}
|
||||
8
src/DownloadManager.Core/Enums/AppTheme.cs
Normal file
8
src/DownloadManager.Core/Enums/AppTheme.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DownloadManager.Core.Enums;
|
||||
|
||||
public enum AppTheme
|
||||
{
|
||||
Light,
|
||||
Dark,
|
||||
System
|
||||
}
|
||||
12
src/DownloadManager.Core/Enums/DownloadStatus.cs
Normal file
12
src/DownloadManager.Core/Enums/DownloadStatus.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace DownloadManager.Core.Enums;
|
||||
|
||||
public enum DownloadStatus
|
||||
{
|
||||
Pending,
|
||||
Queued,
|
||||
Downloading,
|
||||
Paused,
|
||||
Completed,
|
||||
Error,
|
||||
Cancelled
|
||||
}
|
||||
9
src/DownloadManager.Core/Enums/ProxyType.cs
Normal file
9
src/DownloadManager.Core/Enums/ProxyType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DownloadManager.Core.Enums;
|
||||
|
||||
public enum ProxyType
|
||||
{
|
||||
None,
|
||||
Http,
|
||||
Socks4,
|
||||
Socks5
|
||||
}
|
||||
9
src/DownloadManager.Core/Enums/SegmentStatus.cs
Normal file
9
src/DownloadManager.Core/Enums/SegmentStatus.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DownloadManager.Core.Enums;
|
||||
|
||||
public enum SegmentStatus
|
||||
{
|
||||
Pending,
|
||||
Downloading,
|
||||
Completed,
|
||||
Failed
|
||||
}
|
||||
15
src/DownloadManager.Core/Events/DownloadProgressEvent.cs
Normal file
15
src/DownloadManager.Core/Events/DownloadProgressEvent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using DownloadManager.Core.Enums;
|
||||
using System;
|
||||
|
||||
namespace DownloadManager.Core.Events;
|
||||
|
||||
public class DownloadProgressEvent
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public double ProgressPercent { get; set; }
|
||||
public long DownloadedBytes { get; set; }
|
||||
public long TotalBytes { get; set; }
|
||||
public long SpeedBytesPerSec { get; set; }
|
||||
public TimeSpan? EstimatedRemaining { get; set; }
|
||||
public DownloadStatus Status { get; set; }
|
||||
}
|
||||
154
src/DownloadManager.Core/Grabber/SiteGrabber.cs
Normal file
154
src/DownloadManager.Core/Grabber/SiteGrabber.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using HtmlAgilityPack;
|
||||
|
||||
namespace DownloadManager.Core.Grabber;
|
||||
|
||||
public class GrabberOptions
|
||||
{
|
||||
public string StartUrl { get; set; } = string.Empty;
|
||||
public int MaxDepth { get; set; } = 2;
|
||||
public bool StayOnDomain { get; set; } = true;
|
||||
public string[] FileExtensions { get; set; } = Array.Empty<string>();
|
||||
public string? UrlPattern { get; set; }
|
||||
public long? MinFileSizeBytes { get; set; }
|
||||
public long? MaxFileSizeBytes { get; set; }
|
||||
public int MaxFileCount { get; set; } = 500;
|
||||
}
|
||||
|
||||
public record GrabberResult(string Url, string FileName, long? SizeBytes, string Extension);
|
||||
|
||||
public class SiteGrabber
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public SiteGrabber(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<GrabberResult> GrabAsync(
|
||||
GrabberOptions opts,
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var queue = new Queue<(string Url, int Depth)>();
|
||||
queue.Enqueue((opts.StartUrl, 0));
|
||||
|
||||
var baseUri = new Uri(opts.StartUrl);
|
||||
var found = 0;
|
||||
|
||||
while (queue.Count > 0 && !ct.IsCancellationRequested && found < opts.MaxFileCount)
|
||||
{
|
||||
var (url, depth) = queue.Dequeue();
|
||||
if (!visited.Add(url)) continue;
|
||||
|
||||
string html;
|
||||
try
|
||||
{
|
||||
var handler = new HttpClientHandler { AllowAutoRedirect = true };
|
||||
using var client = new HttpClient(handler);
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0");
|
||||
html = await client.GetStringAsync(url, ct);
|
||||
}
|
||||
catch { continue; }
|
||||
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(html);
|
||||
|
||||
var nodes = doc.DocumentNode.SelectNodes("//a[@href]");
|
||||
if (nodes == null) continue;
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
var href = node.GetAttributeValue("href", "");
|
||||
if (string.IsNullOrEmpty(href)) continue;
|
||||
|
||||
if (!Uri.TryCreate(baseUri, href, out var absoluteUri)) continue;
|
||||
var absoluteUrl = absoluteUri.AbsoluteUri;
|
||||
|
||||
if (IsDownloadTarget(absoluteUrl, opts))
|
||||
{
|
||||
found++;
|
||||
long? size = await GetFileSizeRobustAsync(absoluteUrl, ct);
|
||||
|
||||
yield return new GrabberResult(
|
||||
absoluteUrl,
|
||||
Path.GetFileName(absoluteUri.LocalPath),
|
||||
size,
|
||||
Path.GetExtension(absoluteUrl).TrimStart('.').ToLower()
|
||||
);
|
||||
}
|
||||
else if (depth < opts.MaxDepth && IsSameDomain(absoluteUrl, baseUri.Host))
|
||||
{
|
||||
queue.Enqueue((absoluteUrl, depth + 1));
|
||||
}
|
||||
|
||||
if (found >= opts.MaxFileCount) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<long?> GetFileSizeRobustAsync(string url, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handler = new HttpClientHandler { AllowAutoRedirect = true };
|
||||
using var client = new HttpClient(handler);
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
client.Timeout = TimeSpan.FromSeconds(6);
|
||||
|
||||
// 1. Önce HEAD dene
|
||||
using var headReq = new HttpRequestMessage(HttpMethod.Head, url);
|
||||
using var headResp = await client.SendAsync(headReq, HttpCompletionOption.ResponseHeadersRead, ct);
|
||||
|
||||
// Eğer boyut 500 bayttan büyükse gerçektir, değilse muhtemelen sahte redirect sayfasıdır
|
||||
if (headResp.IsSuccessStatusCode && headResp.Content.Headers.ContentLength > 500)
|
||||
{
|
||||
return headResp.Content.Headers.ContentLength;
|
||||
}
|
||||
|
||||
// 2. HEAD başarısızsa veya 146B gibi gelmişse, Range ile GET dene
|
||||
using var getReq = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
getReq.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(0, 0);
|
||||
using var getResp = await client.SendAsync(getReq, HttpCompletionOption.ResponseHeadersRead, ct);
|
||||
|
||||
if (getResp.Content.Headers.ContentRange?.HasLength == true)
|
||||
{
|
||||
return getResp.Content.Headers.ContentRange.Length;
|
||||
}
|
||||
|
||||
var fallback = getResp.Content.Headers.ContentLength;
|
||||
return fallback > 500 ? fallback : null;
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
private bool IsDownloadTarget(string url, GrabberOptions opts)
|
||||
{
|
||||
var ext = Path.GetExtension(url).TrimStart('.').ToLower();
|
||||
if (string.IsNullOrEmpty(ext)) return false;
|
||||
var skip = new[] { "html", "htm", "php", "aspx", "jsp", "txt" };
|
||||
if (skip.Contains(ext)) return false;
|
||||
return opts.FileExtensions.Length == 0 || opts.FileExtensions.Contains(ext);
|
||||
}
|
||||
|
||||
private bool IsSameDomain(string url, string host)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
return uri.Host.Equals(host, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
}
|
||||
10
src/DownloadManager.Core/Models/AppSetting.cs
Normal file
10
src/DownloadManager.Core/Models/AppSetting.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DownloadManager.Core.Models;
|
||||
|
||||
public class AppSetting
|
||||
{
|
||||
[Key]
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
9
src/DownloadManager.Core/Models/DownloadCategory.cs
Normal file
9
src/DownloadManager.Core/Models/DownloadCategory.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DownloadManager.Core.Models;
|
||||
|
||||
public class DownloadCategory
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string SavePath { get; set; } = string.Empty;
|
||||
public string Extensions { get; set; } = string.Empty; // Virgülle ayrılmış uzantılar
|
||||
}
|
||||
32
src/DownloadManager.Core/Models/DownloadItem.cs
Normal file
32
src/DownloadManager.Core/Models/DownloadItem.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using DownloadManager.Core.Enums;
|
||||
|
||||
namespace DownloadManager.Core.Models;
|
||||
|
||||
public class DownloadItem
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string Url { get; set; } = string.Empty;
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string SavePath { get; set; } = string.Empty;
|
||||
public int CategoryId { get; set; }
|
||||
public DownloadStatus Status { get; set; }
|
||||
public long TotalSize { get; set; }
|
||||
public long DownloadedBytes { get; set; }
|
||||
public int SegmentCount { get; set; } = 8;
|
||||
public int Priority { get; set; } = 5; // 1=yüksek 10=düşük
|
||||
public string? Referrer { get; set; }
|
||||
public string? UserAgent { get; set; }
|
||||
public string? Username { get; set; }
|
||||
public string? PasswordHash { get; set; } // DPAPI
|
||||
public string? ETag { get; set; }
|
||||
public string? LastModified { get; set; }
|
||||
public DateTime? LastActivityAt { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
public string? Sha256Checksum { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
|
||||
public DownloadCategory Category { get; set; } = null!;
|
||||
public ICollection<DownloadSegment> Segments { get; set; } = new List<DownloadSegment>();
|
||||
}
|
||||
15
src/DownloadManager.Core/Models/DownloadSegment.cs
Normal file
15
src/DownloadManager.Core/Models/DownloadSegment.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using DownloadManager.Core.Enums;
|
||||
|
||||
namespace DownloadManager.Core.Models;
|
||||
|
||||
public class DownloadSegment
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Guid DownloadItemId { get; set; }
|
||||
public int Index { get; set; }
|
||||
public long StartByte { get; set; }
|
||||
public long EndByte { get; set; }
|
||||
public long Downloaded { get; set; }
|
||||
public SegmentStatus Status { get; set; }
|
||||
public string TempFilePath { get; set; } = string.Empty;
|
||||
}
|
||||
6
src/DownloadManager.Core/Models/GrabberProject.cs
Normal file
6
src/DownloadManager.Core/Models/GrabberProject.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace DownloadManager.Core.Models;
|
||||
|
||||
public class GrabberProject
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
18
src/DownloadManager.Core/Models/ScheduleJob.cs
Normal file
18
src/DownloadManager.Core/Models/ScheduleJob.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace DownloadManager.Core.Models;
|
||||
|
||||
public class ScheduleJob
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public DateTime? StartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
// Günler: 1111111 (Pt, Sa, Ça, Pe, Cu, Ct, Pz)
|
||||
public string DaysOfWeek { get; set; } = "1111111";
|
||||
|
||||
public bool ShutdownOnComplete { get; set; }
|
||||
public bool HangUpOnComplete { get; set; }
|
||||
}
|
||||
108
src/DownloadManager.Core/Protocols/HttpProtocol.cs
Normal file
108
src/DownloadManager.Core/Protocols/HttpProtocol.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Protocols;
|
||||
|
||||
public class HttpProtocol : IDownloadProtocol
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public HttpProtocol(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public async Task<DownloadItem> GetFileInfoAsync(string url, CancellationToken ct)
|
||||
{
|
||||
using var client = _httpClientFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
client.DefaultRequestHeaders.Add("Accept", "*/*");
|
||||
|
||||
HttpResponseMessage? response = null;
|
||||
try
|
||||
{
|
||||
// Bazı sunucular HEAD isteğini reddeder veya yanlış sonuç döndürür (TechSpot gibi)
|
||||
// Bu yüzden önce GET ile header-only (ResponseHeadersRead) deniyoruz.
|
||||
using var getRequest = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
response = await client.SendAsync(getRequest, HttpCompletionOption.ResponseHeadersRead, ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
// Alternatif olarak HEAD deneyelim
|
||||
response.Dispose();
|
||||
using var headRequest = new HttpRequestMessage(HttpMethod.Head, url);
|
||||
response = await client.SendAsync(headRequest, HttpCompletionOption.ResponseHeadersRead, ct);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Hata durumunda son çare GET
|
||||
using var getRequest = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
response = await client.SendAsync(getRequest, HttpCompletionOption.ResponseHeadersRead, ct);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
// Yönlendirmeler sonrası ulaşılan nihai adres
|
||||
var finalUrl = response.RequestMessage?.RequestUri?.ToString() ?? url;
|
||||
|
||||
var item = new DownloadItem
|
||||
{
|
||||
Url = url,
|
||||
FileName = GetFileNameFromResponse(response, finalUrl),
|
||||
TotalSize = response.Content.Headers.ContentLength ?? 0,
|
||||
ETag = response.Headers.ETag?.Tag,
|
||||
LastModified = response.Content.Headers.LastModified?.ToString("R")
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
finally
|
||||
{
|
||||
response?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFileNameFromResponse(HttpResponseMessage response, string finalUrl)
|
||||
{
|
||||
// 1. Öncelik: Content-Disposition (Sunucunun önerdiği isim)
|
||||
var contentDisposition = response.Content.Headers.ContentDisposition;
|
||||
if (contentDisposition != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(contentDisposition.FileNameStar))
|
||||
return contentDisposition.FileNameStar.Trim('"');
|
||||
|
||||
if (!string.IsNullOrEmpty(contentDisposition.FileName))
|
||||
return contentDisposition.FileName.Trim('"');
|
||||
}
|
||||
|
||||
// 2. Öncelik: Nihai URL'nin son parçası
|
||||
try
|
||||
{
|
||||
var uri = new Uri(finalUrl);
|
||||
var path = uri.LocalPath;
|
||||
|
||||
// Eğer URL query string içinde dosya adı barındırıyorsa (bazı CDN'ler için)
|
||||
if (path.EndsWith("/") || string.IsNullOrEmpty(Path.GetFileName(path)))
|
||||
{
|
||||
var queryParts = System.Web.HttpUtility.ParseQueryString(uri.Query);
|
||||
var fileFromQuery = queryParts["file"] ?? queryParts["filename"] ?? queryParts["name"];
|
||||
if (!string.IsNullOrEmpty(fileFromQuery)) return fileFromQuery;
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileName(path);
|
||||
if (!string.IsNullOrEmpty(fileName) && fileName.Contains("."))
|
||||
return fileName;
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "download_" + DateTime.Now.Ticks.ToString().Substring(10);
|
||||
}
|
||||
}
|
||||
8
src/DownloadManager.Core/Protocols/IDownloadProtocol.cs
Normal file
8
src/DownloadManager.Core/Protocols/IDownloadProtocol.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using DownloadManager.Core.Models;
|
||||
|
||||
namespace DownloadManager.Core.Protocols;
|
||||
|
||||
public interface IDownloadProtocol
|
||||
{
|
||||
Task<DownloadItem> GetFileInfoAsync(string url, CancellationToken ct);
|
||||
}
|
||||
66
src/DownloadManager.Core/Queue/DownloadQueue.cs
Normal file
66
src/DownloadManager.Core/Queue/DownloadQueue.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Queue;
|
||||
|
||||
public class DownloadQueue
|
||||
{
|
||||
private SemaphoreSlim _concurrencySemaphore;
|
||||
private readonly SemaphoreSlim _itemsAvailableSemaphore = new(0);
|
||||
private readonly PriorityQueue<DownloadItem, int> _inner = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
public DownloadQueue(int maxConcurrent = 3)
|
||||
=> _concurrencySemaphore = new SemaphoreSlim(maxConcurrent, maxConcurrent);
|
||||
|
||||
public void Enqueue(DownloadItem item)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_inner.Enqueue(item, item.Priority);
|
||||
_itemsAvailableSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DownloadItem?> DequeueAsync(CancellationToken ct)
|
||||
{
|
||||
// Önce bir slotun boşalmasını bekle (concurrency limit)
|
||||
await _concurrencySemaphore.WaitAsync(ct);
|
||||
|
||||
try
|
||||
{
|
||||
// Sonra kuyrukta bir öğe olmasını bekle
|
||||
await _itemsAvailableSemaphore.WaitAsync(ct);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_inner.TryDequeue(out var item, out _))
|
||||
return item;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_concurrencySemaphore.Release();
|
||||
throw;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Release() => _concurrencySemaphore.Release();
|
||||
|
||||
/// <summary>Çalışma zamanında limit değiştir (ayarlar panelinden)</summary>
|
||||
public void SetConcurrencyLimit(int newLimit)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// Not: Bu basit uygulama çalışma anındaki limit değişimini tam olarak yansıtmayabilir,
|
||||
// ama temel yapı için yeterli.
|
||||
var old = _concurrencySemaphore;
|
||||
_concurrencySemaphore = new SemaphoreSlim(newLimit, newLimit);
|
||||
old.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/DownloadManager.Core/Security/AntivirusLauncher.cs
Normal file
43
src/DownloadManager.Core/Security/AntivirusLauncher.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Security;
|
||||
|
||||
public class AntivirusLauncher
|
||||
{
|
||||
// Gelecekte ISettingsService üzerinden okunacak
|
||||
public string ExePath { get; set; } = string.Empty;
|
||||
public string ParamTemplate { get; set; } = "\"{file}\" /scan";
|
||||
|
||||
/// <summary>
|
||||
/// İndirilen dosyayı kullanıcının seçtiği antivirüs ile tara.
|
||||
/// Parametre şablonundaki {file} gerçek yol ile değiştirilir.
|
||||
/// </summary>
|
||||
public async Task ScanAsync(string downloadedFilePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ExePath) || !File.Exists(ExePath))
|
||||
return;
|
||||
|
||||
// Güvenlik: dosya yolu tırnak içine alınmış olmalı
|
||||
var safeFilePath = $"\"{downloadedFilePath}\"";
|
||||
var arguments = ParamTemplate.Replace("{file}", safeFilePath);
|
||||
|
||||
var psi = new ProcessStartInfo(ExePath, arguments)
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var proc = Process.Start(psi);
|
||||
if (proc != null)
|
||||
await proc.WaitForExitAsync();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Antivirüs taraması başlatılamadı: {FilePath}", downloadedFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/DownloadManager.Core/Security/CredentialProtector.cs
Normal file
36
src/DownloadManager.Core/Security/CredentialProtector.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace DownloadManager.Core.Security;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static class CredentialProtector
|
||||
{
|
||||
// Windows DPAPI — kullanıcı oturumuna bağlı şifreleme
|
||||
public static string Protect(string plainText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(plainText)) return string.Empty;
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(plainText);
|
||||
var encrypted = ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser);
|
||||
return Convert.ToBase64String(encrypted);
|
||||
}
|
||||
|
||||
public static string Unprotect(string cipherText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cipherText)) return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var bytes = Convert.FromBase64String(cipherText);
|
||||
var decrypted = ProtectedData.Unprotect(bytes, null, DataProtectionScope.CurrentUser);
|
||||
return Encoding.UTF8.GetString(decrypted);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/DownloadManager.Core/Services/CategoryService.cs
Normal file
20
src/DownloadManager.Core/Services/CategoryService.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using DownloadManager.Core.Data.Repositories;
|
||||
using DownloadManager.Core.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Services;
|
||||
|
||||
public interface ICategoryService
|
||||
{
|
||||
Task<IEnumerable<DownloadCategory>> GetCategoriesAsync();
|
||||
}
|
||||
|
||||
public class CategoryService : ICategoryService
|
||||
{
|
||||
private readonly ICategoryRepository _repository;
|
||||
public CategoryService(ICategoryRepository repository) => _repository = repository;
|
||||
|
||||
public Task<IEnumerable<DownloadCategory>> GetCategoriesAsync()
|
||||
=> _repository.GetAllAsync();
|
||||
}
|
||||
71
src/DownloadManager.Core/Services/DownloadService.cs
Normal file
71
src/DownloadManager.Core/Services/DownloadService.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using DownloadManager.Core.Data.Repositories;
|
||||
using DownloadManager.Core.Engine;
|
||||
using DownloadManager.Core.Models;
|
||||
using DownloadManager.Core.Queue;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Services;
|
||||
|
||||
public interface IDownloadService
|
||||
{
|
||||
Task<IEnumerable<DownloadItem>> GetAllDownloadsAsync();
|
||||
Task AddDownloadAsync(DownloadItem item);
|
||||
Task UpdateDownloadAsync(DownloadItem item);
|
||||
Task PauseDownloadAsync(Guid id);
|
||||
Task ResumeDownloadAsync(Guid id);
|
||||
Task DeleteDownloadAsync(Guid id);
|
||||
}
|
||||
|
||||
public class DownloadService : IDownloadService
|
||||
{
|
||||
private readonly IDownloadRepository _repository;
|
||||
private readonly DownloadEngine _engine;
|
||||
private readonly DownloadQueue _queue;
|
||||
|
||||
public DownloadService(IDownloadRepository repository, DownloadEngine engine, DownloadQueue queue)
|
||||
{
|
||||
_repository = repository;
|
||||
_engine = engine;
|
||||
_queue = queue;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<DownloadItem>> GetAllDownloadsAsync()
|
||||
=> _repository.GetAllAsync();
|
||||
|
||||
public async Task AddDownloadAsync(DownloadItem item)
|
||||
{
|
||||
await _repository.AddAsync(item);
|
||||
_queue.Enqueue(item);
|
||||
}
|
||||
|
||||
public async Task UpdateDownloadAsync(DownloadItem item)
|
||||
{
|
||||
await _repository.UpdateAsync(item);
|
||||
}
|
||||
|
||||
public Task PauseDownloadAsync(Guid id)
|
||||
{
|
||||
_engine.CancelDownload(id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task ResumeDownloadAsync(Guid id)
|
||||
{
|
||||
var item = await _repository.GetByIdAsync(id);
|
||||
if (item != null && item.Status != Enums.DownloadStatus.Completed)
|
||||
{
|
||||
item.Status = Enums.DownloadStatus.Queued;
|
||||
await _repository.UpdateAsync(item);
|
||||
_queue.Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task DeleteDownloadAsync(Guid id)
|
||||
{
|
||||
await _repository.DeleteAsync(id);
|
||||
_engine.CancelDownload(id);
|
||||
}
|
||||
}
|
||||
29
src/DownloadManager.Core/Services/ScheduleService.cs
Normal file
29
src/DownloadManager.Core/Services/ScheduleService.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using DownloadManager.Core.Data.Repositories;
|
||||
using DownloadManager.Core.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Services;
|
||||
|
||||
public interface IScheduleService
|
||||
{
|
||||
Task<IEnumerable<ScheduleJob>> GetJobsAsync();
|
||||
Task SaveJobAsync(ScheduleJob job);
|
||||
Task DeleteJobAsync(int id);
|
||||
}
|
||||
|
||||
public class ScheduleService : IScheduleService
|
||||
{
|
||||
private readonly IScheduleRepository _repository;
|
||||
public ScheduleService(IScheduleRepository repository) => _repository = repository;
|
||||
|
||||
public Task<IEnumerable<ScheduleJob>> GetJobsAsync() => _repository.GetAllAsync();
|
||||
|
||||
public async Task SaveJobAsync(ScheduleJob job)
|
||||
{
|
||||
if (job.Id == 0) await _repository.AddAsync(job);
|
||||
else await _repository.UpdateAsync(job);
|
||||
}
|
||||
|
||||
public Task DeleteJobAsync(int id) => _repository.DeleteAsync(id);
|
||||
}
|
||||
113
src/DownloadManager.Core/Services/SchedulerWatchdog.cs
Normal file
113
src/DownloadManager.Core/Services/SchedulerWatchdog.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using DownloadManager.Core.Models;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Services;
|
||||
|
||||
public class SchedulerWatchdog
|
||||
{
|
||||
private readonly IScheduleService _scheduleService;
|
||||
private readonly IDownloadService _downloadService;
|
||||
private Timer? _timer;
|
||||
private bool _isProcessing;
|
||||
|
||||
public SchedulerWatchdog(IScheduleService scheduleService, IDownloadService downloadService)
|
||||
{
|
||||
_scheduleService = scheduleService;
|
||||
_downloadService = downloadService;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// Her 30 saniyede bir kontrol et
|
||||
_timer = new Timer(async _ => await CheckScheduleAsync(), null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
|
||||
Serilog.Log.Information("Zamanlayıcı İzleyici (Watchdog) başlatıldı.");
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_timer?.Dispose();
|
||||
Serilog.Log.Information("Zamanlayıcı İzleyici durduruldu.");
|
||||
}
|
||||
|
||||
private async Task CheckScheduleAsync()
|
||||
{
|
||||
if (_isProcessing) return;
|
||||
_isProcessing = true;
|
||||
|
||||
try
|
||||
{
|
||||
var jobs = await _scheduleService.GetJobsAsync();
|
||||
var activeJob = jobs.FirstOrDefault(j => j.IsActive);
|
||||
|
||||
if (activeJob == null) return;
|
||||
|
||||
var now = DateTime.Now;
|
||||
var currentTime = now.TimeOfDay;
|
||||
|
||||
// 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;
|
||||
|
||||
// Başlatma kontrolü
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Durdurma kontrolü
|
||||
if (activeJob.EndTime.HasValue)
|
||||
{
|
||||
var endTime = activeJob.EndTime.Value.TimeOfDay;
|
||||
if (currentTime >= endTime && currentTime < endTime.Add(TimeSpan.FromMinutes(1)))
|
||||
{
|
||||
await StopAllDownloads();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Zamanlayıcı kontrolü sırasında hata oluştu.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartScheduledDownloads()
|
||||
{
|
||||
Serilog.Log.Information("Zamanlayıcı: İndirmeler başlatılıyor...");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StopAllDownloads()
|
||||
{
|
||||
Serilog.Log.Information("Zamanlayıcı: İndirmeler durduruluyor...");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/DownloadManager.Core/Services/SettingsService.cs
Normal file
22
src/DownloadManager.Core/Services/SettingsService.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using DownloadManager.Core.Data.Repositories;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DownloadManager.Core.Services;
|
||||
|
||||
public interface ISettingsService
|
||||
{
|
||||
Task<string?> GetAsync(string key, string? defaultValue = null);
|
||||
Task SetAsync(string key, string value);
|
||||
}
|
||||
|
||||
public class SettingsService : ISettingsService
|
||||
{
|
||||
private readonly ISettingsRepository _repository;
|
||||
public SettingsService(ISettingsRepository repository) => _repository = repository;
|
||||
|
||||
public async Task<string?> GetAsync(string key, string? defaultValue = null)
|
||||
=> await _repository.GetValueAsync(key) ?? defaultValue;
|
||||
|
||||
public Task SetAsync(string key, string value)
|
||||
=> _repository.SetValueAsync(key, value);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
"configProperties": {
|
||||
"System.Reflection.NullabilityInfoContext.IsSupported": true,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
"configProperties": {
|
||||
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
|
||||
"System.Reflection.NullabilityInfoContext.IsSupported": true,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
@@ -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.Core")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("DownloadManager.Core")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("DownloadManager.Core")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// MSBuild WriteCodeFragment sınıfı tarafından oluşturuldu.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
46bf396899510cb77858921f8a30d4ed1bbe8d1ebf3aa0a92a7b12cea57c65d4
|
||||
@@ -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.Core
|
||||
build_property.ProjectDir = D:\Calismalar\ai\hDM\DownloadManager\src\DownloadManager.Core\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -0,0 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using global::System;
|
||||
global using global::System.Collections.Generic;
|
||||
global using global::System.IO;
|
||||
global using global::System.Linq;
|
||||
global using global::System.Net.Http;
|
||||
global using global::System.Threading;
|
||||
global using global::System.Threading.Tasks;
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
4d79c1e07d3393b6086287484da8294d3710161dbd1296f2efebb760c1a85310
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user