mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-15 17:21:19 +10:00
feat: Added basic url downloading
Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
@ -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 {
|
||||
|
||||
@ -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)?)
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod autostart;
|
||||
pub mod cleanup;
|
||||
pub mod commands;
|
||||
pub mod commands;
|
||||
pub mod url_downloader;
|
||||
178
src-tauri/src/client/url_downloader.rs
Normal file
178
src-tauri/src/client/url_downloader.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
pub mod commands;
|
||||
pub mod download_agent;
|
||||
mod download_logic;
|
||||
pub mod download_logic;
|
||||
mod manifest;
|
||||
mod stored_manifest;
|
||||
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user