Merge pull request #863 from subhra74/migrate-to-c#

Migrate to c#
master
Subhra Das Gupta 2022-08-02 00:41:56 +05:30 committed by GitHub
commit be9f27f9bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
954 changed files with 92090 additions and 56563 deletions

View File

@ -0,0 +1,129 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build, test, sign and package a WPF or Windows Forms desktop application
# built on .NET Core.
# To learn how to migrate your existing application to .NET Core,
# refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework
#
# To configure this workflow:
#
# 1. Configure environment variables
# GitHub sets default environment variables for every workflow run.
# Replace the variables relative to your project in the "env" section below.
#
# 2. Signing
# Generate a signing certificate in the Windows Application
# Packaging Project or add an existing signing certificate to the project.
# Next, use PowerShell to encode the .pfx file using Base64 encoding
# by running the following Powershell script to generate the output string:
#
# $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte
# [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt'
#
# Open the output file, SigningCertificate_Encoded.txt, and copy the
# string inside. Then, add the string to the repo as a GitHub secret
# and name it "Base64_Encoded_Pfx."
# For more information on how to configure your signing certificate for
# this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing
#
# Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key".
# See "Build the Windows Application Packaging project" below to see how the secret is used.
#
# For more information on GitHub Actions, refer to https://github.com/features/actions
# For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications,
# refer to https://github.com/microsoft/github-actions-for-desktop-apps
name: .NET Core Desktop
on:
push:
branches: [ "migrate-to-c#" ]
pull_request:
branches: [ "migrate-to-c#" ]
jobs:
build:
#strategy:
# matrix:
# configuration: [Debug, Release]
runs-on: windows-latest # For a list of available runner types, refer to
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
env:
Solution_Name: app\XDM\XDM_CoreFx.sln # Replace with your solution name, i.e. MyWpfApp.sln.
Test_Project_Path: app\XDM\XDM_Tests\XDM.SystemTests.csproj # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj.
Wap_Project_Directory: app\XDM\XDM.WinForm.UI # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package.
Wap_Project_Path: app\XDM\XDM.WinForm.UI\XDM.WinForm.UI.csproj # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj.
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.0.2
#- name: Build Winforms project with Debug configuration
# run: msbuild /t:build /p:Configuration=Debug /p:TargetFramework=net4.7.2 app\XDM\XDM.WinForm.UI\XDM.WinForm.UI.csproj
# Install the .NET Core workload
- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
#- name: Setup MSBuild.exe
# uses: microsoft/setup-msbuild@v1.0.2
# Execute all unit tests in the solution
#- name: Execute unit tests
# run: dotnet test $env:Test_Project_Path
# Execute all unit tests in the solution
- name: Build Winforms project with Debug configuration
run: dotnet build -f net4.7.2 -c Debug $env:Wap_Project_Path
- name: Build Winforms project with Release configuration
run: dotnet build -f net4.7.2 -c Release $env:Wap_Project_Path
# Restore the application to populate the obj folder with RuntimeIdentifiers
#- name: Restore the application
# run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration
# env:
# Configuration: ${{ matrix.configuration }}
# Decode the base 64 encoded pfx and save the Signing_Certificate
#- name: Decode the pfx
# run: |
# $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}")
# $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx
# [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte)
# Create the app package by building and packaging the Windows Application Packaging project
#- name: Create the app package
# run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }}
# env:
# Appx_Bundle: Always
# Appx_Bundle_Platforms: x86|x64
# Appx_Package_Build_Mode: StoreUpload
# Configuration: ${{ matrix.configuration }}
# Remove the pfx
#- name: Remove the pfx
# run: Remove-Item -path $env:Wap_Project_Directory\$env:Signing_Certificate
# Upload the MSIX package: https://github.com/marketplace/actions/upload-a-build-artifact
#- name: Upload build artifacts
# uses: actions/upload-artifact@v2
# with:
# name: MSIX Package
# path: ${{ env.Wap_Project_Directory }}\AppPackages

363
.gitignore vendored
View File

@ -1,5 +1,358 @@
app/.classpath
app/.project
app/.settings/*
app/target/*
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
app/XDM/XDM.Win.Installer/ffmpeg-x86.exe
*.wixobj
app/XDM/XDM.Win.Installer/net4.7.2.wxs
app/XDM/XDM.Win.Installer/net4.7.2/
app/XDM/XDM.Win.Installer/*.msi
*.wixpdb
app/XDM/XDM.Win.Installer/*.exe
app/xdm-browser-monitor/extension-code.js

351
app/.gitignore vendored
View File

@ -1 +1,350 @@
/target/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

4
app/XDM/.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*.cs]
# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
dotnet_diagnostic.CS8618.severity = silent

View File

@ -0,0 +1,2 @@
Clone https://github.com/m-ab-s/media-autobuild_suite
Put ffmpeg_options.txt inside build folder

View File

@ -0,0 +1,16 @@
# Lines starting with this character are ignored
# To override some options specifically for the shared build, create a ffmpeg_options_shared.txt file.
# Basic built-in options, can be removed if you delete "--disable-autodetect"
--disable-everything
--disable-network
--disable-autodetect
--enable-small
--enable-demuxer=mov,mp4,m4a,3gp,3g2,mj2,matroska,webm,mpegts
--enable-libmp3lame
--enable-encoder=libmp3lame
--enable-muxer=mp4,matroska,mpegts,mp3
--enable-protocol=file,srt,concat
--enable-bsfs
--enable-filter=acopy,concat,copy
--enable-decoder=vorbis,opus,aac,ac3

50
app/XDM/Lang/Arabic.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=غير مكتمل
ALL_FINISHED=اكتمال
CAT_DOCUMENTS=مستندات
CAT_COMPRESSED=مضغوط
CAT_MUSIC=موسيقى
CAT_VIDEOS=فيديو
CAT_PROGRAMS=برامج
MENU_BATCH_DOWNLOAD=باتش التحميل
MENU_DELETE_DWN=حذف التنزيلات
MENU_DELETE_COMPLETED=تم الانتهاء من الحذف
MENU_IMPORT=استيراد
MENU_EXPORT=تصدير
MENU_EXIT=خروج
MENU_PAUSE=توقف
MENU_RESUME=استئناف
MENU_RESTART=أعاد التشغيل
MENU_REFRESH_LINK=تحديث الرابط
MENU_PROPERTIES=خصائص
MENU_UPDATE=البحث عن تحديث
MENU_ABOUT=XDM...حول
SORT_DATE=تاريخ
SORT_SIZE=حجم
SORT_NAME=اسم
ND_CANCEL=إلغاء
MSG_OK=اوك
LBL_MENU=القائمة
TITLE_SETTINGS=اعدادات
SETTINGS_MONITORING=رصد المتصفح
DESC_NEW=جديد
DESC_DEL=حذف
DESC_Q_TITLE=قائمة الانتظار وجدولة
Q_SCHEDULE_TXT=جدولة
Q_MOVE_TO=حرك الى
CTX_OPEN_FILE=فتح
CTX_OPEN_FOLDER=فتح مجلد
CTX_SAVE_AS=حفظ باسم
CTX_COPY_URL=إنسخ الرابط
CTX_COPY_FILE=نسخ الملف
MENU_IMPORT=استيراد
MENU_EXPORT=تصدير
MENU_LANG=لغة
MSG_LANG1=اختار اللغة
MSG_LANG2=XDM يرجى ملاحظة أن التغييرات ستدخل حيز التنفيذ في المرة القادمة التي تبدأ فيها
LBL_REPORT_PROBLEM=الإبلاغ عن خطأ
LBL_SUPPORT_PAGE=صفحة الدعم
LBL_SHOW_PROGRESS=عرض التقدم
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=正在進行
ALL_FINISHED=已完成
CAT_DOCUMENTS=檔案
CAT_COMPRESSED=壓縮檔案
CAT_MUSIC=音樂
CAT_VIDEOS=影片
CAT_PROGRAMS=程式
MENU_BATCH_DOWNLOAD=批量下載
MENU_DELETE_DWN=刪除此下載
MENU_DELETE_COMPLETED=清除已完成
MENU_IMPORT=匯入
MENU_EXPORT=匯出
MENU_EXIT=結束
MENU_PAUSE=暫停
MENU_RESUME=繼續
MENU_RESTART=重新開始
MENU_REFRESH_LINK=重新整理連結
MENU_PROPERTIES=屬性
MENU_UPDATE=檢查更新
MENU_ABOUT=關於 XDM...
SORT_DATE=按時間
SORT_SIZE=按大小
SORT_NAME=按名稱
ND_CANCEL=取消
MSG_OK=確定
LBL_MENU=選單
TITLE_SETTINGS=設定
SETTINGS_MONITORING=瀏覽器監視延伸模組
DESC_NEW=新增
DESC_DEL=刪除
DESC_Q_TITLE=佇列和下載安排
Q_SCHEDULE_TXT=調度安排
Q_MOVE_TO=移動到
CTX_OPEN_FILE=打開
CTX_OPEN_FOLDER=打開目錄
CTX_SAVE_AS=另存為
CTX_COPY_URL=複製URL
CTX_COPY_FILE=複製檔案
MENU_IMPORT=匯入
MENU_EXPORT=匯出
MENU_LANG=語言
MSG_LANG1=選擇語言
MSG_LANG2=請註意變更將在下次啟動XDM時生效
LBL_REPORT_PROBLEM=報告一個bug
LBL_SUPPORT_PAGE=支援頁面
LBL_SHOW_PROGRESS=顯示進度
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=未完成
ALL_FINISHED=已完成
CAT_DOCUMENTS=文档
CAT_COMPRESSED=压缩
CAT_MUSIC=音乐
CAT_VIDEOS=视频
CAT_PROGRAMS=程序
MENU_BATCH_DOWNLOAD=批量下载
MENU_DELETE_DWN=删除下载
MENU_DELETE_COMPLETED=清除已完成
MENU_IMPORT=导入
MENU_EXPORT=导出
MENU_EXIT=退出
MENU_PAUSE=暂停
MENU_RESUME=恢复
MENU_RESTART=重新下载
MENU_REFRESH_LINK=刷新链接
MENU_PROPERTIES=属性
MENU_UPDATE=检查更新
MENU_ABOUT=关于XDM...
SORT_DATE=时间
SORT_SIZE=大小
SORT_NAME=名称
ND_CANCEL=取消
MSG_OK=确定
LBL_MENU=菜单
TITLE_SETTINGS=设置
SETTINGS_MONITORING=浏览器监视
DESC_NEW=新建
DESC_DEL=删除
DESC_Q_TITLE=队列和计划
Q_SCHEDULE_TXT=计划
Q_MOVE_TO=移至
CTX_OPEN_FILE=打开
CTX_OPEN_FOLDER=打开目录
CTX_SAVE_AS=保存为
CTX_COPY_URL=复制URL
CTX_COPY_FILE=复制文件
MENU_IMPORT=导入
MENU_EXPORT=导出
MENU_LANG=语言
MSG_LANG1=选择语言
MSG_LANG2=在下次启动XDM时才会生效
LBL_REPORT_PROBLEM=报告错误
LBL_SUPPORT_PAGE=支持页
LBL_SHOW_PROGRESS=显示进度
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/Czech.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Nedokonèeno
ALL_FINISHED=Dokonèeno
CAT_DOCUMENTS=Dokumenty
CAT_COMPRESSED=Archivy
CAT_MUSIC=Hudba
CAT_VIDEOS=Video
CAT_PROGRAMS=Programy
MENU_BATCH_DOWNLOAD=Dávkové stahování
MENU_DELETE_DWN=Smazat stahování
MENU_DELETE_COMPLETED=Vymazat dokonèené
MENU_IMPORT=Importovat
MENU_EXPORT=Exportovat
MENU_EXIT=Konec
MENU_PAUSE=Pauza
MENU_RESUME=Pokraèovat
MENU_RESTART=Restartovat
MENU_REFRESH_LINK=Obnovit odkaz
MENU_PROPERTIES=Vlastnosti
MENU_UPDATE=Zkontrolovat aktualizace
MENU_ABOUT=O programu XDM...
SORT_DATE=Datum
SORT_SIZE=Velikost
SORT_NAME=Název
ND_CANCEL=ZRUŠIT
MSG_OK=OK
LBL_MENU=Menu
TITLE_SETTINGS=Nastavení
SETTINGS_MONITORING=Integrace v prohlížeèi
DESC_NEW=Nový
DESC_DEL=Smazat
DESC_Q_TITLE=Fronta a plánovaè
Q_SCHEDULE_TXT=Plánovaè
Q_MOVE_TO=Pøesunout do
CTX_OPEN_FILE=Otevøít
CTX_OPEN_FOLDER=Otevøít složku
CTX_SAVE_AS=Uložit jako
CTX_COPY_URL=Kopírovat URL
CTX_COPY_FILE=Kopírovat soubor
MENU_IMPORT=Importovat
MENU_EXPORT=Exportovat
MENU_LANG=Jazyk
MSG_LANG1=Zvolte jazyk
MSG_LANG2=Zmìny vejdou v platnost po restartování programu XDM
LBL_REPORT_PROBLEM=Nahlásit chybu
LBL_SUPPORT_PAGE=Stránka podpory
LBL_SHOW_PROGRESS=Zobrazit prùbìh
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

192
app/XDM/Lang/English--.txt Normal file
View File

@ -0,0 +1,192 @@
DESC_NEW=*New
DESC_DEL=*Delete
MENU_PAUSE=*Pause
MENU_RESUME=*Resume
CTX_OPEN_FILE=*Open
CTX_OPEN_FOLDER=*Open folder
LBL_SEARCH=*Search
TITLE_SETTINGS=*Settings
MENU_DELETE_COMPLETED=*Remove finished downloads
MENU_IMPORT=*Import
MENU_EXPORT=*Export
MENU_EXIT=*Exit
MENU_UPDATE=*Check for update
MENU_ABOUT=*About XDM
LBL_REPORT_PROBLEM=*Report a problem
LBL_SUPPORT_PAGE=*Help and support
MENU_DELETE_DWN=*Delete downloads
LBL_NEW_DOWNLOAD=*New download
LBL_MENU=*Menu
SETTINGS_MONITORING=*Browser monitoring
ALL_UNFINISHED=*Incomplete
ALL_FINISHED=*Complete
CAT_DOCUMENTS=*Document
CAT_COMPRESSED=*Compressed
CAT_MUSIC=*Music
CAT_VIDEOS=*Video
CAT_PROGRAMS=*Application
LBL_VIDEO_DOWNLOAD=*Video download
MENU_BATCH_DOWNLOAD=*Batch download
DESC_Q_TITLE=*Queue and scheduler
CTX_SAVE_AS=*Save As
CTX_COPY_URL=*Copy URL
CTX_COPY_FILE=*Copy File
MENU_REFRESH_LINK=*Refresh link
LBL_SHOW_PROGRESS=*Show progress
MENU_PROPERTIES=*Properties
MENU_RESTART=*Restart
Q_SCHEDULE_TXT=*Scheduler
Q_MOVE_TO=*Move to Queue
SORT_NAME=*Name
SORT_DATE=*Date
SORT_SIZE=*Size
SORT_STATUS=*Status
MENU_LANG=*Language
MSG_LANG1=*Select language
MSG_LANG2=*Please note changes will take effect next time you start XDM
MSG_OK=*OK
ND_CANCEL=*Cancel
STAT_DOWNLOADING=*Downloading
STAT_STOPPED=*Stopped
STAT_FINISHED=*Finished
ND_ADDRESS=*Address
ND_FILE=*File
LBL_SAVE_IN=*Save in
ND_IGNORE_URL=*Do not capture download from this address
ND_MORE=*More...
ND_DOWNLOAD_LATER=*Download Later
ND_DOWNLOAD_NOW=*Download Now
ND_AUTO_CAT=*Automatically select based on file type
BTN_BROWSE=*Browse...
ND_TITLE=*New Download
DESC_ADV_TITLE=*Advanced settings
ND_AUTH=*Authentication
DESC_NET4=*Proxy settings
SPEED_LIMIT_TITLE=*Speed limit
DESC_USER=*User Name
DESC_PASS=*Password
ND_AUTH_REMEMBER=*Remember authentication for this website
LBL_NET_OPT_DEF=*System default
ND_NO_PROXY=*No Proxy
ND_MANUAL_PROXY=*Manual Proxy
PROXY_HOST=*Proxy Host
PROXY_PORT=*Proxy Port
DESC_NET7=*Proxy username
DESC_NET8=*Proxy password
ND_SYSTEM_PROXY=*Open system proxy settings
MENU_SPEED_LIMITER=*Speed limiter
MSG_SPEED_LIMIT=*Download speed limit [KB/Sec ](0 unlimited)
MSG_INVALID_PORT=*Invalid port
MSG_INVALID_URL=*Download address is invalid or unsupported
MSG_NO_FILE=*Please enter file name
LBL_NEW_QUEUE=*Name
MSG_DOWNLOAD_FFMPEG=*Download FFmpeg?
LBL_QUEUE_OPT3=*Do not use queue
VID_PASTE_URL=*Please paste video URL link here
SETTINGS_ADV=*Advanced settings
VID_CHK=*Check/Uncheck All
O_VID_FMT=*Formats
BAT_PATTERN=*Pattern
BAT_LINKS=*Links
BAT_SELECT_ITEMS=*Select items to download
BAT_PASTE_LINK=*Please paste download links below
LBL_BATCH_DESC=*Download a group of sequential files using asterisk wild card (example http://xdman.sourceforge.net/images/edge*.png)
LBL_BATCH_ASTERISK=*Replace asterisk with
LBL_BATCH_LETTER=*Letters
LBL_BATCH_NUM=*Numbers
LBL_BATCH_FROM=*From
LBL_BATCH_TO=*To
LBL_BATCH_WILDCARD_SIZE=*Wildcard size
LBL_BATCH_FILE1=*First file
LBL_BATCH_FILE2=*Second file
LBL_BATCH_FILEN=*Last file
BAT_LEADING_ZERO=*Use leading zero
BAT_NO_LINK=*No link
MENU_START_Q=*Start Queue
DESC_SAVE_Q=*Save
Q_ADD=*Add
Q_REMOVE=*Remove
Q_MOVE_UP=*Move up
Q_MOVE_DN=*Move down
Q_MOVE_TO=*Move to...
Q_LIST_FILES=*Files in queue
Q_SCHEDULE_TXT=*Scheduler
Q_ENABLE=*Enable scheduler
MSG_Q_START=*Start queue at
MSG_Q_STOP=*Stop queue at
MSG_Q_DAILY=*Daily
MSG_Q_D1=*Sunday
MSG_Q_D2=*Monday
MSG_Q_D3=*Tuesday
MSG_Q_D4=*Wednesday
MSG_Q_D5=*Thursday
MSG_Q_D6=*Friday
MSG_Q_D7=*Saturday
SETTINGS_GENERAL=*General settings
SETTINGS_NETWORK=*Network settings
SETTINGS_CRED=*Password manager
SETTINGS_ADV=*Advanced settings
DESC_MONITORING_1=*Please select browsers to monitor and make sure, browser addon is installed and enabled in respective browsers
DESC_OTHER_BROWSERS=*XDM can also be integrated into other Chromium based browser (SRWare Iron etc) or Mozilla based (Icewasel, Waterfox etc) browsers using below links
DESC_CHROME=*Chromium based browsers
DESC_MOZ=*Firefox based browsers
DESC_FILETYPES=*XDM will automatically take over downloads from browser for below file types
DESC_DEF=*Defaults
DESC_VIDEOTYPES=*XDM will show download option when below video formats are played in browser
LBL_MIN_VIDEO_SIZE=*Download video larger than
DESC_SITEEXCEPTIONS=*Do not automatically capture downloads from below web sites
MENU_CLIP_ADD=*Monitor clipboard
LBL_GET_TIMESTAMP=*Get timestamp from server
SHOW_DWN_PRG=*Show download progress window
SHOW_DWN_COMPLETE=*Show download complete dialog
LBL_START_AUTO=*Start download automatically
LBL_OVERWRITE_EXISTING=*Overwrite existing file
LBL_TEMP_FOLDER=*Temporary folder
MSG_MAX_DOWNLOAD=*Maximum simultaneous downloads
SETTINGS_CAT=*Download categories
SETTINGS_CAT_NAME=*Category name
SETTINGS_CAT_TYPES=*File types
SETTINGS_CAT_FOLDER=*Download folder
SETTINGS_ATUO_CAT=*Automatically select download folder based on file type
SETTINGS_FOLDER=*Default download folder
SETTINGS_DARK_THEME=*Use dark theme if possible (Require app restart)
SETTINGS_CAT_ADD=*Add
SETTINGS_CAT_EDIT=*Edit
DESC_NET1=*Connection timeout
DESC_NET2=*Segments per download
NET_MAX_RETRY=*Maximum retry limit
NET_SYSTEM_PROXY=*Use system proxy settings
DESC_HOST=*Host/Server
MSG_HALT=*Shutdown computer after all download completes
MSG_AWAKE=*Prevent hibernate or sleep during downloads
EXEC_CMD=*Run a program after all download finishes
EXE_ANTI_VIR=*Scan file with antivirus after download
ANTIVIR_CMD=*Antivirus executable
ANTIVIR_ARGS=*Command line parameters
AUTO_START=*Launch XDM when system starts
LBL_DELETE_FILE=*Delete files from disk
DEL_SEL_TEXT=*Are you sure you want to delete selected downloads?
ERR_MSG_FILE_NOT_FOUND_MSG=*The file has been moved,renamed or deleted
NO_ITEM_SELECTED=*Please select item to open
MSG_QUEUE_NAME_MISSING=*Please specify queue name
LBL_QUEUE_OPT1=*Create new queue
MSG_QUEUE_NAME=*Queue name
MSG_QUEUE_SELECT_ITEMS=*Select downloads to add to queue
MSG_DWN_STOP=*Download stopped
MSG_NO_SPEED_LIMIT=*No speed limit
BTN_STOP_PROCESSING=*Stop
DWN_HIDE=*Hide
MSG_TIME_LEFT=*Time left
CD_TITLE=*Download Complete
REF_WAITING_FOR_LINK=*Waiting for download link...
SORT_TYPE=*Type
PROP_REFERER=*Referer
PROP_COOKIE=*Cookies
MSG_HEADERS=*Headers
OPT_UPDATE_FFMPEG=*Update components
MSG_FAILED=*Download failed
MSG_NO_UPDATE=*No updates available/already upto date
MSG_UPDATED=*Update successfull
MSG_ALREADY_RUNNING=*An old version of XDM is already running
MSG_BROWSER_LAUNCH_FAILED=*Unable to launch
MSG_NATIVE_HOST_FAILED=*Error installing native host

219
app/XDM/Lang/English.txt Normal file
View File

@ -0,0 +1,219 @@
DESC_NEW=New
DESC_DEL=Delete
MENU_PAUSE=Pause
MENU_RESUME=Resume
CTX_OPEN_FILE=Open
CTX_OPEN_FOLDER=Open folder
LBL_SEARCH=Search
TITLE_SETTINGS=Settings
MENU_DELETE_COMPLETED=Remove finished downloads
MENU_IMPORT=Import
MENU_EXPORT=Export
MENU_EXIT=Exit
MENU_UPDATE=Check for update
MENU_ABOUT=About XDM
LBL_REPORT_PROBLEM=Report a problem
LBL_SUPPORT_PAGE=Help and support
MENU_DELETE_DWN=Delete downloads
LBL_NEW_DOWNLOAD=New download
LBL_MENU=Menu
SETTINGS_MONITORING=Browser monitoring
ALL_UNFINISHED=Incomplete
ALL_FINISHED=Complete
CAT_DOCUMENTS=Document
CAT_COMPRESSED=Compressed
CAT_MUSIC=Music
CAT_VIDEOS=Video
CAT_PROGRAMS=Application
LBL_VIDEO_DOWNLOAD=Video download
MENU_BATCH_DOWNLOAD=Batch download
DESC_Q_TITLE=Queue and scheduler
CTX_SAVE_AS=Save As
CTX_COPY_URL=Copy URL
CTX_COPY_FILE=Copy File
MENU_REFRESH_LINK=Refresh link
LBL_SHOW_PROGRESS=Show progress
MENU_PROPERTIES=Properties
MENU_RESTART=Restart
Q_SCHEDULE_TXT=Scheduler
Q_MOVE_TO=Move to Queue
SORT_NAME=Name
SORT_DATE=Date
SORT_SIZE=Size
SORT_STATUS=Status
MENU_LANG=Language
MSG_LANG1=Select language
MSG_LANG2=Please note changes will take effect next time you start XDM
MSG_OK=OK
ND_CANCEL=Cancel
STAT_DOWNLOADING=Downloading
STAT_STOPPED=Stopped
STAT_FINISHED=Finished
ND_ADDRESS=Address
ND_FILE=File
LBL_SAVE_IN=Save in
ND_IGNORE_URL=Do not capture download from this address
ND_MORE=More...
ND_DOWNLOAD_LATER=Download Later
ND_DOWNLOAD_NOW=Download Now
ND_AUTO_CAT=Automatically select based on file type
BTN_BROWSE=Browse...
ND_TITLE=New Download
DESC_ADV_TITLE=Advanced settings
ND_AUTH=Authentication
DESC_NET4=Proxy settings
SPEED_LIMIT_TITLE=Speed limit
DESC_USER=User Name
DESC_PASS=Password
ND_AUTH_REMEMBER=Remember authentication for this website
LBL_NET_OPT_DEF=System default
ND_NO_PROXY=No Proxy
ND_MANUAL_PROXY=Manual Proxy
PROXY_HOST=Proxy Host
PROXY_PORT=Proxy Port
DESC_NET7=Proxy username
DESC_NET8=Proxy password
ND_SYSTEM_PROXY=Open system proxy settings
MENU_SPEED_LIMITER=Speed limiter
MSG_SPEED_LIMIT=Download speed limit [KB/Sec ](0 unlimited)
MSG_INVALID_PORT=Invalid port
MSG_INVALID_URL=Download address is invalid or unsupported
MSG_NO_FILE=Please enter file name
LBL_NEW_QUEUE=Name
MSG_DOWNLOAD_FFMPEG=Download FFmpeg?
LBL_QUEUE_OPT3=Do not use queue
VID_PASTE_URL=Please paste video URL link here
SETTINGS_ADV=Advanced settings
VID_CHK=Select all
O_VID_FMT=Format
BAT_PATTERN=Pattern
BAT_LINKS=Links
BAT_SELECT_ITEMS=Select items to download
BAT_PASTE_LINK=Please paste download links below
LBL_BATCH_DESC=Download a group of sequential files using asterisk wild card (example http://xdman.sourceforge.net/images/edge*.png)
LBL_BATCH_ASTERISK=Replace asterisk with
LBL_BATCH_LETTER=Letters
LBL_BATCH_NUM=Numbers
LBL_BATCH_FROM=From
LBL_BATCH_TO=To
LBL_BATCH_WILDCARD_SIZE=Wildcard size
LBL_BATCH_FILE1=First file
LBL_BATCH_FILE2=Second file
LBL_BATCH_FILEN=Last file
BAT_LEADING_ZERO=Use leading zero
BAT_NO_LINK=No link
MENU_START_Q=Start Queue
DESC_SAVE_Q=Save
Q_ADD=Add
Q_REMOVE=Remove
Q_MOVE_UP=Move up
Q_MOVE_DN=Move down
Q_MOVE_TO=Move to...
Q_LIST_FILES=Files in queue
Q_SCHEDULE_TXT=Scheduler
Q_ENABLE=Enable scheduler
MSG_Q_START=Start queue at
MSG_Q_STOP=Stop queue at
MSG_Q_DAILY=Daily
MSG_Q_D1=Sunday
MSG_Q_D2=Monday
MSG_Q_D3=Tuesday
MSG_Q_D4=Wednesday
MSG_Q_D5=Thursday
MSG_Q_D6=Friday
MSG_Q_D7=Saturday
SETTINGS_GENERAL=General settings
SETTINGS_NETWORK=Network settings
SETTINGS_CRED=Password manager
SETTINGS_ADV=Advanced settings
DESC_MONITORING_1=Please make sure XDM extension is installed in your browser. To install the browser extension, please click on the buttons below or copy paste the link directly in the browser
DESC_OTHER_BROWSERS=XDM can also be integrated into other Chromium based browser (Vivaldi, Brave browser etc) or Mozilla based (Icewasel, Waterfox etc) browsers using below links
DESC_CHROME=Chromium based browsers
DESC_MOZ=Firefox based browsers
DESC_FILETYPES=XDM will automatically take over downloads from browser for below file types
DESC_DEF=Defaults
DESC_VIDEOTYPES=XDM will show download option when below video formats are played in browser
LBL_MIN_VIDEO_SIZE=Download video larger than
DESC_SITEEXCEPTIONS=Do not automatically capture downloads from below web sites
MENU_CLIP_ADD=Monitor clipboard
LBL_GET_TIMESTAMP=Get timestamp from server
SHOW_DWN_PRG=Show download progress window
SHOW_DWN_COMPLETE=Show download complete dialog
LBL_START_AUTO=Start download automatically
LBL_OVERWRITE_EXISTING=Overwrite existing file
LBL_TEMP_FOLDER=Temporary folder
MSG_MAX_DOWNLOAD=Maximum simultaneous downloads
SETTINGS_CAT=Download categories
SETTINGS_CAT_NAME=Category name
SETTINGS_CAT_TYPES=File types
SETTINGS_CAT_FOLDER=Download folder
SETTINGS_ATUO_CAT=Automatically select download folder based on file type
SETTINGS_FOLDER=Default download folder
SETTINGS_DARK_THEME=Use dark theme if possible (Require app restart)
SETTINGS_CAT_ADD=Add
SETTINGS_CAT_EDIT=Edit
DESC_NET1=Connection timeout in seconds
DESC_NET2=Segments per download
NET_MAX_RETRY=Maximum retry limit
NET_SYSTEM_PROXY=Use system proxy settings
DESC_HOST=Host/Server
MSG_HALT=Shutdown computer after all download completes
MSG_AWAKE=Prevent hibernate or sleep during downloads
EXEC_CMD=Run a program after all download finishes
EXE_ANTI_VIR=Scan file with antivirus after download
ANTIVIR_CMD=Antivirus executable
ANTIVIR_ARGS=Command line parameters
AUTO_START=Launch XDM when system starts
LBL_DELETE_FILE=Delete files from disk
DEL_SEL_TEXT=Are you sure you want to delete selected downloads?
ERR_MSG_FILE_NOT_FOUND_MSG=The file has been moved,renamed or deleted
NO_ITEM_SELECTED=Please select item to open
MSG_QUEUE_NAME_MISSING=Please specify queue name
LBL_QUEUE_OPT1=Create new queue
MSG_QUEUE_NAME=Queue name
MSG_QUEUE_SELECT_ITEMS=Select downloads to add to queue
MSG_DWN_STOP=Download stopped
MSG_NO_SPEED_LIMIT=No speed limit
BTN_STOP_PROCESSING=Stop
DWN_HIDE=Hide
MSG_TIME_LEFT=Time left
CD_TITLE=Download Complete
REF_WAITING_FOR_LINK=Waiting for download link...
SORT_TYPE=Type
PROP_REFERER=Referer
PROP_COOKIE=Cookies
MSG_HEADERS=Headers
OPT_UPDATE_FFMPEG=Update components
MSG_FAILED=Download failed
MSG_NO_UPDATE=No updates available/already upto date
MSG_UPDATED=Update successfull
MSG_ALREADY_RUNNING=An old version of XDM is already running
MSG_BROWSER_LAUNCH_FAILED=Unable to launch
MSG_NATIVE_HOST_FAILED=Error installing native host
MSG_DONT_SHOW_AGAIN=Don't show this again
MSG_NO_USERNAME=User name required
MSG_REF_LINK_MSG=New download link is accepted
MSG_CATEGORY=Category
MSG_CAT_NAME_MISSING=Category name required
MSG_CAT_FILE_TYPES_MISSING=File types required
MSG_CAT_FOLDER_MISSING=Download folder required
MSG_HOST_NAME_MISSING=Host name required
MSG_QUALITY=Quality
MSG_MP3=Mp3 Audio
MSG_TIME=Time
STAT_ASSEMBLING=Assembling
STAT_WAITING=Waiting
MSG_UPDATE_AVAILABLE=Update(s) available
MSG_RESTORE=Restore Window
MSG_DOUBLE_CLICK_ACTION=Double click on download item
MSG_OPEN_FILE=Open file
MSG_FALLBACK_UA=User agent to be used if download is added manually
MSG_SAVE_AS_MP3=Save as MP3
MSG_VID_WIKI_TEXT=If you are interested in downloading streaming video from the browser using XDM
MSG_VID_WIKI_LINK=Please click here
NO_REFRESH_LINK=Link refresh is not available for this download
MSG_NO_VIDEO=No video found. However there might be other ways to download the video. Please click "Learn More" button for details.
MSG_VIDEO_DOWNLOAD_HELP=Learn More
MSG_READ_BROWSER_COOKIE=Read cookies from browser
MSG_SELECT_FOLDER=Select
MSG_IMPORT_DONE=Import complete

View File

@ -0,0 +1,4 @@
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/French.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Incomplets
ALL_FINISHED=Terminés
CAT_DOCUMENTS=Documents
CAT_COMPRESSED=Compressés
CAT_MUSIC=Musiques
CAT_VIDEOS=Vidéos
CAT_PROGRAMS=Programmes
MENU_BATCH_DOWNLOAD=Téléchargement par lot
MENU_DELETE_DWN=Supprimer le(s) téléchargement(s)
MENU_DELETE_COMPLETED=Supprimer téléchargement(s) terminé(s)
MENU_IMPORT=Importer
MENU_EXPORT=Exporter
MENU_EXIT=Quitter
MENU_PAUSE=Mettre en pause
MENU_RESUME=Reprendre
MENU_RESTART=Recommencer
MENU_REFRESH_LINK=Rafraîchir le lien
MENU_PROPERTIES=Propriétés
MENU_UPDATE=Vérifier les mises à jour
MENU_ABOUT=A propos de XDM...
SORT_DATE=Date
SORT_SIZE=Taille
SORT_NAME=Nom
ND_CANCEL=ANNULER
MSG_OK=OK
LBL_MENU=Menu
TITLE_SETTINGS=Paramètres
SETTINGS_MONITORING=Intégration navigateur
DESC_NEW=Nouveau
DESC_DEL=Supprimer
DESC_Q_TITLE=File d'attente/Planification
Q_SCHEDULE_TXT=Planification :
Q_MOVE_TO=Déplacer
CTX_OPEN_FILE=Ouvrir
CTX_OPEN_FOLDER=Ouvrir l'emplacement
CTX_SAVE_AS=Enregistrer sous
CTX_COPY_URL=Copier l'URL
CTX_COPY_FILE=Copier le fichier
MENU_IMPORT=Importer
MENU_EXPORT=Exporter
MENU_LANG=Langue
MSG_LANG1=Choisir langue
MSG_LANG2=Veuillez noter que les changements seront pris en compte au prochain démarrage de XDM
LBL_REPORT_PROBLEM=Rapporter un bug
LBL_SUPPORT_PAGE=Page de support
LBL_SHOW_PROGRESS=Afficher la progression
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/German.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Unvollständig
ALL_FINISHED=Vollständig
CAT_DOCUMENTS=Dokumente
CAT_COMPRESSED=Komprimiert
CAT_MUSIC=Musik
CAT_VIDEOS=Video
CAT_PROGRAMS=Programme
MENU_BATCH_DOWNLOAD=Batch herunterladen
MENU_DELETE_DWN=Downloads löschen
MENU_DELETE_COMPLETED=Abgeschlossene Downloads löschen
MENU_IMPORT=Importieren
MENU_EXPORT=Exportieren
MENU_EXIT=Beenden
MENU_PAUSE=Pausieren
MENU_RESUME=Fortsetzen
MENU_RESTART=Neu starten
MENU_REFRESH_LINK=Link aktualisieren
MENU_PROPERTIES=Eigenschaften
MENU_UPDATE=Auf Updates überprüfen
MENU_ABOUT=Über XDM...
SORT_DATE=Datum
SORT_SIZE=Größe
SORT_NAME=Name
ND_CANCEL=ABBRECHEN
MSG_OK=OK
LBL_MENU=Menü
TITLE_SETTINGS=Einstellungen
SETTINGS_MONITORING=Browser Überwachung
DESC_NEW=Neu
DESC_DEL=Löschen
DESC_Q_TITLE=Warteschlange und Planer
Q_SCHEDULE_TXT=Planer
Q_MOVE_TO=Verschieben nach
CTX_OPEN_FILE=Öffnen
CTX_OPEN_FOLDER=Ordner öffnen
CTX_SAVE_AS=Speichern unter
CTX_COPY_URL=URL kopieren
CTX_COPY_FILE=Datei kopieren
MENU_IMPORT=Importieren
MENU_EXPORT=Exportieren
MENU_LANG=Sprache
MSG_LANG1=Sprache auswählen
MSG_LANG2=Bitte berücksichtigen Sie, dass die Änderungen erst beim nächsten Start von XDM wirksam werden
LBL_REPORT_PROBLEM=Fehler melden
LBL_SUPPORT_PAGE=Unterstützungsseite
LBL_SHOW_PROGRESS=Fortschritt anzeigen
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Befejezetlen
ALL_FINISHED=Befejezett
CAT_DOCUMENTS=Dokumentumok
CAT_COMPRESSED=Tömörített
CAT_MUSIC=Zene
CAT_VIDEOS=Videó
CAT_PROGRAMS=Programok
MENU_BATCH_DOWNLOAD=Kötegelt letöltés
MENU_DELETE_DWN=Letöltések törlése
MENU_DELETE_COMPLETED=Törlés befejeződött
MENU_IMPORT=Import
MENU_EXPORT=Export
MENU_EXIT=Kilép
MENU_PAUSE=Szünet
MENU_RESUME=Folytatás
MENU_RESTART=Újraindítás
MENU_REFRESH_LINK=Link frissítés
MENU_PROPERTIES=Tulajdonságok
MENU_UPDATE=Frissítés ellenőrzése
MENU_ABOUT=Az XDM-ről...
SORT_DATE=Dátum
SORT_SIZE=Méret
SORT_NAME=Név
ND_CANCEL=Mégse
MSG_OK=OK
LBL_MENU=Menü
TITLE_SETTINGS=Beállítások
SETTINGS_MONITORING=Böngészőfelügyelet
DESC_NEW=Új
DESC_DEL=Töröl
DESC_Q_TITLE=Sor és időzítő
Q_SCHEDULE_TXT=Időzítő
Q_MOVE_TO=Áthelyezés
CTX_OPEN_FILE=Megnyit
CTX_OPEN_FOLDER=Mappa megnyitása
CTX_SAVE_AS=Mentés másként
CTX_COPY_URL=Webcím másolása
CTX_COPY_FILE=Fájl másolása
MENU_IMPORT=Import
MENU_EXPORT=Export
MENU_LANG=Nyelv
MSG_LANG1=Válasszon nyelvet
MSG_LANG2=Kérjük, vegye figyelembe, hogy a változtatások az XDM újraindítása után lépnek érvénybe
LBL_REPORT_PROBLEM=Hibajelentés
LBL_SUPPORT_PAGE=Támogatási oldal
LBL_SHOW_PROGRESS=Haladás megjelenítése
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Belum Selesai
ALL_FINISHED=Selesai
CAT_DOCUMENTS=Dokumen
CAT_COMPRESSED=Kompresi
CAT_MUSIC=Musik
CAT_VIDEOS=Video
CAT_PROGRAMS=Program
MENU_BATCH_DOWNLOAD=Unduh rangkap
MENU_DELETE_DWN=Hapus unduhan
MENU_DELETE_COMPLETED=Hapus unduhan selesai
MENU_IMPORT=Impor
MENU_EXPORT=Ekspor
MENU_EXIT=Keluar
MENU_PAUSE=Jeda
MENU_RESUME=Lanjutkan
MENU_RESTART=Mulai Ulang
MENU_REFRESH_LINK=Segarkan link
MENU_PROPERTIES=Properti
MENU_UPDATE=Periksa pembaruan
MENU_ABOUT=Tentang XDM...
SORT_DATE=Tanggal
SORT_SIZE=Ukuran
SORT_NAME=Nama
ND_CANCEL=BATAL
MSG_OK=OK
LBL_MENU=Menu
TITLE_SETTINGS=Pengaturan
SETTINGS_MONITORING=Monitor browser
DESC_NEW=Baru
DESC_DEL=Hapus
DESC_Q_TITLE=Antrian dan Jadwal
Q_SCHEDULE_TXT=Jadwal
Q_MOVE_TO=Pindah ke
CTX_OPEN_FILE=Buka
CTX_OPEN_FOLDER=Buka folder
CTX_SAVE_AS=Simpan Sebagai
CTX_COPY_URL=Salin URL
CTX_COPY_FILE=Salin Berkas
MENU_IMPORT=Impor
MENU_EXPORT=Ekspor
MENU_LANG=Bahasa
MSG_LANG1=Pilih bahasa
MSG_LANG2=Perhatian perubahan akan berlaku saat anda memulai ulang XDM
LBL_REPORT_PROBLEM=Laporkan kutu
LBL_SUPPORT_PAGE=Halaman dukungan
LBL_SHOW_PROGRESS=Lihat proses
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/Italian.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Incompleti
ALL_FINISHED=Completati
CAT_DOCUMENTS=Documenti
CAT_COMPRESSED=Compressi
CAT_MUSIC=Musica
CAT_VIDEOS=Video
CAT_PROGRAMS=Programmi
MENU_BATCH_DOWNLOAD=Scarica in batch
MENU_DELETE_DWN=Cancella downloads
MENU_DELETE_COMPLETED=Cancella completati
MENU_IMPORT=Importa
MENU_EXPORT=Esporta
MENU_EXIT=Esci
MENU_PAUSE=Pausa
MENU_RESUME=Riprendi
MENU_RESTART=Riavvia
MENU_REFRESH_LINK=Aggiorna collegamento
MENU_PROPERTIES=Proprietà
MENU_UPDATE=Controlla aggiornamenti
MENU_ABOUT=Informazioni su XDM...
SORT_DATE=Data
SORT_SIZE=Dimensione
SORT_NAME=Nome
ND_CANCEL=ANNULLA
MSG_OK=OK
LBL_MENU=Menu
TITLE_SETTINGS=Impostazioni
SETTINGS_MONITORING=Monitoraggio browser
DESC_NEW=Nuovo
DESC_DEL=Elimina
DESC_Q_TITLE=Accoda e pianifica
Q_SCHEDULE_TXT=Pianificatore
Q_MOVE_TO=Sposta a
CTX_OPEN_FILE=Apri
CTX_OPEN_FOLDER=Apri cartella
CTX_SAVE_AS=Salva come
CTX_COPY_URL=Copia URL
CTX_COPY_FILE=Copia File
MENU_IMPORT=Importa
MENU_EXPORT=Esporta
MENU_LANG=Lingua
MSG_LANG1=Seleziona lingua
MSG_LANG2=Si prega di notare che i cambiamenti entreranno in vigore al prossimo avvio di XDM
LBL_REPORT_PROBLEM=Riporta un bug
LBL_SUPPORT_PAGE=Pagina del supporto
LBL_SHOW_PROGRESS=Mostra progresso
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/Korea.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=미완료
ALL_FINISHED=완료
CAT_DOCUMENTS=문서
CAT_COMPRESSED=압축됨
CAT_MUSIC=음악
CAT_VIDEOS=비디오
CAT_PROGRAMS=프로그램
MENU_BATCH_DOWNLOAD=배치 다운로드
MENU_DELETE_DWN=다운로드 삭제
MENU_DELETE_COMPLETED=클리어 완료
MENU_IMPORT=Import
MENU_EXPORT=Export
MENU_EXIT=종료
MENU_PAUSE=일시 중지
MENU_RESUME=계속
MENU_RESTART=다시 시작
MENU_REFRESH_LINK=리프레시 링크
MENU_PROPERTIES=속성
MENU_UPDATE=업데이트 확인
MENU_ABOUT=XDM 정보
SORT_DATE=날짜
SORT_SIZE=사이즈
SORT_NAME=이름
ND_CANCEL=취소
MSG_OK=OK
LBL_MENU=메뉴
TITLE_SETTINGS=설정
SETTINGS_MONITORING=브라우저 모니터링
DESC_NEW=새로 만들기
DESC_DEL=삭제
DESC_Q_TITLE=큐 및 스케줄러
Q_SCHEDULE_TXT=스케줄러
Q_MOVE_TO=이동하기
CTX_OPEN_FILE=열림
CTX_OPEN_FOLDER=폴더 열기
CTX_SAVE_AS=다른 이름으로 저장
CTX_COPY_URL=URL 복사
CTX_COPY_FILE=파일 복사
MENU_IMPORT=Import
MENU_EXPORT=Export
MENU_LANG=언어
MSG_LANG1=언어 선택
MSG_LANG2=다음 번에 XDM을 시작할 때 변경 사항이 적용된다는 점에 유의하십시오.
LBL_REPORT_PROBLEM=버그리포트
LBL_SUPPORT_PAGE=지원페이지
LBL_SHOW_PROGRESS=진행 표시
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,4 @@
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

11
app/XDM/Lang/Nepali.txt Normal file
View File

@ -0,0 +1,11 @@
ALL_UNFINISHED=सबै नसकिएको
ALL_FINISHED=नसकिएको
CAT_DOCUMENTS=कागजात
CAT_COMPRESSED=थिचिएको
CAT_MUSIC=गाना
CAT_VIDEOS=मुभि
CAT_PROGRAMS=प्रोग्राम
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/Polish.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Nieukończone
ALL_FINISHED=Ukończone
CAT_DOCUMENTS=Dokumenty
CAT_COMPRESSED=Archiwa
CAT_MUSIC=Muzyka
CAT_VIDEOS=Wideo
CAT_PROGRAMS=Programy
MENU_BATCH_DOWNLOAD=Pobieranie wsadowe
MENU_DELETE_DWN=Usuń pobieranie
MENU_DELETE_COMPLETED=Wyczyść zakończone
MENU_IMPORT=Zaimportuj
MENU_EXPORT=Wyeksportuj
MENU_EXIT=Zamknij
MENU_PAUSE=Wstrzymaj
MENU_RESUME=Wznów
MENU_RESTART=Zrestartuj
MENU_REFRESH_LINK=Odśwież link
MENU_PROPERTIES=Właściwości
MENU_UPDATE=Aktualizuj
MENU_ABOUT=O programie
SORT_DATE=Data
SORT_SIZE=Rozmiar
SORT_NAME=Nazwa
ND_CANCEL=Anuluj
MSG_OK=OK
LBL_MENU=Menu
TITLE_SETTINGS=Ustawienia
SETTINGS_MONITORING=Monitor przeglądarki
DESC_NEW=Nowy wpis
DESC_DEL=Usuń
DESC_Q_TITLE=Kolejka i harmonogram
Q_SCHEDULE_TXT=Harmonogram
Q_MOVE_TO=Przenieś do
CTX_OPEN_FILE=Otwórz
CTX_OPEN_FOLDER=Otwórz folder
CTX_SAVE_AS=Zapisz jako
CTX_COPY_URL=Skopiuj URL
CTX_COPY_FILE=Skopiuj plik
MENU_IMPORT=Zaimportuj
MENU_EXPORT=Wyeksportuj
MENU_LANG=Język
MSG_LANG1=Wybierz język
MSG_LANG2=Zmiany zaczną obowiązywać po restarcie programu
LBL_REPORT_PROBLEM=Zgłoś błąd
LBL_SUPPORT_PAGE=Strona wsparcia
LBL_SHOW_PROGRESS=Pokaż postęp
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Incompleto
ALL_FINISHED=Completo
CAT_DOCUMENTS=Documentos
CAT_COMPRESSED=Compactos
CAT_MUSIC=Áudio
CAT_VIDEOS=Vídeo
CAT_PROGRAMS=Programas
MENU_BATCH_DOWNLOAD=Pacote de download
MENU_DELETE_DWN=Excluir download
MENU_DELETE_COMPLETED=Limpeza concluída
MENU_IMPORT=Importar
MENU_EXPORT=Exportar
MENU_EXIT=Fechar XDM
MENU_PAUSE=Pausar
MENU_RESUME=Continuar download
MENU_RESTART=Reiniciar download
MENU_REFRESH_LINK=Atualizar link
MENU_PROPERTIES=Propriedades
MENU_UPDATE=Procurar atualizações
MENU_ABOUT=Sobre o XDM...
SORT_DATE=Data
SORT_SIZE=Tamanho
SORT_NAME=Nome
ND_CANCEL=Fechar
MSG_OK=Certo
LBL_MENU=Menu
TITLE_SETTINGS=Configura\u00e7\u00f5es
SETTINGS_MONITORING=Monitorar navegador(es)
DESC_NEW=Novo
DESC_DEL=Exclu\u00edr
DESC_Q_TITLE=Fila e programa\u00e7\u00e3o
Q_SCHEDULE_TXT=Programar
Q_MOVE_TO=Mover para
CTX_OPEN_FILE=Abrir
CTX_OPEN_FOLDER=Abrir pasta
CTX_SAVE_AS=Salvar como
CTX_COPY_URL=Copiar URL
CTX_COPY_FILE=Copiar arquivo
MENU_IMPORT=Importar
MENU_EXPORT=Exportar
MENU_LANG=Idioma
MSG_LANG1=Selecionar idioma
MSG_LANG2=Vale constar que as mudan\u00e7as aparecer\u00e3o ao fechar e abrir o XDM.
LBL_REPORT_PROBLEM=Reportar um bug
LBL_SUPPORT_PAGE=P\u00e1gina de ajuda
LBL_SHOW_PROGRESS=Mostrar Progresso
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/Romanian.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Incomplete
ALL_FINISHED=Complete
CAT_DOCUMENTS=Documente
CAT_COMPRESSED=Comprimate
CAT_MUSIC=Musică
CAT_VIDEOS=Video
CAT_PROGRAMS=Programe
MENU_BATCH_DOWNLOAD=Descărcare în serie
MENU_DELETE_DWN=Șterge descărcări
MENU_DELETE_COMPLETED=Șterge complete
MENU_IMPORT=Importă
MENU_EXPORT=Exportă
MENU_EXIT=Ieși
MENU_PAUSE=Pauză
MENU_RESUME=Continuă
MENU_RESTART=Repornește
MENU_REFRESH_LINK=Actualizează link
MENU_PROPERTIES=Proprietăți
MENU_UPDATE=Caută actualizări
MENU_ABOUT=Despre XDM...
SORT_DATE=Data
SORT_SIZE=Dimensiune
SORT_NAME=Nume
ND_CANCEL=RENUNȚĂ
MSG_OK=OK
LBL_MENU=Meniu
TITLE_SETTINGS=Reglări
SETTINGS_MONITORING=Monitorizare Browser
DESC_NEW=Nou
DESC_DEL=Șterge
DESC_Q_TITLE=Coadă și planificator
Q_SCHEDULE_TXT=Planificator
Q_MOVE_TO=Mută în
CTX_OPEN_FILE=Deschide
CTX_OPEN_FOLDER=Deschide Dosar
CTX_SAVE_AS=Salvează ca
CTX_COPY_URL=Copiază URL
CTX_COPY_FILE=Copiază Fișier
MENU_IMPORT=Importă
MENU_EXPORT=Exportă
MENU_LANG=Limbă
MSG_LANG1=Selecționează limba
MSG_LANG2=Modificările vor avea efect data viitoare când porniți XDM
LBL_REPORT_PROBLEM=Raportează o eroare
LBL_SUPPORT_PAGE=Pagină de Suport
LBL_SHOW_PROGRESS=Afișează progres
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/Russian.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Незавершенные
ALL_FINISHED=Завершенные
CAT_DOCUMENTS=Документы
CAT_COMPRESSED=Сжатые
CAT_MUSIC=Музыка
CAT_VIDEOS=Видео
CAT_PROGRAMS=Программы
MENU_BATCH_DOWNLOAD=Пакетная загрузка
MENU_DELETE_DWN=Удалить загрузки
MENU_DELETE_COMPLETED=Очистить загруженные
MENU_IMPORT=Импорт
MENU_EXPORT=Экспорт
MENU_EXIT=Выход
MENU_PAUSE=Пауза
MENU_RESUME=Возобновить
MENU_RESTART=Перезапустить
MENU_REFRESH_LINK=Обновить ссылку
MENU_PROPERTIES=Свойства
MENU_UPDATE=Проверить обновления
MENU_ABOUT=Об XDM...
SORT_DATE=Дата
SORT_SIZE=Размер
SORT_NAME=Имя
ND_CANCEL=Отмена
MSG_OK=OK
LBL_MENU=Меню
TITLE_SETTINGS=Настройки
SETTINGS_MONITORING=Отслеживание в браузере
DESC_NEW=Новый
DESC_DEL=Удалить
DESC_Q_TITLE=Очередь и планировщик
Q_SCHEDULE_TXT=Планировщик
Q_MOVE_TO=Перейти к
CTX_OPEN_FILE=Открыть
CTX_OPEN_FOLDER=Открыть каталог
CTX_SAVE_AS=Сохранить как
CTX_COPY_URL=Копировать ссылку
CTX_COPY_FILE=Копировать файл
MENU_IMPORT=Импорт
MENU_EXPORT=Экспорт
MENU_LANG=Язык
MSG_LANG1=Выберите язык
MSG_LANG2=Пожалуйста, внесите изменения, которые будут применены при следующем запуске XDM
LBL_REPORT_PROBLEM=Сообщить об ошибке
LBL_SUPPORT_PAGE=Страница поддержки
LBL_SHOW_PROGRESS=Показать прогресс
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Nezavršeno
ALL_FINISHED=Završeno
CAT_DOCUMENTS=Dokumenta
CAT_COMPRESSED=Zapakovano
CAT_MUSIC=Muzika
CAT_VIDEOS=Video
CAT_PROGRAMS=Programi
MENU_BATCH_DOWNLOAD=Grupno preuzimanje
MENU_DELETE_DWN=Obriši preuzimanja
MENU_DELETE_COMPLETED=Obriši završena
MENU_IMPORT=Uvezi
MENU_EXPORT=Izvezi
MENU_EXIT=Izlaz
MENU_PAUSE=Pauziraj
MENU_RESUME=Nastavi
MENU_RESTART=Ponovo počni
MENU_REFRESH_LINK=Osveži adresu
MENU_PROPERTIES=Svojstva
MENU_UPDATE=Proveri nadogradnje
MENU_ABOUT=O programu XDM...
SORT_DATE=Datum
SORT_SIZE=Veličina
SORT_NAME=Naziv
ND_CANCEL=PONIŠTI
MSG_OK=U REDU
LBL_MENU=Meni
TITLE_SETTINGS=Postavke
SETTINGS_MONITORING=Nadgledanje pregledača
DESC_NEW=Novo
DESC_DEL=Obriši
DESC_Q_TITLE=Lista i zakazivanje
Q_SCHEDULE_TXT=Zakazivač
Q_MOVE_TO=Pomeri na
CTX_OPEN_FILE=Otvori
CTX_OPEN_FOLDER=Otvori lokaciju
CTX_SAVE_AS=Sačuvaj kao
CTX_COPY_URL=Kopiraj adresu
CTX_COPY_FILE=Kopiraj datoteku
MENU_IMPORT=Uvezi
MENU_EXPORT=Izvezi
MENU_LANG=Jezik
MSG_LANG1=Izaberi jezik
MSG_LANG2=Promene će biti vidljive nakon ponovnog pokretanja programa
LBL_REPORT_PROBLEM=Prijavi grešku
LBL_SUPPORT_PAGE=Stranica podrške
LBL_SHOW_PROGRESS=Prikaži napredak
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Незавршено
ALL_FINISHED=Завршено
CAT_DOCUMENTS=Документа
CAT_COMPRESSED=Запаковано
CAT_MUSIC=Музика
CAT_VIDEOS=Видео
CAT_PROGRAMS=Програми
MENU_BATCH_DOWNLOAD=Групно преузимање
MENU_DELETE_DWN=Обриши преузимања
MENU_DELETE_COMPLETED=Обриши завршена
MENU_IMPORT=Увези
MENU_EXPORT=Извези
MENU_EXIT=Излаз
MENU_PAUSE=Паузирај
MENU_RESUME=Настави
MENU_RESTART=Поново почни
MENU_REFRESH_LINK=Освежи адресу
MENU_PROPERTIES=Својства
MENU_UPDATE=Провери надоградње
MENU_ABOUT=О програму XDM...
SORT_DATE=Датум
SORT_SIZE=Величина
SORT_NAME=Назив
ND_CANCEL=ПОНИШТИ
MSG_OK=У РЕДУ
LBL_MENU=Мени
TITLE_SETTINGS=Поставке
SETTINGS_MONITORING=Надгледање прегледача
DESC_NEW=Ново
DESC_DEL=Обриши
DESC_Q_TITLE=Листа и заказивање
Q_SCHEDULE_TXT=Заказивач
Q_MOVE_TO=Помери на
CTX_OPEN_FILE=Отвори
CTX_OPEN_FOLDER=Отвори локацију
CTX_SAVE_AS=Сачувај као
CTX_COPY_URL=Копирај адресу
CTX_COPY_FILE=Копирај датотеку
MENU_IMPORT=Увези
MENU_EXPORT=Извези
MENU_LANG=Језик
MSG_LANG1=Изабери језик
MSG_LANG2=Промене ће бити видљиве након поновног покретања програма
LBL_REPORT_PROBLEM=Пријави грешку
LBL_SUPPORT_PAGE=Страница подршке
LBL_SHOW_PROGRESS=Прикажи напредак
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/Spanish.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Incompleto
ALL_FINISHED=Completado
CAT_DOCUMENTS=Documentos
CAT_COMPRESSED=Comprimido
CAT_MUSIC=Música
CAT_VIDEOS=Vídeo
CAT_PROGRAMS=Programas
MENU_BATCH_DOWNLOAD=Descarga por lotes
MENU_DELETE_DWN=Eliminar descargas
MENU_DELETE_COMPLETED=Limpiar finalizados
MENU_IMPORT=Importar
MENU_EXPORT=Exportar
MENU_EXIT=Salir
MENU_PAUSE=Pausa
MENU_RESUME=Reanudar
MENU_RESTART=Reiniciar
MENU_REFRESH_LINK=Actualizar enlace
MENU_PROPERTIES=Propiedades
MENU_UPDATE=Buscar actualizaciones
MENU_ABOUT=Sobre XDM ...
SORT_DATE=Fecha
SORT_SIZE=Tamaño
SORT_NAME=Nombre
ND_CANCEL=CANCELAR
MSG_OK=Ok
LBL_MENU=Menú
TITLE_SETTINGS=Opciones
SETTINGS_MONITORING=Monitorización del navegador
DESC_NEW=Nuevo
DESC_DEL=Borrar
DESC_Q_TITLE=Colas y planificador
Q_SCHEDULE_TXT=Planificador
Q_MOVE_TO=Mover a
CTX_OPEN_FILE=Abrir
CTX_OPEN_FOLDER=Abrir carpeta
CTX_SAVE_AS=Guardar como
CTX_COPY_URL=Copiar URL
CTX_COPY_FILE=Copiar archivo
MENU_IMPORT=Importar
MENU_EXPORT=Exportar
MENU_LANG=Idioma
MSG_LANG1=Seleccione el idioma
MSG_LANG2=Por favor, tenga en cuenta que los cambios tendrán efecto la próxima vez que inicie XDM
LBL_REPORT_PROBLEM=Reportar un error
LBL_SUPPORT_PAGE=Pagina de soporte
LBL_SHOW_PROGRESS=Mostrar progreso
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,27 @@
ALL_UNFINISHED=正在下載
ALL_FINISHED=已完成
CAT_DOCUMENTS=檔案
CAT_COMPRESSED=壓縮檔
CAT_MUSIC=音樂
CAT_VIDEOS=影片
CAT_PROGRAMS=程式
MENU_BATCH_DOWNLOAD=批次下載
MENU_DELETE_DWN=删除此下載
MENU_DELETE_COMPLETED=清除已完成
MENU_IMPORT=匯入
MENU_EXPORT=匯出
MENU_EXIT=離開
MENU_PAUSE=暫停
MENU_RESUME=繼續
MENU_RESTART=重新開始
MENU_REFRESH_LINK=更新網址
MENU_PROPERTIES=內容
MENU_UPDATE=檢查更新
MENU_ABOUT=關於 XDM...
SORT_DATE=依照時間
SORT_SIZE=依照大小
SORT_NAME=依照名稱
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

50
app/XDM/Lang/Turkish.txt Normal file
View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Bitmeyenler
ALL_FINISHED=Bitenler
CAT_DOCUMENTS=Belgeler
CAT_COMPRESSED=Sıkıştırılmış
CAT_MUSIC=Müzikler
CAT_VIDEOS=Videolar
CAT_PROGRAMS=Programlar
MENU_BATCH_DOWNLOAD=Toplu indirme
MENU_DELETE_DWN=İndirmeleri sil
MENU_DELETE_COMPLETED=Bitenleri temizle
MENU_IMPORT=İçe aktar
MENU_EXPORT=Dışa aktar
MENU_EXIT=Çık
MENU_PAUSE=Duraklat
MENU_RESUME=Sürdür
MENU_RESTART=Yeniden başlat
MENU_REFRESH_LINK=Bağlantıyı yenile
MENU_PROPERTIES=Özellikler
MENU_UPDATE=Güncellemeleri kontrol et
MENU_ABOUT=XDM hakkında...
SORT_DATE=Tarih
SORT_SIZE=Boyut
SORT_NAME=Ad
ND_CANCEL=İPTAL ET
MSG_OK=Tamam
LBL_MENU=Menü
TITLE_SETTINGS=Ayarlar
SETTINGS_MONITORING=Tarayıcı izleme
DESC_NEW=Yeni
DESC_DEL=Sil
DESC_Q_TITLE=Kuyruk ve zamanlayıcı
Q_SCHEDULE_TXT=Zamanlayıcı
Q_MOVE_TO=Şuraya taşı:
CTX_OPEN_FILE=Aç
CTX_OPEN_FOLDER=Klasörü aç
CTX_SAVE_AS=Farklı Kaydet
CTX_COPY_URL=URL Kopyala
CTX_COPY_FILE=Dosya Kopyala
MENU_IMPORT=İçe aktar
MENU_EXPORT=Dışa aktar
MENU_LANG=Diller
MSG_LANG1=Dil seçin
MSG_LANG2=Lütfen, değişikliklerin XDM'yi bir sonraki başlatışınızda geçerli olacağını unutmayın.
LBL_REPORT_PROBLEM=Hata bildir
LBL_SUPPORT_PAGE=Destek sayfası
LBL_SHOW_PROGRESS=İlerlemeyi göster
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,50 @@
ALL_UNFINISHED=Незавершені
ALL_FINISHED=Завершені
CAT_DOCUMENTS=Документи
CAT_COMPRESSED=Стиснуті
CAT_MUSIC=Музика
CAT_VIDEOS=Відео
CAT_PROGRAMS=Програми
MENU_BATCH_DOWNLOAD=Пакетне завантаження
MENU_DELETE_DWN=Видалити завантаження
MENU_DELETE_COMPLETED=Очистити завершені
MENU_IMPORT=Імпорт
MENU_EXPORT=Експорт
MENU_EXIT=Вихід
MENU_PAUSE=Пауза
MENU_RESUME=Відновити
MENU_RESTART=Перезавантажити
MENU_REFRESH_LINK=Оновити посилання
MENU_PROPERTIES=Властивості
MENU_UPDATE=Перевірити оновлення
MENU_ABOUT=Про XDM...
SORT_DATE=Дата
SORT_SIZE=Розмір
SORT_NAME=Ім'я
ND_CANCEL=СКАСУВАТИ
MSG_OK=Добре
LBL_MENU=Меню
TITLE_SETTINGS=Налаштування
SETTINGS_MONITORING=Відстеження браузера
DESC_NEW=Новий
DESC_DEL=Видалити
DESC_Q_TITLE=Черга і планувальник
Q_SCHEDULE_TXT=Планувальник
Q_MOVE_TO=Перемістити до
CTX_OPEN_FILE=Відкрити
CTX_OPEN_FOLDER=Відкрити теку
CTX_SAVE_AS=Зберегти як
CTX_COPY_URL=Копіювати адресу URL
CTX_COPY_FILE=Копіювати файл
MENU_IMPORT=Імпорт
MENU_EXPORT=Експорт
MENU_LANG=Мова
MSG_LANG1=Обрати мову
MSG_LANG2=Зверніть увагу, що зміни наберуть чинності наступного разу, коли ви запустите XDM
LBL_REPORT_PROBLEM=Повідомити про помилку
LBL_SUPPORT_PAGE=Сторінка підтримки
LBL_SHOW_PROGRESS=Показати хід виконання
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

View File

@ -0,0 +1,49 @@
ALL_UNFINISHED=Đang Tải Về
ALL_FINISHED=Đã Xong
CAT_DOCUMENTS=Tài Liệu
CAT_COMPRESSED=Tập Tin Nén
CAT_MUSIC=Nhạc
CAT_VIDEOS=Video
CAT_PROGRAMS=Chương Trình
MENU_BATCH_DOWNLOAD=Batch download
MENU_DELETE_DWN=Xóa
MENU_DELETE_COMPLETED=Xóa Các Tệp Tải Xong
MENU_IMPORT=Nhập
MENU_EXPORT=Xuất
MENU_EXIT=Thoát
MENU_PAUSE=Tạm Dừng
MENU_RESUME=Tiếp Tục Tải Về
MENU_RESTART=Khởi Động Lại
MENU_REFRESH_LINK=Làm Mới Liên Kết
MENU_PROPERTIES=Thuộc Tính
MENU_UPDATE=Kiểm Tra Cập Nhật
MENU_ABOUT=Về XDM...
SORT_DATE=Ngày
SORT_SIZE=Kích Cỡ
ND_CANCEL=HỦY BỎ
MSG_OK=OK
LBL_MENU=Menu
TITLE_SETTINGS=Cài Đặt
SETTINGS_MONITORING=Bắt Link Tự Động
DESC_NEW=Mới
DESC_DEL=Xóa
DESC_Q_TITLE=Queue and scheduler
Q_SCHEDULE_TXT=Kế hoạch
Q_MOVE_TO=Di chuyển tới
CTX_OPEN_FILE=Mở
CTX_OPEN_FOLDER=Mở Thư Mục
CTX_SAVE_AS=Lưu Như
CTX_COPY_URL=Sao Chép Liên Kết
CTX_COPY_FILE=Sao Chép Tập Tin
MENU_IMPORT=Nhập
MENU_EXPORT=Xuất
MENU_LANG=Ngôn Ngữ
MSG_LANG1=Lựa Chọn Ngôn Ngữ
MSG_LANG2=Vui lòng khởi động lại XDM để thay đổi có hiệu lực
LBL_REPORT_PROBLEM=Báo Lỗi
LBL_SUPPORT_PAGE=Trang Hỗ Trợ
LBL_SHOW_PROGRESS=Hiển thị quá trình tải về
SORT_STATUS=Status
LBL_SEARCH=Search
LBL_VIDEO_DOWNLOAD=Video download
LBL_NEW_DOWNLOAD=New download

25
app/XDM/Lang/index.txt Normal file
View File

@ -0,0 +1,25 @@
English=English.txt
Arabic (العربية)=Arabic.txt
Chinese simplified (简体中文)=Chinese simplified.txt
Chinese Traditional (繁體中文)=Chinese Traditional.txt
Czech=Czech.txt
Farsi - Persian (فارسی)=Farsi-Persian.txt
French(Français)=French.txt
German(Deutsch)=German.txt
Hungarian=Hungarian.txt
Indonesian (Bahasa Indonesia)=Indonesian.txt
Italian=Italian.txt
Korea (한국어)=Korea.txt
Malayalam=Malayalam.txt
Nepali=Nepali.txt
Polish=Polish.txt
Portuguese Brazil (Português (Brasil))=Portuguese Brazil.txt
Romanian (ROMÂNĂ)=Romanian.txt
Russian(Rусский)=Russian.txt
Serbian - Latin (Srpski (latinica))=Serbian - Latin.txt
Serbian Cyrillic (Српски (ћирилица))=Serbian Cyrillic.txt
Spanish (Español)=Spanish.txt
Traditional Chinese - Taiwan (繁體中文(台灣))=Traditional Chinese - Taiwan.txt
Turkish(Türkçe)=Turkish.txt
Ukrainian(Українська)=Ukrainian.txt
Vietnamese=Vietnamese.txt

View File

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MockServer
{
public class MockServer: IDisposable
{
private Dictionary<string, string> fileMap = new Dictionary<string, string>();
private Dictionary<string, Dictionary<string, string>> headerMap = new Dictionary<string, Dictionary<string, string>>();
private Dictionary<string, string> fileHashMap = new Dictionary<string, string>();
public readonly CancellationTokenSource CancellationToken = new CancellationTokenSource();
private HttpListener listener;
private long stopAfterBytes = -1;
private long bytesServed = 0;
public bool NonResumable { get; set; }
public bool HasContentLength { get; set; } = true;
private bool closed = false;
public string BaseUrl => "http://127.0.0.1:49000/";
public string GetHash(string id)
{
return fileHashMap["/" + id];
}
public void StartAsync()
{
listener = new HttpListener();
listener.Prefixes.Add(BaseUrl);
listener.Start();
Task.Factory.StartNew(Start);
}
public void Stop()
{
if (this.closed)
{
return;
}
this.closed = true;
this.CancellationToken.Cancel();
this.listener.Stop();
foreach (var key in fileMap.Keys)
{
var file = fileMap[key];
try
{
File.Delete(file);
}
catch { }
}
}
public void Start()
{
while (!this.CancellationToken.IsCancellationRequested)
{
HttpListenerContext context = listener.GetContext();
if (fileMap.ContainsKey(context.Request.RawUrl))
{
new RequestHandler
{
FilePath = fileMap[context.Request.RawUrl],
Hash = fileHashMap[context.Request.RawUrl],
Headers = headerMap.GetValueOrDefault(context.Request.RawUrl, null),
CancellationToken = CancellationToken.Token,
NonResumable = this.NonResumable,
HasContentLength = this.HasContentLength,
BytesAction =
x =>
{
Interlocked.Add(ref bytesServed, x);
if (bytesServed >= stopAfterBytes && stopAfterBytes > 0)
{
this.listener.Stop();
}
}
}.HandleAsync(context.Request, context.Response);
}
}
}
public (string File, string Hash, long Size) AddMockHandler(string id, long stopAfterPercent = -1, Dictionary<string, string> headers = null, int start = 50, int end = 100, byte[] contents = null, long fixedSize = -1)
{
long size;
long sz = 0;
string tempFile = Path.Combine(Path.GetTempPath(), id);
if (contents == null)
{
Random random = new Random();
size = fixedSize > 0 ? fixedSize : random.Next(start, end) * 1024 * 1024;
sz = size;
int bufSize = 128 * 1024;
byte[] b = new byte[bufSize];
if (stopAfterPercent >= 0)
{
stopAfterBytes = size * stopAfterPercent / 100;
}
using (FileStream fs = new FileStream(tempFile, FileMode.Create))
{
while (size > 0)
{
int rem = (int)Math.Min(bufSize, size);
random.NextBytes(b);
fs.Write(b, 0, rem);
size -= rem;
}
}
}
else
{
File.WriteAllBytes(tempFile, contents);
size = contents.Length;
}
var hash = "";
using (SHA256 sha256Hash = SHA256.Create())
{
using (FileStream fs = new FileStream(tempFile, FileMode.Open))
{
byte[] buf = new byte[8192];
while (true)
{
int x = fs.Read(buf, 0, buf.Length);
if (x == 0) break;
sha256Hash.TransformBlock(buf, 0, x, null, 0);
}
sha256Hash.TransformFinalBlock(new byte[0] { }, 0, 0);
}
byte[] bytes = sha256Hash.Hash;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
builder.Append(bytes[i].ToString("x2"));
}
this.fileHashMap.Add("/" + id, builder.ToString());
hash = builder.ToString();
}
this.fileMap.Add("/" + id, tempFile);
if (headers != null)
{
this.headerMap.Add("/" + id, headers);
}
return (File: tempFile, Hash: hash, Size: sz);
}
public void Dispose()
{
Stop();
}
}
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net5.0;</TargetFrameworks>
<Platforms>AnyCPU;x86;x64</Platforms>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net5.0|x86'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace MockServer
{
public class RequestHandler
{
private Regex rx = new Regex(@"bytes=(\d+)-(\d*)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public string FilePath { get; set; }
public string Hash { get; set; }
private byte[] buffer = new byte[8192];
public CancellationToken CancellationToken { get; set; }
public Action<long> BytesAction;
public bool NonResumable { get; set; }
public Dictionary<string, string> Headers;
public bool HasContentLength { get; set; } = true;
public void HandleAsync(HttpListenerRequest request, HttpListenerResponse response)
{
Task.Factory.StartNew(() =>
{
HandleRequest(request, response);
});
}
private void HandleRequest(HttpListenerRequest request, HttpListenerResponse response)
{
var r = new Random();
var match = rx.Match(request.Headers.Get("Range") ?? "");
if (!match.Success || !HasContentLength || NonResumable)
{
NonResumable = true;
}
response.StatusCode = NonResumable ? 200 : 206;
if (Headers != null)
{
foreach (var header in Headers)
{
response.Headers.Add(header.Key, header.Value);
}
}
using Stream fs = new BufferedStream(new FileStream(FilePath, FileMode.Open, FileAccess.Read,
FileShare.ReadWrite));
long rem;
if (NonResumable)
{
if (HasContentLength)
{
response.ContentLength64 = fs.Length;
}
else
{
response.SendChunked = true;
}
rem = fs.Length;
}
else
{
//Thread.Sleep(r.Next(10, 50));
long startRange = long.Parse(match.Groups[1].ToString());
var endRangeStr = match.Groups[2].ToString();
long endRange = endRangeStr.Length > 0 ? long.Parse(endRangeStr) : fs.Length - 1;
response.Headers.Add("X-start", match.Groups[1].ToString());
if (match.Groups.Count > 2)
response.Headers.Add("X-End", match.Groups[2].ToString());
response.Headers.Add("Content-Range", "bytes " + startRange + "-" + endRange + "/" + fs.Length);
response.ContentLength64 = endRange - startRange + 1;
fs.Seek(startRange, SeekOrigin.Begin);
rem = endRange - startRange + 1;
}
while (rem > 0 && !CancellationToken.IsCancellationRequested)
{
try
{
Thread.Sleep(r.Next(1, NonResumable ? 2 : 30));
long k = rem > buffer.Length ? buffer.Length : rem;
int x = fs.Read(buffer, 0, (int)k);
response.OutputStream.Write(buffer, 0, (int)k);
BytesAction(x);
rem -= k;
}
catch
{
break;
}
}
response.OutputStream.Flush();
response.OutputStream.Close();
response.Close();
}
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
<supportedRuntime version="v2.0.50727" />
</startup>
</configuration>

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>

View File

@ -0,0 +1,39 @@
#if NET35
using System.Collections.Generic;
using System.Threading;
namespace NetFX.Polyfill2
{
public class BlockingCollection<T>
{
private object _queueLock = new();
private Queue<T> _queue = new();
private AutoResetEvent _objectAvailableEvent = new(false);
public T Take()
{
lock (_queueLock)
{
if (_queue.Count > 0)
return _queue.Dequeue();
}
_objectAvailableEvent.WaitOne();
return Take();
}
public void Add(T obj)
{
lock (_queueLock)
{
_queue.Enqueue(obj);
}
_objectAvailableEvent.Set();
}
}
}
#endif

View File

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net3.5;net4.7.2;net6.0</TargetFrameworks>
<InvariantGlobalization>true</InvariantGlobalization>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<Platforms>x86;x64</Platforms>
<AssemblyName>xdm-messaging-host</AssemblyName>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>Link</TrimMode>
<!--<Platforms>AnyCPU;x86</Platforms>-->
<!--<RuntimeIdentifiers>win-x86;linux-x64</RuntimeIdentifiers>-->
</PropertyGroup>
<!--<ItemGroup Condition=" '$(TargetFramework)' != 'net5.0' or '$(TargetFramework)' != 'net6.0' ">
<ProjectReference Include="..\XDM.Compatibility\XDM.Compatibility.csproj" />
</ItemGroup>-->
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net4.7.2' or '$(TargetFramework)' == 'net4.5' ">
<AppConfig>App.PostDotNet4.config</AppConfig>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net3.5' ">
<AppConfig>App.PreDotNet4.config</AppConfig>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net4.7.2|x86'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<Import Project="..\XDM.Messaging\XDM.Messaging.projitems" Label="Shared" />
</Project>

View File

@ -0,0 +1,304 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Diagnostics;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using XDM.Core.BrowserMonitoring;
#if NET35
using NetFX.Polyfill2;
#else
using System.Collections.Concurrent;
#endif
namespace NativeHost
{
public class NativeMessagingHostApp
{
static bool isFirefox = true;
static BlockingCollection<byte[]> receivedBrowserMessages = new();
static BlockingCollection<byte[]> queuedBrowserMessages = new();
static CamelCasePropertyNamesContractResolver cr = new();
static void Main(string[] args)
{
try
{
var debugMode = Environment.GetEnvironmentVariable("XDM_DEBUG_MODE");
if (!string.IsNullOrEmpty(debugMode) && debugMode == "1")
{
var logFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "messaging-log.txt");
Trace.Listeners.Add(new TextWriterTraceListener(logFile, "myListener"));
Trace.AutoFlush = true;
}
Debug("Application_Startup");
if (args.Length > 0 && args[0].StartsWith("chrome-extension:"))
{
isFirefox = false;
}
}
catch { }
Debug("Process running from: " + AppDomain.CurrentDomain.BaseDirectory);
#if !NET35
Debug("Is64BitProcess: " + Environment.Is64BitProcess);
#endif
try
{
var inputReader = Console.OpenStandardInput();
var outputWriter = Console.OpenStandardOutput();
try
{
Debug("Trying to open mutex");
using var mutex = Mutex.OpenExisting(@"Global\XDM_Active_Instance");
Debug("Mutex opened");
}
catch
{
Debug("Mutex open failed, spawn xdm process...++");
CreateXDMInstance();
}
var t1 = new Thread(() =>
{
Debug("t1 reading messages from stdin sent by browser: ");
try
{
while (true)
{
//read from process stdin and write to blocking queue,
//they will be sent to xdm once pipe handshake complets
Debug("Waiting for message - stdin...");
var msg = NativeMessageSerializer.ReadMessageBytes(inputReader, false);
Debug("Reading message from stdin - size: " + msg.Length);
Debug("Stdin - " + Encoding.UTF8.GetString(msg));
receivedBrowserMessages.Add(JsonToBinary(msg));
}
}
catch (Exception exx)
{
Debug(exx.ToString());
Environment.Exit(1);
}
});
t1.Start();
var t2 = new Thread(() =>
{
try
{
while (true)
{
//read from blocking queue and write to stdout,
//these messages were queued by xdm
var msg = queuedBrowserMessages.Take();
Debug("Sending to browser: " + Encoding.UTF8.GetString(msg));
var json = BinaryToJson(msg);
Debug("Sending to browser: ");
Debug(Encoding.UTF8.GetString(json));
NativeMessageSerializer.WriteMessage(outputWriter, json, false);
}
}
catch (Exception exx)
{
Debug(exx.ToString());
Environment.Exit(1);
}
});
t2.Start();
Debug("Start message proccessing...");
ProcessMessages();
Debug("Finished message proccessing.");
}
catch (Exception ex)
{
Debug(ex.ToString());
}
}
private static void CreateXDMInstance(bool minimized = true)
{
try
{
var file = Path.Combine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ".."),
Environment.OSVersion.Platform == PlatformID.Win32NT ? "xdm-app.exe" : "xdm-app");
Debug("XDM instance creating...1 " + file);
if (isFirefox && Environment.OSVersion.Platform == PlatformID.Win32NT)
{
var args = minimized ? " -m" : "";
if (!NativeProcess.Win32CreateProcess(file, $"\"{file}\"{args}"))
{
Debug("Win32 create process failed!");
}
}
else
{
ProcessStartInfo psi = new()
{
FileName = file,
UseShellExecute = true
};
if (minimized)
{
psi.Arguments = "-m";
}
Debug("XDM instance creating...");
Process.Start(psi);
Debug("XDM instance created");
}
}
catch (Exception ex)
{
Debug(ex.ToString());
}
}
private static void ProcessMessages()
{
Debug("start");
try
{
NamedPipeClientStream? pipe = null;
{
try
{
//start handshake with XDM
pipe = new NamedPipeClientStream(".", "XDM_Ipc_Browser_Monitoring_Pipe", PipeDirection.InOut, PipeOptions.Asynchronous);
Debug("start handshake with XDM");
pipe.Connect();
//handshake with XDM is complete
Debug("handshake with XDM is complete");
using var waitHandle = new ManualResetEvent(false);
//Direction: XDM ---> NativeHost
//Read messages from XDM's named pipe and add them to queuedBrowserMessages
var task1 = new Thread(() =>
{
try
{
while (true)
{
var syncMsgBytes = NativeMessageSerializer.ReadMessageBytes(pipe);
Debug("Message received from XDM of size: " + syncMsgBytes.Length);
if (syncMsgBytes.Length == 0)
{
break;
}
Debug("Message from XDM: " + Encoding.UTF8.GetString(syncMsgBytes));
queuedBrowserMessages.Add(syncMsgBytes);
}
}
catch (Exception ex)
{
Debug(ex.ToString(), ex);
queuedBrowserMessages.Add(Encoding.UTF8.GetBytes("{\"appExited\":\"true\"}"));
}
waitHandle.Set();
Debug("Task1 finished");
}
);
//Direction: NativeHost ---> XDM
//Take messages from receivedBrowserMessages and write them to XDM's named pipe
var task2 = new Thread(() =>
{
try
{
while (true)
{
Debug("Task2 reading messages queued by browser...");
byte[] syncMsgBytes = receivedBrowserMessages.Take();
if (syncMsgBytes.Length == 2 && (char)syncMsgBytes[0] == '{' && (char)syncMsgBytes[1] == '}')
{
Debug("Task2 empty object received: " + syncMsgBytes.Length + " " + Encoding.UTF8.GetString(syncMsgBytes));
throw new OperationCanceledException("Empty object");
}
Debug("Sending message to XDM...");
NativeMessageSerializer.WriteMessage(pipe, syncMsgBytes);
Debug("Sent message to XDM");
}
}
catch (Exception ex)
{
Debug(ex.ToString(), ex);
}
waitHandle.Set();
Debug("Task2 finished");
}
);
task1.Start();
task2.Start();
waitHandle.WaitOne();
Debug("Any one task finished");
}
catch (Exception ex)
{
Debug(ex.ToString(), ex);
}
try
{
pipe?.Close();
}
catch { }
try
{
pipe?.Dispose();
}
catch { }
}
}
catch (Exception exxxx)
{
Debug(exxxx.ToString());
}
}
private static void Debug(string msg, Exception? ex2 = null)
{
Trace.WriteLine($"[xdm-native-messaging-host {DateTime.Now}] {msg}");
if (ex2 != null)
{
Trace.WriteLine($"[xdm-native-messaging-host {DateTime.Now}] {ex2}");
}
}
private static byte[] JsonToBinary(byte[] input)
{
var envelop = JsonConvert.DeserializeObject<RawBrowserMessageEnvelop>(Encoding.UTF8.GetString(input),
new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Ignore
}
);
using var ms = new MemoryStream();
using var w = new BinaryWriter(ms);
envelop.Serialize(w);
w.Close();
ms.Close();
return ms.ToArray();
}
private static byte[] BinaryToJson(byte[] input)
{
var msg = SyncMessage.Deserialize(input);
var json = JsonConvert.SerializeObject(msg, Formatting.Indented, new JsonSerializerSettings
{
ContractResolver = cr
});
return Encoding.UTF8.GetBytes(json);
}
}
}

View File

@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace NativeHost
{
internal static class NativeProcess
{
internal static bool Win32CreateProcess(string? file, string? args)
{
const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
const uint CREATE_BREAKAWAY_FROM_JOB = 0x01000000;
const uint CREATE_NEW_PROCESS_GROUP = 0x00000200;
PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION();
STARTUPINFO sInfo = new STARTUPINFO();
SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES tSec = new SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
tSec.nLength = Marshal.SizeOf(tSec);
return CreateProcess(file, args,
ref pSec, ref tSec, false, CREATE_UNICODE_ENVIRONMENT | CREATE_BREAKAWAY_FROM_JOB | CREATE_NEW_PROCESS_GROUP,
IntPtr.Zero, null, ref sInfo, out pInfo);
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern bool CreateProcess(
string? lpApplicationName,
string? lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string? lpCurrentDirectory,
[In] ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
// This also works with CharSet.Ansi as long as the calling function uses the same character set.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
}

View File

@ -0,0 +1 @@
Language files are stored in $(SOLUTION)\Lang directory

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Translations
{
public static class TextResource
{
private static Dictionary<string, string> texts = new();
static TextResource()
{
Load("English.txt");
}
public static void Load(string language)
{
var file = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.Combine("Lang", $"{language}"));
if (File.Exists(file))
{
LoadTexts(file);
}
}
public static string GetText(string key)
{
if (texts.TryGetValue(key, out string? label) && label != null)
{
return label;
}
return string.Empty;
}
private static void LoadTexts(string path)
{
var lines = File.ReadAllLines(path);
foreach (var line in lines)
{
if (string.IsNullOrEmpty(line))
{
continue;
}
var index = line.IndexOf('=');
var key = line.Substring(0, index);
var val = line.Substring(index + 1);
texts[key] = val;
}
}
public static IEnumerable<string> GetKeys()
{
return texts.Keys;
}
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net3.5;net4.5;net4.7.2;net5.0</TargetFrameworks>
<Platforms>AnyCPU;x86;x64</Platforms>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net4.7.2|x86'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,39 @@
#if NET35
using System.Collections.Generic;
using System.Threading;
namespace XDM.Compatibility
{
public class BlockingCollection<T>
{
private object _queueLock = new();
private Queue<T> _queue = new();
private AutoResetEvent _objectAvailableEvent = new(false);
public T Take()
{
lock (_queueLock)
{
if (_queue.Count > 0)
return _queue.Dequeue();
}
_objectAvailableEvent.WaitOne();
return Take();
}
public void Add(T obj)
{
lock (_queueLock)
{
_queue.Enqueue(obj);
}
_objectAvailableEvent.Set();
}
}
}
#endif

View File

@ -0,0 +1,27 @@
#if !NET5_0_OR_GREATER
using System.Collections.Generic;
namespace XDM.Compatibility
{
public static class DictionaryExtensions
{
public static V GetValueOrDefault<K, V>(this Dictionary<K, V> dict, K key, V defaultValue)
{
if (dict.TryGetValue(key, out V value))
{
return value;
}
return defaultValue;
}
public static V GetValueOrDefault<K, V>(this Dictionary<K, V> dict, K key)
{
#pragma warning disable CS8604 // Possible null reference argument.
return dict.GetValueOrDefault<K, V>(key, default);
#pragma warning restore CS8604 // Possible null reference argument.
}
}
}
#endif

View File

@ -0,0 +1,16 @@
#if !NET5_0_OR_GREATER
using System.Text.RegularExpressions;
namespace XDM.Compatibility
{
public static class GroupCollectionExtension
{
public static bool ContainsKey(this GroupCollection collection, string key)
{
return collection[key].Success;
}
}
}
#endif

View File

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace XDM.Compatibility
{
public static class ProcessStartInfoHelper
{
public static string ArgumentListToArgsString(IList<string> arguments)
{
var stringBuilder = new StringBuilder();
foreach (var argument in arguments)
{
AppendArgument(stringBuilder, argument);
}
return stringBuilder.ToString();
}
public static void AppendArgument(StringBuilder stringBuilder, string argument)
{
if (stringBuilder.Length != 0)
{
stringBuilder.Append(' ');
}
// Parsing rules for non-argv[0] arguments:
// - Backslash is a normal character except followed by a quote.
// - 2N backslashes followed by a quote ==> N literal backslashes followed by unescaped quote
// - 2N+1 backslashes followed by a quote ==> N literal backslashes followed by a literal quote
// - Parsing stops at first whitespace outside of quoted region.
// - (post 2008 rule): A closing quote followed by another quote ==> literal quote, and parsing remains in quoting mode.
if (argument.Length != 0 && ContainsNoWhitespaceOrQuotes(argument))
{
// Simple case - no quoting or changes needed.
stringBuilder.Append(argument);
}
else
{
stringBuilder.Append(Quote);
int idx = 0;
while (idx < argument.Length)
{
char c = argument[idx++];
if (c == Backslash)
{
int numBackSlash = 1;
while (idx < argument.Length && argument[idx] == Backslash)
{
idx++;
numBackSlash++;
}
if (idx == argument.Length)
{
// We'll emit an end quote after this so must double the number of backslashes.
stringBuilder.Append(Backslash, numBackSlash * 2);
}
else if (argument[idx] == Quote)
{
// Backslashes will be followed by a quote. Must double the number of backslashes.
stringBuilder.Append(Backslash, numBackSlash * 2 + 1);
stringBuilder.Append(Quote);
idx++;
}
else
{
// Backslash will not be followed by a quote, so emit as normal characters.
stringBuilder.Append(Backslash, numBackSlash);
}
continue;
}
if (c == Quote)
{
// Escape the quote so it appears as a literal. This also guarantees that we won't end up generating a closing quote followed
// by another quote (which parses differently pre-2008 vs. post-2008.)
stringBuilder.Append(Backslash);
stringBuilder.Append(Quote);
continue;
}
stringBuilder.Append(c);
}
stringBuilder.Append(Quote);
}
}
private static bool ContainsNoWhitespaceOrQuotes(string s)
{
for (int i = 0; i < s.Length; i++)
{
char c = s[i];
if (char.IsWhiteSpace(c) || c == Quote)
{
return false;
}
}
return true;
}
private const char Quote = '\"';
private const char Backslash = '\\';
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace XDM.Compatibility
{
public static class StreamExtension
{
public static void CopyTo(this Stream stream, Stream destination)
{
#if NET35
var buffer = new byte[8192];
#else
var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(8192);
#endif
try
{
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) != 0)
destination.Write(buffer, 0, read);
}
finally
{
#if !NET35
System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
#endif
}
}
}
}

View File

@ -0,0 +1,62 @@
//https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TupleElementNamesAttribute.cs
#if NET35
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
namespace System.Runtime.CompilerServices
{
/// <summary>
/// Indicates that the use of <see cref="System.ValueTuple"/> on a member is meant to be treated as a tuple with element names.
/// </summary>
//[CLSCompliant(false)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Event)]
public sealed class TupleElementNamesAttribute : Attribute
{
private readonly string?[] _transformNames;
/// <summary>
/// Initializes a new instance of the <see
/// cref="TupleElementNamesAttribute"/> class.
/// </summary>
/// <param name="transformNames">
/// Specifies, in a pre-order depth-first traversal of a type's
/// construction, which <see cref="System.ValueType"/> occurrences are
/// meant to carry element names.
/// </param>
/// <remarks>
/// This constructor is meant to be used on types that contain an
/// instantiation of <see cref="System.ValueType"/> that contains
/// element names. For instance, if <c>C</c> is a generic type with
/// two type parameters, then a use of the constructed type <c>C{<see
/// cref="System.ValueTuple{T1, T2}"/>, <see
/// cref="System.ValueTuple{T1, T2, T3}"/></c> might be intended to
/// treat the first type argument as a tuple with element names and the
/// second as a tuple without element names. In which case, the
/// appropriate attribute specification should use a
/// <c>transformNames</c> value of <c>{ "name1", "name2", null, null,
/// null }</c>.
/// </remarks>
public TupleElementNamesAttribute(string?[] transformNames)
{
if (transformNames == null)
{
throw new ArgumentNullException(nameof(transformNames));
}
_transformNames = transformNames;
}
/// <summary>
/// Specifies, in a pre-order depth-first traversal of a type's
/// construction, which <see cref="System.ValueTuple"/> elements are
/// meant to carry element names.
/// </summary>
public IList<string?> TransformNames => _transformNames;
}
}
#endif

View File

@ -0,0 +1,66 @@
#if NET35
namespace System
{
public struct ValueTuple<T1, T2>
{
public T1 Item1;
public T2 Item2;
public ValueTuple(T1 t1, T2 t2)
{
this.Item1 = t1;
this.Item2 = t2;
}
}
public struct ValueTuple<T1, T2, T3>
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public ValueTuple(T1 t1, T2 t2, T3 t3)
{
this.Item1 = t1;
this.Item2 = t2;
this.Item3 = t3;
}
}
public struct ValueTuple<T1, T2, T3, T4>
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public T4 Item4;
public ValueTuple(T1 t1, T2 t2, T3 t3, T4 t4)
{
this.Item1 = t1;
this.Item2 = t2;
this.Item3 = t3;
this.Item4 = t4;
}
}
public struct ValueTuple<T1, T2, T3, T4, T5>
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public T4 Item4;
public T5 Item5;
public ValueTuple(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
{
this.Item1 = t1;
this.Item2 = t2;
this.Item3 = t3;
this.Item4 = t4;
this.Item5 = t5;
}
}
}
#endif

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>6bffa6bc-bf01-450e-96b7-9a1d3dcc2333</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>XDM.Compatibility</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)BlockingCollection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DictionaryExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GroupCollectionExtension.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ProcessStartInfoHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)StreamExtension.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TupleElementNamesAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ValueTuplePolyfill.cs" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>6bffa6bc-bf01-450e-96b7-9a1d3dcc2333</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="XDM.Compatibility.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

View File

@ -0,0 +1,817 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TraceLog;
using Translations;
using XDM.Core.UI;
using XDM.Core;
using XDM.Core.DataAccess;
using XDM.Core.Downloader;
using XDM.Core.Util;
namespace XDM.Core
{
public class UIService : IAppController
{
private IMainView peer;
private IAppService app;
private delegate void UpdateItemCallBack(string id, string targetFileName, long size);
private Action<string, int, double, long> updateProgressAction;
private long lastProgressUpdate = 0;
public event EventHandler WindowLoaded;
public UIService(IMainView peer, IAppService app)
{
this.peer = peer;
this.app = app;
this.updateProgressAction = new Action<string, int, double, long>(this.UpdateProgressOnUI);
AttachedEventHandler();
this.LoadDownloadList();
UpdateToolbarButtonState();
}
public IAppService App { get => app; set => app = value; }
public void AddItemToTop(
string id,
string targetFileName,
DateTime date,
long fileSize,
string type,
FileNameFetchMode fileNameFetchMode,
string primaryUrl,
DownloadStartType startType,
AuthenticationInfo? authentication,
ProxyInfo? proxyInfo,
int maxSpeedLimit)
{
var downloadEntry = new InProgressDownloadEntry
{
Name = targetFileName,
DateAdded = date,
DownloadType = type,
Id = id,
Progress = 0,
Size = fileSize,
Status = startType == DownloadStartType.Waiting ? DownloadStatus.Waiting : DownloadStatus.Stopped,
TargetDir = "",
PrimaryUrl = primaryUrl,
Authentication = authentication,
Proxy = proxyInfo,
MaxSpeedLimitInKiB = maxSpeedLimit,
};
AppDB.Instance.Downloads.AddNewDownload(downloadEntry);
RunOnUiThread(() =>
{
//var downloadEntry = new InProgressDownloadEntry
//{
// Name = targetFileName,
// DateAdded = date,
// DownloadType = type,
// Id = id,
// Progress = 0,
// Size = fileSize,
// Status = startType == DownloadStartType.Waiting ? DownloadStatus.Waiting : DownloadStatus.Stopped,
// TargetDir = "",
// PrimaryUrl = primaryUrl,
// Authentication = authentication,
// Proxy = proxyInfo,
// MaxSpeedLimitInKiB = maxSpeedLimit,
//};
this.peer.AddToTop(downloadEntry);
this.peer.SwitchToInProgressView();
this.peer.ClearInProgressViewSelection();
//this.SaveInProgressList();
UpdateToolbarButtonState();
});
}
public bool Confirm(object? window, string text)
{
return peer.Confirm(window, text);
}
public IDownloadCompleteDialog CreateDownloadCompleteDialog()
{
return peer.CreateDownloadCompleteDialog(this.app);
}
public INewDownloadDialogSkeleton CreateNewDownloadDialog(bool empty)
{
return peer.CreateNewDownloadDialog(empty);
}
public INewVideoDownloadDialog CreateNewVideoDialog()
{
return peer.CreateNewVideoDialog();
}
public IProgressWindow CreateProgressWindow(string downloadId)
{
return peer.CreateProgressWindow(downloadId, this.app, this);
}
public void DownloadCanelled(string id)
{
DownloadFailed(id);
}
public void DownloadFailed(string id)
{
AppDB.Instance.Downloads.UpdateDownloadStatus(id, DownloadStatus.Stopped);
RunOnUiThread(() =>
{
CallbackActions.DownloadFailed(id, peer);
//SaveInProgressList();
UpdateToolbarButtonState();
});
}
public void DownloadFinished(string id, long finalFileSize, string filePath)
{
if (!string.IsNullOrEmpty(filePath))
{
var name = Path.GetFileName(filePath);
var folder = Path.GetDirectoryName(filePath);
AppDB.Instance.Downloads.MarkAsFinished(id, finalFileSize, name, folder);
}
Log.Debug("Final file name: " + filePath);
var downloadEntry = AppDB.Instance.Downloads.GetDownloadById(id);
if (downloadEntry != null)
{
var finishedEntry = new FinishedDownloadEntry
{
Name = Path.GetFileName(filePath),
Id = downloadEntry.Id,
DateAdded = downloadEntry.DateAdded,
Size = downloadEntry.Size > 0 ? downloadEntry.Size : finalFileSize,
DownloadType = downloadEntry.DownloadType,
TargetDir = Path.GetDirectoryName(filePath)!,
PrimaryUrl = downloadEntry.PrimaryUrl,
Authentication = downloadEntry.Authentication,
Proxy = downloadEntry.Proxy
};
AppDB.Instance.Downloads.UpdateDownloadEntry(finishedEntry);
RunOnUiThread(() =>
{
var download = peer.FindInProgressItem(id);
if (download == null) return;
peer.AddToTop(finishedEntry);
peer.Delete(download);
QueueManager.RemoveFinishedDownload(download.DownloadEntry.Id);
if (app.ActiveDownloadCount == 0 && peer.IsInProgressViewSelected)
{
Log.Debug("switching to finished listview");
peer.SwitchToFinishedView();
}
});
}
//var download = peer.FindInProgressItem(id);
//if (download == null) return;
//peer.AddToTop(finishedEntry);
//peer.Delete(download);
//QueueManager.RemoveFinishedDownload(download.DownloadEntry.Id);
//if (app.ActiveDownloadCount == 0 && peer.IsInProgressViewSelected)
//{
// Log.Debug("switching to finished listview");
// peer.SwitchToFinishedView();
//}
//RunOnUiThread(() =>
//{
// CallbackActions.DownloadFinished(id, finalFileSize, filePath, peer, app, () =>
// {
// UpdateToolbarButtonState();
// QueueWindowManager.RefreshView();
// });
// //this.SaveFinishedList();
// //this.SaveInProgressList();
// //UpdateToolbarButtonState();
// //QueueWindowManager.RefreshView();
//});
}
public void DownloadStarted(string id)
{
RunOnUiThread(() =>
{
CallbackActions.DownloadStarted(id, peer);
UpdateToolbarButtonState();
});
}
public IEnumerable<InProgressDownloadEntry> GetAllInProgressDownloads()
{
//var downloads = new List<InProgressDownloadEntry>();
//if (!AppDB.Instance.DownloadsDB.LoadDownloads(out downloads, out _, QueryMode.InProgress))
//{
// Log.Debug("GetAllInProgressDownloads::failed");
//}
//return downloads;
return peer.InProgressDownloads;
}
public InProgressDownloadEntry? GetInProgressDownloadEntry(string downloadId)
{
return peer.FindInProgressItem(downloadId)?.DownloadEntry;
}
public string? GetUrlFromClipboard()
{
var text = peer.GetUrlFromClipboard();
if (Helpers.IsUriValid(text))
{
return text;
}
return null;
}
public AuthenticationInfo? PromtForCredentials(string message)
{
return peer.PromtForCredentials(message);
}
public void RenameFileOnUI(string id, string folder, string file)
{
if (!AppDB.Instance.Downloads.UpdateNameAndFolder(id, file, folder))
{
Log.Debug("RenameFileOnUI::failed");
}
RunOnUiThread(() =>
{
var downloadEntry = this.peer.FindInProgressItem(id);
if (downloadEntry == null) return;
if (file != null)
{
downloadEntry.Name = file;
}
if (folder != null)
{
downloadEntry.DownloadEntry.TargetDir = folder;
}
//this.SaveInProgressList();
});
}
public void ResumeDownload(string downloadId)
{
var idDict = new Dictionary<string, BaseDownloadEntry>();
var download = peer.FindInProgressItem(downloadId);
if (download == null) return;
idDict[download.DownloadEntry.Id] = download.DownloadEntry;
App.ResumeDownload(idDict);
}
public void RunOnUiThread(Action action)
{
peer.RunOnUIThread(action);
}
public void SetDownloadStatusWaiting(string id)
{
RunOnUiThread(() =>
{
var download = this.peer.FindInProgressItem(id);
if (download == null) return;
download.Status = DownloadStatus.Waiting;
UpdateToolbarButtonState();
});
}
public void ShowUpdateAvailableNotification()
{
RunOnUiThread(() =>
{
peer.ShowUpdateAvailableNotification();
});
}
public void ShowDownloadCompleteDialog(string file, string folder)
{
RunOnUiThread(() =>
{
DownloadCompleteDialogHelper.ShowDialog(this.App, CreateDownloadCompleteDialog(), file, folder);
});
}
public void ShowMessageBox(object? window, string message)
{
peer.ShowMessageBox(window, message);
}
public void ShowNewDownloadDialog(Message message)
{
var url = message.Url;
if (NewDownloadPromptTracker.IsPromptAlreadyOpen(url))
{
return;
}
peer.RunOnUIThread(() =>
{
NewDownloadPromptTracker.PromptOpen(url);
NewDownloadDialogHelper.CreateAndShowDialog(this.App, this, this.CreateNewDownloadDialog(false), message,
() => NewDownloadPromptTracker.PromptClosed(url));
});
}
public void ShowVideoDownloadDialog(string videoId, string name, long size, string? contentType)
{
RunOnUiThread(() =>
{
NewVideoDownloadDialogHelper.ShowVideoDownloadDialog(this.App, this, this.CreateNewVideoDialog(),
videoId, name, size, contentType);
});
}
public void UpdateItem(string id, string targetFileName, long size)
{
if (!AppDB.Instance.Downloads.UpdateNameAndSize(id, size, targetFileName))
{
Log.Debug("UpdateItem::failed");
}
RunOnUiThread(() =>
{
var download = peer.FindInProgressItem(id);
if (download == null) return;
download.Name = targetFileName;
download.Size = size;
//this.SaveInProgressList();
});
}
private void UpdateProgressOnUI(string id, int progress, double speed, long eta)
{
var downloadEntry = peer.FindInProgressItem(id);
if (downloadEntry != null)
{
downloadEntry.Progress = progress;
downloadEntry.DownloadSpeed = Helpers.FormatSize(speed) + "/s";
downloadEntry.ETA = Helpers.ToHMS(eta);
}
}
public void UpdateProgress(string id, int progress, double speed, long eta)
{
if (!AppDB.Instance.Downloads.UpdateDownloadProgress(id, progress))
{
Log.Debug("UpdateProgress::failed");
}
peer.RunOnUIThread(this.updateProgressAction, id, progress, speed, eta);
}
private void LoadDownloadList()
{
try
{
if (AppDB.Instance.Downloads.LoadDownloads(out var inProgressDownloads, out var finishedDownloads))
{
peer.InProgressDownloads = inProgressDownloads;
peer.FinishedDownloads = finishedDownloads;
return;
}
else
{
Log.Debug("Could not load download list");
}
//peer.InProgressDownloads = TransactedIO.ReadInProgressList("inprogress-downloads.dat", Config.DataDir);
//peer.FinishedDownloads = TransactedIO.ReadFinishedList("finished-downloads.dat", Config.DataDir);
}
catch (Exception ex)
{
Log.Debug(ex, "LoadDownloadList");
}
}
//private void SaveInProgressList()
//{
// lock (this)
// {
// TransactedIO.WriteInProgressList(peer.InProgressDownloads, "inprogress-downloads.dat", Config.DataDir);
// }
//}
//private void SaveFinishedList()
//{
// lock (this)
// {
// TransactedIO.WriteFinishedList(peer.FinishedDownloads, "finished-downloads.dat", Config.DataDir);
// }
//}
private void DisableButton(IButton button)
{
button.Enable = false;
}
private void EnableButton(IButton button)
{
button.Enable = true;
}
private void UpdateToolbarButtonState()
{
DisableButton(peer.OpenFileButton);
DisableButton(peer.OpenFolderButton);
DisableButton(peer.PauseButton);
DisableButton(peer.ResumeButton);
DisableButton(peer.DeleteButton);
if (peer.IsInProgressViewSelected)
{
peer.OpenFileButton.Visible = peer.OpenFolderButton.Visible = false;
peer.PauseButton.Visible = peer.ResumeButton.Visible = true;
var selectedRows = peer.SelectedInProgressRows;
if (selectedRows.Count > 0)
{
EnableButton(peer.DeleteButton);
}
if (selectedRows.Count > 1)
{
EnableButton(peer.ResumeButton);
EnableButton(peer.PauseButton);
}
else if (selectedRows.Count == 1)
{
var ent = selectedRows[0];
var isActive = App.IsDownloadActive(ent.DownloadEntry.Id);
if (isActive)
{
EnableButton(peer.PauseButton);
}
else
{
EnableButton(peer.ResumeButton);
}
}
}
else
{
peer.OpenFileButton.Visible = peer.OpenFolderButton.Visible = true;
peer.PauseButton.Visible = peer.ResumeButton.Visible = false;
if (peer.SelectedFinishedRows.Count > 0)
{
EnableButton(peer.DeleteButton);
}
if (peer.SelectedFinishedRows.Count == 1)
{
EnableButton(peer.OpenFileButton);
EnableButton(peer.OpenFolderButton);
}
}
}
private void DeleteDownloads()
{
UIActions.DeleteDownloads(peer.IsInProgressViewSelected,
peer, App, null);
//inProgress =>
//{
// if (inProgress)
// {
// SaveInProgressList();
// }
// else
// {
// SaveFinishedList();
// }
//});
}
private void AttachedEventHandler()
{
peer.NewDownloadClicked += (s, e) =>
{
peer.RunOnUIThread(() =>
{
NewDownloadDialogHelper.CreateAndShowDialog(this.App, this, CreateNewDownloadDialog(true));
});
};
peer.YoutubeDLDownloadClicked += (s, e) =>
{
peer.ShowYoutubeDLDialog(this, app);
};
peer.BatchDownloadClicked += (s, e) =>
{
peer.ShowBatchDownloadWindow(app, this);
};
peer.SelectionChanged += (s, e) =>
{
UpdateToolbarButtonState();
};
peer.CategoryChanged += (s, e) =>
{
UpdateToolbarButtonState();
};
peer.NewButton.Clicked += (s, e) =>
{
peer.OpenNewDownloadMenu();
};
peer.DeleteButton.Clicked += (a, b) =>
{
DeleteDownloads();
};
peer.DownloadListDoubleClicked += (a, b) => UIActions.OnDblClick(peer, App);
peer.OpenFolderButton.Clicked += (a, b) => UIActions.OpenSelectedFolder(peer);
peer.OpenFileButton.Clicked += (a, b) =>
{
UIActions.OpenSelectedFile(peer);
};
peer.PauseButton.Clicked += (a, b) =>
{
if (peer.IsInProgressViewSelected)
{
UIActions.StopSelectedDownloads(peer, App);
}
};
peer.ResumeButton.Clicked += (a, b) =>
{
if (peer.IsInProgressViewSelected)
{
UIActions.ResumeDownloads(peer, App);
}
};
peer.SettingsClicked += (s, e) =>
{
peer.ShowSettingsDialog(app, 1);
peer.UpdateParallalismLabel();
};
peer.BrowserMonitoringSettingsClicked += (s, e) =>
{
peer.ShowBrowserMonitoringDialog(app);
peer.UpdateParallalismLabel();
};
peer.ClearAllFinishedClicked += (s, e) =>
{
peer.DeleteAllFinishedDownloads();
AppDB.Instance.Downloads.RemoveAllFinished();
//SaveFinishedList();
};
peer.ImportClicked += (s, e) =>
{
var file = peer.OpenFileDialog(null, "zip", null);
if (!string.IsNullOrEmpty(file) && File.Exists(file))
{
Log.Debug("Exporting to: " + file);
app.Import(file!);
}
LoadDownloadList();
};
peer.ExportClicked += (s, e) =>
{
var file = peer.SaveFileDialog("xdm-download-list.zip", "zip", "All files (*.*)|*.*");
if (!string.IsNullOrEmpty(file))
{
Log.Debug("Exporting to: " + file);
app.Export(file!);
}
};
peer.HelpClicked += (s, e) =>
{
Helpers.OpenBrowser(app.HelpPage);
};
peer.UpdateClicked += (s, e) =>
{
if (App.IsAppUpdateAvailable)
{
Helpers.OpenBrowser(App.UpdatePage);
return;
}
if (App.IsComponentUpdateAvailable)
{
if (peer.Confirm(peer, App.ComponentUpdateText))
{
LaunchUpdater(UpdateMode.FFmpegUpdateOnly | UpdateMode.YoutubeDLUpdateOnly);
return;
}
else
{
return;
}
}
peer.ShowMessageBox(peer, TextResource.GetText("MSG_NO_UPDATE"));
};
peer.BrowserMonitoringButtonClicked += (s, e) =>
{
if (Config.Instance.IsBrowserMonitoringEnabled)
{
Config.Instance.IsBrowserMonitoringEnabled = false;
}
else
{
Config.Instance.IsBrowserMonitoringEnabled = true;
}
Config.SaveConfig();
app.ApplyConfig();
peer.UpdateBrowserMonitorButton();
};
peer.SupportPageClicked += (s, e) =>
{
Helpers.OpenBrowser("https://subhra74.github.io/xdm/redirect-support.html");
};
peer.BugReportClicked += (s, e) =>
{
Helpers.OpenBrowser("https://subhra74.github.io/xdm/redirect-issue.html");
};
peer.CheckForUpdateClicked += (s, e) =>
{
Helpers.OpenBrowser(app.UpdatePage);
};
peer.SchedulerClicked += (s, e) =>
{
ShowQueueWindow(peer);
};
peer.WindowCreated += (s, e) =>
{
this.WindowLoaded?.Invoke(this, EventArgs.Empty);
};
AttachContextMenuEvents();
peer.InProgressContextMenuOpening += (_, _) => InProgressContextMenuOpening();
peer.FinishedContextMenuOpening += (_, _) => FinishedContextMenuOpening();
}
public void ShowQueueWindow(object window)
{
QueueWindowManager.ShowWindow(window, peer.CreateQueuesAndSchedulerWindow(this), this.app);
}
private void LaunchUpdater(UpdateMode updateMode)
{
var updateDlg = peer.CreateUpdateUIDialog(this);
var updates = App.Updates?.Where(u => u.IsExternal)?.ToList() ?? new List<UpdateInfo>(0);
if (updates.Count == 0) return;
var commonUpdateUi = new ComponentUpdaterUI(updateDlg, app, updateMode);
updateDlg.Load += (_, _) => commonUpdateUi.StartUpdate();
updateDlg.Finished += (_, _) =>
{
RunOnUiThread(() =>
{
peer.ClearUpdateInformation();
});
};
updateDlg.Show();
}
private void AttachContextMenuEvents()
{
try
{
peer.MenuItemMap["pause"].Clicked += (_, _) => UIActions.StopSelectedDownloads(peer, App);
peer.MenuItemMap["resume"].Clicked += (_, _) => UIActions.ResumeDownloads(peer, App);
peer.MenuItemMap["delete"].Clicked += (_, _) => DeleteDownloads();
peer.MenuItemMap["saveAs"].Clicked += (_, _) => UIActions.SaveAs(peer, App);
peer.MenuItemMap["refresh"].Clicked += (_, _) => UIActions.RefreshLink(peer, App);
peer.MenuItemMap["moveToQueue"].Clicked += (_, _) => UIActions.MoveToQueue(peer, this);
peer.MenuItemMap["showProgress"].Clicked += (_, _) => UIActions.ShowProgressWindow(peer, App);
peer.MenuItemMap["copyURL"].Clicked += (_, _) => UIActions.CopyURL1(peer, App);
peer.MenuItemMap["copyURL1"].Clicked += (_, _) => UIActions.CopyURL2(peer, App);
peer.MenuItemMap["properties"].Clicked += (_, _) => UIActions.ShowSeletectedItemProperties(peer, App);
peer.MenuItemMap["open"].Clicked += (_, _) => UIActions.OpenSelectedFile(peer);
peer.MenuItemMap["openFolder"].Clicked += (_, _) => UIActions.OpenSelectedFolder(peer);
peer.MenuItemMap["deleteDownloads"].Clicked += (_, _) => DeleteDownloads();
peer.MenuItemMap["copyFile"].Clicked += (_, _) => UIActions.CopyFile(peer);
peer.MenuItemMap["properties1"].Clicked += (_, _) => UIActions.ShowSeletectedItemProperties(peer, App);
peer.MenuItemMap["downloadAgain"].Clicked += (_, _) => UIActions.RestartDownload(peer, App);
peer.MenuItemMap["restart"].Clicked += (_, _) => UIActions.RestartDownload(peer, App);
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
private void InProgressContextMenuOpening()
{
foreach (var menu in peer.MenuItems)
{
menu.Enabled = false;
}
peer.MenuItemMap["delete"].Enabled = true;
peer.MenuItemMap["schedule"].Enabled = true;
peer.MenuItemMap["moveToQueue"].Enabled = true;
var selectedRows = peer.SelectedInProgressRows;
if (selectedRows.Count > 1)
{
peer.MenuItemMap["pause"].Enabled = true;
peer.MenuItemMap["resume"].Enabled = true;
peer.MenuItemMap["showProgress"].Enabled = true;
}
else if (selectedRows.Count == 1)
{
peer.MenuItemMap["showProgress"].Enabled = true;
peer.MenuItemMap["copyURL"].Enabled = true;
peer.MenuItemMap["saveAs"].Enabled = true;
peer.MenuItemMap["refresh"].Enabled = true;
peer.MenuItemMap["properties"].Enabled = true;
peer.MenuItemMap["saveAs"].Enabled = true;
peer.MenuItemMap["saveAs"].Enabled = true;
peer.MenuItemMap["copyURL"].Enabled = true;
var ent = selectedRows[0].DownloadEntry;//selectedRows[0].Cells[1].Value as InProgressDownloadEntry;
if (ent == null) return;
var isActive = App.IsDownloadActive(ent.Id);
Log.Debug("Selected item active: " + isActive);
if (isActive)
{
peer.MenuItemMap["pause"].Enabled = true;
}
else
{
peer.MenuItemMap["resume"].Enabled = true;
peer.MenuItemMap["restart"].Enabled = true;
}
}
}
private void FinishedContextMenuOpening()
{
foreach (var menu in peer.MenuItems)
{
menu.Enabled = false;
}
peer.MenuItemMap["deleteDownloads"].Enabled = true;
var selectedRows = peer.SelectedFinishedRows;
if (selectedRows.Count == 1)
{
foreach (var menu in peer.MenuItems)
{
menu.Enabled = true;
}
}
}
public void InstallLatestFFmpeg()
{
LaunchUpdater(UpdateMode.FFmpegUpdateOnly);
}
public void InstallLatestYoutubeDL()
{
LaunchUpdater(UpdateMode.YoutubeDLUpdateOnly);
}
public void ShowDownloadSelectionWindow(FileNameFetchMode mode, IEnumerable<object> downloads)
{
RunOnUiThread(() =>
{
peer.ShowDownloadSelectionWindow(this.App, this, mode, downloads);
});
}
public IClipboardMonitor GetClipboardMonitor()
{
return peer.GetClipboardMonitor();
}
public void ShowFloatingVideoWidget()
{
peer.ShowFloatingWidget();
}
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using TraceLog;
using Translations;
using XDM.Core.Util;
namespace XDM.Core
{
public static class AppUpdater
{
private static Timer UpdateCheckTimer;
public static IList<UpdateInfo>? Updates { get; private set; }
public static bool ComponentsInstalled { get; private set; }
public static bool IsAppUpdateAvailable => Updates?.Any(u => !u.IsExternal) ?? false;
public static bool IsComponentUpdateAvailable => Updates?.Any(u => u.IsExternal) ?? false;
public static string ComponentUpdateText => GetUpdateText();
public static string UpdatePage => $"https://subhra74.github.io/xdm/update-checker.html?v={ApplicationContext.CoreService.AppVerion}";
public static void QueryNewVersion()
{
UpdateCheckTimer = new(
callback: a => CheckForUpdate(),
state: null,
dueTime: TimeSpan.FromSeconds(5),
period: TimeSpan.FromHours(3));
}
private static void CheckForUpdate()
{
try
{
Log.Debug("Checking for updates...");
if (UpdateChecker.GetAppUpdates(ApplicationContext.CoreService.AppVerion, out IList<UpdateInfo> upd, out bool firstUpdate))
{
Updates = upd;
ComponentsInstalled = !firstUpdate;
ApplicationContext.Application.ShowUpdateAvailableNotification();
}
}
catch (Exception ex)
{
Log.Debug(ex, "CheckForUpdate");
}
}
private static string GetUpdateText()
{
if (Updates == null || Updates.Count < 1) return TextResource.GetText("MSG_NO_UPDATE");
var text = new StringBuilder();
var size = 0L;
text.Append((ComponentsInstalled ? "Update available: " : "XDM require FFmpeg and YoutubeDL to download streaming videos") + Environment.NewLine);
foreach (var update in Updates)
{
text.Append(update.Name + " " + update.TagName + Environment.NewLine);
size += update.Size;
}
text.Append(Environment.NewLine + "Total download: " + FormattingHelper.FormatSize(size) + Environment.NewLine);
text.Append("Would you like to continue?");
return text.ToString();
}
}
}

View File

@ -0,0 +1,723 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TraceLog;
using Translations;
using XDM.Core.UI;
using XDM.Core;
using XDM.Core.DataAccess;
using XDM.Core.Downloader;
using XDM.Core.Util;
namespace XDM.Core
{
public class Application : IApplication
{
private delegate void UpdateItemCallBack(string id, string targetFileName, long size);
private Action<string, int, double, long> updateProgressAction;
public event EventHandler WindowLoaded;
public Application()
{
ApplicationContext.Initialized += AppInstance_Initialized;
this.updateProgressAction = new Action<string, int, double, long>(this.UpdateProgressOnUI);
}
private void AppInstance_Initialized(object? sender, EventArgs e)
{
AppDB.Instance.Init(Path.Combine(Config.DataDir, "downloads.db"));
AttachedEventHandler();
LoadDownloadList();
UpdateToolbarButtonState();
AppUpdater.QueryNewVersion();
WindowLoaded += (_, _) => ApplicationContext.ClipboardMonitor.Start();
}
public void AddItemToTop(
string id,
string targetFileName,
DateTime date,
long fileSize,
string type,
FileNameFetchMode fileNameFetchMode,
string primaryUrl,
DownloadStartType startType,
AuthenticationInfo? authentication,
ProxyInfo? proxyInfo)
{
var downloadEntry = new InProgressDownloadItem
{
Name = targetFileName,
DateAdded = date,
DownloadType = type,
Id = id,
Progress = 0,
Size = fileSize,
Status = startType == DownloadStartType.Waiting ? DownloadStatus.Waiting : DownloadStatus.Stopped,
TargetDir = "",
PrimaryUrl = primaryUrl,
Authentication = authentication,
Proxy = proxyInfo
};
AppDB.Instance.Downloads.AddNewDownload(downloadEntry);
RunOnUiThread(() =>
{
ApplicationContext.MainWindow.AddToTop(downloadEntry);
ApplicationContext.MainWindow.SwitchToInProgressView();
ApplicationContext.MainWindow.ClearInProgressViewSelection();
UpdateToolbarButtonState();
});
}
public bool Confirm(object? window, string text)
{
return ApplicationContext.MainWindow.Confirm(window, text);
}
public IDownloadCompleteDialog CreateDownloadCompleteDialog()
{
return ApplicationContext.PlatformUIService.CreateDownloadCompleteDialog();
}
public INewDownloadDialog CreateNewDownloadDialog(bool empty)
{
return ApplicationContext.PlatformUIService.CreateNewDownloadDialog(empty);
}
public INewVideoDownloadDialog CreateNewVideoDialog()
{
return ApplicationContext.PlatformUIService.CreateNewVideoDialog();
}
public IProgressWindow CreateProgressWindow(string downloadId)
{
return ApplicationContext.PlatformUIService.CreateProgressWindow(downloadId);
}
public void DownloadCanelled(string id)
{
DownloadFailed(id);
}
public void DownloadFailed(string id)
{
AppDB.Instance.Downloads.UpdateDownloadStatus(id, DownloadStatus.Stopped);
RunOnUiThread(() =>
{
CallbackActions.DownloadFailed(id);
UpdateToolbarButtonState();
});
}
public void DownloadFinished(string id, long finalFileSize, string filePath)
{
if (!string.IsNullOrEmpty(filePath))
{
var name = Path.GetFileName(filePath);
var folder = Path.GetDirectoryName(filePath);
AppDB.Instance.Downloads.MarkAsFinished(id, finalFileSize, name, folder);
}
Log.Debug("Final file name: " + filePath);
var downloadEntry = AppDB.Instance.Downloads.GetDownloadById(id);
if (downloadEntry != null)
{
var finishedEntry = new FinishedDownloadItem
{
Name = Path.GetFileName(filePath),
Id = downloadEntry.Id,
DateAdded = downloadEntry.DateAdded,
Size = downloadEntry.Size > 0 ? downloadEntry.Size : finalFileSize,
DownloadType = downloadEntry.DownloadType,
TargetDir = Path.GetDirectoryName(filePath)!,
PrimaryUrl = downloadEntry.PrimaryUrl,
Authentication = downloadEntry.Authentication,
Proxy = downloadEntry.Proxy
};
AppDB.Instance.Downloads.UpdateDownloadEntry(finishedEntry);
RunOnUiThread(() =>
{
var download = ApplicationContext.MainWindow.FindInProgressItem(id);
if (download == null) return;
ApplicationContext.MainWindow.AddToTop(finishedEntry);
ApplicationContext.MainWindow.Delete(download);
QueueManager.RemoveFinishedDownload(download.DownloadEntry.Id);
if (ApplicationContext.CoreService.ActiveDownloadCount == 0 && ApplicationContext.MainWindow.IsInProgressViewSelected)
{
Log.Debug("switching to finished listview");
ApplicationContext.MainWindow.SwitchToFinishedView();
}
});
}
}
public void DownloadStarted(string id)
{
RunOnUiThread(() =>
{
CallbackActions.DownloadStarted(id);
UpdateToolbarButtonState();
});
}
public IEnumerable<InProgressDownloadItem> GetAllInProgressDownloads()
{
return ApplicationContext.MainWindow.InProgressDownloads;
}
public InProgressDownloadItem? GetInProgressDownloadEntry(string downloadId)
{
return ApplicationContext.MainWindow.FindInProgressItem(downloadId)?.DownloadEntry;
}
public string? GetUrlFromClipboard()
{
var text = ApplicationContext.MainWindow.GetUrlFromClipboard();
if (Helpers.IsUriValid(text))
{
return text;
}
return null;
}
public AuthenticationInfo? PromtForCredentials(string message)
{
return ApplicationContext.PlatformUIService.PromtForCredentials(ApplicationContext.MainWindow, message);
}
public void RenameFileOnUI(string id, string folder, string file)
{
if (!AppDB.Instance.Downloads.UpdateNameAndFolder(id, file, folder))
{
Log.Debug("RenameFileOnUI::failed");
}
RunOnUiThread(() =>
{
var downloadEntry = ApplicationContext.MainWindow.FindInProgressItem(id);
if (downloadEntry == null) return;
if (file != null)
{
downloadEntry.Name = file;
}
if (folder != null)
{
downloadEntry.DownloadEntry.TargetDir = folder;
}
//this.SaveInProgressList();
});
}
public void ResumeDownload(string downloadId)
{
var idDict = new Dictionary<string, DownloadItemBase>();
var download = ApplicationContext.MainWindow.FindInProgressItem(downloadId);
if (download == null) return;
idDict[download.DownloadEntry.Id] = download.DownloadEntry;
ApplicationContext.CoreService.ResumeDownload(idDict);
}
public void RunOnUiThread(Action action)
{
ApplicationContext.MainWindow.RunOnUIThread(action);
}
public void SetDownloadStatusWaiting(string id)
{
RunOnUiThread(() =>
{
var download = ApplicationContext.MainWindow.FindInProgressItem(id);
if (download == null) return;
download.Status = DownloadStatus.Waiting;
UpdateToolbarButtonState();
});
}
public void ShowUpdateAvailableNotification()
{
RunOnUiThread(() =>
{
ApplicationContext.MainWindow.ShowUpdateAvailableNotification();
});
}
public void ShowDownloadCompleteDialog(string file, string folder)
{
RunOnUiThread(() =>
{
DownloadCompleteUIController.ShowDialog(CreateDownloadCompleteDialog(), file, folder);
});
}
public void ShowMessageBox(object? window, string message)
{
ApplicationContext.PlatformUIService.ShowMessageBox(window, message);
}
public void ShowNewDownloadDialog(Message message)
{
var url = message.Url;
if (NewDownloadPromptTracker.IsPromptAlreadyOpen(url))
{
return;
}
ApplicationContext.MainWindow.RunOnUIThread(() =>
{
NewDownloadPromptTracker.PromptOpen(url);
NewDownloadDialogUIController.CreateAndShowDialog(this.CreateNewDownloadDialog(false), message,
() => NewDownloadPromptTracker.PromptClosed(url));
});
}
public void ShowVideoDownloadDialog(string videoId, string name, long size, string? contentType)
{
RunOnUiThread(() =>
{
NewVideoDownloadDialogUIController.ShowVideoDownloadDialog(this.CreateNewVideoDialog(),
videoId, name, size, contentType);
});
}
public void UpdateItem(string id, string targetFileName, long size)
{
if (!AppDB.Instance.Downloads.UpdateNameAndSize(id, size, targetFileName))
{
Log.Debug("UpdateItem::failed");
}
RunOnUiThread(() =>
{
var download = ApplicationContext.MainWindow.FindInProgressItem(id);
if (download == null) return;
download.Name = targetFileName;
download.Size = size;
//this.SaveInProgressList();
});
}
private void UpdateProgressOnUI(string id, int progress, double speed, long eta)
{
var downloadEntry = ApplicationContext.MainWindow.FindInProgressItem(id);
if (downloadEntry != null)
{
downloadEntry.Progress = progress;
downloadEntry.DownloadSpeed = FormattingHelper.FormatSize(speed) + "/s";
downloadEntry.ETA = FormattingHelper.ToHMS(eta);
}
}
public void UpdateProgress(string id, int progress, double speed, long eta)
{
if (!AppDB.Instance.Downloads.UpdateDownloadProgress(id, progress))
{
Log.Debug("UpdateProgress::failed");
}
ApplicationContext.MainWindow.RunOnUIThread(this.updateProgressAction, id, progress, speed, eta);
}
private void LoadDownloadList()
{
try
{
if (AppDB.Instance.Downloads.LoadDownloads(out var inProgressDownloads, out var finishedDownloads))
{
ApplicationContext.MainWindow.InProgressDownloads = inProgressDownloads;
ApplicationContext.MainWindow.FinishedDownloads = finishedDownloads;
return;
}
else
{
Log.Debug("Could not load download list");
}
}
catch (Exception ex)
{
Log.Debug(ex, "LoadDownloadList");
}
}
private void DisableButton(IButton button)
{
button.Enable = false;
}
private void EnableButton(IButton button)
{
button.Enable = true;
}
private void UpdateToolbarButtonState()
{
DisableButton(ApplicationContext.MainWindow.OpenFileButton);
DisableButton(ApplicationContext.MainWindow.OpenFolderButton);
DisableButton(ApplicationContext.MainWindow.PauseButton);
DisableButton(ApplicationContext.MainWindow.ResumeButton);
DisableButton(ApplicationContext.MainWindow.DeleteButton);
if (ApplicationContext.MainWindow.IsInProgressViewSelected)
{
ApplicationContext.MainWindow.OpenFileButton.Visible = ApplicationContext.MainWindow.OpenFolderButton.Visible = false;
ApplicationContext.MainWindow.PauseButton.Visible = ApplicationContext.MainWindow.ResumeButton.Visible = true;
var selectedRows = ApplicationContext.MainWindow.SelectedInProgressRows;
if (selectedRows.Count > 0)
{
EnableButton(ApplicationContext.MainWindow.DeleteButton);
}
if (selectedRows.Count > 1)
{
EnableButton(ApplicationContext.MainWindow.ResumeButton);
EnableButton(ApplicationContext.MainWindow.PauseButton);
}
else if (selectedRows.Count == 1)
{
var ent = selectedRows[0];
var isActive = ApplicationContext.CoreService.IsDownloadActive(ent.DownloadEntry.Id);
if (isActive)
{
EnableButton(ApplicationContext.MainWindow.PauseButton);
}
else
{
EnableButton(ApplicationContext.MainWindow.ResumeButton);
}
}
}
else
{
ApplicationContext.MainWindow.OpenFileButton.Visible = ApplicationContext.MainWindow.OpenFolderButton.Visible = true;
ApplicationContext.MainWindow.PauseButton.Visible = ApplicationContext.MainWindow.ResumeButton.Visible = false;
if (ApplicationContext.MainWindow.SelectedFinishedRows.Count > 0)
{
EnableButton(ApplicationContext.MainWindow.DeleteButton);
}
if (ApplicationContext.MainWindow.SelectedFinishedRows.Count == 1)
{
EnableButton(ApplicationContext.MainWindow.OpenFileButton);
EnableButton(ApplicationContext.MainWindow.OpenFolderButton);
}
}
}
private void DeleteDownloads()
{
UIActions.DeleteDownloads(ApplicationContext.MainWindow.IsInProgressViewSelected, null);
}
private void AttachedEventHandler()
{
ApplicationContext.MainWindow.NewDownloadClicked += (s, e) =>
{
ApplicationContext.MainWindow.RunOnUIThread(() =>
{
NewDownloadDialogUIController.CreateAndShowDialog(CreateNewDownloadDialog(true));
});
};
ApplicationContext.MainWindow.YoutubeDLDownloadClicked += (s, e) =>
{
ApplicationContext.PlatformUIService.ShowYoutubeDLDialog();
};
ApplicationContext.MainWindow.BatchDownloadClicked += (s, e) =>
{
ApplicationContext.PlatformUIService.ShowBatchDownloadWindow();
};
ApplicationContext.MainWindow.SelectionChanged += (s, e) =>
{
UpdateToolbarButtonState();
};
ApplicationContext.MainWindow.CategoryChanged += (s, e) =>
{
UpdateToolbarButtonState();
};
ApplicationContext.MainWindow.NewButton.Clicked += (s, e) =>
{
ApplicationContext.MainWindow.OpenNewDownloadMenu();
};
ApplicationContext.MainWindow.DeleteButton.Clicked += (a, b) =>
{
DeleteDownloads();
};
ApplicationContext.MainWindow.DownloadListDoubleClicked += (a, b) => UIActions.OnDblClick();
ApplicationContext.MainWindow.OpenFolderButton.Clicked += (a, b) => UIActions.OpenSelectedFolder();
ApplicationContext.MainWindow.OpenFileButton.Clicked += (a, b) =>
{
UIActions.OpenSelectedFile();
};
ApplicationContext.MainWindow.PauseButton.Clicked += (a, b) =>
{
if (ApplicationContext.MainWindow.IsInProgressViewSelected)
{
UIActions.StopSelectedDownloads();
}
};
ApplicationContext.MainWindow.ResumeButton.Clicked += (a, b) =>
{
if (ApplicationContext.MainWindow.IsInProgressViewSelected)
{
UIActions.ResumeDownloads();
}
};
ApplicationContext.MainWindow.SettingsClicked += (s, e) =>
{
ApplicationContext.PlatformUIService.ShowSettingsDialog(1);
};
ApplicationContext.MainWindow.BrowserMonitoringSettingsClicked += (s, e) =>
{
ApplicationContext.PlatformUIService.ShowBrowserMonitoringDialog();
};
ApplicationContext.MainWindow.ClearAllFinishedClicked += (s, e) =>
{
ApplicationContext.MainWindow.DeleteAllFinishedDownloads();
AppDB.Instance.Downloads.RemoveAllFinished();
//SaveFinishedList();
};
ApplicationContext.MainWindow.ImportClicked += (s, e) =>
{
var file = ApplicationContext.PlatformUIService.OpenFileDialog(null, "zip", null);
if (!string.IsNullOrEmpty(file) && File.Exists(file))
{
Log.Debug("Exporting to: " + file);
ApplicationContext.CoreService.Import(file!);
}
LoadDownloadList();
};
ApplicationContext.MainWindow.ExportClicked += (s, e) =>
{
var file = ApplicationContext.PlatformUIService.SaveFileDialog("xdm-download-list.zip", "zip", "All files (*.*)|*.*");
if (!string.IsNullOrEmpty(file))
{
Log.Debug("Exporting to: " + file);
ApplicationContext.CoreService.Export(file!);
}
};
ApplicationContext.MainWindow.HelpClicked += (s, e) =>
{
PlatformHelper.OpenBrowser(Links.SupportUrl);
};
ApplicationContext.MainWindow.UpdateClicked += (s, e) =>
{
if (AppUpdater.IsAppUpdateAvailable)
{
PlatformHelper.OpenBrowser(AppUpdater.UpdatePage);
return;
}
if (AppUpdater.IsComponentUpdateAvailable)
{
if (ApplicationContext.MainWindow.Confirm(ApplicationContext.MainWindow, AppUpdater.ComponentUpdateText))
{
LaunchUpdater(UpdateMode.FFmpegUpdateOnly | UpdateMode.YoutubeDLUpdateOnly);
return;
}
else
{
return;
}
}
ApplicationContext.PlatformUIService.ShowMessageBox(ApplicationContext.MainWindow, TextResource.GetText("MSG_NO_UPDATE"));
};
ApplicationContext.MainWindow.BrowserMonitoringButtonClicked += (s, e) =>
{
if (Config.Instance.IsBrowserMonitoringEnabled)
{
Config.Instance.IsBrowserMonitoringEnabled = false;
}
else
{
Config.Instance.IsBrowserMonitoringEnabled = true;
}
Config.SaveConfig();
ApplicationContext.BroadcastConfigChange();
ApplicationContext.MainWindow.UpdateBrowserMonitorButton();
};
ApplicationContext.MainWindow.SupportPageClicked += (s, e) =>
{
PlatformHelper.OpenBrowser(Links.SupportUrl);
};
ApplicationContext.MainWindow.BugReportClicked += (s, e) =>
{
PlatformHelper.OpenBrowser(Links.IssueUrl);
};
ApplicationContext.MainWindow.CheckForUpdateClicked += (s, e) =>
{
PlatformHelper.OpenBrowser(AppUpdater.UpdatePage);
};
ApplicationContext.MainWindow.SchedulerClicked += (s, e) =>
{
ShowQueueWindow(ApplicationContext.MainWindow);
};
ApplicationContext.MainWindow.WindowCreated += (s, e) =>
{
this.WindowLoaded?.Invoke(this, EventArgs.Empty);
};
AttachContextMenuEvents();
ApplicationContext.MainWindow.InProgressContextMenuOpening += (_, _) => InProgressContextMenuOpening();
ApplicationContext.MainWindow.FinishedContextMenuOpening += (_, _) => FinishedContextMenuOpening();
}
public void ShowQueueWindow(object window)
{
QueueWindowManager.ShowWindow(window, ApplicationContext.PlatformUIService.CreateQueuesAndSchedulerWindow());
}
private void LaunchUpdater(UpdateMode updateMode)
{
var updateDlg = ApplicationContext.PlatformUIService.CreateUpdateUIDialog();
var updates = AppUpdater.Updates?.Where(u => u.IsExternal)?.ToList() ?? new List<UpdateInfo>(0);
if (updates.Count == 0) return;
var commonUpdateUi = new ComponentUpdaterUIController(updateDlg, updateMode);
updateDlg.Load += (_, _) => commonUpdateUi.StartUpdate();
updateDlg.Finished += (_, _) =>
{
RunOnUiThread(() =>
{
ApplicationContext.MainWindow.ClearUpdateInformation();
});
};
updateDlg.Show();
}
private void AttachContextMenuEvents()
{
try
{
ApplicationContext.MainWindow.MenuItemMap["pause"].Clicked += (_, _) => UIActions.StopSelectedDownloads();
ApplicationContext.MainWindow.MenuItemMap["resume"].Clicked += (_, _) => UIActions.ResumeDownloads();
ApplicationContext.MainWindow.MenuItemMap["delete"].Clicked += (_, _) => DeleteDownloads();
ApplicationContext.MainWindow.MenuItemMap["saveAs"].Clicked += (_, _) => UIActions.SaveAs();
ApplicationContext.MainWindow.MenuItemMap["refresh"].Clicked += (_, _) => UIActions.RefreshLink();
ApplicationContext.MainWindow.MenuItemMap["moveToQueue"].Clicked += (_, _) => UIActions.MoveToQueue();
ApplicationContext.MainWindow.MenuItemMap["showProgress"].Clicked += (_, _) => UIActions.ShowProgressWindow();
ApplicationContext.MainWindow.MenuItemMap["copyURL"].Clicked += (_, _) => UIActions.CopyURL1();
ApplicationContext.MainWindow.MenuItemMap["copyURL1"].Clicked += (_, _) => UIActions.CopyURL2();
ApplicationContext.MainWindow.MenuItemMap["properties"].Clicked += (_, _) => UIActions.ShowSeletectedItemProperties();
ApplicationContext.MainWindow.MenuItemMap["open"].Clicked += (_, _) => UIActions.OpenSelectedFile();
ApplicationContext.MainWindow.MenuItemMap["openFolder"].Clicked += (_, _) => UIActions.OpenSelectedFolder();
ApplicationContext.MainWindow.MenuItemMap["deleteDownloads"].Clicked += (_, _) => DeleteDownloads();
ApplicationContext.MainWindow.MenuItemMap["copyFile"].Clicked += (_, _) => UIActions.CopyFile();
ApplicationContext.MainWindow.MenuItemMap["properties1"].Clicked += (_, _) => UIActions.ShowSeletectedItemProperties();
ApplicationContext.MainWindow.MenuItemMap["downloadAgain"].Clicked += (_, _) => UIActions.RestartDownload();
ApplicationContext.MainWindow.MenuItemMap["restart"].Clicked += (_, _) => UIActions.RestartDownload();
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
private void InProgressContextMenuOpening()
{
foreach (var menu in ApplicationContext.MainWindow.MenuItems)
{
menu.Enabled = false;
}
ApplicationContext.MainWindow.MenuItemMap["delete"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["schedule"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["moveToQueue"].Enabled = true;
var selectedRows = ApplicationContext.MainWindow.SelectedInProgressRows;
if (selectedRows.Count > 1)
{
ApplicationContext.MainWindow.MenuItemMap["pause"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["resume"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["showProgress"].Enabled = true;
}
else if (selectedRows.Count == 1)
{
ApplicationContext.MainWindow.MenuItemMap["showProgress"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["copyURL"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["saveAs"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["refresh"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["properties"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["saveAs"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["saveAs"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["copyURL"].Enabled = true;
var ent = selectedRows[0].DownloadEntry;//selectedRows[0].Cells[1].Value as InProgressDownloadEntry;
if (ent == null) return;
var isActive = ApplicationContext.CoreService.IsDownloadActive(ent.Id);
Log.Debug("Selected item active: " + isActive);
if (isActive)
{
ApplicationContext.MainWindow.MenuItemMap["pause"].Enabled = true;
}
else
{
ApplicationContext.MainWindow.MenuItemMap["resume"].Enabled = true;
ApplicationContext.MainWindow.MenuItemMap["restart"].Enabled = true;
}
}
}
private void FinishedContextMenuOpening()
{
foreach (var menu in ApplicationContext.MainWindow.MenuItems)
{
menu.Enabled = false;
}
ApplicationContext.MainWindow.MenuItemMap["deleteDownloads"].Enabled = true;
var selectedRows = ApplicationContext.MainWindow.SelectedFinishedRows;
if (selectedRows.Count == 1)
{
foreach (var menu in ApplicationContext.MainWindow.MenuItems)
{
menu.Enabled = true;
}
}
}
public void InstallLatestFFmpeg()
{
LaunchUpdater(UpdateMode.FFmpegUpdateOnly);
}
public void InstallLatestYoutubeDL()
{
LaunchUpdater(UpdateMode.YoutubeDLUpdateOnly);
}
public void ShowDownloadSelectionWindow(FileNameFetchMode mode, IEnumerable<IRequestData> downloads)
{
RunOnUiThread(() =>
{
ApplicationContext.PlatformUIService.ShowDownloadSelectionWindow(mode, downloads);
});
}
public IPlatformClipboardMonitor GetPlatformClipboardMonitor()
{
return ApplicationContext.MainWindow.GetClipboardMonitor();
}
}
}

View File

@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Text;
using XDM.Core.BrowserMonitoring;
using XDM.Core.UI;
namespace XDM.Core
{
public static class ApplicationContext
{
private static IApplicationCore? s_ApplicationCore;
private static IApplication? s_IApplication;
private static IApplicationWindow? s_ApplicationWindow;
private static ILinkRefresher? s_LinkRefresher;
private static IVideoTracker? s_VideoTracker;
private static IClipboardMonitor? s_ClipboardMonitor;
private static IPlatformUIService? s_PlatformUIService;
private static bool s_Init = false;
public static event EventHandler? Initialized;
public static event EventHandler<ApplicationEvent>? ApplicationEvent;
public static IApplicationCore CoreService
{
get
{
if (!s_Init)
{
throw new Exception("ApplicationContext is not initialized...");
}
return s_ApplicationCore!;
}
}
public static IApplication Application
{
get
{
if (!s_Init)
{
throw new Exception("ApplicationContext is not initialized...");
}
return s_IApplication!;
}
}
public static IApplicationWindow MainWindow
{
get
{
if (!s_Init)
{
throw new Exception("ApplicationContext is not initialized...");
}
return s_ApplicationWindow!;
}
}
public static ILinkRefresher LinkRefresher
{
get
{
if (!s_Init)
{
throw new Exception("ApplicationContext is not initialized...");
}
return s_LinkRefresher!;
}
}
public static IVideoTracker VideoTracker
{
get
{
if (!s_Init)
{
throw new Exception("ApplicationContext is not initialized...");
}
return s_VideoTracker!;
}
}
public static IClipboardMonitor ClipboardMonitor
{
get
{
if (!s_Init)
{
throw new Exception("ApplicationContext is not initialized...");
}
return s_ClipboardMonitor!;
}
}
public static IPlatformUIService PlatformUIService
{
get
{
if (!s_Init)
{
throw new Exception("ApplicationContext is not initialized...");
}
return s_PlatformUIService!;
}
}
public static void BroadcastConfigChange()
{
ApplicationEvent?.Invoke(null, new ApplicationEvent("ConfigChanged"));
}
public static AppInstanceConfigurer Configurer()
{
return new AppInstanceConfigurer();
}
public class AppInstanceConfigurer
{
public AppInstanceConfigurer RegisterApplicationCore(IApplicationCore service)
{
s_ApplicationCore = service;
return this;
}
public AppInstanceConfigurer RegisterApplication(IApplication service)
{
s_IApplication = service;
return this;
}
public AppInstanceConfigurer RegisterApplicationWindow(IApplicationWindow service)
{
s_ApplicationWindow = service;
return this;
}
public AppInstanceConfigurer RegisterLinkRefresher(ILinkRefresher service)
{
s_LinkRefresher = service;
return this;
}
public AppInstanceConfigurer RegisterCapturedVideoTracker(IVideoTracker service)
{
s_VideoTracker = service;
return this;
}
public AppInstanceConfigurer RegisterClipboardMonitor(IClipboardMonitor service)
{
s_ClipboardMonitor = service;
return this;
}
public AppInstanceConfigurer RegisterPlatformUIService(IPlatformUIService service)
{
s_PlatformUIService = service;
return this;
}
public void Configure()
{
if (s_ApplicationCore == null || s_IApplication == null
|| s_ApplicationWindow == null || s_LinkRefresher == null
|| s_VideoTracker == null || s_ClipboardMonitor == null
|| s_PlatformUIService == null)
{
throw new Exception("Please configure all dependecies");
}
s_Init = true;
Initialized?.Invoke(null, EventArgs.Empty);
}
}
}
public class ApplicationEvent : EventArgs
{
public ApplicationEvent(string eventType, object? data = null)
{
EventType = eventType;
Data = data;
}
public string EventType { get; }
public object? Data { get; set; }
}
}

View File

@ -0,0 +1,818 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using XDM.Core.UI;
using XDM.Core;
using XDM.Core.MediaProcessor;
using XDM.Core.Util;
using XDM.Core.BrowserMonitoring;
using XDM.Core.Collections;
using System.Timers;
using TraceLog;
using Translations;
using XDM.Core.Downloader;
using XDM.Core.Downloader.Progressive.DualHttp;
using XDM.Core.Downloader.Progressive.SingleHttp;
using XDM.Core.Downloader.Adaptive.Hls;
using XDM.Core.Downloader.Adaptive.Dash;
using XDM.Core.Downloader.Progressive;
using XDM.Core.DataAccess;
using XDM.Core.IO;
#if !NET5_0_OR_GREATER
using XDM.Compatibility;
#endif
namespace XDM.Core
{
public class ApplicationCore : IApplicationCore
{
public Version AppVerion => new(8, 0, 0);
private Dictionary<string, (IBaseDownloader Downloader, bool NonInteractive)> liveDownloads = new();
private GenericOrderedDictionary<string, bool> queuedDownloads = new();
private GenericOrderedDictionary<string, IProgressWindow> activeProgressWindows = new();
private Scheduler scheduler;
private Timer awakePingTimer;
public int ActiveDownloadCount { get => liveDownloads.Count + queuedDownloads.Count; }
public ApplicationCore()
{
ApplicationContext.Initialized += AppInstance_Initialized;
}
private void AppInstance_Initialized(object sender, EventArgs e)
{
awakePingTimer = new Timer(60000)
{
AutoReset = true
};
awakePingTimer.Elapsed += (a, b) => PlatformHelper.SendKeepAlivePing();
try
{
QueueManager.Load();
}
catch (Exception ex)
{
Log.Debug(ex, ex.ToString());
}
StartScheduler();
StartBrowserMonitoring();
}
public void StartBrowserMonitoring()
{
BrowserMonitor.RunNativeHostHandler();
BrowserMonitor.RunHttpIpcHandler();
}
public string? StartDownload(
IRequestData downloadInfo,
string fileName,
FileNameFetchMode fileNameFetchMode,
string? targetFolder,
bool startImmediately,
AuthenticationInfo? authentication,
ProxyInfo? proxyInfo,
string? queueId,
bool convertToMp3)
{
Log.Debug($"Starting download: {fileName} {fileNameFetchMode} {convertToMp3}");
IBaseDownloader? http;
switch (downloadInfo)
{
case SingleSourceHTTPDownloadInfo info:
http = new SingleSourceHTTPDownloader(info, authentication: authentication,
proxy: proxyInfo, mediaProcessor: new FFmpegMediaProcessor(),
convertToMp3: convertToMp3);
RequestDataIO.SaveDownloadInfo(http.Id!, info);
break;
case DualSourceHTTPDownloadInfo info:
http = new DualSourceHTTPDownloader(info, authentication: authentication,
proxy: proxyInfo, mediaProcessor: new FFmpegMediaProcessor());
RequestDataIO.SaveDownloadInfo(http.Id!, info);
break;
case MultiSourceHLSDownloadInfo info:
http = new MultiSourceHLSDownloader(info, authentication: authentication,
proxy: proxyInfo, mediaProcessor: new FFmpegMediaProcessor());
RequestDataIO.SaveDownloadInfo(http.Id!, info);
break;
case MultiSourceDASHDownloadInfo info:
http = new MultiSourceDASHDownloader(info, authentication: authentication,
proxy: proxyInfo, mediaProcessor: new FFmpegMediaProcessor());
RequestDataIO.SaveDownloadInfo(http.Id!, info);
break;
default:
Log.Debug("Unknow request info :: skipping download");
return null;
}
if (!string.IsNullOrEmpty(queueId))
{
QueueManager.AddDownloadsToQueue(queueId!, new string[] { http.Id! });
}
http.SetFileName(FileHelper.SanitizeFileName(fileName), fileNameFetchMode);
http.SetTargetDirectory(targetFolder);
StartDownload(http, startImmediately, authentication, proxyInfo);
return http.Id;
}
private void StartDownload(IBaseDownloader download,
bool startImmediately,
AuthenticationInfo? authentication,
ProxyInfo? proxyInfo)
{
if (!awakePingTimer.Enabled)
{
Log.Debug("Starting keep awaik timer");
awakePingTimer.Start();
}
var id = download.Id;
var startType = DownloadStartType.Waiting;
if (!startImmediately)
{
startType = DownloadStartType.Stopped;
}
else if (liveDownloads.Count >= Config.Instance.MaxParallelDownloads)
{
startImmediately = false;
queuedDownloads.Add(id, false);
}
ApplicationContext.Application.AddItemToTop(id, download.TargetFileName, DateTime.Now,
download.FileSize, download.Type, download.FileNameFetchMode,
download.PrimaryUrl?.ToString(), startType, authentication,
proxyInfo);
if (startImmediately)
{
this.liveDownloads.Add(download.Id, (Downloader: download, NonInteractive: false));
download.Started += HandleDownloadStart;
download.Probed += HandleProbeResult;
download.Finished += DownloadFinished;
download.ProgressChanged += DownloadProgressChanged;
download.AssembingProgressChanged += AssembleProgressChanged;
download.Cancelled += DownloadCancelled;
download.Failed += DownloadFailed;
var showProgress = Config.Instance.ShowProgressWindow;
if (showProgress)
{
ApplicationContext.Application.RunOnUiThread(() =>
{
var prgWin = CreateProgressWindow(download);
activeProgressWindows[download.Id] = prgWin;
prgWin.FileNameText = download.TargetFileName;
prgWin.FileSizeText = $"{TextResource.GetText("STAT_DOWNLOADING")} ...";
prgWin.UrlText = download.PrimaryUrl?.ToString() ?? string.Empty;
prgWin.ShowProgressWindow();
});
}
download.Start();
}
else
{
download.SaveForLater();
}
}
public void AddBatchLinks(List<Message> messages)
{
var list = new List<IRequestData>(messages.Count);
foreach (var message in messages)
{
var url = message.Url;
if (string.IsNullOrEmpty(url)) continue;
var file = FileHelper.SanitizeFileName(message.File ?? FileHelper.GetFileName(new Uri(message.Url)));
var si = new SingleSourceHTTPDownloadInfo
{
Uri = url,
File = file,
Headers = message?.RequestHeaders,
Cookies = message?.Cookies
};
list.Add(si);
}
ApplicationContext.Application.ShowDownloadSelectionWindow(FileNameFetchMode.FileNameAndExtension, list);
}
public void AddDownload(Message message)
{
if (ApplicationContext.LinkRefresher.LinkAccepted(message)) return;
if (Config.Instance.StartDownloadAutomatically)
{
var url = message.Url;
var file = FileHelper.SanitizeFileName(message.File ?? FileHelper.GetFileName(new Uri(message.Url)));
StartDownload(
new SingleSourceHTTPDownloadInfo
{
Uri = url,
File = file,
Headers = message?.RequestHeaders,
Cookies = message?.Cookies
},
file,
FileNameFetchMode.FileNameAndExtension,
null,
true,
null,
Config.Instance.Proxy, null, false);
}
else
{
Log.Debug("Adding download");
ApplicationContext.Application.ShowNewDownloadDialog(message);
}
}
public void ResumeNonInteractiveDownloads(IEnumerable<string> idList)
{
foreach (var id in idList)
{
var entry = AppDB.Instance.Downloads.GetDownloadById(id);// ApplicationContext.Current.GetInProgressDownloadEntry(id);
if (entry != null)
{
ResumeDownload(new Dictionary<string, DownloadItemBase> { [id] = entry }, true);
}
}
}
public void ResumeDownload(Dictionary<string, DownloadItemBase> list,
bool nonInteractive = false)
{
if (!awakePingTimer.Enabled)
{
Log.Debug("Starting keep awake timer");
awakePingTimer.Start();
}
foreach (var item in list)
{
if (liveDownloads.ContainsKey(item.Key) || queuedDownloads.ContainsKey(item.Key)) return;
if (liveDownloads.Count >= Config.Instance.MaxParallelDownloads)
{
queuedDownloads.Add(item.Key, nonInteractive);
ApplicationContext.Application.RunOnUiThread(() =>
{
ApplicationContext.Application.SetDownloadStatusWaiting(item.Key);
Log.Debug("Setting status waiting...");
});
continue;
}
IBaseDownloader? download = null;
switch (item.Value.DownloadType)
{
case "Http":
download = new SingleSourceHTTPDownloader((string)item.Key,
mediaProcessor: new FFmpegMediaProcessor());
break;
case "Dash":
download = new DualSourceHTTPDownloader((string)item.Key,
mediaProcessor: new FFmpegMediaProcessor());
break;
case "Hls":
download = new MultiSourceHLSDownloader(item.Key,
mediaProcessor: new FFmpegMediaProcessor());
break;
case "Mpd-Dash":
download = new MultiSourceDASHDownloader(item.Key,
mediaProcessor: new FFmpegMediaProcessor());
break;
default:
continue;
}
download.Started += HandleDownloadStart;
download.Probed += HandleProbeResult;
download.Finished += DownloadFinished;
download.ProgressChanged += DownloadProgressChanged;
download.AssembingProgressChanged += AssembleProgressChanged;
download.Cancelled += DownloadCancelled;
download.Failed += DownloadFailed;
download.SetTargetDirectory(item.Value.TargetDir);
download.SetFileName(item.Value.Name, item.Value.FileNameFetchMode);
liveDownloads[item.Key] = (Downloader: download, NonInteractive: nonInteractive);
var showProgressWindow = Config.Instance.ShowProgressWindow;
if (showProgressWindow && !nonInteractive)
{
var prgWin = GetProgressWindow(download);// CreateOrGetProgressWindow(download);
ApplicationContext.Application.RunOnUiThread(() =>
{
if (prgWin == null)
{
prgWin = CreateProgressWindow(download);
activeProgressWindows[download.Id] = prgWin;
}
prgWin.FileNameText = download.TargetFileName;
prgWin.FileSizeText = $"{TextResource.GetText("STAT_DOWNLOADING")} ...";
prgWin.DownloadStarted();
prgWin.ShowProgressWindow();
});
}
liveDownloads[item.Key].Downloader.Resume();
}
}
public void ShowProgressWindow(string downloadId)
{
try
{
if (!liveDownloads.ContainsKey(downloadId))
{
return;
}
var downloader = liveDownloads[downloadId].Downloader;
var prgWin = CreateOrGetProgressWindow(downloader);
prgWin.FileNameText = downloader.TargetFileName;
prgWin.FileSizeText = $"{TextResource.GetText("STAT_DOWNLOADING")} ...";
prgWin.ShowProgressWindow();
}
catch (Exception ex)
{
Log.Debug(ex, "Error showing progress window");
}
}
public void StopDownloads(IEnumerable<string> list, bool closeProgressWindow = false)
{
var ids = new List<string>(list);
foreach (var id in ids)
{
(var http, _) = liveDownloads.GetValueOrDefault(id);
if (http != null)
{
http.Stop();
//liveDownloads.Remove(id);
}
else
{
if (queuedDownloads.ContainsKey(id))
{
queuedDownloads.Remove(id);
ApplicationContext.Application.DownloadCanelled(id);
if (activeProgressWindows.ContainsKey(id))
{
var prgWin = activeProgressWindows[id];
prgWin.DownloadCancelled();
}
}
}
if (activeProgressWindows.ContainsKey(id) && closeProgressWindow)
{
var prgWin = activeProgressWindows[id];
activeProgressWindows.Remove(id);
prgWin.DestroyWindow();
Log.Debug("Progress window removed");
}
};
}
void DownloadProgressChanged(object source, ProgressResultEventArgs args)
{
lock (this)
{
var http = source as IBaseDownloader;
ApplicationContext.Application.UpdateProgress(http.Id, args.Progress, args.DownloadSpeed, args.Eta);
if (activeProgressWindows.ContainsKey(http.Id))
{
var prgWin = activeProgressWindows[http.Id];
prgWin.DownloadProgress = args.Progress;
prgWin.FileSizeText = $"{TextResource.GetText("STAT_DOWNLOADING")} {FormattingHelper.FormatSize(args.Downloaded)} / {FormattingHelper.FormatSize(http.FileSize)}";
prgWin.DownloadSpeedText = FormattingHelper.FormatSize((long)args.DownloadSpeed) + "/s";
prgWin.DownloadETAText = $"{TextResource.GetText("MSG_TIME_LEFT")}: {FormattingHelper.ToHMS(args.Eta)}";
}
}
}
void AssembleProgressChanged(object source, ProgressResultEventArgs args)
{
lock (this)
{
var http = source as IBaseDownloader;
if (activeProgressWindows.ContainsKey(http.Id))
{
var prgWin = activeProgressWindows[http.Id];
prgWin.DownloadProgress = args.Progress;
prgWin.FileSizeText = $"{TextResource.GetText("STAT_ASSEMBLING")} {FormattingHelper.FormatSize(args.Downloaded)} / {FormattingHelper.FormatSize(http.FileSize)}";
prgWin.DownloadSpeedText = "---";
prgWin.DownloadETAText = "---";
}
}
}
void DownloadFinished(object source, EventArgs args)
{
lock (this)
{
var http = source as IBaseDownloader;
DetachEventHandlers(http);
ApplicationContext.Application.DownloadFinished(http.Id, http.FileSize < 0 ? new FileInfo(http.TargetFile).Length : http.FileSize, http.TargetFile);
var showCompleteDialog = false;
if (liveDownloads.ContainsKey(http.Id))
{
(_, bool nonInteractive) = liveDownloads[http.Id];
liveDownloads.Remove(http.Id);
if (!nonInteractive && Config.Instance.ShowDownloadCompleteWindow)
{
showCompleteDialog = true;
}
}
if (activeProgressWindows.ContainsKey(http.Id))
{
var prgWin = activeProgressWindows[http.Id];
activeProgressWindows.Remove(http.Id);
prgWin.DownloadId = null;
prgWin.DestroyWindow();
}
if (showCompleteDialog)
{
ApplicationContext.Application.ShowDownloadCompleteDialog(http.TargetFileName, Path.GetDirectoryName(http.TargetFile));
}
if (Config.Instance.ScanWithAntiVirus)
{
PlatformHelper.RunAntivirus(Config.Instance.AntiVirusExecutable, Config.Instance.AntiVirusArgs, http.TargetFile);
}
Helpers.RunGC();
ProcessNextQueuedItem();
}
}
void DownloadFailed(object source, DownloadFailedEventArgs args)
{
lock (this)
{
Log.Debug("Download failed: " + args.ErrorCode);
var http = source as IBaseDownloader;
DetachEventHandlers(http);
liveDownloads.Remove(http.Id);
ApplicationContext.Application.DownloadFailed(http.Id);
if (activeProgressWindows.ContainsKey(http.Id))
{
var prgWin = activeProgressWindows[http.Id];
prgWin.DownloadFailed(new ErrorDetails { Message = ErrorMessages.GetLocalizedErrorMessage(args.ErrorCode) });
}
Helpers.RunGC();
ProcessNextQueuedItem();
}
}
void DownloadCancelled(object source, EventArgs args)
{
lock (this)
{
Log.Debug("Download cancelled");
var http = source as IBaseDownloader;
DetachEventHandlers(http);
liveDownloads.Remove(http.Id);
ApplicationContext.Application.DownloadCanelled(http.Id);
if (activeProgressWindows.ContainsKey(http.Id))
{
var prgWin = activeProgressWindows[http.Id];
prgWin.DownloadCancelled();
}
Helpers.RunGC();
ProcessNextQueuedItem();
}
}
void HandleProbeResult(object source, EventArgs args)
{
lock (this)
{
var http = source as IBaseDownloader;
ApplicationContext.Application.UpdateItem(http.Id, http.TargetFileName, http.FileSize > 0 ? http.FileSize : 0);
if (activeProgressWindows.ContainsKey(http.Id))
{
var prgWin = activeProgressWindows[http.Id];
prgWin.FileNameText = http.TargetFileName;
prgWin.FileSizeText =
$"{TextResource.GetText("STAT_DOWNLOADING")} {FormattingHelper.FormatSize(0)} / {FormattingHelper.FormatSize(http.FileSize)}";
}
}
}
void HandleDownloadStart(object source, EventArgs args)
{
lock (this)
{
var http = source as IBaseDownloader;
ApplicationContext.Application.DownloadStarted(http.Id);
}
}
private IProgressWindow? GetProgressWindow(IBaseDownloader downloader)
{
IProgressWindow? prgWin = null;
#pragma warning disable CS8604 // Possible null reference argument.
if (activeProgressWindows.ContainsKey(downloader.Id))
#pragma warning restore CS8604 // Possible null reference argument.
{
prgWin = activeProgressWindows[downloader.Id];
}
return prgWin;
}
private IProgressWindow CreateOrGetProgressWindow(IBaseDownloader downloader)
{
IProgressWindow prgWin = null;
if (activeProgressWindows.ContainsKey(downloader.Id))
{
prgWin = activeProgressWindows[downloader.Id];
}
else
{
prgWin = CreateProgressWindow(downloader);
activeProgressWindows[downloader.Id] = prgWin;
}
return prgWin;
}
private IProgressWindow CreateProgressWindow(IBaseDownloader downloader)
{
var prgWin = ApplicationContext.Application.CreateProgressWindow(downloader.Id);
prgWin.UrlText = ApplicationContext.Application.GetInProgressDownloadEntry(downloader.Id)?.PrimaryUrl;
prgWin.DownloadSpeedText = "---";
prgWin.DownloadETAText = "---";
prgWin.FileSizeText = "---";
return prgWin;
}
public bool IsDownloadActive(string id)
{
return liveDownloads.ContainsKey(id) || queuedDownloads.ContainsKey(id);
}
private void ProcessNextQueuedItem()
{
if (queuedDownloads.Count > 0)
{
var kv = queuedDownloads.First();
queuedDownloads.Remove(kv.Key);
var entry = AppDB.Instance.Downloads.GetDownloadById(kv.Key);// ApplicationContext.Current.GetInProgressDownloadEntry(kv.Key);
if (entry != null)
{
ResumeDownload(new Dictionary<string, DownloadItemBase> { [kv.Key] = entry }, kv.Value);
}
}
else
{
if (Config.Instance.ShutdownAfterAllFinished)
{
PlatformHelper.ShutDownPC();
}
if (awakePingTimer.Enabled)
{
Log.Debug("Stopping keep awake timer");
awakePingTimer.Stop();
}
if (Config.Instance.RunCommandAfterCompletion)
{
PlatformHelper.RunCommand(Config.Instance.AfterCompletionCommand);
}
}
}
public void StartScheduler()
{
this.scheduler = new Scheduler();
this.scheduler.Start();
}
public void RenameDownload(string id, string folder, string file)
{
if (liveDownloads.ContainsKey(id))
{
var downloader = liveDownloads[id].Downloader;
downloader.SetTargetDirectory(folder);
downloader.SetFileName(file, downloader.FileNameFetchMode);
}
ApplicationContext.Application.RenameFileOnUI(id, folder, file);
}
private void DetachEventHandlers(IBaseDownloader download)
{
try
{
download.Started -= HandleDownloadStart;
download.Probed -= HandleProbeResult;
download.Finished -= DownloadFinished;
download.ProgressChanged -= DownloadProgressChanged;
download.AssembingProgressChanged += AssembleProgressChanged;
download.Cancelled -= DownloadCancelled;
download.Failed -= DownloadFailed;
}
catch { }
}
public AuthenticationInfo? PromptForCredential(string id, string message)
{
try
{
if (liveDownloads[id].NonInteractive)
{
return null;
}
return ApplicationContext.Application.PromtForCredentials(message);
}
catch { }
return null;
}
public void HideProgressWindow(string id)
{
if (activeProgressWindows.ContainsKey(id))
{
var prgWin = activeProgressWindows[id];
activeProgressWindows.Remove(id);
prgWin.DestroyWindow();
}
}
public string? GetPrimaryUrl(DownloadItemBase entry)
{
if (entry == null) return null;
switch (entry.DownloadType)
{
case "Http":
var h1 = RequestDataIO.LoadSingleSourceHTTPDownloadInfo(entry.Id);// LoadInfo<SingleSourceHTTPDownloadInfo>(entry.Id);
if (h1 != null)
{
return h1.Uri;
}
break;
case "Dash":
var h2 = RequestDataIO.LoadDualSourceHTTPDownloadInfo(entry.Id);// LoadInfo<DualSourceHTTPDownloadInfo>(entry.Id);
if (h2 != null)
{
return h2.Uri1;
}
break;
case "Hls":
var hls = RequestDataIO.LoadMultiSourceHLSDownloadInfo(entry.Id);// LoadInfo<MultiSourceHLSDownloadInfo>(entry.Id);
if (hls != null)
{
return hls.VideoUri;
}
break;
case "Mpd-Dash":
var dash = RequestDataIO.LoadMultiSourceDASHDownloadInfo(entry.Id); //LoadInfo<MultiSourceDASHDownloadInfo>(entry.Id);
if (dash != null)
{
return dash.Url;
}
break;
}
return null;
}
public void RemoveDownload(DownloadItemBase entry, bool deleteDownloadedFile)
{
try
{
if (entry == null) return;
string? tempDir = null;
var validEntry = false;
switch (entry.DownloadType)
{
case "Http":
var h1 = DownloadStateIO.LoadSingleSourceHTTPDownloaderState(entry.Id);
if (h1 != null)
{
tempDir = h1.TempDir;
validEntry = true;
}
break;
case "Dash":
var h2 = DownloadStateIO.LoadDualSourceHTTPDownloaderState(entry.Id);
if (h2 != null)
{
tempDir = h2.TempDir;
validEntry = true;
}
break;
case "Hls":
var hls = DownloadStateIO.LoadMultiSourceHLSDownloadState(entry.Id);
if (hls != null)
{
tempDir = hls.TempDirectory;
validEntry = true;
}
break;
case "Mpd-Dash":
var dash = DownloadStateIO.LoadMultiSourceDASHDownloadState(entry.Id);
if (dash != null)
{
tempDir = dash.TempDirectory;
validEntry = true;
}
break;
}
if (validEntry)
{
var infoFile = Path.Combine(Config.DataDir, entry.Id + ".info");
var stateFile = Path.Combine(Config.DataDir, entry.Id + ".state");
if (Directory.Exists(tempDir) && !string.IsNullOrEmpty(tempDir))
{
Directory.Delete(tempDir, true);
}
if (File.Exists(stateFile))
{
File.Delete(stateFile);
}
if (File.Exists(infoFile))
{
File.Delete(infoFile);
}
if (entry is FinishedDownloadItem && deleteDownloadedFile)
{
var outFile = Path.Combine(entry.TargetDir, entry.Name);
if (File.Exists(outFile))
{
File.Delete(outFile);
}
}
}
}
catch (Exception ex)
{
Log.Debug(ex, "Error deleting temp folder");
}
}
public void RestartDownload(DownloadItemBase entry)
{
if (entry == null) return;
var convertToMp3 = false;
IRequestData? request;
try
{
switch (entry.DownloadType)
{
case "Http":
var info = RequestDataIO.LoadSingleSourceHTTPDownloadInfo(entry.Id);
request = info;
convertToMp3 = info?.ConvertToMp3 ?? false;
break;
case "Dash":
request = RequestDataIO.LoadDualSourceHTTPDownloadInfo(entry.Id);
break;
case "Hls":
request = RequestDataIO.LoadMultiSourceHLSDownloadInfo(entry.Id);
break;
case "Mpd-Dash":
request = RequestDataIO.LoadMultiSourceDASHDownloadInfo(entry.Id);
break;
default:
request = null;
break;
}
if (request != null)
{
this.StartDownload(request, entry.Name,
FileNameFetchMode.FileNameAndExtension,
entry.TargetDir, true, entry.Authentication, entry.Proxy, null,
convertToMp3);
RemoveDownload(entry, false);
}
}
catch (Exception ex)
{
Log.Debug(ex, "Error restarting download");
}
}
public void Export(string path)
{
ImportExport.Export(path);
}
public void Import(string path)
{
ImportExport.Import(path);
ApplicationContext.Application.ShowMessageBox(null, TextResource.GetText("MSG_IMPORT_DONE"));
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XDM.Core;
namespace XDM.Core
{
public static class ArgsProcessor
{
public static void Process(Dictionary<string, string?> args)
{
if (args.ContainsKey("-u"))
{
var url = args["-u"];
if (!string.IsNullOrEmpty(url))
{
ApplicationContext.CoreService.AddDownload(new Message { Url = url! });
}
}
}
public static Dictionary<string, string?> ParseArgs(string[] args, int start = 0)
{
var options = new Dictionary<string, string?>();
var key = string.Empty;
for (int i = start; i < args.Length; i++)
{
var arg = args[i];
if (key != string.Empty)
{
options[key] = arg;
key = string.Empty;
}
else
{
key = arg;
options[key] = null;
}
}
return options;
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace XDM.Core
{
public struct AuthenticationInfo
{
public string UserName { get; set; }
public string Password { get; set; }
}
}

View File

@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TraceLog;
using XDM.Core;
using XDM.Core.Util;
namespace XDM.Core.BrowserMonitoring
{
internal static class BrowserMessageHandler
{
internal static void Handle(RawBrowserMessageEnvelop envelop)
{
//Log.Debug("Type: " + envelop.MessageType);
if (envelop.MessageType == "videoIds")
{
foreach (var item in envelop.VideoIds)
{
ApplicationContext.VideoTracker.AddVideoDownload(item);
}
return;
}
if (envelop.MessageType == "clear")
{
ApplicationContext.VideoTracker.ClearVideoList();
return;
}
if (envelop.MessageType == "sync")
{
return;
}
if (envelop.MessageType == "custom")
{
var args = ArgsProcessor.ParseArgs(envelop.CustomData.Split('\r'));
ArgsProcessor.Process(args);
return;
}
var rawMessage = envelop.Message;
if (rawMessage == null && envelop.Messages == null)
{
Log.Debug("Raw message/messages is null");
return;
};
switch (envelop.MessageType)
{
case "download":
{
var message = Parse(rawMessage);
if (!(Helpers.IsBlockedHost(message.Url) || Helpers.IsCompressedJSorCSS(message.Url)))
{
ApplicationContext.CoreService.AddDownload(message);
}
break;
}
case "links":
{
var messages = new List<Message>(envelop.Messages.Length);
foreach (var msg in envelop.Messages)
{
var message = Parse(msg);
messages.Add(message);
}
ApplicationContext.CoreService.AddBatchLinks(messages);
break;
}
case "video":
{
var message = Parse(rawMessage);
var contentType = message.GetResponseHeaderFirstValue("Content-Type");
if (VideoUrlHelper.IsYtFormat(contentType))
{
VideoUrlHelper.ProcessPostYtFormats(message);
}
//if (VideoUrlHelper.IsFBFormat(contentType, message.Url))
//{
// VideoUrlHelper.ProcessPostFBFormats(message, ApplicationContext.Core);
//}
if (VideoUrlHelper.IsHLS(contentType))
{
VideoUrlHelper.ProcessHLSVideo(message);
}
if (VideoUrlHelper.IsDASH(contentType))
{
VideoUrlHelper.ProcessDashVideo(message);
}
if (!VideoUrlHelper.ProcessYtDashSegment(message))
{
if (VideoUrlHelper.IsNormalVideo(contentType, message.Url, message.GetContentLength()))
{
VideoUrlHelper.ProcessNormalVideo(message);
}
}
break;
}
}
}
private static string GetFileName(string text)
{
try
{
return Uri.UnescapeDataString(text);
}
catch { }
return text;
}
internal static Message Parse(RawBrowserMessage rawMessage)
{
var message = new Message
{
File = GetFileName(rawMessage.File),
Url = rawMessage.Url,
RequestMethod = rawMessage.Method ?? "GET",
RequestBody = rawMessage.RequestBody
};
var cookies = new List<string>();
if (rawMessage.RequestHeaders != null && rawMessage.RequestHeaders.Count > 0)
{
foreach (var key in rawMessage.RequestHeaders.Keys)
{
if (string.IsNullOrEmpty(key)) continue;
if (key.Equals("cookie", StringComparison.InvariantCultureIgnoreCase))
{
cookies.AddRange(rawMessage.RequestHeaders[key]);
}
var invalidHeader = IsBlockedHeader(key);
if (key.Equals("content-type", StringComparison.InvariantCultureIgnoreCase) &&
!string.IsNullOrEmpty(rawMessage.RequestBody))
{
invalidHeader = false;
}
if (!invalidHeader)
{
message.RequestHeaders.Add(key, rawMessage.RequestHeaders[key]);
}
}
}
var cookieSet = new HashSet<string>();
foreach (var cookie in cookies)
{
foreach (var item in cookie.Split(';'))
{
var value = item.Trim();
if (!string.IsNullOrEmpty(value))
{
cookieSet.Add(value);
}
}
}
if (rawMessage.ResponseHeaders != null && rawMessage.ResponseHeaders.Count > 0)
{
foreach (var key in rawMessage.ResponseHeaders.Keys)
{
if (!string.IsNullOrEmpty(key))
{
message.ResponseHeaders.Add(key, rawMessage.ResponseHeaders[key]);
}
}
}
if (rawMessage.Cookies != null && rawMessage.Cookies.Count > 0)
{
foreach (var key in rawMessage.Cookies.Keys)
{
if (!string.IsNullOrEmpty(key))
{
cookieSet.Add(key + "=" + rawMessage.Cookies[key]);
}
}
}
if (cookieSet.Count > 0)
{
message.Cookies.Add("Cookie", Helpers.MakeCookieString(cookieSet));
}
if (!message.RequestHeaders.ContainsKey("User-Agent") && message.ResponseHeaders.ContainsKey("realUA"))
{
message.ResponseHeaders["User-Agent"] = message.ResponseHeaders["realUA"];
}
return message;
}
private static bool IsBlockedHeader(string header) =>
blockedHeaders.Any(blockedHeader => (header?.ToLowerInvariant() ?? string.Empty).StartsWith(blockedHeader));
private static string[] blockedHeaders = { "accept", "if", "authorization", "proxy", "connection", "expect", "te",
"upgrade", "range", "transfer-encoding", "content-type", "content-length","content-encoding" ,"accept-encoding"};
}
}

View File

@ -0,0 +1,19 @@
using XDM.Core;
namespace XDM.Core.BrowserMonitoring
{
public static class BrowserMonitor
{
public static void RunHttpIpcHandler()
{
var handler = new IpcHttpHandler();
handler.StartHttpIpcChannel();
}
public static void RunNativeHostHandler()
{
var handler = new NativeMessagingHostHandler();
handler.StartPipedChannel();
}
}
}

View File

@ -0,0 +1,242 @@
using System;
using System.Collections.Generic;
using System.Text;
using TraceLog;
using XDM.Core.Collections;
using XDM.Core.Downloader;
using XDM.Core.Downloader.Adaptive.Dash;
using XDM.Core.Downloader.Adaptive.Hls;
using XDM.Core.Downloader.Progressive.DualHttp;
using XDM.Core.Downloader.Progressive.SingleHttp;
using XDM.Core.Util;
namespace XDM.Core.BrowserMonitoring
{
public class VideoTracker : IVideoTracker
{
private GenericOrderedDictionary<string, (DualSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> ytVideoList = new();
private GenericOrderedDictionary<string, (SingleSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> videoList = new();
private GenericOrderedDictionary<string, (MultiSourceHLSDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> hlsVideoList = new();
private GenericOrderedDictionary<string, (MultiSourceDASHDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> dashVideoList = new();
public void ClearVideoList()
{
ytVideoList.Clear();
hlsVideoList.Clear();
dashVideoList.Clear();
videoList.Clear();
ApplicationContext.BroadcastConfigChange();
}
public bool IsFFmpegRequiredForDownload(string id)
{
return ytVideoList.ContainsKey(id) || dashVideoList.ContainsKey(id) || hlsVideoList.ContainsKey(id);
}
public void StartVideoDownload(string videoId,
string name,
string? folder,
bool startImmediately,
AuthenticationInfo? authentication,
ProxyInfo? proxyInfo,
int maxSpeedLimit,
string? queueId,
bool convertToMp3 = false //only applicable for dual source http downloads
)
{
//IBaseDownloader downloader = null;
if (ytVideoList.ContainsKey(videoId))
{
ApplicationContext.CoreService.StartDownload(ytVideoList[videoId].Info, name, FileNameFetchMode.ExtensionOnly,
folder, startImmediately, authentication, proxyInfo, Helpers.GetSpeedLimit(), queueId);
}
else if (videoList.ContainsKey(videoId))
{
ApplicationContext.CoreService.StartDownload(videoList[videoId].Info, name, convertToMp3 ? FileNameFetchMode.None : FileNameFetchMode.ExtensionOnly,
folder, startImmediately, authentication, proxyInfo, Helpers.GetSpeedLimit(), queueId, convertToMp3);
}
else if (hlsVideoList.ContainsKey(videoId))
{
ApplicationContext.CoreService.StartDownload(hlsVideoList[videoId].Info, name, FileNameFetchMode.ExtensionOnly,
folder, startImmediately, authentication, proxyInfo, Helpers.GetSpeedLimit(), queueId);
}
else if (dashVideoList.ContainsKey(videoId))
{
ApplicationContext.CoreService.StartDownload(dashVideoList[videoId].Info, name, FileNameFetchMode.ExtensionOnly,
folder, startImmediately, authentication, proxyInfo, Helpers.GetSpeedLimit(), queueId);
}
}
public List<(string ID, string File, string DisplayName, DateTime Time)> GetVideoList(bool encode = true)
{
lock (this)
{
var list = new List<(string ID, string File, string DisplayName, DateTime Time)>();
foreach (var e in ytVideoList)
{
list.Add((e.Key, encode ? Helpers.EncodeToCharCode(e.Value.Info.File) : e.Value.Info.File, e.Value.DisplayInfo.Quality, e.Value.DisplayInfo.CreationTime));
}
foreach (var e in videoList)
{
list.Add((e.Key, encode ? Helpers.EncodeToCharCode(e.Value.Info.File) : e.Value.Info.File, e.Value.DisplayInfo.Quality, e.Value.DisplayInfo.CreationTime));
}
foreach (var e in hlsVideoList)
{
list.Add((e.Key, encode ? Helpers.EncodeToCharCode(e.Value.Info.File) : e.Value.Info.File, e.Value.DisplayInfo.Quality, e.Value.DisplayInfo.CreationTime));
}
foreach (var e in dashVideoList)
{
list.Add((e.Key, encode ? Helpers.EncodeToCharCode(e.Value.Info.File) : e.Value.Info.File, e.Value.DisplayInfo.Quality, e.Value.DisplayInfo.CreationTime));
}
list.Sort((a, b) => a.Time.CompareTo(b.Time));
return list;
}
}
public void AddVideoNotifications(IEnumerable<(DualSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> notifications)
{
lock (this)
{
foreach (var info in notifications)
{
ytVideoList.Add(Guid.NewGuid().ToString(), info);
}
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotifications(IEnumerable<(SingleSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> notifications)
{
lock (this)
{
foreach (var info in notifications)
{
videoList.Add(Guid.NewGuid().ToString(), info);
}
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotifications(IEnumerable<(MultiSourceHLSDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> notifications)
{
lock (this)
{
foreach (var info in notifications)
{
hlsVideoList.Add(Guid.NewGuid().ToString(), info);
}
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotifications(IEnumerable<(MultiSourceDASHDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> notifications)
{
lock (this)
{
foreach (var info in notifications)
{
dashVideoList.Add(Guid.NewGuid().ToString(), info);
}
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotification(StreamingVideoDisplayInfo displayInfo, DualSourceHTTPDownloadInfo info)
{
lock (this)
{
ytVideoList.Add(Guid.NewGuid().ToString(), (info, displayInfo));
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotification(StreamingVideoDisplayInfo displayInfo, SingleSourceHTTPDownloadInfo info)
{
lock (this)
{
videoList.Add(Guid.NewGuid().ToString(), (info, displayInfo));
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotification(StreamingVideoDisplayInfo displayInfo, MultiSourceHLSDownloadInfo info)
{
lock (this)
{
var id = Guid.NewGuid().ToString();
hlsVideoList.Add(id, (info, displayInfo));
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotification(StreamingVideoDisplayInfo displayInfo, MultiSourceDASHDownloadInfo info)
{
lock (this)
{
var id = Guid.NewGuid().ToString();
Log.Debug("DASH video added with id: " + id);
dashVideoList.Add(id, (info, displayInfo));
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoDownload(string videoId)
{
var name = string.Empty;
var size = 0L;
var contentType = string.Empty;
var valid = false;
if (ytVideoList.ContainsKey(videoId))
{
if (ApplicationContext.LinkRefresher.LinkAccepted(ytVideoList[videoId].Info)) return;
name = ytVideoList[videoId].Info.File;
size = ytVideoList[videoId].DisplayInfo.Size;
contentType = ytVideoList[videoId].Info.ContentType1;
valid = true;
}
else if (videoList.ContainsKey(videoId))
{
if (ApplicationContext.LinkRefresher.LinkAccepted(videoList[videoId].Info)) return;
name = videoList[videoId].Info.File;
size = videoList[videoId].DisplayInfo.Size;
contentType = videoList[videoId].Info.ContentType;
valid = true;
}
else if (hlsVideoList.ContainsKey(videoId))
{
Log.Debug("Download HLS video added with id: " + videoId);
name = hlsVideoList[videoId].Info.File;
valid = true;
try
{
contentType = hlsVideoList[videoId].Info.ContentType;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
else if (dashVideoList.ContainsKey(videoId))
{
Log.Debug("Download DASH video added with id: " + videoId);
name = dashVideoList[videoId].Info.File;
contentType = dashVideoList[videoId].Info.ContentType;
valid = true;
}
if (valid)
{
if (Config.Instance.StartDownloadAutomatically)
{
StartVideoDownload(
videoId, Helpers.SanitizeFileName(name),
null, true, null, Config.Instance.Proxy,
Helpers.GetSpeedLimit(), null);
}
else
{
ApplicationContext.Application.ShowVideoDownloadDialog(videoId, name, size, contentType);
}
}
}
}
}

View File

@ -0,0 +1,156 @@
using System;
using System.Text;
using System.Net;
using System.Linq;
using Newtonsoft.Json;
using XDM.Core;
using XDM.Core.Util;
using XDM.Core.HttpServer;
using System.Threading;
using TraceLog;
using Translations;
namespace XDM.Core.BrowserMonitoring
{
public class IpcHttpHandler
{
private NanoServer server;
public IpcHttpHandler()
{
server = new NanoServer(IPAddress.Loopback, 9614);
server.RequestReceived += (sender, args) =>
{
HandleRequest(args.RequestContext);
};
}
public void StartHttpIpcChannel()
{
new Thread(() =>
{
try
{
server.Start();
}
catch (Exception ex)
{
Log.Debug(ex.ToString());
ApplicationContext.Application.ShowMessageBox(null, TextResource.GetText("MSG_ALREADY_RUNNING"));
}
}).Start();
}
public void HandleRequest(RequestContext context)
{
if (context.RequestPath == "/204")
{
context.ResponseStatus = new ResponseStatus
{
StatusCode = 204,
StatusMessage = "No Content"
};
context.AddResponseHeader("Cache-Control", "max-age=0, no-cache, must-revalidate");
context.SendResponse();
return;
}
try
{
switch (context.RequestPath)
{
case "/download":
{
var text = Encoding.UTF8.GetString(context.RequestBody!);
Log.Debug(text);
var message = Message.ParseMessage(text);
if (!(Helpers.IsBlockedHost(message.Url) || Helpers.IsCompressedJSorCSS(message.Url)))
{
ApplicationContext.CoreService.AddDownload(message);
}
break;
}
case "/video":
{
var text = Encoding.UTF8.GetString(context.RequestBody!);
Log.Debug(text);
var message2 = Message.ParseMessage(Encoding.UTF8.GetString(context.RequestBody!));
var contentType = message2.GetResponseHeaderFirstValue("Content-Type")?.ToLowerInvariant() ?? string.Empty;
if (VideoUrlHelper.IsHLS(contentType))
{
VideoUrlHelper.ProcessHLSVideo(message2);
}
if (VideoUrlHelper.IsDASH(contentType))
{
VideoUrlHelper.ProcessDashVideo(message2);
}
if (!VideoUrlHelper.ProcessYtDashSegment(message2))
{
if (contentType != null && !(contentType.Contains("f4f") ||
contentType.Contains("m4s") ||
contentType.Contains("mp2t") || message2.Url.Contains("abst") ||
message2.Url.Contains("f4x") || message2.Url.Contains(".fbcdn")
|| message2.Url.Contains("http://127.0.0.1:9614")))
{
VideoUrlHelper.ProcessNormalVideo(message2);
}
}
break;
}
case "/links":
{
var text = Encoding.UTF8.GetString(context.RequestBody!);
Log.Debug(text);
var arr = text.Split(new string[] { "\r\n\r\n" }, StringSplitOptions.RemoveEmptyEntries);
ApplicationContext.CoreService.AddBatchLinks(arr.Select(str => Message.ParseMessage(str.Trim())).ToList());
break;
}
case "/item":
{
foreach (var item in Encoding.UTF8.GetString(context.RequestBody!).Split(new char[] { '\r', '\n' }))
{
ApplicationContext.VideoTracker.AddVideoDownload(item);
}
break;
}
case "/clear":
ApplicationContext.VideoTracker.ClearVideoList();
break;
}
}
finally
{
SendSyncResponse(context);
}
}
private void SendSyncResponse(RequestContext context)
{
var resp = new
{
enabled = Config.Instance.IsBrowserMonitoringEnabled,
blockedHosts = new string[0],
videoUrls = new string[0],
fileExts = Config.Instance.FileExtensions,
vidExts = Config.Instance.VideoExtensions,
vidList = ApplicationContext.VideoTracker.GetVideoList().Select(a => new
{
id = a.ID,
text = a.File,
info = a.DisplayName
}).ToList(),
mimeList = new string[] { "video", "audio", "mpegurl", "f4m", "m3u8", "dash" }
};
var json = JsonConvert.SerializeObject(resp);
context.ResponseStatus = new ResponseStatus
{
StatusCode = 200,
StatusMessage = "OK"
};
context.AddResponseHeader("Content-Type", "application/json");
context.AddResponseHeader("Cache-Control", "max-age=0, no-cache, must-revalidate");
context.ResponseBody = Encoding.UTF8.GetBytes(json);
context.SendResponse();
}
}
}

View File

@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Newtonsoft.Json;
namespace XDM.Core.BrowserMonitoring
{
public class JsonMessageParser
{
private T? ReadProperty<T>(JsonTextReader reader, string name)
{
if (reader.TokenType == JsonToken.PropertyName && reader.Value?.ToString() == name &&
reader.Read() && reader.Value != null)
{
return (T)reader.Value;
}
return default(T);
}
private bool IsObjectStart(JsonTextReader reader, string name)
{
return reader.TokenType == JsonToken.PropertyName && reader.Value?.ToString() == name &&
reader.Read() && reader.TokenType == JsonToken.StartObject;
}
private bool IsListStart(JsonTextReader reader, string name)
{
return reader.TokenType == JsonToken.PropertyName && reader.Value?.ToString() == name &&
reader.Read() && reader.TokenType == JsonToken.StartArray;
}
private void SkipUnknownParts(JsonTextReader reader)
{
if (reader.TokenType == JsonToken.PropertyName && reader.Value != null)
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.StartObject)
{
var n = 1;
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) n--;
if (reader.TokenType == JsonToken.StartObject) n++;
if (n == 0) return;
}
}
else if (reader.TokenType == JsonToken.StartArray)
{
var n = 1;
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray) n--;
if (reader.TokenType == JsonToken.StartArray) n++;
if (n == 0) return;
}
}
else if (reader.Value != null)
{
continue;
}
}
}
}
private RawBrowserMessage ReadMessageObject(JsonTextReader reader)
{
var msg = new RawBrowserMessage { Cookies = new(), ResponseHeaders = new(), RequestHeaders = new() };
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) break;
var url = ReadProperty<string>(reader, "url");
if (url != null)
{
msg.Url = url;
}
if (IsObjectStart(reader, "cookies"))
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) break;
if (reader.TokenType == JsonToken.PropertyName && reader.Value != null)
{
var cookieName = (string)reader.Value;
if (reader.Read() && reader.TokenType == JsonToken.String)
{
var cookieValue = (string)reader.Value;
msg.Cookies[cookieName] = cookieValue;
}
}
}
}
if (IsObjectStart(reader, "responseHeaders"))// && IsListStart(reader, "realUA"))
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) break;
if (reader.TokenType == JsonToken.PropertyName && reader.Value != null)
{
var headerName = (string)reader.Value;
if (IsListStart(reader, headerName))
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray) break;
if (reader.TokenType == JsonToken.String)
{
if (msg.ResponseHeaders.TryGetValue(headerName, out var list))
{
list.Add((string)reader.Value);
}
else
{
msg.ResponseHeaders[headerName] = new() { (string)reader.Value };
}
}
}
}
}
}
}
if (IsObjectStart(reader, "requestHeaders"))
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) break;
if (reader.TokenType == JsonToken.PropertyName && reader.Value != null)
{
var headerName = (string)reader.Value;
if (IsListStart(reader, headerName))
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray) break;
if (reader.TokenType == JsonToken.String)
{
if (msg.RequestHeaders.TryGetValue(headerName, out var list))
{
list.Add((string)reader.Value);
}
else
{
msg.RequestHeaders[headerName] = new() { (string)reader.Value };
}
}
}
}
}
}
}
SkipUnknownParts(reader);
}
return msg;
}
public RawBrowserMessageEnvelop Parse(Stream stream)
{
var envelop = new RawBrowserMessageEnvelop();
var reader = new JsonTextReader(new StreamReader(stream));
if (reader.Read() && reader.TokenType == JsonToken.StartObject)
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) break;
var messageType = ReadProperty<string>(reader, "messageType");
if (messageType != null)
{
envelop.MessageType = messageType;
}
var customData = ReadProperty<string>(reader, "customData");
if (customData != null)
{
envelop.CustomData = customData;
}
if (IsObjectStart(reader, "message"))
{
var msg = ReadMessageObject(reader);
envelop.Message = msg;
}
if (IsListStart(reader, "messages"))
{
var list = new List<RawBrowserMessage>();
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray) break;
if (reader.TokenType == JsonToken.StartObject)
{
var msg = ReadMessageObject(reader);
list.Add(msg);
envelop.Messages = list.ToArray();
}
}
}
if (IsListStart(reader, "videoIds"))
{
var list = new List<RawBrowserMessage>();
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray) break;
if (reader.TokenType == JsonToken.StartObject)
{
var msg = ReadMessageObject(reader);
list.Add(msg);
envelop.Messages = list.ToArray();
}
}
}
SkipUnknownParts(reader);
}
}
return envelop;
}
}
}

View File

@ -0,0 +1,108 @@
using Microsoft.Win32;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using TraceLog;
using XDM.Core;
namespace XDM.Core.BrowserMonitoring
{
internal static class NativeMessagingConfigurer
{
private static void CreateMessagingHostManifest(Browser browser, string appName, string manifestPath)
{
var allowedExtensions = browser == Browser.Firefox ? new[] {
"browser-mon@xdman.sourceforge.net"
} : new[] {
"chrome-extension://danmljfachfhpbfikjgedlfifabhofcj/",
"chrome-extension://dkckaoghoiffdbomfbbodbbgmhjblecj/",
"chrome-extension://ejpbcmllmliidhlpkcgbphhmaodjihnc/",
"chrome-extension://fogpiboapmefmkbodpmfnohfflonbgig/"
};
var folder = Path.GetDirectoryName(manifestPath)!;
if (!Directory.Exists(folder))
{
try
{
Directory.CreateDirectory(folder);
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
using var stream = new FileStream(manifestPath, FileMode.Create);
using var textWriter = new StreamWriter(stream);
using var writer = new JsonTextWriter(textWriter);
writer.Formatting = Formatting.Indented;
writer.WriteStartObject();
writer.WritePropertyName("name");
writer.WriteValue(appName);
writer.WritePropertyName("description");
writer.WriteValue("Native messaging host for Xtreme Download Manager");
writer.WritePropertyName("path");
writer.WriteValue(Path.Combine(
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MessagingHost"),
Environment.OSVersion.Platform == PlatformID.Win32NT ?
"xdm-messaging-host.exe" : "xdm-messaging-host"));
writer.WritePropertyName("type");
writer.WriteValue("stdio");
writer.WritePropertyName(browser == Browser.Firefox ? "allowed_extensions" : "allowed_origins");
writer.WriteStartArray();
foreach (var extension in allowedExtensions)
{
writer.WriteValue(extension);
}
writer.WriteEndArray();
writer.WriteEndObject();
writer.Close();
}
public static void InstallNativeMessagingHost(Browser browser)
{
var appName = browser == Browser.Firefox ? "xdmff.native_host" :
"xdm_chrome.native_host";
var os = Environment.OSVersion.Platform;
if (os == PlatformID.Win32NT)
{
var manifestPath = Path.Combine(Config.DataDir, $"{appName}.json");
CreateMessagingHostManifest(browser, appName, manifestPath);
var regPath = (browser == Browser.Firefox ?
@"Software\Mozilla\NativeMessagingHosts\" :
@"SOFTWARE\Google\Chrome\NativeMessagingHosts");
using var regKey = Registry.CurrentUser.CreateSubKey(regPath);
using var key = regKey.CreateSubKey(appName, RegistryKeyPermissionCheck.ReadWriteSubTree);
key.SetValue(null, manifestPath);
}
else
{
#if NET5_0_OR_GREATER
string manifestPath;
var home = Environment.GetEnvironmentVariable("HOME")!;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
manifestPath = Path.Combine(home, browser == Browser.Firefox ?
$"Library/Application Support/Mozilla/NativeMessagingHosts/{appName}.json" :
$"Library/Application Support/Google/Chrome/NativeMessagingHosts/{appName}.json");
}
else
{
manifestPath = Path.Combine(home, browser == Browser.Firefox ?
$".mozilla/native-messaging-hosts/{appName}.json" :
$".config/google-chrome/NativeMessagingHosts/{appName}.json");
}
Log.Debug($"Manifest file: {manifestPath}");
CreateMessagingHostManifest(browser, appName, manifestPath);
#endif
}
}
}
public enum Browser
{
Chrome, Firefox, MSEdge
}
}

View File

@ -0,0 +1,97 @@
using System;
using System.IO.Pipes;
using System.Threading;
using TraceLog;
namespace XDM.Core.BrowserMonitoring
{
internal sealed class NativeMessagingHostChannel
{
private NamedPipeServerStream pipe;
internal event EventHandler<NativeMessageEventArgs>? MessageReceived;
internal event EventHandler? Disconnected;
private Thread readerThread;
internal NativeMessagingHostChannel(NamedPipeServerStream pipe)
{
this.pipe = pipe;
readerThread = new Thread(() =>
{
try
{
ReadDataFromPipe();
}
catch (Exception ex)
{
Log.Debug(ex, ex.ToString());
Disconnect();
}
this.Disconnected?.Invoke(this, EventArgs.Empty);
});
}
internal void Disconnect()
{
try
{
if (pipe.IsConnected)
{
pipe.Disconnect();
}
}
catch (Exception ex)
{
Log.Debug(ex, ex.ToString());
}
}
internal void Publish(byte[] data)
{
try
{
NativeMessageSerializer.WriteMessage(pipe, data);
pipe.Flush();
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
internal void ReadDataFromPipe()
{
while (true)
{
Log.Debug("Waiting for message from native host...");
var bytes = NativeMessageSerializer.ReadMessageBytes(pipe);
Log.Debug($"Message from native host {bytes.Length} bytes");
this.MessageReceived?.Invoke(this, new NativeMessageEventArgs(bytes));
}
}
internal void Start(byte[] initialConfig)
{
try
{
readerThread.Start();
NativeMessageSerializer.WriteMessage(pipe, initialConfig);
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
}
internal class NativeMessageEventArgs : EventArgs
{
private byte[] data;
internal NativeMessageEventArgs(byte[] data)
{
this.data = data;
}
internal byte[] Data => data;
}
}

View File

@ -0,0 +1,369 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Pipes;
using System.IO;
using XDM.Core;
using System.Threading;
#if NET35
using XDM.Compatibility;
#else
using System.Collections.Concurrent;
#endif
using TraceLog;
namespace XDM.Core.BrowserMonitoring
{
public class NativeMessagingHostHandler : IDisposable
{
private int MaxPipeInstance = 254;
private static readonly string PipeName = "XDM_Ipc_Browser_Monitoring_Pipe";
private List<NativeMessagingHostChannel> connectedChannels = new();
private static Mutex globalMutex;
private Thread listenerThread;
public static void EnsureSingleInstance()
{
try
{
using var mutex = Mutex.OpenExisting(@"Global\XDM_Active_Instance");
throw new InstanceAlreadyRunningException(@"XDM instance already running, Mutex exists 'Global\XDM_Active_Instance'");
}
catch (Exception ex)
{
Log.Debug(ex, "Exception in NativeMessagingHostHandler ctor");
if (ex is InstanceAlreadyRunningException)
{
var args = Environment.GetCommandLineArgs().Skip(1);
if (args.Count() > 0)
{
Log.Debug(ex, "Sending args to running instance");
SendArgsToRunningInstance(args);
Environment.Exit(0);
}
throw;
}
}
globalMutex = new Mutex(true, @"Global\XDM_Active_Instance");
}
public NativeMessagingHostHandler()
{
EnsureSingleInstance();
}
public void BroadcastConfig()
{
var bytes = GetSyncBytes();
lock (this)
{
foreach (var channel in connectedChannels)
{
try
{
channel.Publish(bytes);
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
}
}
public void StartPipedChannel()
{
listenerThread = new Thread(() =>
{
while (true)
{
var pipe =
new NamedPipeServerStream(PipeName,
PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Log.Debug("Waiting for native host pipe...");
pipe.WaitForConnection();
Log.Debug("Pipe request received");
lock (connectedChannels)
{
var channel = CreateChannel(pipe);
connectedChannels.Add(channel);
channel.Start(GetSyncBytes());
}
}
});
listenerThread.Start();
ApplicationContext.ApplicationEvent += ApplicationContext_ApplicationEvent;
}
private void ApplicationContext_ApplicationEvent(object? sender, ApplicationEvent e)
{
if (e.EventType == "ConfigChanged")
{
BroadcastConfig();
}
}
private NativeMessagingHostChannel CreateChannel(NamedPipeServerStream pipe)
{
var channel = new NativeMessagingHostChannel(pipe);
channel.MessageReceived += (sender, args) =>
{
try
{
using var br = new BinaryReader(new MemoryStream(args.Data));
var envelop = RawBrowserMessageEnvelop.Deserialize(br);
BrowserMessageHandler.Handle(envelop);
}
catch (Exception ex)
{
Log.Debug(ex, ex.ToString());
}
};
channel.Disconnected += (sender, bytes) =>
{
lock (connectedChannels)
{
connectedChannels.Remove((NativeMessagingHostChannel)sender);
}
};
return channel;
}
//public void StartPipedChannel()
//{
// WriterThread = new Thread(() =>
// {
// while (true)
// {
// //Log.Debug("Total messages to be sent to native host: " + Messages.Count);
// var bytes = Messages.Take();
// foreach (var key in inOutMap.Keys)
// {
// //Log.Debug("Sending message to native host");
// try
// {
// var outpipe = inOutMap[key];
// NativeMessageSerializer.WriteMessage(outpipe, bytes);
// //Log.Debug("Send message to native host successfully");
// }
// catch (Exception ex)
// {
// Log.Debug(ex, "Send message to native host failed");
// }
// }
// }
// });
// WriterThread.Start();
// new Thread(() =>
// {
// try
// {
// if (inPipes.Count == MaxPipeInstance)
// {
// Log.Debug("Max pipe count of " + MaxPipeInstance + " is reached");
// return;
// }
// var inPipe =
// new NamedPipeServerStream(PipeName,
// PipeDirection.In, NamedPipeServerStream.MaxAllowedServerInstances,
// PipeTransmissionMode.Byte, PipeOptions.WriteThrough);
// inPipes.Add(inPipe);
// var first = true;
// while (true)
// {
// Log.Debug("Waiting for native host pipe...");
// inPipe.WaitForConnection();
// Log.Debug("Pipe request received");
// if (first)
// {
// Log.Debug("Creating one more additional pipe");
// StartPipedChannel();
// first = false;
// }
// try
// {
// ConsumePipe(inPipe);
// }
// catch (Exception e)
// {
// inPipe.Disconnect();
// Log.Debug(e, "Error in message exchange");
// }
// Log.Debug("Terminated message exchange, will reuse the pipe");
// }
// }
// catch (Exception ex)
// {
// Log.Debug(ex, "Error in message exchange flow");
// }
// }).Start();
//}
//private void ConsumePipe(NamedPipeServerStream inPipe)
//{
// try
// {
// Log.Debug("Initiate message handshake");
// var clientPipeName = Encoding.UTF8.GetString(NativeMessageSerializer.ReadMessageBytes(inPipe));
// Log.Debug("Client pipe: " + clientPipeName);
// if (clientPipeName.StartsWith("XDM-ApplicationContext.Core-"))
// {
// var command = NativeMessageSerializer.ReadMessageBytes(inPipe);
// var args = ArgsProcessor.ParseArgs(Encoding.UTF8.GetString(command).Split('\r'));
// ArgsProcessor.Process(ApplicationContext.Core, args);
// return;
// }
// var outPipe = new NamedPipeClientStream(".", clientPipeName, PipeDirection.Out);
// outPipe.Connect();
// SendConfig(outPipe);
// inOutMap[inPipe] = outPipe;
// Log.Debug("Message handshake completed");
// while (true)
// {
// var text = NativeMessageSerializer.ReadMessageBytes(inPipe);
// using var ms = new MemoryStream(text);
// using var br = new BinaryReader(ms);
// // Log.Debug("{Text}", text);
// var envelop = RawBrowserMessageEnvelop.Deserialize(br);
// BrowserMessageHandler.Handle(ApplicationContext.Core, envelop);
// }
// }
// finally
// {
// try
// {
// NamedPipeClientStream? op = null;
// lock (this)
// {
// if (inOutMap.TryGetValue(inPipe, out op))
// {
// inOutMap.Remove(inPipe);
// }
// }
// op?.Close();
// op?.Dispose();
// }
// catch { }
// }
//}
//private void SendConfig(Stream pipe)
//{
// var bytes = GetSyncBytes(ApplicationContext.Core);
// NativeMessageSerializer.WriteMessage(pipe, bytes);
//}
//private static void ReadFully(Stream stream, byte[] buf, int bytesToRead)
//{
// var rem = bytesToRead;
// var index = 0;
// while (rem > 0)
// {
// var c = stream.Read(buf, index, rem);
// if (c == 0) throw new IOException("Unexpected EOF");
// index += c;
// rem -= c;
// }
//}
//private static byte[] ReadMessageBytes(Stream pipe)
//{
// var b4 = new byte[4];
// ReadFully(pipe, b4, 4);
// var syncLength = BitConverter.ToInt32(b4, 0);
// if (syncLength > 4 * 8196)
// {
// throw new ArgumentException($"Message length too long: {syncLength}");
// }
// var bytes = new byte[syncLength];
// ReadFully(pipe, bytes, syncLength);
// return bytes;
//}
//private static void WriteMessage(Stream pipe, string message)
//{
// var msgBytes = Encoding.UTF8.GetBytes(message);
// WriteMessage(pipe, msgBytes);
//}
//private static void WriteMessage(Stream pipe, byte[] msgBytes)
//{
// var bytes = BitConverter.GetBytes(msgBytes.Length);
// pipe.Write(bytes, 0, bytes.Length);
// pipe.Write(msgBytes, 0, msgBytes.Length);
// pipe.Flush();
//}
public void Dispose()
{
lock (connectedChannels)
{
foreach (var channel in connectedChannels)
{
channel.Disconnect();
}
}
//foreach (var pipe in inPipes)
//{
// try { pipe.Disconnect(); } catch { }
// try { pipe.Dispose(); } catch { }
//}
}
private static byte[] GetSyncBytes()
{
var msg = new SyncMessage()
{
Enabled = Config.Instance.IsBrowserMonitoringEnabled,
BlockedHosts = Config.Instance.BlockedHosts,
VideoUrls = new string[0],
FileExts = Config.Instance.FileExtensions,
VidExts = Config.Instance.VideoExtensions,
VidList = ApplicationContext.VideoTracker.GetVideoList(false).Select(a => new VideoItem
{
Id = a.ID,
Text = a.File,
Info = a.DisplayName
}).ToList(),
MimeList = new string[] { "video", "audio", "mpegurl", "f4m", "m3u8", "dash" },
BlockedMimeList = new string[] { "text/javascript", "application/javascript", "text/css", "text/html" },
VideoUrlsWithPostReq = new string[] { "ubei/v1/player?key=", "ubei/v1/next?key=" }
};
return msg.Serialize();
}
private static void SendArgsToRunningInstance(IEnumerable<string> args)
{
if (args == null || args.Count() < 1) return;
try
{
using var npc = new NamedPipeClientStream(".", PipeName, PipeDirection.Out);
npc.Connect();
var b = new MemoryStream();
var wb = new BinaryWriter(b);
wb.Write(Int32.MaxValue);
wb.Write(string.Join("\r", args.ToArray()));
wb.Close();
NativeMessageSerializer.WriteMessage(npc, b.ToArray());
npc.Flush();
npc.Close();
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
}
public class InstanceAlreadyRunningException : Exception
{
public InstanceAlreadyRunningException(string message) : base(message)
{
}
}
}

View File

@ -0,0 +1,242 @@
using System;
using System.Collections.Generic;
using System.Text;
using TraceLog;
using XDM.Core.Collections;
using XDM.Core.Downloader;
using XDM.Core.Downloader.Adaptive.Dash;
using XDM.Core.Downloader.Adaptive.Hls;
using XDM.Core.Downloader.Progressive.DualHttp;
using XDM.Core.Downloader.Progressive.SingleHttp;
using XDM.Core.Util;
namespace XDM.Core.BrowserMonitoring
{
public class VideoTracker : IVideoTracker
{
private GenericOrderedDictionary<string, (DualSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> ytVideoList = new();
private GenericOrderedDictionary<string, (SingleSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> videoList = new();
private GenericOrderedDictionary<string, (MultiSourceHLSDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> hlsVideoList = new();
private GenericOrderedDictionary<string, (MultiSourceDASHDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> dashVideoList = new();
public void ClearVideoList()
{
ytVideoList.Clear();
hlsVideoList.Clear();
dashVideoList.Clear();
videoList.Clear();
ApplicationContext.BroadcastConfigChange();
}
public bool IsFFmpegRequiredForDownload(string id)
{
return ytVideoList.ContainsKey(id) || dashVideoList.ContainsKey(id) || hlsVideoList.ContainsKey(id);
}
public void StartVideoDownload(string videoId,
string name,
string? folder,
bool startImmediately,
AuthenticationInfo? authentication,
ProxyInfo? proxyInfo,
int maxSpeedLimit,
string? queueId,
bool convertToMp3 = false //only applicable for dual source http downloads
)
{
//IBaseDownloader downloader = null;
if (ytVideoList.ContainsKey(videoId))
{
ApplicationContext.CoreService.StartDownload(ytVideoList[videoId].Info, name, FileNameFetchMode.ExtensionOnly,
folder, startImmediately, authentication, proxyInfo, queueId, false);
}
else if (videoList.ContainsKey(videoId))
{
ApplicationContext.CoreService.StartDownload(videoList[videoId].Info, name, convertToMp3 ? FileNameFetchMode.None : FileNameFetchMode.ExtensionOnly,
folder, startImmediately, authentication, proxyInfo, queueId, convertToMp3);
}
else if (hlsVideoList.ContainsKey(videoId))
{
ApplicationContext.CoreService.StartDownload(hlsVideoList[videoId].Info, name, FileNameFetchMode.ExtensionOnly,
folder, startImmediately, authentication, proxyInfo, queueId, false);
}
else if (dashVideoList.ContainsKey(videoId))
{
ApplicationContext.CoreService.StartDownload(dashVideoList[videoId].Info, name, FileNameFetchMode.ExtensionOnly,
folder, startImmediately, authentication, proxyInfo, queueId, false);
}
}
public List<(string ID, string File, string DisplayName, DateTime Time)> GetVideoList(bool encode = true)
{
lock (this)
{
var list = new List<(string ID, string File, string DisplayName, DateTime Time)>();
foreach (var e in ytVideoList)
{
list.Add((e.Key, encode ? Helpers.EncodeToCharCode(e.Value.Info.File) : e.Value.Info.File, e.Value.DisplayInfo.Quality, e.Value.DisplayInfo.CreationTime));
}
foreach (var e in videoList)
{
list.Add((e.Key, encode ? Helpers.EncodeToCharCode(e.Value.Info.File) : e.Value.Info.File, e.Value.DisplayInfo.Quality, e.Value.DisplayInfo.CreationTime));
}
foreach (var e in hlsVideoList)
{
list.Add((e.Key, encode ? Helpers.EncodeToCharCode(e.Value.Info.File) : e.Value.Info.File, e.Value.DisplayInfo.Quality, e.Value.DisplayInfo.CreationTime));
}
foreach (var e in dashVideoList)
{
list.Add((e.Key, encode ? Helpers.EncodeToCharCode(e.Value.Info.File) : e.Value.Info.File, e.Value.DisplayInfo.Quality, e.Value.DisplayInfo.CreationTime));
}
list.Sort((a, b) => a.Time.CompareTo(b.Time));
return list;
}
}
public void AddVideoNotifications(IEnumerable<(DualSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> notifications)
{
lock (this)
{
foreach (var info in notifications)
{
ytVideoList.Add(Guid.NewGuid().ToString(), info);
}
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotifications(IEnumerable<(SingleSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> notifications)
{
lock (this)
{
foreach (var info in notifications)
{
videoList.Add(Guid.NewGuid().ToString(), info);
}
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotifications(IEnumerable<(MultiSourceHLSDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> notifications)
{
lock (this)
{
foreach (var info in notifications)
{
hlsVideoList.Add(Guid.NewGuid().ToString(), info);
}
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotifications(IEnumerable<(MultiSourceDASHDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)> notifications)
{
lock (this)
{
foreach (var info in notifications)
{
dashVideoList.Add(Guid.NewGuid().ToString(), info);
}
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotification(StreamingVideoDisplayInfo displayInfo, DualSourceHTTPDownloadInfo info)
{
lock (this)
{
ytVideoList.Add(Guid.NewGuid().ToString(), (info, displayInfo));
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotification(StreamingVideoDisplayInfo displayInfo, SingleSourceHTTPDownloadInfo info)
{
lock (this)
{
videoList.Add(Guid.NewGuid().ToString(), (info, displayInfo));
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotification(StreamingVideoDisplayInfo displayInfo, MultiSourceHLSDownloadInfo info)
{
lock (this)
{
var id = Guid.NewGuid().ToString();
hlsVideoList.Add(id, (info, displayInfo));
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoNotification(StreamingVideoDisplayInfo displayInfo, MultiSourceDASHDownloadInfo info)
{
lock (this)
{
var id = Guid.NewGuid().ToString();
Log.Debug("DASH video added with id: " + id);
dashVideoList.Add(id, (info, displayInfo));
ApplicationContext.BroadcastConfigChange();
}
}
public void AddVideoDownload(string videoId)
{
var name = string.Empty;
var size = 0L;
var contentType = string.Empty;
var valid = false;
if (ytVideoList.ContainsKey(videoId))
{
if (ApplicationContext.LinkRefresher.LinkAccepted(ytVideoList[videoId].Info)) return;
name = ytVideoList[videoId].Info.File;
size = ytVideoList[videoId].DisplayInfo.Size;
contentType = ytVideoList[videoId].Info.ContentType1;
valid = true;
}
else if (videoList.ContainsKey(videoId))
{
if (ApplicationContext.LinkRefresher.LinkAccepted(videoList[videoId].Info)) return;
name = videoList[videoId].Info.File;
size = videoList[videoId].DisplayInfo.Size;
contentType = videoList[videoId].Info.ContentType;
valid = true;
}
else if (hlsVideoList.ContainsKey(videoId))
{
Log.Debug("Download HLS video added with id: " + videoId);
name = hlsVideoList[videoId].Info.File;
valid = true;
try
{
contentType = hlsVideoList[videoId].Info.ContentType;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
else if (dashVideoList.ContainsKey(videoId))
{
Log.Debug("Download DASH video added with id: " + videoId);
name = dashVideoList[videoId].Info.File;
contentType = dashVideoList[videoId].Info.ContentType;
valid = true;
}
if (valid)
{
if (Config.Instance.StartDownloadAutomatically)
{
StartVideoDownload(
videoId, FileHelper.SanitizeFileName(name),
null, true, null, Config.Instance.Proxy,
Helpers.GetSpeedLimit(), null);
}
else
{
ApplicationContext.Application.ShowVideoDownloadDialog(videoId, name, size, contentType);
}
}
}
}
}

View File

@ -0,0 +1,885 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XDM.Core;
using XDM.Core.Util;
//using XDM.Core.Downloader.YT.Dash;
using System.IO;
using XDM.Core.MediaParser.Hls;
using XDM.Core.MediaParser.Dash;
using XDM.Core.MediaParser.YouTube;
using System.Security.Cryptography;
using TraceLog;
using XDM.Core.Clients.Http;
using XDM.Core.Downloader.Progressive.DualHttp;
using XDM.Core.Downloader.Progressive.SingleHttp;
using XDM.Core.Downloader.Adaptive.Dash;
using XDM.Core.Downloader.Adaptive.Hls;
#if !NET5_0_OR_GREATER
using XDM.Compatibility;
#endif
namespace XDM.Core.BrowserMonitoring
{
static class VideoUrlHelper
{
private static object lockObject = new object();
private static DashInfo lastVid;
private static List<DashInfo> videoQueue = new();
private static List<DashInfo> audioQueue = new();
private static Dictionary<string, DateTime> referersToSkip = new(); //Skip the video requests whose referer hash is present in below dict
//as they were triggered by HLS or DASH
internal static bool IsNormalVideo(string contentType, string url, long size)
{
if (size > 0 && size < Config.Instance.MinVideoSize * 1024)
{
return false;
}
return (contentType != null && !(contentType.Contains("f4f") ||
contentType.Contains("m4s") ||
contentType.Contains("mp2t") || url.Contains("abst") ||
url.Contains("f4x") || url.Contains(".fbcdn")
|| url.Contains("http://127.0.0.1:9614")));
}
internal static void ProcessPostYtFormats(Message message)
{
//var file = message.File ?? FileHelper.GetFileName(new Uri(message.Url));
var manifest = DownloadManifest(message);
if (manifest == null)
{
Log.Debug("Failed to download youtube manifest: " + message.Url);
return;
}
//var manifestText = File.ReadAllText(manifest);
//Log.Debug("Text: {text}", manifestText);
try
{
var (DualVideoItems, VideoItems) = YoutubeDataFormatParser.GetFormats(manifest);
Log.Debug("DualVideoItems: " + DualVideoItems.Count + " VideoItems: " + VideoItems.Count);
message.RequestHeaders.Remove("Content-Type");
if (DualVideoItems != null && DualVideoItems.Count > 0)
{
lock (ApplicationContext.CoreService)
{
var list = new List<(DualSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)>();
foreach (var item in DualVideoItems)
{
var fileExt = item.MediaContainer;
var mediaItem = new DualSourceHTTPDownloadInfo
{
Uri1 = item.VideoUrl,
Uri2 = item.AudioUrl,
Headers1 = message.RequestHeaders,
Headers2 = message.RequestHeaders,
File = FileHelper.SanitizeFileName(item.Title) + "." + fileExt,
Cookies1 = message.Cookies,
Cookies2 = message.Cookies
};
var size = item.Size > 0 ? FormattingHelper.FormatSize(item.Size) : string.Empty;
var displayInfo = new StreamingVideoDisplayInfo
{
Quality = $"[{fileExt.ToUpperInvariant()}] {size} {item.FormatDescription}",
Size = item.Size,
CreationTime = DateTime.Now
};
//var displayText = $"[{fileExt.ToUpperInvariant()}] {size} {item.FormatDescription}";
list.Add((Info: mediaItem, DisplayInfo: displayInfo));
//ApplicationContext.Core.AddVideoNotification(displayText, mediaItem);
}
ApplicationContext.VideoTracker.AddVideoNotifications(list);
}
}
if (VideoItems != null && VideoItems.Count > 0)
{
lock (ApplicationContext.CoreService)
{
var list = new List<(SingleSourceHTTPDownloadInfo Info, StreamingVideoDisplayInfo DisplayInfo)>();
foreach (var item in VideoItems)
{
var fileExt = item.MediaContainer;
var mediaItem = new SingleSourceHTTPDownloadInfo
{
Uri = item.MediaUrl,
Headers = message.RequestHeaders,
File = FileHelper.SanitizeFileName(item.Title) + "." + fileExt,
Cookies = message.Cookies
};
var size = item.Size > 0 ? FormattingHelper.FormatSize(item.Size) : string.Empty;
var displayText = $"[{fileExt.ToUpperInvariant()}] {size} {item.FormatDescription}";
var displayInfo = new StreamingVideoDisplayInfo
{
Quality = $"[{fileExt.ToUpperInvariant()}] {size} {item.FormatDescription}",
Size = item.Size,
CreationTime = DateTime.Now
};
list.Add((Info: mediaItem, DisplayInfo: displayInfo));
}
ApplicationContext.VideoTracker.AddVideoNotifications(list);
}
}
}
catch (Exception ex)
{
Log.Debug(ex, "Failed to parse youtube manifest");
}
}
internal static void ProcessDashVideo(Message message)
{
var file = message.File ?? FileHelper.GetFileName(new Uri(message.Url));
Log.Debug("Downloading MPD manifest: " + message.Url);
AddToSkippedRefererList(message.GetRequestHeaderFirstValue("Referer"));
var manifest = DownloadManifest(message);
if (manifest == null) { return; }
var manifestText = File.ReadAllText(manifest);
Log.Debug("MPD playlist");
var mediaEntries = MpdParser.Parse(manifestText.Split('\n'), message.Url);
if (mediaEntries.Count < 1) return;
Log.Debug("Manifest contains: " + mediaEntries.Count);
var count = 0;
foreach (var plc in mediaEntries)
{
foreach ((Representation video, Representation audio) in plc)
{
//prefix added for multi period
var prefix = (count == 0 ? "" : count.ToString() + " ");
if (video != null && audio != null)
{
if (video.Segments.Count == 1 && audio.Segments.Count == 1)
{
Log.Debug("DASH manifest contains 1 audio and 1 video, making it DualSourceHTTPDownload");
var fileExt = (((video.MimeType + "").Contains("mp4") && (audio.MimeType + "").Contains("mp4")) ? "mp4" : "mkv");
var mediaItem = new DualSourceHTTPDownloadInfo
{
Uri1 = video.Segments[0].ToString(),
Uri2 = audio.Segments[0].ToString(),
Headers1 = message.RequestHeaders,
Headers2 = message.RequestHeaders,
File = prefix + FileHelper.SanitizeFileName(file) + "." + fileExt,
Cookies1 = message.Cookies,
Cookies2 = message.Cookies
};
var displayText = $"[{fileExt.ToUpperInvariant()}] {GetQualityString(video, audio)}";
Log.Debug("Display text dash: " + displayText);
ApplicationContext.VideoTracker.AddVideoNotification(new StreamingVideoDisplayInfo
{
Quality = displayText,
CreationTime = DateTime.Now
}, mediaItem);
}
else
{
var fileExt = (((video.MimeType + "").Contains("mp4") && (audio.MimeType + "").Contains("mp4")) ? "mp4" : "mkv");
var mediaItem = new MultiSourceDASHDownloadInfo
{
VideoSegments = video.Segments,
AudioSegments = audio.Segments,
Headers = message.RequestHeaders,
File = prefix + FileHelper.SanitizeFileName(file) + "." + fileExt,
Cookies = message.Cookies,
Duration = Math.Max(video.Duration, audio.Duration),
Url = message.Url,
VideoMimeType = video.MimeType,
AudioMimeType = audio.MimeType
};
var displayText = $"[{fileExt.ToUpperInvariant()}] {GetQualityString(video, audio)}";
Log.Debug("Display text hls: " + displayText);
ApplicationContext.VideoTracker.AddVideoNotification(new StreamingVideoDisplayInfo
{
Quality = displayText,
CreationTime = DateTime.Now
}, mediaItem);
}
}
else if (video != null)
{
Log.Debug("DASH manifest contains no audio and 1 video, making it SingleSourceHTTPDownload");
AddSingleItem(video, message, prefix, false, file);
}
else if (audio != null)
{
Log.Debug("DASH manifest contains 1 audio and no video, making it SingleSourceHTTPDownload");
AddSingleItem(audio, message, prefix, true, file);
}
else
{
Log.Debug("No audio or video in dash mpd");
}
}
count++;
}
}
private static void AddSingleItem(Representation item, Message message, string prefix, bool audio, string file)
{
var fileExt = (item.MimeType + "").Contains("mp4") ? "mp4" : "mkv";
var mediaItem = new SingleSourceHTTPDownloadInfo
{
Uri = item.Segments[0].ToString(),
Headers = message.RequestHeaders,
File = prefix + FileHelper.SanitizeFileName(file) + "." + fileExt,
Cookies = message.Cookies
};
var quality = audio ? GetQualityString(null, item) : GetQualityString(item, null);
var displayText = $"[{fileExt.ToUpperInvariant()}] {quality}";
Log.Debug("Display text hls: " + displayText);
ApplicationContext.VideoTracker.AddVideoNotification(new StreamingVideoDisplayInfo
{
Quality = displayText,
CreationTime = DateTime.Now
}, mediaItem);
}
private static string GetQualityString(Representation video, Representation audio)
{
string GetVideoResolution(Representation video)
{
return video.Height > 0 ? video.Height + "p " : "";
}
string GetAudioLanguage(Representation audio)
{
return audio.Language != null && audio.Language != "und" ? audio.Language + " " : "";
}
string GetBandwidth(params Representation[] args)
{
var sum = 0L;
foreach (var arg in args)
{
if (arg != null && arg.Bandwidth > 0) sum += arg.Bandwidth;
}
return sum > 0 ? (sum / 1024) + " Kbps " : "";
}
var text = new StringBuilder();
if (video != null && audio != null)
{
text.Append(GetVideoResolution(video) + GetBandwidth(video, audio) + " " + GetAudioLanguage(audio));
}
else if (video != null)
{
text.Append(GetVideoResolution(video) + GetBandwidth(video, audio));
}
else if (audio != null)
{
text.Append(GetAudioLanguage(audio) + GetBandwidth(video, audio));
}
return text.ToString();
}
internal static void ProcessHLSVideo(Message message)
{
Log.Debug("Downloading HLS manifest: " + message.Url);
AddToSkippedRefererList(message.GetRequestHeaderFirstValue("Referer"));
var manifest = DownloadManifest(message);
if (manifest != null)
{
var manifestText = File.ReadAllText(manifest);
if (manifestText.Contains(HlsParser.EXT_X_STREAM_INF))
{
Log.Debug("Master playlist: " + message.Url);
var playlists = HlsParser.ParseMasterPlaylist(manifestText.Split('\n'), message.Url);
if (playlists != null && playlists.Count > 0)
{
Log.Debug("Master playlist contains: " + playlists.Count);
foreach (var plc in playlists)
{
var type = (plc.AudioPlaylist != null && plc.VideoPlaylist != null ? "MP4" : "TS");
var video = new MultiSourceHLSDownloadInfo
{
VideoUri = plc.VideoPlaylist?.ToString(),
AudioUri = plc.AudioPlaylist?.ToString(),
Headers = message.RequestHeaders,
File = FileHelper.SanitizeFileName(message.File ?? (FileHelper.GetFileName(new Uri(message.Url)))) +
(plc.AudioPlaylist != null && plc.VideoPlaylist != null ?
"." + type.ToLowerInvariant() : "." + type.ToLowerInvariant()),
Cookies = message.Cookies
};
var displayText = $"{type} {plc.Quality}";
Log.Debug("Display text hls: " + plc.Quality);
ApplicationContext.VideoTracker.AddVideoNotification(new StreamingVideoDisplayInfo
{
Quality = displayText,
CreationTime = DateTime.Now
}, video);
}
}
}
else
{
if (manifestText.Contains(HlsParser.EXT_X_I_FRAMES_ONLY))
{
Log.Debug("Skipping EXT_X_I_FRAMES_ONLY: " + message.Url);
return;
}
Log.Debug("Not Master playlist");
var mediaPlaylist = HlsParser.ParseMediaSegments(manifestText.Split('\n'), message.Url);
if (mediaPlaylist == null) return;
var file = FileHelper.GetFileName(mediaPlaylist.MediaSegments.Last().Url);
var container = FileExtensionHelper.GuessContainerFormatFromSegmentExtension(Path.GetExtension(file));
var video = new MultiSourceHLSDownloadInfo
{
VideoUri = message.Url,
Headers = message.RequestHeaders,
File = FileHelper.SanitizeFileName(message.File ?? FileHelper.GetFileName(new Uri(message.Url))) + ".ts",
Cookies = message.Cookies,
};
var displayText = $"[{container}]";
ApplicationContext.VideoTracker.AddVideoNotification(new StreamingVideoDisplayInfo
{
Quality = displayText,
CreationTime = DateTime.Now
}, video);
}
}
}
public static bool ProcessYtDashSegment(Message message)
{
try
{
var url = new Uri(message.Url);
if (!(url.Host.Contains("youtube.com") || url.Host.Contains("googlevideo.com")))
{
return false;
}
var contentType = message.GetResponseHeaderValue("Content-Type")?[0]?.ToLowerInvariant();
if (!(contentType != null && (contentType.Contains("audio/") ||
contentType.Contains("video/") ||
contentType.Contains("application/octet"))))
{
return false;
}
var lowUrl = message.Url.ToLowerInvariant();
if (!(lowUrl.Contains("videoplayback") && lowUrl.Contains("itag")))
{
return false;
}
(var path, var query, _) = ParsingHelper.ParseKeyValuePair(message.Url, '?');
string[] arr = query.Split('&');
var yt_url = new StringBuilder();
yt_url.Append(path + "?");
int itag = 0;
long clen = 0;
String id = "";
String mime = "";
for (int i = 0; i < arr.Length; i++)
{
var str = arr[i];
(var key, var val, var success) = ParsingHelper.ParseKeyValuePair(str, '=');
if (!success)
{
continue;
}
if (key.StartsWith("range"))
{
continue;
}
if (key == "itag")
{
itag = Int32.Parse(val);
}
if (key == "clen")
{
clen = Int64.Parse(val);
}
if (key.StartsWith("mime"))
{
mime = Uri.UnescapeDataString(val);
}
if (str.StartsWith("id"))
{
id = val;
}
yt_url.Append(str);
if (i < arr.Length - 1)
{
yt_url.Append('&');
}
}
if (itag != 0 && IsNormalVideo(itag))
{
return false;
}
var info = new DashInfo()
{
Url = yt_url.ToString(),
Length = clen,
IsVideo = mime.StartsWith("video"),
ITag = itag,
ID = id,
Mime = mime,
Headers = message.RequestHeaders,
Cookies = message.Cookies
};
if (AddToQueue(info))
{
if (!info.IsVideo && mime.StartsWith("audio/"))
{
HandleDashAudio(info, message);
}
var di = GetDASHPair(info);
if (di == null)
{
return true;
}
var video = CreateDualSourceHTTPDownloadInfo(di, info, message);
// new DualSourceHTTPDownloadInfo
//{
// Uri1 = di.Url,
// Uri2 = info.Url,
// Headers1 = di.Headers,
// Headers2 = info.Headers,
// File = FileHelper.SanitizeFileName(message.File ?? FileHelper.GetFileName(new Uri(message.Url))) + ".mkv",
// Cookies1 = di.Cookies,
// Cookies2 = info.Cookies,
// ContentLength = di.Length + info.Length
//};
var size = di.Length + info.Length;
Log.Debug("Itag: " + info.ITag + " " + di.ITag);
var quality = Itags.GetValueOrDefault(info.IsVideo ? info.ITag : di.ITag, "MKV");
var displayText = $"[{quality}] {(size > 0 ? FormattingHelper.FormatSize(size) : string.Empty)}";
ApplicationContext.VideoTracker.AddVideoNotification(new StreamingVideoDisplayInfo
{
Quality = displayText,
Size = size,
CreationTime = DateTime.Now
}, video);
}
return true;
}
catch { }
return false;
}
private static void HandleDashAudio(DashInfo info, Message message)
{
try
{
var size = info.Length;
Log.Debug("Itag: " + info.ITag + " " + info.ITag);
var name = FileHelper.GetFileName(new Uri(info.Url));
var ext = Path.GetExtension(name);
if (string.IsNullOrEmpty(ext))
{
ext = info.Mime.Contains("webm") ? ".webm" : info.Mime.Contains("mp4") ? ".mp4" : "mkv";
}
var quality = ext.Substring(1)?.ToUpperInvariant();
var displayText = $"[{quality} AUDIO] {(size > 0 ? FormattingHelper.FormatSize(size) : string.Empty)}";
var video = new SingleSourceHTTPDownloadInfo
{
Uri = info.Url,
Headers = info.Headers,
File = FileHelper.SanitizeFileName(message.File ?? FileHelper.GetFileName(new Uri(message.Url))) + ext,
Cookies = info.Cookies,
ContentLength = size,
ContentType = info.Mime
};
ApplicationContext.VideoTracker.AddVideoNotification(new StreamingVideoDisplayInfo
{
Quality = displayText,
Size = size,
CreationTime = DateTime.Now
}, video);
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
}
private static DualSourceHTTPDownloadInfo CreateDualSourceHTTPDownloadInfo(DashInfo info1, DashInfo info2, Message message)
{
var (video, audio) = info1.IsVideo ? (info1, info2) : (info2, info1);
return new DualSourceHTTPDownloadInfo
{
Uri1 = video.Url,
Uri2 = audio.Url,
Headers1 = video.Headers,
Headers2 = audio.Headers,
File = FileHelper.SanitizeFileName(message.File ?? FileHelper.GetFileName(new Uri(message.Url))) + ".mkv",
Cookies1 = video.Cookies,
Cookies2 = audio.Cookies,
ContentLength = video.Length + audio.Length
};
}
internal static void ProcessNormalVideo(Message message2)
{
if (IsMediaFragment(message2.GetRequestHeaderFirstValue("Referer")))
{
Log.Debug($"Skipping url:{message2.Url} as it seems a media fragment");
return;
}
var file = (message2.File ?? FileHelper.GetFileName(new Uri(message2.Url)));
var type = message2.GetResponseHeaderFirstValue("Content-Type")?.ToLowerInvariant() ?? string.Empty;
var len = message2.GetContentLength();
if (string.IsNullOrEmpty(file))
{
file = FileHelper.GetFileName(new Uri(message2.Url));
}
string ext;
if (type.Contains("video/mp4"))
{
ext = "mp4";
}
else if (type.Contains("video/x-flv"))
{
ext = "flv";
}
else if (type.Contains("video/webm"))
{
ext = "mkv";
}
else if (type.Contains("matroska") || type.Contains("mkv"))
{
ext = "mkv";
}
else if (type.Equals("audio/mpeg") || type.Contains("audio/mp3"))
{
ext = "mp3";
}
else if (type.Contains("audio/aac"))
{
ext = "aac";
}
else if (type.Contains("audio/mp4"))
{
ext = "m4a";
}
else
{
return;
}
var video = new SingleSourceHTTPDownloadInfo
{
Uri = message2.Url,
Headers = message2.RequestHeaders,
File = FileHelper.SanitizeFileName(file) + "." + ext,
Cookies = message2.Cookies,
ContentLength = len
};
var size = long.Parse(message2.GetResponseHeaderFirstValue("Content-Length"));
var displayText = $"[{ext.ToUpperInvariant()}] {(size > 0 ? FormattingHelper.FormatSize(size) : string.Empty)}";
ApplicationContext.VideoTracker.AddVideoNotification(new StreamingVideoDisplayInfo
{
Quality = displayText,
Size = size,
CreationTime = DateTime.Now
}, video); ;
}
public static bool IsNormalVideo(int itag)
{
return ((itag > 4 && itag < 79) || (itag > 81 && itag < 86) || (itag > 99 && itag < 103));
}
private static bool AddIfNew(DashInfo info, List<DashInfo> list)
{
for (int i = list.Count - 1; i >= 0; i--)
{
var di = list[i];
if (di.Length == info.Length && di.ID == info.ID)
{
return false;
}
}
list.Add(info);
return true;
}
internal static bool IsHLS(string contentType)
{
foreach (var key in new string[] { "mpegurl", ".m3u8", "m3u8" })
{
if (contentType.Contains(key))
{
return true;
}
}
return false;
}
internal static bool IsDASH(string contentType)
{
foreach (var key in new string[] { "dash" })
{
if (contentType.Contains(key))
{
return true;
}
}
return false;
}
internal static bool IsYtFormat(string contentType)
{
foreach (var key in new string[] { "application/json" })
{
if (contentType.ToLowerInvariant().Contains(key))
{
return true;
}
}
return false;
}
public static bool AddToQueue(DashInfo info)
{
lock (lockObject)
{
if (videoQueue.Count > 32)
{
videoQueue.RemoveAt(0);
}
if (audioQueue.Count > 32)
{
audioQueue.RemoveAt(0);
}
if (info.IsVideo)
{
return AddIfNew(info, videoQueue);
}
else
{
return AddIfNew(info, audioQueue);
}
}
}
public static DashInfo GetDASHPair(DashInfo info)
{
lock (lockObject)
{
if (info.IsVideo)
{
if (audioQueue.Count < 1)
return null;
for (int i = audioQueue.Count - 1; i >= 0; i--)
{
var di = audioQueue[i];
if (di.ID == info.ID)
{
return di;
}
}
}
else
{
if (videoQueue.Count < 1)
return null;
for (int i = videoQueue.Count - 1; i >= 0; i--)
{
var di = videoQueue[i];
if (di.ID == info.ID)
{
if (lastVid?.Length == di.Length)
{
return null;
}
lastVid = di;
return di;
}
}
}
return null;
}
}
internal static string? DownloadManifest(Message message)
{
try
{
using var http = HttpClientFactory.NewHttpClient(null);
http.Timeout = TimeSpan.FromSeconds(Config.Instance.NetworkTimeout);
var acceptHeaderAdded = false;
var headers = new Dictionary<string, List<string>>();
var cookies = new Dictionary<string, string>();
foreach (var header in message.RequestHeaders)
{
headers.Add(header.Key, header.Value);
if (header.Key.ToLowerInvariant() == "accept")
{
acceptHeaderAdded = true;
}
}
if (acceptHeaderAdded)
{
headers.Add("Accept", new List<string> { "*/*" });
}
foreach (var cookie in message.Cookies)
{
cookies.Add(cookie.Key, cookie.Value);
}
byte[]? body = null;
if (message.RequestBody != null)
{
body = Convert.FromBase64String(message.RequestBody);
}
var request = "POST" == message.RequestMethod ?
http.CreatePostRequest(new Uri(message.Url), headers, cookies, null, body) :
http.CreateGetRequest(new Uri(message.Url), headers, cookies, null);
using var response = http.Send(request);
Log.Debug("Downloading manifest response: " + response.StatusCode);
var path = Path.GetTempFileName();
using var fs = new FileStream(path, FileMode.Create);
response.GetResponseStream().CopyTo(fs);
Log.Debug("Downloaded manifest: " + message.Url + " -> " + path);
return path;
}
catch (Exception e) { Log.Debug(e, "Error"); }
return null;
}
private static bool IsMediaFragment(string referer)
{
if (string.IsNullOrEmpty(referer)) return false;
var sha1 = ComputeHash(referer);
lock (referersToSkip)
{
if (referersToSkip.ContainsKey(sha1))
{
referersToSkip[sha1] = DateTime.Now;
return true;
}
}
return false;
}
private static void AddToSkippedRefererList(string referer)
{
if (string.IsNullOrEmpty(referer)) return;
lock (referersToSkip)
{
referersToSkip[ComputeHash(referer)] = DateTime.Now;
}
}
private static string ComputeHash(string input)
{
using var sha1 = new SHA1Managed();
var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(input));
return string.Concat(hash.Select(b => b.ToString("x2")));
}
public static readonly Dictionary<Int32, string> Itags = new()
{
[5] = "240p",
[133] = "240p",
[6] = "270p",
[134] = "360p",
[135] = "480p",
[136] = "720p",
[264] = "1440p",
[137] = "1080p",
[266] = "2160p",
[139] = "Low bitrate",
[140] = "Med bitrate",
[13] = "Small",
[141] = "Hi bitrate",
[271] = "1440p",
[272] = "2160p",
[17] = "144p",
[18] = "360p",
[22] = "720p",
[278] = "144p",
[160] = "144p",
[34] = "360p",
[35] = "480p",
[36] = "240p",
[37] = "1080p",
[38] = "1080p",
[167] = "360p",
[168] = "480p",
[169] = "720p",
[170] = "1080p",
[298] = "720p",
[43] = "360p",
[171] = "Med bitrate",
[299] = "2160p",
[44] = "480p",
[172] = "Hi bitrate",
[45] = "720p",
[46] = "1080p",
[302] = "720p",
[303] = "1080p",
[308] = "1440p",
[313] = "2160p",
[59] = "480p",
[315] = "2160p",
[78] = "480p",
[82] = "360p 3D",
[83] = "480p 3D",
[84] = "720p 3D",
[85] = "1080p 3D",
[218] = "480p",
[219] = "480p",
[100] = "360p 3D",
[101] = "480p 3D",
[102] = "720p 3D",
[242] = "240p",
[243] = "360p",
[244] = "480p",
[245] = "480p",
[246] = "480p",
[247] = "720p",
[248] = "1080p"
};
}
class DashInfo
{
public string Url;
public long Length;
public bool IsVideo;
public string ID;
public int ITag;
public string Mime;
public Dictionary<string, List<string>> Headers;
public Dictionary<string, string> Cookies;
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.IO;
using TraceLog;
using XDM.Core;
using XDM.Core.UI;
namespace XDM.Core
{
internal static class CallbackActions
{
public static void DownloadStarted(string id)
{
var download = ApplicationContext.MainWindow.FindInProgressItem(id);
if (download == null) return;
download.Status = DownloadStatus.Downloading;
}
public static void DownloadFailed(string id)
{
var download = ApplicationContext.MainWindow.FindInProgressItem(id);
if (download == null) return;
download.Status = DownloadStatus.Stopped;
}
public static void DownloadFinished(string id, long finalFileSize, string filePath, Action callback)
{
Log.Debug("Final file name: " + filePath);
var download = ApplicationContext.MainWindow.FindInProgressItem(id);
if (download == null) return;
var downloadEntry = download.DownloadEntry;
downloadEntry.Progress = 100;
var finishedEntry = new FinishedDownloadItem
{
Name = Path.GetFileName(filePath),
Id = downloadEntry.Id,
DateAdded = downloadEntry.DateAdded,
Size = downloadEntry.Size > 0 ? downloadEntry.Size : finalFileSize,
DownloadType = downloadEntry.DownloadType,
TargetDir = Path.GetDirectoryName(filePath)!,
PrimaryUrl = downloadEntry.PrimaryUrl,
Authentication = downloadEntry.Authentication,
Proxy = downloadEntry.Proxy
};
ApplicationContext.MainWindow.AddToTop(finishedEntry);
ApplicationContext.MainWindow.Delete(download);
QueueManager.RemoveFinishedDownload(download.DownloadEntry.Id);
if (ApplicationContext.CoreService.ActiveDownloadCount == 0 && ApplicationContext.MainWindow.IsInProgressViewSelected)
{
Log.Debug("switching to finished listview");
ApplicationContext.MainWindow.SwitchToFinishedView();
}
callback.Invoke();
}
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.Threading;
namespace XDM.Core
{
public class CancelFlag
{
public static CancelFlag None = new CancelFlag();
private bool _cancelled = false;
private ReaderWriterLockSlim locker = new(LockRecursionPolicy.SupportsRecursion);
public void Cancel()
{
try
{
locker.EnterWriteLock();
this._cancelled = true;
}
finally
{
locker.ExitWriteLock();
}
}
public bool IsCancellationRequested
{
get
{
try
{
locker.EnterReadLock();
return this._cancelled;
}
finally
{
locker.ExitReadLock();
}
}
}
public void ThrowIfCancellationRequested()
{
try
{
locker.EnterReadLock();
if (this._cancelled)
{
throw new OperationCanceledException();
}
}
finally
{
locker.ExitReadLock();
}
}
}
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace XDM.Core
{
public struct Category
{
public string Name { get; set; }
public HashSet<string> FileExtensions { get; set; }
public string DisplayName { get; set; }
public bool IsPredefined { get; set; }
public string DefaultFolder { get; set; }
public override string ToString()
{
return DisplayName;
}
}
}

View File

@ -0,0 +1,208 @@
#if NET5_0_OR_GREATER
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using TraceLog;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
internal class DotNetHttpClient : IHttpClient
{
private bool disposed;
private HttpClient? hc;
private CancellationTokenSource cts = new();
private ProxyInfo? proxy;
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(100);
internal DotNetHttpClient(ProxyInfo? proxy)
{
this.proxy = proxy;
}
private HttpRequestMessage CreateRequest(Uri uri, HttpMethod method,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null)
{
if (disposed)
{
throw new ObjectDisposedException("DotNetHttpClient");
}
var http = new HttpRequestMessage
{
Method = method,
RequestUri = uri
};
lock (this)
{
if (this.hc == null)
{
var handler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.All,
PreAuthenticate = true,
UseDefaultCredentials = true,
MaxConnectionsPerServer = 100,
ServerCertificateCustomValidationCallback = (a, b, c, d) => true
};
var p = ProxyHelper.GetProxy(this.proxy);
if (p != null)
{
handler.Proxy = p;
}
if (authentication != null && !string.IsNullOrEmpty(authentication.Value.UserName))
{
handler.Credentials = new NetworkCredential(authentication.Value.UserName, authentication.Value.Password);
}
this.hc = new HttpClient(handler)
{
Timeout = this.Timeout
};
}
}
if (headers != null)
{
foreach (var e in headers)
{
SetHeader(http, e.Key, e.Value);
}
}
if (cookies != null)
{
foreach (var e in cookies)
{
SetHeader(http, e.Key, e.Value);
}
}
return http;
}
public HttpRequest CreateGetRequest(Uri uri,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null)
{
var req = this.CreateRequest(uri, HttpMethod.Get, headers, cookies, authentication);
return new HttpRequest { Session = new DotNetHttpSession { Request = req } };
}
public HttpRequest CreatePostRequest(Uri uri,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null,
byte[]? body = null)
{
var req = this.CreateRequest(uri, HttpMethod.Post, headers, cookies, authentication);
if (body != null)
{
req.Content = new ByteArrayContent(body);
if (headers != null && headers.TryGetValue("Content-Type", out List<string>? values))
{
if (values != null && values.Count > 0)
{
req.Content.Headers.ContentType = new MediaTypeHeaderValue(values[0]);
}
}
}
return new HttpRequest { Session = new DotNetHttpSession { Request = req } };
}
public HttpResponse Send(HttpRequest request)
{
HttpRequestMessage r;
HttpResponseMessage? response = null;
if (request.Session == null)
{
throw new ArgumentNullException(nameof(request.Session));
}
if (request.Session is not DotNetHttpSession session)
{
throw new ArgumentNullException(nameof(request.Session));
}
if (session.Request == null)
{
throw new ArgumentNullException(nameof(session.Request));
}
r = session.Request;
try
{
response = this.hc!.Send(r, HttpCompletionOption.ResponseHeadersRead, cts.Token);
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException we)
{
Log.Debug(we, we.Message);
response?.Dispose();
}
session.Response = response;
return new HttpResponse { Session = session };
}
public void Dispose()
{
lock (this)
{
try
{
disposed = true;
cts.Cancel();
this.hc?.Dispose();
}
catch { }
}
}
public void Close()
{
this.Dispose();
}
private void SetHeader(HttpRequestMessage request, string key, IEnumerable<string> values)
{
try
{
foreach (var value in values)
{
if (!string.Equals(key, "Content-Type", StringComparison.InvariantCultureIgnoreCase))
{
request.Headers.TryAddWithoutValidation(key, value);
}
}
}
catch (Exception ex)
{
Log.Debug(ex, "Error setting header value");
}
}
private void SetHeader(HttpRequestMessage request, string key, string value)
{
try
{
if (!string.Equals(key, "Content-Type", StringComparison.InvariantCultureIgnoreCase))
{
request.Headers.TryAddWithoutValidation(key, value);
}
}
catch (Exception ex)
{
Log.Debug(ex, "Error setting header value");
}
}
}
}
#endif

View File

@ -0,0 +1,98 @@
#if NET5_0_OR_GREATER
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
internal class DotNetHttpSession : IHttpSession
{
private CancellationTokenSource cancellationTokenSource = new();
internal CancellationToken CancellationToken => cancellationTokenSource.Token;
internal HttpRequestMessage? Request { get; set; }
internal HttpResponseMessage? Response { get; set; }
public string? ContentType => Response?.Content?.Headers?.ContentType?.MediaType;
public string? ContentDispositionFileName => GetContentDisposition();
public long ContentLength => Response?.Content?.Headers?.ContentLength ?? Response?.Content?.Headers?.ContentRange?.Length ?? -1;
public DateTime LastModified => Response!.Content?.Headers?.LastModified?.Date ?? DateTime.Now;
public HttpStatusCode StatusCode => Response!.StatusCode;
public Uri ResponseUri => Response!.RequestMessage!.RequestUri;
public void Abort()
{
cancellationTokenSource.Cancel();
Response?.Dispose();
}
public void AddRange(long range)
{
Request!.Headers.Range = new RangeHeaderValue(range, null);
}
public void AddRange(long start, long end)
{
Request!.Headers.Range = new RangeHeaderValue(start, end);
}
public void Close()
{
Response?.Dispose();
}
public void Dispose()
{
Response?.Dispose();
}
public Stream GetResponseStream()
{
return Response!.Content.ReadAsStreamAsync(this.CancellationToken).Result;
}
public string? ReadAsString(CancelFlag cancellationToken)
{
return Response?.Content.ReadAsStringAsync(this.CancellationToken).Result;
}
public void EnsureSuccessStatusCode()
{
WebRequestExtensions.EnsureSuccessStatusCode(Response!.StatusCode, Response!.ReasonPhrase);
}
private string? GetContentDisposition()
{
if (Response!.Content.Headers.TryGetValues("Content-Disposition", out IEnumerable<string>? values) && values != null)
{
var cd = values.FirstOrDefault();
if (!string.IsNullOrWhiteSpace(cd))
{
return WebRequestExtensions.GetContentDispositionFileName(cd!);
}
}
return null;
}
public long GetTotalLengthFromContentRange()
{
var len = Response?.Content?.Headers?.ContentRange?.Length ?? -1;
return len;
}
}
}
#endif

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
public static class HttpClientFactory
{
public static IHttpClient NewHttpClient(ProxyInfo? proxyInfo)
{
ProxyInfo? proxy = null;
if (proxyInfo.HasValue)
{
if (proxyInfo.Value.ProxyType != ProxyType.Custom)
{
proxy = proxyInfo;
}
else if (!string.IsNullOrEmpty(proxyInfo.Value.Host) && proxyInfo.Value.Port > 0)
{
proxy = proxyInfo;
}
}
if (Environment.Version.Major == 2)
{
return new WinHttpClient(proxy);
}
else
{
#if NET5_0_OR_GREATER
return new DotNetHttpClient(proxy);
#else
return new NetFxHttpClient(proxy);
#endif
}
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
public class HttpRequest
{
//private Uri uri;
//private Dictionary<string, List<string>>? headers = null;
//private Dictionary<string, string>? cookies = null;
//private AuthenticationInfo? authentication = null;
//private long rangeStart = 0, rangeEnd = -1;
//public HttpRequest(Uri uri,
// Dictionary<string, List<string>>? headers,
// Dictionary<string, string>? cookies,
// AuthenticationInfo? authentication)
//{
// this.uri = uri;
// this.headers = headers;
// this.cookies = cookies;
// this.authentication = authentication;
//}
//public Uri Uri => uri;
//public Dictionary<string, List<string>>? Headers => headers;
//public Dictionary<string, string>? Cookies => cookies;
//public AuthenticationInfo? Authentication => authentication;
//public long RangeStart => rangeStart;
//public long RangeEnd => rangeEnd;
internal IHttpSession? Session { get; set; }
public void AddRange(long range)
{
this.Session!.AddRange(range);
}
public void AddRange(long start, long end)
{
this.Session!.AddRange(start, end);
}
public void Abort()
{
this.Session?.Abort();
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
public class HttpResponse : IDisposable
{
internal IHttpSession? Session { get; set; }
public void EnsureSuccessStatusCode() => Session!.EnsureSuccessStatusCode();
public string ContentType => Session!.ContentType;
public HttpStatusCode StatusCode => Session!.StatusCode;
public long ContentLength => Session!.ContentLength;
public long ContentRangeLength => Session!.GetTotalLengthFromContentRange();
public string ReadAsString(CancelFlag cancellationToken) => Session!.ReadAsString(cancellationToken);
public Uri ResponseUri => Session!.ResponseUri;
public string? ContentDispositionFileName => Session!.ContentDispositionFileName;
public DateTime LastModified => Session!.LastModified;
public void Dispose()
{
this.Close();
}
public void Close()
{
Session?.Close();
}
public Stream GetResponseStream() => Session!.GetResponseStream();
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace XDM.Core.Clients.Http
{
class HttpSession
{
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
public interface IHttpClient : IDisposable
{
public TimeSpan Timeout { get; set; }
public HttpRequest CreateGetRequest(Uri uri,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null);
public HttpRequest CreatePostRequest(Uri uri,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null,
byte[]? body = null);
public HttpResponse Send(HttpRequest request);
public void Close();
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace XDM.Core.Clients.Http
{
interface IHttpClientFactory
{
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
internal interface IHttpSession : IDisposable
{
public void AddRange(long range);
public void AddRange(long start, long end);
public string? ContentType { get; }
public string? ContentDispositionFileName { get; }
public long ContentLength { get; }
public DateTime LastModified { get; }
public HttpStatusCode StatusCode { get; }
public string ReadAsString(CancelFlag cancellationToken);
public void EnsureSuccessStatusCode();
public Uri ResponseUri { get; }
public void Close();
public Stream GetResponseStream();
public void Abort();
public long GetTotalLengthFromContentRange();
}
}

View File

@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using TraceLog;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
internal class NetFxHttpClient : IHttpClient
{
private readonly string connectionGroupName = Guid.NewGuid().ToString();
private HashSet<ServicePoint> servicePoints = new();
private bool disposed;
private ProxyInfo? proxy;
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(100);
internal NetFxHttpClient(ProxyInfo? proxy)
{
this.proxy = proxy;
}
private HttpWebRequest CreateRequest(Uri uri)
{
if (disposed)
{
throw new ObjectDisposedException("HttpWebRequestClient");
}
var http = (HttpWebRequest)WebRequest.Create(uri);
var p = ProxyHelper.GetProxy(this.proxy);
if (p != null)
{
http.Proxy = p;
}
http.Timeout = http.ReadWriteTimeout = (int)Timeout.TotalMilliseconds;
http.UseDefaultCredentials = true;
http.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
http.AllowAutoRedirect = true;
http.ConnectionGroupName = this.connectionGroupName;
var sp = http.ServicePoint;
lock (servicePoints)
{
if (sp != null)
{
servicePoints.Add(sp);
}
}
return http;
}
public HttpRequest CreateGetRequest(Uri uri,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null)
{
var req = this.CreateRequest(uri);
if (headers != null)
{
foreach (var e in headers)
{
SetHeader(req, e.Key, e.Value);
}
}
if (cookies != null)
{
foreach (var e in cookies)
{
SetHeader(req, e.Key, e.Value);
}
}
if (authentication != null && !string.IsNullOrEmpty(authentication.Value.UserName))
{
req.Credentials = new NetworkCredential(authentication.Value.UserName, authentication.Value.Password);
}
return new HttpRequest { Session = new NetFxHttpSession { Request = req } };
}
public HttpRequest CreatePostRequest(Uri uri,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null,
byte[]? body = null)
{
var req = this.CreateRequest(uri);
req.Method = "POST";
if (headers != null)
{
foreach (var e in headers)
{
SetHeader(req, e.Key, e.Value);
}
}
if (cookies != null)
{
foreach (var e in cookies)
{
SetHeader(req, e.Key, e.Value);
}
}
if (authentication != null && !string.IsNullOrEmpty(authentication.Value.UserName))
{
req.Credentials = new NetworkCredential(authentication.Value.UserName, authentication.Value.Password);
}
if (body != null)
{
req.ContentLength = body.Length;
using var rs = req.GetRequestStream();
rs.Write(body, 0, body.Length);
rs.Close();
}
return new HttpRequest { Session = new NetFxHttpSession { Request = req } };
}
public HttpResponse Send(HttpRequest request)
{
HttpWebRequest r;
HttpWebResponse response;
if (request.Session == null)
{
throw new ArgumentNullException(nameof(request.Session));
}
if (request.Session is not NetFxHttpSession session)
{
throw new ArgumentNullException(nameof(request.Session));
}
if (session.Request == null)
{
throw new ArgumentNullException(nameof(session.Request));
}
r = session.Request;
try
{
response = (HttpWebResponse)r.GetResponse();
}
catch (WebException we)
{
Log.Debug(we, we.Message);
if (we.Response == null)
{
throw new Exception("Connectivity error");
}
response = (HttpWebResponse?)we.Response!;
response.Discard();
response.Close();
}
var servicePoint = r.ServicePoint;
if (servicePoint != null)
{
servicePoints.Add(servicePoint);
}
session.Response = response;
return new HttpResponse { Session = session };
}
public void Dispose()
{
lock (this)
{
try
{
disposed = true;
foreach (var servicePoint in servicePoints)
{
Log.Debug("Disposing service point");
Log.Debug("ConnectionName: " + servicePoint.ConnectionName +
"\nCurrentConnections: " + servicePoint.CurrentConnections +
"\nAddress: " + servicePoint.Address +
"\nGetHashCode(): " + servicePoint.GetHashCode());
servicePoint.CloseConnectionGroup(this.connectionGroupName);
}
}
catch { }
}
}
public void Close()
{
this.Dispose();
}
private static void SetHeader(HttpWebRequest request, string key, IEnumerable<string> values)
{
try
{
foreach (var value in values)
{
SetHeader(request, key, value);
}
}
catch (Exception ex)
{
Log.Debug(ex, "Error setting header value");
}
}
private static void SetHeader(HttpWebRequest request, string key, string value)
{
switch (key.ToLowerInvariant())
{
case "accept":
request.Accept = value;
break;
case "connection":
request.Connection = value;
break;
case "content-type":
request.ContentType = value;
break;
case "expect":
request.Expect = value;
break;
#if !NET35
case "date":
request.Date = DateTime.Parse(value);
break;
case "host":
request.Host = value;
break;
#endif
case "if-modified-since":
request.IfModifiedSince = DateTime.Parse(value);
break;
case "referer":
request.Referer = value;
break;
case "user-agent":
request.UserAgent = value;
break;
case "transfer-encoding":
request.TransferEncoding = value;
break;
case "range":
case "content-length":
break;
default:
request.Headers.Add(key, value);
break;
}
}
}
}

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
internal class NetFxHttpSession : IHttpSession
{
internal HttpWebRequest? Request { get; set; }
internal HttpWebResponse? Response { get; set; }
public string ContentType => Response!.ContentType;
public string? ContentDispositionFileName => Response!.GetContentDispositionFileName();
public long ContentLength => Response!.GetContentLength();
public DateTime LastModified => Response!.LastModified;
public HttpStatusCode StatusCode => Response!.StatusCode;
public Uri ResponseUri => Response!.ResponseUri;
public void Abort()
{
try { Request!.Abort(); } catch { }
}
public void AddRange(long range)
{
Request!.AddRange(range);
}
public void AddRange(long start, long end)
{
Request!.AddRange(start, end);
}
public void Close()
{
Response!.Close();
}
public void Dispose()
{
#if NET35
Response!.Close();
#else
Response!.Dispose();
#endif
}
public Stream GetResponseStream()
{
return Response!.GetResponseStream();
}
public string ReadAsString(CancelFlag cancellationToken)
{
return Response!.ReadAsString(cancellationToken);
}
public void EnsureSuccessStatusCode()
{
Response!.EnsureSuccessStatusCode();
}
public long GetTotalLengthFromContentRange()
{
var contentRange = Response!.Headers.GetValues("Content-Range")?.First();
return WebRequestExtensions.ContentLengthFromContentRange(contentRange);
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using TraceLog;
using XDM.Core;
namespace XDM.Core.Clients.Http
{
internal static class ProxyHelper
{
internal static IWebProxy? GetProxy(ProxyInfo? proxy)
{
if (proxy.HasValue)
{
Log.Debug("Proxy type: " + proxy.Value.ProxyType);
if (proxy.Value.ProxyType == ProxyType.Direct)
{
return new WebProxy();
}
else if (proxy.Value.ProxyType == ProxyType.Custom)
{
var p = new WebProxy(proxy.Value.Host, proxy.Value.Port);
if (!string.IsNullOrEmpty(proxy.Value.UserName))
{
p.Credentials = new NetworkCredential(proxy.Value.UserName, proxy.Value.Password);
}
return p;
}
}
return null;
}
}
}

View File

@ -0,0 +1,175 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using TraceLog;
using XDM.Core;
using XDM.Core.Util;
namespace XDM.Core.Clients.Http
{
public static class WebRequestExtensions
{
public static void EnsureSuccessStatusCode(this HttpWebResponse response)
{
if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.PartialContent)
{
throw new HttpException(response.StatusDescription, null, response.StatusCode);
}
}
public static void EnsureSuccessStatusCode(HttpStatusCode statusCode, string? statusDescription)
{
if (statusCode != HttpStatusCode.OK && statusCode != HttpStatusCode.PartialContent)
{
throw new HttpException(statusDescription ?? "Invalid response", null, statusCode);
}
}
public static void Discard(this HttpWebResponse response)
{
#if NET35
var bytes = new byte[8192];
#else
var bytes = System.Buffers.ArrayPool<byte>.Shared.Rent(8192);
#endif
try
{
using var stream = response.GetResponseStream();
while (true)
{
int x = stream.Read(bytes, 0, bytes.Length);
if (x == 0) break;
}
}
finally
{
#if !NET35
System.Buffers.ArrayPool<byte>.Shared.Return(bytes);
#endif
}
}
public static long GetContentLength(this HttpWebResponse response)
{
try
{
if (response.ContentLength > 0) return response.ContentLength;
var contentRange = response.Headers.GetValues("Content-Range")?.First();
return ContentLengthFromContentRange(contentRange);
}
catch { }
return -1;
}
public static long ContentLengthFromContentRange(string? contentRange)
{
try
{
if (contentRange == null) return -1;
var index = contentRange.IndexOf('/');
if (index == -1) return -1;
if (Int64.TryParse(contentRange.Substring(index + 1).Trim(), out long result))
{
return result;
}
}
catch { }
return -1;
}
public static string? GetContentDispositionFileName(this HttpWebResponse response)
{
return GetContentDispositionFileName(response.Headers.Get("Content-Disposition"));
}
public static string? GetContentDispositionFileName(string contentDisposition)
{
try
{
var r1 = new Regex(@"\s*filename\*\s*=\s*[^']*\'\s*\'(.*)");
var r2 = new Regex("\\s*filename\\s*=\\s*\"([^\"]*)\"");
var r3 = new Regex("filename\\s*=\\s*([^\"]+)");
//var contentDisposition = response.Headers.Get("Content-Disposition");
if (contentDisposition != null)
{
Log.Debug("Trying to get filename from: " + contentDisposition);
foreach (var r in new Regex[] { r1, r2, r3 })
{
var m = r.Match(contentDisposition);
if (m.Success && m.Groups.Count >= 2)
{
return FileHelper.SanitizeFileName(Uri.UnescapeDataString(m.Groups[1].Value));
}
}
}
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
return null;
}
public static string ReadAsString(this HttpWebResponse response, CancelFlag cancellationToken)
{
#if NET35
var buf = new byte[8192];
#else
var buf = System.Buffers.ArrayPool<byte>.Shared.Rent(8192);
#endif
try
{
var sourceStream = response.GetResponseStream();
var ms = new MemoryStream();
while (!cancellationToken.IsCancellationRequested)
{
var x = sourceStream.Read(buf, 0, buf.Length);
if (x == 0)
{
break;
}
ms.Write(buf, 0, x);
cancellationToken.ThrowIfCancellationRequested();
}
return Encoding.UTF8.GetString(ms.ToArray());
}
finally
{
#if !NET35
System.Buffers.ArrayPool<byte>.Shared.Return(buf);
#endif
}
}
#if NET35
public static void AddRange(this HttpWebRequest request, long start, long end)
{
var method = typeof(WebHeaderCollection).GetMethod
("AddWithoutValidate", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
string key = "Range";
string val = string.Format("bytes={0}-{1}", start, end);
method.Invoke(request.Headers, new object[] { key, val });
}
public static void AddRange(this HttpWebRequest request, long start)
{
var method = typeof(WebHeaderCollection).GetMethod
("AddWithoutValidate", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
string key = "Range";
string val = string.Format("bytes={0}-", start);
method.Invoke(request.Headers, new object[] { key, val });
}
#endif
}
}

View File

@ -0,0 +1,347 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using XDM.Core;
using static Interop.WinHttp;
using System.Runtime.InteropServices;
using TraceLog;
using System.Net.Http;
namespace XDM.Core.Clients.Http
{
internal class WinHttpClient : IHttpClient
{
private SafeWinHttpHandle? sessionHandle;
private bool disposed;
private ProxyInfo? proxy;
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(100);
internal WinHttpClient(ProxyInfo? proxy)
{
this.proxy = proxy;
}
private HttpRequest CreateRequest(
Uri uri,
string method,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null,
byte[]? body = null)
{
if (disposed)
{
throw new ObjectDisposedException("HttpWebRequestClient");
}
lock (this)
{
if (this.sessionHandle == null)
{
if (this.proxy.HasValue && this.proxy.Value.ProxyType == ProxyType.System &&
GetProxyForUrl(uri, out string? proxy, out string? bypass) && proxy != null)
{
sessionHandle = WinHttpOpen(
IntPtr.Zero,
WINHTTP_ACCESS_TYPE_NAMED_PROXY,
proxy,
bypass,
0);
}
else if (this.proxy.HasValue && this.proxy.Value.ProxyType == ProxyType.Custom)
{
sessionHandle = WinHttpOpen(
IntPtr.Zero,
WINHTTP_ACCESS_TYPE_NAMED_PROXY,
"http://" + this.proxy.Value.Host + ":" + this.proxy.Value.Port,
WINHTTP_NO_PROXY_BYPASS,
0);
}
else
{
sessionHandle = WinHttpOpen(
IntPtr.Zero,
WINHTTP_ACCESS_TYPE_NO_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0);
}
if (sessionHandle.IsInvalid)
{
throw new IOException(nameof(sessionHandle) + " " + Marshal.GetLastWin32Error());
}
uint optionData = 100;
if (!WinHttpSetOption(sessionHandle, WINHTTP_OPTION_MAX_CONNS_PER_SERVER, ref optionData))
{
Log.Debug("Unable to set connection limit");
}
//const uint WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 = 0x00000008;
//const uint WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 = 0x00000020;
//const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 = 0x00000080;
//const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 = 0x00000200;
//const uint WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 = 0x00000800;
//const uint WINHTTP_FLAG_SECURE_PROTOCOL_ALL = (WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 | WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1);
var proto = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
if (!WinHttpSetOption(sessionHandle, WINHTTP_OPTION_SECURE_PROTOCOLS, ref proto))
{
Log.Debug("WINHTTP_OPTION_SECURE_PROTOCOLS not set: " + Marshal.GetLastWin32Error());
}
var timeout = (int)Timeout.TotalMilliseconds;
if (!WinHttpSetTimeouts(sessionHandle, timeout, timeout, timeout, timeout))
{
Log.Debug("Set WinHttpSetTimeouts failed: " + Marshal.GetLastWin32Error());
}
}
}
var hConnect = WinHttpConnect(sessionHandle, uri.Host, (ushort)uri.Port, 0);
if (hConnect.IsInvalid)
{
throw new IOException(nameof(hConnect));
}
var hRequest = WinHttpOpenRequest(hConnect, method, uri.PathAndQuery,
null, null, null,
uri.Scheme == "https" ? WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE : WINHTTP_FLAG_ESCAPE_DISABLE);
if (hRequest.IsInvalid)
{
throw new IOException(nameof(hRequest));
}
var dwFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
if (!WinHttpSetOption(
hRequest,
WINHTTP_OPTION_SECURITY_FLAGS,
ref dwFlags))
{
Log.Debug("Ignore cert error: " + Marshal.GetLastWin32Error());
}
else
{
Log.Debug("Ignore cert error config set");
}
if (authentication.HasValue && !string.IsNullOrEmpty(authentication.Value.UserName))
{
if (!WinHttpSetCredentials(hRequest, WINHTTP_AUTH_TARGET_SERVER,
WINHTTP_AUTH_SCHEME_BASIC, authentication.Value.UserName, authentication.Value.Password, IntPtr.Zero))
{
Log.Debug("Error WinHttpSetCredentials - server: " + Marshal.GetLastWin32Error());
}
}
if (this.proxy.HasValue && !string.IsNullOrEmpty(this.proxy.Value.UserName))
{
if (!WinHttpSetCredentials(hRequest, WINHTTP_AUTH_TARGET_PROXY,
WINHTTP_AUTH_SCHEME_BASIC, authentication.Value.UserName, authentication.Value.Password, IntPtr.Zero))
{
Log.Debug("Error WinHttpSetCredentials - proxy: " + Marshal.GetLastWin32Error());
}
}
//dwFlags = WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1;
//if (!WinHttpSetOption(
// hConnect,
// WINHTTP_OPTION_SECURE_PROTOCOLS,
// ref dwFlags))
//{
// Log.Debug("WINHTTP_OPTION_SECURE_PROTOCOLS: " + Marshal.GetLastWin32Error());
//}
//else
//{
// Log.Debug("WINHTTP_OPTION_SECURE_PROTOCOLS set");
//}
return new HttpRequest { Session = new WinHttpSession(uri, hConnect, hRequest, headers, cookies) };
}
public HttpRequest CreateGetRequest(
Uri uri,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null)
{
return CreateRequest(uri, "GET", headers, cookies, authentication);
}
public HttpRequest CreatePostRequest(
Uri uri,
Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
AuthenticationInfo? authentication = null,
byte[]? body = null)
{
return CreateRequest(uri, "POST", headers, cookies, authentication, body);
}
public void Dispose()
{
disposed = true;
this.sessionHandle?.Close();
}
public void Close()
{
Dispose();
}
public HttpResponse Send(HttpRequest request)
{
var session = (WinHttpSession)request.Session!;
var headerBuf = PrepareHeaders(session.Headers, session.Cookies, session.RangeStart, session.RangeEnd);
if (headerBuf.Length > 0)
{
if (!WinHttpAddRequestHeaders(session.RequestHandle, headerBuf, (uint)headerBuf.Length, WINHTTP_ADDREQ_FLAG_ADD))
{
Log.Debug("Unable to set headers");
}
}
var lpOptional = IntPtr.Zero;
var dwOptionalLength = 0;
if (session.PostData.HasValue)
{
lpOptional = session.PostData.Value;
dwOptionalLength = session.PostDataSize;
}
if (WinHttpSendRequest(session.RequestHandle, IntPtr.Zero, 0, lpOptional,
(uint)dwOptionalLength, (uint)dwOptionalLength, IntPtr.Zero))
{
if (WinHttpReceiveResponse(session.RequestHandle, IntPtr.Zero))
{
var headerSize = CalculateHeaderBufferSize(session.RequestHandle);
if (headerSize > 0)
{
var headers = GetHeaders(session.RequestHandle, headerSize);
session.SetHeaders(headers);
return new HttpResponse { Session = session };
}
else
{
Log.Debug("Failed CalculateHeaderBufferSize: " + Marshal.GetLastWin32Error());
}
}
else
{
Log.Debug("Failed WinHttpReceiveResponse: " + Marshal.GetLastWin32Error());
}
}
else
{
Log.Debug("Failed WinHttpSendRequest: " + Marshal.GetLastWin32Error());
}
throw new Exception();
}
private uint CalculateHeaderBufferSize(SafeWinHttpHandle hRequest)
{
uint bufferLengthInBytes = 0;
uint index = 0;
if (!WinHttpQueryHeaders(
hRequest,
WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,
IntPtr.Zero,
ref bufferLengthInBytes,
ref index) && Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
{
return bufferLengthInBytes;
}
return 0;
}
private string GetHeaders(SafeWinHttpHandle hRequest, uint bufferLengthInBytes)
{
uint index = 0;
var pBuf = Marshal.AllocHGlobal((int)bufferLengthInBytes);
if (!WinHttpQueryHeaders(
hRequest,
WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,
pBuf,
ref bufferLengthInBytes,
ref index))
{
throw new IOException("Unable to read headers: " + Marshal.GetLastWin32Error());
}
Log.Debug("Header len after read: " + bufferLengthInBytes);
var header = Marshal.PtrToStringAuto(pBuf);
Marshal.FreeHGlobal(pBuf);
return header;
}
private StringBuilder PrepareHeaders(Dictionary<string, List<string>>? headers = null,
Dictionary<string, string>? cookies = null,
long rangeStart = -1, long rangeEnd = -1)
{
var buf = new StringBuilder();
if (headers != null)
{
foreach (var key in headers.Keys)
{
buf.Append(key).Append(": ").Append(string.Join(", ", headers[key].ToArray())).Append("\r\n");
}
}
if (cookies != null && cookies.Count > 0)
{
buf.Append("Cookie: ");
var first = true;
foreach (var key in cookies.Keys)
{
if (!first) buf.Append(", ");
buf.Append(cookies[key]);
first = false;
}
buf.Append("\r\n");
}
if (rangeStart > 0 && rangeEnd > 0)
{
buf.Append(string.Format("Range: bytes={0}-{1}\r\n", rangeStart, rangeEnd));
}
else if (rangeStart > 0)
{
buf.Append(string.Format("Range: bytes={0}-\r\n", rangeStart));
}
return buf;
}
private bool GetProxyForUrl(Uri url, out string? proxyHost, out string? bypass)
{
proxyHost = null;
bypass = null;
var handle = WinHttpOpen(
IntPtr.Zero,
WINHTTP_ACCESS_TYPE_NO_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0);
try
{
var _proxyHelper = new WinInetProxyHelper();
if (_proxyHelper.GetProxyForUrl(handle, url, out WINHTTP_PROXY_INFO info))
{
proxyHost = Marshal.PtrToStringAuto(info.Proxy);
bypass = Marshal.PtrToStringAuto(info.ProxyBypass);
return true;
}
return false;
}
finally
{
handle.Close();
}
}
}
}

View File

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using static Interop.WinHttp;
namespace XDM.Core.Clients.Http
{
internal class WinHttpResponseStream : Stream
{
private SafeWinHttpHandle hRequest;
public WinHttpResponseStream(SafeWinHttpHandle hRequest)
{
this.hRequest = hRequest;
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => -1;
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public override void Flush()
{
}
public override int Read(byte[] buffer, int offset, int count)
{
lock (hRequest)
{
var pData = Marshal.AllocHGlobal(count);
try
{
var ret = WinHttpReadData(hRequest, pData, (uint)count, out long lpdwNumberOfBytesAvailable);
if (ret && lpdwNumberOfBytesAvailable == 0)
{
return 0;
}
else if (lpdwNumberOfBytesAvailable == 0)
{
throw new IOException("Unable to read from reponse");
}
Marshal.Copy(pData, buffer, offset, (int)lpdwNumberOfBytesAvailable);
return (int)lpdwNumberOfBytesAvailable;
}
finally
{
Marshal.FreeHGlobal(pData);
}
}
}
public override long Seek(long offset, SeekOrigin origin)
{
return -1;
}
public override void SetLength(long value)
{
}
public override void Write(byte[] buffer, int offset, int count)
{
}
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool WinHttpReadData(
SafeWinHttpHandle requestHandle,
IntPtr buffer,
uint bufferSize,
out long lpdwNumberOfBytesAvailable);
}
}

View File

@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using TraceLog;
using XDM.Core;
using static Interop.WinHttp;
namespace XDM.Core.Clients.Http
{
internal class WinHttpSession : IHttpSession
{
private SafeWinHttpHandle hConnect, hRequest;
private WINHTTP_STATUS_CALLBACK callback;
private Uri responseUri;
private HttpStatusCode statusCode;
private string? statusDescription;
private long contentLength;
private string contentRange;
private string? contentType;
private string? contentDispositionFileName;
private DateTime lastModified = DateTime.Now;
private long rangeStart = -1, rangeEnd = -1;
private Dictionary<string, List<string>>? headers = null;
private Dictionary<string, string>? cookies = null;
private Stream responseStream;
private IntPtr? postData;
private int postDataSize;
public Dictionary<string, List<string>>? Headers => this.headers;
public Dictionary<string, string>? Cookies => this.cookies;
public SafeWinHttpHandle ConnectHandle => hConnect;
public SafeWinHttpHandle RequestHandle => hRequest;
public IntPtr? PostData => postData;
public int PostDataSize => postDataSize;
public WinHttpSession(
Uri responseUri,
SafeWinHttpHandle hConnect,
SafeWinHttpHandle hRequest,
Dictionary<string, List<string>>? headers,
Dictionary<string, string>? cookies,
byte[]? postData = null)
{
this.responseUri = responseUri;
this.hConnect = hConnect;
this.hRequest = hRequest;
this.callback = new(WinHttpCallback);
IntPtr oldCallback = WinHttpSetStatusCallback(
hRequest,
this.callback,
WINHTTP_CALLBACK_FLAG_REDIRECT,
IntPtr.Zero);
if (oldCallback == new IntPtr(WINHTTP_INVALID_STATUS_CALLBACK))
{
int lastError = Marshal.GetLastWin32Error();
if (lastError != ERROR_INVALID_HANDLE) // Ignore error if handle was already closed.
{
throw new IOException(nameof(WinHttpSetStatusCallback));
}
}
this.headers = headers;
this.cookies = cookies;
if (postData != null && postData.Length > 0)
{
this.postData = Marshal.AllocHGlobal(postData.Length);
Marshal.Copy(postData, 0, this.postData.Value, postData.Length);
this.postDataSize = postData.Length;
}
this.responseStream = new WinHttpResponseStream(this.hRequest);
}
private void WinHttpCallback(
IntPtr handle,
IntPtr context,
uint internetStatus,
IntPtr statusInformation,
uint statusInformationLength)
{
if (internetStatus == WINHTTP_CALLBACK_STATUS_REDIRECT)
{
responseUri = new Uri(Marshal.PtrToStringUni(statusInformation)!);
Log.Debug("Redirected to: " + responseUri);
}
}
public string? ContentType => this.contentType;
public string? ContentDispositionFileName => this.contentDispositionFileName;
public long ContentLength => this.contentLength;
public DateTime LastModified => this.lastModified;
public HttpStatusCode StatusCode => this.statusCode;
public Uri ResponseUri => responseUri;
public long RangeEnd => rangeEnd;
public long RangeStart => rangeStart;
public void Abort()
{
Close();
}
public void AddRange(long range)
{
this.rangeStart = range;
}
public void AddRange(long start, long end)
{
this.rangeStart = start;
this.rangeEnd = end;
}
public void Close()
{
lock (this)
{
this.RequestHandle.Close();
this.ConnectHandle.Close();
if (this.postData.HasValue)
{
Marshal.FreeHGlobal(this.postData.Value);
}
}
}
public void Dispose()
{
Close();
}
public void EnsureSuccessStatusCode()
{
if (statusCode != HttpStatusCode.OK && statusCode != HttpStatusCode.PartialContent)
{
throw new HttpException(statusDescription, null, statusCode);
}
}
public Stream GetResponseStream()
{
return this.responseStream;
}
public string ReadAsString(CancelFlag cancellationToken)
{
#if NET35
var buf = new byte[8192];
#else
var buf = System.Buffers.ArrayPool<byte>.Shared.Rent(8192);
#endif
try
{
var sourceStream = responseStream;
var ms = new MemoryStream();
while (!cancellationToken.IsCancellationRequested)
{
var x = sourceStream.Read(buf, 0, buf.Length);
if (x == 0)
{
break;
}
ms.Write(buf, 0, x);
cancellationToken.ThrowIfCancellationRequested();
}
return Encoding.UTF8.GetString(ms.ToArray());
}
finally
{
#if !NET35
System.Buffers.ArrayPool<byte>.Shared.Return(buf);
#endif
}
}
internal void SetHeaders(string headers)
{
var lines = headers.Split('\r', '\n');
var status = lines[0];
this.statusCode = (HttpStatusCode)Int32.Parse(status.Split(' ')[1].Trim());
this.statusDescription = status.Split(' ')[2];
foreach (var line in lines)
{
var index = line.IndexOf(':');
if (index > 0)
{
var key = line.Substring(0, index).ToLowerInvariant();
var value = line.Substring(index + 1).Trim();
switch (key)
{
case "content-length":
this.contentLength = Int64.Parse(value);
break;
case "content-range":
this.contentRange = value;
break;
case "content-type":
this.contentType = value;
break;
case "content-disposition":
this.contentDispositionFileName = WebRequestExtensions.GetContentDispositionFileName(value);
break;
default:
break;
}
}
}
}
public long GetTotalLengthFromContentRange()
{
if (!string.IsNullOrEmpty(contentRange))
{
return WebRequestExtensions.ContentLengthFromContentRange(contentRange);
}
return -1;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,184 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Runtime.InteropServices;
using SafeWinHttpHandle = Interop.WinHttp.SafeWinHttpHandle;
namespace System.Net.Http
{
// This class is only used on OS versions where WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
// is not supported (i.e. before Win8.1/Win2K12R2) in the WinHttpOpen() function.
internal sealed class WinInetProxyHelper
{
private const int RecentAutoDetectionInterval = 120_000; // 2 minutes in milliseconds.
private readonly string? _autoConfigUrl, _proxy, _proxyBypass;
private readonly bool _autoDetect;
private readonly bool _useProxy;
private bool _autoDetectionFailed;
private int _lastTimeAutoDetectionFailed; // Environment.TickCount units (milliseconds).
public WinInetProxyHelper()
{
Interop.WinHttp.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = default;
try
{
if (Interop.WinHttp.WinHttpGetIEProxyConfigForCurrentUser(out proxyConfig))
{
_autoConfigUrl = Marshal.PtrToStringUni(proxyConfig.AutoConfigUrl)!;
_autoDetect = proxyConfig.AutoDetect;
_proxy = Marshal.PtrToStringUni(proxyConfig.Proxy)!;
_proxyBypass = Marshal.PtrToStringUni(proxyConfig.ProxyBypass)!;
_useProxy = true;
}
else
{
// We match behavior of WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY and ignore errors.
int lastError = Marshal.GetLastWin32Error();
}
}
finally
{
// FreeHGlobal already checks for null pointer before freeing the memory.
Marshal.FreeHGlobal(proxyConfig.AutoConfigUrl);
Marshal.FreeHGlobal(proxyConfig.Proxy);
Marshal.FreeHGlobal(proxyConfig.ProxyBypass);
}
}
public string? AutoConfigUrl => _autoConfigUrl;
public bool AutoDetect => _autoDetect;
public bool AutoSettingsUsed => AutoDetect || !string.IsNullOrEmpty(AutoConfigUrl);
public bool ManualSettingsUsed => !string.IsNullOrEmpty(Proxy);
public bool ManualSettingsOnly => !AutoSettingsUsed && ManualSettingsUsed;
public string? Proxy => _proxy;
public string? ProxyBypass => _proxyBypass;
public bool RecentAutoDetectionFailure =>
_autoDetectionFailed &&
Environment.TickCount - _lastTimeAutoDetectionFailed <= RecentAutoDetectionInterval;
public bool GetProxyForUrl(
SafeWinHttpHandle? sessionHandle,
Uri uri,
out Interop.WinHttp.WINHTTP_PROXY_INFO proxyInfo)
{
proxyInfo.AccessType = Interop.WinHttp.WINHTTP_ACCESS_TYPE_NO_PROXY;
proxyInfo.Proxy = IntPtr.Zero;
proxyInfo.ProxyBypass = IntPtr.Zero;
if (!_useProxy)
{
return false;
}
bool useProxy = false;
Interop.WinHttp.WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions;
autoProxyOptions.AutoConfigUrl = AutoConfigUrl;
autoProxyOptions.AutoDetectFlags = AutoDetect ?
(Interop.WinHttp.WINHTTP_AUTO_DETECT_TYPE_DHCP | Interop.WinHttp.WINHTTP_AUTO_DETECT_TYPE_DNS_A) : 0;
autoProxyOptions.AutoLoginIfChallenged = false;
autoProxyOptions.Flags =
(AutoDetect ? Interop.WinHttp.WINHTTP_AUTOPROXY_AUTO_DETECT : 0) |
(!string.IsNullOrEmpty(AutoConfigUrl) ? Interop.WinHttp.WINHTTP_AUTOPROXY_CONFIG_URL : 0);
autoProxyOptions.Reserved1 = IntPtr.Zero;
autoProxyOptions.Reserved2 = 0;
// AutoProxy Cache.
// https://docs.microsoft.com/en-us/windows/desktop/WinHttp/autoproxy-cache
// If the out-of-process service is active when WinHttpGetProxyForUrl is called, the cached autoproxy
// URL and script are available to the whole computer. However, if the out-of-process service is used,
// and the fAutoLogonIfChallenged flag in the pAutoProxyOptions structure is true, then the autoproxy
// URL and script are not cached. Therefore, calling WinHttpGetProxyForUrl with the fAutoLogonIfChallenged
// member set to TRUE results in additional overhead operations that may affect performance.
// The following steps can be used to improve performance:
// 1. Call WinHttpGetProxyForUrl with the fAutoLogonIfChallenged parameter set to false. The autoproxy
// URL and script are cached for future calls to WinHttpGetProxyForUrl.
// 2. If Step 1 fails, with ERROR_WINHTTP_LOGIN_FAILURE, then call WinHttpGetProxyForUrl with the
// fAutoLogonIfChallenged member set to TRUE.
//
// We match behavior of WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY and ignore errors.
#pragma warning disable CA1845 // file is shared with a build that lacks string.Concat for spans
// Underlying code does not understand WebSockets so we need to convert it to http or https.
string destination = uri.AbsoluteUri;
if (uri.Scheme == "wss")
{
destination = "https" + destination.Substring("wss".Length);
}
else if (uri.Scheme == "ws")
{
destination = "http" + destination.Substring("ws".Length);
}
#pragma warning restore CA1845
var repeat = false;
do
{
_autoDetectionFailed = false;
if (Interop.WinHttp.WinHttpGetProxyForUrl(
sessionHandle!,
destination,
ref autoProxyOptions,
out proxyInfo))
{
useProxy = true;
break;
}
else
{
var lastError = Marshal.GetLastWin32Error();
if (lastError == Interop.WinHttp.ERROR_WINHTTP_LOGIN_FAILURE)
{
if (repeat)
{
// We don't retry more than once.
break;
}
else
{
repeat = true;
autoProxyOptions.AutoLoginIfChallenged = true;
}
}
else
{
if (lastError == Interop.WinHttp.ERROR_WINHTTP_AUTODETECTION_FAILED)
{
_autoDetectionFailed = true;
_lastTimeAutoDetectionFailed = Environment.TickCount;
}
break;
}
}
} while (repeat);
// Fall back to manual settings if available.
if (!useProxy && !string.IsNullOrEmpty(Proxy))
{
proxyInfo.AccessType = Interop.WinHttp.WINHTTP_ACCESS_TYPE_NAMED_PROXY;
proxyInfo.Proxy = Marshal.StringToHGlobalUni(Proxy);
proxyInfo.ProxyBypass = string.IsNullOrEmpty(ProxyBypass) ?
IntPtr.Zero : Marshal.StringToHGlobalUni(ProxyBypass);
useProxy = true;
}
return useProxy;
}
}
}

View File

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Text;
using TraceLog;
using XDM.Core.Util;
namespace XDM.Core
{
public interface IClipboardMonitor
{
void Start();
void Stop();
}
public class ClipboardMonitor : IClipboardMonitor
{
private bool isClipboardMonitorActive = false;
private string lastClipboardText;
public ClipboardMonitor()
{
ApplicationContext.ApplicationEvent += ApplicationContext_ApplicationEvent;
}
private void ApplicationContext_ApplicationEvent(object sender, ApplicationEvent e)
{
if (e.EventType == "ConfigChanged")
{
if (Config.Instance.MonitorClipboard)
{
Start();
}
else
{
Stop();
}
}
}
public void Start()
{
Log.Debug("StartClipboardMonitor");
if (isClipboardMonitorActive) return;
var cm = ApplicationContext.Application.GetPlatformClipboardMonitor();
if (Config.Instance.MonitorClipboard)
{
cm.StartClipboardMonitoring();
isClipboardMonitorActive = true;
cm.ClipboardChanged += Cm_ClipboardChanged;
}
}
public void Stop()
{
if (!isClipboardMonitorActive) return;
var cm = ApplicationContext.Application.GetPlatformClipboardMonitor();
cm.StopClipboardMonitoring();
isClipboardMonitorActive = false;
cm.ClipboardChanged -= Cm_ClipboardChanged;
}
private void Cm_ClipboardChanged(object? sender, EventArgs e)
{
var text = ApplicationContext.Application.GetPlatformClipboardMonitor().GetClipboardText();
if (!string.IsNullOrEmpty(text) && Helpers.IsUriValid(text) && text != lastClipboardText)
{
lastClipboardText = text;
ApplicationContext.CoreService.AddDownload(new Message { Url = text });
}
}
}
}

View File

@ -0,0 +1,131 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
namespace XDM.Core.Collections
{
public class GenericOrderedDictionary<K, V> : IDictionary<K, V>
{
private readonly List<K> keys = new();
private readonly Dictionary<K, V> dict = new();
public V this[K key]
{
get => dict[key];
set
{
lock (this)
{
if (!dict.ContainsKey(key))
{
keys.Add(key);
}
dict[key] = value;
}
}
}
public ICollection<K> Keys => keys;
public ICollection<V> Values
{
get
{
lock (this)
{
return keys.Select(key => dict[key]).ToList();
}
}
}
public int Count => keys.Count;
public bool IsReadOnly => false;
public void Add(K key, V value)
{
lock (this)
{
if (!dict.ContainsKey(key))
{
keys.Add(key);
}
dict[key] = value;
}
}
public void Add(KeyValuePair<K, V> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
lock (this)
{
keys.Clear();
dict.Clear();
}
}
public bool Contains(KeyValuePair<K, V> item)
{
return ContainsKey(item.Key);
}
public bool ContainsKey(K key)
{
return dict.ContainsKey(key);
}
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
{
lock (this)
{
foreach (var key in this.keys)
{
var kv = new KeyValuePair<K, V>(key, this.dict[key]);
array[arrayIndex++] = kv;
}
}
}
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
foreach (var key in this.keys)
{
yield return new KeyValuePair<K, V>(key, this.dict[key]);
}
}
public bool Remove(K key)
{
lock (this)
{
keys.Remove(key);
return dict.Remove(key);
}
}
public bool Remove(KeyValuePair<K, V> item)
{
return Remove(item.Key);
}
public bool TryGetValue(K key, out V value)
{
return this.dict.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
foreach (var key in this.keys)
{
yield return new KeyValuePair<K, V>(key, this.dict[key]);
}
}
}
}

489
app/XDM/XDM.Core/Config.cs Normal file
View File

@ -0,0 +1,489 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TraceLog;
using XDM.Core.IO;
using XDM.Core.Util;
namespace XDM.Core
{
public class Config
{
private static Config instance;
private static object lockObj = new();
public static Config Instance
{
get
{
if (instance == null)
{
lock (lockObj)
{
if (instance == null)
{
LoadConfig();
}
}
}
return instance!;
}
private set
{
instance = value;
}
}
public static string DataDir { get; set; }
public bool IsBrowserMonitoringEnabled { get; set; } = true;
public static string DefaultFallbackUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36";
public string FallbackUserAgent { get; set; } = DefaultFallbackUserAgent;
public static string[] DefaultVideoExtensions => new string[]
{
"MP4", "M3U8", "F4M", "WEBM", "OGG", "MP3", "AAC", "FLV", "MKV", "DIVX",
"MOV", "MPG", "MPEG","OPUS"
};
public string[] VideoExtensions { get; set; }
public static string[] DefaultFileExtensions => new string[]
{
"3GP", "7Z", "AVI", "BZ2", "DEB", "DOC", "DOCX", "EXE", "ISO",
"MSI", "PDF", "PPT", "PPTX", "RAR", "RPM", "XLS", "XLSX", "SIT", "SITX", "TAR", "JAR", "ZIP", "XZ"
};
public string[] FileExtensions { get; set; }
public static string[] DefaultBlockedHosts => new string[]
{
"update.microsoft.com","windowsupdate.com","thwawte.com"
};
public string[] BlockedHosts { get; set; }
public string Language { get; set; } = "English";
public bool AllowSystemDarkTheme { get; set; } = false;
private Config()
{
VideoExtensions = DefaultVideoExtensions;
FileExtensions = DefaultFileExtensions;
BlockedHosts = DefaultBlockedHosts;
}
public List<string> RecentFolders { get; set; } = new List<string>();
public FolderSelectionMode FolderSelectionMode { get; set; }
public FileConflictResolution FileConflictResolution { get; set; }
public int MaxRetry { get; set; } = 10;
public int RetryDelay { get; set; } = 10;
public int MaxParallelDownloads { get; set; } = 1;
public bool ShowProgressWindow { get; set; } = true;
public bool ShowDownloadCompleteWindow { get; set; } = true;
public bool StartDownloadAutomatically { get; set; } = false;
public bool FetchServerTimeStamp { get; set; } = false;
public bool MonitorClipboard { get; set; } = false;
public int MinVideoSize { get; set; } = 1 * 1024;
public string TempDir { get; set; }
public int NetworkTimeout { get; set; } = 30;
public int MaxSegments { get; set; } = 8;
public int DefaltDownloadSpeed { get; set; } = 0;
public bool EnableSpeedLimit { get; set; } = false;
public bool ShutdownAfterAllFinished { get; set; } = false;
public bool KeepPCAwake { get; set; } = true;
public bool RunCommandAfterCompletion { get; set; } = false;
public string AfterCompletionCommand { get; set; }
public bool ScanWithAntiVirus { get; set; } = false;
public string AntiVirusExecutable { get; set; }
public string AntiVirusArgs { get; set; }
public ProxyInfo? Proxy { get; set; }
public bool DoubleClickOpenFile { get; set; } = false;
public bool RunOnLogon
{
get => PlatformHelper.IsAutoStartEnabled();
set => PlatformHelper.EnableAutoStart(value);
}
public string UserSelectedDownloadFolder { get; set; }
public string DefaultDownloadFolder { get; set; } =
PlatformHelper.GetOsDefaultDownloadFolder();
public static IEnumerable<Category> DefaultCategories = new[]
{
new Category
{
Name="CAT_DOCUMENTS",
DisplayName="Document",
FileExtensions=new HashSet<string>
{
".DOC", ".DOCX", ".PDF", ".MD", ".XLSX",".XLS", ".CBZ"
},
DefaultFolder=Path.Combine(PlatformHelper.GetOsDefaultDownloadFolder(),
"Documents"),
IsPredefined=true
},
new Category
{
Name="CAT_MUSIC",
DisplayName="Music",
FileExtensions=new HashSet<string>
{
".MP3", ".AAC",".MPA",".WMA",".MIDI"
},
DefaultFolder=Path.Combine(PlatformHelper.GetOsDefaultDownloadFolder(),"Music"),
IsPredefined=true
},
new Category
{
Name="CAT_VIDEOS",
DisplayName="Video",
FileExtensions=new HashSet<string>
{
".MP4", ".WEBM", ".OGG", ".FLV", ".MKV", ".DIVX",
".MOV", ".MPG", ".MPEG",".OPUS",".AVI",".WMV",".TS"
},
DefaultFolder=Path.Combine(PlatformHelper.GetOsDefaultDownloadFolder(),"Video"),
IsPredefined=true
},
new Category
{
Name="CAT_COMPRESSED",
DisplayName="Compressed",
FileExtensions=new HashSet<string>
{
".7Z", ".ZIP", ".RAR", ".BZ2", ".GZ",".XZ", ".TAR"
},
DefaultFolder=Path.Combine(PlatformHelper.GetOsDefaultDownloadFolder(),"Compressed"),
IsPredefined=true
},
new Category
{
Name="CAT_PROGRAMS",
DisplayName="Application",
FileExtensions=new HashSet<string>
{
".EXE", ".DEB", ".RPM", ".MSI"
},
DefaultFolder=Path.Combine(PlatformHelper.GetOsDefaultDownloadFolder(),"Programs"),
IsPredefined=true
},
//new Category
//{
// Name="Other",
// DisplayName="Other",
// FileExtensions=new HashSet<string>
// {
// }
//}
};
public IEnumerable<Category> Categories = DefaultCategories;
public IEnumerable<PasswordEntry> UserCredentials { get; set; } = new List<PasswordEntry>();
public static void LoadConfig(string? path = null)
{
DataDir = path ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".xdman");
instance = new Config
{
TempDir = Path.Combine(DataDir, "temp")
};
try
{
if (!Directory.Exists(DataDir))
{
Directory.CreateDirectory(DataDir);
}
var bytes = TransactedIO.ReadBytes("settings.dat", DataDir);
if (bytes != null)
{
using var ms = new MemoryStream(bytes);
using var reader = new BinaryReader(ms);
ConfigIO.DeserializeConfig(instance, reader);
}
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
//var json = TransactedIO.Read("settings.json", Config.DataDir);
//Config? instance = null;
//if (json != null)
//{
// instance = JsonConvert.DeserializeObject<Config>(
// json, new JsonSerializerSettings
// {
// MissingMemberHandling = MissingMemberHandling.Ignore,
// ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
// });
//}
//if (instance == null)
//{
// Instance = new Config
// {
// TempDir = Path.Combine(Config.DataDir, "temp")
// };
//}
//else
//{
// Instance = instance;
//}
//var path = Path.Combine(Config.DataDir, "settings.json");
//if (File.Exists(path))
//{
// try
// {
// var instance = JsonConvert.DeserializeObject<Config>(
// File.ReadAllText(path), new JsonSerializerSettings
// {
// MissingMemberHandling = MissingMemberHandling.Ignore,
// ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
// });
// if (instance != null)
// {
// Instance = instance;
// return;
// }
// }
// catch (Exception exx)
// {
// Log.Debug(exx, "Error loading config");
// }
//}
//Instance = new Config
//{
// TempDir = Path.Combine(Config.DataDir, "temp")
//};
}
//private static void PopulateConfig32(Config instance, BinaryReader r)
//{
// instance.AfterCompletionCommand = XDM.Messaging.StreamHelper.ReadString(r);
// instance.AntiVirusArgs = XDM.Messaging.StreamHelper.ReadString(r);
// instance.AntiVirusExecutable = XDM.Messaging.StreamHelper.ReadString(r);
// var count = r.ReadInt32();
// instance.BlockedHosts = new string[count];
// for (int i = 0; i < count; i++)
// {
// instance.BlockedHosts[i] = r.ReadString();
// }
// count = r.ReadInt32();
// var list = new List<Category>(count);
// for (int i = 0; i < count; i++)
// {
// var category = new Category
// {
// DefaultFolder = XDM.Messaging.StreamHelper.ReadString(r),
// DisplayName = XDM.Messaging.StreamHelper.ReadString(r),
// FileExtensions = new HashSet<string>(),
// };
// var c2 = r.ReadInt32();
// for (int j = 0; j < c2; j++)
// {
// category.FileExtensions.Add(r.ReadString());
// }
// category.IsPredefined = r.ReadBoolean();
// category.Name = r.ReadString();
// list.Add(category);
// }
// instance.Categories = list;
// instance.DefaultDownloadFolder = XDM.Messaging.StreamHelper.ReadString(r);
// instance.EnableSpeedLimit = r.ReadBoolean();
// instance.FetchServerTimeStamp = r.ReadBoolean();
// instance.FileConflictResolution = (FileConflictResolution)r.ReadInt32();
// count = r.ReadInt32();
// instance.FileExtensions = new string[count];
// for (int i = 0; i < count; i++)
// {
// instance.FileExtensions[i] = r.ReadString();
// }
// instance.FolderSelectionMode = (FolderSelectionMode)r.ReadInt32();
// instance.DefaltDownloadSpeed = r.ReadInt32();
// instance.IsBrowserMonitoringEnabled = r.ReadBoolean();
// instance.KeepPCAwake = r.ReadBoolean();
// instance.Language = r.ReadString();
// instance.MaxParallelDownloads = r.ReadInt32();
// instance.MaxRetry = r.ReadInt32();
// instance.MaxSegments = r.ReadInt32();
// instance.MinVideoSize = r.ReadInt32();
// instance.MonitorClipboard = r.ReadBoolean();
// instance.NetworkTimeout = r.ReadInt32();
// count = r.ReadInt32();
// instance.RecentFolders = new List<string>(count);
// for (int i = 0; i < count; i++)
// {
// instance.RecentFolders.Add(r.ReadString());
// }
// instance.RetryDelay = r.ReadInt32();
// instance.RunCommandAfterCompletion = r.ReadBoolean();
// instance.RunOnLogon = r.ReadBoolean();
// instance.ScanWithAntiVirus = r.ReadBoolean();
// instance.ShowDownloadCompleteWindow = r.ReadBoolean();
// instance.ShowProgressWindow = r.ReadBoolean();
// instance.ShutdownAfterAllFinished = r.ReadBoolean();
// instance.StartDownloadAutomatically = r.ReadBoolean();
// instance.TempDir = XDM.Messaging.StreamHelper.ReadString(r);
// count = r.ReadInt32();
// var list2 = new List<PasswordEntry>(count);
// for (int i = 0; i < count; i++)
// {
// var passwordEntry = new PasswordEntry
// {
// Host = XDM.Messaging.StreamHelper.ReadString(r),
// User = XDM.Messaging.StreamHelper.ReadString(r),
// Password = XDM.Messaging.StreamHelper.ReadString(r)
// };
// list2.Add(passwordEntry);
// }
// instance.UserCredentials = list2;
// count = r.ReadInt32();
// instance.VideoExtensions = new string[count];
// for (int i = 0; i < count; i++)
// {
// instance.VideoExtensions[i] = r.ReadString();
// }
// instance.Proxy = ProxyInfoSerializer.Deserialize(r);
// instance.AllowSystemDarkTheme = r.ReadBoolean();
//}
public static void SaveConfig()
{
ConfigIO.SerializeConfig();
}
//public static void SaveConfig3()
//{
// using var ms = new MemoryStream();
// using var writer = new BinaryWriter(ms);
// writer.Write(Instance.AfterCompletionCommand ?? string.Empty);
// writer.Write(Instance.AntiVirusArgs ?? string.Empty);
// writer.Write(Instance.AntiVirusExecutable ?? string.Empty);
// var count = Instance.BlockedHosts?.Length ?? 0;
// writer.Write(count);
// for (int i = 0; i < count; i++)
// {
// writer.Write(Instance.BlockedHosts![i]);
// }
// count = Instance.Categories.Count();
// writer.Write(count);
// foreach (var category in Instance.Categories)
// {
// writer.Write(category.DefaultFolder);
// writer.Write(category.DisplayName ?? string.Empty);
// count = category.FileExtensions.Count();
// writer.Write(count);
// foreach (var ext in category.FileExtensions)
// {
// writer.Write(ext);
// }
// writer.Write(category.IsPredefined);
// writer.Write(category.Name);
// }
// writer.Write(Instance.DefaultDownloadFolder ?? string.Empty);
// writer.Write(Instance.EnableSpeedLimit);
// writer.Write(Instance.FetchServerTimeStamp);
// writer.Write((int)Instance.FileConflictResolution);
// count = Instance.FileExtensions.Length;
// writer.Write(count);
// foreach (var ext in Instance.FileExtensions)
// {
// writer.Write(ext);
// }
// writer.Write((int)Instance.FolderSelectionMode);
// writer.Write(Instance.DefaltDownloadSpeed);
// writer.Write(Instance.IsBrowserMonitoringEnabled);
// writer.Write(Instance.KeepPCAwake);
// writer.Write(Instance.Language);
// writer.Write(Instance.MaxParallelDownloads);
// writer.Write(Instance.MaxRetry);
// writer.Write(Instance.MaxSegments);
// writer.Write(Instance.MinVideoSize);
// writer.Write(Instance.MonitorClipboard);
// writer.Write(Instance.NetworkTimeout);
// count = Instance.RecentFolders.Count;
// writer.Write(count);
// foreach (var recentFolder in Instance.RecentFolders)
// {
// writer.Write(recentFolder);
// }
// writer.Write(Instance.RetryDelay);
// writer.Write(Instance.RunCommandAfterCompletion);
// writer.Write(Instance.RunOnLogon);
// writer.Write(Instance.ScanWithAntiVirus);
// writer.Write(Instance.ShowDownloadCompleteWindow);
// writer.Write(Instance.ShowProgressWindow);
// writer.Write(Instance.ShutdownAfterAllFinished);
// writer.Write(Instance.StartDownloadAutomatically);
// writer.Write(Instance.TempDir);
// count = Instance.UserCredentials.Count();
// writer.Write(count);
// foreach (var pe in Instance.UserCredentials)
// {
// writer.Write(pe.Host ?? string.Empty);
// writer.Write(pe.User ?? string.Empty);
// writer.Write(pe.Password ?? string.Empty);
// }
// count = Instance.VideoExtensions.Length;
// writer.Write(count);
// foreach (var ext in Instance.VideoExtensions)
// {
// writer.Write(ext);
// }
// //ProxyInfoSerializer.Serialize(Instance.Proxy, writer);
// //writer.Write(Instance.AllowSystemDarkTheme);
// writer.Close();
// ms.Close();
// TransactedIO.WriteBytes(ms.ToArray(), "settings.db", Config.DataDir);
// //TransactedIO.Write(JsonConvert.SerializeObject(Config.Instance), "settings.json", Config.DataDir);
// //File.WriteAllText(Path.Combine(Config.DataDir, "settings.json"), JsonConvert.SerializeObject(Config.Instance));
//}
}
public enum FolderSelectionMode
{
Auto, Manual
}
public enum FileConflictResolution
{
AutoRename,
Overwrite
}
}

View File

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Text;
using TraceLog;
using XDM.Core;
using XDM.Core.Downloader;
namespace XDM.Core.DataAccess
{
public class AppDB
{
private static object lockObj = new();
private bool init = false;
private SQLiteConnection db;
private AppDB() { }
private DownloadList downloadsDB;
public DownloadList Downloads => downloadsDB;
private static AppDB instance;
public static AppDB Instance
{
get
{
lock (lockObj)
{
if (instance == null)
{
instance = new AppDB();
}
}
return instance;
}
}
public bool Init(string file)
{
lock (this)
{
try
{
string cs = $"URI=file:{file}";
if (!File.Exists(file))
{
SQLiteConnection.CreateFile(file);
}
db = new SQLiteConnection(cs);
db.Open();
SchemaInitializer.Init(db);
this.downloadsDB = new DownloadList(db);
init = true;
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
public bool Export(string file)
{
try
{
DataImportExport.CopyToFile(db, file);
return true;
}
catch (Exception e)
{
Log.Debug(e, e.Message);
return false;
throw;
}
}
public bool Import(string file)
{
try
{
DataImportExport.CopyFromFile(db, file);
return true;
}
catch (Exception e)
{
Log.Debug(e, e.Message);
return false;
throw;
}
}
}
}

View File

@ -0,0 +1,91 @@
using System;
using System.Data.SQLite;
using System.IO;
using TraceLog;
namespace XDM.Core.DataAccess
{
public static class DataImportExport
{
public static bool CopyToFile(SQLiteConnection sql, string file)
{
try
{
var cs = $"URI=file:{file}";
if (!File.Exists(file))
{
SQLiteConnection.CreateFile(file);
}
using var dest = new SQLiteConnection(cs);
dest.Open();
sql.BackupDatabase(dest, "main", "main", -1, null, 0);
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
public static bool CopyFromFile(SQLiteConnection sql, string file)
{
try
{
using var attachCmd = new SQLiteCommand($"ATTACH '{file}' as db", sql);
attachCmd.ExecuteNonQuery();
var tx = sql.BeginTransaction();
try
{
using var mergeCmd = new SQLiteCommand($"INSERT OR IGNORE INTO downloads SELECT * FROM db.downloads", sql);
mergeCmd.ExecuteNonQuery();
tx.Commit();
}
catch (Exception ex)
{
tx.Rollback();
Log.Debug("Error during merge insert, performing rollback!!");
Log.Debug(ex, ex.Message);
}
using var detachCmd = new SQLiteCommand($"DETACH db", sql);
detachCmd.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
//public bool CopyDB(SQLiteConnection sourceDB, SQLiteConnection targetDB, )
//{
// try
// {
// using var attachCmd = new SQLiteCommand($"ATTACH '{file}' as db", db);
// attachCmd.ExecuteNonQuery();
// var tx = db.BeginTransaction();
// try
// {
// using var mergeCmd = new SQLiteCommand($"INSERT OR IGNORE INTO downloads SELECT * FROM db.downloads", db);
// mergeCmd.ExecuteNonQuery();
// tx.Commit();
// }
// catch (Exception ex)
// {
// tx.Rollback();
// Log.Debug("Error during merge insert, performing rollback!!");
// Log.Debug(ex, ex.Message);
// }
// using var detachCmd = new SQLiteCommand($"DETACH db", db);
// detachCmd.ExecuteNonQuery();
// return true;
// }
// catch (Exception ex)
// {
// Log.Debug(ex, ex.Message);
// return false;
// }
//}
}
}

View File

@ -0,0 +1,477 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Text;
using TraceLog;
using XDM.Core;
using XDM.Core.DataAccess.Extensions;
using XDM.Core.Downloader;
namespace XDM.Core.DataAccess
{
public class DownloadList
{
private SQLiteConnection db;
public DownloadList(SQLiteConnection db)
{
this.db = db;
}
private SQLiteCommand cmdFetchAll, cmdFetchConditional, cmdFetchOne, cmdUpdateProgress, cmdUpdateTargetDir,
cmdInsertOne, cmdMarkFinished, cmdUpdateStatus, cmdUpdateNameAndSize, cmdUpdateNameAndFolder, cmdUpdateOne, cmdDelete;
public bool LoadDownloads(
out List<InProgressDownloadItem> inProgressDownloads,
out List<FinishedDownloadItem> finishedDownloads, QueryMode queryMode = QueryMode.All)
{
lock (db)
{
inProgressDownloads = new List<InProgressDownloadItem>();
finishedDownloads = new List<FinishedDownloadItem>();
try
{
SQLiteCommand sqlCommand;
if (queryMode == QueryMode.All)
{
if (cmdFetchAll == null)
{
cmdFetchAll = new SQLiteCommand("SELECT * FROM downloads", db);
}
sqlCommand = cmdFetchAll;
}
else
{
if (cmdFetchConditional == null)
{
cmdFetchConditional = new SQLiteCommand("SELECT * FROM downloads WHERE completed=@completed", db);
}
SetParam("@completed", queryMode == QueryMode.InProgress ? 0 : 1, cmdFetchConditional.Parameters);
sqlCommand = cmdFetchConditional;
}
using SQLiteDataReader r = sqlCommand.ExecuteReader();
while (r.Read())
{
var id = r.GetSafeString(0);
var inProgress = r.GetInt32(1) == 0;
DownloadItemBase entry = r.GetInt32(1) == 0 ? new InProgressDownloadItem() : new FinishedDownloadItem();
entry.Id = id;
entry.Name = r.GetSafeString(2);
entry.DateAdded = DateTime.FromBinary(r.GetInt64(3));
entry.Size = r.GetInt64(4);
entry.DownloadType = r.GetSafeString(7);
entry.FileNameFetchMode = (FileNameFetchMode)r.GetInt32(8);
entry.MaxSpeedLimitInKiB = r.GetInt32(9);
entry.TargetDir = r.GetSafeString(10);
entry.PrimaryUrl = r.GetSafeString(11);
entry.RefererUrl = r.GetSafeString(12);
if (r.GetInt32(13) == 1)
{
var user = r.GetSafeString(14);
var pass = r.GetSafeString(15);
if (user != null)
{
entry.Authentication = new AuthenticationInfo
{
UserName = user,
Password = pass
};
}
}
var proxy = new ProxyInfo { };
proxy.ProxyType = (ProxyType)r.GetInt32(16);
proxy.Host = r.GetSafeString(17);
proxy.Port = r.GetInt32(18);
proxy.UserName = r.GetSafeString(19);
proxy.Password = r.GetSafeString(20);
entry.Proxy = proxy;
if (inProgress)
{
var inp = (InProgressDownloadItem)entry;
inp.Status = DownloadStatus.Stopped;
inp.Progress = r.GetInt32(6);
inProgressDownloads.Add(inp);
}
else
{
finishedDownloads.Add((FinishedDownloadItem)entry);
}
}
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
return false;
}
}
public DownloadItemBase? GetDownloadById(string id)
{
lock (db)
{
try
{
if (cmdFetchOne == null)
{
cmdFetchOne = new SQLiteCommand("SELECT * FROM downloads WHERE id=@id", db);
}
SetParam("@id", id, cmdFetchOne.Parameters);
//cmdFetchOne.Parameters["@id"].Value = id;
using SQLiteDataReader r = cmdFetchOne.ExecuteReader();
if (r.Read())
{
var inProgress = r.GetInt32(1) == 0;
DownloadItemBase entry = r.GetInt32(1) == 0 ? new InProgressDownloadItem() : new FinishedDownloadItem();
entry.Id = id;
entry.Name = r.GetSafeString(2);
entry.DateAdded = DateTime.FromBinary(r.GetInt64(3));
entry.Size = r.GetInt64(4);
entry.DownloadType = r.GetSafeString(7);
entry.FileNameFetchMode = (FileNameFetchMode)r.GetInt32(8);
entry.MaxSpeedLimitInKiB = r.GetInt32(9);
entry.TargetDir = r.GetSafeString(10);
entry.PrimaryUrl = r.GetSafeString(11);
entry.RefererUrl = r.GetSafeString(12);
if (r.GetInt32(13) == 1)
{
var user = r.GetSafeString(14);
var pass = r.GetSafeString(15);
if (user != null)
{
entry.Authentication = new AuthenticationInfo
{
UserName = user,
Password = pass
};
}
}
var proxy = new ProxyInfo { };
proxy.ProxyType = (ProxyType)r.GetInt32(16);
proxy.Host = r.GetSafeString(17);
proxy.Port = r.GetInt32(18);
proxy.UserName = r.GetSafeString(19);
proxy.Password = r.GetSafeString(20);
entry.Proxy = proxy;
if (inProgress)
{
var inp = (InProgressDownloadItem)entry;
inp.Status = DownloadStatus.Stopped;
inp.Progress = r.GetInt32(6);
}
return entry;
}
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
}
return null;
}
}
public bool AddNewDownload(InProgressDownloadItem entry)
{
lock (db)
{
try
{
if (cmdInsertOne == null)
{
cmdInsertOne = new SQLiteCommand(@"INSERT INTO downloads(
id, completed, name, date_added, size, status,
progress, download_type, filenamefetchmode, maxspeedlimitinkib, targetdir, primary_url,
referer_url, auth, user, pass, proxy, proxy_host,
proxy_port, proxy_user, proxy_pass, proxy_type)
VALUES(
@id, @completed, @name, @date_added, @size, @status,
@progress, @download_type, @filenamefetchmode, @maxspeedlimitinkib, @targetdir, @primary_url,
@referer_url, @auth, @user, @pass, @proxy, @proxy_host,
@proxy_port, @proxy_user, @proxy_pass, @proxy_type)", db);
}
SetParam("@id", entry.Id, cmdInsertOne.Parameters);
SetParam("@completed", 0, cmdInsertOne.Parameters);
SetParam("@name", entry.Name, cmdInsertOne.Parameters);
SetParam("@date_added", entry.DateAdded.ToBinary(), cmdInsertOne.Parameters);
SetParam("@size", entry.Size, cmdInsertOne.Parameters);
SetParam("@status", (int)entry.Status, cmdInsertOne.Parameters);
SetParam("@progress", entry.Progress, cmdInsertOne.Parameters);
SetParam("@download_type", entry.DownloadType, cmdInsertOne.Parameters);
SetParam("@filenamefetchmode", (int)entry.FileNameFetchMode, cmdInsertOne.Parameters);
SetParam("@maxspeedlimitinkib", entry.MaxSpeedLimitInKiB, cmdInsertOne.Parameters);
SetParam("@targetdir", entry.TargetDir, cmdInsertOne.Parameters);
SetParam("@primary_url", entry.PrimaryUrl, cmdInsertOne.Parameters);
SetParam("@referer_url", entry.RefererUrl, cmdInsertOne.Parameters);
SetParam("@auth", entry.Authentication.HasValue ? 1 : 0, cmdInsertOne.Parameters);
SetParam("@user", entry.Authentication?.UserName ?? null, cmdInsertOne.Parameters);
SetParam("@pass", entry.Authentication?.Password ?? null, cmdInsertOne.Parameters);
SetParam("@proxy", (int)(entry.Proxy?.ProxyType ?? 0), cmdInsertOne.Parameters);
SetParam("@proxy_host", entry.Proxy?.Host ?? null, cmdInsertOne.Parameters);
SetParam("@proxy_port", (int)(entry.Proxy?.Port ?? 0), cmdInsertOne.Parameters);
SetParam("@proxy_user", entry.Proxy?.UserName ?? null, cmdInsertOne.Parameters);
SetParam("@proxy_pass", entry.Proxy?.Password ?? null, cmdInsertOne.Parameters);
SetParam("@proxy_type", 1, cmdInsertOne.Parameters);
cmdInsertOne.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
public bool UpdateDownloadEntry(FinishedDownloadItem entry)
{
lock (db)
{
try
{
if (cmdUpdateOne == null)
{
cmdUpdateOne = new SQLiteCommand(@"UPDATE downloads SET name=@name, date_added=@date_added, size=@size,
download_type=@download_type, targetdir=@targetdir, primary_url=@primary_url,
auth=@auth, user=@user, pass=@pass, proxy=@proxy, proxy_host=@proxy_host,
proxy_port=@proxy_port, proxy_user=@proxy_user, proxy_pass=@proxy_pass,
proxy_type=@proxy_type WHERE id=@id", db);
}
SetParam("@id", entry.Id, cmdUpdateOne.Parameters);
SetParam("@name", entry.Name, cmdUpdateOne.Parameters);
SetParam("@date_added", entry.DateAdded.ToBinary(), cmdUpdateOne.Parameters);
SetParam("@size", entry.Size, cmdUpdateOne.Parameters);
SetParam("@download_type", entry.DownloadType, cmdUpdateOne.Parameters);
SetParam("@primary_url", entry.PrimaryUrl, cmdUpdateOne.Parameters);
SetParam("@auth", entry.Authentication.HasValue ? 1 : 0, cmdUpdateOne.Parameters);
SetParam("@user", entry.Authentication?.UserName ?? null, cmdUpdateOne.Parameters);
SetParam("@pass", entry.Authentication?.Password ?? null, cmdUpdateOne.Parameters);
SetParam("@proxy", (int)(entry.Proxy?.ProxyType ?? 0), cmdUpdateOne.Parameters);
SetParam("@proxy_host", entry.Proxy?.Host ?? null, cmdUpdateOne.Parameters);
SetParam("@proxy_port", (int)(entry.Proxy?.Port ?? 0), cmdUpdateOne.Parameters);
SetParam("@proxy_user", entry.Proxy?.UserName ?? null, cmdUpdateOne.Parameters);
SetParam("@proxy_pass", entry.Proxy?.Password ?? null, cmdUpdateOne.Parameters);
SetParam("@proxy_type", 1, cmdUpdateOne.Parameters);
SetParam("@targetdir", entry.TargetDir, cmdUpdateOne.Parameters);
cmdUpdateOne.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
public bool UpdateDownloadProgress(string id, int progress)
{
lock (db)
{
try
{
if (cmdUpdateProgress == null)
{
cmdUpdateProgress = new SQLiteCommand("UPDATE downloads SET progress=@progress WHERE id=@id", db);
}
SetParam("@progress", progress, cmdUpdateProgress.Parameters);
SetParam("@id", id, cmdUpdateProgress.Parameters);
//cmdUpdateProgress.Parameters["@progress"].Value = progress;
//cmdUpdateProgress.Parameters["@id"].Value = id;
cmdUpdateProgress.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
public bool UpdateDownloadFolder(string id, string folder)
{
lock (db)
{
try
{
if (cmdUpdateTargetDir == null)
{
cmdUpdateTargetDir = new SQLiteCommand("UPDATE downloads SET targetdir=@targetdir WHERE id=@id", db);
}
SetParam("@targetdir", folder, cmdUpdateTargetDir.Parameters);
SetParam("@id", id, cmdUpdateTargetDir.Parameters);
//cmdUpdateProgress.Parameters["@targetdir"].Value = folder;
//cmdUpdateProgress.Parameters["@id"].Value = id;
cmdUpdateProgress.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
private void SetParam<T>(string name, T value, SQLiteParameterCollection param)
{
if (!param.Contains(name))
{
param.AddWithValue(name, value);
return;
}
param[name].Value = value;
}
public bool MarkAsFinished(string id, long finalFileSize, string file, string folder)
{
lock (db)
{
try
{
if (cmdMarkFinished == null)
{
cmdMarkFinished = new SQLiteCommand("UPDATE downloads SET targetdir=@targetdir, name=@name, " +
"size=@finalFileSize, completed=@completed WHERE id=@id", db);
}
SetParam("@targetdir", folder, cmdMarkFinished.Parameters);
SetParam("@name", file, cmdMarkFinished.Parameters);
SetParam("@finalFileSize", finalFileSize, cmdMarkFinished.Parameters);
SetParam("@id", id, cmdMarkFinished.Parameters);
SetParam("@completed", 1, cmdMarkFinished.Parameters);
cmdMarkFinished.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
public bool UpdateDownloadStatus(string id, DownloadStatus status)
{
lock (db)
{
try
{
if (cmdUpdateStatus == null)
{
cmdUpdateStatus = new SQLiteCommand("UPDATE downloads SET status=@status WHERE id=@id", db);
}
SetParam("@status", (int)status, cmdUpdateStatus.Parameters);
SetParam("@id", id, cmdUpdateStatus.Parameters);
cmdUpdateStatus.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
public bool UpdateNameAndSize(string id, long size, string name)
{
lock (db)
{
try
{
if (cmdUpdateNameAndSize == null)
{
cmdUpdateNameAndSize = new SQLiteCommand("UPDATE downloads SET name=@name, size=@size WHERE id=@id", db);
}
SetParam("@id", id, cmdUpdateNameAndSize.Parameters);
SetParam("@name", name, cmdUpdateNameAndSize.Parameters);
SetParam("@size", size, cmdUpdateNameAndSize.Parameters);
cmdUpdateNameAndSize.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
public bool UpdateNameAndFolder(string id, string name, string folder)
{
lock (db)
{
try
{
if (cmdUpdateNameAndFolder == null)
{
cmdUpdateNameAndFolder = new SQLiteCommand("UPDATE downloads SET name=@name, targetdir=@targetdir WHERE id=@id", db);
}
SetParam("@name", name, cmdUpdateNameAndFolder.Parameters);
SetParam("@targetdir", folder, cmdUpdateNameAndFolder.Parameters);
SetParam("@id", id, cmdUpdateNameAndFolder.Parameters);
cmdUpdateNameAndFolder.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
public bool RemoveAllFinished()
{
lock (db)
{
try
{
using var cmdClearAllFinished = new SQLiteCommand("DELETE FROM downloads WHERE completed=1", db);
cmdClearAllFinished.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
public bool RemoveDownloadById(string id)
{
lock (db)
{
try
{
if (cmdDelete == null)
{
cmdDelete = new SQLiteCommand("DELETE FROM downloads WHERE id=@id", db);
}
SetParam("@id", id, cmdDelete.Parameters);
cmdDelete.ExecuteNonQuery();
return true;
}
catch (Exception ex)
{
Log.Debug(ex, ex.Message);
return false;
}
}
}
}
public enum QueryMode
{
Finished,
InProgress,
All
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using System.Text;
namespace XDM.Core.DataAccess.Extensions
{
public static class DataReaderExtensions
{
public static string GetSafeString(this SQLiteDataReader r, int index)
{
if (!r.IsDBNull(index))
{
return r.GetString(index);
}
#pragma warning disable CS8603 // Possible null reference return.
return null;
#pragma warning restore CS8603 // Possible null reference return.
}
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using System.Text;
namespace XDM.Core.DataAccess
{
public static class SchemaInitializer
{
private static void CreateTablesIfNotExists(SQLiteConnection c)
{
var query = @"CREATE TABLE IF NOT EXISTS downloads(
id TEXT PRIMARY KEY,
completed INT,
name TEXT,
date_added INT,
size INT,
status INT,
progress INT,
download_type TEXT,
filenamefetchmode INT,
maxspeedlimitinkib INT,
targetdir TEXT,
primary_url TEXT,
referer_url TEXT,
auth INT,
user TEXT,
pass TEXT,
proxy INT,
proxy_host TEXT,
proxy_port INT,
proxy_user TEXT,
proxy_pass TEXT,
proxy_type INT
) WITHOUT ROWID";
using var cmd = new SQLiteCommand(c);
cmd.CommandText = query;
cmd.ExecuteNonQuery();
}
public static void Init(SQLiteConnection c)
{
CreateTablesIfNotExists(c);
}
}
}

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