Skip to content

Commit a8c196f

Browse files
authored
Merge pull request #1211 from tonymushah/995-experimental-upload-integration
Feat: finish upload implementation part 1
2 parents 17108e9 + 0831e09 commit a8c196f

File tree

74 files changed

+4568
-218
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+4568
-218
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ clap = "4"
3737
csv = "1"
3838
hotpath = { version = "0.7" }
3939
shrink_fit_wrapper = "1"
40+
rand = "0.9"
4041

4142
[workspace.dependencies.tauri-plugin-notification]
4243
git = "https://github.com/tauri-apps/plugins-workspace"

src-tauri/mangadex/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ serde-xml-rs.workspace = true
5858
csv.workspace = true
5959
hotpath = { workspace = true, optional = true }
6060
shrink_fit_wrapper.workspace = true
61+
rand.workspace = true
6162

6263
[dependencies.eureka-mmanager]
6364
version = "0.4"

src-tauri/mangadex/src/mutation/upload.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
pub mod inner_queue;
2+
13
use crate::error::wrapped::Result;
4+
use crate::mutation::upload::inner_queue::InternalSessionsMutations;
25
use crate::utils::traits_utils::{MangadexAsyncGraphQLContextExt, MangadexTauriManagerExt};
36
use async_graphql::{Context, Object};
47
use mangadex_api::{
@@ -41,6 +44,9 @@ pub struct UploadMutations;
4144
#[Object]
4245
#[cfg_attr(feature = "hotpath", hotpath::measure_all)]
4346
impl UploadMutations {
47+
pub async fn internal(&self) -> InternalSessionsMutations {
48+
InternalSessionsMutations
49+
}
4450
pub async fn begin_session(
4551
&self,
4652
ctx: &Context<'_>,
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use std::path::PathBuf;
2+
3+
use async_graphql::{Context, Object};
4+
use uuid::Uuid;
5+
6+
use crate::{
7+
ErrorWrapper,
8+
upload::InternUploadSessionCommitData,
9+
utils::traits_utils::{MangadexAsyncGraphQLContextExt, MangadexTauriManagerExt},
10+
};
11+
12+
pub struct InternalSessionsMutations;
13+
14+
#[Object]
15+
impl InternalSessionsMutations {
16+
pub async fn session(&self, id: Uuid) -> InternalSessionMutation {
17+
InternalSessionMutation(id)
18+
}
19+
/// Returns the internal session id
20+
pub async fn create_session(
21+
&self,
22+
ctx: &Context<'_>,
23+
manga_id: Uuid,
24+
groups: Option<Vec<Uuid>>,
25+
) -> Result<Uuid, ErrorWrapper> {
26+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
27+
let manager = app_handle.upload_manager();
28+
Ok(manager.create_session(manga_id, groups).await?)
29+
}
30+
pub async fn start_queue_runner(
31+
&self,
32+
ctx: &Context<'_>,
33+
) -> Result<Option<bool>, ErrorWrapper> {
34+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
35+
let manager = app_handle.upload_manager();
36+
manager.start_queue_runner().await;
37+
Ok(None)
38+
}
39+
pub async fn swap_queue_order(
40+
&self,
41+
ctx: &Context<'_>,
42+
a: Uuid,
43+
b: Uuid,
44+
) -> Result<Option<bool>, ErrorWrapper> {
45+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
46+
let manager = app_handle.upload_manager();
47+
manager.swap(a, b).await?;
48+
Ok(None)
49+
}
50+
}
51+
52+
pub struct InternalSessionMutation(Uuid);
53+
54+
#[Object]
55+
impl InternalSessionMutation {
56+
pub async fn send_in_queue(&self, ctx: &Context<'_>) -> Result<Option<bool>, ErrorWrapper> {
57+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
58+
let manager = app_handle.upload_manager();
59+
manager.send_session_in_queue(self.0).await?;
60+
Ok(None)
61+
}
62+
pub async fn remove(&self, ctx: &Context<'_>) -> Result<Option<bool>, ErrorWrapper> {
63+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
64+
let manager = app_handle.upload_manager();
65+
manager.remove_session(self.0).await?;
66+
Ok(None)
67+
}
68+
pub async fn add_file(
69+
&self,
70+
ctx: &Context<'_>,
71+
img_path: String,
72+
index: Option<u32>,
73+
) -> Result<Option<bool>, ErrorWrapper> {
74+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
75+
let manager = app_handle.upload_manager();
76+
manager
77+
.add_file_to_session(self.0, img_path.into(), index)
78+
.await?;
79+
Ok(None)
80+
}
81+
pub async fn add_files(
82+
&self,
83+
ctx: &Context<'_>,
84+
img_paths: Vec<String>,
85+
index: Option<u32>,
86+
) -> Result<Option<bool>, ErrorWrapper> {
87+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
88+
let manager = app_handle.upload_manager();
89+
manager
90+
.add_files_to_session(
91+
self.0,
92+
img_paths.into_iter().map(PathBuf::from).collect(),
93+
index,
94+
)
95+
.await?;
96+
Ok(None)
97+
}
98+
pub async fn remove_file(
99+
&self,
100+
ctx: &Context<'_>,
101+
img_path: String,
102+
) -> Result<Option<bool>, ErrorWrapper> {
103+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
104+
let manager = app_handle.upload_manager();
105+
manager.remove_file_from_session(self.0, img_path).await?;
106+
Ok(None)
107+
}
108+
pub async fn remove_files(
109+
&self,
110+
ctx: &Context<'_>,
111+
img_paths: Vec<String>,
112+
) -> Result<Option<bool>, ErrorWrapper> {
113+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
114+
let manager = app_handle.upload_manager();
115+
manager.remove_files_from_session(self.0, img_paths).await?;
116+
Ok(None)
117+
}
118+
pub async fn set_commit_data(
119+
&self,
120+
ctx: &Context<'_>,
121+
commit_data: Option<InternUploadSessionCommitData>,
122+
start_runner: Option<bool>,
123+
) -> Result<Option<bool>, ErrorWrapper> {
124+
let app_handle = ctx.get_app_handle::<tauri::Wry>()?;
125+
let manager = app_handle.upload_manager();
126+
if start_runner.unwrap_or_default() {
127+
manager
128+
.set_commit_data_and_send_to_queue(self.0, commit_data)
129+
.await?;
130+
} else {
131+
manager.set_commit_data(self.0, commit_data).await?;
132+
}
133+
Ok(None)
134+
}
135+
}

src-tauri/mangadex/src/scheme.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod chapters;
22
pub mod covers;
33
pub mod favicon;
4+
pub mod upload_image;
45

56
use std::error::Error;
67

@@ -124,6 +125,9 @@ fn handle<R: Runtime>(app: AppHandle<R>, req: Request<Vec<u8>>) -> Response<Vec<
124125
"chapter-cache" => {
125126
chapters::cache::handle_chapters_cache(&app, &req).into_response()
126127
}
128+
"upload-image" => {
129+
upload_image::handle_upload_image_req(&app, &req).into_response()
130+
}
127131
_ => not_found,
128132
}
129133
} else {

src-tauri/mangadex/src/scheme/chapters/offline.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
use eureka_mmanager::prelude::ChapterDataPullAsyncTrait;
66
use reqwest::header::{ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_LENGTH, CONTENT_TYPE};
77
use std::{
8-
io::{self, Write},
8+
io::{self, BufReader, Write},
99
ops::Deref,
1010
path::Path,
1111
};
@@ -53,7 +53,7 @@ impl<'a, R: Runtime> ChaptersHandlerOffline<'a, R> {
5353
.map_err(|_| not_found_chapter_image(self.chapter_id, &self.filename))?,
5454
}
5555
};
56-
io::copy(&mut file, &mut buf)?;
56+
io::copy(&mut BufReader::new(&mut file), &mut buf)?;
5757
buf.flush()?;
5858
Ok(buf)
5959
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use std::io::{self, BufReader};
2+
3+
use reqwest::header::{ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_LENGTH, CONTENT_TYPE};
4+
use tauri::{
5+
AppHandle, Runtime,
6+
http::{Request, StatusCode},
7+
};
8+
9+
use crate::utils::traits_utils::MangadexTauriManagerExt;
10+
11+
use super::{SchemeResponseError, SchemeResponseResult, parse_uri};
12+
use regex::Regex;
13+
use uuid::Uuid;
14+
15+
#[cfg_attr(feature = "hotpath", hotpath::measure)]
16+
fn not_found_upload_image(session_id: Uuid, filename: &str) -> SchemeResponseError {
17+
SchemeResponseError::NotFound(
18+
format!("the given internal session `{session_id}`/{filename} is not found").into_bytes(),
19+
)
20+
}
21+
22+
#[cfg_attr(feature = "hotpath", hotpath::measure)]
23+
fn get_upload_session_params(req: &Request<Vec<u8>>) -> SchemeResponseResult<(Uuid, String)> {
24+
let regex = Regex::new(
25+
r"(?x)/(?P<session_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/(?P<filename>\w*.*)",
26+
)?;
27+
let uri = parse_uri(req)?;
28+
let captures = regex
29+
.captures(uri.path())
30+
.ok_or(SchemeResponseError::InvalidURLInput)?;
31+
let chapter_id = captures
32+
.name("session_id")
33+
.and_then(|id| Uuid::parse_str(id.as_str()).ok())
34+
.ok_or(SchemeResponseError::InvalidURLInput)?;
35+
let filename = captures
36+
.name("filename")
37+
.map(|match_| match_.as_str().into())
38+
.ok_or(SchemeResponseError::InvalidURLInput)?;
39+
Ok((chapter_id, filename))
40+
}
41+
42+
pub fn handle_upload_image_req<'a, R: Runtime>(
43+
app: &'a AppHandle<R>,
44+
req: &'a Request<Vec<u8>>,
45+
) -> SchemeResponseResult<tauri::http::Response<Vec<u8>>> {
46+
let manager = app.upload_manager();
47+
let (id, filename) = get_upload_session_params(req)?;
48+
let res = tauri::async_runtime::block_on(async {
49+
manager
50+
.get_read_file_from_session(id, filename.clone())
51+
.await
52+
});
53+
// preallocating here
54+
// to make it more performant?
55+
let mut body: Vec<u8> = match &res {
56+
Ok(file) => file
57+
.metadata()
58+
.map(|m| {
59+
let cp: usize = m.len().try_into().unwrap_or(1_024);
60+
Vec::with_capacity(cp)
61+
})
62+
.unwrap_or(Vec::with_capacity(1_024)),
63+
_ => Vec::with_capacity(1_024),
64+
};
65+
match res {
66+
Ok(file) => {
67+
let mut buf_read = BufReader::new(file);
68+
io::copy(&mut buf_read, &mut body)
69+
.map_err(|e| SchemeResponseError::InternalError(Box::new(e)))?;
70+
}
71+
Err(crate::Error::Io(io)) if io.kind() == std::io::ErrorKind::NotFound => {
72+
return Err(not_found_upload_image(id, &filename));
73+
}
74+
Err(err) => {
75+
return Err(SchemeResponseError::InternalError(Box::new(err)));
76+
}
77+
}
78+
tauri::http::Response::builder()
79+
.status(StatusCode::OK)
80+
.header(CONTENT_TYPE, "image/*")
81+
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
82+
.header(CONTENT_LENGTH, format!("{}", body.len()).as_str())
83+
.body(body)
84+
.map_err(|e| SchemeResponseError::InternalError(Box::new(e)))
85+
}

src-tauri/mangadex/src/subscription.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ pub struct Subscriptions(
3434
chapter_pages::ChapterPagesSubscription,
3535
user_option_next::UserOptionNextSubscriptions,
3636
read_marker::ChapterReadMarkerSubscriptions,
37+
upload::internal::InternalUploadSubscriptions,
3738
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pub mod internal;
12
pub mod session;
23
pub mod session_file;

0 commit comments

Comments
 (0)