Skip to content

Commit 4c0a726

Browse files
committed
feat: health check and upload speed
1 parent 6371907 commit 4c0a726

File tree

16 files changed

+11214
-15742
lines changed

16 files changed

+11214
-15742
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/src-tauri/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ specta = "=2.0.0-rc.19"
6060
tauri-specta = { version = "=2.0.0-rc.14", features = ["derive", "typescript"] }
6161
specta-typescript = "0.0.6"
6262
dirs = "5.0.1"
63+
atomic_float = "1.0.0"
6364

6465
[target.'cfg(windows)'.dependencies]
6566
winapi = { version = "0.3.9", features = [

apps/desktop/src-tauri/src/app/commands.rs

+46
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
use std::sync::atomic::Ordering;
2+
13
use tauri::{Emitter, Manager, Window};
24
use tauri_plugin_oauth::start;
35

6+
use crate::{HEALTH_CHECK, UPLOAD_SPEED};
7+
48
#[tauri::command]
59
#[specta::specta]
610
pub async fn start_server(window: Window) -> Result<u16, String> {
@@ -112,3 +116,45 @@ pub fn make_webview_transparent(app_handle: tauri::AppHandle, label: String) ->
112116
#[cfg(not(target_os = "macos"))]
113117
"This command is only available on macOS."
114118
}
119+
120+
#[tauri::command]
121+
#[specta::specta]
122+
pub fn get_health_check_status() -> bool {
123+
let health = HEALTH_CHECK.load(Ordering::Relaxed);
124+
return health;
125+
}
126+
127+
#[tauri::command]
128+
#[specta::specta]
129+
pub fn get_upload_speed() -> f64 {
130+
let upload_speed = UPLOAD_SPEED.load(Ordering::Relaxed);
131+
return upload_speed;
132+
}
133+
134+
#[cfg(test)]
135+
mod tests {
136+
use super::{get_health_check_status, get_upload_speed, HEALTH_CHECK, UPLOAD_SPEED};
137+
use std::sync::atomic::Ordering;
138+
139+
#[test]
140+
fn test_get_health_check_status() {
141+
// example 1
142+
HEALTH_CHECK.store(true, Ordering::Relaxed);
143+
assert_eq!(get_health_check_status(), true);
144+
145+
// example 2
146+
HEALTH_CHECK.store(false, Ordering::Relaxed);
147+
assert_eq!(get_health_check_status(), false);
148+
}
149+
150+
#[test]
151+
fn test_get_upload_speed() {
152+
// example 1
153+
UPLOAD_SPEED.store(10.5, Ordering::Relaxed);
154+
assert_eq!(get_upload_speed(), 10.5);
155+
156+
// example 2
157+
UPLOAD_SPEED.store(20.7, Ordering::Relaxed);
158+
assert_eq!(get_upload_speed(), 20.7);
159+
}
160+
}

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

+51-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
22

3+
use reqwest::multipart::{Form, Part};
34
use sentry_tracing::EventFilter;
45
use specta_typescript::Typescript;
5-
use std::path::PathBuf;
6+
use std::sync::atomic::Ordering;
67
use std::sync::Arc;
8+
use std::time::Instant;
79
use std::vec;
10+
use std::{path::PathBuf, sync::atomic::AtomicBool};
11+
use atomic_float::AtomicF64;
12+
813
use tauri::{
914
tray::{MouseButton, MouseButtonState},
1015
Emitter, Manager,
@@ -34,6 +39,9 @@ use ffmpeg_sidecar::{
3439

3540
use winit::monitor::{MonitorHandle, VideoMode};
3641

42+
static UPLOAD_SPEED: AtomicF64 = AtomicF64::new(0.0);
43+
static HEALTH_CHECK: AtomicBool = AtomicBool::new(false);
44+
3745
fn main() {
3846
let _ = fix_path_env::fix();
3947

@@ -101,6 +109,37 @@ fn main() {
101109
Ok(())
102110
}
103111

112+
async fn perform_health_check_and_calculate_upload_speed() -> Result<(), Box<dyn std::error::Error>> {
113+
let client = reqwest::Client::new();
114+
let sample_screen_recording = vec![0u8; 1_000_000];
115+
116+
let health_check_url_base: &'static str = dotenvy_macro::dotenv!("NEXT_PUBLIC_URL");
117+
let health_check_url = format!("{}/api/health-check", health_check_url_base);
118+
119+
let form = Form::new().part(
120+
"file",
121+
Part::bytes(sample_screen_recording.clone())
122+
.file_name("sample_screen_recording.webm")
123+
.mime_str("video/webm")?,
124+
);
125+
let start_time = Instant::now();
126+
let resp = client.post(health_check_url).multipart(form).send().await?;
127+
let time_elapsed = start_time.elapsed();
128+
129+
let is_success = resp.status().is_success();
130+
HEALTH_CHECK.store(is_success, Ordering::Relaxed);
131+
132+
if is_success {
133+
let upload_speed = (sample_screen_recording.len() as f64 / time_elapsed.as_secs_f64()) / 1250000.0;
134+
UPLOAD_SPEED.store(upload_speed, Ordering::Relaxed);
135+
tracing::debug!("Health check successful. Upload speed: {} Mbps", upload_speed);
136+
} else {
137+
tracing::debug!("Health check failed.");
138+
}
139+
140+
Ok(())
141+
}
142+
104143
if let Err(error) = handle_ffmpeg_installation() {
105144
tracing::error!(error);
106145
// TODO: UI message instead
@@ -141,7 +180,9 @@ fn main() {
141180
reset_microphone_permissions,
142181
reset_camera_permissions,
143182
close_webview,
144-
make_webview_transparent
183+
make_webview_transparent,
184+
get_health_check_status,
185+
get_upload_speed
145186
]);
146187

147188
#[cfg(debug_assertions)] // <- Only export on non-release builds
@@ -156,6 +197,14 @@ fn main() {
156197
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
157198
.invoke_handler(specta_builder.invoke_handler())
158199
.setup(move |app| {
200+
tracing::info!("Setting up application...");
201+
202+
tauri::async_runtime::spawn(async {
203+
if let Err(error) = perform_health_check_and_calculate_upload_speed().await {
204+
tracing::error!("Health check and upload speed calculation failed: {}", error);
205+
}
206+
});
207+
159208
let handle = app.handle();
160209

161210
if let Some(main_window) = app.get_webview_window("main") {

apps/desktop/src-tauri/src/media/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use tracing::Level;
1616
use crate::{
1717
app::config,
1818
recording::RecordingOptions,
19-
utils::{create_named_pipe, ffmpeg_path_as_str},
19+
utils::{create_named_pipe, ffmpeg_path_as_str}, UPLOAD_SPEED,
2020
};
2121

2222
mod audio;
@@ -108,6 +108,7 @@ impl MediaRecorder {
108108
max_screen_width,
109109
max_screen_height,
110110
self.should_stop.clone(),
111+
VideoCapturer::get_dynamic_resolution(UPLOAD_SPEED.load(Ordering::Relaxed)),
111112
);
112113
let adjusted_width = video_capturer.frame_width;
113114
let adjusted_height = video_capturer.frame_height;

apps/desktop/src-tauri/src/media/video.rs

+20-3
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,20 @@ pub struct VideoCapturer {
2828
impl VideoCapturer {
2929
pub const FPS: u32 = 30;
3030

31-
pub fn new(_width: usize, _height: usize, should_stop: SharedFlag) -> VideoCapturer {
31+
pub fn new(
32+
_width: usize,
33+
_height: usize,
34+
should_stop: SharedFlag,
35+
resolution: Resolution,
36+
) -> VideoCapturer {
3237
let mut capturer = Capturer::new(Options {
3338
fps: Self::FPS,
3439
target: None,
3540
show_cursor: true,
3641
show_highlight: true,
3742
excluded_targets: None,
3843
output_type: FrameType::BGRAFrame,
39-
output_resolution: Resolution::Captured,
44+
output_resolution: resolution,
4045
crop_area: None,
4146
});
4247

@@ -229,4 +234,16 @@ impl VideoCapturer {
229234
let _ = pipe.sync_all().await;
230235
}
231236
}
232-
}
237+
238+
pub fn get_dynamic_resolution(upload_speed: f64) -> Resolution {
239+
match upload_speed {
240+
speed if speed >= 60.0 => Resolution::Captured,
241+
speed if speed >= 50.0 => Resolution::_4320p,
242+
speed if speed >= 25.0 => Resolution::_2160p,
243+
speed if speed >= 15.0 => Resolution::_1440p,
244+
speed if speed >= 8.0 => Resolution::_1080p,
245+
speed if speed >= 5.0 => Resolution::_720p,
246+
_ => Resolution::_480p,
247+
}
248+
}
249+
}

apps/desktop/src/app/page.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { authFetch } from "@/utils/auth/helpers";
1414
import { commands } from "@/utils/commands";
1515
import { setTrayMenu } from "@/utils/tray";
1616
import { useMediaDevices } from "@/utils/recording/MediaDeviceContext";
17+
import { UploadSpeed } from "@/components/upload-speed";
1718

1819
export const dynamic = "force-static";
1920

@@ -127,6 +128,7 @@ export default function CameraPage() {
127128
<div id="app" data-tauri-drag-region style={{ borderRadius: "16px" }}>
128129
<WindowActions />
129130
<Recorder />
131+
<UploadSpeed />
130132
</div>
131133
);
132134
}
@@ -149,6 +151,7 @@ export default function CameraPage() {
149151
) : (
150152
<Recorder />
151153
)}
154+
<UploadSpeed />
152155
</>
153156
) : (
154157
<SignIn />

apps/desktop/src/components/WindowActions.tsx

+21-17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Home } from "@/components/icons/Home";
44
import { openLinkInBrowser } from "@/utils/helpers";
5+
import { HealthCheckStatus } from "./health";
56

67
export const WindowActions = () => {
78
const actionButtonBase = "w-3 h-3 bg-gray-500 rounded-full m-0 p-0 block";
@@ -41,23 +42,26 @@ export const WindowActions = () => {
4142
<span className={actionButtonBase}></span>
4243
</div>
4344
</div>
44-
<div className="flex">
45-
<button
46-
onClick={async () => {
47-
if (window.fathom !== undefined) {
48-
window.fathom.trackEvent("home_clicked");
49-
}
50-
await openLinkInBrowser(
51-
`${process.env.NEXT_PUBLIC_URL}/dashboard`
52-
);
53-
}}
54-
className="p-1.5 bg-transparent hover:bg-gray-200 rounded-full transition-all"
55-
>
56-
<Home className="w-5 h-5" />
57-
</button>
58-
{/* <button className="p-1.5 bg-transparent hover:bg-gray-200 rounded-full transition-all">
59-
<Settings className="w-5 h-5" />
60-
</button> */}
45+
<div className="flex space-x-2">
46+
<HealthCheckStatus/>
47+
<div className="flex">
48+
<button
49+
onClick={async () => {
50+
if (window.fathom !== undefined) {
51+
window.fathom.trackEvent("home_clicked");
52+
}
53+
await openLinkInBrowser(
54+
`${process.env.NEXT_PUBLIC_URL}/dashboard`
55+
);
56+
}}
57+
className="p-1.5 bg-transparent hover:bg-gray-200 rounded-full transition-all"
58+
>
59+
<Home className="w-5 h-5" />
60+
</button>
61+
{/* <button className="p-1.5 bg-transparent hover:bg-gray-200 rounded-full transition-all">
62+
<Settings className="w-5 h-5" />
63+
</button> */}
64+
</div>
6165
</div>
6266
</div>
6367
</div>
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { useState } from 'react';
2+
import { useHealthCheck } from '../utils/hooks/useHealthCheck';
3+
import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogFooter } from '@cap/ui';
4+
5+
export const HealthCheckStatus: React.FC = () => {
6+
const { isHealthy, message } = useHealthCheck();
7+
const [showMessage, setShowMessage] = useState(false);
8+
9+
const handleClick = () => {
10+
setShowMessage(true);
11+
};
12+
13+
return (
14+
<div className="flex items-center space-x-2">
15+
<button
16+
onClick={handleClick}
17+
className={`w-4 h-4 rounded-full ${isHealthy ? 'bg-green-500' : 'bg-red-500'}`}
18+
aria-label="Health Check Status"
19+
title={isHealthy ? "System is healthy" : "System health issue detected"}
20+
/>
21+
<Dialog
22+
open={showMessage}
23+
onOpenChange={setShowMessage}
24+
>
25+
<DialogContent>
26+
<DialogTitle>Health Check Status</DialogTitle>
27+
<DialogDescription>
28+
{message}
29+
{!isHealthy && (
30+
<p className="text-sm mt-2">
31+
If you are still having an issue, please contact our
32+
<a href="/support" className="text-blue-500 hover:underline ml-1">support</a>.
33+
</p>
34+
)}
35+
</DialogDescription>
36+
<DialogFooter>
37+
<button
38+
onClick={() => setShowMessage(false)}
39+
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
40+
>
41+
Close
42+
</button>
43+
</DialogFooter>
44+
</DialogContent>
45+
</Dialog>
46+
</div>
47+
);
48+
};

0 commit comments

Comments
 (0)