Skip to content

Commit

Permalink
feat(download): add a download demo (#22)
Browse files Browse the repository at this point in the history
* feat(download): add a download demo

* feat(download): add a download demo

* chore(fmt): setup cargo fmt for lint-staged

* chore(changeset): add changeset
  • Loading branch information
fu050409 authored Jul 13, 2024
1 parent 60e0ca4 commit 15224fd
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .changeset/chilled-ghosts-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'grassator': patch
'@noctisynth/grassator': patch
---

Add a multi-process download demo
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"prettier --write --ignore-unknown"
],
"src-tauri/**/*.rs": [
"cargo fmt --all"
"pnpm --filter grassator run fmt"
]
}
}
3 changes: 2 additions & 1 deletion src-tauri/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"dev": "cd .. && pnpm dev",
"build": "cd .. && pnpm build"
"build": "cd .. && pnpm build",
"fmt": "cargo fmt -- "
}
}
24 changes: 23 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,34 @@ async fn get_file_size(url: &str) -> Result<models::FileSize, ()> {
}
}

#[tauri::command]
async fn download_file(
url: &str,
output: &str,
config: tauri::State<'_, state::Config>,
) -> Result<models::DownloadResult, ()> {
match utils::download_file(url, output, &config.inner()).await {
Ok(_) => Ok(models::DownloadResult {
file_path: output.to_string(),
error: None,
}),
Err(e) => Ok(models::DownloadResult {
file_path: String::new(),
error: Some(e.to_string()),
}),
}
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.manage(state::Config::default())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![get_config, get_file_size])
.invoke_handler(tauri::generate_handler![
get_config,
get_file_size,
download_file
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
6 changes: 6 additions & 0 deletions src-tauri/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ pub(crate) struct FileSize {
pub(crate) size: u64,
pub(crate) error: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct DownloadResult {
pub(crate) file_path: String,
pub(crate) error: Option<String>,
}
4 changes: 2 additions & 2 deletions src-tauri/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Config {
pub(crate) preset_file: String,
pub(crate) threads_count: u32,
pub(crate) num_threads: u32,
}

impl Default for Config {
fn default() -> Self {
Self {
preset_file: String::from("presets/default.json"),
threads_count: 1,
num_threads: 7,
}
}
}
69 changes: 68 additions & 1 deletion src-tauri/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,78 @@
use std::{
fs::OpenOptions,
io::{Seek, SeekFrom, Write},
sync::Arc,
};

use reqwest::header::CONTENT_LENGTH;
use reqwest::header::RANGE;
use tauri::async_runtime::{spawn, Mutex};

pub(crate) async fn get_file_size(url: &str) -> Result<u64, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let response = client.head(url).send().await?;
let size = response
.headers()
.get("content-length")
.get(CONTENT_LENGTH)
.ok_or("No content-length header")?
.to_str()?
.parse::<u64>()?;
Ok(size)
}

pub(crate) async fn download_file(
url: &str,
output_path: &str,
config: &crate::state::Config,
) -> Result<bool, Box<dyn std::error::Error>> {
let output = Arc::new(Mutex::new(
OpenOptions::new()
.write(true)
.create(true)
.open(output_path)
.unwrap(),
));

let file_size = get_file_size(url).await?;
let mut handles = Vec::new();
let chunk_size = file_size / config.num_threads as u64;

for i in 0..config.num_threads {
let start = i as u64 * chunk_size;
let end = if i == config.num_threads - 1 {
file_size - 1
} else {
(i as u64 + 1) * chunk_size - 1
};

let url = url.to_string();
let output = Arc::clone(&output);
let handle = spawn(async move {
download_chunk(&url, start, end, &output).await.unwrap();
});
handles.push(handle);
}

for handle in handles {
handle.await.unwrap();
}
Ok(true)
}

pub(crate) async fn download_chunk(
url: &str,
start: u64,
end: u64,
output: &Arc<Mutex<std::fs::File>>,
) -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let range_header = format!("bytes={}-{}", start, end);
let response = client.get(url).header(RANGE, range_header).send().await?;

let mut file = output.lock().await;
file.seek(SeekFrom::Start(start))?;
let content = response.bytes().await?;
file.write_all(&content)?;

Ok(())
}
4 changes: 4 additions & 0 deletions src/views/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ onMounted(async () => {
url: 'https://freetestdata.com/wp-content/uploads/2021/09/1-MB-DOC.doc',
});
console.log(size.error ? size.error : `File size: ${size.size}`);
await invoke('download_file', {
url: 'https://freetestdata.com/wp-content/uploads/2021/09/1-MB-DOC.doc',
output: '1-MB-DOC.doc',
});
});
</script>

Expand Down

0 comments on commit 15224fd

Please sign in to comment.