feat: Added basic url downloading

Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
quexeky
2025-06-03 16:52:48 +10:00
parent 065eb2356a
commit beea0505d1
7 changed files with 227 additions and 10 deletions

View File

@ -68,7 +68,7 @@
Open Data Directory
</button>
<button
@click="() => openLogFile()"
@click="() => queue_url_download()"
type="button"
class="inline-flex items-center gap-x-2 rounded-md bg-blue-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
@ -115,6 +115,15 @@ dataDir.value = systemData.dataDir;
const currentPlatform = await platform();
platformInfo.value = currentPlatform;
async function queue_url_download() {
try {
await invoke("queue_url_download", { url: "https://codeload.github.com/Drop-OSS/drop-app/zip/refs/heads/develop"});
}
catch (e) {
console.error(e);
}
}
async function openDataDir() {
if (!dataDir.value) return;
try {

View File

@ -1,4 +1,8 @@
use crate::AppState;
use std::sync::Arc;
use crate::{
client::url_downloader::URLDownloader, download_manager::{download_manager::DownloadManagerSignal, downloadable::Downloadable}, error::download_manager_error::DownloadManagerError, AppState
};
#[tauri::command]
pub fn fetch_state(
@ -9,3 +13,22 @@ pub fn fetch_state(
drop(guard);
Ok(cloned_state)
}
#[tauri::command]
pub fn queue_url_download(
state: tauri::State<'_, std::sync::Mutex<AppState<'_>>>,
url: String
) -> Result<(), DownloadManagerError<DownloadManagerSignal>> {
let sender = state.lock().unwrap().download_manager.get_sender();
let game_download_agent = Arc::new(Box::new(URLDownloader::new(
String::from("Test URL Download"),
"/home/quexeky/Downloads/test_url_download",
sender,
url,
)) as Box<dyn Downloadable + Send + Sync>);
Ok(state
.lock()
.unwrap()
.download_manager
.queue_download(game_download_agent)?)
}

View File

@ -1,3 +1,4 @@
pub mod autostart;
pub mod cleanup;
pub mod commands;
pub mod commands;
pub mod url_downloader;

View File

@ -0,0 +1,178 @@
use std::{
fs::{self, create_dir_all},
io::{self, copy},
path::{Path, PathBuf},
sync::{mpsc::Sender, Arc, Mutex},
u64, usize,
};
use log::{debug, error, warn};
use reqwest::redirect::Policy;
use tauri::{AppHandle, Emitter};
use crate::{
database::{
db::borrow_db_checked,
models::data::{DownloadType, DownloadableMetadata},
},
download_manager::{
download_manager::{DownloadManagerSignal, DownloadStatus},
downloadable::Downloadable,
util::{
download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
progress_object::{ProgressHandle, ProgressObject},
},
},
error::application_download_error::ApplicationDownloadError,
games::downloads::download_logic::{DropDownloadPipeline, DropWriter},
DB,
};
pub struct URLDownloader {
id: String,
version: String,
url: String,
control_flag: DownloadThreadControl,
progress: Arc<ProgressObject>,
target: PathBuf,
sender: Sender<DownloadManagerSignal>,
status: Mutex<DownloadStatus>,
}
struct URLDownloaderManager {
current_offset: usize,
}
impl URLDownloader {
pub fn new<S: Into<String>, P: AsRef<Path>>(
id: String,
target: P,
sender: Sender<DownloadManagerSignal>,
url: S,
) -> Self {
// Don't run by default
let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop);
Self {
id,
version: String::new(),
control_flag,
target: target.as_ref().into(),
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
sender,
status: Mutex::new(DownloadStatus::Queued),
url: url.into(),
}
}
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
// TODO: Fix these unwraps and implement From<io::Error> for ApplicationDownloadError
let client = reqwest::blocking::Client::builder()
.redirect(Policy::default())
.build()
.unwrap();
let response = client.head(&self.url).send().unwrap();
let content_length = response
.headers()
.get(reqwest::header::CONTENT_LENGTH)
.map(|x| x.to_str().unwrap().parse().unwrap())
.unwrap_or(usize::MAX);
let response = client.get(&self.url).send().unwrap();
println!("{:?}, content length: {}", response, content_length);
self.set_progress_object_params(content_length);
let progress = self.progress.get(0);
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
let mut pipeline = DropDownloadPipeline::new(
response,
DropWriter::new(&self.target),
&self.control_flag,
progress_handle,
content_length,
);
let completed = pipeline
.copy()
.map_err(|e| ApplicationDownloadError::IoError(e.kind()))?;
if !completed {
return Ok(false);
};
Ok(true)
}
fn set_progress_object_params(&self, max: usize) {
// Avoid re-setting it
if self.progress.get_max() != 0 {
return;
}
self.progress.set_max(max);
self.progress.set_size(1);
self.progress.set_time_now();
}
}
impl Downloadable for URLDownloader {
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
*self.status.lock().unwrap() = DownloadStatus::Downloading;
self.download(app_handle)
}
fn progress(&self) -> Arc<ProgressObject> {
self.progress.clone()
}
fn control_flag(&self) -> DownloadThreadControl {
self.control_flag.clone()
}
fn metadata(&self) -> DownloadableMetadata {
DownloadableMetadata {
id: self.id.clone(),
version: Some(self.version.clone()),
download_type: DownloadType::Game,
}
}
fn on_initialised(&self, _app_handle: &tauri::AppHandle) {
*self.status.lock().unwrap() = DownloadStatus::Queued;
}
fn on_error(&self, app_handle: &tauri::AppHandle, error: &ApplicationDownloadError) {
*self.status.lock().unwrap() = DownloadStatus::Error;
app_handle
.emit("download_error", error.to_string())
.unwrap();
error!("error while managing download: {}", error);
let mut handle = DB.borrow_data_mut().unwrap();
handle
.applications
.transient_statuses
.remove(&self.metadata());
}
fn on_complete(&self, app_handle: &tauri::AppHandle) {
println!("Completed url download");
}
// TODO: fix this function. It doesn't restart the download properly, nor does it reset the state properly
fn on_incomplete(&self, app_handle: &tauri::AppHandle) {
println!("Incomplete url download");
}
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {
println!("Cancelled url download");
}
fn status(&self) -> DownloadStatus {
self.status.lock().unwrap().clone()
}
}

View File

@ -11,6 +11,7 @@ use std::fs::{set_permissions, Permissions};
use std::io::{ErrorKind, Read};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::{
fs::{File, OpenOptions},
io::{self, BufWriter, Seek, SeekFrom, Write},
@ -22,14 +23,14 @@ pub struct DropWriter<W: Write> {
destination: W,
}
impl DropWriter<File> {
fn new(path: PathBuf) -> Self {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
Self {
destination: OpenOptions::new().write(true).open(path).unwrap(),
destination: OpenOptions::new().create(true).write(true).open(path).unwrap(),
hasher: Context::new(),
}
}
fn finish(mut self) -> io::Result<Digest> {
pub fn finish(mut self) -> io::Result<Digest> {
self.flush().unwrap();
Ok(self.hasher.compute())
}
@ -66,7 +67,7 @@ pub struct DropDownloadPipeline<'a, R: Read, W: Write> {
pub size: usize,
}
impl<'a> DropDownloadPipeline<'a, Response, File> {
fn new(
pub fn new(
source: Response,
destination: DropWriter<File>,
control_flag: &'a DownloadThreadControl,
@ -82,7 +83,7 @@ impl<'a> DropDownloadPipeline<'a, Response, File> {
}
}
fn copy(&mut self) -> Result<bool, io::Error> {
pub fn copy(&mut self) -> Result<bool, io::Error> {
let copy_buf_size = 512;
let mut copy_buf = vec![0; copy_buf_size];
let mut buf_writer = BufWriter::with_capacity(1024 * 1024, &mut self.destination);
@ -102,6 +103,10 @@ impl<'a> DropDownloadPipeline<'a, Response, File> {
if current_size == self.size {
break;
}
if bytes_read == 0 {
println!("Terminated stream");
break;
}
}
Ok(true)

View File

@ -1,5 +1,5 @@
pub mod commands;
pub mod download_agent;
mod download_logic;
pub mod download_logic;
mod manifest;
mod stored_manifest;

View File

@ -7,7 +7,7 @@ mod error;
mod process;
mod remote;
use crate::database::db::DatabaseImpls;
use crate::{client::commands::queue_url_download, database::db::DatabaseImpls};
use client::{
autostart::{get_autostart_enabled, sync_autostart_on_startup, toggle_autostart},
cleanup::{cleanup_and_exit, quit},
@ -264,6 +264,7 @@ pub fn run() {
resume_downloads,
cancel_game,
uninstall_game,
queue_url_download,
// Processes
launch_game,
kill_game,