sciveo 0.3.2__tar.gz → 0.3.4__tar.gz
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.
- {sciveo-0.3.2 → sciveo-0.3.4}/PKG-INFO +16 -3
- sciveo-0.3.2/sciveo.egg-info/PKG-INFO → sciveo-0.3.4/README.md +15 -27
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/terminal.py +1 -1
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/config.py +3 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/filesystem.py +64 -15
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/server.py +86 -9
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/defaults.py +2 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/version.py +1 -1
- sciveo-0.3.2/README.md → sciveo-0.3.4/sciveo.egg-info/PKG-INFO +40 -2
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo.egg-info/SOURCES.txt +2 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_cli_defaults.py +18 -0
- sciveo-0.3.4/test/test_cli_regression.py +143 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_nvr.py +75 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_sniff.py +1 -1
- sciveo-0.3.4/test/test_storage_downloads.py +145 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/MANIFEST.in +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/pyproject.toml +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/admin/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/admin/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/admin/config.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/admin/device.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/admin/diagnostics.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/admin/network.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/admin/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/clients/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/clients/anthropic.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/clients/gemini.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/clients/groq.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/clients/hf.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/clients/ollama.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/clients/openai.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/configurations/coder.md +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/configurations/ops.md +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/configurations/researcher.md +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/configurations/reviewer.md +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/configurations/tester.md +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/console.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/evaluation/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/evaluation/cases.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/evaluation/challenges.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/evaluation/checks.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/evaluation/local_chat.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/evaluation/preflight.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/evaluation/report.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/evaluation/runner.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/media.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/orchestration.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/protocol.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/render.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/runtime.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/specialization.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/thinking.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/tools/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/tools/images.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/tools/os.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/agents/tools/web.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/api/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/api/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/api/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/api/ipc.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/api/prediction.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/api/predictors.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/api/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/api/upload.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/certification/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/certification/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/certification/evaluator.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/certification/profiles.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/certification/renderer.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/certification/scenarios.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/chat/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/chat/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/chat/client.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/chat/crypto.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/chat/protocol.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/chat/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/chat/store.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/chat/ui.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/common/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/common/configuration.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/common/model.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/common/optimizers.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/common/sampling.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/content/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/content/dataset.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/content/experiment.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/content/project.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/content/runner.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/db/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/db/read_table.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/extensions/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/extensions/assets/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/extensions/assets/vscode-sciveo-0.1.0.vsix +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/extensions/catalog.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/extensions/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/help.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/iam/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/iam/access.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/iam/api.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/iam/client.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/iam/database.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/iam/identity.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/iam/policy.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/iam/users.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/can.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/device.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/dlms.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/dlms_io.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/iec104.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/iec104_listener.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/iec61850.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/modbus/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/modbus_device.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/opcua.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/industrial/sdi12.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/log.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/logger.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/cam.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/cameras.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/discovery.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/draw.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/asteroid.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/base_move.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/circle.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/game.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/geometrics.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/gravity_ball.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/laser.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/rocket.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/space_asteroids.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/games/asteroids/volume_bar.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/gst_server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/motion_detection.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/nvr.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/onvif.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/onvif_device.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/player.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/readers.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/recording_timeline.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/recordings.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/rpi.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/rtp.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/rtsp.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/rtsp_client.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/rtsp_scan.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/settings.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/storage.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/system_player.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/capture/transcode.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/encoders/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/encoders/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/encoders/normalizer.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/nlp/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/nlp/search.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/time_series/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/time_series/dataset.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/time_series/predictor.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/time_series/trainer.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/ml/time_series/window_generator.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/job_daemon.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/layouts/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/layouts/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/local.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/pipeline.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/postprocessors/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/postprocessors/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/postprocessors/default.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/audio/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/audio/audio.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/audio/audio_extractor_process.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/aws.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/file/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/file/archive.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/album.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/album_in_image.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/depth_esimation.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/embeddings.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/filters.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/generators.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/histogram.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/mask.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/object_detection.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/resize.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/segmentation.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/image/watermark.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/media_info.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/nlp/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/nlp/address.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/qr.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/sci/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/sci/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/sci/dataset.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/sci/time_series/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/sci/time_series/predictor.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/sci/time_series/trainer.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/tpu_base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/video/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/video/generators.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/video/motion_detection.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/video/resize.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/video/video_album.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/video/video_frames.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/processors/video/video_resample.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/queues.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/web/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/pipelines/web/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/tools/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/media/tools/video_interactive.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/dataset/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/dataset/object_detection.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/energy/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/energy/optimization.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/energy/solar.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/evaluation/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/evaluation/markdown.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/evaluation/object_detection.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/description.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/embeddings.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/object_detection.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/artifact.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/engines.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/evaluate.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/ipc.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/model.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/results.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/runtime.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/runtime_stats.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/torch_net.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/train.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/train_checkpoint.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/train_data.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/train_loss.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/sciveyolo/train_validate.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/tools.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/transformers.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/images/vlm.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/nlp/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/nlp/embeddings.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/nlp/tokenizers/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/nlp/tokenizers/bpe.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/video/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ml/video/description.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/monitor.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/plant/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/plant/ems300.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/plant/sim.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/plant/tools.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/power.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/start.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/watchdog/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/watchdog/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/watchdog/gateway.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/watchdog/network.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/watchdog/privileged.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/watchdog/process.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/monitoring/watchdog/repair.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/camera.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/health.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/http_server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/http_tools.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/interfaces.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/l2.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/catalog.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/dashboard.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/discovery.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/models.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/profiles.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/session.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/sunspec.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/terminal.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/modbus/tools.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/mqtt.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/mqtt_server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/client.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/device.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/discovery.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/ptz.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/security.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/services.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/settings.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/onvif/xml.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/selectors.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/av1.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/capture.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/codecs.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/inspector.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/modbus.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/modbus_cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/modbus_inventory.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/modbus_pdu.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/network_cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/packets.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/protocols.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/streams.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniff/traffic.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/sniffer.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/snmp/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/snmp/oids.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/snmp/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/snmp/snapshot.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/snmp/tools.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/ssh.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/network/tools.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/notifications/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/notifications/base.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/notifications/slack.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/notifications/telegram.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/notifications/viber.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ops/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ops/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ops/doctor.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ops/fleet.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ops/industrial.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/plant/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/plant/measurement.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/plant/modbus.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/auth.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/client.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/config.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/controller.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/gateway.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/metadata.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/node.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/placement.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/protocol.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/repair.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cluster/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/cors.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/iam.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/local.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/metadata.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/mutation.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/policy.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/post.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/process.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/runtime.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/s3.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/storage/sync.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/array.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/aws/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/aws/priority_queue.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/aws/s3.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/common.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/complexity.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/compress.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/configuration.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/configuration_cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/configuration_tui.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/crypto.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/daemon.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/draw/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/draw/contours.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/formating.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/hardware.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/http.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/logger.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/logggin.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/os.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/preferences.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/queue.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/random.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/remote.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/simple_counter.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/sorting.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/synchronized.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/timers.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/tools/totp.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/gui/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/gui/html/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/gui/html/client.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/gui/html/renderer.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/gui/html/server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/gui/renderer.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/gui/window.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/html.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/tables.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/containers.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/dialogs.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/documents.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/gallery.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/html.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/images.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/layout.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/live.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/measurements.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/panels.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/progress.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/screen.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/selectors.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/status.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/streaming.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/tables.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/terminal.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/text.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/themes.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/trees.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/ui/term/widgets.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/web/__init__.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo/web/common.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo.egg-info/dependency_links.txt +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo.egg-info/entry_points.txt +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo.egg-info/requires.txt +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/sciveo.egg-info/top_level.txt +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/setup.cfg +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/setup.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_admin.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_admin_concurrency.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_agents.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_agents_chat_listener.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_agents_evaluation.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_agents_evaluation_extended.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_agents_image_integration.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_agents_orchestration.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_agents_thinking.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_api_client.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_camera_discovery.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_camera_liveness.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_certification.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_chat.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_chint_dtsu666_catalog.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_cli_delegation.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_cli_modules.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_cli_themes.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_complexity.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_compress.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_configuration.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_configuration_tui.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_crypto.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_eval_markdown.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_extensions_catalog.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_gui.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_gui_http.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_help.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_iam.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_iam_database.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_industrial.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_industrial_protocols.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_l2.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_log.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_media_cli.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_ml_datasets.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_ml_energy.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_ml_ipc.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_ml_sciveyolo.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_ml_vlm.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_modbus.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_modbus_dashboard.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_modbus_models.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_modbus_sessions.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_monitor_http.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_monitoring.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_network_health.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_network_interfaces.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_network_selector.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_network_sniffer.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_notifications.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_nvr_capture.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_nvr_faulty_video.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_nvr_transcode.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_onvif.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_onvif_ptz.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_onvif_server.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_onvif_settings.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_ops_doctor_fleet.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_plant_modbus.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_predictor_transport.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_preferences.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_protocol_emulators_integration.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_protocol_io.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_recordings.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_rtsp_play.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_rtsp_scan.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_rtsp_viewer.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_runner.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_sampling.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_sciveyolo_ipc.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_sdm630_catalog.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_sniff_runtime.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_snmp.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_sorting.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_ssh.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_storage.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_storage_auth_hardening.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_storage_cluster.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_storage_cors.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_storage_iam.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_storage_iam_users.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_storage_metadata_hardening.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_storage_security_limits.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_sungrow_sh10rt_catalog.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_containers.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_dialogs.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_documents.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_gallery.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_html.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_progress.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_reports.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_selectors.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_streaming.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_table_options.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_tables.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_themes.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_term_views.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_tokenizers.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_totp.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_ui_html.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_ui_tables.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_video_codecs.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_video_player.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_watchdog_gateway.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_watchdog_network.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_watchdog_privileged.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_watchdog_repair.py +0 -0
- {sciveo-0.3.2 → sciveo-0.3.4}/test/test_watchdog_service.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sciveo
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Description-Content-Type: text/markdown
|
|
5
5
|
Provides-Extra: mon
|
|
6
6
|
Provides-Extra: agent
|
|
@@ -2396,6 +2396,19 @@ staging, and `BASE/video` for retained recordings. No separate `tmp`, `video`,
|
|
|
2396
2396
|
`motion.src`, or `motion.dst` paths are needed. Camera-specific motion settings
|
|
2397
2397
|
remain under `motion`.
|
|
2398
2398
|
|
|
2399
|
+
To keep temporary video I/O in RAM while retaining recordings on those HDD roots,
|
|
2400
|
+
set `"temp_path": "/mnt/nvr-ram"` to a separately mounted, size-limited tmpfs.
|
|
2401
|
+
Capture, motion processing and upload staging then use `TEMP/tmp2`, `TEMP/tmp3`
|
|
2402
|
+
and `TEMP/tmp4`; archives still use the ordered `BASE/video` roots and their
|
|
2403
|
+
capacity-based cleanup. The same free-space reserve applies to the temporary
|
|
2404
|
+
filesystem, so size it above that reserve plus the expected queued video volume.
|
|
2405
|
+
When temporary space runs low, capture waits while processing drains the queues;
|
|
2406
|
+
it does not switch temporary writes onto an HDD or delete archives to free RAM.
|
|
2407
|
+
Make the NVR service require the tmpfs mount at boot. RAM queues are volatile
|
|
2408
|
+
across reboot; finalized HDD archives remain persistent. Existing HDD queues are
|
|
2409
|
+
not moved or deleted when changing `temp_path`. Point external uploaders at
|
|
2410
|
+
`TEMP/tmp4` when this option is enabled.
|
|
2411
|
+
|
|
2399
2412
|
Optional per-stage transcoding is disabled unless configured. Put `transcode`
|
|
2400
2413
|
under `cam.CAMERA` to transcode every completed clip before archival and motion
|
|
2401
2414
|
processing. Put it under `motion.CAMERA` to transcode **only motion-selected**
|
|
@@ -2432,7 +2445,7 @@ processing continues without repeatedly encoding the same failed clip. Storage
|
|
|
2432
2445
|
write failures retain the source for retry; no incomplete MP4 enters the uploader
|
|
2433
2446
|
queue. An all-zero motion grid skips video decoding entirely.
|
|
2434
2447
|
|
|
2435
|
-
|
|
2448
|
+
Stages without a separate `temp_path` use the first available root in list order, skipping absent, unmounted,
|
|
2436
2449
|
or unwritable roots. Low space does not cause fallback. Free space must cover
|
|
2437
2450
|
both the incoming clip and the larger of `min_free_bytes` and `min_free_ratio`
|
|
2438
2451
|
(defaults: 1 GiB and 5%). Capture rechecks
|
|
@@ -2473,7 +2486,7 @@ With no usable root, capture waits and queued files are retained until storage
|
|
|
2473
2486
|
recovers. This provides fallback, not replication: clips already on a disconnected
|
|
2474
2487
|
disk become available again when that disk returns.
|
|
2475
2488
|
|
|
2476
|
-
|
|
2489
|
+
Without `temp_path`, external uploaders must watch `BASE/tmp4` on **every** configured root, including
|
|
2477
2490
|
roots that recover after startup. Existing queued files are not relocated when
|
|
2478
2491
|
migrating configurations. Legacy single-directory `path.tmp`, `path.video`,
|
|
2479
2492
|
`motion.src`, and `motion.dst` configurations remain supported when `paths` is
|
|
@@ -1,28 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: sciveo
|
|
3
|
-
Version: 0.3.2
|
|
4
|
-
Description-Content-Type: text/markdown
|
|
5
|
-
Provides-Extra: mon
|
|
6
|
-
Provides-Extra: agent
|
|
7
|
-
Provides-Extra: agent-local
|
|
8
|
-
Provides-Extra: net
|
|
9
|
-
Provides-Extra: server
|
|
10
|
-
Provides-Extra: storage
|
|
11
|
-
Provides-Extra: storage-postgres
|
|
12
|
-
Provides-Extra: chat
|
|
13
|
-
Provides-Extra: media
|
|
14
|
-
Provides-Extra: media-ml
|
|
15
|
-
Provides-Extra: sciveyolo
|
|
16
|
-
Provides-Extra: sciveyolo-gpu
|
|
17
|
-
Provides-Extra: sciveyolo-trt
|
|
18
|
-
Provides-Extra: web
|
|
19
|
-
Provides-Extra: db
|
|
20
|
-
Provides-Extra: power
|
|
21
|
-
Provides-Extra: industrial
|
|
22
|
-
Provides-Extra: energy
|
|
23
|
-
Provides-Extra: all
|
|
24
|
-
Provides-Extra: ml
|
|
25
|
-
|
|
26
1
|
# SCIVEO
|
|
27
2
|
|
|
28
3
|
Sciveo is a Python toolkit and CLI for ML/AI workflows, engineering automation,
|
|
@@ -2396,6 +2371,19 @@ staging, and `BASE/video` for retained recordings. No separate `tmp`, `video`,
|
|
|
2396
2371
|
`motion.src`, or `motion.dst` paths are needed. Camera-specific motion settings
|
|
2397
2372
|
remain under `motion`.
|
|
2398
2373
|
|
|
2374
|
+
To keep temporary video I/O in RAM while retaining recordings on those HDD roots,
|
|
2375
|
+
set `"temp_path": "/mnt/nvr-ram"` to a separately mounted, size-limited tmpfs.
|
|
2376
|
+
Capture, motion processing and upload staging then use `TEMP/tmp2`, `TEMP/tmp3`
|
|
2377
|
+
and `TEMP/tmp4`; archives still use the ordered `BASE/video` roots and their
|
|
2378
|
+
capacity-based cleanup. The same free-space reserve applies to the temporary
|
|
2379
|
+
filesystem, so size it above that reserve plus the expected queued video volume.
|
|
2380
|
+
When temporary space runs low, capture waits while processing drains the queues;
|
|
2381
|
+
it does not switch temporary writes onto an HDD or delete archives to free RAM.
|
|
2382
|
+
Make the NVR service require the tmpfs mount at boot. RAM queues are volatile
|
|
2383
|
+
across reboot; finalized HDD archives remain persistent. Existing HDD queues are
|
|
2384
|
+
not moved or deleted when changing `temp_path`. Point external uploaders at
|
|
2385
|
+
`TEMP/tmp4` when this option is enabled.
|
|
2386
|
+
|
|
2399
2387
|
Optional per-stage transcoding is disabled unless configured. Put `transcode`
|
|
2400
2388
|
under `cam.CAMERA` to transcode every completed clip before archival and motion
|
|
2401
2389
|
processing. Put it under `motion.CAMERA` to transcode **only motion-selected**
|
|
@@ -2432,7 +2420,7 @@ processing continues without repeatedly encoding the same failed clip. Storage
|
|
|
2432
2420
|
write failures retain the source for retry; no incomplete MP4 enters the uploader
|
|
2433
2421
|
queue. An all-zero motion grid skips video decoding entirely.
|
|
2434
2422
|
|
|
2435
|
-
|
|
2423
|
+
Stages without a separate `temp_path` use the first available root in list order, skipping absent, unmounted,
|
|
2436
2424
|
or unwritable roots. Low space does not cause fallback. Free space must cover
|
|
2437
2425
|
both the incoming clip and the larger of `min_free_bytes` and `min_free_ratio`
|
|
2438
2426
|
(defaults: 1 GiB and 5%). Capture rechecks
|
|
@@ -2473,7 +2461,7 @@ With no usable root, capture waits and queued files are retained until storage
|
|
|
2473
2461
|
recovers. This provides fallback, not replication: clips already on a disconnected
|
|
2474
2462
|
disk become available again when that disk returns.
|
|
2475
2463
|
|
|
2476
|
-
|
|
2464
|
+
Without `temp_path`, external uploaders must watch `BASE/tmp4` on **every** configured root, including
|
|
2477
2465
|
roots that recover after startup. Existing queued files are not relocated when
|
|
2478
2466
|
migrating configurations. Legacy single-directory `path.tmp`, `path.video`,
|
|
2479
2467
|
`motion.src`, and `motion.dst` configurations remain supported when `paths` is
|
|
@@ -280,7 +280,7 @@ class CameraDashboard:
|
|
|
280
280
|
|
|
281
281
|
@staticmethod
|
|
282
282
|
def selection_marker(record):
|
|
283
|
-
return {'rtsp': '▶', 'file': '
|
|
283
|
+
return {'rtsp': '▶', 'file': '▷', 'sniff': '»'}.get(record.get('source_kind'), '>')
|
|
284
284
|
|
|
285
285
|
@staticmethod
|
|
286
286
|
def label(record):
|
|
@@ -97,6 +97,8 @@ class StorageConfig:
|
|
|
97
97
|
self.iam = dict(iam or {})
|
|
98
98
|
self.iam_base_path = os.path.abspath(os.path.expanduser(str(iam_base_path))) if iam_base_path else None
|
|
99
99
|
self.http = self._normalize_http_config(http)
|
|
100
|
+
if not 0 <= self.http["max_downloads"] < self.threads:
|
|
101
|
+
raise ValueError("Storage http.max_downloads must be zero (unlimited) or smaller than threads")
|
|
100
102
|
self.limits = dict(limits or {})
|
|
101
103
|
self.retention = dict(retention or {})
|
|
102
104
|
if self.resume_free_ratio < self.min_free_ratio:
|
|
@@ -406,6 +408,7 @@ class StorageConfig:
|
|
|
406
408
|
"read_timeout": max(1.0, float(value.get("read_timeout") or min(timeout, 120))),
|
|
407
409
|
"lock_timeout": max(0.1, float(value.get("lock_timeout") or 30)),
|
|
408
410
|
"keepalive": max(1, int(value.get("keepalive") or 65)),
|
|
411
|
+
"max_downloads": int(value.get("max_downloads") or 0),
|
|
409
412
|
}
|
|
410
413
|
|
|
411
414
|
def db_label(self):
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
# 2026
|
|
10
10
|
#
|
|
11
11
|
|
|
12
|
+
import contextlib
|
|
12
13
|
import errno
|
|
13
14
|
import hashlib
|
|
14
15
|
import json
|
|
@@ -40,6 +41,14 @@ class StorageSizeExceeded(StoragePathError):
|
|
|
40
41
|
pass
|
|
41
42
|
|
|
42
43
|
|
|
44
|
+
class StorageUploadNotFound(StoragePathError):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class StoragePartNotFound(StoragePathError):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
43
52
|
class StorageRequestBodyError(StoragePathError):
|
|
44
53
|
def __init__(self, message, bytes_read=0, expected_size=None):
|
|
45
54
|
super().__init__(message)
|
|
@@ -52,6 +61,7 @@ class StoragePathMapper:
|
|
|
52
61
|
MARKER_FILE_NAME = ".sciveo-s3-prefix-marker"
|
|
53
62
|
ROOT_IDENTITY_FILE = ".sciveo-storage-root.json"
|
|
54
63
|
MUTATION_DIRECTORY = "mutations"
|
|
64
|
+
UNHEALTHY_RECHECK_INTERVAL = 30.0
|
|
55
65
|
|
|
56
66
|
@classmethod
|
|
57
67
|
def from_config(cls, config, metadata, namespace="sciveo-s3"):
|
|
@@ -1178,6 +1188,8 @@ class StoragePathMapper:
|
|
|
1178
1188
|
with self.health_lock:
|
|
1179
1189
|
state = dict(self.root_health[root_index])
|
|
1180
1190
|
interval = max(0.0, self.health_check_interval)
|
|
1191
|
+
if not state["healthy"]:
|
|
1192
|
+
interval = min(interval, self.UNHEALTHY_RECHECK_INTERVAL)
|
|
1181
1193
|
if not force and state["healthy"] and interval <= 0:
|
|
1182
1194
|
return
|
|
1183
1195
|
if not force and time.time() - float(state.get("last_checked") or 0.0) < interval:
|
|
@@ -1288,7 +1300,7 @@ class StoragePathMapper:
|
|
|
1288
1300
|
|
|
1289
1301
|
def health_monitor_loop(self):
|
|
1290
1302
|
intervals = [value for value in (self.health_check_interval, self.capacity_check_interval) if value > 0]
|
|
1291
|
-
interval = max(1.0, min(intervals))
|
|
1303
|
+
interval = max(1.0, min(min(intervals), self.UNHEALTHY_RECHECK_INTERVAL))
|
|
1292
1304
|
while not self.health_stop.wait(interval):
|
|
1293
1305
|
try:
|
|
1294
1306
|
self.refresh_unhealthy_roots(force=False)
|
|
@@ -1332,8 +1344,7 @@ class StoragePathMapper:
|
|
|
1332
1344
|
candidate = os.path.join(self.base_path(path), "multipart", safe_upload_id)
|
|
1333
1345
|
if os.path.exists(candidate):
|
|
1334
1346
|
return candidate
|
|
1335
|
-
|
|
1336
|
-
return os.path.join(self.base_path(self.paths[root_index]), "multipart", safe_upload_id)
|
|
1347
|
+
return os.path.join(self.base_path(self.paths[0]), "multipart", safe_upload_id)
|
|
1337
1348
|
|
|
1338
1349
|
def create_multipart_upload(self, bucket, key, storage_class="STANDARD", content_type=None, metadata=None, tags=None):
|
|
1339
1350
|
bucket = self.validate_bucket(bucket)
|
|
@@ -1367,8 +1378,27 @@ class StoragePathMapper:
|
|
|
1367
1378
|
raise StoragePathUnavailable(f"No writable storage paths are available for multipart upload: {last_error}")
|
|
1368
1379
|
|
|
1369
1380
|
def multipart_upload_info(self, upload_id):
|
|
1370
|
-
|
|
1371
|
-
|
|
1381
|
+
last_error = None
|
|
1382
|
+
for root_index in range(len(self.paths)):
|
|
1383
|
+
try:
|
|
1384
|
+
with open(os.path.join(self.multipart_root(upload_id, root_index=root_index), "source"), "r") as fp:
|
|
1385
|
+
return json.load(fp)
|
|
1386
|
+
except FileNotFoundError:
|
|
1387
|
+
continue
|
|
1388
|
+
except OSError as error:
|
|
1389
|
+
last_error = self.write_error(root_index, error)
|
|
1390
|
+
if last_error is not None:
|
|
1391
|
+
raise last_error
|
|
1392
|
+
for root_index in range(len(self.paths)):
|
|
1393
|
+
self.assert_root_present(root_index)
|
|
1394
|
+
raise StorageUploadNotFound("Multipart upload does not exist")
|
|
1395
|
+
|
|
1396
|
+
def missing_multipart_file(self, upload_id, root_index):
|
|
1397
|
+
# An abort may remove staging files while a part is still streaming.
|
|
1398
|
+
# Verify the actual disk before classifying this as an upload-level error.
|
|
1399
|
+
self.assert_root_present(root_index)
|
|
1400
|
+
self.multipart_upload_info(upload_id)
|
|
1401
|
+
return StoragePartNotFound("Multipart part does not exist")
|
|
1372
1402
|
|
|
1373
1403
|
def multipart_root_index(self, upload_id):
|
|
1374
1404
|
info = self.multipart_upload_info(upload_id)
|
|
@@ -1393,7 +1423,8 @@ class StoragePathMapper:
|
|
|
1393
1423
|
source,
|
|
1394
1424
|
chunk_size=1024 * 1024,
|
|
1395
1425
|
expected_size=None,
|
|
1396
|
-
max_size=None
|
|
1426
|
+
max_size=None,
|
|
1427
|
+
publish_lock=None):
|
|
1397
1428
|
root_index = self.multipart_root_index(upload_id)
|
|
1398
1429
|
expected_size = max(0, int(expected_size or 0))
|
|
1399
1430
|
max_size = None if max_size is None else max(0, int(max_size))
|
|
@@ -1404,7 +1435,6 @@ class StoragePathMapper:
|
|
|
1404
1435
|
size = 0
|
|
1405
1436
|
tmp_path = None
|
|
1406
1437
|
try:
|
|
1407
|
-
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
1408
1438
|
fd, tmp_path = tempfile.mkstemp(prefix=".part-", dir=os.path.dirname(path))
|
|
1409
1439
|
with os.fdopen(fd, "wb") as fp:
|
|
1410
1440
|
self.preallocate(fp, expected_size)
|
|
@@ -1420,8 +1450,14 @@ class StoragePathMapper:
|
|
|
1420
1450
|
fp.truncate(size)
|
|
1421
1451
|
fp.flush()
|
|
1422
1452
|
os.fsync(fp.fileno())
|
|
1423
|
-
|
|
1424
|
-
|
|
1453
|
+
# Keep streaming abortable; serialize only publication with completion/abort.
|
|
1454
|
+
with publish_lock or contextlib.nullcontext():
|
|
1455
|
+
self.multipart_upload_info(upload_id)
|
|
1456
|
+
os.replace(tmp_path, path)
|
|
1457
|
+
self.fsync_directory(os.path.dirname(path))
|
|
1458
|
+
etag = self.stat_etag(os.stat(path))
|
|
1459
|
+
except FileNotFoundError as e:
|
|
1460
|
+
raise self.missing_multipart_file(upload_id, root_index) from e
|
|
1425
1461
|
except OSError as e:
|
|
1426
1462
|
raise self.write_error(root_index, e, source_consumed=size > 0) from e
|
|
1427
1463
|
finally:
|
|
@@ -1430,13 +1466,22 @@ class StoragePathMapper:
|
|
|
1430
1466
|
os.unlink(tmp_path)
|
|
1431
1467
|
except OSError:
|
|
1432
1468
|
pass
|
|
1433
|
-
return path, size,
|
|
1469
|
+
return path, size, etag
|
|
1434
1470
|
|
|
1435
1471
|
def multipart_size(self, upload_id, part_numbers):
|
|
1436
|
-
return sum(
|
|
1472
|
+
return sum(self.multipart_part_stat(upload_id, part_number).st_size for part_number in part_numbers)
|
|
1437
1473
|
|
|
1438
1474
|
def multipart_part_etag(self, upload_id, part_number):
|
|
1439
|
-
return self.stat_etag(
|
|
1475
|
+
return self.stat_etag(self.multipart_part_stat(upload_id, part_number))
|
|
1476
|
+
|
|
1477
|
+
def multipart_part_stat(self, upload_id, part_number):
|
|
1478
|
+
root_index = self.multipart_root_index(upload_id)
|
|
1479
|
+
try:
|
|
1480
|
+
return os.stat(self.multipart_part_path(upload_id, part_number))
|
|
1481
|
+
except FileNotFoundError as error:
|
|
1482
|
+
raise self.missing_multipart_file(upload_id, root_index) from error
|
|
1483
|
+
except OSError as error:
|
|
1484
|
+
raise self.write_error(root_index, error) from error
|
|
1440
1485
|
|
|
1441
1486
|
def list_multipart_uploads(self, bucket=None, prefix=""):
|
|
1442
1487
|
uploads = []
|
|
@@ -1505,8 +1550,6 @@ class StoragePathMapper:
|
|
|
1505
1550
|
self.preallocate(output, expected_size)
|
|
1506
1551
|
for part_number in part_numbers:
|
|
1507
1552
|
part_path = self.multipart_part_path(upload_id, part_number)
|
|
1508
|
-
if not os.path.exists(part_path):
|
|
1509
|
-
raise StoragePathError(f"Missing multipart part: {part_number}")
|
|
1510
1553
|
with open(part_path, "rb") as part:
|
|
1511
1554
|
while True:
|
|
1512
1555
|
chunk = part.read(chunk_size)
|
|
@@ -1519,6 +1562,9 @@ class StoragePathMapper:
|
|
|
1519
1562
|
os.fsync(output.fileno())
|
|
1520
1563
|
os.replace(tmp_path, path)
|
|
1521
1564
|
self.fsync_directory(directory)
|
|
1565
|
+
except FileNotFoundError as e:
|
|
1566
|
+
self.assert_root_present(root_index)
|
|
1567
|
+
raise self.missing_multipart_file(upload_id, multipart_root_index) from e
|
|
1522
1568
|
except OSError as e:
|
|
1523
1569
|
raise self.write_error(root_index, e, source_consumed=False) from e
|
|
1524
1570
|
finally:
|
|
@@ -1540,7 +1586,10 @@ class StoragePathMapper:
|
|
|
1540
1586
|
return
|
|
1541
1587
|
for dirpath, dirnames, filenames in os.walk(root, topdown=False):
|
|
1542
1588
|
for filename in filenames:
|
|
1543
|
-
|
|
1589
|
+
try:
|
|
1590
|
+
os.unlink(os.path.join(dirpath, filename))
|
|
1591
|
+
except FileNotFoundError:
|
|
1592
|
+
pass
|
|
1544
1593
|
for dirname in dirnames:
|
|
1545
1594
|
os.rmdir(os.path.join(dirpath, dirname))
|
|
1546
1595
|
os.rmdir(root)
|
|
@@ -18,6 +18,7 @@ import json
|
|
|
18
18
|
import mimetypes
|
|
19
19
|
import os
|
|
20
20
|
import sys
|
|
21
|
+
import threading
|
|
21
22
|
import uuid
|
|
22
23
|
import zlib
|
|
23
24
|
import xml.etree.ElementTree as ET
|
|
@@ -35,6 +36,8 @@ from sciveo.storage.filesystem import (
|
|
|
35
36
|
StoragePathError,
|
|
36
37
|
StoragePathMapper,
|
|
37
38
|
StoragePathUnavailable,
|
|
39
|
+
StorageUploadNotFound,
|
|
40
|
+
StoragePartNotFound,
|
|
38
41
|
StorageRequestBodyError,
|
|
39
42
|
StorageSizeExceeded,
|
|
40
43
|
)
|
|
@@ -235,11 +238,59 @@ class TimedInputStream:
|
|
|
235
238
|
return getattr(self.source, name)
|
|
236
239
|
|
|
237
240
|
|
|
241
|
+
class StorageDownloadSlot:
|
|
242
|
+
"""Hold admission until the WSGI server finishes or closes a download."""
|
|
243
|
+
|
|
244
|
+
def __init__(self, slots, close_body):
|
|
245
|
+
self.slots, self.close_body = slots, close_body
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def wrap(cls, body, slots, file_wrapper):
|
|
249
|
+
slot = cls(slots, getattr(body, "close", None))
|
|
250
|
+
if isinstance(file_wrapper, type) and isinstance(body, file_wrapper):
|
|
251
|
+
# Gunicorn dispatches to sendfile by checking the response iterable's type.
|
|
252
|
+
try:
|
|
253
|
+
body.close = slot.close
|
|
254
|
+
except (AttributeError, TypeError):
|
|
255
|
+
pass
|
|
256
|
+
else:
|
|
257
|
+
return body
|
|
258
|
+
return StorageDownloadIterator(body, slot.close)
|
|
259
|
+
|
|
260
|
+
def close(self):
|
|
261
|
+
if self.slots is None:
|
|
262
|
+
return
|
|
263
|
+
slots, self.slots = self.slots, None
|
|
264
|
+
close_body, self.close_body = self.close_body, None
|
|
265
|
+
try:
|
|
266
|
+
if close_body is not None:
|
|
267
|
+
close_body()
|
|
268
|
+
finally:
|
|
269
|
+
slots.release()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class StorageDownloadIterator:
|
|
273
|
+
def __init__(self, body, close):
|
|
274
|
+
self.iterator, self.close = iter(body), close
|
|
275
|
+
|
|
276
|
+
def __iter__(self):
|
|
277
|
+
return self
|
|
278
|
+
|
|
279
|
+
def __next__(self):
|
|
280
|
+
try:
|
|
281
|
+
return next(self.iterator)
|
|
282
|
+
except BaseException:
|
|
283
|
+
self.close()
|
|
284
|
+
raise
|
|
285
|
+
|
|
286
|
+
|
|
238
287
|
class S3StorageService:
|
|
239
288
|
XMLNS = "http://s3.amazonaws.com/doc/2006-03-01/"
|
|
240
289
|
|
|
241
290
|
def __init__(self, config):
|
|
242
291
|
self.config = config
|
|
292
|
+
maximum = config.http.get("max_downloads", 0)
|
|
293
|
+
self.download_slots = threading.BoundedSemaphore(maximum) if maximum else None
|
|
243
294
|
self.config.validate_server_credentials()
|
|
244
295
|
self.metadata = StorageMetadata.from_config(config)
|
|
245
296
|
self.mapper = StoragePathMapper.from_config(config, self.metadata)
|
|
@@ -627,7 +678,7 @@ class S3StorageService:
|
|
|
627
678
|
if max_size is not None:
|
|
628
679
|
part_options["max_size"] = max_size
|
|
629
680
|
_, _, etag = self.mapper.write_multipart_part(
|
|
630
|
-
upload_id, part_number, self.request_body_stream(), **part_options)
|
|
681
|
+
upload_id, part_number, self.request_body_stream(), publish_lock=self.mutations.object(bucket, key), **part_options)
|
|
631
682
|
return Response("", status=200, headers={"ETag": f"\"{etag}\""})
|
|
632
683
|
|
|
633
684
|
def complete_multipart_upload(self, bucket, key):
|
|
@@ -635,11 +686,12 @@ class S3StorageService:
|
|
|
635
686
|
self.mapper.assert_multipart_source(upload_id, bucket, key)
|
|
636
687
|
parts = self.complete_parts(self.request_data_verified())
|
|
637
688
|
part_numbers = [part["part_number"] for part in parts]
|
|
638
|
-
for part in parts:
|
|
639
|
-
actual = self.mapper.multipart_part_etag(upload_id, part["part_number"])
|
|
640
|
-
if part["etag"] and part["etag"] != actual:
|
|
641
|
-
return self.error("InvalidPart", "Multipart part ETag does not match", 400)
|
|
642
689
|
with self.mutations.write(bucket, key, serialize_bucket=self.policy.has_quota(bucket)):
|
|
690
|
+
upload_info = self.mapper.assert_multipart_source(upload_id, bucket, key)
|
|
691
|
+
for part in parts:
|
|
692
|
+
actual = self.mapper.multipart_part_etag(upload_id, part["part_number"])
|
|
693
|
+
if part["etag"] and part["etag"] != actual:
|
|
694
|
+
return self.error("InvalidPart", "Multipart part ETag does not match", 400)
|
|
643
695
|
self.mutation_journal.recover_identity_locked(bucket, key)
|
|
644
696
|
existing = self.metadata.get_object(bucket, key)
|
|
645
697
|
self.policy.validate_overwrite(
|
|
@@ -648,13 +700,12 @@ class S3StorageService:
|
|
|
648
700
|
write_state = {}
|
|
649
701
|
root_index, path, size, etag = self.complete_multipart_upload_file(
|
|
650
702
|
bucket, key, upload_id, part_numbers, existing=existing, write_state=write_state)
|
|
651
|
-
upload_info = self.mapper.multipart_upload_info(upload_id)
|
|
652
703
|
content_type = upload_info.get("content_type") or request.headers.get("Content-Type")
|
|
653
704
|
storage_class = self.storage_class_from_headers(default=upload_info.get("storage_class") or "STANDARD")
|
|
654
705
|
obj = self.put_object_metadata_after_write(
|
|
655
706
|
bucket, key, root_index, path, size, etag, content_type, dict(upload_info.get("metadata") or {}),
|
|
656
707
|
storage_class=storage_class, existing=existing, write_state=write_state, tags=upload_info.get("tags"))
|
|
657
|
-
|
|
708
|
+
self.mapper.cleanup_multipart(upload_id)
|
|
658
709
|
self.record_operation("multipart_complete", bucket=bucket, root_index=root_index, bytes=size)
|
|
659
710
|
|
|
660
711
|
body = [
|
|
@@ -671,8 +722,9 @@ class S3StorageService:
|
|
|
671
722
|
self.mapper.validate_bucket(bucket)
|
|
672
723
|
self.mapper.validate_key(key)
|
|
673
724
|
upload_id = request.args.get("uploadId")
|
|
674
|
-
self.
|
|
675
|
-
|
|
725
|
+
with self.mutations.object(bucket, key):
|
|
726
|
+
self.mapper.assert_multipart_source(upload_id, bucket, key)
|
|
727
|
+
self.mapper.cleanup_multipart(upload_id)
|
|
676
728
|
return Response("", status=204)
|
|
677
729
|
|
|
678
730
|
def list_multipart_uploads(self, bucket):
|
|
@@ -722,6 +774,25 @@ class S3StorageService:
|
|
|
722
774
|
return self.xml(body)
|
|
723
775
|
|
|
724
776
|
def get_object(self, bucket, key):
|
|
777
|
+
if self.download_slots is None:
|
|
778
|
+
return self.get_object_response(bucket, key)
|
|
779
|
+
if not self.download_slots.acquire(blocking=False):
|
|
780
|
+
self.record_failure("failure_download_limit")
|
|
781
|
+
response = self.error("SlowDown", "Concurrent download limit reached; retry later", 503)
|
|
782
|
+
response.headers["Retry-After"] = "1"
|
|
783
|
+
return response
|
|
784
|
+
try:
|
|
785
|
+
response = self.get_object_response(bucket, key)
|
|
786
|
+
if response.status_code in (200, 206):
|
|
787
|
+
response.response = StorageDownloadSlot.wrap(response.response, self.download_slots, request.environ.get("wsgi.file_wrapper"))
|
|
788
|
+
return response
|
|
789
|
+
except BaseException:
|
|
790
|
+
self.download_slots.release()
|
|
791
|
+
raise
|
|
792
|
+
self.download_slots.release()
|
|
793
|
+
return response
|
|
794
|
+
|
|
795
|
+
def get_object_response(self, bucket, key):
|
|
725
796
|
with self.mutations.object(bucket, key):
|
|
726
797
|
obj = self.committed_object(bucket, key)
|
|
727
798
|
if obj is None:
|
|
@@ -1813,6 +1884,12 @@ class S3StorageService:
|
|
|
1813
1884
|
except StoragePolicyError as e:
|
|
1814
1885
|
self.record_failure("failure_policy")
|
|
1815
1886
|
return self.error("PolicyDenied", str(e), 409)
|
|
1887
|
+
except StorageUploadNotFound as e:
|
|
1888
|
+
self.record_failure("failure_no_such_upload")
|
|
1889
|
+
return self.error("NoSuchUpload", str(e), 404)
|
|
1890
|
+
except StoragePartNotFound as e:
|
|
1891
|
+
self.record_failure("failure_invalid_part")
|
|
1892
|
+
return self.error("InvalidPart", str(e), 400)
|
|
1816
1893
|
except StoragePathError as e:
|
|
1817
1894
|
self.record_failure("failure_invalid_argument")
|
|
1818
1895
|
return self.error("InvalidArgument", str(e), 400)
|
|
@@ -124,6 +124,8 @@ class CLIOptions:
|
|
|
124
124
|
for word in argv:
|
|
125
125
|
if word.startswith("-"):
|
|
126
126
|
option = self.parser._parse_optional(word)
|
|
127
|
+
if isinstance(option, list): # Python 3.12.11+ returns a list of matches.
|
|
128
|
+
option = option[0] if len(option) == 1 else None
|
|
127
129
|
if option and option[0] is not None:
|
|
128
130
|
explicit.add(option[0].dest)
|
|
129
131
|
command = "sniff" if args.command == "sniffer" else args.command
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sciveo
|
|
3
|
+
Version: 0.3.4
|
|
4
|
+
Description-Content-Type: text/markdown
|
|
5
|
+
Provides-Extra: mon
|
|
6
|
+
Provides-Extra: agent
|
|
7
|
+
Provides-Extra: agent-local
|
|
8
|
+
Provides-Extra: net
|
|
9
|
+
Provides-Extra: server
|
|
10
|
+
Provides-Extra: storage
|
|
11
|
+
Provides-Extra: storage-postgres
|
|
12
|
+
Provides-Extra: chat
|
|
13
|
+
Provides-Extra: media
|
|
14
|
+
Provides-Extra: media-ml
|
|
15
|
+
Provides-Extra: sciveyolo
|
|
16
|
+
Provides-Extra: sciveyolo-gpu
|
|
17
|
+
Provides-Extra: sciveyolo-trt
|
|
18
|
+
Provides-Extra: web
|
|
19
|
+
Provides-Extra: db
|
|
20
|
+
Provides-Extra: power
|
|
21
|
+
Provides-Extra: industrial
|
|
22
|
+
Provides-Extra: energy
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Provides-Extra: ml
|
|
25
|
+
|
|
1
26
|
# SCIVEO
|
|
2
27
|
|
|
3
28
|
Sciveo is a Python toolkit and CLI for ML/AI workflows, engineering automation,
|
|
@@ -2371,6 +2396,19 @@ staging, and `BASE/video` for retained recordings. No separate `tmp`, `video`,
|
|
|
2371
2396
|
`motion.src`, or `motion.dst` paths are needed. Camera-specific motion settings
|
|
2372
2397
|
remain under `motion`.
|
|
2373
2398
|
|
|
2399
|
+
To keep temporary video I/O in RAM while retaining recordings on those HDD roots,
|
|
2400
|
+
set `"temp_path": "/mnt/nvr-ram"` to a separately mounted, size-limited tmpfs.
|
|
2401
|
+
Capture, motion processing and upload staging then use `TEMP/tmp2`, `TEMP/tmp3`
|
|
2402
|
+
and `TEMP/tmp4`; archives still use the ordered `BASE/video` roots and their
|
|
2403
|
+
capacity-based cleanup. The same free-space reserve applies to the temporary
|
|
2404
|
+
filesystem, so size it above that reserve plus the expected queued video volume.
|
|
2405
|
+
When temporary space runs low, capture waits while processing drains the queues;
|
|
2406
|
+
it does not switch temporary writes onto an HDD or delete archives to free RAM.
|
|
2407
|
+
Make the NVR service require the tmpfs mount at boot. RAM queues are volatile
|
|
2408
|
+
across reboot; finalized HDD archives remain persistent. Existing HDD queues are
|
|
2409
|
+
not moved or deleted when changing `temp_path`. Point external uploaders at
|
|
2410
|
+
`TEMP/tmp4` when this option is enabled.
|
|
2411
|
+
|
|
2374
2412
|
Optional per-stage transcoding is disabled unless configured. Put `transcode`
|
|
2375
2413
|
under `cam.CAMERA` to transcode every completed clip before archival and motion
|
|
2376
2414
|
processing. Put it under `motion.CAMERA` to transcode **only motion-selected**
|
|
@@ -2407,7 +2445,7 @@ processing continues without repeatedly encoding the same failed clip. Storage
|
|
|
2407
2445
|
write failures retain the source for retry; no incomplete MP4 enters the uploader
|
|
2408
2446
|
queue. An all-zero motion grid skips video decoding entirely.
|
|
2409
2447
|
|
|
2410
|
-
|
|
2448
|
+
Stages without a separate `temp_path` use the first available root in list order, skipping absent, unmounted,
|
|
2411
2449
|
or unwritable roots. Low space does not cause fallback. Free space must cover
|
|
2412
2450
|
both the incoming clip and the larger of `min_free_bytes` and `min_free_ratio`
|
|
2413
2451
|
(defaults: 1 GiB and 5%). Capture rechecks
|
|
@@ -2448,7 +2486,7 @@ With no usable root, capture waits and queued files are retained until storage
|
|
|
2448
2486
|
recovers. This provides fallback, not replication: clips already on a disconnected
|
|
2449
2487
|
disk become available again when that disk returns.
|
|
2450
2488
|
|
|
2451
|
-
|
|
2489
|
+
Without `temp_path`, external uploaders must watch `BASE/tmp4` on **every** configured root, including
|
|
2452
2490
|
roots that recover after startup. Existing queued files are not relocated when
|
|
2453
2491
|
migrating configurations. Legacy single-directory `path.tmp`, `path.video`,
|
|
2454
2492
|
`motion.src`, and `motion.dst` configurations remain supported when `paths` is
|
|
@@ -458,6 +458,7 @@ test/test_chint_dtsu666_catalog.py
|
|
|
458
458
|
test/test_cli_defaults.py
|
|
459
459
|
test/test_cli_delegation.py
|
|
460
460
|
test/test_cli_modules.py
|
|
461
|
+
test/test_cli_regression.py
|
|
461
462
|
test/test_cli_themes.py
|
|
462
463
|
test/test_complexity.py
|
|
463
464
|
test/test_compress.py
|
|
@@ -523,6 +524,7 @@ test/test_storage.py
|
|
|
523
524
|
test/test_storage_auth_hardening.py
|
|
524
525
|
test/test_storage_cluster.py
|
|
525
526
|
test/test_storage_cors.py
|
|
527
|
+
test/test_storage_downloads.py
|
|
526
528
|
test/test_storage_iam.py
|
|
527
529
|
test/test_storage_iam_users.py
|
|
528
530
|
test/test_storage_metadata_hardening.py
|
|
@@ -54,6 +54,24 @@ class TestCLIOptions(unittest.TestCase):
|
|
|
54
54
|
args, _ = self.arguments("scan")
|
|
55
55
|
self.assertEqual((args.timeout, args.theme), (2, "light"))
|
|
56
56
|
|
|
57
|
+
def test_explicit_options_support_old_and_new_argparse_results(self):
|
|
58
|
+
self.preferences.update({"scan.port": "22", "timeout": "7", "local-echo": "false", "theme": "amber"})
|
|
59
|
+
options = self.definitions(self.preferences)
|
|
60
|
+
argv = ["scan", "--port", "502", "--timeo=-2", "--handle-local-echo"]
|
|
61
|
+
matches = [(options.actions["port"], "--port", None, None),
|
|
62
|
+
(options.actions["timeout"], "--timeout", "=", "-2"),
|
|
63
|
+
(options.actions["local-echo"], "--handle-local-echo", None, None)]
|
|
64
|
+
for result_type in ("tuple", "list"):
|
|
65
|
+
with self.subTest(result_type=result_type):
|
|
66
|
+
args = options.parser.parse_args(argv)
|
|
67
|
+
results = [(action, name, value) for action, name, _, value in matches]
|
|
68
|
+
if result_type == "list":
|
|
69
|
+
results = [[match] for match in matches]
|
|
70
|
+
with patch.object(options.parser, "_parse_optional", side_effect=results):
|
|
71
|
+
applied = options.apply(args, argv)
|
|
72
|
+
self.assertEqual((args.port, args.timeout, args.handle_local_echo, args.theme), (502, -2, True, "amber"))
|
|
73
|
+
self.assertEqual(applied, {"theme"})
|
|
74
|
+
|
|
57
75
|
def test_camera_expiry_global_command_and_explicit_precedence(self):
|
|
58
76
|
options = self.definitions()
|
|
59
77
|
for command in ("sniff", "nvr", "rtsp"):
|