@camera.ui/server 2.0.4 → 2.0.6
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.
- package/dist/interface/assets/{404-B8P9h05I.js → 404-ekmIpFW1.js} +1 -1
- package/dist/interface/assets/About-CvajU0JT.js +1 -0
- package/dist/interface/assets/Adminpanel-DrKaMQqd.js +1 -0
- package/dist/interface/assets/Automation-Dn1ZXmhy.js +2 -0
- package/dist/interface/assets/AutomationImport-U7r6BcW_.js +2 -0
- package/dist/interface/assets/{AutomationStore-qLsO1tHb.js → AutomationStore-DZBtLuIs.js} +2 -2
- package/dist/interface/assets/{AutomationStoreDetail-BAW2nfPd.js → AutomationStoreDetail-BXpOtPMl.js} +2 -2
- package/dist/interface/assets/{Automations-CUCVYZSr.js → Automations-DGHcWJ0h.js} +2 -2
- package/dist/interface/assets/Camera-CVJ-46dw.js +2 -0
- package/dist/interface/assets/Camera-Da5zEitE.css +1 -0
- package/dist/interface/assets/{CameraConsole-t6nY7h-V.js → CameraConsole-DPI9LjVK.js} +1 -1
- package/dist/interface/assets/Cameras-2kM76WUB.css +1 -0
- package/dist/interface/assets/Cameras-DsZ7VINe.js +652 -0
- package/dist/interface/assets/Camview-Lnxd3oe8.js +1 -0
- package/dist/interface/assets/Config-B1ZKVfMn.js +2 -0
- package/dist/interface/assets/ConfigActionCameraControl-BHKVJQqx.js +1 -0
- package/dist/interface/assets/{ConfigActionDelay-Rnxk--Qt.js → ConfigActionDelay-BS1vLvDQ.js} +1 -1
- package/dist/interface/assets/ConfigActionHttp-BKlRdtJr.js +1 -0
- package/dist/interface/assets/{ConfigActionImageInput-C8_H3ubM.js → ConfigActionImageInput-DveJ7oK8.js} +1 -1
- package/dist/interface/assets/{ConfigActionNotification-4VYN5Hkr.js → ConfigActionNotification-Cn3qFFiK.js} +1 -1
- package/dist/interface/assets/ConfigActionOutput-Bg6d4zFD.js +1 -0
- package/dist/interface/assets/{ConfigActionPlugin-DrPmBv9V.js → ConfigActionPlugin-u-_sCQs9.js} +1 -1
- package/dist/interface/assets/{ConfigActionSensor-CyaEZRfx.js → ConfigActionSensor-Cx1Q9VtS.js} +1 -1
- package/dist/interface/assets/{ConfigActionSnapshot-l7VvnulO.js → ConfigActionSnapshot-DN4UC9jz.js} +1 -1
- package/dist/interface/assets/{ConfigActionVariable-DIlUMHDy.js → ConfigActionVariable-Yufao52p.js} +1 -1
- package/dist/interface/assets/{ConfigConditionIfElse-0XRDQcTw.js → ConfigConditionIfElse-X9Sf0RwG.js} +1 -1
- package/dist/interface/assets/ConfigConditionSensorState-DQkz0FC6.js +1 -0
- package/dist/interface/assets/{ConfigConditionSwitch-C7pp1EZI.js → ConfigConditionSwitch-B4OICWxL.js} +1 -1
- package/dist/interface/assets/ConfigConditionTime-DRhAEuuC.js +1 -0
- package/dist/interface/assets/{ConfigTriggerDetection-CKw-fo2n.js → ConfigTriggerDetection-CzI4hgRA.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerGeofence-DjBRl2tE.js → ConfigTriggerGeofence-RUlZmXAo.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerManual-BpBTh3CS.js → ConfigTriggerManual-Jjh7ZhyC.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSchedule-DZG10NcA.js → ConfigTriggerSchedule-Dr6ALguI.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSensor-CpFa6nUj.js → ConfigTriggerSensor-DLxdw9Fd.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSystem-BsQElCAL.js → ConfigTriggerSystem-DYDpUWwi.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerWebhook-BKPOGAs3.js → ConfigTriggerWebhook-DiW3cwjT.js} +1 -1
- package/dist/interface/assets/Console-B55HKp9M.js +1 -0
- package/dist/interface/assets/{CuiActionButton-BSy6VaJ6.js → CuiActionButton-iNuXCl7_.js} +1 -1
- package/dist/interface/assets/{CuiAvatar-8AhiddF1.js → CuiAvatar-B1PdXoLS.js} +1 -1
- package/dist/interface/assets/{CuiBBoxPlayground-BUPzZ0sI.js → CuiBBoxPlayground-BJYeyVTF.js} +1 -1
- package/dist/interface/assets/{CuiBottomSheet-BNCym-11.js → CuiBottomSheet-B57WDcr5.js} +1 -1
- package/dist/interface/assets/CuiBottomSheet-Da0yy5zi.js +1 -0
- package/dist/interface/assets/CuiCameraCard-Dix4Iyrv.js +1 -0
- package/dist/interface/assets/{CuiCameraPipCard-CUPWEo3r.js → CuiCameraPipCard-BAOlD5uq.js} +1 -1
- package/dist/interface/assets/CuiCameraRecordings-ET-hbDL9.js +1 -0
- package/dist/interface/assets/{CuiCameraShares-oA3ockIQ.js → CuiCameraShares-CMzYmZt7.js} +1 -1
- package/dist/interface/assets/CuiCameraSnapshot-BcJfNxnj.js +1 -0
- package/dist/interface/assets/CuiCameraTable-CmyHU7oP.js +2 -0
- package/dist/interface/assets/{CuiDataTable-DhnQRMXG.js → CuiDataTable-Cd1jvrN0.js} +1 -1
- package/dist/interface/assets/{CuiFloatingButton-YIEmTfno.js → CuiFloatingButton-B3_fNyCP.js} +1 -1
- package/dist/interface/assets/{CuiImage-CzYVSrkC.js → CuiImage-BupeZIam.js} +1 -1
- package/dist/interface/assets/{CuiMarkdownContent-ecpe3mHA.js → CuiMarkdownContent-B4fVlO8h.js} +1 -1
- package/dist/interface/assets/CuiMarkdownContent-CMPFzD-0.js +1 -0
- package/dist/interface/assets/{CuiMenu-CEaJl-MF.js → CuiMenu-C-rnBf9W.js} +1 -1
- package/dist/interface/assets/{CuiPluginCard-BBV_hV1R.css → CuiPluginCard-C9zsmIaC.css} +1 -1
- package/dist/interface/assets/CuiPluginCard-Cs3gCugA.js +2 -0
- package/dist/interface/assets/{CuiPluginCategoryChip-BwL2qCTM.js → CuiPluginCategoryChip-D_ICXcvd.js} +1 -1
- package/dist/interface/assets/{CuiPluginOAuthButton-BmBW2Pdf.js → CuiPluginOAuthButton-B1e0NCmA.js} +2 -2
- package/dist/interface/assets/{CuiPluginTrustBadge-KcOasEJH.js → CuiPluginTrustBadge-CuxXssET.js} +1 -1
- package/dist/interface/assets/{CuiQRCode-BUSzb3ek.js → CuiQRCode-46CN267y.js} +1 -1
- package/dist/interface/assets/{CuiRecordingsGrid-lcLty5PV.css → CuiRecordingsGrid-DRBC07wN.css} +1 -1
- package/dist/interface/assets/CuiRecordingsGrid-FObjS4y5.js +1 -0
- package/dist/interface/assets/CuiSchema-BvjMb-mO.js +1 -0
- package/dist/interface/assets/{CuiTopNavbarItem-DVwItfbG.js → CuiTopNavbarItem-CmnJEc_r.js} +1 -1
- package/dist/interface/assets/{CuiTopbarSlot-h5_1cb9v.js → CuiTopbarSlot-T_pdxsit.js} +1 -1
- package/dist/interface/assets/Faces-DHk9Jq3T.js +1 -0
- package/dist/interface/assets/FirstSteps-Bb3XOidA.js +1 -0
- package/dist/interface/assets/{Instances-BNasTbK_.js → Instances-CXPH9NdC.js} +1 -1
- package/dist/interface/assets/Logs-jN6cBnCk.js +6 -0
- package/dist/interface/assets/{Menu-DTC3IWLc.js → Menu-BhWV3vKB.js} +1 -1
- package/dist/interface/assets/{OAuthDeviceFlow-BOIY4kBt.js → OAuthDeviceFlow-XfU0jZ6x.js} +1 -1
- package/dist/interface/assets/Plugin-BF0VMRby.js +1 -0
- package/dist/interface/assets/Plugin-DbSiTjYn.css +1 -0
- package/dist/interface/assets/{PluginConsole-DwNRvKqW.js → PluginConsole-DoeboO_n.js} +1 -1
- package/dist/interface/assets/PluginDetail-C2UBJlKS.js +2 -0
- package/dist/interface/assets/{PluginSchema-DWnYqRll.js → PluginSchema-BD-rLvro.js} +1 -1
- package/dist/interface/assets/PluginSearch-CTIiYmP4.js +1 -0
- package/dist/interface/assets/PluginSearch-NnCJ7vG2.js +2 -0
- package/dist/interface/assets/Plugins-BLtk8Off.js +1 -0
- package/dist/interface/assets/Recordings-CMO0UwaS.js +1 -0
- package/dist/interface/assets/{Settings-CfiNEl95.js → Settings-BAVtA47G2.js} +1 -1
- package/dist/interface/assets/SettingsAccount-Iz4WalaS.js +1 -0
- package/dist/interface/assets/SettingsAccount-NUdKG4ow.js +1 -0
- package/dist/interface/assets/SettingsAppearance-Ck1O2w3P.js +1 -0
- package/dist/interface/assets/{SettingsAppearance-Ck4VcB-w.js → SettingsAppearance-SLzxaqjN.js} +1 -1
- package/dist/interface/assets/SettingsBackup-CPC0aLD3.js +1 -0
- package/dist/interface/assets/SettingsNotifications-DB4CcddP.js +2 -0
- package/dist/interface/assets/SettingsRecordings-C15sTnLd.js +1 -0
- package/dist/interface/assets/SettingsRemote-5nVdezk3.js +1 -0
- package/dist/interface/assets/SettingsSystem-FqKuLssS.js +2 -0
- package/dist/interface/assets/SettingsUsers-CCp_3xlV.js +1 -0
- package/dist/interface/assets/Terminal-BHvfL8kE.js +4 -0
- package/dist/interface/assets/{VariableInput-DmE1m9Il.js → VariableInput-BnNttSx9.js} +1 -1
- package/dist/interface/assets/{VariableSuggestions-eII-1Bd_.js → VariableSuggestions-BbT0U9F0.js} +1 -1
- package/dist/interface/assets/VersionsHandler-jleSwMVy.js +7 -0
- package/dist/interface/assets/{Workers-Bj_tLx-1.js → Workers-ELW90lll.js} +2 -2
- package/dist/interface/assets/{account-CMuSWY7R.js → account-DqyKcqe6.js} +1 -1
- package/dist/interface/assets/ace-6JpPWRIj.js +1 -0
- package/dist/interface/assets/{ace.config-D884GuRj.js → ace.config-BnKJ0Xjj.js} +1 -1
- package/dist/interface/assets/{alert-circle-BBVlWmUb.js → alert-circle-DeKKKwiu.js} +1 -1
- package/dist/interface/assets/{alert-outline-MJaaWeWU.js → alert-outline-C4_42ECn.js} +1 -1
- package/dist/interface/assets/{api-CltBUyci.js → api-BRin642H.js} +1 -1
- package/dist/interface/assets/api-ByvWBLH3.js +2 -0
- package/dist/interface/assets/api-dcK5urq7.css +2 -0
- package/dist/interface/assets/{arrow-left-Bzo6lbMk.js → arrow-left-DroMCfOc.js} +1 -1
- package/dist/interface/assets/{arrow-right-outline-DU6p5Mjp.js → arrow-right-outline-CvgD4nuT.js} +1 -1
- package/dist/interface/assets/auth-D5gzm869.js +1 -0
- package/dist/interface/assets/{automation-BjkFcQH9.js → automation-BjZc8WL7.js} +1 -1
- package/dist/interface/assets/{automations-73X55Wxy.js → automations-B9KDzbWq.js} +1 -1
- package/dist/interface/assets/automations-CmA6Yx5B.js +1 -0
- package/dist/interface/assets/{back-filled-DI8fXlRr.js → back-filled-LVLhJP7G.js} +1 -1
- package/dist/interface/assets/{bell-Xdsks-42.js → bell-_E2Lq4ub.js} +1 -1
- package/dist/interface/assets/{bell-outline-Cy3lRQBX.js → bell-outline-CHhIZudN.js} +1 -1
- package/dist/interface/assets/{bell-ring-DR_BFore.js → bell-ring-DgLkBDcU.js} +1 -1
- package/dist/interface/assets/{bug-BkbVdwX_.js → bug-Eq4PDwA4.js} +1 -1
- package/dist/interface/assets/{cameras-Crvf7J3t.js → cameras-CYwpMvA8.js} +1 -1
- package/dist/interface/assets/{cameras.schema-CQPF4BCK.js → cameras.schema-neefhBQz.js} +1 -1
- package/dist/interface/assets/{cctv-D8gURS6i.js → cctv-BZIDJlxZ.js} +1 -1
- package/dist/interface/assets/{cctv-izLKpHLU.js → cctv-DbFrrwEm.js} +1 -1
- package/dist/interface/assets/{check-vQdlfVb0.js → check-COWE52r_.js} +1 -1
- package/dist/interface/assets/{check-circle-pC6Mk9ok.js → check-circle-CAbOGJc_.js} +1 -1
- package/dist/interface/assets/{chevron-down-5hhpriLd.js → chevron-down-Bu7d-Y9N.js} +1 -1
- package/dist/interface/assets/{chevron-right-B8jZK42K.js → chevron-right-90_Zn7fJ.js} +1 -1
- package/dist/interface/assets/{circular-connection-CShziQTj.js → circular-connection-Fq-Vqe3X.js} +1 -1
- package/dist/interface/assets/{clock-outline-C1Qoi42Q.js → clock-outline-CqPKrGj2.js} +1 -1
- package/dist/interface/assets/{close-CunDSika.js → close-BTPBtoEq.js} +1 -1
- package/dist/interface/assets/{close-circle-ClO0DCH3.js → close-circle-BAE2M1UZ.js} +1 -1
- package/dist/interface/assets/{cloud-check-DH_MvuMa.js → cloud-check-C2zK34QI.js} +1 -1
- package/dist/interface/assets/{cloud-outline-C6S72e1A.js → cloud-outline-64fdhdhh.js} +1 -1
- package/dist/interface/assets/{cloud-outline-CNhBvx4x.js → cloud-outline-BooRVJGO.js} +1 -1
- package/dist/interface/assets/cloudHandoff-CdYAKJPM.js +1 -0
- package/dist/interface/assets/{cog-Bgvz011z.js → cog-BOiZ8Brk.js} +1 -1
- package/dist/interface/assets/{cog-outline-uZ6J7_NJ.js → cog-outline-CXjehRoX.js} +1 -1
- package/dist/interface/assets/config-BLaep_I5.js +1 -0
- package/dist/interface/assets/{copy-16-filled-I80UlDbg.js → copy-16-filled-DfOe7jyi.js} +1 -1
- package/dist/interface/assets/{copy-B4-Xsyae.js → copy-D1B4K3K8.js} +1 -1
- package/dist/interface/assets/{delete-BdZ-cqP2.js → delete-BEupTYe7.js} +1 -1
- package/dist/interface/assets/{deviceId-BJm8lmpp.js → deviceId-B7SNJduj.js} +1 -1
- package/dist/interface/assets/{dist-D7Ztbw2o.js → dist-PhgrarGW.js} +1 -1
- package/dist/interface/assets/{download-C1GMtykU.js → download-BKGJ_BFg.js} +1 -1
- package/dist/interface/assets/{download-CNVBKEUL.js → download-DLRWZxWu.js} +1 -1
- package/dist/interface/assets/esm-2cKQcsYl.js +1 -0
- package/dist/interface/assets/esm-BC-ykYHP.js +1 -0
- package/dist/interface/assets/esm-BQAccPYQ2.js +1 -0
- package/dist/interface/assets/esm-B_RAh7V8.js +1 -0
- package/dist/interface/assets/esm-BsEkUnid.js +1 -0
- package/dist/interface/assets/esm-C1u--4jU.js +1 -0
- package/dist/interface/assets/esm-Cmrq9Tli.js +1 -0
- package/dist/interface/assets/esm-CrZMuT0_.js +1 -0
- package/dist/interface/assets/esm-CxS-x_QY.js +1 -0
- package/dist/interface/assets/esm-D-xtFbBs.js +1 -0
- package/dist/interface/assets/esm-D6PR9MbI.js +1 -0
- package/dist/interface/assets/esm-DTxHX_m3.js +1 -0
- package/dist/interface/assets/esm-Dzks7cY7.js +1 -0
- package/dist/interface/assets/esm-nCaFxJ36.js +1 -0
- package/dist/interface/assets/{eventIcons-GmV5zC8O.js → eventIcons-OxuOsPp8.js} +1 -1
- package/dist/interface/assets/{face-recognition-CS2Rf6Zc.js → face-recognition-CPwwcApe.js} +1 -1
- package/dist/interface/assets/{github-BrijWwqw.js → github-CWr5rTTh.js} +1 -1
- package/dist/interface/assets/{grid-fill-vdgMWTlf.js → grid-fill-DepYpY1i.js} +1 -1
- package/dist/interface/assets/{hasPermission-LUHh3i2_.js → hasPermission-FP_aMe9r.js} +1 -1
- package/dist/interface/assets/{home-By41XQGy.js → home-DTNk2yog.js} +1 -1
- package/dist/interface/assets/{information-outline-Kgo_4wPg.js → information-outline-BaVRQ1WL.js} +1 -1
- package/dist/interface/assets/{instances-A-hAMJRC.js → instances-DWzDpyhw.js} +1 -1
- package/dist/interface/assets/{lightbulb-outline-XVFprQ8f.js → lightbulb-outline-DPjpJn5F.js} +1 -1
- package/dist/interface/assets/main-7zIUc9Mt.css +1 -0
- package/dist/interface/assets/main-eaoHjmUh.js +3 -0
- package/dist/interface/assets/{microphone-GG_JO-aa.js → microphone-DJsihw9n.js} +1 -1
- package/dist/interface/assets/{motion-sensor-BvWcG84o.js → motion-sensor-D9ErhY-p.js} +1 -1
- package/dist/interface/assets/native-BK8vZMAo.js +1 -0
- package/dist/interface/assets/native-Z2MQ5D82.js +1 -0
- package/dist/interface/assets/{nodeDefinitions-BSLOZMDt.js → nodeDefinitions-kqqBPelM.js} +1 -1
- package/dist/interface/assets/{notifications-eX5p2YaS.js → notifications-Dae9EImK.js} +1 -1
- package/dist/interface/assets/{play-solid-BtIose8X.js → play-solid-cginGSgT.js} +1 -1
- package/dist/interface/assets/plugin-DHenq6mQ.js +1 -0
- package/dist/interface/assets/{plugins-DfuM5k5Q.js → plugins-bWz9yaCS.js} +1 -1
- package/dist/interface/assets/{plus-CPMfvO5i.js → plus-CJ2GnX_q.js} +1 -1
- package/dist/interface/assets/{plus-BKTSMYeY.js → plus-CJzoaVfR.js} +1 -1
- package/dist/interface/assets/{power-M2I2_fCM.js → power-CH_DVgfm.js} +1 -1
- package/dist/interface/assets/{puzzle-filled-C2Lcmza4.js → puzzle-filled-BsJvAZrD.js} +1 -1
- package/dist/interface/assets/{refresh-rounded-B-PvjcAK.js → refresh-rounded-D9PcUR6I.js} +1 -1
- package/dist/interface/assets/{ring-resize-CdUkE4zr.js → ring-resize-QxO72Hs5.js} +1 -1
- package/dist/interface/assets/{round-alt-arrow-left-bold-MIf6gSgD.js → round-alt-arrow-left-bold-C57m543l.js} +1 -1
- package/dist/interface/assets/{round-restart-alt-rDMgoX7-.js → round-restart-alt-C6zOLIvt.js} +1 -1
- package/dist/interface/assets/router-jxQ55Ssp.js +2 -0
- package/dist/interface/assets/{routerState-DxipV4mY.js → routerState-D1WXHNt5.js} +1 -1
- package/dist/interface/assets/{search-CHjonsLq.js → search-BgYt2zqA.js} +1 -1
- package/dist/interface/assets/{server-CoQ6jFok.js → server-Uk7xnHXH.js} +1 -1
- package/dist/interface/assets/{settings-CJjCbQsx.js → settings-DHy64jsU.js} +1 -1
- package/dist/interface/assets/shares-Cz4zuZa_.js +1 -0
- package/dist/interface/assets/{shield-check-oKTXgAfr.js → shield-check-0hUJRszq.js} +1 -1
- package/dist/interface/assets/{source-branch-aFYjLKkc.js → source-branch-Dv9Du0bc.js} +1 -1
- package/dist/interface/assets/{terminal-B8qmxarl.js → terminal-CNEjR0aR.js} +1 -1
- package/dist/interface/assets/{theme-BzXEv5Er.js → theme-GXDCnY_7.js} +1 -1
- package/dist/interface/assets/{trash-2-rEVmOxCl.js → trash-2-CAa4rD_d.js} +1 -1
- package/dist/interface/assets/{trash-fill-DfnCMe3h.js → trash-fill-Br00Vept.js} +1 -1
- package/dist/interface/assets/{trash2-BvyW-bOK.js → trash2-tuK9V5bO.js} +1 -1
- package/dist/interface/assets/{types-Ds4g4ynm.js → types-BpBkaZ8S.js} +1 -1
- package/dist/interface/assets/{types-CAW47AJe.js → types-jpiqSoyF.js} +1 -1
- package/dist/interface/assets/{ui-Dg-QtLMl.js → ui-DzCa9zWL.js} +1 -1
- package/dist/interface/assets/{upload-DkFN5YK-.js → upload-DAasFa9r.js} +1 -1
- package/dist/interface/assets/useAppVersion-CqnE2uYa.js +1 -0
- package/dist/interface/assets/useBackupRestore-Dm7jUOC-.js +1 -0
- package/dist/interface/assets/{useBiometricCredentials-Buylkybs.js → useBiometricCredentials-DJqfT1lk.js} +1 -1
- package/dist/interface/assets/useCameraOptions-Dv3QCMBK.js +1 -0
- package/dist/interface/assets/{useCuiCameraDrawer-DwlhaZ2i.js → useCuiCameraDrawer-D1zCpVbQ.js} +1 -1
- package/dist/interface/assets/useCuiDialog-C02Or5oR.js +2 -0
- package/dist/interface/assets/{useFileDownload-Bhdbhy_2.js → useFileDownload-CwxydJkw.js} +1 -1
- package/dist/interface/assets/{useLogsSocket-81hTPkwS.js → useLogsSocket-CunaH_T7.js} +2 -2
- package/dist/interface/assets/{useServerRestart-3pT0h7CH.js → useServerRestart-BQO5FbNN.js} +1 -1
- package/dist/interface/assets/{useSharedComposables-Ki01kFNJ.js → useSharedComposables-BrdI_nDH.js} +1 -1
- package/dist/interface/assets/{useStreamStatus-CHITNYE7.js → useStreamStatus-e7l-QLmI.js} +1 -1
- package/dist/interface/assets/useUpdateChannel-CW45dNAk.js +1 -0
- package/dist/interface/assets/useUserOptions-EhjyVt4P.js +1 -0
- package/dist/interface/assets/users.schema-4d5Hcv-l.js +1 -0
- package/dist/interface/assets/vendor-ClrOISZp.js +22 -0
- package/dist/interface/assets/{vendor-ace-DdrK3_G3.js → vendor-ace-BlP3GAWI.js} +4 -4
- package/dist/interface/assets/vendor-cameraui-E9E7fP4y.js +1978 -0
- package/dist/interface/assets/{vendor-capacitor-Cmm68VPg.js → vendor-capacitor-DWVJM4o8.js} +2 -2
- package/dist/interface/assets/vendor-chart-Cn72DQnu.js +3 -0
- package/dist/interface/assets/{vendor-core-DnC_SdtD.js → vendor-core-D2PMTaN3.js} +1 -1
- package/dist/interface/assets/{vendor-dnd-BAVhbK8Q.js → vendor-dnd-B9-e5xxH.js} +1 -1
- package/dist/interface/assets/{vendor-draggabilly-C56V4WL_.js → vendor-draggabilly-CPB8E5_r.js} +1 -1
- package/dist/interface/assets/{vendor-leaflet-_2Z0GiOD.js → vendor-leaflet-BMMJO5Nd.js} +1 -1
- package/dist/interface/assets/{vendor-markdown-D8dkSMBf.js → vendor-markdown-D7EhExwE.js} +2 -2
- package/dist/interface/assets/{vendor-primevue-OV61z06m.js → vendor-primevue-Ba4XR6PC.js} +301 -301
- package/dist/interface/assets/vendor-vueflow-Ci6VMxDa.js +26 -0
- package/dist/interface/assets/vendor-xterm-IIYDrBG1.js +142 -0
- package/dist/interface/assets/view-grid-Bs5Mc3pI.js +1 -0
- package/dist/interface/assets/view-grid-CWZLWCco.css +1 -0
- package/dist/interface/assets/web-1H50tiqZ.js +1 -0
- package/dist/interface/assets/web-BBRAFu75.js +1 -0
- package/dist/interface/assets/web-BQqZ-0se.js +1 -0
- package/dist/interface/assets/web-BgDADYuL.js +1 -0
- package/dist/interface/assets/web-BjKL51lK-DF7_OzmH.js +1 -0
- package/dist/interface/assets/web-C3oASmFS.js +1 -0
- package/dist/interface/assets/web-CQIOnZbD.js +1 -0
- package/dist/interface/assets/web-CW6kIZQg.js +1 -0
- package/dist/interface/assets/web-CXLWILCn.js +1 -0
- package/dist/interface/assets/web-Cz9RS9vK.js +1 -0
- package/dist/interface/assets/web-D7_BjGke.js +1 -0
- package/dist/interface/assets/web-Dn-uR4FN.js +1 -0
- package/dist/interface/assets/web-GsFXaCFL.js +1 -0
- package/dist/interface/assets/web-kh2rHftO.js +1 -0
- package/dist/interface/assets/{webhook-nx5ZDIQ-.js → webhook-BCBlzc3z.js} +1 -1
- package/dist/interface/assets/workbox-window.prod.es5-CQTy-jf8.js +1 -0
- package/dist/interface/assets/{zoom-in-wM6G8w-4.js → zoom-in-CKM5nWQX.js} +1 -1
- package/dist/interface/index.html +88 -85
- package/dist/interface/sw.js +1 -1
- package/dist/server/package.json +36 -19
- package/dist/server/requirements.txt +4 -3
- package/dist/server/src/_camera_ui_tools/camera_ui_common/common_utils/signal_handler.py +17 -9
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/detection.py +10 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/internal/event_emitter.py +45 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/manager/__init__.py +3 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/storage/__init__.py +47 -12
- package/dist/server/src/api/controllers/plugins.controller.js +11 -3
- package/dist/server/src/api/controllers/plugins.controller.js.map +1 -1
- package/dist/server/src/api/controllers/remote.controller.d.ts +1 -0
- package/dist/server/src/api/controllers/remote.controller.js +9 -2
- package/dist/server/src/api/controllers/remote.controller.js.map +1 -1
- package/dist/server/src/api/database/index.d.ts +2 -1
- package/dist/server/src/api/database/index.js +22 -3
- package/dist/server/src/api/database/index.js.map +1 -1
- package/dist/server/src/api/middlewares/authValidation.middleware.js +1 -1
- package/dist/server/src/api/middlewares/authValidation.middleware.js.map +1 -1
- package/dist/server/src/api/schemas/cameras.schema.d.ts +27 -0
- package/dist/server/src/api/schemas/cameras.schema.js +6 -0
- package/dist/server/src/api/schemas/cameras.schema.js.map +1 -1
- package/dist/server/src/api/schemas/config.schema.d.ts +2 -0
- package/dist/server/src/api/schemas/config.schema.js +1 -0
- package/dist/server/src/api/schemas/config.schema.js.map +1 -1
- package/dist/server/src/api/services/backup.service.d.ts +4 -0
- package/dist/server/src/api/services/backup.service.js +35 -4
- package/dist/server/src/api/services/backup.service.js.map +1 -1
- package/dist/server/src/api/services/plugins.service.js +1 -1
- package/dist/server/src/api/services/plugins.service.js.map +1 -1
- package/dist/server/src/api/services/remote.service.d.ts +0 -1
- package/dist/server/src/api/services/remote.service.js +6 -13
- package/dist/server/src/api/services/remote.service.js.map +1 -1
- package/dist/server/src/api/types/index.d.ts +1 -0
- package/dist/server/src/api.d.ts +1 -0
- package/dist/server/src/api.js +11 -3
- package/dist/server/src/api.js.map +1 -1
- package/dist/server/src/camera/decoder/audio-source.d.ts +2 -0
- package/dist/server/src/camera/decoder/audio-source.js +12 -0
- package/dist/server/src/camera/decoder/audio-source.js.map +1 -1
- package/dist/server/src/camera/decoder/detection-coordinator.js +16 -2
- package/dist/server/src/camera/decoder/detection-coordinator.js.map +1 -1
- package/dist/server/src/camera/decoder/frame-source.d.ts +2 -0
- package/dist/server/src/camera/decoder/frame-source.js +23 -2
- package/dist/server/src/camera/decoder/frame-source.js.map +1 -1
- package/dist/server/src/camera/decoder/ptz-autotracker.js +51 -78
- package/dist/server/src/camera/decoder/ptz-autotracker.js.map +1 -1
- package/dist/server/src/camera/streaming/fmp4-session.js +9 -0
- package/dist/server/src/camera/streaming/fmp4-session.js.map +1 -1
- package/dist/server/src/camera/streaming/rtp-session.js +0 -13
- package/dist/server/src/camera/streaming/rtp-session.js.map +1 -1
- package/dist/server/src/go2rtc/api/routes/streams.js +5 -6
- package/dist/server/src/go2rtc/api/routes/streams.js.map +1 -1
- package/dist/server/src/go2rtc/index.js +1 -1
- package/dist/server/src/go2rtc/index.js.map +1 -1
- package/dist/server/src/go2rtc/types.d.ts +3 -0
- package/dist/server/src/go2rtc/types.js.map +1 -1
- package/dist/server/src/main.js +1 -1
- package/dist/server/src/main.js.map +1 -1
- package/dist/server/src/manager/deviceManager.js +4 -1
- package/dist/server/src/manager/deviceManager.js.map +1 -1
- package/dist/server/src/manager/discoveryManager.d.ts +3 -2
- package/dist/server/src/manager/discoveryManager.js +60 -29
- package/dist/server/src/manager/discoveryManager.js.map +1 -1
- package/dist/server/src/plugins/config-store.d.ts +1 -1
- package/dist/server/src/plugins/config-store.js +17 -10
- package/dist/server/src/plugins/config-store.js.map +1 -1
- package/dist/server/src/plugins/index.d.ts +1 -0
- package/dist/server/src/plugins/index.js +38 -2
- package/dist/server/src/plugins/index.js.map +1 -1
- package/dist/server/src/plugins/plugin.js +2 -2
- package/dist/server/src/plugins/plugin.js.map +1 -1
- package/dist/server/src/plugins/runtime/base.js +10 -5
- package/dist/server/src/plugins/runtime/base.js.map +1 -1
- package/dist/server/src/plugins/runtime/go/index.js +1 -0
- package/dist/server/src/plugins/runtime/go/index.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/child.d.ts +1 -0
- package/dist/server/src/plugins/runtime/node/child.js +62 -19
- package/dist/server/src/plugins/runtime/node/child.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/configDb.d.ts +8 -0
- package/dist/server/src/plugins/runtime/node/configDb.js +15 -0
- package/dist/server/src/plugins/runtime/node/configDb.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/index.js +2 -0
- package/dist/server/src/plugins/runtime/node/index.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/pluginApi.d.ts +4 -0
- package/dist/server/src/plugins/runtime/node/pluginApi.js +13 -5
- package/dist/server/src/plugins/runtime/node/pluginApi.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.d.ts +1 -2
- package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.js +2 -8
- package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/proxy/coreManager.d.ts +3 -2
- package/dist/server/src/plugins/runtime/node/proxy/coreManager.js +9 -13
- package/dist/server/src/plugins/runtime/node/proxy/coreManager.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/proxy/deviceManager.d.ts +3 -2
- package/dist/server/src/plugins/runtime/node/proxy/deviceManager.js +14 -12
- package/dist/server/src/plugins/runtime/node/proxy/deviceManager.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/proxy/fileServe.js +4 -9
- package/dist/server/src/plugins/runtime/node/proxy/fileServe.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/proxy/notificationManager.js +3 -0
- package/dist/server/src/plugins/runtime/node/proxy/notificationManager.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/storage.d.ts +5 -2
- package/dist/server/src/plugins/runtime/node/storage.js +92 -67
- package/dist/server/src/plugins/runtime/node/storage.js.map +1 -1
- package/dist/server/src/plugins/runtime/node/storageController.d.ts +2 -0
- package/dist/server/src/plugins/runtime/node/storageController.js +12 -4
- package/dist/server/src/plugins/runtime/node/storageController.js.map +1 -1
- package/dist/server/src/plugins/runtime/python/child.py +50 -38
- package/dist/server/src/plugins/runtime/python/config_db.py +28 -17
- package/dist/server/src/plugins/runtime/python/index.js +1 -0
- package/dist/server/src/plugins/runtime/python/index.js.map +1 -1
- package/dist/server/src/plugins/runtime/python/plugin_api.py +5 -5
- package/dist/server/src/plugins/runtime/python/proxy/camera_device.py +14 -13
- package/dist/server/src/plugins/runtime/python/proxy/core_manager.py +11 -14
- package/dist/server/src/plugins/runtime/python/proxy/device_manager.py +23 -18
- package/dist/server/src/plugins/runtime/python/remote_urls.py +3 -7
- package/dist/server/src/plugins/runtime/python/storage.py +157 -126
- package/dist/server/src/plugins/runtime/python/storage_controller.py +28 -13
- package/dist/server/src/plugins/runtime/python/store.py +513 -0
- package/dist/server/src/plugins/runtime/python/store_migrate.py +59 -0
- package/dist/server/src/plugins/store/coalescingWriter.d.ts +7 -0
- package/dist/server/src/plugins/store/coalescingWriter.js +52 -0
- package/dist/server/src/plugins/store/coalescingWriter.js.map +1 -0
- package/dist/server/src/plugins/store/envelope.d.ts +5 -0
- package/dist/server/src/plugins/store/envelope.js +40 -0
- package/dist/server/src/plugins/store/envelope.js.map +1 -0
- package/dist/server/src/plugins/store/layout.d.ts +2 -0
- package/dist/server/src/plugins/store/layout.js +60 -0
- package/dist/server/src/plugins/store/layout.js.map +1 -0
- package/dist/server/src/plugins/store/location.d.ts +14 -0
- package/dist/server/src/plugins/store/location.js +55 -0
- package/dist/server/src/plugins/store/location.js.map +1 -0
- package/dist/server/src/plugins/store/migrate.d.ts +1 -0
- package/dist/server/src/plugins/store/migrate.js +22 -0
- package/dist/server/src/plugins/store/migrate.js.map +1 -0
- package/dist/server/src/plugins/store/pluginStoreFile.d.ts +14 -0
- package/dist/server/src/plugins/store/pluginStoreFile.js +58 -0
- package/dist/server/src/plugins/store/pluginStoreFile.js.map +1 -0
- package/dist/server/src/plugins/store/storeFile.d.ts +5 -0
- package/dist/server/src/plugins/store/storeFile.js +112 -0
- package/dist/server/src/plugins/store/storeFile.js.map +1 -0
- package/dist/server/src/plugins/store/validate.d.ts +1 -0
- package/dist/server/src/plugins/store/validate.js +49 -0
- package/dist/server/src/plugins/store/validate.js.map +1 -0
- package/dist/server/src/plugins/worker.js +9 -8
- package/dist/server/src/plugins/worker.js.map +1 -1
- package/dist/server/src/remote/services/cloudflare-managed.js +2 -2
- package/dist/server/src/remote/services/cloudflare-managed.js.map +1 -1
- package/dist/server/src/remote/services/cloudflare.js +1 -0
- package/dist/server/src/remote/services/cloudflare.js.map +1 -1
- package/dist/server/src/remote/tunnel/client.js +1 -1
- package/dist/server/src/remote/tunnel/client.js.map +1 -1
- package/dist/server/src/rpc/interfaces/discovery.d.ts +1 -0
- package/dist/server/src/rpc/server.js +12 -5
- package/dist/server/src/rpc/server.js.map +1 -1
- package/dist/server/src/services/config/defaults.js +1 -0
- package/dist/server/src/services/config/defaults.js.map +1 -1
- package/dist/server/src/services/config/index.d.ts +1 -0
- package/dist/server/src/services/config/index.js +2 -0
- package/dist/server/src/services/config/index.js.map +1 -1
- package/dist/server/src/services/config/types.d.ts +1 -0
- package/dist/server/src/utils/npm/index.d.ts +1 -1
- package/dist/server/src/utils/npm/index.js +7 -4
- package/dist/server/src/utils/npm/index.js.map +1 -1
- package/dist/server/src/workers/capabilities/plugin-host.js +1 -1
- package/dist/server/src/workers/capabilities/plugin-host.js.map +1 -1
- package/package.json +36 -19
- package/dist/interface/assets/About-CjQs3rZw.js +0 -1
- package/dist/interface/assets/Adminpanel-BTIcUxWm.js +0 -1
- package/dist/interface/assets/Automation-BNbTehB5.js +0 -2
- package/dist/interface/assets/AutomationImport-BEm0hc0d.js +0 -2
- package/dist/interface/assets/Camera-BG8K5bH_.css +0 -1
- package/dist/interface/assets/Camera-eIsFEdVo.js +0 -2
- package/dist/interface/assets/Cameras-54LaZPN3.css +0 -1
- package/dist/interface/assets/Cameras-Bswv6ojJ.js +0 -652
- package/dist/interface/assets/Camview-DMywoy_4.js +0 -1
- package/dist/interface/assets/Config-C7H5amm4.js +0 -2
- package/dist/interface/assets/ConfigActionCameraControl-C0hvC4QW.js +0 -1
- package/dist/interface/assets/ConfigActionHttp-BF8vrrHR.js +0 -1
- package/dist/interface/assets/ConfigActionOutput-CxB3eLLl.js +0 -1
- package/dist/interface/assets/ConfigConditionSensorState-BvygRJ79.js +0 -1
- package/dist/interface/assets/ConfigConditionTime-B0zctDc4.js +0 -1
- package/dist/interface/assets/Console-DHlNpv-R.js +0 -1
- package/dist/interface/assets/CuiBottomSheet-ChG_av5z.js +0 -1
- package/dist/interface/assets/CuiCameraCard-BGAQxNHs.js +0 -1
- package/dist/interface/assets/CuiCameraRecordings-B25441dH.js +0 -1
- package/dist/interface/assets/CuiCameraSnapshot-BLM8v7rY.js +0 -1
- package/dist/interface/assets/CuiCameraTable-3joRnfiM.js +0 -2
- package/dist/interface/assets/CuiMarkdownContent-5LJK4h8V.js +0 -1
- package/dist/interface/assets/CuiPluginCard-De_cbBBe.js +0 -2
- package/dist/interface/assets/CuiRecordingsGrid-DHSABYJb.js +0 -1
- package/dist/interface/assets/CuiSchema-D35n-DE5.js +0 -1
- package/dist/interface/assets/Faces-BJlHL3gp.js +0 -1
- package/dist/interface/assets/FirstSteps-D1Zf1CYN.js +0 -1
- package/dist/interface/assets/Logs-CW5EqpAL.js +0 -6
- package/dist/interface/assets/Plugin-DHhrlRqP.css +0 -1
- package/dist/interface/assets/Plugin-DIPGTrNi.js +0 -1
- package/dist/interface/assets/PluginDetail-D1TJZJhY.js +0 -2
- package/dist/interface/assets/PluginSearch-C5bkQgYA.js +0 -1
- package/dist/interface/assets/PluginSearch-DqwgO886.js +0 -2
- package/dist/interface/assets/Plugins-o7IfSFjd.js +0 -1
- package/dist/interface/assets/Recordings-DTw5IG8W.js +0 -1
- package/dist/interface/assets/SettingsAccount-Ch2SR80X.js +0 -1
- package/dist/interface/assets/SettingsAccount-DIwWUyp6.js +0 -1
- package/dist/interface/assets/SettingsAppearance-CkF7biyP.js +0 -1
- package/dist/interface/assets/SettingsBackup-BigThNuk.js +0 -1
- package/dist/interface/assets/SettingsNotifications-XLBjwgwX.js +0 -2
- package/dist/interface/assets/SettingsRecordings-Dp9tJdHk.js +0 -1
- package/dist/interface/assets/SettingsRemote-B5jm7MdB.js +0 -1
- package/dist/interface/assets/SettingsSystem-Isy5hiDl.js +0 -2
- package/dist/interface/assets/SettingsUsers-QuUercEC.js +0 -1
- package/dist/interface/assets/Terminal-BTfYdYuv.js +0 -4
- package/dist/interface/assets/VersionsHandler-BYj7-lpw.js +0 -7
- package/dist/interface/assets/ace-DQ1lNH50.js +0 -1
- package/dist/interface/assets/api-D7tGlI-z.css +0 -2
- package/dist/interface/assets/api-DoPgGqHU.js +0 -2
- package/dist/interface/assets/auth-BiWutL3Z.js +0 -1
- package/dist/interface/assets/automations-hsi5MCtT.js +0 -1
- package/dist/interface/assets/backup-CHJIDGvl.js +0 -1
- package/dist/interface/assets/cloudHandoff-lLBGqrXl.js +0 -1
- package/dist/interface/assets/esm-0fUwrSGA2.js +0 -1
- package/dist/interface/assets/esm-50afX45o.js +0 -1
- package/dist/interface/assets/esm-B0VLR4af.js +0 -1
- package/dist/interface/assets/esm-BfZLgtrg.js +0 -1
- package/dist/interface/assets/esm-Bnmu7wlp.js +0 -1
- package/dist/interface/assets/esm-CG1dvQ4U.js +0 -1
- package/dist/interface/assets/esm-CZTaj2CB.js +0 -1
- package/dist/interface/assets/esm-CxRM74Bx.js +0 -1
- package/dist/interface/assets/esm-DBpRougC.js +0 -1
- package/dist/interface/assets/esm-DQ7OYRdT.js +0 -1
- package/dist/interface/assets/esm-DVYEXBOA.js +0 -1
- package/dist/interface/assets/esm-DdCbmz73.js +0 -1
- package/dist/interface/assets/esm-DiryHoSP.js +0 -1
- package/dist/interface/assets/esm-DyoMjqMI.js +0 -1
- package/dist/interface/assets/main-BjuXWRyJ.css +0 -1
- package/dist/interface/assets/main-DEbfQmxF.js +0 -3
- package/dist/interface/assets/native-Dg-M0-9s.js +0 -1
- package/dist/interface/assets/native-DyQXBqB9.js +0 -1
- package/dist/interface/assets/plugin-_eNpRpqe.js +0 -1
- package/dist/interface/assets/router-D1e_o7wa.js +0 -2
- package/dist/interface/assets/shares-VWzmq1V-.js +0 -1
- package/dist/interface/assets/useCameraOptions-GAeXCTvO.js +0 -1
- package/dist/interface/assets/useCuiDialog-yRn5B1oO.js +0 -2
- package/dist/interface/assets/useUserOptions-CUvE4OL9.js +0 -1
- package/dist/interface/assets/users.schema-DWL8-tZN.js +0 -1
- package/dist/interface/assets/vendor-c9Zhw4pk.js +0 -22
- package/dist/interface/assets/vendor-cameraui-DhctzD3V.js +0 -1978
- package/dist/interface/assets/vendor-chart-BjXPY6za.js +0 -3
- package/dist/interface/assets/vendor-vueflow-CJdb-8KZ.js +0 -26
- package/dist/interface/assets/vendor-xterm-BRN-hOYD.js +0 -145
- package/dist/interface/assets/view-grid-D04t817d.css +0 -1
- package/dist/interface/assets/view-grid-_whxsqcn.js +0 -1
- package/dist/interface/assets/web-B9o4PkxX.js +0 -1
- package/dist/interface/assets/web-BDuGOqy7.js +0 -1
- package/dist/interface/assets/web-BUVPtx3X.js +0 -1
- package/dist/interface/assets/web-B_hO9nzV.js +0 -1
- package/dist/interface/assets/web-BjKL51lK-CV2GxIDK.js +0 -1
- package/dist/interface/assets/web-CCgyqWSl.js +0 -1
- package/dist/interface/assets/web-Cm_fnqV2.js +0 -1
- package/dist/interface/assets/web-CsSSdPVW.js +0 -1
- package/dist/interface/assets/web-D2AMa4qD.js +0 -1
- package/dist/interface/assets/web-D2vwufvW.js +0 -1
- package/dist/interface/assets/web-D86U7zR6.js +0 -1
- package/dist/interface/assets/web-DilWncpX.js +0 -1
- package/dist/interface/assets/web-OjqOEI0k.js +0 -1
- package/dist/interface/assets/web-mrIKbnbx.js +0 -1
- package/dist/interface/assets/workbox-window.prod.es5-CsKLpYYC.js +0 -1
- /package/dist/interface/assets/{error-256-DLKVOU2S.png → logo-512-DLKVOU2S.png} +0 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import math
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import zlib
|
|
10
|
+
from contextlib import suppress
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
14
|
+
|
|
15
|
+
import msgpack
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Awaitable, Callable
|
|
19
|
+
|
|
20
|
+
STORE_FILE_NAME = "store.cui"
|
|
21
|
+
|
|
22
|
+
_MAGIC = b"CUI1"
|
|
23
|
+
_MAX_SAFE_INTEGER = 2**53 - 1
|
|
24
|
+
_RENAME_RETRY_DELAYS_MS = (10, 25, 60, 150, 300)
|
|
25
|
+
_IS_WINDOWS = sys.platform == "win32"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StoreLogger(Protocol):
|
|
29
|
+
def debug(self, message: str, /) -> None: ...
|
|
30
|
+
|
|
31
|
+
def warn(self, message: str, /) -> None: ...
|
|
32
|
+
|
|
33
|
+
def error(self, message: str, /) -> None: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class StoreCorruptError(Exception):
|
|
37
|
+
def __init__(self, message: str) -> None:
|
|
38
|
+
super().__init__(f"store envelope: {message}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def encode_envelope(payload: dict[str, Any]) -> bytes:
|
|
42
|
+
body: bytes = msgpack.packb(payload, use_bin_type=True)
|
|
43
|
+
return _MAGIC + body + zlib.crc32(body).to_bytes(4, "little")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def decode_envelope(buf: bytes) -> dict[str, Any]:
|
|
47
|
+
if len(buf) < len(_MAGIC) + 4 or buf[: len(_MAGIC)] != _MAGIC:
|
|
48
|
+
raise StoreCorruptError("bad magic")
|
|
49
|
+
|
|
50
|
+
body = buf[len(_MAGIC) : -4]
|
|
51
|
+
if zlib.crc32(body) != int.from_bytes(buf[-4:], "little"):
|
|
52
|
+
raise StoreCorruptError("crc mismatch")
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
payload = msgpack.unpackb(body, raw=False, strict_map_key=False)
|
|
56
|
+
except Exception as error:
|
|
57
|
+
raise StoreCorruptError(f"payload decode failed: {error}") from error
|
|
58
|
+
|
|
59
|
+
if not isinstance(payload, dict):
|
|
60
|
+
raise StoreCorruptError("payload is not a map")
|
|
61
|
+
|
|
62
|
+
return payload
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _replace_with_retry(tmp_path: str, path: str) -> None:
|
|
66
|
+
for attempt, delay_ms in enumerate(_RENAME_RETRY_DELAYS_MS):
|
|
67
|
+
try:
|
|
68
|
+
os.replace(tmp_path, path)
|
|
69
|
+
return
|
|
70
|
+
except PermissionError:
|
|
71
|
+
# Windows AV scanners / the search indexer transiently hold the
|
|
72
|
+
# rename target open; the error is retryable there within a small
|
|
73
|
+
# budget (~0.5s total), nowhere else.
|
|
74
|
+
if not _IS_WINDOWS or attempt >= len(_RENAME_RETRY_DELAYS_MS) - 1:
|
|
75
|
+
raise
|
|
76
|
+
time.sleep(delay_ms / 1000)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _write_store_file_sync(path: str, buf: bytes) -> None:
|
|
80
|
+
tmp_path = f"{path}.tmp-{os.getpid()}"
|
|
81
|
+
try:
|
|
82
|
+
fd = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)
|
|
83
|
+
try:
|
|
84
|
+
os.write(fd, buf)
|
|
85
|
+
os.fsync(fd)
|
|
86
|
+
finally:
|
|
87
|
+
os.close(fd)
|
|
88
|
+
_replace_with_retry(tmp_path, path)
|
|
89
|
+
except Exception:
|
|
90
|
+
with suppress(OSError):
|
|
91
|
+
os.unlink(tmp_path)
|
|
92
|
+
raise
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def write_store_file(path: str, payload: dict[str, Any], log: StoreLogger | None = None) -> None:
|
|
96
|
+
buf = encode_envelope(payload)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
await asyncio.to_thread(_write_store_file_sync, path, buf)
|
|
100
|
+
except Exception as error:
|
|
101
|
+
if log:
|
|
102
|
+
log.error(f"store: write {path} failed: {error}")
|
|
103
|
+
raise
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _read_bytes(path: str) -> bytes | None:
|
|
107
|
+
try:
|
|
108
|
+
return Path(path).read_bytes()
|
|
109
|
+
except FileNotFoundError:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def read_store_file(path: str, log: StoreLogger | None = None) -> dict[str, Any] | None:
|
|
114
|
+
buf = await asyncio.to_thread(_read_bytes, path)
|
|
115
|
+
if buf is None:
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
payload = decode_envelope(buf)
|
|
120
|
+
return payload
|
|
121
|
+
except StoreCorruptError as error:
|
|
122
|
+
if log:
|
|
123
|
+
log.error(f"store: {path} is corrupt ({error}) — trying backup")
|
|
124
|
+
|
|
125
|
+
bak_path = f"{path}.bak"
|
|
126
|
+
try:
|
|
127
|
+
bak_buf = await asyncio.to_thread(Path(bak_path).read_bytes)
|
|
128
|
+
except OSError:
|
|
129
|
+
bak_buf = None
|
|
130
|
+
|
|
131
|
+
if bak_buf is not None:
|
|
132
|
+
try:
|
|
133
|
+
payload = decode_envelope(bak_buf)
|
|
134
|
+
except StoreCorruptError as error:
|
|
135
|
+
if log:
|
|
136
|
+
log.error(f"store: backup {bak_path} is corrupt too ({error})")
|
|
137
|
+
else:
|
|
138
|
+
# Self-heal: replace the corrupt file immediately, otherwise the
|
|
139
|
+
# next backup refresh would clobber the only good copy.
|
|
140
|
+
await asyncio.to_thread(_write_store_file_sync, path, bak_buf)
|
|
141
|
+
if log:
|
|
142
|
+
log.warn(f"store: recovered {path} from backup ({len(bak_buf)} bytes)")
|
|
143
|
+
return payload
|
|
144
|
+
|
|
145
|
+
# A corrupt store must never silently become an empty one - that would
|
|
146
|
+
# wipe the plugin's persisted state. Recover from the backup generation or
|
|
147
|
+
# fail the open loudly.
|
|
148
|
+
raise StoreCorruptError(f"{path} unreadable and no usable backup")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _backup_store_file_sync(path: str) -> None:
|
|
152
|
+
# Copy to a temp sibling first - a crash mid-copy must never leave a
|
|
153
|
+
# truncated .bak, it may be the only recovery generation.
|
|
154
|
+
tmp_path = f"{path}.bak.tmp-{os.getpid()}"
|
|
155
|
+
try:
|
|
156
|
+
shutil.copyfile(path, tmp_path)
|
|
157
|
+
_replace_with_retry(tmp_path, f"{path}.bak")
|
|
158
|
+
except Exception:
|
|
159
|
+
with suppress(OSError):
|
|
160
|
+
os.unlink(tmp_path)
|
|
161
|
+
raise
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
async def backup_store_file(path: str, log: StoreLogger | None = None) -> None:
|
|
165
|
+
try:
|
|
166
|
+
await asyncio.to_thread(_backup_store_file_sync, path)
|
|
167
|
+
except FileNotFoundError:
|
|
168
|
+
return
|
|
169
|
+
except OSError as error:
|
|
170
|
+
if log:
|
|
171
|
+
log.warn(f"store: backup for {path} failed: {error}")
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class _PendingFlush:
|
|
176
|
+
__slots__ = ("future", "snapshot")
|
|
177
|
+
|
|
178
|
+
def __init__(self, snapshot: dict[str, Any], future: asyncio.Future[None]) -> None:
|
|
179
|
+
self.snapshot = snapshot
|
|
180
|
+
self.future = future
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# Serializes flushes and collapses saves arriving mid-flush into one trailing
|
|
184
|
+
# flush of the latest snapshot. Snapshots are whole-document states, so the
|
|
185
|
+
# newest one contains every earlier caller's write - a burst of N saves costs
|
|
186
|
+
# at most 2 flushes without weakening the durability guarantee.
|
|
187
|
+
class CoalescingWriter:
|
|
188
|
+
def __init__(
|
|
189
|
+
self,
|
|
190
|
+
flush: Callable[[dict[str, Any]], Awaitable[None]],
|
|
191
|
+
log: StoreLogger | None = None,
|
|
192
|
+
) -> None:
|
|
193
|
+
self._flush = flush
|
|
194
|
+
self._log = log
|
|
195
|
+
self._in_flight: asyncio.Task[None] | None = None
|
|
196
|
+
self._pending: _PendingFlush | None = None
|
|
197
|
+
|
|
198
|
+
def write(self, snapshot: dict[str, Any]) -> Awaitable[None]:
|
|
199
|
+
if self._in_flight is None:
|
|
200
|
+
self._in_flight = asyncio.ensure_future(self._run(snapshot))
|
|
201
|
+
return self._in_flight
|
|
202
|
+
|
|
203
|
+
if self._pending is not None:
|
|
204
|
+
self._pending.snapshot = snapshot
|
|
205
|
+
return self._pending.future
|
|
206
|
+
|
|
207
|
+
self._pending = _PendingFlush(snapshot, asyncio.get_running_loop().create_future())
|
|
208
|
+
return self._pending.future
|
|
209
|
+
|
|
210
|
+
async def idle(self) -> None:
|
|
211
|
+
while (in_flight := self._in_flight) is not None:
|
|
212
|
+
try:
|
|
213
|
+
await in_flight
|
|
214
|
+
except asyncio.CancelledError:
|
|
215
|
+
# Re-raise only our own cancellation; a cancelled flush task
|
|
216
|
+
# must not abort the drain loop.
|
|
217
|
+
if not in_flight.cancelled():
|
|
218
|
+
raise
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
async def _run(self, snapshot: dict[str, Any]) -> None:
|
|
223
|
+
try:
|
|
224
|
+
await self._flush(snapshot)
|
|
225
|
+
finally:
|
|
226
|
+
pending = self._pending
|
|
227
|
+
self._pending = None
|
|
228
|
+
|
|
229
|
+
if pending is not None:
|
|
230
|
+
task = asyncio.ensure_future(self._run(pending.snapshot))
|
|
231
|
+
task.add_done_callback(_settler(pending.future))
|
|
232
|
+
self._in_flight = task
|
|
233
|
+
else:
|
|
234
|
+
self._in_flight = None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _settler(future: asyncio.Future[None]) -> Callable[[asyncio.Task[None]], None]:
|
|
238
|
+
# Every handed-out awaitable must settle no matter how the trailing flush
|
|
239
|
+
# ends (success, error, or cancellation) - a pending future here would
|
|
240
|
+
# hang its awaiting caller forever.
|
|
241
|
+
def settle(task: asyncio.Task[None]) -> None:
|
|
242
|
+
if future.done():
|
|
243
|
+
return
|
|
244
|
+
if task.cancelled():
|
|
245
|
+
future.cancel()
|
|
246
|
+
elif (error := task.exception()) is not None:
|
|
247
|
+
future.set_exception(error)
|
|
248
|
+
else:
|
|
249
|
+
future.set_result(None)
|
|
250
|
+
|
|
251
|
+
return settle
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@dataclass(frozen=True)
|
|
255
|
+
class PluginLocation:
|
|
256
|
+
pass
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@dataclass(frozen=True)
|
|
260
|
+
class CameraLocation:
|
|
261
|
+
camera_id: str
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@dataclass(frozen=True)
|
|
265
|
+
class SensorLocation:
|
|
266
|
+
camera_id: str
|
|
267
|
+
sensor_type: str
|
|
268
|
+
sensor_name: str
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
StoreLocation = PluginLocation | CameraLocation | SensorLocation
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def read_location(config: dict[str, Any], location: StoreLocation) -> dict[str, Any] | None:
|
|
275
|
+
if isinstance(location, PluginLocation):
|
|
276
|
+
return config.get("plugin")
|
|
277
|
+
if isinstance(location, CameraLocation):
|
|
278
|
+
return (config.get("cameras") or {}).get(location.camera_id)
|
|
279
|
+
by_camera = (config.get("sensors") or {}).get(location.camera_id) or {}
|
|
280
|
+
return (by_camera.get(location.sensor_type) or {}).get(location.sensor_name)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def write_location(config: dict[str, Any], location: StoreLocation, values: dict[str, Any]) -> None:
|
|
284
|
+
if isinstance(location, PluginLocation):
|
|
285
|
+
config["plugin"] = values
|
|
286
|
+
return
|
|
287
|
+
if isinstance(location, CameraLocation):
|
|
288
|
+
config.setdefault("cameras", {})[location.camera_id] = values
|
|
289
|
+
return
|
|
290
|
+
by_camera = config.setdefault("sensors", {}).setdefault(location.camera_id, {})
|
|
291
|
+
by_camera.setdefault(location.sensor_type, {})[location.sensor_name] = values
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def delete_location(config: dict[str, Any], location: StoreLocation) -> None:
|
|
295
|
+
if isinstance(location, PluginLocation):
|
|
296
|
+
config.pop("plugin", None)
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
if isinstance(location, CameraLocation):
|
|
300
|
+
cameras = config.get("cameras")
|
|
301
|
+
if isinstance(cameras, dict):
|
|
302
|
+
cameras.pop(location.camera_id, None)
|
|
303
|
+
_prune_if_empty(config, "cameras")
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
sensors = config.get("sensors")
|
|
307
|
+
if not isinstance(sensors, dict):
|
|
308
|
+
return
|
|
309
|
+
by_camera = sensors.get(location.camera_id)
|
|
310
|
+
if not isinstance(by_camera, dict):
|
|
311
|
+
return
|
|
312
|
+
by_type = by_camera.get(location.sensor_type)
|
|
313
|
+
if not isinstance(by_type, dict):
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
by_type.pop(location.sensor_name, None)
|
|
317
|
+
_prune_if_empty(by_camera, location.sensor_type)
|
|
318
|
+
_prune_if_empty(sensors, location.camera_id)
|
|
319
|
+
_prune_if_empty(config, "sensors")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _prune_if_empty(parent: dict[str, Any], key: str) -> None:
|
|
323
|
+
child = parent.get(key)
|
|
324
|
+
if isinstance(child, dict) and not child:
|
|
325
|
+
del parent[key]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
_CANONICAL_SECTIONS = ("plugin", "cameras", "sensors")
|
|
329
|
+
_LEGACY_SENSOR_MARKER = ":sensor:"
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def is_canonical_layout(payload: dict[str, Any]) -> bool:
|
|
333
|
+
return all(key in _CANONICAL_SECTIONS for key in payload)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# Go-SDK legacy blobs ('<pluginId>.plugin', ...) are remapped by the Go SDK
|
|
337
|
+
# itself, not here: server and plugin update independently, and a plugin on
|
|
338
|
+
# an old SDK must keep finding its keys untouched.
|
|
339
|
+
def _is_go_legacy_layout(payload: dict[str, Any], plugin_id: str) -> bool:
|
|
340
|
+
return len(payload) > 0 and all(key.startswith(f"{plugin_id}.") for key in payload)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def remap_legacy_layout(
|
|
344
|
+
payload: dict[str, Any], plugin_id: str, log: StoreLogger | None = None
|
|
345
|
+
) -> dict[str, Any]:
|
|
346
|
+
if is_canonical_layout(payload) or _is_go_legacy_layout(payload, plugin_id):
|
|
347
|
+
return payload
|
|
348
|
+
|
|
349
|
+
canonical: dict[str, Any] = {
|
|
350
|
+
section: payload[section] for section in _CANONICAL_SECTIONS if section in payload
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
for key, values in payload.items():
|
|
354
|
+
if key in _CANONICAL_SECTIONS:
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
if key == "storage":
|
|
358
|
+
# In a mixed legacy+canonical document the canonical section is the
|
|
359
|
+
# newer write - the legacy duplicate is stale and must never win.
|
|
360
|
+
if "plugin" in canonical:
|
|
361
|
+
if log:
|
|
362
|
+
log.warn(
|
|
363
|
+
f"store: legacy key 'storage' dropped — canonical 'plugin' already present ({plugin_id})"
|
|
364
|
+
)
|
|
365
|
+
continue
|
|
366
|
+
canonical["plugin"] = values
|
|
367
|
+
continue
|
|
368
|
+
|
|
369
|
+
sensor_idx = key.find(_LEGACY_SENSOR_MARKER)
|
|
370
|
+
if sensor_idx != -1:
|
|
371
|
+
# <camId>:sensor:<type>:<pluginId>:<name> - type and pluginId contain
|
|
372
|
+
# no colons; the name keeps everything after them verbatim.
|
|
373
|
+
camera_id = key[:sensor_idx]
|
|
374
|
+
rest = key[sensor_idx + len(_LEGACY_SENSOR_MARKER) :]
|
|
375
|
+
type_end = rest.find(":")
|
|
376
|
+
plugin_end = rest.find(":", type_end + 1)
|
|
377
|
+
if type_end != -1 and plugin_end != -1:
|
|
378
|
+
sensor_type = rest[:type_end]
|
|
379
|
+
sensor_name = rest[plugin_end + 1 :]
|
|
380
|
+
by_camera = canonical.setdefault("sensors", {}).setdefault(camera_id, {})
|
|
381
|
+
if sensor_name in by_camera.get(sensor_type, {}):
|
|
382
|
+
if log:
|
|
383
|
+
log.warn(
|
|
384
|
+
f"store: legacy sensor key '{key}' dropped — canonical target already present ({plugin_id})"
|
|
385
|
+
)
|
|
386
|
+
continue
|
|
387
|
+
by_camera.setdefault(sensor_type, {})[sensor_name] = values
|
|
388
|
+
continue
|
|
389
|
+
if log:
|
|
390
|
+
log.warn(f"store: unparsable legacy sensor key '{key}' kept under cameras ({plugin_id})")
|
|
391
|
+
|
|
392
|
+
if key in (canonical.get("cameras") or {}):
|
|
393
|
+
if log:
|
|
394
|
+
log.warn(
|
|
395
|
+
f"store: legacy key '{key}' dropped — canonical 'cameras.{key}' already present ({plugin_id})"
|
|
396
|
+
)
|
|
397
|
+
continue
|
|
398
|
+
canonical.setdefault("cameras", {})[key] = values
|
|
399
|
+
|
|
400
|
+
return canonical
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# Values must round-trip identically through all three runtimes' msgpack
|
|
404
|
+
# codecs - anything outside the shared domain is refused up front.
|
|
405
|
+
def validate_store_value(key: str, value: Any) -> None:
|
|
406
|
+
_walk_value(value, key, 0)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
_MAX_VALUE_DEPTH = 64
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _walk_value(value: Any, path: str, depth: int) -> None:
|
|
413
|
+
# Depth cap: rejects circular references (and absurd nesting) with a clear
|
|
414
|
+
# error instead of a RecursionError.
|
|
415
|
+
if depth > _MAX_VALUE_DEPTH:
|
|
416
|
+
raise ValueError(
|
|
417
|
+
f"store: value at '{path}' exceeds {_MAX_VALUE_DEPTH} nesting levels — circular reference?"
|
|
418
|
+
)
|
|
419
|
+
# bool is an int subclass, so it must be accepted before the int check.
|
|
420
|
+
if value is None or isinstance(value, bool | str):
|
|
421
|
+
return
|
|
422
|
+
|
|
423
|
+
if isinstance(value, int):
|
|
424
|
+
if abs(value) > _MAX_SAFE_INTEGER:
|
|
425
|
+
raise ValueError(f"store: value at '{path}' exceeds the float64-safe integer range (±2^53)")
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
if isinstance(value, float):
|
|
429
|
+
if math.isnan(value) or math.isinf(value):
|
|
430
|
+
raise ValueError(f"store: value at '{path}' is {value} — NaN/Infinity are not storable")
|
|
431
|
+
return
|
|
432
|
+
|
|
433
|
+
if isinstance(value, bytes | bytearray | memoryview):
|
|
434
|
+
raise ValueError(
|
|
435
|
+
f"store: value at '{path}' is binary — large artifacts belong in files under the plugin storage dir"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
if isinstance(value, list | tuple):
|
|
439
|
+
for index, entry in enumerate(value):
|
|
440
|
+
_walk_value(entry, f"{path}[{index}]", depth + 1)
|
|
441
|
+
return
|
|
442
|
+
|
|
443
|
+
if isinstance(value, dict):
|
|
444
|
+
for key, entry in value.items():
|
|
445
|
+
if not isinstance(key, str):
|
|
446
|
+
raise ValueError(f"store: map key {key!r} at '{path}' is not a string")
|
|
447
|
+
_walk_value(entry, f"{path}.{key}", depth + 1)
|
|
448
|
+
return
|
|
449
|
+
|
|
450
|
+
raise ValueError(
|
|
451
|
+
f"store: value at '{path}' is a {type(value).__name__} instance — only plain objects are storable"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class PluginStoreFile:
|
|
456
|
+
def __init__(self, volume_dir: str, plugin_id: str, log: StoreLogger | None = None) -> None:
|
|
457
|
+
self._volume_dir = volume_dir
|
|
458
|
+
self._plugin_id = plugin_id
|
|
459
|
+
self._log = log
|
|
460
|
+
self._path = str(Path(volume_dir) / STORE_FILE_NAME)
|
|
461
|
+
self._writer = CoalescingWriter(self._write_snapshot, log)
|
|
462
|
+
self._payload: dict[str, Any] = {}
|
|
463
|
+
self._closed = True
|
|
464
|
+
|
|
465
|
+
@property
|
|
466
|
+
def path(self) -> str:
|
|
467
|
+
return self._path
|
|
468
|
+
|
|
469
|
+
async def open(self) -> None:
|
|
470
|
+
await asyncio.to_thread(os.makedirs, self._volume_dir, exist_ok=True)
|
|
471
|
+
await asyncio.to_thread(self._remove_orphaned_tmp_files)
|
|
472
|
+
|
|
473
|
+
payload = await read_store_file(self._path, self._log)
|
|
474
|
+
if payload is None:
|
|
475
|
+
# Persist the initial envelope so later opens skip the legacy probe.
|
|
476
|
+
payload = {}
|
|
477
|
+
await write_store_file(self._path, payload, self._log)
|
|
478
|
+
|
|
479
|
+
remapped = remap_legacy_layout(payload, self._plugin_id, self._log)
|
|
480
|
+
if remapped is not payload:
|
|
481
|
+
await write_store_file(self._path, remapped, self._log)
|
|
482
|
+
|
|
483
|
+
self._payload = remapped
|
|
484
|
+
await backup_store_file(self._path, self._log)
|
|
485
|
+
self._closed = False
|
|
486
|
+
|
|
487
|
+
def get(self) -> dict[str, Any]:
|
|
488
|
+
return self._payload
|
|
489
|
+
|
|
490
|
+
async def put(self, payload: dict[str, Any]) -> None:
|
|
491
|
+
if self._closed:
|
|
492
|
+
# A silently "successful" no-op here would lose the write during
|
|
493
|
+
# shutdown windows. Fail loudly instead.
|
|
494
|
+
raise RuntimeError(f"store: put on closed store {self._path}")
|
|
495
|
+
self._payload = payload
|
|
496
|
+
await self._writer.write(payload)
|
|
497
|
+
|
|
498
|
+
async def close(self) -> None:
|
|
499
|
+
self._closed = True
|
|
500
|
+
await self._writer.idle()
|
|
501
|
+
|
|
502
|
+
async def _write_snapshot(self, snapshot: dict[str, Any]) -> None:
|
|
503
|
+
await write_store_file(self._path, snapshot, self._log)
|
|
504
|
+
|
|
505
|
+
def _remove_orphaned_tmp_files(self) -> None:
|
|
506
|
+
try:
|
|
507
|
+
names = os.listdir(self._volume_dir)
|
|
508
|
+
except OSError:
|
|
509
|
+
return
|
|
510
|
+
for name in names:
|
|
511
|
+
if name.startswith(STORE_FILE_NAME) and ".tmp-" in name:
|
|
512
|
+
with suppress(OSError):
|
|
513
|
+
os.unlink(os.path.join(self._volume_dir, name))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
7
|
+
|
|
8
|
+
from lmdbm import Lmdb
|
|
9
|
+
|
|
10
|
+
from plugins.runtime.python.store import write_store_file
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from plugins.runtime.python.store import StoreLogger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class JsonLmdb(Lmdb[Literal["config"], dict[str, Any]]):
|
|
19
|
+
def _pre_key(self, key: Literal["config"]) -> bytes:
|
|
20
|
+
return key.encode("utf-8")
|
|
21
|
+
|
|
22
|
+
def _post_key(self, key: bytes) -> Literal["config"]:
|
|
23
|
+
_key: Any = key.decode("utf-8")
|
|
24
|
+
return _key
|
|
25
|
+
|
|
26
|
+
def _pre_value(self, value: dict[str, Any]) -> bytes:
|
|
27
|
+
return json.dumps(value).encode("utf-8")
|
|
28
|
+
|
|
29
|
+
def _post_value(self, value: bytes) -> dict[str, Any]:
|
|
30
|
+
return json.loads(value.decode("utf-8"))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def needs_lmdbm_migration(volume_dir: Path, store_path: Path) -> bool:
|
|
34
|
+
return not store_path.exists() and (volume_dir / "data.mdb").exists()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def migrate_legacy_lmdbm(volume_dir: Path, store_path: Path, log: StoreLogger) -> None:
|
|
38
|
+
start = time.perf_counter()
|
|
39
|
+
|
|
40
|
+
def read_legacy_config() -> dict[str, Any] | None:
|
|
41
|
+
legacy_db = cast(
|
|
42
|
+
"Lmdb[Literal['config'], dict[str, Any]]",
|
|
43
|
+
JsonLmdb.open(file=str(volume_dir), flag="c"),
|
|
44
|
+
)
|
|
45
|
+
try:
|
|
46
|
+
return cast("dict[str, Any] | None", legacy_db.get("config"))
|
|
47
|
+
finally:
|
|
48
|
+
legacy_db.close()
|
|
49
|
+
|
|
50
|
+
config = await asyncio.to_thread(read_legacy_config)
|
|
51
|
+
|
|
52
|
+
if not config:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
await write_store_file(str(store_path), config, log)
|
|
56
|
+
log.warn(
|
|
57
|
+
f"store: migrated legacy lmdbm env {volume_dir} -> {store_path} "
|
|
58
|
+
f"({len(config)} top-level keys) in {(time.perf_counter() - start) * 1000:.1f}ms"
|
|
59
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Serializes flushes and collapses saves arriving mid-flush into one trailing
|
|
2
|
+
// flush of the latest snapshot. Snapshots are whole-document states, so the
|
|
3
|
+
// newest one contains every earlier caller's write — a burst of N saves costs
|
|
4
|
+
// at most 2 flushes without weakening the durability guarantee.
|
|
5
|
+
export class CoalescingWriter {
|
|
6
|
+
flush;
|
|
7
|
+
#inFlight;
|
|
8
|
+
#pending;
|
|
9
|
+
constructor(flush) {
|
|
10
|
+
this.flush = flush;
|
|
11
|
+
}
|
|
12
|
+
write(snapshot) {
|
|
13
|
+
if (!this.#inFlight) {
|
|
14
|
+
this.#inFlight = this.#run(snapshot);
|
|
15
|
+
return this.#inFlight;
|
|
16
|
+
}
|
|
17
|
+
if (this.#pending) {
|
|
18
|
+
this.#pending.snapshot = snapshot;
|
|
19
|
+
return this.#pending.promise;
|
|
20
|
+
}
|
|
21
|
+
let resolve;
|
|
22
|
+
let reject;
|
|
23
|
+
const promise = new Promise((res, rej) => {
|
|
24
|
+
resolve = res;
|
|
25
|
+
reject = rej;
|
|
26
|
+
});
|
|
27
|
+
this.#pending = { snapshot, promise, resolve, reject };
|
|
28
|
+
return promise;
|
|
29
|
+
}
|
|
30
|
+
async idle() {
|
|
31
|
+
while (this.#inFlight) {
|
|
32
|
+
await this.#inFlight.catch(() => { });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async #run(snapshot) {
|
|
36
|
+
try {
|
|
37
|
+
await this.flush(snapshot);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
const pending = this.#pending;
|
|
41
|
+
this.#pending = undefined;
|
|
42
|
+
if (pending) {
|
|
43
|
+
this.#inFlight = this.#run(pending.snapshot);
|
|
44
|
+
this.#inFlight.then(pending.resolve, pending.reject);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
this.#inFlight = undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=coalescingWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coalescingWriter.js","sourceRoot":"","sources":["../../../../../src/plugins/store/coalescingWriter.ts"],"names":[],"mappings":"AAOA,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAC9E,gEAAgE;AAChE,MAAM,OAAO,gBAAgB;IAIE;IAH7B,SAAS,CAAiB;IAC1B,QAAQ,CAAgB;IAExB,YAA6B,KAAuD;QAAvD,UAAK,GAAL,KAAK,CAAkD;IAAG,CAAC;IAEjF,KAAK,CAAC,QAA6B;QACxC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACrC,OAAO,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;QAED,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAClC,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAC/B,CAAC;QAED,IAAI,OAAoB,CAAC;QACzB,IAAI,MAAiC,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC7C,OAAO,GAAG,GAAG,CAAC;YACd,MAAM,GAAG,GAAG,CAAC;QACf,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QACvD,OAAO,OAAO,CAAC;IACjB,CAAC;IAEM,KAAK,CAAC,IAAI;QACf,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAA6B;QACtC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC9B,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;YAE1B,IAAI,OAAO,EAAE,CAAC;gBACZ,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC7C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YACvD,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Packr } from 'msgpackr';
|
|
2
|
+
import { crc32 } from 'node:zlib';
|
|
3
|
+
// Standard msgpack only — records/string bundles are msgpackr-proprietary and
|
|
4
|
+
// unreadable by the Go/Python sides; int64AsType keeps foreign integers in
|
|
5
|
+
// the float64 number domain instead of surfacing as BigInt.
|
|
6
|
+
const packr = new Packr({ useRecords: false, bundleStrings: false, int64AsType: 'number' });
|
|
7
|
+
const MAGIC = Buffer.from('CUI1');
|
|
8
|
+
export class StoreCorruptError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(`store envelope: ${message}`);
|
|
11
|
+
this.name = 'StoreCorruptError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function encodeEnvelope(payload) {
|
|
15
|
+
const body = packr.pack(payload);
|
|
16
|
+
const crc = Buffer.allocUnsafe(4);
|
|
17
|
+
crc.writeUInt32LE(crc32(body) >>> 0);
|
|
18
|
+
return Buffer.concat([MAGIC, body, crc]);
|
|
19
|
+
}
|
|
20
|
+
export function decodeEnvelope(buf) {
|
|
21
|
+
if (buf.length < MAGIC.length + 4 || !buf.subarray(0, MAGIC.length).equals(MAGIC)) {
|
|
22
|
+
throw new StoreCorruptError('bad magic');
|
|
23
|
+
}
|
|
24
|
+
const body = buf.subarray(MAGIC.length, buf.length - 4);
|
|
25
|
+
if (crc32(body) >>> 0 !== buf.readUInt32LE(buf.length - 4)) {
|
|
26
|
+
throw new StoreCorruptError('crc mismatch');
|
|
27
|
+
}
|
|
28
|
+
let payload;
|
|
29
|
+
try {
|
|
30
|
+
payload = packr.unpack(body);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new StoreCorruptError(`payload decode failed: ${error instanceof Error ? error.message : error}`);
|
|
34
|
+
}
|
|
35
|
+
if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
36
|
+
throw new StoreCorruptError('payload is not a map');
|
|
37
|
+
}
|
|
38
|
+
return payload;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../../../src/plugins/store/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAElC,8EAA8E;AAC9E,2EAA2E;AAC3E,4DAA4D;AAC5D,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;AAE5F,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAElC,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,mBAAmB,OAAO,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAAC,OAA4B;IACzD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAClC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACrC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAClF,MAAM,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACxD,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,iBAAiB,CAAC,cAAc,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,iBAAiB,CAAC,0BAA0B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC1G,CAAC;IAED,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,iBAAiB,CAAC,sBAAsB,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,OAA8B,CAAC;AACxC,CAAC"}
|