Skip to content

Commit 08bbaa0

Browse files
authored
Merge pull request #28 from piotrpdev/OKO-105-Admin-Camera-Settings
feat: OKO-105 Admin Camera Settings
2 parents 54bd24f + 0151a7a commit 08bbaa0

File tree

9 files changed

+288
-35
lines changed

9 files changed

+288
-35
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
INSERT INTO camera_settings (setting_id, camera_id, flashlight_enabled, resolution, framerate, last_modified, modified_by) VALUES
2-
(1, 1, false, '800x600', 5, '2024-10-21 17:02:33', 1),
3-
(2, 2, false, '800x600', 5, '2024-10-21 17:02:25', 2);
2+
(1, 1, false, 'SVGA', 5, '2024-10-21 17:02:33', 1),
3+
(2, 2, false, 'SVGA', 5, '2024-10-21 17:02:25', 2);

backend/src/db/camera_setting.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ mod tests {
187187
assert_eq!(returned_setting.setting_id, setting_id);
188188
assert_eq!(returned_setting.camera_id, 1);
189189
assert!(!returned_setting.flashlight_enabled);
190-
assert_eq!(returned_setting.resolution, "800x600");
190+
assert_eq!(returned_setting.resolution, "SVGA");
191191
assert_eq!(returned_setting.framerate, 5);
192192
assert_eq!(
193193
returned_setting.last_modified,

backend/src/web/app.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ async fn ws_handler(
259259
ws.on_upgrade(move |socket| handle_socket(socket, addr, state, auth_session))
260260
}
261261

262+
// ! Camera restart does not guarantee new recording, frames will keep going to the same video unless socket times out?
262263
// TODO: Find out if ECONNRESET after a while of no messages only affects vite dev server or if it is a general issue
263264
// ? Using tick await for empty tasks might not be the best idea
264265
/// Actual websocket statemachine (one will be spawned per connection)
@@ -369,11 +370,14 @@ async fn handle_socket(
369370
cameras = i_cameras;
370371
}
371372

373+
let initial_camera_settings_clone = initial_camera_settings.clone();
374+
372375
// ? Maybe use spawn_blocking here, be aware .abort() is not available on blocking tasks
373376
// ? Maybe assume is camera if IP belongs to camera in DB
374377
// TODO: Handle stopping recording properly
375378
// TODO: Inform client/db if recording fails
376379
// TODO: Find out which is better, ingesting encoded or decoded images
380+
// ! Camera restart does not guarantee new recording, frames will keep going to the same video unless socket times out?
377381
let mut recording_task: JoinHandle<Result<(), Box<dyn std::error::Error + Send + Sync>>> =
378382
if is_camera {
379383
// TODO: Check if errors are returned properly here, had some issues with the ? operator being silent
@@ -396,16 +400,38 @@ async fn handle_socket(
396400
// ? Maybe don't create video until first frame (or maybe doing this is actually a good approach)?
397401
video.create_using_self(&auth_session.backend.db).await?;
398402

403+
let (frame_width, frame_height, framerate) = match initial_camera_settings_clone {
404+
#[allow(clippy::match_same_arms)] // readability
405+
Some(settings) => {
406+
let (frame_width, frame_height) = match settings.resolution.as_str() {
407+
"SVGA" => (800, 600),
408+
"VGA" => (640, 480),
409+
_ => (800, 600),
410+
};
411+
412+
(frame_width, frame_height, settings.framerate)
413+
}
414+
None => (800, 600, 12),
415+
};
416+
399417
// TODO: Don't hardcode these
400418
let video_fourcc = VideoWriter::fourcc('m', 'p', '4', 'v')?;
401-
let video_size = Size::new(800, 600);
402-
let mut video_writer =
403-
VideoWriter::new_def(&video.file_path, video_fourcc, 12.5, video_size)?;
419+
let video_size = Size::new(frame_width, frame_height);
420+
// TODO: Investigate why video is too fast
421+
#[allow(clippy::cast_precision_loss)] // the precision loss is acceptable
422+
let mut video_writer = VideoWriter::new_def(
423+
&video.file_path,
424+
video_fourcc,
425+
framerate as f64,
426+
video_size,
427+
)?;
404428

405429
let mut total_bytes = 0;
406430

407431
let mut first_received = false;
408432
// TODO: Adding a sleep might be a good idea?
433+
// ! Camera restart does not guarantee new recording, frames will keep going to the same video unless socket times out?
434+
// TODO: Handle if user changes resolution/framerate during recording (this is likely to happen)
409435
loop {
410436
// TODO: this might not be the best way of doing this
411437
let message = (*images_rx_rec.borrow_and_update()).clone();

backend/src/web/protected.rs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ pub fn router(api_channel: watch::Sender<ApiChannelMessage>) -> Router<()> {
3636
"/api/settings/:setting_id",
3737
patch(self::patch::camera_settings),
3838
)
39+
.route(
40+
"/api/cameras/:camera_id/restart",
41+
post(self::post::camera_restart),
42+
)
3943
.with_state(api_channel)
4044
}
4145

@@ -221,10 +225,13 @@ mod get {
221225

222226
mod post {
223227
use super::{AuthSession, IntoResponse, StatusCode};
228+
use crate::ApiChannelMessage;
224229
use crate::{Camera, CameraPermission, CameraSetting, Model};
230+
use axum::extract::{Path, State};
225231
use axum::Form;
226232
use axum::Json;
227233
use serde::Deserialize;
234+
use tokio::sync::watch;
228235

229236
#[derive(Debug, Clone, Deserialize)]
230237
pub struct AddCameraForm {
@@ -260,7 +267,7 @@ mod post {
260267
setting_id: CameraSetting::DEFAULT.setting_id,
261268
camera_id: camera.camera_id,
262269
flashlight_enabled: CameraSetting::DEFAULT.flashlight_enabled,
263-
resolution: "800x600".to_string(),
270+
resolution: "SVGA".to_string(),
264271
framerate: 5,
265272
last_modified: CameraSetting::DEFAULT.last_modified(),
266273
modified_by: Some(user.user_id),
@@ -295,6 +302,32 @@ mod post {
295302
None => StatusCode::UNAUTHORIZED.into_response(),
296303
}
297304
}
305+
306+
pub async fn camera_restart(
307+
auth_session: AuthSession,
308+
Path(camera_id): Path<i64>,
309+
state: State<watch::Sender<ApiChannelMessage>>,
310+
) -> impl IntoResponse {
311+
match auth_session.user {
312+
Some(user) => {
313+
if user.username != "admin" {
314+
return StatusCode::FORBIDDEN.into_response();
315+
}
316+
317+
let api_message = ApiChannelMessage::CameraRelated {
318+
camera_id,
319+
message: crate::web::CameraMessage::Restart,
320+
};
321+
322+
if state.send(api_message).is_err() {
323+
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
324+
}
325+
326+
StatusCode::OK.into_response()
327+
}
328+
None => StatusCode::UNAUTHORIZED.into_response(),
329+
}
330+
}
298331
}
299332

300333
// TODO: Don't always return the same error
@@ -352,8 +385,8 @@ mod patch {
352385
#[derive(Debug, Clone, Deserialize)]
353386
pub struct UpdateSettingsForm {
354387
pub flashlight_enabled: bool,
355-
// pub resolution: String,
356-
// pub framerate: i64
388+
pub resolution: String,
389+
pub framerate: i64,
357390
}
358391

359392
pub async fn camera_settings(
@@ -384,10 +417,23 @@ mod patch {
384417
return StatusCode::FORBIDDEN.into_response();
385418
}
386419

387-
// TODO: Update resolution and framerate
420+
// TODO: resolution
388421
setting.flashlight_enabled = settings_form.flashlight_enabled;
389-
// setting.resolution = settings_form.resolution;
390-
// setting.framerate = settings_form.framerate;
422+
423+
// ? Maybe allow any framerate/resolution for admin but give warning
424+
if user.username == "admin" {
425+
if (settings_form.framerate < 1) || (settings_form.framerate > 60) {
426+
return StatusCode::BAD_REQUEST.into_response();
427+
}
428+
429+
if !["SVGA", "VGA"].contains(&settings_form.resolution.as_str()) {
430+
return StatusCode::BAD_REQUEST.into_response();
431+
}
432+
433+
setting.resolution = settings_form.resolution;
434+
setting.framerate = settings_form.framerate;
435+
}
436+
391437
setting.last_modified = CameraSetting::DEFAULT.last_modified();
392438
setting.modified_by = Some(user.user_id);
393439

backend/tests/web.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ async fn camera_setting_updates(
849849
assert_eq!(camera_setting.camera_id, 2);
850850
assert_eq!(camera_setting.setting_id, 2);
851851
assert!(!camera_setting.flashlight_enabled);
852-
assert_eq!(camera_setting.resolution, "800x600");
852+
assert_eq!(camera_setting.resolution, "SVGA");
853853
assert_eq!(camera_setting.framerate, 5);
854854
assert_eq!(camera_setting.modified_by, Some(2));
855855
assert_eq!(
@@ -898,7 +898,7 @@ async fn camera_setting_updates(
898898
assert_eq!(updated_camera_setting.camera_id, 2);
899899
assert_eq!(updated_camera_setting.setting_id, 2);
900900
assert!(updated_camera_setting.flashlight_enabled);
901-
assert_eq!(updated_camera_setting.resolution, "800x600");
901+
assert_eq!(updated_camera_setting.resolution, "SVGA");
902902
assert_eq!(updated_camera_setting.framerate, 5);
903903
assert_eq!(updated_camera_setting.modified_by, Some(1));
904904
assert!(updated_camera_setting.last_modified > camera_setting.last_modified);

0 commit comments

Comments
 (0)