mirror of
https://github.com/yuhkix/wuwa-downloader.git
synced 2025-06-06 17:53:44 +00:00
Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
|
118b4f5fe0 | ||
|
29bd052ef4 | ||
|
bece26c433 | ||
|
cc837ec0cf | ||
|
f6477359e9 | ||
|
8c0ee23828 |
14 changed files with 343 additions and 35 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -4,11 +4,15 @@ debug/
|
||||||
target/
|
target/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# packaging
|
||||||
|
dist/
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
# Error / Debug Logging
|
# Error / Debug Logging
|
||||||
*.log
|
*.log
|
||||||
|
*.txt
|
||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
@ -19,4 +23,4 @@ target/
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
61
Cargo.lock
generated
61
Cargo.lock
generated
|
@ -113,6 +113,19 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"unicode-width",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -200,6 +213,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.35"
|
version = "0.8.35"
|
||||||
|
@ -647,6 +666,19 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indicatif"
|
||||||
|
version = "0.17.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"number_prefix",
|
||||||
|
"portable-atomic",
|
||||||
|
"unicode-width",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
|
@ -780,6 +812,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "number_prefix"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
|
@ -869,6 +907,12 @@ version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.94"
|
version = "1.0.94"
|
||||||
|
@ -1386,6 +1430,12 @@ version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -1532,6 +1582,16 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -1849,6 +1909,7 @@ dependencies = [
|
||||||
"colored",
|
"colored",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
"indicatif",
|
||||||
"md-5",
|
"md-5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -13,6 +13,7 @@ serde = "1.0.219"
|
||||||
ctrlc = "3.4.7"
|
ctrlc = "3.4.7"
|
||||||
shellexpand = "3.1.1"
|
shellexpand = "3.1.1"
|
||||||
flate2 = "1.1.1"
|
flate2 = "1.1.1"
|
||||||
|
indicatif = "0.17.11"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winconsole = "0.11.1"
|
winconsole = "0.11.1"
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
## 📦 Requirements
|
## 📦 Requirements
|
||||||
- **Rust nightly toolchain** - 1.87.0-nightly or newer
|
- **Rust nightly toolchain** - 1.87.0-nightly or newer
|
||||||
- **Windows** - for full console feature support
|
- **Windows** - for full console feature support
|
||||||
|
- **Linux** - for simply being better
|
||||||
|
|
||||||
### 🛠️ Installation & Usage
|
### 🛠️ Installation & Usage
|
||||||
- **Install the nightly toolchain:**
|
- **Install the nightly toolchain:**
|
||||||
|
|
113
package.sh
Normal file
113
package.sh
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BIN_NAME="wuwa-downloader"
|
||||||
|
DIST_DIR="dist"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
function info() {
|
||||||
|
echo -e "${CYAN}==> $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
echo -e "${GREEN}✔ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function warn() {
|
||||||
|
echo -e "${YELLOW}⚠ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function error() {
|
||||||
|
echo -e "${RED}✖ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_linux() {
|
||||||
|
local OUT_DIR="target/release"
|
||||||
|
local PACKAGE_NAME="${BIN_NAME}-linux-x86_64"
|
||||||
|
local PACKAGE_DIR="${DIST_DIR}/${PACKAGE_NAME}"
|
||||||
|
local ARCHIVE_NAME="${PACKAGE_NAME}.tar.gz"
|
||||||
|
|
||||||
|
clear
|
||||||
|
|
||||||
|
info "Cleaning binaries to rebuild..."
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
info "Building release binary for Linux..."
|
||||||
|
cargo build --release
|
||||||
|
clear
|
||||||
|
success "Linux build finished"
|
||||||
|
|
||||||
|
info "Creating package directory..."
|
||||||
|
rm -rf "$PACKAGE_DIR"
|
||||||
|
mkdir -p "$PACKAGE_DIR"
|
||||||
|
success "Package directory ready: $PACKAGE_DIR"
|
||||||
|
|
||||||
|
info "Copying binary..."
|
||||||
|
cp "$OUT_DIR/$BIN_NAME" "$PACKAGE_DIR/"
|
||||||
|
success "Copied binary to package directory"
|
||||||
|
|
||||||
|
info "Creating archive..."
|
||||||
|
cd "$DIST_DIR"
|
||||||
|
tar -czf "$ARCHIVE_NAME" "$PACKAGE_NAME"
|
||||||
|
cd -
|
||||||
|
success "Archive created: ${DIST_DIR}/${ARCHIVE_NAME}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_windows() {
|
||||||
|
local TARGET="x86_64-pc-windows-gnu"
|
||||||
|
local OUT_DIR="target/${TARGET}/release"
|
||||||
|
local PACKAGE_NAME="${BIN_NAME}-windows-x86_64"
|
||||||
|
local PACKAGE_DIR="${DIST_DIR}/${PACKAGE_NAME}"
|
||||||
|
local ARCHIVE_NAME="${PACKAGE_NAME}.zip"
|
||||||
|
|
||||||
|
clear
|
||||||
|
|
||||||
|
info "Cleaning binaries to rebuild..."
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
info "Building release binary for Windows..."
|
||||||
|
cargo build --release --target "$TARGET"
|
||||||
|
clear
|
||||||
|
success "Windows build finished"
|
||||||
|
|
||||||
|
info "Creating package directory..."
|
||||||
|
rm -rf "$PACKAGE_DIR"
|
||||||
|
mkdir -p "$PACKAGE_DIR"
|
||||||
|
success "Package directory ready: $PACKAGE_DIR"
|
||||||
|
|
||||||
|
info "Copying binary..."
|
||||||
|
cp "$OUT_DIR/${BIN_NAME}.exe" "$PACKAGE_DIR/"
|
||||||
|
success "Copied binary to package directory"
|
||||||
|
|
||||||
|
info "Creating archive..."
|
||||||
|
cd "$DIST_DIR"
|
||||||
|
zip -r "$ARCHIVE_NAME" "$PACKAGE_NAME"
|
||||||
|
cd -
|
||||||
|
success "Archive created: ${DIST_DIR}/${ARCHIVE_NAME}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
error "Usage: $0 [linux|windows]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
linux)
|
||||||
|
build_linux
|
||||||
|
;;
|
||||||
|
windows)
|
||||||
|
build_windows
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "Unknown target: $1"
|
||||||
|
error "Usage: $0 [linux|windows]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
|
@ -2,4 +2,4 @@
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub index_url: String,
|
pub index_url: String,
|
||||||
pub zip_bases: Vec<String>,
|
pub zip_bases: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,25 @@ use colored::*;
|
||||||
pub struct Status;
|
pub struct Status;
|
||||||
|
|
||||||
impl Status {
|
impl Status {
|
||||||
pub fn info() -> ColoredString { "[*]".cyan() }
|
pub fn info() -> ColoredString {
|
||||||
pub fn success() -> ColoredString { "[+]".green() }
|
"[*]".cyan()
|
||||||
pub fn warning() -> ColoredString { "[!]".yellow() }
|
}
|
||||||
pub fn error() -> ColoredString { "[-]".red() }
|
pub fn success() -> ColoredString {
|
||||||
pub fn question() -> ColoredString { "[?]".blue() }
|
"[+]".green()
|
||||||
pub fn progress() -> ColoredString { "[→]".purple() }
|
}
|
||||||
pub fn matched() -> ColoredString { "[↓]".bright_purple() }
|
pub fn warning() -> ColoredString {
|
||||||
}
|
"[!]".yellow()
|
||||||
|
}
|
||||||
|
pub fn error() -> ColoredString {
|
||||||
|
"[-]".red()
|
||||||
|
}
|
||||||
|
pub fn question() -> ColoredString {
|
||||||
|
"[?]".blue()
|
||||||
|
}
|
||||||
|
pub fn progress() -> ColoredString {
|
||||||
|
"[→]".purple()
|
||||||
|
}
|
||||||
|
pub fn matched() -> ColoredString {
|
||||||
|
"[↓]".bright_purple()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{io, path::Path};
|
|
||||||
use colored::Colorize;
|
|
||||||
use crate::config::status::Status;
|
use crate::config::status::Status;
|
||||||
|
use colored::Colorize;
|
||||||
|
use std::{io, path::Path};
|
||||||
|
|
||||||
pub fn print_results(success: usize, total: usize, folder: &Path) {
|
pub fn print_results(success: usize, total: usize, folder: &Path) {
|
||||||
let title = if success == total {
|
let title = if success == total {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use std::{fs::{self, OpenOptions}, io::Write, time::SystemTime};
|
use std::{
|
||||||
|
fs::{self, OpenOptions},
|
||||||
|
io::Write,
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn setup_logging() -> fs::File {
|
pub fn setup_logging() -> fs::File {
|
||||||
OpenOptions::new()
|
OpenOptions::new()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod console;
|
pub mod console;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
|
@ -8,6 +8,10 @@ use std::{
|
||||||
thread,
|
thread,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winconsole::console::{clear, set_title};
|
use winconsole::console::{clear, set_title};
|
||||||
|
|
||||||
|
@ -122,6 +126,9 @@ pub fn calculate_total_size(resources: &[Value], client: &Client, config: &Confi
|
||||||
bytes_to_human(total_size).cyan()
|
bytes_to_human(total_size).cyan()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
Command::new("clear").status().unwrap();
|
||||||
|
|
||||||
total_size
|
total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -1,6 +1,10 @@
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use reqwest::blocking::Client;
|
use reqwest::blocking::Client;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winconsole::console::{clear, set_title};
|
use winconsole::console::{clear, set_title};
|
||||||
|
|
||||||
|
@ -9,10 +13,11 @@ use wuwa_downloader::{
|
||||||
io::{
|
io::{
|
||||||
console::print_results,
|
console::print_results,
|
||||||
file::get_dir,
|
file::get_dir,
|
||||||
util::{
|
|
||||||
calculate_total_size, download_resources, exit_with_error, setup_ctrlc, track_progress, start_title_thread
|
|
||||||
},
|
|
||||||
logging::setup_logging,
|
logging::setup_logging,
|
||||||
|
util::{
|
||||||
|
calculate_total_size, download_resources, exit_with_error, setup_ctrlc,
|
||||||
|
start_title_thread, track_progress,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
network::client::{fetch_index, get_config},
|
network::client::{fetch_index, get_config},
|
||||||
};
|
};
|
||||||
|
@ -33,6 +38,9 @@ fn main() {
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
clear().unwrap();
|
clear().unwrap();
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
Command::new("clear").status().unwrap();
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"\n{} Download folder: {}\n",
|
"\n{} Download folder: {}\n",
|
||||||
Status::info(),
|
Status::info(),
|
||||||
|
@ -83,7 +91,7 @@ fn main() {
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
clear().unwrap();
|
clear().unwrap();
|
||||||
|
|
||||||
print_results(
|
print_results(
|
||||||
success.load(std::sync::atomic::Ordering::SeqCst),
|
success.load(std::sync::atomic::Ordering::SeqCst),
|
||||||
resources.len(),
|
resources.len(),
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use reqwest::blocking::Client;
|
use reqwest::blocking::Client;
|
||||||
use serde_json::{Value, from_reader, from_str};
|
use serde_json::{from_reader, from_str, Value};
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
use std::process::Command;
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs::{self, OpenOptions},
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
path::Path,
|
path::Path,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
u64,
|
u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winconsole::console::clear;
|
use winconsole::console::clear;
|
||||||
|
|
||||||
|
@ -234,16 +238,23 @@ pub fn download_file(
|
||||||
|
|
||||||
println!("{} Downloading: {}", Status::progress(), filename.purple());
|
println!("{} Downloading: {}", Status::progress(), filename.purple());
|
||||||
|
|
||||||
|
let pb = ProgressBar::new(expected_size.unwrap_or(0));
|
||||||
|
pb.set_style(ProgressStyle::default_bar()
|
||||||
|
.template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta}, {binary_bytes_per_sec})")
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("#>-"));
|
||||||
|
|
||||||
let mut retries = MAX_RETRIES;
|
let mut retries = MAX_RETRIES;
|
||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
|
|
||||||
while retries > 0 {
|
while retries > 0 {
|
||||||
let result = download_single_file(&client, &url, &path, should_stop, progress);
|
let result = download_single_file(&client, &url, &path, should_stop, progress, &pb);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => break,
|
Ok(_) => break,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if should_stop.load(std::sync::atomic::Ordering::SeqCst) {
|
if should_stop.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
|
pb.finish_and_clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,6 +274,8 @@ pub fn download_file(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pb.finish_and_clear();
|
||||||
|
|
||||||
if should_stop.load(std::sync::atomic::Ordering::SeqCst) {
|
if should_stop.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -315,18 +328,46 @@ fn download_single_file(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
should_stop: &std::sync::atomic::AtomicBool,
|
should_stop: &std::sync::atomic::AtomicBool,
|
||||||
progress: &DownloadProgress,
|
progress: &DownloadProgress,
|
||||||
|
pb: &ProgressBar,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut response = client
|
let mut downloaded: u64 = 0;
|
||||||
.get(url)
|
if path.exists() {
|
||||||
.timeout(DOWNLOAD_TIMEOUT)
|
downloaded = fs::metadata(path)
|
||||||
|
.map_err(|e| format!("Metadata error: {}", e))?
|
||||||
|
.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = client.get(url).timeout(DOWNLOAD_TIMEOUT);
|
||||||
|
|
||||||
|
let request = if downloaded > 0 {
|
||||||
|
request.header("Range", format!("bytes={}-", downloaded))
|
||||||
|
} else {
|
||||||
|
request
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut response = request
|
||||||
.send()
|
.send()
|
||||||
.map_err(|e| format!("Network error: {}", e))?;
|
.map_err(|e| format!("Network error: {}", e))?;
|
||||||
|
|
||||||
if !response.status().is_success() {
|
if response.status() == reqwest::StatusCode::RANGE_NOT_SATISFIABLE {
|
||||||
|
return Err("Range not satisfiable. File may already be fully downloaded.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response.status().is_success() && response.status() != reqwest::StatusCode::PARTIAL_CONTENT
|
||||||
|
{
|
||||||
return Err(format!("HTTP error: {}", response.status()));
|
return Err(format!("HTTP error: {}", response.status()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file = fs::File::create(path).map_err(|e| format!("File error: {}", e))?;
|
let mut file = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(path)
|
||||||
|
.map_err(|e| format!("File error: {}", e))?;
|
||||||
|
|
||||||
|
pb.set_position(downloaded);
|
||||||
|
progress
|
||||||
|
.downloaded_bytes
|
||||||
|
.store(downloaded, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
|
||||||
let mut buffer = [0; BUFFER_SIZE];
|
let mut buffer = [0; BUFFER_SIZE];
|
||||||
loop {
|
loop {
|
||||||
|
@ -345,9 +386,11 @@ fn download_single_file(
|
||||||
file.write_all(&buffer[..bytes_read])
|
file.write_all(&buffer[..bytes_read])
|
||||||
.map_err(|e| format!("Write error: {}", e))?;
|
.map_err(|e| format!("Write error: {}", e))?;
|
||||||
|
|
||||||
|
downloaded += bytes_read as u64;
|
||||||
|
pb.set_position(downloaded);
|
||||||
progress
|
progress
|
||||||
.downloaded_bytes
|
.downloaded_bytes
|
||||||
.fetch_add(bytes_read as u64, std::sync::atomic::Ordering::SeqCst);
|
.store(downloaded, std::sync::atomic::Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -508,22 +551,75 @@ pub fn fetch_gist(client: &Client) -> Result<String, String> {
|
||||||
response
|
response
|
||||||
.copy_to(&mut buffer)
|
.copy_to(&mut buffer)
|
||||||
.map_err(|e| format!("Error reading response: {}", e))?;
|
.map_err(|e| format!("Error reading response: {}", e))?;
|
||||||
|
|
||||||
let mut gz = GzDecoder::new(&buffer[..]);
|
let mut gz = GzDecoder::new(&buffer[..]);
|
||||||
let mut decompressed = String::new();
|
let mut decompressed = String::new();
|
||||||
gz.read_to_string(&mut decompressed)
|
gz.read_to_string(&mut decompressed)
|
||||||
.map_err(|e| format!("Error decompressing: {}", e))?;
|
.map_err(|e| format!("Error decompressing: {}", e))?;
|
||||||
|
|
||||||
from_str(&decompressed).map_err(|e| format!("Invalid JSON: {}", e))?
|
from_str(&decompressed).map_err(|e| format!("Invalid JSON: {}", e))?
|
||||||
} else {
|
} else {
|
||||||
from_reader(response).map_err(|e| format!("Invalid JSON: {}", e))?
|
from_reader(response).map_err(|e| format!("Invalid JSON: {}", e))?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
Command::new("clear").status().unwrap();
|
||||||
|
|
||||||
|
let entries = [
|
||||||
|
("live", "os", "Live - OS"),
|
||||||
|
("live", "cn", "Live - CN"),
|
||||||
|
("beta", "os", "Beta - OS"),
|
||||||
|
("beta", "cn", "Beta - CN"),
|
||||||
|
];
|
||||||
|
|
||||||
println!("{} Available versions:", Status::info());
|
println!("{} Available versions:", Status::info());
|
||||||
println!("1. Live - OS");
|
|
||||||
println!("2. Live - CN");
|
for (i, (cat, ver, label)) in entries.iter().enumerate() {
|
||||||
println!("3. Beta - OS");
|
let index_url = get_version(&gist_data, cat, ver)?;
|
||||||
println!("4. Beta - CN (wicked-waifus-rs)");
|
|
||||||
|
let mut resp = client
|
||||||
|
.get(&index_url)
|
||||||
|
.send()
|
||||||
|
.map_err(|e| format!("Error fetching index.json: {}", e))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let version_json: Value = {
|
||||||
|
let content_encoding = resp
|
||||||
|
.headers()
|
||||||
|
.get("content-encoding")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
|
if content_encoding.contains("gzip") {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
resp.copy_to(&mut buffer)
|
||||||
|
.map_err(|e| format!("Error reading response: {}", e))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut gz = GzDecoder::new(&buffer[..]);
|
||||||
|
let mut decompressed = String::new();
|
||||||
|
gz.read_to_string(&mut decompressed)
|
||||||
|
.map_err(|e| format!("Error decompressing: {}", e))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
from_str(&decompressed)
|
||||||
|
.map_err(|e| format!("Invalid JSON: {}", e))
|
||||||
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
from_reader(resp)
|
||||||
|
.map_err(|e| format!("Invalid JSON: {}", e))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let version = version_json
|
||||||
|
.get("default")
|
||||||
|
.and_then(|d| d.get("config"))
|
||||||
|
.and_then(|c| c.get("version"))
|
||||||
|
.or_else(|| version_json.get("default").and_then(|d| d.get("version")))
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown");
|
||||||
|
|
||||||
|
println!("{}. {} ({})", i + 1, label, version);
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
print!("{} Select version: ", Status::question());
|
print!("{} Select version: ", Status::question());
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
|
Loading…
Reference in a new issue