Skip to content

Commit 4262364

Browse files
authored
provide md5 hash for self hosted instances (#1250)
* provide md5 hash for self hosted instances * fix md5 hash logic * remove explicit return
1 parent 0ccf2df commit 4262364

File tree

4 files changed

+67
-25
lines changed

4 files changed

+67
-25
lines changed

Cargo.lock

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

apps/desktop/src-tauri/src/api.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,29 @@ pub async fn upload_multipart_presign_part(
5252
video_id: &str,
5353
upload_id: &str,
5454
part_number: u32,
55+
md5_sum: Option<&str>,
5556
) -> Result<String, AuthedApiError> {
5657
#[derive(Deserialize)]
5758
#[serde(rename_all = "camelCase")]
5859
pub struct Response {
5960
presigned_url: String,
6061
}
6162

63+
let mut body = serde_json::Map::from_iter([
64+
("videoId".to_string(), json!(video_id)),
65+
("uploadId".to_string(), json!(upload_id)),
66+
("partNumber".to_string(), json!(part_number)),
67+
]);
68+
69+
if let Some(md5_sum) = md5_sum {
70+
body.insert("md5Sum".to_string(), json!(md5_sum));
71+
}
72+
6273
let resp = app
6374
.authed_api_request("/api/upload/multipart/presign-part", |c, url| {
6475
c.post(url)
6576
.header("Content-Type", "application/json")
66-
.json(&serde_json::json!({
67-
"videoId": video_id,
68-
"uploadId": upload_id,
69-
"partNumber": part_number,
70-
}))
77+
.json(&serde_json::json!(body))
7178
})
7279
.await
7380
.map_err(|err| format!("api/upload_multipart_presign_part/request: {err}"))?;

apps/desktop/src-tauri/src/upload.rs

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub struct UploadProgressEvent {
5656
// a typical recommended chunk size is 5MB (AWS min part size).
5757
const CHUNK_SIZE: u64 = 5 * 1024 * 1024; // 5MB
5858

59-
#[instrument(skip(app, channel))]
59+
#[instrument(skip(app, channel, file_path, screenshot_path))]
6060
pub async fn upload_video(
6161
app: &AppHandle,
6262
video_id: String,
@@ -593,44 +593,66 @@ fn multipart_uploader(
593593
let start = Instant::now();
594594

595595
try_stream! {
596-
let mut stream = pin!(stream);
596+
let use_md5_hashes = app.is_server_url_custom().await;
597+
598+
let mut stream = pin!(stream);
597599
let mut prev_part_number = None;
598600
let mut expected_part_number = 1u32;
599601

600602
loop {
601-
let (Some(item), presigned_url) = join(
602-
stream.next(),
603-
// We generate the presigned URL ahead of time for the part we expect to come next.
604-
// If it's not the chunk that actually comes next we just throw it out.
605-
// This means if the filesystem takes a while for the recording to reach previous total + CHUNK_SIZE, which is the common case, we aren't just doing nothing.
606-
api::upload_multipart_presign_part(&app, &video_id, &upload_id, expected_part_number)
607-
).await else {
608-
break;
609-
};
610-
let mut presigned_url = presigned_url?;
611-
612-
613-
let Chunk { total_size, part_number, chunk } = item.map_err(|err| format!("uploader/part/{:?}/fs: {err:?}", prev_part_number.map(|p| p + 1)))?;
603+
let (item, mut presigned_url, md5_sum) = if use_md5_hashes {
604+
let Some(item) = stream.next().await else {
605+
break;
606+
};
607+
let item = item.map_err(|err| format!("uploader/part/{:?}/fs: {err:?}", prev_part_number.map(|p| p + 1)))?;
608+
let md5_sum = base64::encode(md5::compute(&item.chunk).0);
609+
let presigned_url = api::upload_multipart_presign_part(&app, &video_id, &upload_id, expected_part_number, Some(
610+
&md5_sum
611+
)).await?;
612+
613+
(item, presigned_url, Some(md5_sum))
614+
} else {
615+
let (Some(item), presigned_url) = join(
616+
stream.next(),
617+
// We generate the presigned URL ahead of time for the part we expect to come next.
618+
// If it's not the chunk that actually comes next we just throw it out.
619+
// This means if the filesystem takes a while for the recording to reach previous total + CHUNK_SIZE, which is the common case, we aren't just doing nothing.
620+
api::upload_multipart_presign_part(&app, &video_id, &upload_id, expected_part_number, None)
621+
).await else {
622+
break;
623+
};
624+
625+
let item = item.map_err(|err| format!("uploader/part/{:?}/fs: {err:?}", prev_part_number.map(|p| p + 1)))?;
626+
627+
(item, presigned_url?, None)
628+
};
629+
630+
let Chunk { total_size, part_number, chunk } = item;
614631
trace!("Uploading chunk {part_number} ({} bytes) for video {video_id:?}", chunk.len());
615632
prev_part_number = Some(part_number);
616633
let size = chunk.len();
617634

618635
// We prefetched for the wrong chunk. Let's try again.
619636
if expected_part_number != part_number {
620-
presigned_url = api::upload_multipart_presign_part(&app, &video_id, &upload_id, part_number)
637+
presigned_url = api::upload_multipart_presign_part(&app, &video_id, &upload_id, part_number, md5_sum.as_deref())
621638
.await?
622639
}
623640

624641
trace!("Uploading part {part_number}");
625642

626643
let url = Uri::from_str(&presigned_url).map_err(|err| format!("uploader/part/{part_number}/invalid_url: {err:?}"))?;
627-
let resp = retryable_client(url.host().unwrap_or("<unknown>").to_string())
644+
let mut req = retryable_client(url.host().unwrap_or("<unknown>").to_string())
628645
.build()
629646
.map_err(|err| format!("uploader/part/{part_number}/client: {err:?}"))?
630647
.put(&presigned_url)
631648
.header("Content-Length", chunk.len())
632-
.timeout(Duration::from_secs(120))
633-
.body(chunk)
649+
.timeout(Duration::from_secs(120)).body(chunk);
650+
651+
if let Some(md5_sum) = &md5_sum {
652+
req = req.header("Content-MD5", md5_sum);
653+
}
654+
655+
let resp = req
634656
.send()
635657
.await
636658
.map_err(|err| format!("uploader/part/{part_number}/error: {err:?}"))?;

apps/desktop/src-tauri/src/web_api.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ pub trait ManagerExt<R: Runtime>: Manager<R> {
8484
) -> Result<reqwest::Response, reqwest::Error>;
8585

8686
async fn make_app_url(&self, pathname: impl AsRef<str>) -> String;
87+
88+
async fn is_server_url_custom(&self) -> bool;
8789
}
8890

8991
impl<T: Manager<R> + Emitter<R>, R: Runtime> ManagerExt<R> for T {
@@ -125,4 +127,15 @@ impl<T: Manager<R> + Emitter<R>, R: Runtime> ManagerExt<R> for T {
125127
let server_url = &app_state.read().await.server_url;
126128
format!("{}{}", server_url, pathname.as_ref())
127129
}
130+
131+
async fn is_server_url_custom(&self) -> bool {
132+
let mut state = self.state::<ArcLock<crate::App>>();
133+
let mut app_state = state.read().await;
134+
135+
if let Some(env_url) = std::option_env!("VITE_SERVER_URL") {
136+
return app_state.server_url != env_url;
137+
}
138+
139+
false
140+
}
128141
}

0 commit comments

Comments
 (0)