feat: align motrix-style download UI/actions and stabilize aria2 ops

This commit is contained in:
tongki078
2026-02-24 12:00:30 +09:00
parent 845d5ca65c
commit 552f27c002
29 changed files with 2164 additions and 226 deletions

67
src-tauri/Cargo.lock generated
View File

@@ -86,6 +86,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-dialog",
"tauri-plugin-log",
]
@@ -668,6 +669,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [
"bitflags 2.11.0",
"block2",
"libc",
"objc2",
]
@@ -2955,6 +2958,30 @@ dependencies = [
"web-sys",
]
[[package]]
name = "rfd"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672"
dependencies = [
"block2",
"dispatch2",
"glib-sys",
"gobject-sys",
"gtk-sys",
"js-sys",
"log",
"objc2",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation",
"raw-window-handle",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows-sys 0.60.2",
]
[[package]]
name = "ring"
version = "0.17.14"
@@ -3760,6 +3787,46 @@ dependencies = [
"walkdir",
]
[[package]]
name = "tauri-plugin-dialog"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b"
dependencies = [
"log",
"raw-window-handle",
"rfd",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"tauri-plugin-fs",
"thiserror 2.0.18",
"url",
]
[[package]]
name = "tauri-plugin-fs"
version = "2.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804"
dependencies = [
"anyhow",
"dunce",
"glob",
"percent-encoding",
"schemars 0.8.22",
"serde",
"serde_json",
"serde_repr",
"tauri",
"tauri-plugin",
"tauri-utils",
"thiserror 2.0.18",
"toml 0.9.12+spec-1.1.0",
"url",
]
[[package]]
name = "tauri-plugin-log"
version = "2.8.0"

View File

@@ -23,5 +23,6 @@ serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.10.0", features = [] }
tauri-plugin-log = "2"
tauri-plugin-dialog = "2"
reqwest = { version = "0.12.24", default-features = false, features = ["json", "rustls-tls"] }
base64 = "0.22"

View File

@@ -6,6 +6,7 @@
"main"
],
"permissions": [
"core:default"
"core:default",
"dialog:allow-open"
]
}

View File

@@ -0,0 +1,91 @@
###############################
# Motrix macOS Aria2 config file
#
# @see https://aria2.github.io/manual/en/html/aria2c.html
#
###############################
################ RPC ################
# Enable JSON-RPC/XML-RPC server.
enable-rpc=true
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
rpc-allow-origin-all=true
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
rpc-listen-all=true
################ File system ################
# Save a control file(*.aria2) every SEC seconds.
auto-save-interval=10
# Enable disk cache.
disk-cache=64M
# Specify file allocation method.
file-allocation=none
# No file allocation is made for files whose size is smaller than SIZE
no-file-allocation-limit=64M
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
save-session-interval=10
################ Task ################
# Exclude seed only downloads when counting concurrent active downloads
bt-detach-seed-only=true
# Verify the peer using certificates specified in --ca-certificate option.
check-certificate=false
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
# without getting a single byte, then force the download to fail.
max-file-not-found=10
# Set number of tries.
max-tries=0
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
retry-wait=10
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
connect-timeout=10
# Set timeout in seconds.
timeout=10
# aria2 does not split less than 2*SIZE byte range.
min-split-size=1M
# Send Accept: deflate, gzip request header.
http-accept-gzip=true
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
remote-time=true
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
summary-interval=0
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
content-disposition-default-utf8=true
################ BT Task ################
# Enable Local Peer Discovery.
bt-enable-lpd=true
# Requires BitTorrent message payload encryption with arc4.
# bt-force-encryption=true
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
bt-hash-check-seed=true
# Specify the maximum number of peers per torrent.
bt-max-peers=128
# Try to download first and last pieces of each file first. This is useful for previewing files.
bt-prioritize-piece=head
# Removes the unselected files when download is completed in BitTorrent.
bt-remove-unselected-file=true
# Seed previously downloaded files without verifying piece hashes.
bt-seed-unverified=false
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
bt-tracker-connect-timeout=10
# Set timeout in seconds.
bt-tracker-timeout=10
# Set host and port as an entry point to IPv4 DHT network.
dht-entry-point=dht.transmissionbt.com:6881
# Set host and port as an entry point to IPv6 DHT network.
dht-entry-point6=dht.transmissionbt.com:6881
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
enable-dht=true
# Enable IPv6 DHT functionality.
enable-dht6=true
# Enable Peer Exchange extension.
enable-peer-exchange=true
# Specify the string used during the bitorrent extended handshake for the peer's client version.
peer-agent=Transmission/3.00
# Specify the prefix of peer ID.
peer-id-prefix=-TR3000-

Binary file not shown.

View File

@@ -0,0 +1,91 @@
###############################
# Motrix macOS Aria2 config file
#
# @see https://aria2.github.io/manual/en/html/aria2c.html
#
###############################
################ RPC ################
# Enable JSON-RPC/XML-RPC server.
enable-rpc=true
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
rpc-allow-origin-all=true
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
rpc-listen-all=true
################ File system ################
# Save a control file(*.aria2) every SEC seconds.
auto-save-interval=10
# Enable disk cache.
disk-cache=64M
# Specify file allocation method.
file-allocation=none
# No file allocation is made for files whose size is smaller than SIZE
no-file-allocation-limit=64M
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
save-session-interval=10
################ Task ################
# Exclude seed only downloads when counting concurrent active downloads
bt-detach-seed-only=true
# Verify the peer using certificates specified in --ca-certificate option.
check-certificate=false
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
# without getting a single byte, then force the download to fail.
max-file-not-found=10
# Set number of tries.
max-tries=0
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
retry-wait=10
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
connect-timeout=10
# Set timeout in seconds.
timeout=10
# aria2 does not split less than 2*SIZE byte range.
min-split-size=1M
# Send Accept: deflate, gzip request header.
http-accept-gzip=true
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
remote-time=true
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
summary-interval=0
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
content-disposition-default-utf8=true
################ BT Task ################
# Enable Local Peer Discovery.
bt-enable-lpd=true
# Requires BitTorrent message payload encryption with arc4.
# bt-force-encryption=true
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
bt-hash-check-seed=true
# Specify the maximum number of peers per torrent.
bt-max-peers=128
# Try to download first and last pieces of each file first. This is useful for previewing files.
bt-prioritize-piece=head
# Removes the unselected files when download is completed in BitTorrent.
bt-remove-unselected-file=true
# Seed previously downloaded files without verifying piece hashes.
bt-seed-unverified=false
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
bt-tracker-connect-timeout=10
# Set timeout in seconds.
bt-tracker-timeout=10
# Set host and port as an entry point to IPv4 DHT network.
dht-entry-point=dht.transmissionbt.com:6881
# Set host and port as an entry point to IPv6 DHT network.
dht-entry-point6=dht.transmissionbt.com:6881
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
enable-dht=true
# Enable IPv6 DHT functionality.
enable-dht6=true
# Enable Peer Exchange extension.
enable-peer-exchange=true
# Specify the string used during the bitorrent extended handshake for the peer's client version.
peer-agent=Transmission/3.00
# Specify the prefix of peer ID.
peer-id-prefix=-TR3000-

Binary file not shown.

View File

@@ -0,0 +1,91 @@
###############################
# Motrix Linux Aria2 config file
#
# @see https://aria2.github.io/manual/en/html/aria2c.html
#
###############################
################ RPC ################
# Enable JSON-RPC/XML-RPC server.
enable-rpc=true
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
rpc-allow-origin-all=true
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
rpc-listen-all=true
################ File system ################
# Save a control file(*.aria2) every SEC seconds.
auto-save-interval=10
# Enable disk cache.
disk-cache=64M
# Specify file allocation method.
file-allocation=trunc
# No file allocation is made for files whose size is smaller than SIZE
no-file-allocation-limit=64M
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
save-session-interval=10
################ Task ################
# Exclude seed only downloads when counting concurrent active downloads
bt-detach-seed-only=true
# Verify the peer using certificates specified in --ca-certificate option.
check-certificate=false
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
# without getting a single byte, then force the download to fail.
max-file-not-found=10
# Set number of tries.
max-tries=0
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
retry-wait=10
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
connect-timeout=10
# Set timeout in seconds.
timeout=10
# aria2 does not split less than 2*SIZE byte range.
min-split-size=1M
# Send Accept: deflate, gzip request header.
http-accept-gzip=true
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
remote-time=true
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
summary-interval=0
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
content-disposition-default-utf8=true
################ BT Task ################
# Enable Local Peer Discovery.
bt-enable-lpd=true
# Requires BitTorrent message payload encryption with arc4.
# bt-force-encryption=true
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
bt-hash-check-seed=true
# Specify the maximum number of peers per torrent.
bt-max-peers=128
# Try to download first and last pieces of each file first. This is useful for previewing files.
bt-prioritize-piece=head
# Removes the unselected files when download is completed in BitTorrent.
bt-remove-unselected-file=true
# Seed previously downloaded files without verifying piece hashes.
bt-seed-unverified=false
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
bt-tracker-connect-timeout=10
# Set timeout in seconds.
bt-tracker-timeout=10
# Set host and port as an entry point to IPv4 DHT network.
dht-entry-point=dht.transmissionbt.com:6881
# Set host and port as an entry point to IPv6 DHT network.
dht-entry-point6=dht.transmissionbt.com:6881
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
enable-dht=true
# Enable IPv6 DHT functionality.
enable-dht6=true
# Enable Peer Exchange extension.
enable-peer-exchange=true
# Specify the string used during the bitorrent extended handshake for the peer's client version.
peer-agent=Transmission/3.00
# Specify the prefix of peer ID.
peer-id-prefix=-TR3000-

Binary file not shown.

View File

@@ -0,0 +1,91 @@
###############################
# Motrix Linux Aria2 config file
#
# @see https://aria2.github.io/manual/en/html/aria2c.html
#
###############################
################ RPC ################
# Enable JSON-RPC/XML-RPC server.
enable-rpc=true
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
rpc-allow-origin-all=true
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
rpc-listen-all=true
################ File system ################
# Save a control file(*.aria2) every SEC seconds.
auto-save-interval=10
# Enable disk cache.
disk-cache=64M
# Specify file allocation method.
file-allocation=trunc
# No file allocation is made for files whose size is smaller than SIZE
no-file-allocation-limit=64M
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
save-session-interval=10
################ Task ################
# Exclude seed only downloads when counting concurrent active downloads
bt-detach-seed-only=true
# Verify the peer using certificates specified in --ca-certificate option.
check-certificate=false
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
# without getting a single byte, then force the download to fail.
max-file-not-found=10
# Set number of tries.
max-tries=0
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
retry-wait=10
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
connect-timeout=10
# Set timeout in seconds.
timeout=10
# aria2 does not split less than 2*SIZE byte range.
min-split-size=1M
# Send Accept: deflate, gzip request header.
http-accept-gzip=true
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
remote-time=true
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
summary-interval=0
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
content-disposition-default-utf8=true
################ BT Task ################
# Enable Local Peer Discovery.
bt-enable-lpd=true
# Requires BitTorrent message payload encryption with arc4.
# bt-force-encryption=true
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
bt-hash-check-seed=true
# Specify the maximum number of peers per torrent.
bt-max-peers=128
# Try to download first and last pieces of each file first. This is useful for previewing files.
bt-prioritize-piece=head
# Removes the unselected files when download is completed in BitTorrent.
bt-remove-unselected-file=true
# Seed previously downloaded files without verifying piece hashes.
bt-seed-unverified=false
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
bt-tracker-connect-timeout=10
# Set timeout in seconds.
bt-tracker-timeout=10
# Set host and port as an entry point to IPv4 DHT network.
dht-entry-point=dht.transmissionbt.com:6881
# Set host and port as an entry point to IPv6 DHT network.
dht-entry-point6=dht.transmissionbt.com:6881
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
enable-dht=true
# Enable IPv6 DHT functionality.
enable-dht6=true
# Enable Peer Exchange extension.
enable-peer-exchange=true
# Specify the string used during the bitorrent extended handshake for the peer's client version.
peer-agent=Transmission/3.00
# Specify the prefix of peer ID.
peer-id-prefix=-TR3000-

Binary file not shown.

View File

@@ -0,0 +1,91 @@
###############################
# Motrix Linux Aria2 config file
#
# @see https://aria2.github.io/manual/en/html/aria2c.html
#
###############################
################ RPC ################
# Enable JSON-RPC/XML-RPC server.
enable-rpc=true
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
rpc-allow-origin-all=true
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
rpc-listen-all=true
################ File system ################
# Save a control file(*.aria2) every SEC seconds.
auto-save-interval=10
# Enable disk cache.
disk-cache=64M
# Specify file allocation method.
file-allocation=trunc
# No file allocation is made for files whose size is smaller than SIZE
no-file-allocation-limit=64M
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
save-session-interval=10
################ Task ################
# Exclude seed only downloads when counting concurrent active downloads
bt-detach-seed-only=true
# Verify the peer using certificates specified in --ca-certificate option.
check-certificate=false
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
# without getting a single byte, then force the download to fail.
max-file-not-found=10
# Set number of tries.
max-tries=0
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
retry-wait=10
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
connect-timeout=10
# Set timeout in seconds.
timeout=10
# aria2 does not split less than 2*SIZE byte range.
min-split-size=1M
# Send Accept: deflate, gzip request header.
http-accept-gzip=true
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
remote-time=true
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
summary-interval=0
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
content-disposition-default-utf8=true
################ BT Task ################
# Enable Local Peer Discovery.
bt-enable-lpd=true
# Requires BitTorrent message payload encryption with arc4.
# bt-force-encryption=true
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
bt-hash-check-seed=true
# Specify the maximum number of peers per torrent.
bt-max-peers=128
# Try to download first and last pieces of each file first. This is useful for previewing files.
bt-prioritize-piece=head
# Removes the unselected files when download is completed in BitTorrent.
bt-remove-unselected-file=true
# Seed previously downloaded files without verifying piece hashes.
bt-seed-unverified=false
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
bt-tracker-connect-timeout=10
# Set timeout in seconds.
bt-tracker-timeout=10
# Set host and port as an entry point to IPv4 DHT network.
dht-entry-point=dht.transmissionbt.com:6881
# Set host and port as an entry point to IPv6 DHT network.
dht-entry-point6=dht.transmissionbt.com:6881
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
enable-dht=true
# Enable IPv6 DHT functionality.
enable-dht6=true
# Enable Peer Exchange extension.
enable-peer-exchange=true
# Specify the string used during the bitorrent extended handshake for the peer's client version.
peer-agent=Transmission/3.00
# Specify the prefix of peer ID.
peer-id-prefix=-TR3000-

Binary file not shown.

View File

@@ -0,0 +1,91 @@
###############################
# Motrix Windows Aria2 config file
#
# @see https://aria2.github.io/manual/en/html/aria2c.html
#
###############################
################ RPC ################
# Enable JSON-RPC/XML-RPC server.
enable-rpc=true
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
rpc-allow-origin-all=true
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
rpc-listen-all=true
################ File system ################
# Save a control file(*.aria2) every SEC seconds.
auto-save-interval=10
# Enable disk cache.
disk-cache=64M
# Specify file allocation method.
file-allocation=none
# No file allocation is made for files whose size is smaller than SIZE
no-file-allocation-limit=64M
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
save-session-interval=10
################ Task ################
# Exclude seed only downloads when counting concurrent active downloads
bt-detach-seed-only=true
# Verify the peer using certificates specified in --ca-certificate option.
check-certificate=false
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
# without getting a single byte, then force the download to fail.
max-file-not-found=10
# Set number of tries.
max-tries=0
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
retry-wait=10
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
connect-timeout=10
# Set timeout in seconds.
timeout=10
# aria2 does not split less than 2*SIZE byte range.
min-split-size=1M
# Send Accept: deflate, gzip request header.
http-accept-gzip=true
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
remote-time=true
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
summary-interval=0
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
content-disposition-default-utf8=true
################ BT Task ################
# Enable Local Peer Discovery.
bt-enable-lpd=true
# Requires BitTorrent message payload encryption with arc4.
# bt-force-encryption=true
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
bt-hash-check-seed=true
# Specify the maximum number of peers per torrent.
bt-max-peers=128
# Try to download first and last pieces of each file first. This is useful for previewing files.
bt-prioritize-piece=head
# Removes the unselected files when download is completed in BitTorrent.
bt-remove-unselected-file=true
# Seed previously downloaded files without verifying piece hashes.
bt-seed-unverified=false
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
bt-tracker-connect-timeout=10
# Set timeout in seconds.
bt-tracker-timeout=10
# Set host and port as an entry point to IPv4 DHT network.
dht-entry-point=dht.transmissionbt.com:6881
# Set host and port as an entry point to IPv6 DHT network.
dht-entry-point6=dht.transmissionbt.com:6881
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
enable-dht=true
# Enable IPv6 DHT functionality.
enable-dht6=true
# Enable Peer Exchange extension.
enable-peer-exchange=true
# Specify the string used during the bitorrent extended handshake for the peer's client version.
peer-agent=Transmission/3.00
# Specify the prefix of peer ID.
peer-id-prefix=-TR3000-

Binary file not shown.

View File

@@ -0,0 +1,91 @@
###############################
# Motrix Windows Aria2 config file
#
# @see https://aria2.github.io/manual/en/html/aria2c.html
#
###############################
################ RPC ################
# Enable JSON-RPC/XML-RPC server.
enable-rpc=true
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
rpc-allow-origin-all=true
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
rpc-listen-all=true
################ File system ################
# Save a control file(*.aria2) every SEC seconds.
auto-save-interval=10
# Enable disk cache.
disk-cache=64M
# Specify file allocation method.
file-allocation=falloc
# No file allocation is made for files whose size is smaller than SIZE
no-file-allocation-limit=64M
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
save-session-interval=10
################ Task ################
# Exclude seed only downloads when counting concurrent active downloads
bt-detach-seed-only=true
# Verify the peer using certificates specified in --ca-certificate option.
check-certificate=false
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
# without getting a single byte, then force the download to fail.
max-file-not-found=10
# Set number of tries.
max-tries=0
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
retry-wait=10
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
connect-timeout=10
# Set timeout in seconds.
timeout=10
# aria2 does not split less than 2*SIZE byte range.
min-split-size=1M
# Send Accept: deflate, gzip request header.
http-accept-gzip=true
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
remote-time=true
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
summary-interval=0
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
content-disposition-default-utf8=true
################ BT Task ################
# Enable Local Peer Discovery.
bt-enable-lpd=true
# Requires BitTorrent message payload encryption with arc4.
# bt-force-encryption=true
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
bt-hash-check-seed=true
# Specify the maximum number of peers per torrent.
bt-max-peers=128
# Try to download first and last pieces of each file first. This is useful for previewing files.
bt-prioritize-piece=head
# Removes the unselected files when download is completed in BitTorrent.
bt-remove-unselected-file=true
# Seed previously downloaded files without verifying piece hashes.
bt-seed-unverified=false
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
bt-tracker-connect-timeout=10
# Set timeout in seconds.
bt-tracker-timeout=10
# Set host and port as an entry point to IPv4 DHT network.
dht-entry-point=dht.transmissionbt.com:6881
# Set host and port as an entry point to IPv6 DHT network.
dht-entry-point6=dht.transmissionbt.com:6881
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
enable-dht=true
# Enable IPv6 DHT functionality.
enable-dht6=true
# Enable Peer Exchange extension.
enable-peer-exchange=true
# Specify the string used during the bitorrent extended handshake for the peer's client version.
peer-agent=Transmission/3.00
# Specify the prefix of peer ID.
peer-id-prefix=-TR3000-

Binary file not shown.

View File

@@ -2,11 +2,13 @@ use base64::{engine::general_purpose::STANDARD, Engine as _};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::env;
use std::net::{SocketAddr, TcpStream};
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};
use tauri::State;
use tauri::{Manager, State};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -46,6 +48,13 @@ pub struct Aria2AddTorrentRequest {
pub split: Option<u16>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Aria2TaskCommandRequest {
pub rpc: Aria2RpcConfig,
pub gid: String,
}
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct EngineStatusResponse {
@@ -66,6 +75,7 @@ pub struct Aria2TaskSummary {
pub download_speed: String,
pub dir: String,
pub file_name: String,
pub uri: String,
}
#[derive(Debug, Serialize)]
@@ -84,9 +94,20 @@ pub struct TorrentFilePayload {
pub size: u64,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Aria2BinaryProbeResponse {
pub found: bool,
pub binary_path: Option<String>,
pub source: Option<String>,
pub candidates: Vec<String>,
}
#[derive(Debug)]
struct EngineRuntime {
child: Option<Child>,
external_reuse: bool,
rpc_port: Option<u16>,
binary_path: Option<String>,
args: Vec<String>,
started_at: Option<u64>,
@@ -96,6 +117,8 @@ impl Default for EngineRuntime {
fn default() -> Self {
Self {
child: None,
external_reuse: false,
rpc_port: None,
binary_path: None,
args: vec![],
started_at: None,
@@ -111,7 +134,7 @@ pub struct EngineState {
impl EngineState {
fn status(runtime: &EngineRuntime) -> EngineStatusResponse {
EngineStatusResponse {
running: runtime.child.is_some(),
running: runtime.child.is_some() || runtime.external_reuse,
pid: runtime.child.as_ref().map(std::process::Child::id),
binary_path: runtime.binary_path.clone(),
args: runtime.args.clone(),
@@ -153,6 +176,133 @@ fn default_aria2_binary() -> String {
}
}
fn normalize_binary_hint(binary_hint: Option<&str>) -> String {
let value = binary_hint.unwrap_or("").trim();
if value.is_empty() {
default_aria2_binary()
} else {
value.to_string()
}
}
fn candidate_push(path: &Path, out: &mut Vec<String>) {
if let Some(s) = path.to_str() {
let v = s.trim();
if !v.is_empty() {
out.push(v.to_string());
}
}
}
fn platform_aliases() -> Vec<&'static str> {
match env::consts::OS {
"macos" => vec!["macos", "darwin"],
"windows" => vec!["windows", "win32"],
"linux" => vec!["linux"],
_ => vec![env::consts::OS],
}
}
fn arch_aliases() -> Vec<&'static str> {
match env::consts::ARCH {
"aarch64" => vec!["aarch64", "arm64"],
"x86_64" => vec!["x86_64", "x64"],
"x86" => vec!["x86", "ia32"],
"arm" => vec!["arm", "armv7l"],
other => vec![other],
}
}
fn collect_binary_candidates(app: &tauri::AppHandle, binary_hint: Option<&str>) -> Vec<String> {
let binary_name = normalize_binary_hint(binary_hint);
let mut candidates: Vec<String> = vec![];
if let Some(raw) = binary_hint.map(str::trim).filter(|v| !v.is_empty()) {
candidates.push(raw.to_string());
}
if let Ok(env_path) = env::var("ARIA2C_BIN") {
let trimmed = env_path.trim();
if !trimmed.is_empty() {
candidates.push(trimmed.to_string());
}
}
if let Ok(env_path) = env::var("ARIA2C_PATH") {
let trimmed = env_path.trim();
if !trimmed.is_empty() {
candidates.push(trimmed.to_string());
}
}
if let Some(path_var) = env::var_os("PATH") {
for dir in env::split_paths(&path_var) {
candidate_push(&dir.join(&binary_name), &mut candidates);
#[cfg(target_os = "windows")]
{
let lower = binary_name.to_ascii_lowercase();
if !lower.ends_with(".exe") {
candidate_push(&dir.join(format!("{binary_name}.exe")), &mut candidates);
}
}
}
}
let source_engine_base = Path::new(env!("CARGO_MANIFEST_DIR")).join("resources").join("engine");
let mut engine_bases = vec![source_engine_base];
if let Ok(resource_dir) = app.path().resource_dir() {
engine_bases.push(resource_dir.join("engine"));
}
let platforms = platform_aliases();
let arches = arch_aliases();
for base in engine_bases {
for platform in &platforms {
for arch in &arches {
candidate_push(&base.join(platform).join(arch).join(&binary_name), &mut candidates);
}
candidate_push(&base.join(platform).join(&binary_name), &mut candidates);
}
candidate_push(&base.join(&binary_name), &mut candidates);
}
let mut deduped = Vec::with_capacity(candidates.len());
for path in candidates {
if !deduped.contains(&path) {
deduped.push(path);
}
}
deduped
}
fn resolve_binary_from_candidates(candidates: &[String]) -> Option<(String, String)> {
for (idx, candidate) in candidates.iter().enumerate() {
let path = Path::new(candidate);
if path.is_file() {
let source = if idx == 0 { "user_or_default" } else { "auto_detected" };
return Some((candidate.clone(), source.to_string()));
}
}
None
}
fn classify_engine_spawn_error(binary: &str, err: &std::io::Error) -> String {
use std::io::ErrorKind;
match err.kind() {
ErrorKind::NotFound => format!(
"aria2 binary not found: '{binary}'. Binary Path를 지정하거나 PATH/resources/engine 경로를 확인하세요."
),
ErrorKind::PermissionDenied => {
format!("aria2 binary is not executable: '{binary}'. 실행 권한(chmod +x)을 확인하세요.")
}
_ => format!("failed to start aria2 engine with '{binary}': {err}"),
}
}
fn is_local_port_open(port: u16) -> bool {
let addr = SocketAddr::from(([127, 0, 0, 1], port));
TcpStream::connect_timeout(&addr, std::time::Duration::from_millis(220)).is_ok()
}
fn rpc_endpoint(config: &Aria2RpcConfig) -> String {
format!(
"http://127.0.0.1:{}/jsonrpc",
@@ -249,6 +399,17 @@ fn map_task(task: &Value) -> Aria2TaskSummary {
download_speed: value_to_string(task.get("downloadSpeed")),
dir: value_to_string(task.get("dir")),
file_name: pick_file_name(file_path),
uri: task
.get("files")
.and_then(Value::as_array)
.and_then(|files| files.first())
.and_then(|file| file.get("uris"))
.and_then(Value::as_array)
.and_then(|uris| uris.first())
.and_then(|uri| uri.get("uri"))
.and_then(Value::as_str)
.unwrap_or_default()
.to_string(),
}
}
@@ -277,16 +438,33 @@ fn build_rpc_options(out: Option<&String>, dir: Option<&String>, split: Option<u
Value::Object(options)
}
fn is_gid_not_found_error(message: &str) -> bool {
let lower = message.to_ascii_lowercase();
lower.contains("not found for gid#")
}
#[tauri::command]
pub fn engine_start(
app: tauri::AppHandle,
state: State<'_, EngineState>,
request: EngineStartRequest,
) -> Result<EngineStatusResponse, String> {
let rpc_port = request.rpc_listen_port.unwrap_or(6800);
let mut runtime = state
.runtime
.lock()
.map_err(|err| format!("failed to lock engine state: {err}"))?;
if runtime.external_reuse {
if let Some(port) = runtime.rpc_port {
if is_local_port_open(port) {
return Ok(EngineState::status(&runtime));
}
}
runtime.external_reuse = false;
runtime.rpc_port = None;
}
if let Some(child) = runtime.child.as_mut() {
match child.try_wait() {
Ok(Some(_)) => {
@@ -302,7 +480,31 @@ pub fn engine_start(
}
let args = build_engine_args(&request);
let binary = request.binary_path.unwrap_or_else(default_aria2_binary);
// Reuse existing engine when the target RPC port is already occupied.
// This mirrors Motrix-style behavior where an already-running aria2 instance is reused.
if is_local_port_open(rpc_port) {
runtime.child = None;
runtime.external_reuse = true;
runtime.rpc_port = Some(rpc_port);
runtime.binary_path = Some(format!("external://127.0.0.1:{rpc_port}"));
runtime.args = args;
runtime.started_at = Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|err| format!("failed to get system time: {err}"))?
.as_secs(),
);
return Ok(EngineState::status(&runtime));
}
let candidates = collect_binary_candidates(&app, request.binary_path.as_deref());
let (binary, _) = resolve_binary_from_candidates(&candidates).ok_or_else(|| {
let sample = candidates.into_iter().take(6).collect::<Vec<String>>().join(", ");
format!(
"aria2 binary를 찾지 못했습니다. Binary Path를 직접 지정하세요. (검색 후보: {sample})"
)
})?;
let child = Command::new(&binary)
.args(&args)
@@ -310,9 +512,25 @@ pub fn engine_start(
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(|err| format!("failed to start aria2 engine with '{binary}': {err}"))?;
.map_err(|err| classify_engine_spawn_error(&binary, &err))?;
let mut child = child;
std::thread::sleep(std::time::Duration::from_millis(220));
match child.try_wait() {
Ok(Some(status)) => {
return Err(format!(
"aria2 engine exited immediately (status={status}). 포트 충돌 또는 잘못된 옵션일 수 있습니다."
));
}
Ok(None) => {}
Err(err) => {
return Err(format!("failed to inspect aria2 engine process: {err}"));
}
}
runtime.child = Some(child);
runtime.external_reuse = false;
runtime.rpc_port = Some(rpc_port);
runtime.binary_path = Some(binary);
runtime.args = args;
runtime.started_at = Some(
@@ -325,6 +543,21 @@ pub fn engine_start(
Ok(EngineState::status(&runtime))
}
#[tauri::command]
pub fn detect_aria2_binary(
app: tauri::AppHandle,
binary_path: Option<String>,
) -> Aria2BinaryProbeResponse {
let candidates = collect_binary_candidates(&app, binary_path.as_deref());
let resolved = resolve_binary_from_candidates(&candidates);
Aria2BinaryProbeResponse {
found: resolved.is_some(),
binary_path: resolved.as_ref().map(|v| v.0.clone()),
source: resolved.map(|v| v.1),
candidates,
}
}
#[tauri::command]
pub fn engine_stop(state: State<'_, EngineState>) -> Result<EngineStatusResponse, String> {
let mut runtime = state
@@ -339,6 +572,8 @@ pub fn engine_stop(state: State<'_, EngineState>) -> Result<EngineStatusResponse
let _ = child.wait();
}
runtime.external_reuse = false;
runtime.rpc_port = None;
runtime.started_at = None;
Ok(EngineState::status(&runtime))
}
@@ -363,6 +598,15 @@ pub fn engine_status(state: State<'_, EngineState>) -> Result<EngineStatusRespon
}
}
if runtime.external_reuse {
let alive = runtime.rpc_port.map(is_local_port_open).unwrap_or(false);
if !alive {
runtime.external_reuse = false;
runtime.rpc_port = None;
runtime.started_at = None;
}
}
Ok(EngineState::status(&runtime))
}
@@ -426,6 +670,105 @@ pub async fn aria2_list_tasks(config: Aria2RpcConfig) -> Result<Aria2TaskSnapsho
})
}
#[tauri::command]
pub async fn aria2_pause_task(request: Aria2TaskCommandRequest) -> Result<String, String> {
let gid = request.gid.trim();
if gid.is_empty() {
return Err("gid is required".to_string());
}
let client = Client::new();
let result = call_aria2_rpc(&client, &request.rpc, "aria2.pause", vec![json!(gid)]).await?;
result
.as_str()
.map(|value| value.to_string())
.ok_or_else(|| format!("aria2.pause returned unexpected result: {result}"))
}
#[tauri::command]
pub async fn aria2_resume_task(request: Aria2TaskCommandRequest) -> Result<String, String> {
let gid = request.gid.trim();
if gid.is_empty() {
return Err("gid is required".to_string());
}
let client = Client::new();
let result = call_aria2_rpc(&client, &request.rpc, "aria2.unpause", vec![json!(gid)]).await?;
result
.as_str()
.map(|value| value.to_string())
.ok_or_else(|| format!("aria2.unpause returned unexpected result: {result}"))
}
#[tauri::command]
pub async fn aria2_remove_task(request: Aria2TaskCommandRequest) -> Result<String, String> {
let gid = request.gid.trim();
if gid.is_empty() {
return Err("gid is required".to_string());
}
let client = Client::new();
match call_aria2_rpc(&client, &request.rpc, "aria2.remove", vec![json!(gid)]).await {
Ok(result) => result
.as_str()
.map(|value| value.to_string())
.ok_or_else(|| format!("aria2.remove returned unexpected result: {result}")),
Err(err) if is_gid_not_found_error(&err) => {
match call_aria2_rpc(
&client,
&request.rpc,
"aria2.removeDownloadResult",
vec![json!(gid)],
)
.await
{
Ok(result) => result
.as_str()
.map(|value| value.to_string())
.ok_or_else(|| format!("aria2.removeDownloadResult returned unexpected result: {result}")),
Err(fallback_err) if is_gid_not_found_error(&fallback_err) => Ok(gid.to_string()),
Err(fallback_err) => Err(fallback_err),
}
}
Err(err) => Err(err),
}
}
#[tauri::command]
pub async fn aria2_remove_task_record(request: Aria2TaskCommandRequest) -> Result<String, String> {
let gid = request.gid.trim();
if gid.is_empty() {
return Err("gid is required".to_string());
}
let client = Client::new();
match call_aria2_rpc(&client, &request.rpc, "aria2.removeDownloadResult", vec![json!(gid)]).await
{
Ok(result) => result
.as_str()
.map(|value| value.to_string())
.ok_or_else(|| format!("aria2.removeDownloadResult returned unexpected result: {result}")),
Err(err) if is_gid_not_found_error(&err) => Ok(gid.to_string()),
Err(err) => Err(err),
}
}
#[tauri::command]
pub async fn aria2_pause_all(config: Aria2RpcConfig) -> Result<String, String> {
let client = Client::new();
let result = call_aria2_rpc(&client, &config, "aria2.pauseAll", vec![]).await?;
result
.as_str()
.map(|value| value.to_string())
.ok_or_else(|| format!("aria2.pauseAll returned unexpected result: {result}"))
}
#[tauri::command]
pub async fn aria2_resume_all(config: Aria2RpcConfig) -> Result<String, String> {
let client = Client::new();
let result = call_aria2_rpc(&client, &config, "aria2.unpauseAll", vec![]).await?;
result
.as_str()
.map(|value| value.to_string())
.ok_or_else(|| format!("aria2.unpauseAll returned unexpected result: {result}"))
}
#[tauri::command]
pub fn load_torrent_file(path: String) -> Result<TorrentFilePayload, String> {
if !path.to_ascii_lowercase().ends_with(".torrent") {
@@ -445,3 +788,34 @@ pub fn load_torrent_file(path: String) -> Result<TorrentFilePayload, String> {
size: bytes.len() as u64,
})
}
#[tauri::command]
pub fn open_path_in_file_manager(path: String) -> Result<(), String> {
let target = path.trim();
if target.is_empty() {
return Err("path is required".to_string());
}
let mut command = if cfg!(target_os = "macos") {
let mut cmd = Command::new("open");
cmd.arg(target);
cmd
} else if cfg!(target_os = "windows") {
let mut cmd = Command::new("explorer");
cmd.arg(target);
cmd
} else {
let mut cmd = Command::new("xdg-open");
cmd.arg(target);
cmd
};
let status = command
.status()
.map_err(|err| format!("failed to open path in file manager: {err}"))?;
if !status.success() {
return Err(format!("file manager command exited with status: {status}"));
}
Ok(())
}

View File

@@ -1,14 +1,17 @@
mod engine;
use engine::{
aria2_add_torrent, aria2_add_uri, aria2_list_tasks, engine_start, engine_status, engine_stop,
load_torrent_file, EngineState,
aria2_add_torrent, aria2_add_uri, aria2_list_tasks, aria2_pause_all, aria2_pause_task,
aria2_remove_task, aria2_remove_task_record, aria2_resume_all, aria2_resume_task,
detect_aria2_binary, engine_start, engine_status, engine_stop, load_torrent_file,
open_path_in_file_manager, EngineState,
};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.manage(EngineState::default())
.plugin(tauri_plugin_dialog::init())
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
@@ -23,10 +26,18 @@ pub fn run() {
engine_start,
engine_stop,
engine_status,
detect_aria2_binary,
aria2_add_torrent,
aria2_add_uri,
aria2_list_tasks,
load_torrent_file
aria2_pause_task,
aria2_resume_task,
aria2_remove_task,
aria2_remove_task_record,
aria2_pause_all,
aria2_resume_all,
load_torrent_file,
open_path_in_file_manager
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@@ -12,9 +12,12 @@
"app": {
"windows": [
{
"label": "main",
"title": "gdown",
"width": 800,
"height": 600,
"width": 1280,
"height": 860,
"minWidth": 1080,
"minHeight": 720,
"resizable": true,
"fullscreen": false
}
@@ -26,6 +29,9 @@
"bundle": {
"active": true,
"targets": "all",
"resources": [
"resources/engine/**/*"
],
"icon": [
"icons/32x32.png",
"icons/128x128.png",