mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-13 00:02:41 +10:00
Compare commits
1 Commits
3b09dcfb73
...
v0.3.2-dl
| Author | SHA1 | Date | |
|---|---|---|---|
| 124d51bced |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "view",
|
"name": "view",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.1",
|
"version": "0.3.2-dl",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt generate",
|
"build": "nuxt generate",
|
||||||
|
|||||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@ -1284,7 +1284,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.1"
|
version = "0.3.2-dl"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-instant-full",
|
"atomic-instant-full",
|
||||||
"bitcode",
|
"bitcode",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.1"
|
version = "0.3.2-dl"
|
||||||
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
||||||
authors = ["Drop OSS"]
|
authors = ["Drop OSS"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|||||||
@ -23,7 +23,7 @@ pub struct ProgressObject {
|
|||||||
//last_update: Arc<RwLock<Instant>>,
|
//last_update: Arc<RwLock<Instant>>,
|
||||||
last_update_time: Arc<AtomicInstant>,
|
last_update_time: Arc<AtomicInstant>,
|
||||||
bytes_last_update: Arc<AtomicUsize>,
|
bytes_last_update: Arc<AtomicUsize>,
|
||||||
rolling: RollingProgressWindow<250>,
|
rolling: RollingProgressWindow<10>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@ -7,14 +7,18 @@ use crate::error::drop_server_error::DropServerError;
|
|||||||
use crate::error::remote_access_error::RemoteAccessError;
|
use crate::error::remote_access_error::RemoteAccessError;
|
||||||
use crate::games::downloads::manifest::DropDownloadContext;
|
use crate::games::downloads::manifest::DropDownloadContext;
|
||||||
use crate::remote::auth::generate_authorization_header;
|
use crate::remote::auth::generate_authorization_header;
|
||||||
use log::{debug, warn};
|
use http::response;
|
||||||
|
use log::{debug, info, warn};
|
||||||
use md5::{Context, Digest};
|
use md5::{Context, Digest};
|
||||||
use reqwest::blocking::{RequestBuilder, Response};
|
use reqwest::blocking::{RequestBuilder, Response};
|
||||||
|
|
||||||
use std::fs::{set_permissions, Permissions};
|
use std::fs::{Permissions, set_permissions};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::ops::Sub;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Instant;
|
||||||
use std::{
|
use std::{
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
io::{self, BufWriter, Seek, SeekFrom, Write},
|
io::{self, BufWriter, Seek, SeekFrom, Write},
|
||||||
@ -23,14 +27,21 @@ use std::{
|
|||||||
|
|
||||||
pub struct DropWriter<W: Write> {
|
pub struct DropWriter<W: Write> {
|
||||||
hasher: Context,
|
hasher: Context,
|
||||||
destination: W,
|
destination: BufWriter<W>,
|
||||||
|
progress: ProgressHandle,
|
||||||
}
|
}
|
||||||
impl DropWriter<File> {
|
impl DropWriter<File> {
|
||||||
fn new(path: PathBuf) -> Self {
|
fn new(path: PathBuf, progress: ProgressHandle) -> Self {
|
||||||
let destination = OpenOptions::new().write(true).create(true).truncate(false).open(&path).unwrap();
|
let destination = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(false)
|
||||||
|
.open(&path)
|
||||||
|
.unwrap();
|
||||||
Self {
|
Self {
|
||||||
destination,
|
destination: BufWriter::with_capacity(1 * 1024 * 1024, destination),
|
||||||
hasher: Context::new(),
|
hasher: Context::new(),
|
||||||
|
progress,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +56,10 @@ impl Write for DropWriter<File> {
|
|||||||
self.hasher
|
self.hasher
|
||||||
.write_all(buf)
|
.write_all(buf)
|
||||||
.map_err(|e| io::Error::other(format!("Unable to write to hasher: {e}")))?;
|
.map_err(|e| io::Error::other(format!("Unable to write to hasher: {e}")))?;
|
||||||
self.destination.write(buf)
|
let bytes_written = self.destination.write(buf)?;
|
||||||
|
self.progress.add(bytes_written);
|
||||||
|
|
||||||
|
Ok(bytes_written)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
@ -64,60 +78,33 @@ pub struct DropDownloadPipeline<'a, R: Read, W: Write> {
|
|||||||
pub source: R,
|
pub source: R,
|
||||||
pub destination: DropWriter<W>,
|
pub destination: DropWriter<W>,
|
||||||
pub control_flag: &'a DownloadThreadControl,
|
pub control_flag: &'a DownloadThreadControl,
|
||||||
pub progress: ProgressHandle,
|
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Seek for DropDownloadPipeline<'a, Response, File> {
|
||||||
|
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||||
|
self.destination.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> DropDownloadPipeline<'a, Response, File> {
|
impl<'a> DropDownloadPipeline<'a, Response, File> {
|
||||||
fn new(
|
fn new(
|
||||||
source: Response,
|
source: Response,
|
||||||
destination: DropWriter<File>,
|
destination: PathBuf,
|
||||||
control_flag: &'a DownloadThreadControl,
|
control_flag: &'a DownloadThreadControl,
|
||||||
progress: ProgressHandle,
|
progress: ProgressHandle,
|
||||||
size: usize,
|
size: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source,
|
source,
|
||||||
destination,
|
destination: DropWriter::new(destination, progress),
|
||||||
control_flag,
|
control_flag,
|
||||||
progress,
|
|
||||||
size,
|
size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy(&mut self) -> Result<bool, io::Error> {
|
fn copy(&mut self) -> Result<bool, io::Error> {
|
||||||
let copy_buf_size = 512;
|
io::copy(&mut self.source, &mut self.destination).unwrap();
|
||||||
let mut copy_buf = vec![0; copy_buf_size];
|
|
||||||
let mut buf_writer = BufWriter::with_capacity(1024 * 1024, &mut self.destination);
|
|
||||||
|
|
||||||
let mut current_size = 0;
|
|
||||||
loop {
|
|
||||||
if self.control_flag.get() == DownloadThreadControlFlag::Stop {
|
|
||||||
buf_writer.flush()?;
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut bytes_read = self.source.read(&mut copy_buf)?;
|
|
||||||
current_size += bytes_read;
|
|
||||||
|
|
||||||
if current_size > self.size {
|
|
||||||
let over = current_size - self.size;
|
|
||||||
warn!("server sent too many bytes... {over} over");
|
|
||||||
bytes_read -= over;
|
|
||||||
current_size = self.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf_writer.write_all(©_buf[0..bytes_read])?;
|
|
||||||
self.progress.add(bytes_read);
|
|
||||||
|
|
||||||
if current_size >= self.size {
|
|
||||||
debug!(
|
|
||||||
"finished with final size of {} vs {}",
|
|
||||||
current_size, self.size
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf_writer.flush()?;
|
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
@ -139,11 +126,21 @@ pub fn download_game_chunk(
|
|||||||
progress.set(0);
|
progress.set(0);
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
debug!("started chunk {}", ctx.checksum);
|
||||||
|
|
||||||
|
let header = generate_authorization_header();
|
||||||
|
let header_time = start.elapsed();
|
||||||
|
|
||||||
let response = request
|
let response = request
|
||||||
.header("Authorization", generate_authorization_header())
|
.header("Authorization", header)
|
||||||
.send()
|
.send()
|
||||||
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
|
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
|
||||||
|
|
||||||
|
let response_time = start.elapsed();
|
||||||
|
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
debug!("chunk request got status code: {}", response.status());
|
debug!("chunk request got status code: {}", response.status());
|
||||||
let raw_res = response.text().unwrap();
|
let raw_res = response.text().unwrap();
|
||||||
@ -157,14 +154,6 @@ pub fn download_game_chunk(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut destination = DropWriter::new(ctx.path.clone());
|
|
||||||
|
|
||||||
if ctx.offset != 0 {
|
|
||||||
destination
|
|
||||||
.seek(SeekFrom::Start(ctx.offset))
|
|
||||||
.expect("Failed to seek to file offset");
|
|
||||||
}
|
|
||||||
|
|
||||||
let content_length = response.content_length();
|
let content_length = response.content_length();
|
||||||
if content_length.is_none() {
|
if content_length.is_none() {
|
||||||
warn!("recieved 0 length content from server");
|
warn!("recieved 0 length content from server");
|
||||||
@ -179,8 +168,18 @@ pub fn download_game_chunk(
|
|||||||
return Err(ApplicationDownloadError::DownloadError);
|
return Err(ApplicationDownloadError::DownloadError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pipeline_start = start.elapsed();
|
||||||
|
|
||||||
let mut pipeline =
|
let mut pipeline =
|
||||||
DropDownloadPipeline::new(response, destination, control_flag, progress, length);
|
DropDownloadPipeline::new(response, ctx.path.clone(), control_flag, progress, length);
|
||||||
|
|
||||||
|
if ctx.offset != 0 {
|
||||||
|
pipeline
|
||||||
|
.seek(SeekFrom::Start(ctx.offset))
|
||||||
|
.expect("Failed to seek to file offset");
|
||||||
|
}
|
||||||
|
|
||||||
|
let pipeline_setup = start.elapsed();
|
||||||
|
|
||||||
let completed = pipeline
|
let completed = pipeline
|
||||||
.copy()
|
.copy()
|
||||||
@ -189,6 +188,8 @@ pub fn download_game_chunk(
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pipeline_finish = start.elapsed();
|
||||||
|
|
||||||
// If we complete the file, set the permissions (if on Linux)
|
// If we complete the file, set the permissions (if on Linux)
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
@ -200,15 +201,31 @@ pub fn download_game_chunk(
|
|||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| ApplicationDownloadError::IoError(e.kind()))?;
|
.map_err(|e| ApplicationDownloadError::IoError(e.kind()))?;
|
||||||
|
|
||||||
|
let checksum_finish = start.elapsed();
|
||||||
|
|
||||||
let res = hex::encode(checksum.0);
|
let res = hex::encode(checksum.0);
|
||||||
if res != ctx.checksum {
|
if res != ctx.checksum {
|
||||||
return Err(ApplicationDownloadError::Checksum);
|
return Err(ApplicationDownloadError::Checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let header_update = header_time.as_millis();
|
||||||
|
let response_update = response_time.sub(header_time).as_millis();
|
||||||
|
let pipeline_start_update = pipeline_start.sub(response_time).as_millis();
|
||||||
|
let pipeline_setup_update = pipeline_setup.sub(pipeline_start).as_millis();
|
||||||
|
let pipeline_finish_update = pipeline_finish.sub(pipeline_setup).as_millis();
|
||||||
|
let checksum_update = checksum_finish.sub(pipeline_finish).as_millis();
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Successfully finished download #{}, copied {} bytes",
|
"\nheader: {}\nresponse: {}\npipeline start: {}\npipeline setup: {}\npipeline finish: {}\nchecksum finish: {}",
|
||||||
ctx.checksum, length
|
header_update,
|
||||||
|
response_update,
|
||||||
|
pipeline_start_update,
|
||||||
|
pipeline_setup_update,
|
||||||
|
pipeline_finish_update,
|
||||||
|
checksum_update
|
||||||
);
|
);
|
||||||
|
|
||||||
|
debug!("finished chunk {}", ctx.checksum);
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#![feature(fn_traits)]
|
#![feature(fn_traits)]
|
||||||
#![feature(duration_constructors)]
|
#![feature(duration_constructors)]
|
||||||
|
#![feature(duration_millis_float)]
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
|
|||||||
@ -11,7 +11,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use bitcode::{Decode, DecodeOwned, Encode};
|
use bitcode::{Decode, DecodeOwned, Encode};
|
||||||
use http::{Response, header::CONTENT_TYPE, response::Builder as ResponseBuilder};
|
use http::{Response, header::CONTENT_TYPE, response::Builder as ResponseBuilder};
|
||||||
use log::debug;
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! offline {
|
macro_rules! offline {
|
||||||
@ -68,18 +67,9 @@ pub fn get_cached_object_db<D: DecodeOwned>(
|
|||||||
key: &str,
|
key: &str,
|
||||||
db: &Database,
|
db: &Database,
|
||||||
) -> Result<D, RemoteAccessError> {
|
) -> Result<D, RemoteAccessError> {
|
||||||
let start = SystemTime::now();
|
|
||||||
let bytes = read_sync(&db.cache_dir, key).map_err(RemoteAccessError::Cache)?;
|
let bytes = read_sync(&db.cache_dir, key).map_err(RemoteAccessError::Cache)?;
|
||||||
let read = start.elapsed().unwrap();
|
|
||||||
let data =
|
let data =
|
||||||
bitcode::decode::<D>(&bytes).map_err(|e| RemoteAccessError::Cache(io::Error::other(e)))?;
|
bitcode::decode::<D>(&bytes).map_err(|e| RemoteAccessError::Cache(io::Error::other(e)))?;
|
||||||
let decode = start.elapsed().unwrap();
|
|
||||||
debug!(
|
|
||||||
"cache object took: r:{}, d:{}, b:{}",
|
|
||||||
read.as_millis(),
|
|
||||||
read.abs_diff(decode).as_millis(),
|
|
||||||
bytes.len()
|
|
||||||
);
|
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
#[derive(Encode, Decode)]
|
#[derive(Encode, Decode)]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2.0.0",
|
"$schema": "https://schema.tauri.app/config/2.0.0",
|
||||||
"productName": "Drop Desktop Client",
|
"productName": "Drop Desktop Client",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2-dl",
|
||||||
"identifier": "dev.drop.client",
|
"identifier": "dev.drop.client",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn --cwd main dev --port 1432",
|
"beforeDevCommand": "yarn --cwd main dev --port 1432",
|
||||||
|
|||||||
Reference in New Issue
Block a user