ilk commit

This commit is contained in:
hOLOlu
2026-05-04 01:19:04 +03:00
commit 5f33557f2d
2072 changed files with 75437 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,74 @@
{
"Version": 1,
"WorkspaceRootPath": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|d:\\calismalar\\ai\\hdm\\downloadmanager\\src\\downloadmanager.wpf\\viewmodels\\mainviewmodel.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|solutionrelative:src\\downloadmanager.wpf\\viewmodels\\mainviewmodel.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|d:\\calismalar\\ai\\hdm\\downloadmanager\\src\\downloadmanager.wpf\\views\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
"RelativeMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|solutionrelative:src\\downloadmanager.wpf\\views\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
},
{
"AbsoluteMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|d:\\calismalar\\ai\\hdm\\downloadmanager\\src\\downloadmanager.wpf\\app.xaml.cs||{8B382828-6202-11D1-8870-0000F87579D2}|",
"RelativeMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|solutionrelative:src\\downloadmanager.wpf\\app.xaml.cs||{8B382828-6202-11D1-8870-0000F87579D2}|"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
},
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "MainViewModel.cs",
"DocumentMoniker": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\ViewModels\\MainViewModel.cs",
"RelativeDocumentMoniker": "src\\DownloadManager.WPF\\ViewModels\\MainViewModel.cs",
"ToolTip": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\ViewModels\\MainViewModel.cs",
"RelativeToolTip": "src\\DownloadManager.WPF\\ViewModels\\MainViewModel.cs",
"ViewState": "AgIAABQAAAAAAAAAAAAewCoAAAArAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-03T21:58:25.845Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "App.xaml.cs",
"DocumentMoniker": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\App.xaml.cs",
"RelativeDocumentMoniker": "src\\DownloadManager.WPF\\App.xaml.cs",
"ToolTip": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\App.xaml.cs",
"RelativeToolTip": "src\\DownloadManager.WPF\\App.xaml.cs",
"ViewState": "AgIAAFQAAAAAAAAAAAAYwAAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-03T21:58:15.818Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "MainWindow.xaml",
"DocumentMoniker": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\Views\\MainWindow.xaml",
"RelativeDocumentMoniker": "src\\DownloadManager.WPF\\Views\\MainWindow.xaml",
"ToolTip": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\Views\\MainWindow.xaml",
"RelativeToolTip": "src\\DownloadManager.WPF\\Views\\MainWindow.xaml",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
"WhenOpened": "2026-05-01T23:15:00.129Z",
"EditorCaption": ""
}
]
}
]
}
]
}

View File

@@ -0,0 +1,74 @@
{
"Version": 1,
"WorkspaceRootPath": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|d:\\calismalar\\ai\\hdm\\downloadmanager\\src\\downloadmanager.wpf\\viewmodels\\mainviewmodel.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|solutionrelative:src\\downloadmanager.wpf\\viewmodels\\mainviewmodel.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|d:\\calismalar\\ai\\hdm\\downloadmanager\\src\\downloadmanager.wpf\\views\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
"RelativeMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|solutionrelative:src\\downloadmanager.wpf\\views\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
},
{
"AbsoluteMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|d:\\calismalar\\ai\\hdm\\downloadmanager\\src\\downloadmanager.wpf\\app.xaml.cs||{8B382828-6202-11D1-8870-0000F87579D2}|",
"RelativeMoniker": "D:0:0:{11611D1E-B832-4666-9E37-23014F395F55}|src\\DownloadManager.WPF\\DownloadManager.WPF.csproj|solutionrelative:src\\downloadmanager.wpf\\app.xaml.cs||{8B382828-6202-11D1-8870-0000F87579D2}|"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
},
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "MainViewModel.cs",
"DocumentMoniker": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\ViewModels\\MainViewModel.cs",
"RelativeDocumentMoniker": "src\\DownloadManager.WPF\\ViewModels\\MainViewModel.cs",
"ToolTip": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\ViewModels\\MainViewModel.cs",
"RelativeToolTip": "src\\DownloadManager.WPF\\ViewModels\\MainViewModel.cs",
"ViewState": "AgIAABQAAAAAAAAAAAAewCoAAAArAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-03T21:58:25.845Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "App.xaml.cs",
"DocumentMoniker": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\App.xaml.cs",
"RelativeDocumentMoniker": "src\\DownloadManager.WPF\\App.xaml.cs",
"ToolTip": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\App.xaml.cs",
"RelativeToolTip": "src\\DownloadManager.WPF\\App.xaml.cs",
"ViewState": "AgIAAFQAAAAAAAAAAAAYwAAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-03T21:58:15.818Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "MainWindow.xaml",
"DocumentMoniker": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\Views\\MainWindow.xaml",
"RelativeDocumentMoniker": "src\\DownloadManager.WPF\\Views\\MainWindow.xaml",
"ToolTip": "D:\\Calismalar\\AI\\hDM\\DownloadManager\\src\\DownloadManager.WPF\\Views\\MainWindow.xaml",
"RelativeToolTip": "src\\DownloadManager.WPF\\Views\\MainWindow.xaml",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
"WhenOpened": "2026-05-01T23:15:00.129Z",
"EditorCaption": ""
}
]
}
]
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

78
Down_nb-01.svg Normal file
View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512">
<!-- Generator: Adobe Illustrator 30.3.0, SVG Export Plug-In . SVG Version: 2.1.3 Build 182) -->
<defs>
<style>
.st0 {
stroke: url(#linear-gradient3);
}
.st0, .st1 {
fill: #1e78ba;
}
.st0, .st1, .st2 {
stroke-miterlimit: 10;
stroke-width: 2px;
}
.st3, .st2 {
fill: #fff;
}
.st4 {
fill: #d8d8d8;
}
.st1 {
stroke: url(#linear-gradient2);
}
.st5 {
fill: #aaa;
}
.st2 {
stroke: url(#linear-gradient1);
}
.st6 {
opacity: .7;
}
.st7 {
fill: url(#linear-gradient);
}
</style>
<linearGradient id="linear-gradient" x1="256.04" y1="102.33" x2="256.04" y2="401.45" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#fff"/>
<stop offset=".09" stop-color="#f6f6f6"/>
<stop offset=".22" stop-color="#dfdfdf"/>
<stop offset=".39" stop-color="#bababa"/>
<stop offset=".58" stop-color="#868686"/>
<stop offset=".8" stop-color="#444"/>
<stop offset="1" stop-color="#000"/>
</linearGradient>
<linearGradient id="linear-gradient1" x1="255.86" y1="339.28" x2="255.86" y2="181.51" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#fff"/>
<stop offset="1" stop-color="#000"/>
</linearGradient>
<linearGradient id="linear-gradient2" x1="147.47" y1="243.93" x2="147.47" y2="149.16" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#fff"/>
<stop offset="1" stop-color="#000"/>
</linearGradient>
<linearGradient id="linear-gradient3" x1="364.44" y1="243.93" x2="364.44" xlink:href="#linear-gradient2"/>
</defs>
<path d="M473.56,397.58V103.22c0-7.04-5.76-12.8-12.8-12.8H51.32c-7.04,0-12.8,5.76-12.8,12.8v294.35s436.11,0,435.04,0Z"/>
<rect class="st7" x="54.52" y="106.42" width="403.04" height="275.15"/>
<rect class="st4" y="397.58" width="511.91" height="8"/>
<polygon class="st5" points="486.41 421.58 25.69 421.58 .09 405.58 512 405.58 486.41 421.58"/>
<g class="st6">
<path class="st3" d="M298.99,397.58h-86.07c.82,4.49,4.82,8,9.83,8h66.4c5.02,0,9.02-3.51,9.84-8Z"/>
</g>
<polygon class="st2" points="309.29 235 272.86 272.14 272.86 182.51 238.87 182.51 238.87 272.14 202.45 235 178.19 258.66 255.86 337.85 333.54 258.66 309.29 235"/>
<g>
<polygon class="st1" points="179.23 181.36 157.58 203.44 157.58 150.16 137.38 150.16 137.38 203.44 115.72 181.36 101.3 195.42 147.47 242.5 193.65 195.42 179.23 181.36"/>
<polygon class="st0" points="396.19 181.36 374.54 203.44 374.54 150.16 354.34 150.16 354.34 203.44 332.68 181.36 318.26 195.42 364.43 242.5 410.62 195.42 396.19 181.36"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

116
DownloadManager.sln Normal file
View File

@@ -0,0 +1,116 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DownloadManager.Core", "src\DownloadManager.Core\DownloadManager.Core.csproj", "{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DownloadManager.WPF", "src\DownloadManager.WPF\DownloadManager.WPF.csproj", "{11611D1E-B832-4666-9E37-23014F395F55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DownloadManager.BrowserBridge", "src\DownloadManager.BrowserBridge\DownloadManager.BrowserBridge.csproj", "{9690EB78-195A-424D-927A-1A900F30470D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DownloadManager.Core.Tests", "tests\DownloadManager.Core.Tests\DownloadManager.Core.Tests.csproj", "{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DownloadManager.WPF.Tests", "tests\DownloadManager.WPF.Tests\DownloadManager.WPF.Tests.csproj", "{F95B21F5-5F7E-4BAC-A846-3D1975914422}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DownloadManager.TestConsole", "src\DownloadManager.TestConsole\DownloadManager.TestConsole.csproj", "{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Debug|x64.ActiveCfg = Debug|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Debug|x64.Build.0 = Debug|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Debug|x86.ActiveCfg = Debug|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Debug|x86.Build.0 = Debug|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Release|Any CPU.Build.0 = Release|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Release|x64.ActiveCfg = Release|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Release|x64.Build.0 = Release|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Release|x86.ActiveCfg = Release|Any CPU
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7}.Release|x86.Build.0 = Release|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Debug|x64.ActiveCfg = Debug|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Debug|x64.Build.0 = Debug|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Debug|x86.ActiveCfg = Debug|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Debug|x86.Build.0 = Debug|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Release|Any CPU.Build.0 = Release|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Release|x64.ActiveCfg = Release|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Release|x64.Build.0 = Release|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Release|x86.ActiveCfg = Release|Any CPU
{11611D1E-B832-4666-9E37-23014F395F55}.Release|x86.Build.0 = Release|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Debug|x64.ActiveCfg = Debug|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Debug|x64.Build.0 = Debug|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Debug|x86.ActiveCfg = Debug|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Debug|x86.Build.0 = Debug|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Release|Any CPU.Build.0 = Release|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Release|x64.ActiveCfg = Release|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Release|x64.Build.0 = Release|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Release|x86.ActiveCfg = Release|Any CPU
{9690EB78-195A-424D-927A-1A900F30470D}.Release|x86.Build.0 = Release|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Debug|x64.ActiveCfg = Debug|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Debug|x64.Build.0 = Debug|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Debug|x86.ActiveCfg = Debug|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Debug|x86.Build.0 = Debug|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Release|Any CPU.Build.0 = Release|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Release|x64.ActiveCfg = Release|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Release|x64.Build.0 = Release|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Release|x86.ActiveCfg = Release|Any CPU
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6}.Release|x86.Build.0 = Release|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Debug|x64.ActiveCfg = Debug|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Debug|x64.Build.0 = Debug|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Debug|x86.ActiveCfg = Debug|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Debug|x86.Build.0 = Debug|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Release|Any CPU.Build.0 = Release|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Release|x64.ActiveCfg = Release|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Release|x64.Build.0 = Release|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Release|x86.ActiveCfg = Release|Any CPU
{F95B21F5-5F7E-4BAC-A846-3D1975914422}.Release|x86.Build.0 = Release|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Debug|x64.ActiveCfg = Debug|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Debug|x64.Build.0 = Debug|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Debug|x86.ActiveCfg = Debug|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Debug|x86.Build.0 = Debug|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Release|Any CPU.Build.0 = Release|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Release|x64.ActiveCfg = Release|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Release|x64.Build.0 = Release|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Release|x86.ActiveCfg = Release|Any CPU
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F9D4A5CB-FE99-4766-8662-9688A03CD3B7} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{11611D1E-B832-4666-9E37-23014F395F55} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{9690EB78-195A-424D-927A-1A900F30470D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{D861A9EA-BC51-4A9D-8B2C-9F2ABC7202F6} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{F95B21F5-5F7E-4BAC-A846-3D1975914422} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{8CB8ED62-AA01-4486-8BF9-BB21D77A3317} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,52 @@
const NATIVE_HOST = "com.downloadmanager.bridge";
const interceptExtensions = [
"exe","msi","zip","rar","7z","tar","gz","iso",
"mp4","mkv","avi","mp3","flac","wav",
"pdf","docx","xlsx","pptx"
];
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "dm-link",
title: "Download Manager ile İndir",
contexts: ["link"]
});
});
chrome.downloads.onCreated.addListener((item) => {
const ext = item.filename?.split(".").pop()?.toLowerCase() ?? "";
if (!interceptExtensions.includes(ext)) return;
chrome.downloads.cancel(item.id, () => {
console.log("İndirme yakalandı, bridge'e gönderiliyor:", item.url);
chrome.runtime.sendNativeMessage(NATIVE_HOST, {
action: "add_download",
url: item.url,
filename: item.filename,
referrer: item.referrer ?? ""
}, (response) => {
if (chrome.runtime.lastError) {
console.error("Bridge Hatası:", chrome.runtime.lastError.message);
} else {
console.log("Bridge Yanıtı:", response);
}
});
});
});
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "dm-link") {
console.log("Context menu tıklandı, bridge'e gönderiliyor:", info.linkUrl);
chrome.runtime.sendNativeMessage(NATIVE_HOST, {
action: "add_download",
url: info.linkUrl
}, (response) => {
if (chrome.runtime.lastError) {
console.error("Bridge Hatası:", chrome.runtime.lastError.message);
} else {
console.log("Bridge Yanıtı:", response);
}
});
}
});

View File

@@ -0,0 +1,12 @@
{
"manifest_version": 3,
"name": "Download Manager",
"version": "1.0.0",
"description": "hOLOlu Download Manager'a yönlendir",
"permissions": ["downloads", "contextMenus", "storage", "nativeMessaging"],
"host_permissions": ["<all_urls>"],
"background": { "service_worker": "background.js" },
"action": {
"default_popup": "popup/popup.html"
}
}

View File

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Download Manager</title>
<style>
body {
width: 250px;
padding: 15px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f3f3f3;
}
.header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.header img {
width: 32px;
height: 32px;
margin-right: 10px;
}
.header h1 {
font-size: 16px;
margin: 0;
color: #333;
}
.status {
font-size: 13px;
color: #666;
margin-bottom: 10px;
}
.footer {
border-top: 1px solid #ddd;
padding-top: 10px;
font-size: 11px;
color: #888;
text-align: center;
}
</style>
</head>
<body>
<div class="header">
<h1>Download Manager</h1>
</div>
<div class="status">
Uzantı aktif. İndirmeler otomatik olarak ana uygulamaya yönlendirilir.
</div>
<div class="footer">
© 2026 hDM Project
</div>
</body>
</html>

View File

@@ -0,0 +1,77 @@
# 🌐 Tarayıcı Entegrasyonu Kurulum Kılavuzu
Bu belge, **Download Manager**'ın Google Chrome ve Microsoft Edge tarayıcıları ile nasıl entegre edileceğini adım adım açıklar. Bu entegrasyon sayesinde tarayıcıda bir indirme başladığında otomatik olarak yakalanır veya sağ tık menüsü ile indirmeler uygulamaya gönderilir.
## 🏗️ Mimari Yapı
Sistem üç ana bileşenden oluşur:
1. **Tarayıcı Eklentisi:** İndirme isteklerini yakalayan JavaScript bileşeni.
2. **Browser Bridge (Native Messaging Host):** Tarayıcı ile Windows arasında köprü kuran küçük bir konsol uygulaması.
3. **Main App (WPF):** İndirmeyi gerçekleştiren ana uygulama.
---
## 🛠️ Adım 1: Browser Bridge'i Derleme
Tarayıcının bir `.exe` dosyası ile konuşması gerekir. Önce köprü uygulamasını derleyin:
```powershell
cd DownloadManager
dotnet build src/DownloadManager.BrowserBridge/DownloadManager.BrowserBridge.csproj -c Release
```
Derleme sonrası oluşan dosya yolu şuna benzer olacaktır:
`D:\Calismalar\AI\hDM\DownloadManager\src\DownloadManager.BrowserBridge\bin\Release\net8.0\DownloadManager.BrowserBridge.exe`
---
## 📝 Adım 2: Manifest Dosyasını Yapılandırma
`src/DownloadManager.BrowserBridge/manifest.json` dosyasınıın ve şu iki alanı güncelleyin:
1. **path:** Buraya derlediğiniz `.exe` dosyasının **tam yolunu** yazın.
2. **allowed_origins:** Buraya eklentiyi tarayıcıya yükledikten sonra alacağınız **Eklenti ID'sini** yazacaksınız (3. adımda).
Örnek (Geçici):
```json
{
"name": "com.downloadmanager.bridge",
"description": "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://[EKLEMTI_ID_BURAYA]/"
]
}
```
---
## 🔑 Adım 3: Registry (Kayıt Defteri) Kaydı
Tarayıcının bu köprüyü tanıması için Windows Kayıt Defteri'ne eklenmesi gerekir.
1. `src/DownloadManager.BrowserBridge/register_bridge.reg` dosyasını bir metin düzenleyici ile açın.
2. Dosya yollarının `manifest.json` dosyanızın bulunduğu konumu gösterdiğinden emin olun.
3. Dosyaya çift tıklayarak çalıştırın ve gelen uyarıya "Evet" deyin.
---
## 🔌 Adım 4: Eklentiyi Tarayıcıya Yükleme
1. Tarayıcınızda (Chrome veya Edge) `chrome://extensions/` adresine gidin.
2. Sağ üstteki **"Geliştirici Modu"** (Developer Mode) anahtarınıın.
3. **"Paketlenmemiş öğe yükle"** (Load unpacked) butonuna tıklayın.
4. Proje klasörünüzdeki `DownloadManager/browser-extension` klasörünü seçin.
5. Yükleme tamamlandığında eklenti kutusunda bir **"Kimlik" (ID)** oluşacaktır (Örn: `abcdefg...`).
6. **Bu ID'yi kopyalayın** ve 2. adımdaki `manifest.json` dosyasında `[EKLEMTI_ID_BURAYA]` yerine yapıştırın.
---
## 🚀 Kullanım ve Test
1. **Download Manager (WPF)** uygulamasını çalıştırın ve açık tutun.
2. Tarayıcıda herhangi bir dosyayı (Örn: bir .zip veya .iso dosyası) indirmeye çalışın.
3. Eklenti indirmeyi iptal edecek ve linki otomatik olarak Download Manager listesine ekleyecektir.
4. Alternatif olarak, herhangi bir linke **Sağ Tık > Download Manager ile İndir** diyebilirsiniz.
---
## ❓ Sorun Giderme
* **İndirme Yakalanmıyor:** `DownloadManager.WPF.exe`'nin açık olduğundan emin olun.
* **Tarayıcı Hatası:** Tarayıcı "Native host not found" hatası veriyorsa Registry yolunu ve `manifest.json` içindeki `path` değerini kontrol edin.
* **Loglar:** Köprü uygulaması hata aldığında kendi klasöründe `bridge_error.log` dosyası oluşturur, burayı kontrol edebilirsiniz.

View File

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

View 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");
}
}
}

View File

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

View File

@@ -0,0 +1,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
}
}
}

View File

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

View 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]/"
]
}

View 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/"
]
}

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]

View File

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

View File

@@ -0,0 +1 @@
000d6a996873011518a2be58f132c59662b6ff352ca8842e6f1fa1c31b20e4f1

View File

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

View File

@@ -0,0 +1,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;

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]

View File

@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("DownloadManager.BrowserBridge")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("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.

View File

@@ -0,0 +1 @@
89d0fd93dd3e67ad8186e0c515a8d65ebbbc94026391ff8e88db296a467ee7a3

View File

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

View File

@@ -0,0 +1,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;

View File

@@ -0,0 +1 @@
9a48f8efc256d5e9f4c6bcb351f545b3f8de09f2663f420d4e646e2dde80a99c

View File

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

View File

@@ -0,0 +1 @@
f69caf1b77b5ac1742efde949e911c38081902650a9646ddb4fb0df3c6723dd0

View 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"
}
}
}
}

View 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": []
}

View 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"

View File

@@ -0,0 +1,6 @@
namespace DownloadManager.Core;
public class Class1
{
}

View 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 = "" }
);
}
}

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

View 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
}
}
}

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

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

View 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)";
}

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

View 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");
}

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

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

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

View File

@@ -0,0 +1,8 @@
namespace DownloadManager.Core.Enums;
public enum AppTheme
{
Light,
Dark,
System
}

View File

@@ -0,0 +1,12 @@
namespace DownloadManager.Core.Enums;
public enum DownloadStatus
{
Pending,
Queued,
Downloading,
Paused,
Completed,
Error,
Cancelled
}

View File

@@ -0,0 +1,9 @@
namespace DownloadManager.Core.Enums;
public enum ProxyType
{
None,
Http,
Socks4,
Socks5
}

View File

@@ -0,0 +1,9 @@
namespace DownloadManager.Core.Enums;
public enum SegmentStatus
{
Pending,
Downloading,
Completed,
Failed
}

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

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

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

View 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
}

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

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

View File

@@ -0,0 +1,6 @@
namespace DownloadManager.Core.Models;
public class GrabberProject
{
public int Id { get; set; }
}

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

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

View File

@@ -0,0 +1,8 @@
using DownloadManager.Core.Models;
namespace DownloadManager.Core.Protocols;
public interface IDownloadProtocol
{
Task<DownloadItem> GetFileInfoAsync(string url, CancellationToken ct);
}

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

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

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

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

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

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

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