Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/desktop-gpui/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ pub fn sign_out() -> bool {
pub fn apply_desktop_headers(mut request: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
request = request
.header("X-Cap-Desktop-Version", env!("CARGO_PKG_VERSION"))
.header("X-Cap-Desktop-Features", "googleDriveUpload");
.header("X-Cap-Desktop-Features", "googleDriveUpload,gpui");
if let Ok(secret) = std::env::var("VITE_VERCEL_AUTOMATION_BYPASS_SECRET")
&& !secret.is_empty()
{
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-gpui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ fn main() {
updates::schedule_startup_check(cx);
app_windows::schedule_launch_render_warmup(cx);
upload::queue::init(cx);
upload::instant_cleanup::init(cx);

// The app menu (and with it ⌘W/⌘M/⌘Q) and the status-bar item. Both
// reach into the window registry, so they come after it -- and the menu
Expand Down
58 changes: 45 additions & 13 deletions apps/desktop-gpui/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub struct ActiveRecording {
start_gate: Option<cap_recording::RecordingStartGate>,
instant_upload: Option<SharedInstantUpload>,
instant_share_link: Option<String>,
instant_video_id: Option<String>,
#[cfg(target_os = "linux")]
instant_completion: Option<crate::upload::CompletionControl>,
#[cfg(target_os = "linux")]
Expand Down Expand Up @@ -820,6 +821,10 @@ impl ActiveRecording {
self.instant_share_link.as_deref()
}

pub(crate) fn instant_video_id(&self) -> Option<&str> {
self.instant_video_id.as_deref()
}

/// Stop and finalize. Returns the project directory.
///
/// Finalization mirrors the CLI's `finalize_completed`: studio projects get
Expand Down Expand Up @@ -1118,6 +1123,21 @@ impl ActiveRecording {
/// delete and restart flows. Deleting a directory this app just created is
/// app behavior, same as the Tauri delete button.
pub async fn cancel_and_delete(self) -> anyhow::Result<()> {
self.cancel_and_delete_with_query(None).await
}

/// `cancel_and_delete` for a start that was superseded before it began,
/// telling the server why its Instant video is deleted.
#[cfg(not(target_os = "linux"))]
pub(crate) async fn discard_cancelled_start(self) -> anyhow::Result<()> {
let query = crate::upload::instant_cleanup::delete_query(
crate::upload::instant_cleanup::CleanupReason::StartCancelled,
None,
);
self.cancel_and_delete_with_query(query).await
}

async fn cancel_and_delete_with_query(self, query: Option<String>) -> anyhow::Result<()> {
if let Some(gate) = &self.start_gate {
gate.arm();
}
Expand Down Expand Up @@ -1170,7 +1190,7 @@ impl ActiveRecording {
None => None,
};
let remote_result = if let Some(upload) = upload.as_mut() {
upload.cancel().await
upload.cancel(query.as_deref()).await
} else {
Ok(())
};
Expand Down Expand Up @@ -2117,15 +2137,15 @@ async fn start_internal(
.map(str::to_string);

let pre_created_video = if config.mode == RecordingMode::Instant {
Some(
crate::upload::prepare_instant_upload(
matches!(config.target, ScreenCaptureTarget::CameraOnly),
project_name,
organization_id,
)
.await
.map_err(anyhow::Error::msg)?,
let video = crate::upload::prepare_instant_upload(
matches!(config.target, ScreenCaptureTarget::CameraOnly),
project_name,
organization_id,
)
.await
.map_err(anyhow::Error::msg)?;
crate::upload::instant_cleanup::begin(&video.id, &project_dir);
Some(video)
} else {
None
};
Expand All @@ -2152,12 +2172,20 @@ async fn start_internal(
);
}
}
if result.is_err()
if let Err(start_error) = &result
&& let Some(video) = pre_created_video
&& let Err(error) = crate::upload::delete_instant_video(&video.id).await
{
tracing::error!(video_id = %video.id, "Failed to clean up instant recording: {error}");
return Err(anyhow!("Failed to clean up instant recording: {error}"));
use crate::upload::instant_cleanup::{self, CleanupReason};
let cause = instant_cleanup::classify_start_failure(&format!("{start_error:#}"));
let query = instant_cleanup::delete_query(CleanupReason::StartFailed, Some(cause));
match crate::upload::delete_instant_video_with_query(&video.id, query.as_deref()).await {
Ok(()) => instant_cleanup::resolved(&video.id),
Err(error) => {
instant_cleanup::abandon(&video.id, CleanupReason::StartFailed, Some(cause));
tracing::error!(video_id = %video.id, "Failed to clean up instant recording: {error}");
return Err(anyhow!("Failed to clean up instant recording: {error}"));
}
}
}
result
}
Expand Down Expand Up @@ -2451,6 +2479,9 @@ async fn start_attempt_with_upload(
let instant_share_link = instant_upload
.as_ref()
.map(|upload| upload.video().link.clone());
let instant_video_id = instant_upload
.as_ref()
.map(|upload| upload.video().id.clone());
let instant_upload =
instant_upload.map(|upload| Arc::new(tokio::sync::Mutex::new(Some(upload))));
Ok(ActiveRecording {
Expand All @@ -2459,6 +2490,7 @@ async fn start_attempt_with_upload(
start_gate: config.start_gate.clone(),
instant_upload,
instant_share_link,
instant_video_id,
#[cfg(target_os = "linux")]
instant_completion,
#[cfg(target_os = "linux")]
Expand Down
24 changes: 21 additions & 3 deletions apps/desktop-gpui/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,12 @@ impl RecordingSession {

fn activate(&mut self, active: ActiveRecording, cx: &mut Context<Self>) {
tracing::info!(dir = %active.project_dir.display(), "recording started");
if let Some(video_id) = active.instant_video_id().map(str::to_owned) {
cx.background_spawn(async move {
crate::upload::instant_cleanup::started(&video_id);
})
.detach();
}
let project_dir = active.project_dir.clone();
let done = active.done_fut();
self.active = Some(active);
Expand Down Expand Up @@ -945,16 +951,28 @@ impl RecordingSession {

fn discard_stale_start(&mut self, active: ActiveRecording, cx: &mut Context<Self>) {
let previous = self.discarding.take();
let video_id = active.instant_video_id().map(str::to_owned);
self.discarding = Some(gpui_tokio::Tokio::spawn(cx, async move {
use crate::upload::instant_cleanup::{self, CleanupReason};
if let Some(video_id) = &video_id {
instant_cleanup::abandon(video_id, CleanupReason::StartCancelled, None);
}
if let Some(previous) = previous {
let _ = previous.await;
}
#[cfg(target_os = "linux")]
let discarded = active.cancel_preserving().await;
#[cfg(not(target_os = "linux"))]
let discarded = active.cancel_and_delete().await;
if let Err(error) = discarded {
tracing::warn!(%error, "discarding a cancelled recording start failed");
let discarded = active.discard_cancelled_start().await;
match discarded {
Ok(()) => {
if let Some(video_id) = &video_id {
instant_cleanup::resolved(video_id);
}
}
Err(error) => {
tracing::warn!(%error, "discarding a cancelled recording start failed");
}
}
}));
}
Expand Down
16 changes: 12 additions & 4 deletions apps/desktop-gpui/src/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use serde_json::{Value, json};

use crate::auth::{self, AuthApiError};

pub(crate) mod instant_cleanup;
#[cfg(test)]
mod multipart_tests;
pub(crate) mod queue;
Expand Down Expand Up @@ -461,15 +462,15 @@ impl InstantUpload {
result.map_err(|error| format!("Instant recording thumbnail upload failed: {error}"))
}

pub async fn cancel(&mut self) -> Result<(), String> {
pub async fn cancel(&mut self, query: Option<&str>) -> Result<(), String> {
self.abort_segments().await;
if let Some(ownership) = &self.ownership {
queue::record_cancelled(ownership.project_path())?;
}
with_upload_context(
self.request_context.clone(),
self.reads.clone(),
delete_instant_video(&self.video.id),
delete_instant_video_with_query(&self.video.id, query),
)
.await
}
Expand All @@ -491,11 +492,18 @@ impl InstantUpload {
}
}

pub async fn delete_instant_video(video_id: &str) -> Result<(), String> {
let path = format!(
pub(crate) async fn delete_instant_video_with_query(
video_id: &str,
query: Option<&str>,
) -> Result<(), String> {
let mut path = format!(
"/api/desktop/video/delete?videoId={}",
urlencoding(video_id)
);
if let Some(query) = query {
path.push('&');
path.push_str(query);
}
let response = auth::authed_request(reqwest::Method::DELETE, &path, None)
.await
.map_err(|error| format!("Failed to delete instant recording: {error}"))?;
Expand Down
Loading
Loading