Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ pub async fn start_recording(
match AuthStore::get(&app).ok().flatten() {
Some(_) => {
// Pre-create the video and get the shareable link
if let Ok(s3_config) = create_or_get_video(
let s3_config = create_or_get_video(
&app,
false,
None,
Expand All @@ -261,18 +261,19 @@ pub async fn start_recording(
None,
)
.await
{
let link = app.make_app_url(format!("/s/{}", s3_config.id())).await;
info!("Pre-created shareable link: {}", link);

Some(VideoUploadInfo {
id: s3_config.id().to_string(),
link: link.clone(),
config: s3_config,
})
} else {
None
}
.map_err(|err| {
error!("Error creating instant mode video: {err}");
err
})?;

let link = app.make_app_url(format!("/s/{}", s3_config.id())).await;
info!("Pre-created shareable link: {}", link);

Some(VideoUploadInfo {
id: s3_config.id().to_string(),
link: link.clone(),
config: s3_config,
})
}
// Allow the recording to proceed without error for any signed-in user
_ => {
Expand Down
136 changes: 107 additions & 29 deletions apps/desktop/src-tauri/src/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,45 +135,78 @@ impl UploadProgressUpdater {
}

pub fn update(&mut self, uploaded: u64, total: u64) {
// Safety checks for edge cases
if total == 0 && uploaded == 0 {
debug!("Skipping progress update with both uploaded and total as 0");
return;
}

let clamped_uploaded = uploaded.min(total);

debug!(
"Progress update: {}/{} bytes ({:.1}%)",
clamped_uploaded,
total,
if total > 0 {
(clamped_uploaded as f64 / total as f64) * 100.0
} else {
0.0
}
);

let should_send_immediately = {
let state = self.video_state.get_or_insert_with(|| VideoProgressState {
uploaded,
total,
pending_task: None,
last_update_time: Instant::now(),
let state = self.video_state.get_or_insert_with(|| {
debug!(
"Initializing progress state with {}/{} bytes",
clamped_uploaded, total
);
VideoProgressState {
uploaded: clamped_uploaded,
total,
pending_task: None,
last_update_time: Instant::now(),
}
});

// Cancel any pending task
if let Some(handle) = state.pending_task.take() {
handle.abort();
}

state.uploaded = uploaded;
// Only update if we have meaningful progress or completion
let has_meaningful_change = clamped_uploaded != state.uploaded || total != state.total;

if !has_meaningful_change {
debug!("No meaningful progress change, skipping update");
return;
}

state.uploaded = clamped_uploaded;
state.total = total;
state.last_update_time = Instant::now();

// Send immediately if upload is complete
uploaded >= total
clamped_uploaded >= total && total > 0
};

let app = self.app.clone();
if should_send_immediately {
tokio::spawn({
let video_id = self.video_id.clone();
async move {
Self::send_api_update(&app, video_id, uploaded, total).await;
Self::send_api_update(&app, video_id, clamped_uploaded, total).await;
}
});

// Clear state since upload is complete
self.video_state = None;
} else {
// Schedule delayed update
} else if total > 0 {
// Only schedule delayed update if we have a valid total
let handle = {
let video_id = self.video_id.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(2)).await;
Self::send_api_update(&app, video_id, uploaded, total).await;
Self::send_api_update(&app, video_id, clamped_uploaded, total).await;
})
};

Expand All @@ -184,26 +217,41 @@ impl UploadProgressUpdater {
}

async fn send_api_update(app: &AppHandle, video_id: String, uploaded: u64, total: u64) {
debug!("\tUploadProgressUpdater::send_api_update({video_id}, {uploaded}, {total})");

let response = app
.authed_api_request("/api/desktop/video/progress", |client, url| {
client
.post(url)
.header("X-Cap-Desktop-Version", env!("CARGO_PKG_VERSION"))
.json(&json!({
"videoId": video_id,
"uploaded": uploaded,
"total": total,
"updatedAt": chrono::Utc::now().to_rfc3339()
}))
client.post(url).json(&json!({
"videoId": video_id,
"uploaded": uploaded,
"total": total,
"updatedAt": chrono::Utc::now().to_rfc3339()
}))
})
.await;

match response {
Ok(resp) if resp.status().is_success() => {
trace!("Progress update sent successfully");
trace!(
"Progress update sent successfully: {}/{} bytes",
uploaded, total
);
}
Ok(resp) => error!("Failed to send progress update: {}", resp.status()),
Err(err) => error!("Failed to send progress update: {err}"),
Ok(resp) => {
error!(
"Failed to send progress update: {} - {}/{} bytes",
resp.status(),
uploaded,
total
);
if let Ok(body) = resp.text().await {
error!("Response body: {}", body);
}
}
Err(err) => error!(
"Failed to send progress update: {err} - {}/{} bytes",
uploaded, total
),
}
}
}
Expand Down Expand Up @@ -259,9 +307,14 @@ pub async fn upload_video(

if bytes_uploaded > 0 {
if let Some(channel) = &channel {
let progress_value = if total_size > 0 {
bytes_uploaded as f64 / total_size as f64
} else {
0.0
};
channel
.send(UploadProgress {
progress: bytes_uploaded as f64 / total_size as f64,
progress: progress_value,
})
.ok();
}
Expand Down Expand Up @@ -760,6 +813,17 @@ impl InstantMultipartUpload {
}
};

// Skip if file size is 0
if file_size == 0 {
if realtime_is_done.unwrap_or(false) {
error!("File size is 0 after recording completed");
return Err("File size is 0 after recording completed".to_string());
}
debug!("File size is still 0, waiting for recording to write data...");
sleep(Duration::from_millis(500)).await;
continue;
}

let new_data_size = file_size - last_uploaded_position;

if ((new_data_size >= CHUNK_SIZE)
Expand Down Expand Up @@ -857,6 +921,11 @@ impl InstantMultipartUpload {
Err(e) => return Err(format!("Failed to get file metadata: {e}")),
};

// Check if file size is 0
if file_size == 0 {
return Err("File size is 0, cannot upload".to_string());
}

// Check if we're at the end of the file
if *last_uploaded_position >= file_size {
return Err("No more data to read - already at end of file".to_string());
Expand All @@ -866,6 +935,7 @@ impl InstantMultipartUpload {
let remaining = file_size - *last_uploaded_position;
let bytes_to_read = std::cmp::min(chunk_size, remaining);

// TODO: Surely we can reuse this
let mut file = tokio::fs::File::open(file_path)
.await
.map_err(|e| format!("Failed to open file: {e}"))?;
Expand Down Expand Up @@ -925,6 +995,7 @@ impl InstantMultipartUpload {
);
}

// TODO: Shouldn't this be inferable?
let file_size = tokio::fs::metadata(file_path)
.await
.map(|m| m.len())
Expand Down Expand Up @@ -963,8 +1034,6 @@ impl InstantMultipartUpload {
}
};

progress.update(expected_pos, file_size);

if !presign_response.status().is_success() {
let status = presign_response.status();
let error_body = presign_response
Expand Down Expand Up @@ -1068,10 +1137,19 @@ impl InstantMultipartUpload {

// Advance the global progress
*last_uploaded_position += total_read as u64;
println!(
"After upload: new last_uploaded_position is {} ({}% of file)",
*last_uploaded_position,

// Update progress after successful upload
progress.update(*last_uploaded_position, file_size);

let progress_percent = if file_size > 0 {
(*last_uploaded_position as f64 / file_size as f64 * 100.0) as u32
} else {
0
};

println!(
"After upload: new last_uploaded_position is {} ({}% of file, {}/{} bytes)",
*last_uploaded_position, progress_percent, *last_uploaded_position, file_size
);

let part = UploadedPart {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/web_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async fn do_authed_request(
}
),
)
.header("X-Desktop-Version", env!("CARGO_PKG_VERSION"));
.header("X-Cap-Desktop-Version", env!("CARGO_PKG_VERSION"));

if let Some(s) = std::option_env!("VITE_VERCEL_AUTOMATION_BYPASS_SECRET") {
req = req.header("x-vercel-protection-bypass", s);
Expand Down
17 changes: 9 additions & 8 deletions apps/web/actions/video/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export async function createVideoAndGetUploadUrl({
isScreenshot = false,
isUpload = false,
folderId,
supportsUploadProgress = false,
}: {
videoId?: Video.VideoId;
duration?: number;
Expand All @@ -173,17 +174,16 @@ export async function createVideoAndGetUploadUrl({
isScreenshot?: boolean;
isUpload?: boolean;
folderId?: Folder.FolderId;
// TODO: Remove this once we are happy with it's stability
supportsUploadProgress?: boolean;
}) {
const user = await getCurrentUser();

if (!user) {
throw new Error("Unauthorized");
}
if (!user) throw new Error("Unauthorized");

try {
if (!userIsPro(user) && duration && duration > 300) {
if (!userIsPro(user) && duration && duration > 300)
throw new Error("upgrade_required");
}

const [customBucket] = await db()
.select()
Expand Down Expand Up @@ -237,9 +237,10 @@ export async function createVideoAndGetUploadUrl({

await db().insert(videos).values(videoData);

await db().insert(videoUploads).values({
videoId: idToUse,
});
if (supportsUploadProgress)
await db().insert(videoUploads).values({
videoId: idToUse,
});

const fileKey = `${user.id}/${idToUse}/${
isScreenshot ? "screenshot/screen-capture.jpg" : "result.mp4"
Expand Down
Loading
Loading