@@ -362,8 +362,15 @@ def next_app_logic(
362362 # pinned app is set as the app iname in the device object, we need to clear it.
363363 if getattr (device , "pinned_app" , None ) == app .iname :
364364 logger .info (f"Unpinning app { app .iname } because fail or empty render" )
365- device .pinned_app = ""
366- db .save_user (db_conn , user )
365+ try :
366+ with db .db_transaction (db_conn ) as cursor :
367+ db .update_device_field (
368+ cursor , user .username , device .id , "pinned_app" , ""
369+ )
370+ # Keep the in-memory object in sync
371+ device .pinned_app = ""
372+ except sqlite3 .Error as e :
373+ logger .error (f"Failed to unpin app for device { device .id } : { e } " )
367374
368375 # Pass next_index directly since it already points to the next app
369376 return next_app_logic (db_conn , user , device , next_index , recursion_depth + 1 )
@@ -376,9 +383,16 @@ def next_app_logic(
376383 if webp_path .exists () and webp_path .stat ().st_size > 0 :
377384 # App rendered successfully - display it and save the index
378385 response = send_image (webp_path , device , app )
379- # Save the next_index as the new last_app_index
380- device .last_app_index = next_index
381- db .save_user (db_conn , user )
386+ # Atomically save the next_index as the new last_app_index
387+ try :
388+ with db .db_transaction (db_conn ) as cursor :
389+ db .update_device_field (
390+ cursor , user .username , device .id , "last_app_index" , next_index
391+ )
392+ # Keep the in-memory object in sync for the current request
393+ device .last_app_index = next_index
394+ except sqlite3 .Error as e :
395+ logger .error (f"Failed to update last_app_index for device { device .id } : { e } " )
382396 return response
383397
384398 # WebP file doesn't exist or is empty - skip this app
@@ -578,8 +592,20 @@ async def update_brightness(
578592 content = "Brightness must be between 0 and 5" ,
579593 )
580594
581- device .brightness = db .ui_scale_to_percent (brightness )
582- db .save_user (db_conn , user )
595+ percent_brightness = db .ui_scale_to_percent (brightness )
596+ try :
597+ with db .db_transaction (db_conn ) as cursor :
598+ db .update_device_field (
599+ cursor , user .username , device .id , "brightness" , percent_brightness
600+ )
601+ device .brightness = percent_brightness # keep in-memory model updated
602+ except sqlite3 .Error as e :
603+ logger .error (f"Failed to update brightness for device { device .id } : { e } " )
604+ raise HTTPException (
605+ status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
606+ detail = "Database error" ,
607+ )
608+
583609 new_brightness_percent = db .get_device_brightness_percent (device )
584610
585611 # Send brightness command directly to active websocket connection (if any)
@@ -615,8 +641,18 @@ def update_interval(
615641 if not device :
616642 raise HTTPException (status_code = status .HTTP_404_NOT_FOUND )
617643
618- device .default_interval = interval
619- db .save_user (db_conn , user )
644+ try :
645+ with db .db_transaction (db_conn ) as cursor :
646+ db .update_device_field (
647+ cursor , user .username , device .id , "default_interval" , interval
648+ )
649+ device .default_interval = interval
650+ except sqlite3 .Error as e :
651+ logger .error (f"Failed to update interval for device { device .id } : { e } " )
652+ return Response (
653+ status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
654+ content = "Database error" ,
655+ )
620656 return Response (status_code = status .HTTP_200_OK )
621657
622658
@@ -1031,15 +1067,22 @@ def toggle_pin(
10311067 if iname not in device .apps :
10321068 return Response (status_code = status .HTTP_404_NOT_FOUND , content = "App not found" )
10331069
1034- # Check if this app is currently pinned
1035- if getattr (device , "pinned_app" , None ) == iname :
1036- device .pinned_app = ""
1037- flash (request , _ ("App unpinned." ))
1038- else :
1039- device .pinned_app = iname
1040- flash (request , _ ("App pinned." ))
1070+ new_pinned_app = "" if getattr (device , "pinned_app" , None ) == iname else iname
1071+
1072+ try :
1073+ with db .db_transaction (db_conn ) as cursor :
1074+ db .update_device_field (
1075+ cursor , user .username , device .id , "pinned_app" , new_pinned_app
1076+ )
1077+ device .pinned_app = new_pinned_app
1078+ if new_pinned_app :
1079+ flash (request , _ ("App pinned." ))
1080+ else :
1081+ flash (request , _ ("App unpinned." ))
1082+ except sqlite3 .Error as e :
1083+ logger .error (f"Failed to toggle pin for device { device .id } : { e } " )
1084+ flash (request , _ ("Error updating pin status." ), "error" )
10411085
1042- db .save_user (db_conn , user )
10431086 return RedirectResponse (url = "/" , status_code = status .HTTP_302_FOUND )
10441087
10451088
@@ -1296,9 +1339,23 @@ def toggle_enabled(
12961339 if not app :
12971340 return RedirectResponse (url = "/" , status_code = status .HTTP_404_NOT_FOUND )
12981341
1299- app .enabled = not app .enabled
1300- db .save_user (db_conn , user )
1301- flash (request , _ ("Changes saved." ))
1342+ new_enabled_status = not app .enabled
1343+ try :
1344+ with db .db_transaction (db_conn ) as cursor :
1345+ db .update_app_field (
1346+ cursor ,
1347+ user .username ,
1348+ device .id ,
1349+ app .iname ,
1350+ "enabled" ,
1351+ new_enabled_status ,
1352+ )
1353+ app .enabled = new_enabled_status
1354+ flash (request , _ ("Changes saved." ))
1355+ except sqlite3 .Error as e :
1356+ logger .error (f"Failed to toggle enabled for app { app .iname } : { e } " )
1357+ flash (request , _ ("Error saving changes." ), "error" )
1358+
13021359 return RedirectResponse (url = "/" , status_code = status .HTTP_302_FOUND )
13031360
13041361
@@ -2094,9 +2151,30 @@ def next_app(
20942151 user = deps .user
20952152 device = deps .device
20962153
2097- device .last_seen = datetime .now (timezone .utc )
2098- device .info .protocol_type = ProtocolType .HTTP
2099- db .save_user (db_conn , user )
2154+ now = datetime .now (timezone .utc )
2155+ try :
2156+ with db .db_transaction (db_conn ) as cursor :
2157+ db .update_device_field (
2158+ cursor ,
2159+ user .username ,
2160+ device .id ,
2161+ "last_seen" ,
2162+ now .isoformat (),
2163+ )
2164+ db .update_device_field (
2165+ cursor ,
2166+ user .username ,
2167+ device .id ,
2168+ "info.protocol_type" ,
2169+ ProtocolType .HTTP .value ,
2170+ )
2171+ # Keep in-memory object in sync
2172+ device .last_seen = now
2173+ device .info .protocol_type = ProtocolType .HTTP
2174+ except sqlite3 .Error as e :
2175+ logger .error (
2176+ f"Failed to update device { device .id } last_seen and protocol_type: { e } "
2177+ )
21002178
21012179 return next_app_logic (db_conn , user , device )
21022180
0 commit comments