@camera.ui/server 2.0.4 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/dist/interface/assets/{404-B8P9h05I.js → 404-DRRPa9Hr.js} +1 -1
  2. package/dist/interface/assets/About-Db5MifHo.js +1 -0
  3. package/dist/interface/assets/{Adminpanel-BTIcUxWm.js → Adminpanel-ByDZ8Xpn.js} +1 -1
  4. package/dist/interface/assets/{Automation-BNbTehB5.js → Automation-BqJ8PyPa.js} +2 -2
  5. package/dist/interface/assets/{AutomationImport-BEm0hc0d.js → AutomationImport-BvuLSZcZ.js} +2 -2
  6. package/dist/interface/assets/{AutomationStore-qLsO1tHb.js → AutomationStore-Ca07W8uH.js} +2 -2
  7. package/dist/interface/assets/{AutomationStoreDetail-BAW2nfPd.js → AutomationStoreDetail-DmFvT2az.js} +2 -2
  8. package/dist/interface/assets/{Automations-CUCVYZSr.js → Automations-Dr8K-cDz.js} +2 -2
  9. package/dist/interface/assets/{Camera-eIsFEdVo.js → Camera-Bu1U8Oal.js} +2 -2
  10. package/dist/interface/assets/{CameraConsole-t6nY7h-V.js → CameraConsole-BcvP76iJ.js} +1 -1
  11. package/dist/interface/assets/{Cameras-Bswv6ojJ.js → Cameras-Dwr5fbzg.js} +2 -2
  12. package/dist/interface/assets/{Camview-DMywoy_4.js → Camview-F0vVBWeZ.js} +1 -1
  13. package/dist/interface/assets/Config-CIAVylkc.js +2 -0
  14. package/dist/interface/assets/{ConfigActionCameraControl-C0hvC4QW.js → ConfigActionCameraControl-C1dsQnYF.js} +1 -1
  15. package/dist/interface/assets/{ConfigActionDelay-Rnxk--Qt.js → ConfigActionDelay-DTjjadCG.js} +1 -1
  16. package/dist/interface/assets/{ConfigActionHttp-BF8vrrHR.js → ConfigActionHttp-IFqZaHDZ.js} +1 -1
  17. package/dist/interface/assets/{ConfigActionImageInput-C8_H3ubM.js → ConfigActionImageInput-DY8RQJ2h.js} +1 -1
  18. package/dist/interface/assets/{ConfigActionNotification-4VYN5Hkr.js → ConfigActionNotification-CJL8uNR-.js} +1 -1
  19. package/dist/interface/assets/{ConfigActionOutput-CxB3eLLl.js → ConfigActionOutput-Butmz0jI.js} +1 -1
  20. package/dist/interface/assets/{ConfigActionPlugin-DrPmBv9V.js → ConfigActionPlugin-D5oE8oXm.js} +1 -1
  21. package/dist/interface/assets/{ConfigActionSensor-CyaEZRfx.js → ConfigActionSensor-CJ-mk6eS.js} +1 -1
  22. package/dist/interface/assets/{ConfigActionSnapshot-l7VvnulO.js → ConfigActionSnapshot-DCCiYu-s.js} +1 -1
  23. package/dist/interface/assets/{ConfigActionVariable-DIlUMHDy.js → ConfigActionVariable-F28sicqK.js} +1 -1
  24. package/dist/interface/assets/{ConfigConditionIfElse-0XRDQcTw.js → ConfigConditionIfElse-BJhRgP7t.js} +1 -1
  25. package/dist/interface/assets/{ConfigConditionSensorState-BvygRJ79.js → ConfigConditionSensorState-B6MdgPfo.js} +1 -1
  26. package/dist/interface/assets/{ConfigConditionSwitch-C7pp1EZI.js → ConfigConditionSwitch-BKToCaFK.js} +1 -1
  27. package/dist/interface/assets/{ConfigConditionTime-B0zctDc4.js → ConfigConditionTime-ChXeDbZB.js} +1 -1
  28. package/dist/interface/assets/{ConfigTriggerDetection-CKw-fo2n.js → ConfigTriggerDetection-BWd3QqYf.js} +1 -1
  29. package/dist/interface/assets/{ConfigTriggerGeofence-DjBRl2tE.js → ConfigTriggerGeofence-BoT9t-8V.js} +1 -1
  30. package/dist/interface/assets/{ConfigTriggerManual-BpBTh3CS.js → ConfigTriggerManual-Bt62nmi8.js} +1 -1
  31. package/dist/interface/assets/{ConfigTriggerSchedule-DZG10NcA.js → ConfigTriggerSchedule-oBDIqAMo.js} +1 -1
  32. package/dist/interface/assets/{ConfigTriggerSensor-CpFa6nUj.js → ConfigTriggerSensor-BlTHlstZ.js} +1 -1
  33. package/dist/interface/assets/{ConfigTriggerSystem-BsQElCAL.js → ConfigTriggerSystem--nEg_iQ0.js} +1 -1
  34. package/dist/interface/assets/{ConfigTriggerWebhook-BKPOGAs3.js → ConfigTriggerWebhook-83rLUOjQ.js} +1 -1
  35. package/dist/interface/assets/{Console-DHlNpv-R.js → Console-2sHljg8h.js} +1 -1
  36. package/dist/interface/assets/{CuiActionButton-BSy6VaJ6.js → CuiActionButton-CNjL01gn.js} +1 -1
  37. package/dist/interface/assets/{CuiAvatar-8AhiddF1.js → CuiAvatar-BDT9c-UI.js} +1 -1
  38. package/dist/interface/assets/{CuiBBoxPlayground-BUPzZ0sI.js → CuiBBoxPlayground-CMnBcPwI.js} +1 -1
  39. package/dist/interface/assets/CuiBottomSheet-WuxFsOrw.js +1 -0
  40. package/dist/interface/assets/{CuiBottomSheet-BNCym-11.js → CuiBottomSheet-h5sjwTf4.js} +1 -1
  41. package/dist/interface/assets/{CuiCameraCard-BGAQxNHs.js → CuiCameraCard-Bip9OIZO.js} +1 -1
  42. package/dist/interface/assets/{CuiCameraPipCard-CUPWEo3r.js → CuiCameraPipCard-DElQvAB0.js} +1 -1
  43. package/dist/interface/assets/{CuiCameraRecordings-B25441dH.js → CuiCameraRecordings-Bf5IG7OM.js} +1 -1
  44. package/dist/interface/assets/{CuiCameraShares-oA3ockIQ.js → CuiCameraShares-Cw-Mffg8.js} +1 -1
  45. package/dist/interface/assets/{CuiCameraSnapshot-BLM8v7rY.js → CuiCameraSnapshot-BS-_U34M.js} +1 -1
  46. package/dist/interface/assets/{CuiCameraTable-3joRnfiM.js → CuiCameraTable-DY_unr4m.js} +1 -1
  47. package/dist/interface/assets/{CuiDataTable-DhnQRMXG.js → CuiDataTable-C5qi7zpz.js} +1 -1
  48. package/dist/interface/assets/{CuiFloatingButton-YIEmTfno.js → CuiFloatingButton-Pmur2pAJ.js} +1 -1
  49. package/dist/interface/assets/{CuiImage-CzYVSrkC.js → CuiImage-DVoYtgtl.js} +1 -1
  50. package/dist/interface/assets/{CuiMenu-CEaJl-MF.js → CuiMenu-B7AhQbgB.js} +1 -1
  51. package/dist/interface/assets/{CuiPluginCard-De_cbBBe.js → CuiPluginCard-B5PhaGoF.js} +2 -2
  52. package/dist/interface/assets/{CuiPluginCategoryChip-BwL2qCTM.js → CuiPluginCategoryChip-nEwIiKiK.js} +1 -1
  53. package/dist/interface/assets/{CuiPluginOAuthButton-BmBW2Pdf.js → CuiPluginOAuthButton-CJGsl-89.js} +2 -2
  54. package/dist/interface/assets/{CuiPluginTrustBadge-KcOasEJH.js → CuiPluginTrustBadge-DExKttfC.js} +1 -1
  55. package/dist/interface/assets/{CuiQRCode-BUSzb3ek.js → CuiQRCode-DkRZ5hfS.js} +1 -1
  56. package/dist/interface/assets/{CuiRecordingsGrid-DHSABYJb.js → CuiRecordingsGrid-mFXSsuG8.js} +1 -1
  57. package/dist/interface/assets/{CuiSchema-D35n-DE5.js → CuiSchema-BSv1nUun.js} +1 -1
  58. package/dist/interface/assets/{CuiTopNavbarItem-DVwItfbG.js → CuiTopNavbarItem-w21c3xtQ.js} +1 -1
  59. package/dist/interface/assets/{CuiTopbarSlot-h5_1cb9v.js → CuiTopbarSlot-CYPUqVPx.js} +1 -1
  60. package/dist/interface/assets/{Faces-BJlHL3gp.js → Faces-DLpVsIkY.js} +1 -1
  61. package/dist/interface/assets/{FirstSteps-D1Zf1CYN.js → FirstSteps-BDDy9x_6.js} +1 -1
  62. package/dist/interface/assets/{Instances-BNasTbK_.js → Instances-FeIAnJix.js} +1 -1
  63. package/dist/interface/assets/{Logs-CW5EqpAL.js → Logs-CIUyuGwj.js} +1 -1
  64. package/dist/interface/assets/{Menu-DTC3IWLc.js → Menu-Gqs2UiSW.js} +1 -1
  65. package/dist/interface/assets/{OAuthDeviceFlow-BOIY4kBt.js → OAuthDeviceFlow-hNJ_UrdG.js} +1 -1
  66. package/dist/interface/assets/{Plugin-DIPGTrNi.js → Plugin-BKItJas0.js} +1 -1
  67. package/dist/interface/assets/{PluginConsole-DwNRvKqW.js → PluginConsole-Bwd5Mktk.js} +1 -1
  68. package/dist/interface/assets/PluginDetail-BBh5y9qU.js +2 -0
  69. package/dist/interface/assets/{PluginSchema-DWnYqRll.js → PluginSchema-D92qcqf9.js} +1 -1
  70. package/dist/interface/assets/PluginSearch-BN3U3NY6.js +2 -0
  71. package/dist/interface/assets/PluginSearch-nHAFKMX0.js +1 -0
  72. package/dist/interface/assets/Plugins-gxyxkQ5i.js +1 -0
  73. package/dist/interface/assets/Recordings-B3Xw23vh.js +1 -0
  74. package/dist/interface/assets/{Settings-CfiNEl95.js → Settings-D2LHMohy2.js} +1 -1
  75. package/dist/interface/assets/SettingsAccount-DUN1oTTn.js +1 -0
  76. package/dist/interface/assets/{SettingsAccount-Ch2SR80X.js → SettingsAccount-b4eVxI3M.js} +1 -1
  77. package/dist/interface/assets/{SettingsAppearance-Ck4VcB-w.js → SettingsAppearance--Eidr-fz.js} +1 -1
  78. package/dist/interface/assets/SettingsAppearance-CLVMmnR_.js +1 -0
  79. package/dist/interface/assets/{SettingsBackup-BigThNuk.js → SettingsBackup-DUYVdKjD.js} +1 -1
  80. package/dist/interface/assets/{SettingsNotifications-XLBjwgwX.js → SettingsNotifications-xYOuLZsr.js} +1 -1
  81. package/dist/interface/assets/SettingsRecordings-BQDf4Zeg.js +1 -0
  82. package/dist/interface/assets/{SettingsRemote-B5jm7MdB.js → SettingsRemote-BXr1HLHm.js} +1 -1
  83. package/dist/interface/assets/SettingsSystem-DAjNwp4I.js +2 -0
  84. package/dist/interface/assets/{SettingsUsers-QuUercEC.js → SettingsUsers-DJ-eMMWe.js} +1 -1
  85. package/dist/interface/assets/{Terminal-BTfYdYuv.js → Terminal-P3yVGG8s.js} +1 -1
  86. package/dist/interface/assets/{VariableInput-DmE1m9Il.js → VariableInput-DdVw4SIR.js} +1 -1
  87. package/dist/interface/assets/{VariableSuggestions-eII-1Bd_.js → VariableSuggestions-SJPd5c8d.js} +1 -1
  88. package/dist/interface/assets/VersionsHandler-DcZ3RtHV.js +7 -0
  89. package/dist/interface/assets/{Workers-Bj_tLx-1.js → Workers-DWSJsRko.js} +1 -1
  90. package/dist/interface/assets/api-CKqAhmSC.css +2 -0
  91. package/dist/interface/assets/{api-CltBUyci.js → api-Cv3vt1pa.js} +1 -1
  92. package/dist/interface/assets/api-Ex2VUVD1.js +2 -0
  93. package/dist/interface/assets/{auth-BiWutL3Z.js → auth-W-JA6GOD.js} +1 -1
  94. package/dist/interface/assets/{automations-hsi5MCtT.js → automations-d_B5L3sO.js} +1 -1
  95. package/dist/interface/assets/{automations-73X55Wxy.js → automations-irBgx9Ew.js} +1 -1
  96. package/dist/interface/assets/{backup-CHJIDGvl.js → backup-BT-P0duG.js} +1 -1
  97. package/dist/interface/assets/{cameras-Crvf7J3t.js → cameras-DxjpuzW1.js} +1 -1
  98. package/dist/interface/assets/{cameras.schema-CQPF4BCK.js → cameras.schema-7ex1wBck.js} +1 -1
  99. package/dist/interface/assets/cloudHandoff-Cu1sRatW.js +1 -0
  100. package/dist/interface/assets/config-DwlSsbcP.js +1 -0
  101. package/dist/interface/assets/{deviceId-BJm8lmpp.js → deviceId-I24oaPe6.js} +1 -1
  102. package/dist/interface/assets/{hasPermission-LUHh3i2_.js → hasPermission-Cz0dkfyp.js} +1 -1
  103. package/dist/interface/assets/{instances-A-hAMJRC.js → instances-D6wHW9_0.js} +1 -1
  104. package/dist/interface/assets/main-C-WCGnJh.js +3 -0
  105. package/dist/interface/assets/{notifications-eX5p2YaS.js → notifications-DLNUbEKC.js} +1 -1
  106. package/dist/interface/assets/{plugins-DfuM5k5Q.js → plugins-D-tGC0mr.js} +1 -1
  107. package/dist/interface/assets/{round-restart-alt-rDMgoX7-.js → round-restart-alt-CaOjUTav.js} +1 -1
  108. package/dist/interface/assets/router-Cbt2STjH.js +2 -0
  109. package/dist/interface/assets/{routerState-DxipV4mY.js → routerState-_BXrxePY.js} +1 -1
  110. package/dist/interface/assets/{server-CoQ6jFok.js → server-ACfR-bOw.js} +1 -1
  111. package/dist/interface/assets/{shares-VWzmq1V-.js → shares-Ut3rTZUx.js} +1 -1
  112. package/dist/interface/assets/{theme-BzXEv5Er.js → theme-CDU91XW5.js} +1 -1
  113. package/dist/interface/assets/{types-CAW47AJe.js → types-Blh_1COV.js} +1 -1
  114. package/dist/interface/assets/{ui-Dg-QtLMl.js → ui-SZnjYe2F.js} +1 -1
  115. package/dist/interface/assets/useAppVersion-Bo_n0OVQ.js +1 -0
  116. package/dist/interface/assets/{useBiometricCredentials-Buylkybs.js → useBiometricCredentials-XyEQbPBo.js} +1 -1
  117. package/dist/interface/assets/{useCameraOptions-GAeXCTvO.js → useCameraOptions-C3-ZVV2A.js} +1 -1
  118. package/dist/interface/assets/{useCuiCameraDrawer-DwlhaZ2i.js → useCuiCameraDrawer-CebQFNWX.js} +1 -1
  119. package/dist/interface/assets/useCuiDialog-ClphnG79.js +2 -0
  120. package/dist/interface/assets/{useFileDownload-Bhdbhy_2.js → useFileDownload-CdDprX3v.js} +1 -1
  121. package/dist/interface/assets/{useLogsSocket-81hTPkwS.js → useLogsSocket-DKU0kKe_.js} +1 -1
  122. package/dist/interface/assets/{useServerRestart-3pT0h7CH.js → useServerRestart-CgCmTap7.js} +1 -1
  123. package/dist/interface/assets/{useSharedComposables-Ki01kFNJ.js → useSharedComposables-BcUR_c17.js} +1 -1
  124. package/dist/interface/assets/{useStreamStatus-CHITNYE7.js → useStreamStatus-BTa-XXZJ.js} +1 -1
  125. package/dist/interface/assets/useUpdateChannel-WznJBL9L.js +1 -0
  126. package/dist/interface/assets/{useUserOptions-CUvE4OL9.js → useUserOptions-DffxmIf7.js} +1 -1
  127. package/dist/interface/assets/{users.schema-DWL8-tZN.js → users.schema-BcwArBbH.js} +1 -1
  128. package/dist/interface/assets/{vendor-cameraui-DhctzD3V.js → vendor-cameraui-zHth8Pa5.js} +2 -2
  129. package/dist/interface/assets/{vendor-chart-BjXPY6za.js → vendor-chart-CTk1N5fJ.js} +1 -1
  130. package/dist/interface/assets/{vendor-core-DnC_SdtD.js → vendor-core-_UeC1YMQ.js} +1 -1
  131. package/dist/interface/assets/{vendor-primevue-OV61z06m.js → vendor-primevue-BXMgwPEu.js} +1 -1
  132. package/dist/interface/assets/{view-grid-_whxsqcn.js → view-grid-CX0RwdlE.js} +1 -1
  133. package/dist/interface/index.html +44 -43
  134. package/dist/interface/sw.js +1 -1
  135. package/dist/server/package.json +26 -16
  136. package/dist/server/requirements.txt +3 -2
  137. package/dist/server/src/_camera_ui_tools/camera_ui_common/common_utils/signal_handler.py +17 -9
  138. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/detection.py +10 -0
  139. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/internal/event_emitter.py +45 -0
  140. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/storage/__init__.py +47 -12
  141. package/dist/server/src/api/controllers/plugins.controller.js +11 -3
  142. package/dist/server/src/api/controllers/plugins.controller.js.map +1 -1
  143. package/dist/server/src/api/schemas/cameras.schema.d.ts +27 -0
  144. package/dist/server/src/api/schemas/cameras.schema.js +6 -0
  145. package/dist/server/src/api/schemas/cameras.schema.js.map +1 -1
  146. package/dist/server/src/api/schemas/config.schema.d.ts +2 -0
  147. package/dist/server/src/api/schemas/config.schema.js +1 -0
  148. package/dist/server/src/api/schemas/config.schema.js.map +1 -1
  149. package/dist/server/src/api/services/plugins.service.js +1 -1
  150. package/dist/server/src/api/services/plugins.service.js.map +1 -1
  151. package/dist/server/src/api.d.ts +1 -0
  152. package/dist/server/src/api.js +11 -3
  153. package/dist/server/src/api.js.map +1 -1
  154. package/dist/server/src/camera/decoder/ptz-autotracker.js +51 -78
  155. package/dist/server/src/camera/decoder/ptz-autotracker.js.map +1 -1
  156. package/dist/server/src/go2rtc/api/routes/streams.js +5 -6
  157. package/dist/server/src/go2rtc/api/routes/streams.js.map +1 -1
  158. package/dist/server/src/go2rtc/index.js +1 -0
  159. package/dist/server/src/go2rtc/index.js.map +1 -1
  160. package/dist/server/src/go2rtc/types.d.ts +3 -0
  161. package/dist/server/src/go2rtc/types.js.map +1 -1
  162. package/dist/server/src/main.js +1 -1
  163. package/dist/server/src/main.js.map +1 -1
  164. package/dist/server/src/manager/deviceManager.js +4 -1
  165. package/dist/server/src/manager/deviceManager.js.map +1 -1
  166. package/dist/server/src/manager/discoveryManager.d.ts +3 -2
  167. package/dist/server/src/manager/discoveryManager.js +58 -29
  168. package/dist/server/src/manager/discoveryManager.js.map +1 -1
  169. package/dist/server/src/plugins/config-store.d.ts +1 -1
  170. package/dist/server/src/plugins/config-store.js +17 -10
  171. package/dist/server/src/plugins/config-store.js.map +1 -1
  172. package/dist/server/src/plugins/index.d.ts +1 -0
  173. package/dist/server/src/plugins/index.js +38 -2
  174. package/dist/server/src/plugins/index.js.map +1 -1
  175. package/dist/server/src/plugins/runtime/base.js +10 -5
  176. package/dist/server/src/plugins/runtime/base.js.map +1 -1
  177. package/dist/server/src/plugins/runtime/go/index.js +1 -0
  178. package/dist/server/src/plugins/runtime/go/index.js.map +1 -1
  179. package/dist/server/src/plugins/runtime/node/child.d.ts +1 -0
  180. package/dist/server/src/plugins/runtime/node/child.js +62 -19
  181. package/dist/server/src/plugins/runtime/node/child.js.map +1 -1
  182. package/dist/server/src/plugins/runtime/node/configDb.d.ts +8 -0
  183. package/dist/server/src/plugins/runtime/node/configDb.js +15 -0
  184. package/dist/server/src/plugins/runtime/node/configDb.js.map +1 -1
  185. package/dist/server/src/plugins/runtime/node/pluginApi.d.ts +4 -0
  186. package/dist/server/src/plugins/runtime/node/pluginApi.js +13 -5
  187. package/dist/server/src/plugins/runtime/node/pluginApi.js.map +1 -1
  188. package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.d.ts +1 -2
  189. package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.js +2 -8
  190. package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.js.map +1 -1
  191. package/dist/server/src/plugins/runtime/node/proxy/coreManager.d.ts +3 -2
  192. package/dist/server/src/plugins/runtime/node/proxy/coreManager.js +9 -13
  193. package/dist/server/src/plugins/runtime/node/proxy/coreManager.js.map +1 -1
  194. package/dist/server/src/plugins/runtime/node/proxy/deviceManager.d.ts +3 -2
  195. package/dist/server/src/plugins/runtime/node/proxy/deviceManager.js +14 -12
  196. package/dist/server/src/plugins/runtime/node/proxy/deviceManager.js.map +1 -1
  197. package/dist/server/src/plugins/runtime/node/proxy/fileServe.js +4 -9
  198. package/dist/server/src/plugins/runtime/node/proxy/fileServe.js.map +1 -1
  199. package/dist/server/src/plugins/runtime/node/proxy/notificationManager.js +3 -0
  200. package/dist/server/src/plugins/runtime/node/proxy/notificationManager.js.map +1 -1
  201. package/dist/server/src/plugins/runtime/node/storage.d.ts +5 -2
  202. package/dist/server/src/plugins/runtime/node/storage.js +92 -67
  203. package/dist/server/src/plugins/runtime/node/storage.js.map +1 -1
  204. package/dist/server/src/plugins/runtime/node/storageController.d.ts +2 -0
  205. package/dist/server/src/plugins/runtime/node/storageController.js +12 -4
  206. package/dist/server/src/plugins/runtime/node/storageController.js.map +1 -1
  207. package/dist/server/src/plugins/runtime/python/child.py +47 -38
  208. package/dist/server/src/plugins/runtime/python/config_db.py +28 -17
  209. package/dist/server/src/plugins/runtime/python/index.js +1 -0
  210. package/dist/server/src/plugins/runtime/python/index.js.map +1 -1
  211. package/dist/server/src/plugins/runtime/python/plugin_api.py +5 -5
  212. package/dist/server/src/plugins/runtime/python/proxy/camera_device.py +14 -13
  213. package/dist/server/src/plugins/runtime/python/proxy/core_manager.py +11 -14
  214. package/dist/server/src/plugins/runtime/python/proxy/device_manager.py +23 -18
  215. package/dist/server/src/plugins/runtime/python/remote_urls.py +3 -7
  216. package/dist/server/src/plugins/runtime/python/storage.py +157 -126
  217. package/dist/server/src/plugins/runtime/python/storage_controller.py +28 -13
  218. package/dist/server/src/plugins/runtime/python/store.py +513 -0
  219. package/dist/server/src/plugins/runtime/python/store_migrate.py +59 -0
  220. package/dist/server/src/plugins/store/coalescingWriter.d.ts +7 -0
  221. package/dist/server/src/plugins/store/coalescingWriter.js +52 -0
  222. package/dist/server/src/plugins/store/coalescingWriter.js.map +1 -0
  223. package/dist/server/src/plugins/store/envelope.d.ts +5 -0
  224. package/dist/server/src/plugins/store/envelope.js +40 -0
  225. package/dist/server/src/plugins/store/envelope.js.map +1 -0
  226. package/dist/server/src/plugins/store/layout.d.ts +2 -0
  227. package/dist/server/src/plugins/store/layout.js +60 -0
  228. package/dist/server/src/plugins/store/layout.js.map +1 -0
  229. package/dist/server/src/plugins/store/location.d.ts +14 -0
  230. package/dist/server/src/plugins/store/location.js +55 -0
  231. package/dist/server/src/plugins/store/location.js.map +1 -0
  232. package/dist/server/src/plugins/store/migrate.d.ts +1 -0
  233. package/dist/server/src/plugins/store/migrate.js +22 -0
  234. package/dist/server/src/plugins/store/migrate.js.map +1 -0
  235. package/dist/server/src/plugins/store/pluginStoreFile.d.ts +14 -0
  236. package/dist/server/src/plugins/store/pluginStoreFile.js +58 -0
  237. package/dist/server/src/plugins/store/pluginStoreFile.js.map +1 -0
  238. package/dist/server/src/plugins/store/storeFile.d.ts +5 -0
  239. package/dist/server/src/plugins/store/storeFile.js +112 -0
  240. package/dist/server/src/plugins/store/storeFile.js.map +1 -0
  241. package/dist/server/src/plugins/store/validate.d.ts +1 -0
  242. package/dist/server/src/plugins/store/validate.js +49 -0
  243. package/dist/server/src/plugins/store/validate.js.map +1 -0
  244. package/dist/server/src/plugins/worker.js +9 -8
  245. package/dist/server/src/plugins/worker.js.map +1 -1
  246. package/dist/server/src/remote/services/cloudflare-managed.js +2 -2
  247. package/dist/server/src/remote/services/cloudflare-managed.js.map +1 -1
  248. package/dist/server/src/remote/services/cloudflare.js +1 -0
  249. package/dist/server/src/remote/services/cloudflare.js.map +1 -1
  250. package/dist/server/src/remote/tunnel/client.js +1 -0
  251. package/dist/server/src/remote/tunnel/client.js.map +1 -1
  252. package/dist/server/src/rpc/server.js +12 -5
  253. package/dist/server/src/rpc/server.js.map +1 -1
  254. package/dist/server/src/services/config/defaults.js +1 -0
  255. package/dist/server/src/services/config/defaults.js.map +1 -1
  256. package/dist/server/src/services/config/types.d.ts +1 -0
  257. package/dist/server/src/utils/npm/index.d.ts +1 -1
  258. package/dist/server/src/utils/npm/index.js +7 -4
  259. package/dist/server/src/utils/npm/index.js.map +1 -1
  260. package/dist/server/src/workers/capabilities/plugin-host.js +1 -1
  261. package/dist/server/src/workers/capabilities/plugin-host.js.map +1 -1
  262. package/package.json +26 -16
  263. package/dist/interface/assets/About-CjQs3rZw.js +0 -1
  264. package/dist/interface/assets/Config-C7H5amm4.js +0 -2
  265. package/dist/interface/assets/CuiBottomSheet-ChG_av5z.js +0 -1
  266. package/dist/interface/assets/PluginDetail-D1TJZJhY.js +0 -2
  267. package/dist/interface/assets/PluginSearch-C5bkQgYA.js +0 -1
  268. package/dist/interface/assets/PluginSearch-DqwgO886.js +0 -2
  269. package/dist/interface/assets/Plugins-o7IfSFjd.js +0 -1
  270. package/dist/interface/assets/Recordings-DTw5IG8W.js +0 -1
  271. package/dist/interface/assets/SettingsAccount-DIwWUyp6.js +0 -1
  272. package/dist/interface/assets/SettingsAppearance-CkF7biyP.js +0 -1
  273. package/dist/interface/assets/SettingsRecordings-Dp9tJdHk.js +0 -1
  274. package/dist/interface/assets/SettingsSystem-Isy5hiDl.js +0 -2
  275. package/dist/interface/assets/VersionsHandler-BYj7-lpw.js +0 -7
  276. package/dist/interface/assets/api-D7tGlI-z.css +0 -2
  277. package/dist/interface/assets/api-DoPgGqHU.js +0 -2
  278. package/dist/interface/assets/cloudHandoff-lLBGqrXl.js +0 -1
  279. package/dist/interface/assets/main-DEbfQmxF.js +0 -3
  280. package/dist/interface/assets/router-D1e_o7wa.js +0 -2
  281. package/dist/interface/assets/useCuiDialog-yRn5B1oO.js +0 -2
@@ -1,19 +1,19 @@
1
+ from __future__ import annotations
2
+
1
3
  import truststore
2
4
 
3
5
  truststore.inject_into_ssl()
4
6
 
5
7
  import asyncio
6
8
  import importlib.util
7
- import json
8
9
  import os
9
10
  import sys
10
11
  from contextlib import suppress
11
12
  from pathlib import Path
12
13
  from types import ModuleType
13
- from typing import Any, Literal, cast
14
+ from typing import Any, cast
14
15
 
15
16
  import setproctitle
16
- from lmdbm import Lmdb
17
17
 
18
18
  from _camera_ui_tools.camera_ui_common import (
19
19
  LoggerOptions,
@@ -23,11 +23,18 @@ from _camera_ui_tools.camera_ui_common import (
23
23
  from _camera_ui_tools.camera_ui_rpc import CloseHandler, RPCClient, create_rpc_client
24
24
  from _camera_ui_tools.camera_ui_sdk import API_EVENT, BasePlugin, Camera
25
25
  from _camera_ui_tools.camera_ui_sdk import CameraDevice as CameraDeviceInterface
26
- from plugins.runtime.python.config_db import PluginConfigDb, PluginConfigStoreRPC, RemotePluginConfigDb
26
+ from plugins.runtime.python.config_db import (
27
+ LocalPluginConfigDb,
28
+ PluginConfigDb,
29
+ PluginConfigStoreRPC,
30
+ RemotePluginConfigDb,
31
+ )
27
32
  from plugins.runtime.python.namespaces import NamespaceManager
28
33
  from plugins.runtime.python.plugin_api import PluginAPI
29
34
  from plugins.runtime.python.proxy.camera_device import CameraDeviceProxy
30
35
  from plugins.runtime.python.proxy.file_serve import PluginFileServer
36
+ from plugins.runtime.python.store import STORE_FILE_NAME, PluginStoreFile
37
+ from plugins.runtime.python.store_migrate import migrate_legacy_lmdbm, needs_lmdbm_migration
31
38
  from plugins.runtime.python.typings import (
32
39
  PLUGIN_COMMAND,
33
40
  PLUGIN_STATUS,
@@ -40,20 +47,7 @@ from plugins.runtime.python.typings import (
40
47
  process_name = sys.argv[2] if len(sys.argv) > 2 else sys.argv[1] if len(sys.argv) > 1 else "Plugin"
41
48
  setproctitle.setproctitle(f"camera.ui - {process_name}")
42
49
 
43
-
44
- class JsonLmdb(Lmdb[Literal["config"], dict[str, Any]]):
45
- def _pre_key(self, key: Literal["config"]) -> bytes:
46
- return key.encode("utf-8")
47
-
48
- def _post_key(self, key: bytes) -> Literal["config"]:
49
- _key: Any = key.decode("utf-8")
50
- return _key
51
-
52
- def _pre_value(self, value: dict[str, Any]) -> bytes:
53
- return json.dumps(value).encode("utf-8")
54
-
55
- def _post_value(self, value: bytes) -> dict[str, Any]:
56
- return json.loads(value.decode("utf-8"))
50
+ RPC_TEARDOWN_TIMEOUT = 0.5
57
51
 
58
52
 
59
53
  class PluginChild:
@@ -98,7 +92,7 @@ class PluginChild:
98
92
  self.signal_handler = SignalHandler(
99
93
  {
100
94
  "display_name": "[Signal]",
101
- "timeout_duration": 3,
95
+ "timeout_duration": 2,
102
96
  "logger": self.logger,
103
97
  "close_function": self.stop_plugin,
104
98
  }
@@ -125,7 +119,7 @@ class PluginChild:
125
119
 
126
120
  async def start_plugin(self, data: ProcessLoadMessage) -> None:
127
121
  try:
128
- # Host-local writable dir - on a remote worker the master's path from
122
+ # Host-local writable dir on a remote worker the master's path from
129
123
  # the START message would point at the wrong machine.
130
124
  storage_path = os.environ.get("PLUGIN_STORAGE_PATH") or data["storage"]["storagePath"]
131
125
  plugin_info = data["plugin"]
@@ -145,7 +139,6 @@ class PluginChild:
145
139
  plugin_storage = await self.api.storage_controller.createStorage("plugin")
146
140
  self.plugin = cast(BasePlugin, plugin_constructor(self.logger, self.api, plugin_storage))
147
141
 
148
- # Apply storage_schema if defined by plugin
149
142
  if hasattr(self.plugin, "storage_schema") and self.plugin.storage_schema:
150
143
  plugin_storage.defineSchemas(self.plugin.storage_schema)
151
144
 
@@ -154,11 +147,12 @@ class PluginChild:
154
147
  namespaces.plugin_child_rpc, self.plugin, without_decorators=True
155
148
  )
156
149
 
150
+ # Remote-hosted: serve this worker's files so the master can stream
151
+ # downloads/exports of them.
157
152
  if os.environ.get("PLUGIN_REMOTE_MODE"):
158
153
  self.file_server = PluginFileServer(self.proxy, plugin_info["id"])
159
154
  await self.file_server.register()
160
155
 
161
- # Set the plugin instance on device_manager for lifecycle callbacks
162
156
  self.api.device_manager.set_plugin(self.plugin)
163
157
 
164
158
  await self.api.device_manager.init()
@@ -180,15 +174,31 @@ class PluginChild:
180
174
  self.stopped = True
181
175
 
182
176
  if self.api:
183
- self.api.emit(API_EVENT.SHUTDOWN.value)
184
- await asyncio.sleep(0.5)
177
+ completed = await self.api.emit_and_wait(
178
+ API_EVENT.SHUTDOWN.value,
179
+ timeout=1.0,
180
+ on_error=lambda e: self.logger.error(f"Shutdown listener failed: {e}"),
181
+ )
182
+ if not completed:
183
+ self.logger.warn("Shutdown listeners still pending after 1s, continuing teardown")
184
+
185
+ await self.api.device_manager.close()
186
+ await self.api.core_manager.close()
187
+ await self.api.storage_controller.close()
188
+
185
189
  self.api.cancel()
186
190
 
187
191
  if self.plugin_db is not None:
188
192
  with suppress(Exception):
189
- self.plugin_db.close()
193
+ await self.plugin_db.close()
190
194
  self.plugin_db = None
191
195
 
196
+ try:
197
+ await asyncio.wait_for(self._close_rpc(), timeout=RPC_TEARDOWN_TIMEOUT)
198
+ except TimeoutError:
199
+ self.logger.warn(f"RPC teardown still pending after {RPC_TEARDOWN_TIMEOUT}s, exiting anyway")
200
+
201
+ async def _close_rpc(self) -> None:
192
202
  if self.channel:
193
203
  with suppress(Exception):
194
204
  await self.channel.close()
@@ -202,7 +212,8 @@ class PluginChild:
202
212
  with suppress(Exception):
203
213
  await self.close_proxy()
204
214
 
205
- await self.proxy.disconnect()
215
+ with suppress(Exception):
216
+ await self.proxy.disconnect()
206
217
 
207
218
  async def send_message(self, message: ProcessResponse) -> None:
208
219
  await self.channel.send(message)
@@ -224,25 +235,24 @@ class PluginChild:
224
235
  pass
225
236
 
226
237
  async def configure_plugin_db(self, storage_path: str, plugin_id: str) -> PluginConfigDb:
227
- # Remote-hosted: config lives on the master (re-homing safe) - persist
238
+ # Remote-hosted: config lives on the master (re-homing safe) persist
228
239
  # through its config store instead of a worker-local file.
229
240
  if os.environ.get("PLUGIN_CONFIG_STORE_RPC"):
230
241
  namespaces = NamespaceManager.plugin_namespaces(plugin_id)
231
- store = self.proxy.create_proxy(namespaces.plugin_config_store_rpc, PluginConfigStoreRPC)
232
- remote_db = RemotePluginConfigDb(store)
242
+ store_rpc = self.proxy.create_proxy(namespaces.plugin_config_store_rpc, PluginConfigStoreRPC)
243
+ remote_db = RemotePluginConfigDb(store_rpc)
233
244
  await remote_db.init()
234
245
  return remote_db
235
246
 
236
- db_path = Path(storage_path) / "volume"
237
- Path(db_path).mkdir(parents=True, exist_ok=True)
238
-
239
- plugin_db = cast(Lmdb[Literal["config"], dict[str, Any]], JsonLmdb.open(file=str(db_path), flag="c"))
240
- config = cast(dict[str, Any] | None, plugin_db.get("config"))
247
+ volume_dir = Path(storage_path) / "volume"
248
+ store_path = volume_dir / STORE_FILE_NAME
241
249
 
242
- if config is None:
243
- plugin_db.update({"config": {}})
250
+ if await asyncio.to_thread(needs_lmdbm_migration, volume_dir, store_path):
251
+ await migrate_legacy_lmdbm(volume_dir, store_path, self.logger)
244
252
 
245
- return plugin_db
253
+ store = PluginStoreFile(str(volume_dir), plugin_id, self.logger)
254
+ await store.open()
255
+ return LocalPluginConfigDb(store)
246
256
 
247
257
  async def configure_cameras(self, api: PluginAPI, plugin_info: PluginInfo, cameras: list[Camera]) -> None:
248
258
  cameras_devices: list[CameraDeviceProxy] = []
@@ -254,7 +264,6 @@ class PluginChild:
254
264
  cameras_devices.append(
255
265
  CameraDeviceProxy(
256
266
  self.proxy,
257
- api,
258
267
  api.storage_controller,
259
268
  camera,
260
269
  plugin_info,
@@ -1,5 +1,9 @@
1
- import asyncio
2
- from typing import Any, Literal, Protocol
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Literal, Protocol
4
+
5
+ if TYPE_CHECKING:
6
+ from plugins.runtime.python.store import PluginStoreFile
3
7
 
4
8
 
5
9
  class PluginConfigStoreRPC(Protocol):
@@ -9,11 +13,25 @@ class PluginConfigStoreRPC(Protocol):
9
13
 
10
14
 
11
15
  class PluginConfigDb(Protocol):
12
- def get(self, key: Literal["config"]) -> dict[str, Any] | None: ...
16
+ def get(self, key: Literal["config"], /) -> dict[str, Any] | None: ...
17
+
18
+ async def put(self, key: Literal["config"], value: dict[str, Any], /) -> None: ...
13
19
 
14
- def update(self, mapping: dict[Literal["config"], dict[str, Any]], /) -> None: ...
20
+ async def close(self) -> None: ...
15
21
 
16
- def close(self) -> None: ...
22
+
23
+ class LocalPluginConfigDb:
24
+ def __init__(self, store: PluginStoreFile) -> None:
25
+ self._store = store
26
+
27
+ def get(self, key: Literal["config"], /) -> dict[str, Any]:
28
+ return self._store.get()
29
+
30
+ async def put(self, key: Literal["config"], value: dict[str, Any], /) -> None:
31
+ await self._store.put(value)
32
+
33
+ async def close(self) -> None:
34
+ await self._store.close()
17
35
 
18
36
 
19
37
  class RemotePluginConfigDb:
@@ -24,19 +42,12 @@ class RemotePluginConfigDb:
24
42
  async def init(self) -> None:
25
43
  self._cache = (await self._store.get()) or {}
26
44
 
27
- def get(self, _key: Literal["config"]) -> dict[str, Any]:
45
+ def get(self, key: Literal["config"], /) -> dict[str, Any]:
28
46
  return self._cache
29
47
 
30
- def update(self, mapping: dict[Literal["config"], dict[str, Any]], /) -> None:
31
- config = mapping["config"]
32
- self._cache = config
33
- # Lmdb-compatible sync signature — persistence is fire-and-forget; the
34
- # cache is already current and errors only cost durability of this write.
35
- task = asyncio.create_task(self._persist(dict(config)))
36
- task.add_done_callback(lambda t: t.exception())
48
+ async def put(self, key: Literal["config"], value: dict[str, Any], /) -> None:
49
+ self._cache = value
50
+ await self._store.put(value)
37
51
 
38
- def close(self) -> None:
52
+ async def close(self) -> None:
39
53
  return None
40
-
41
- async def _persist(self, config: dict[str, Any]) -> None:
42
- await self._store.put(config)
@@ -29,6 +29,7 @@ export class PythonPluginRuntime extends BasePluginRuntime {
29
29
  ...this.getEnv(),
30
30
  },
31
31
  cwd: this.plugin.mainPath,
32
+ windowsHide: true,
32
33
  });
33
34
  this.worker.on('spawn', this.onSpawn.bind(this, this.worker.pid, command, args, processNames, resolve));
34
35
  this.worker.on('error', this.onError.bind(this, reject));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../src/plugins/runtime/python/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../src/plugins/runtime/python/index.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,4 @@
1
- from typing import Any, Literal, cast
2
-
3
- from plugins.runtime.python.config_db import PluginConfigDb
1
+ from typing import Any, cast
4
2
 
5
3
  from _camera_ui_tools.camera_ui_common import LoggerService
6
4
  from _camera_ui_tools.camera_ui_rpc import RPCClient
@@ -14,6 +12,7 @@ from _camera_ui_tools.camera_ui_sdk import (
14
12
  )
15
13
  from _camera_ui_tools.camera_ui_sdk import PluginAPI as PluginAPIInterface
16
14
  from _camera_ui_tools.camera_ui_sdk.internal import AsyncEventEmitter
15
+ from plugins.runtime.python.config_db import PluginConfigDb
17
16
  from plugins.runtime.python.proxy.core_manager import CoreManagerProxy
18
17
  from plugins.runtime.python.proxy.device_manager import DeviceManagerProxy
19
18
  from plugins.runtime.python.proxy.download_manager import DownloadManagerProxy
@@ -33,10 +32,11 @@ class PluginAPI(AsyncEventEmitter, PluginAPIInterface):
33
32
  ) -> None:
34
33
  super().__init__()
35
34
 
35
+ self.logger = logger
36
36
  self.storage_path = storage_path
37
37
  self.storage_controller = StorageController(self, proxy, plugin, plugin_db)
38
- self.core_manager = CoreManagerProxy(proxy, self, logger, plugin)
39
- self.device_manager = DeviceManagerProxy(proxy, self, self.storage_controller, logger, plugin)
38
+ self.core_manager = CoreManagerProxy(proxy, logger, plugin)
39
+ self.device_manager = DeviceManagerProxy(proxy, self.storage_controller, logger, plugin)
40
40
  self.download_manager = DownloadManagerProxy(proxy)
41
41
  self.notification_manager = NotificationManagerProxy(proxy, plugin)
42
42
 
@@ -9,7 +9,6 @@ from deepdiff.diff import DeepDiff
9
9
  from _camera_ui_tools.camera_ui_common import Subscribed, TaskSet
10
10
  from _camera_ui_tools.camera_ui_rpc import CloseHandler, RPCClient
11
11
  from _camera_ui_tools.camera_ui_sdk import (
12
- API_EVENT,
13
12
  BehaviorSubject,
14
13
  Camera,
15
14
  CameraDetectionSettings,
@@ -53,7 +52,6 @@ from _camera_ui_tools.camera_ui_sdk import (
53
52
  from _camera_ui_tools.camera_ui_sdk import CameraDevice as CameraDeviceInterface
54
53
  from _camera_ui_tools.camera_ui_sdk.internal import SensorJSON
55
54
  from plugins.runtime.python.camera.utils import build_snapshot_url, build_target_url
56
- from plugins.runtime.python.remote_urls import rewrite_source_urls_for_remote
57
55
  from plugins.runtime.python.namespaces import (
58
56
  CameraNamespaces,
59
57
  FrameWorkerDetectionNamespaces,
@@ -67,6 +65,7 @@ from plugins.runtime.python.proxy.sensor import (
67
65
  SensorControllerInterface,
68
66
  SensorProxy,
69
67
  )
68
+ from plugins.runtime.python.remote_urls import rewrite_source_urls_for_remote
70
69
  from plugins.runtime.python.typings import (
71
70
  CameraEventMessage,
72
71
  PluginInfo,
@@ -78,7 +77,6 @@ from plugins.runtime.python.typings import (
78
77
  )
79
78
 
80
79
  if TYPE_CHECKING:
81
- from plugins.runtime.python.plugin_api import PluginAPI
82
80
  from plugins.runtime.python.storage_controller import StorageController
83
81
 
84
82
 
@@ -99,7 +97,10 @@ class CameraControllerInterface(Protocol):
99
97
  async def disconnect(self) -> None: ...
100
98
  async def snapshot(self, source_id: str, force_new: bool | None = None) -> bytes | None: ...
101
99
  async def probeStream(
102
- self, source_id: str, probe_config: ProbeConfig | None = None, refresh: bool | None = False
100
+ self,
101
+ source_id: str,
102
+ probe_config: ProbeConfig | None = None,
103
+ refresh: bool | None = False,
103
104
  ) -> ProbeStream | None: ...
104
105
  async def refreshStates(self) -> dict[str, Any]: ...
105
106
  async def registerSensor(self, sensor: SensorJSON, plugin_id: str) -> bool: ...
@@ -144,7 +145,6 @@ class CameraDeviceProxy(Subscribed, CameraDeviceInterface):
144
145
  def __init__(
145
146
  self,
146
147
  proxy: RPCClient,
147
- api: PluginAPI,
148
148
  storage_controller: StorageController,
149
149
  camera: Camera,
150
150
  plugin: PluginInfo,
@@ -163,7 +163,6 @@ class CameraDeviceProxy(Subscribed, CameraDeviceInterface):
163
163
  )
164
164
 
165
165
  self._logger = logger
166
- self._api: PluginAPI = api
167
166
  self._proxy = proxy
168
167
  self._plugin = plugin
169
168
  self._contract: PluginContract = plugin["contract"]
@@ -199,8 +198,6 @@ class CameraDeviceProxy(Subscribed, CameraDeviceInterface):
199
198
  NamespaceManager.frame_worker_detection_namespaces(self.id),
200
199
  )
201
200
 
202
- self._api.once(API_EVENT.SHUTDOWN.value, self.cleanup)
203
-
204
201
  @property
205
202
  def _cameraObject(self) -> Camera:
206
203
  return deepcopy(self._camera_subject.value)
@@ -345,7 +342,7 @@ class CameraDeviceProxy(Subscribed, CameraDeviceInterface):
345
342
  return self._proxy.create_proxy(self._namespaces[4].detection_rpc)
346
343
 
347
344
  def getSourceById(self, id: str) -> CameraDeviceSource | None:
348
- return next((s for s in self.sources if s._id == id), None)
345
+ return next((s for s in self.sources if s._id == id), None) # pyright: ignore[reportPrivateUsage]
349
346
 
350
347
  def _can_access_sensor(self, sensor: StoredSensorData) -> bool:
351
348
  # Own sensors are always accessible
@@ -609,7 +606,10 @@ class CameraDeviceProxy(Subscribed, CameraDeviceInterface):
609
606
  return await self._camera_controller_proxy.snapshot(source_id, force_new)
610
607
 
611
608
  async def _probe_stream(
612
- self, source_id: str, probe_config: ProbeConfig | None = None, refresh: bool | None = False
609
+ self,
610
+ source_id: str,
611
+ probe_config: ProbeConfig | None = None,
612
+ refresh: bool | None = False,
613
613
  ) -> ProbeStream | None:
614
614
  return await self._camera_controller_proxy.probeStream(source_id, probe_config, refresh)
615
615
 
@@ -624,8 +624,6 @@ class CameraDeviceProxy(Subscribed, CameraDeviceInterface):
624
624
 
625
625
  self.unsubscribe()
626
626
 
627
- self._api.removeListener(API_EVENT.SHUTDOWN.value, self.cleanup)
628
-
629
627
  self._tasks.remove_all()
630
628
 
631
629
  if self._close_subscription:
@@ -772,7 +770,10 @@ class CameraDeviceProxy(Subscribed, CameraDeviceInterface):
772
770
  self._sensors.pop(removed_data["sensorId"], None)
773
771
 
774
772
  self._sensor_removed_subject.next(
775
- {"sensorId": removed_data["sensorId"], "sensorType": removed_data["sensorType"]}
773
+ {
774
+ "sensorId": removed_data["sensorId"],
775
+ "sensorType": removed_data["sensorType"],
776
+ }
776
777
  )
777
778
 
778
779
  elif event_type == "sensor:assignment:changed":
@@ -2,12 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import os
5
- from typing import TYPE_CHECKING, Any, TypedDict
5
+ from typing import Any, TypedDict
6
6
 
7
7
  from _camera_ui_tools.camera_ui_common import LoggerService
8
8
  from _camera_ui_tools.camera_ui_rpc import CloseHandler, RPCClient
9
9
  from _camera_ui_tools.camera_ui_sdk import (
10
- API_EVENT,
11
10
  BasePlugin,
12
11
  CoreManager,
13
12
  CoreManagerEvent,
@@ -15,13 +14,14 @@ from _camera_ui_tools.camera_ui_sdk import (
15
14
  PluginInterface,
16
15
  Subject,
17
16
  )
18
- from plugins.runtime.python.namespaces import CoreManagerNamespaces, NamespaceManager, PluginNamespaces
17
+ from plugins.runtime.python.namespaces import (
18
+ CoreManagerNamespaces,
19
+ NamespaceManager,
20
+ PluginNamespaces,
21
+ )
19
22
  from plugins.runtime.python.rpc.typings import CoreManagerInterface
20
23
  from plugins.runtime.python.typings import PluginInfo
21
24
 
22
- if TYPE_CHECKING:
23
- from plugins.runtime.python.plugin_api import PluginAPI
24
-
25
25
 
26
26
  class RPCConnection(TypedDict):
27
27
  proxy: BasePlugin
@@ -32,13 +32,11 @@ class CoreManagerProxy(CoreManager):
32
32
  def __init__(
33
33
  self,
34
34
  proxy: RPCClient,
35
- api: PluginAPI,
36
35
  logger: LoggerService,
37
36
  plugin: PluginInfo,
38
37
  ):
39
38
  self.__initialized = False
40
39
 
41
- self.__api: PluginAPI = api
42
40
  self.__proxy = proxy
43
41
  self.__logger = logger
44
42
  self.__plugin = plugin
@@ -52,8 +50,6 @@ class CoreManagerProxy(CoreManager):
52
50
  self.__rpc_connections: dict[str, RPCConnection] = {}
53
51
  self.__event_subject: Subject[CoreManagerEvent] = Subject()
54
52
 
55
- self.__api.once(API_EVENT.SHUTDOWN.value, self.__close)
56
-
57
53
  @property
58
54
  def __core_manager_proxy(self) -> CoreManagerInterface:
59
55
  return self.__proxy.create_proxy(
@@ -112,6 +108,11 @@ class CoreManagerProxy(CoreManager):
112
108
  async def getPluginsByInterface(self, interfaceName: PluginInterface) -> list[PluginInfo]:
113
109
  return await self.__core_manager_proxy.getPluginsByInterface(interfaceName)
114
110
 
111
+ async def close(self) -> None:
112
+ """Internal method to close the core manager proxy."""
113
+ self.__initialized = False
114
+ self.__event_subject.complete()
115
+
115
116
  async def __on_event_message(self, event: dict[str, Any]) -> None:
116
117
  event_type = event.get("type")
117
118
  if not event_type:
@@ -119,10 +120,6 @@ class CoreManagerProxy(CoreManager):
119
120
  data = event.get("data")
120
121
  self.__event_subject.next({"type": event_type, "data": data})
121
122
 
122
- async def __close(self) -> None:
123
- self.__initialized = False
124
- self.__event_subject.complete()
125
- self.__api.removeListener(API_EVENT.SHUTDOWN.value, self.__close)
126
123
  if self.__close_subscription:
127
124
  await self.__close_subscription()
128
125
  self.__close_subscription = None
@@ -1,14 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- from typing import TYPE_CHECKING, Any
4
+ from typing import Any
5
5
 
6
6
  from _camera_ui_tools.camera_ui_common import (
7
7
  LoggerService,
8
8
  )
9
9
  from _camera_ui_tools.camera_ui_rpc import CloseHandler, RPCClient
10
10
  from _camera_ui_tools.camera_ui_sdk import (
11
- API_EVENT,
12
11
  BasePlugin,
13
12
  Camera,
14
13
  DeviceManager,
@@ -20,21 +19,20 @@ from plugins.runtime.python.namespaces import (
20
19
  NamespaceManager,
21
20
  PluginNamespaces,
22
21
  )
23
- from plugins.runtime.python.rpc.typings import DeviceManagerInterface, DiscoveryManagerInterface
22
+ from plugins.runtime.python.rpc.typings import (
23
+ DeviceManagerInterface,
24
+ DiscoveryManagerInterface,
25
+ )
24
26
  from plugins.runtime.python.storage_controller import StorageController
25
27
  from plugins.runtime.python.typings import PluginInfo
26
28
 
27
29
  from .camera_device import CameraDeviceProxy
28
30
 
29
- if TYPE_CHECKING:
30
- from plugins.runtime.python.plugin_api import PluginAPI
31
-
32
31
 
33
32
  class DeviceManagerProxy(DeviceManager):
34
33
  def __init__(
35
34
  self,
36
35
  proxy: RPCClient,
37
- api: PluginAPI,
38
36
  storage_controller: StorageController,
39
37
  logger: LoggerService,
40
38
  plugin: PluginInfo,
@@ -42,7 +40,6 @@ class DeviceManagerProxy(DeviceManager):
42
40
  self.__initialized = False
43
41
  self.__plugin_instance: BasePlugin | None = None
44
42
 
45
- self.__api: PluginAPI = api
46
43
  self.__proxy = proxy
47
44
  self.__storage_controller = storage_controller
48
45
  self.__logger = logger
@@ -56,8 +53,6 @@ class DeviceManagerProxy(DeviceManager):
56
53
  NamespaceManager.discovery_manager_namespaces(),
57
54
  )
58
55
 
59
- self.__api.once(API_EVENT.SHUTDOWN.value, self.__close)
60
-
61
56
  @property
62
57
  def __device_manager_proxy(self) -> DeviceManagerInterface:
63
58
  return self.__proxy.create_proxy(self.__namespaces[0].device_manager_rpc, DeviceManagerInterface)
@@ -97,6 +92,16 @@ class DeviceManagerProxy(DeviceManager):
97
92
  async def configureCameras(self, camera_devices: list[CameraDeviceProxy]) -> None:
98
93
  await asyncio.gather(*[self.__get_camera_device(camera_device) for camera_device in camera_devices])
99
94
 
95
+ async def close(self) -> None:
96
+ """Internal method to close the device manager proxy and cleanup resources."""
97
+ self.__initialized = False
98
+ if self.__close_request:
99
+ await self.__close_request()
100
+
101
+ for device in self.__devices.values():
102
+ await device.cleanup()
103
+ self.__devices.clear()
104
+
100
105
  async def __on_event_message(self, event: Any) -> None:
101
106
  if not self.__plugin_instance:
102
107
  self.__logger.warn("Plugin instance not set, cannot handle lifecycle event")
@@ -107,6 +112,9 @@ class DeviceManagerProxy(DeviceManager):
107
112
 
108
113
  if event_type == "cameraAdded":
109
114
  camera: Camera = data.get("camera")
115
+ if camera["_id"] in self.__devices:
116
+ return
117
+
110
118
  camera_device = await self.__get_camera_device(camera)
111
119
 
112
120
  if camera_device:
@@ -153,11 +161,14 @@ class DeviceManagerProxy(DeviceManager):
153
161
  camera_device = self.__devices[camera["_id"]]
154
162
  else:
155
163
  camera_logger = self.__logger.create_logger(
156
- {"suffix": camera["name"], "target_id": camera["_id"], "target_type": "camera"}
164
+ {
165
+ "suffix": camera["name"],
166
+ "target_id": camera["_id"],
167
+ "target_type": "camera",
168
+ }
157
169
  )
158
170
  camera_device = CameraDeviceProxy(
159
171
  self.__proxy,
160
- self.__api,
161
172
  self.__storage_controller,
162
173
  camera,
163
174
  self.__plugin,
@@ -177,9 +188,3 @@ class DeviceManagerProxy(DeviceManager):
177
188
 
178
189
  async def __remove_camera_storage(self, camera_id: str) -> None:
179
190
  await self.__storage_controller.removeStorage("camera", camera_id)
180
-
181
- async def __close(self) -> None:
182
- self.__initialized = False
183
- self.__api.removeListener(API_EVENT.SHUTDOWN.value, self.__close)
184
- if self.__close_request:
185
- await self.__close_request()
@@ -8,11 +8,7 @@ _LOCALHOST = re.compile(r"127\.0\.0\.1|localhost")
8
8
  def _rewrite_url(url: str, master: str, user: str | None, password: str | None) -> str:
9
9
  rewritten = _LOCALHOST.sub(master, url)
10
10
 
11
- if (
12
- user
13
- and rewritten.startswith("rtsp://")
14
- and "@" not in rewritten[7:].split("/")[0]
15
- ):
11
+ if user and rewritten.startswith("rtsp://") and "@" not in rewritten[7:].split("/")[0]:
16
12
  rewritten = rewritten.replace("rtsp://", f"rtsp://{user}:{password or ''}@", 1)
17
13
 
18
14
  return rewritten
@@ -22,9 +18,9 @@ def _walk(value: Any, master: str, user: str | None, password: str | None) -> An
22
18
  if isinstance(value, str):
23
19
  return _rewrite_url(value, master, user, password)
24
20
  if isinstance(value, list):
25
- return [_walk(v, master, user, password) for v in value]
21
+ return [_walk(v, master, user, password) for v in value] # pyright: ignore[reportUnknownVariableType]
26
22
  if isinstance(value, dict):
27
- return {k: _walk(v, master, user, password) for k, v in value.items()}
23
+ return {k: _walk(v, master, user, password) for k, v in value.items()} # pyright: ignore[reportUnknownVariableType]
28
24
  return value
29
25
 
30
26