Compare commits

...

21 Commits

Author SHA1 Message Date
Scott Lamb
ff383147e4 prep v0.7.23
I'd actually intended the things here to be in v0.7.22, but I tagged
the wrong revision. Embarrassing, but version numbers are free.
2025-10-03 15:42:34 -07:00
Scott Lamb
5cc93349fc prep v0.7.22
...with dependency updates, notably including Retina.
2025-10-03 14:48:12 -07:00
Scott Lamb
dc1909d073 fix lifetime elision warnings 2025-10-03 14:19:52 -07:00
Jared Wolff
bc93712314 Addressing FreeBSD compile issue 2025-06-18 20:46:01 -07:00
Scott Lamb
104ffdc2dc fix obsolete runs-on in workflow 2025-05-08 10:54:36 -07:00
Scott Lamb
7968700aae fix missing log lines
...by using a global rather than per-layer filter with `tracing`.
2025-05-08 10:48:53 -07:00
Scott Lamb
239256e2ba prep v0.7.21 2025-04-04 11:49:47 -07:00
Scott Lamb
1817931c6f update a few Rust deps 2025-04-04 11:48:19 -07:00
Scott Lamb
5147a50771 use mimalloc with musl build fix 2025-04-04 09:23:10 -07:00
Scott Lamb
39b6f6c49c build releases with mimalloc 2025-04-03 10:06:50 -07:00
Scott Lamb
0ccc6d0769 wrap Mutex and Condvar to handle poison
This centralizes a major source of `.unwrap()` throughout the code,
and one that would otherwise grow with upcoming changes. The new
error message should be more clear.
2025-04-03 09:16:44 -07:00
Scott Lamb
2903b680df clippy fixes to tests 2025-04-03 09:16:44 -07:00
Scott Lamb
2985214d87 reduce per-slice allocations
`slices::Slices::get_range` was too closely following the example of
`http_serve::Entity::get_range`:

The latter is once-per-request, so just boxing is low-cost and makes
sense to easily avoid monomorphization bloat when there are potentially
many types of entity streams in one program. In Moonfire, it's used with
different streams defined in the `moonfire_nvr::web::mp4`,
`moonfire_nvr::bundled_ui`, and `http_serve::file` modules. Putting them
all into a single boxless enum would be a pain. In particular, the last
one is not a nameable type today and would need more generic parameters
to implement the caller-demanded `Entity` definition.

The former is once-per-slice, there are tons of slices per request, and
it's easy to define a two-case enum right where it's needed. So the
trade-off is quite different.

Also fix up some out-of-date comments.
2025-03-07 18:54:41 -08:00
Scott Lamb
3cc9603ff3 resurrect benches
Because these require nightly, CI isn't configured to fail if they're
broken. And they have been broken since the conversion to hyper 1.x.
2025-03-07 09:26:35 -08:00
Scott Lamb
e204fe0864 update actions/cache 2025-03-06 21:16:08 -08:00
Scott Lamb
f894f889be log full error chain in a couple spots 2025-03-06 21:05:43 -08:00
Scott Lamb
284a3dd5a9 regenerate pnpm lockfile (mass UI dep bump)
Note: `pnpm install` with pnpm 10+ completely ignores the old lockfile,
so this does actually bump all the versions.
2025-02-10 11:20:56 -08:00
Scott Lamb
7f7b95c56c make prettier ignore pnpm lockfile 2025-02-10 11:20:56 -08:00
Scott Lamb
c75292e43b fix bug spotted by newer typescript 2025-02-10 11:09:21 -08:00
Scott Lamb
0bfa09b1f1 drop ulid dependency
...and use v7 UUIDs exclusively. It's useful to have the timestamp in
request ids in particular, and no reason we _need_ v4 anywhere.
2025-02-07 10:04:29 -08:00
Scott Lamb
d780b28cc2 remove unused cstr dep 2025-02-07 09:07:32 -08:00
40 changed files with 6424 additions and 7520 deletions

View File

@ -15,7 +15,7 @@ jobs:
name: Rust ${{ matrix.rust }}
strategy:
matrix:
rust: [ "stable", "1.82", "nightly" ]
rust: ["stable", "1.88", "nightly"]
include:
- rust: nightly
extra_args: "--features nightly --benches"
@ -29,9 +29,9 @@ jobs:
# `git describe` output gets baked into the binary for `moonfire-nvr --version`.
# Fetch all revs so it can see tag history.
fetch-depth: 0
filter: 'tree:0'
filter: "tree:0"
- name: Cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
@ -63,7 +63,7 @@ jobs:
name: Node ${{ matrix.node }}
strategy:
matrix:
node: [ "18", "20", "21" ]
node: ["18", "20", "21"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -79,7 +79,7 @@ jobs:
- run: cd ui && pnpm run check-format
license:
name: Check copyright/license headers
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@ -52,16 +52,16 @@ jobs:
strategy:
matrix:
include:
# Note: keep these arches in sync with `Upload Docker Manifest` list.
- arch: x86_64 # as in `uname -m` on Linux.
rust_target: x86_64-unknown-linux-musl # as in <https://doc.rust-lang.org/rustc/platform-support.html>
docker_platform: linux/amd64 # as in <https://docs.docker.com/build/building/multi-platform/>
- arch: aarch64
rust_target: aarch64-unknown-linux-musl
docker_platform: linux/arm64
- arch: armv7l
rust_target: armv7-unknown-linux-musleabihf
docker_platform: linux/arm/v7
# Note: keep these arches in sync with `Upload Docker Manifest` list.
- arch: x86_64 # as in `uname -m` on Linux.
rust_target: x86_64-unknown-linux-musl # as in <https://doc.rust-lang.org/rustc/platform-support.html>
docker_platform: linux/amd64 # as in <https://docs.docker.com/build/building/multi-platform/>
- arch: aarch64
rust_target: aarch64-unknown-linux-musl
docker_platform: linux/arm64
- arch: armv7l
rust_target: armv7-unknown-linux-musleabihf
docker_platform: linux/arm/v7
fail-fast: false
runs-on: ubuntu-latest
steps:
@ -90,7 +90,7 @@ jobs:
working-directory: server
target: ${{ matrix.rust_target }}
command: build
args: --release --features bundled
args: --release --features bundled,mimalloc
- name: Upload Docker Artifact
run: |
tag="${DOCKER_TAG}-${{ matrix.arch }}"
@ -115,7 +115,7 @@ jobs:
if-no-files-found: error
release:
needs: [ base, cross ]
needs: [base, cross]
runs-on: ubuntu-latest
permissions:
contents: write

View File

@ -8,6 +8,25 @@ upgrades, e.g. `v0.6.x` -> `v0.7.x`. The config file format and
[API](ref/api.md) currently have no stability guarantees, so they may change
even on minor releases, e.g. `v0.7.5` -> `v0.7.6`.
## v0.7.23 (2025-10-03)
* update Retina to [v0.4.14](https://github.com/scottlamb/retina/blob/main/CHANGELOG.md#v0414-2025-10-03),
improving camera compatibility. Fixes [#344](https://github.com/scottlamb/moonfire-nvr/issues/344).
* bump minimum Rust version to 1.88.
## v0.7.22 (2025-10-03)
(This version was tagged but never released due to an error.)
* switch from per-layer to global `tracing` filter to avoid missing log lines
(tokio-rs/tracing#2519)[https://github.com/tokio-rs/tracing/issues/2519]).
## v0.7.21 (2025-04-04)
* Release with `mimalloc` allocator, which is significantly faster than the memory
allocator built into `musl`.
* Eliminate some memory allocations.
## v0.7.20 (2025-01-31)
* H.265 fixes.

View File

@ -68,7 +68,7 @@ following command:
$ brew install node
```
Next, you need Rust 1.82+ and Cargo. The easiest way to install them is by
Next, you need Rust 1.88+ and Cargo. The easiest way to install them is by
following the instructions at [rustup.rs](https://www.rustup.rs/). Avoid
your Linux distribution's Rust packages, which tend to be too old.
(At least on Debian-based systems; Arch and Gentoo might be okay.)

View File

@ -26,10 +26,10 @@ left, and pick the [latest tagged version](https://github.com/scottlamb/moonfire
Download the binary for your platform from the matching GitHub release.
Install it as `/usr/local/bin/moonfire-nvr` and ensure it is executable, e.g.
for version `v0.7.14`:
for version `v0.7.23`:
```console
$ VERSION=v0.7.14
$ VERSION=v0.7.23
$ ARCH=$(uname -m)
$ curl -OL "https://github.com/scottlamb/moonfire-nvr/releases/download/$VERSION/moonfire-nvr-$VERSION-$ARCH"
$ sudo install -m 755 "moonfire-nvr-$VERSION-$ARCH" /usr/local/bin/moonfire-nvr
@ -65,7 +65,7 @@ services:
moonfire-nvr:
# The `vX.Y.Z` images will work on any architecture (x86-64, arm, or
# aarch64); just pick the correct version.
image: ghcr.io/scottlamb/moonfire-nvr:v0.7.11
image: ghcr.io/scottlamb/moonfire-nvr:v0.7.23
command: run
volumes:

393
server/Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@ -57,6 +57,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.4.0"
@ -104,9 +110,9 @@ checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
[[package]]
name = "blake3"
version = "1.5.5"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e"
checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
dependencies = [
"arrayref",
"arrayvec",
@ -126,9 +132,9 @@ dependencies = [
[[package]]
name = "bpaf"
version = "0.9.15"
version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50fd5174866dc2fa2ddc96e8fb800852d37f064f32a45c7b7c2f8fa2c64c77fa"
checksum = "473976d7a8620bb1e06dcdd184407c2363fe4fec8e983ee03ed9197222634a31"
dependencies = [
"bpaf_derive",
"owo-colors",
@ -137,9 +143,9 @@ dependencies = [
[[package]]
name = "bpaf_derive"
version = "0.5.13"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf95d9c7e6aba67f8fc07761091e93254677f4db9e27197adecebc7039a58722"
checksum = "fefb4feeec9a091705938922f26081aad77c64cd2e76cd1c4a9ece8e42e1618a"
dependencies = [
"proc-macro2",
"quote",
@ -169,9 +175,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.9.0"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "castaway"
@ -285,14 +291,10 @@ dependencies = [
]
[[package]]
name = "cstr"
version = "0.2.12"
name = "cty"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636"
dependencies = [
"proc-macro2",
"quote",
]
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]]
name = "cursive"
@ -381,9 +383,9 @@ dependencies = [
[[package]]
name = "data-encoding"
version = "2.7.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "deranged"
@ -719,9 +721,9 @@ dependencies = [
[[package]]
name = "http"
version = "1.2.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
@ -755,12 +757,12 @@ dependencies = [
[[package]]
name = "http-body-util"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-util",
"futures-core",
"http",
"http-body",
"pin-project-lite",
@ -768,9 +770,9 @@ dependencies = [
[[package]]
name = "http-serve"
version = "0.4.0-rc.1"
version = "0.4.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e30e587eb43944a00ad6b239f691694564fc050c81b86e39006082037188084"
checksum = "18a4eb23604eafa9eafc6d9c2e2032c0bfe9dd7362428eea4914d94168cc992f"
dependencies = [
"bytes",
"flate2",
@ -803,19 +805,21 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.5.2"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-util",
"futures-core",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec",
"tokio",
"want",
@ -823,16 +827,21 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.10"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [
"base64",
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"tokio",
@ -987,9 +996,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.7.1"
version = "2.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
"hashbrown",
@ -1004,12 +1013,33 @@ dependencies = [
"generic-array",
]
[[package]]
name = "io-uring"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "iri-string"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "is_ci"
version = "1.2.0"
@ -1018,9 +1048,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "itertools"
version = "0.12.1"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
@ -1033,10 +1063,11 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "jiff"
version = "0.1.26"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e58800dee30ca82a1c107c64a0effca7ccb4e6106645c0e3af40d5aaba9f5457"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
"log",
"portable-atomic",
@ -1046,16 +1077,27 @@ dependencies = [
]
[[package]]
name = "jiff-tzdb"
version = "0.1.2"
name = "jiff-static"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf2cec2f5d266af45a071ece48b1fb89f3b00b2421ac3a5fe10285a6caaa60d3"
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "jiff-tzdb"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
[[package]]
name = "jiff-tzdb-platform"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a63c62e404e7b92979d2792352d885a7f8f83fd1d0d31eea582d77b2ceca697e"
checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
dependencies = [
"jiff-tzdb",
]
@ -1078,9 +1120,20 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.169"
version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
[[package]]
name = "libmimalloc-sys"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870"
dependencies = [
"cc",
"cty",
"libc",
]
[[package]]
name = "libredox"
@ -1095,9 +1148,9 @@ dependencies = [
[[package]]
name = "libsqlite3-sys"
version = "0.31.0"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8935b44e7c13394a179a438e0cebba0fe08fe01b54f152e29a93b5cf993fd4"
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
dependencies = [
"cc",
"pkg-config",
@ -1146,9 +1199,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.25"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "matchers"
@ -1171,9 +1224,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "memoffset"
@ -1225,6 +1278,7 @@ dependencies = [
"futures",
"jiff",
"libc",
"libmimalloc-sys",
"nix",
"nom",
"rusqlite",
@ -1244,7 +1298,6 @@ dependencies = [
"base64",
"blake3",
"byteorder",
"cstr",
"diff",
"futures",
"h264-reader",
@ -1267,7 +1320,6 @@ dependencies = [
"tempfile",
"tokio",
"tracing",
"ulid",
"url",
"uuid",
]
@ -1283,6 +1335,7 @@ dependencies = [
"byteorder",
"bytes",
"cursive",
"data-encoding",
"flate2",
"futures",
"h264-reader",
@ -1305,6 +1358,7 @@ dependencies = [
"nom",
"num-rational",
"password-hash",
"pin-project",
"pretty-hex",
"protobuf",
"reffers",
@ -1315,6 +1369,7 @@ dependencies = [
"serde",
"serde_json",
"smallvec",
"subtle",
"tempfile",
"tokio",
"tokio-tungstenite",
@ -1325,7 +1380,6 @@ dependencies = [
"tracing-log",
"tracing-subscriber",
"tracing-test",
"ulid",
"url",
"uuid",
"walkdir",
@ -1564,18 +1618,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
version = "1.1.8"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.8"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
@ -1647,20 +1701,18 @@ dependencies = [
[[package]]
name = "protobuf"
version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3a7c64d9bf75b1b8d981124c14c179074e8caa7dfe7b6a12e6222ddcd0c8f72"
version = "3.7.2"
source = "git+https://github.com/scottlamb/rust-protobuf?rev=593aa7f26bb5fc736c2e61d410afa34efb914ecb#593aa7f26bb5fc736c2e61d410afa34efb914ecb"
dependencies = [
"once_cell",
"protobuf-support",
"thiserror 1.0.69",
"thiserror 2.0.17",
]
[[package]]
name = "protobuf-codegen"
version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e26b833f144769a30e04b1db0146b2aaa53fd2fd83acf10a6b5f996606c18144"
version = "3.7.2"
source = "git+https://github.com/scottlamb/rust-protobuf?rev=593aa7f26bb5fc736c2e61d410afa34efb914ecb#593aa7f26bb5fc736c2e61d410afa34efb914ecb"
dependencies = [
"anyhow",
"once_cell",
@ -1668,14 +1720,13 @@ dependencies = [
"protobuf-parse",
"regex",
"tempfile",
"thiserror 1.0.69",
"thiserror 2.0.17",
]
[[package]]
name = "protobuf-parse"
version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322330e133eab455718444b4e033ebfac7c6528972c784fcde28d2cc783c6257"
version = "3.7.2"
source = "git+https://github.com/scottlamb/rust-protobuf?rev=593aa7f26bb5fc736c2e61d410afa34efb914ecb#593aa7f26bb5fc736c2e61d410afa34efb914ecb"
dependencies = [
"anyhow",
"indexmap",
@ -1683,17 +1734,16 @@ dependencies = [
"protobuf",
"protobuf-support",
"tempfile",
"thiserror 1.0.69",
"thiserror 2.0.17",
"which",
]
[[package]]
name = "protobuf-support"
version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b088fd20b938a875ea00843b6faf48579462630015c3788d397ad6a786663252"
version = "3.7.2"
source = "git+https://github.com/scottlamb/rust-protobuf?rev=593aa7f26bb5fc736c2e61d410afa34efb914ecb#593aa7f26bb5fc736c2e61d410afa34efb914ecb"
dependencies = [
"thiserror 1.0.69",
"thiserror 2.0.17",
]
[[package]]
@ -1790,24 +1840,20 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "reqwest"
version = "0.12.12"
version = "0.12.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb"
dependencies = [
"base64",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"once_cell",
"percent-encoding",
"pin-project-lite",
"serde",
@ -1816,19 +1862,19 @@ dependencies = [
"sync_wrapper",
"tokio",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows-registry",
]
[[package]]
name = "retina"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb881f2fb51b0b4e814a524bfb938007bdf3df5f8430ec0b4e014d2dc877f50"
checksum = "783b4b9caaf5c46c93b9946590b29e58b267832b2b9e71fd85740f5001586fe4"
dependencies = [
"base64",
"bitstream-io",
@ -1846,7 +1892,7 @@ dependencies = [
"rtsp-types",
"sdp-types",
"smallvec",
"thiserror 1.0.69",
"thiserror 2.0.17",
"tokio",
"tokio-util",
"url",
@ -1891,9 +1937,9 @@ dependencies = [
[[package]]
name = "rusqlite"
version = "0.33.0"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c6d5e5acb6f6129fe3f7ba0a7fc77bca1942cb568535e18e7bc40262baf3110"
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
dependencies = [
"bitflags",
"fallible-iterator",
@ -1982,18 +2028,28 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.217"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@ -2002,23 +2058,24 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.137"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee"
dependencies = [
"serde",
"serde_core",
]
[[package]]
@ -2091,27 +2148,24 @@ dependencies = [
[[package]]
name = "slab"
version = "0.4.9"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]]
name = "smallvec"
version = "1.13.2"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.5.8"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -2149,9 +2203,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.96"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
@ -2215,11 +2269,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.11"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl 2.0.11",
"thiserror-impl 2.0.17",
]
[[package]]
@ -2235,9 +2289,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.11"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
@ -2314,19 +2368,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.43.0"
version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [
"backtrace",
"bytes",
"io-uring",
"libc",
"mio",
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -2367,38 +2423,43 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.19"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0"
dependencies = [
"serde",
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_edit",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.22.22"
name = "toml_parser"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109"
[[package]]
name = "tower"
version = "0.5.2"
@ -2414,6 +2475,24 @@ dependencies = [
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
@ -2548,7 +2627,7 @@ dependencies = [
"log",
"rand",
"sha1",
"thiserror 2.0.11",
"thiserror 2.0.17",
"utf-8",
]
@ -2558,16 +2637,6 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "ulid"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f294bff79170ed1c5633812aff1e565c35d993a36e757f9bc0accf5eec4e6045"
dependencies = [
"rand",
"web-time",
]
[[package]]
name = "unicode-ident"
version = "1.0.15"
@ -2629,6 +2698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
dependencies = [
"getrandom",
"rand",
"serde",
]
@ -2756,16 +2826,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "which"
version = "4.4.2"
@ -2809,36 +2869,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-registry"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
dependencies = [
"windows-result",
"windows-strings",
"windows-targets",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result",
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
@ -2923,12 +2953,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.24"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
dependencies = [
"memchr",
]
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
[[package]]
name = "write16"

View File

@ -5,7 +5,7 @@ authors = ["Scott Lamb <slamb@slamb.org>"]
edition = "2021"
resolver = "2"
license-file = "../LICENSE.txt"
rust-version = "1.82"
rust-version = "1.88"
publish = false
[features]
@ -18,6 +18,7 @@ nightly = ["db/nightly"]
bundled = ["rusqlite/bundled", "bundled-ui"]
bundled-ui = []
mimalloc = ["base/mimalloc"]
[workspace]
members = ["base", "db"]
@ -25,26 +26,38 @@ members = ["base", "db"]
[workspace.dependencies]
base64 = "0.22.0"
h264-reader = "0.8.0"
itertools = "0.12.0"
jiff = "0.1.8"
itertools = "0.14.0"
jiff = "0.2.1"
nix = "0.27.0"
pretty-hex = "0.4.0"
ring = "0.17.0"
rusqlite = "0.33.0"
rusqlite = "0.37.0"
tracing = { version = "0.1" }
tracing-core = "0.1.30"
tracing-futures = { version = "0.2.5", features = ["futures-03", "std-future"] }
tracing-log = "0.2"
tracing-subscriber = { version = "0.3.16" }
uuid = { version = "1.1.2", features = ["serde", "std", "v7", "fast-rng"] }
# This is 3.7.2 + dependency updates.
protobuf = { git = "https://github.com/scottlamb/rust-protobuf", rev = "593aa7f26bb5fc736c2e61d410afa34efb914ecb" }
protobuf-codegen = { git = "https://github.com/scottlamb/rust-protobuf", rev = "593aa7f26bb5fc736c2e61d410afa34efb914ecb" }
[dependencies]
base = { package = "moonfire-base", path = "base" }
base64 = { workspace = true }
blake3 = "1.0.0"
bpaf = { version = "0.9.15", features = ["autocomplete", "bright-color", "derive"]}
bpaf = { version = "0.9.15", features = [
"autocomplete",
"bright-color",
"derive",
] }
bytes = "1"
byteorder = "1.0"
cursive = { version = "0.21.1", default-features = false, features = ["termion-backend"] }
cursive = { version = "0.21.1", default-features = false, features = [
"termion-backend",
] }
data-encoding = "2.7.0"
db = { package = "moonfire-db", path = "db" }
futures = "0.3"
h264-reader = { workspace = true }
@ -60,29 +73,36 @@ nix = { workspace = true, features = ["time", "user"] }
nom = "7.0.0"
password-hash = "0.5.0"
pretty-hex = { workspace = true }
protobuf = "3.0"
protobuf = { workspace = true }
reffers = "0.7.0"
retina = "0.4.13"
retina = "0.4.14"
ring = { workspace = true }
rusqlite = { workspace = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
smallvec = { version = "1.7", features = ["union"] }
tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "signal", "sync", "time"] }
tokio = { version = "1.24", features = [
"macros",
"rt-multi-thread",
"signal",
"sync",
"time",
] }
tokio-tungstenite = "0.26.1"
toml = "0.8"
toml = "0.9"
tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] }
tracing-core = "0.1.30"
tracing-futures = { version = "0.2.5", features = ["futures-03", "std-future"] }
tracing-log = { workspace = true }
ulid = "1.0.0"
url = "2.1.1"
uuid = { version = "1.1.2", features = ["serde", "std", "v4"] }
uuid = { workspace = true }
flate2 = "1.0.26"
hyper-util = { version = "0.1.7", features = ["server-graceful", "tokio"] }
http-body = "1.0.1"
http-body-util = "0.1.2"
pin-project = "1.1.10"
subtle = "2.6.1"
[target.'cfg(target_os = "linux")'.dependencies]
libsystemd = "0.7.0"
@ -94,7 +114,9 @@ walkdir = "2.3.3"
[dev-dependencies]
mp4 = { git = "https://github.com/scottlamb/mp4-rust", branch = "moonfire" }
num-rational = { version = "0.4.0", default-features = false, features = ["std"] }
num-rational = { version = "0.4.0", default-features = false, features = [
"std",
] }
reqwest = { version = "0.12.0", default-features = false, features = ["json"] }
tempfile = "3.2.0"
tracing-test = "0.2.4"

View File

@ -6,9 +6,10 @@ readme = "../README.md"
edition = "2021"
license-file = "../../LICENSE.txt"
publish = false
rust-version = "1.82"
rust-version = "1.88"
[features]
mimalloc = ["dep:libmimalloc-sys"]
nightly = []
[lib]
@ -16,10 +17,14 @@ path = "lib.rs"
[dependencies]
ahash = "0.8"
coded = { git = "https://github.com/scottlamb/coded", rev = "2c97994974a73243d5dd12134831814f42cdb0e8"}
coded = { git = "https://github.com/scottlamb/coded", rev = "2c97994974a73243d5dd12134831814f42cdb0e8" }
futures = "0.3"
jiff = { workspace = true }
libc = "0.2"
libmimalloc-sys = { version = "0.1.44", features = [
"override",
"extended",
], optional = true }
nix = { workspace = true, features = ["time"] }
nom = "7.0.0"
rusqlite = { workspace = true }

View File

@ -7,8 +7,8 @@
//! Note these types are in a more standard nanosecond-based format, where
//! [`crate::time`] uses Moonfire's 90 kHz time base.
use crate::Mutex;
use nix::sys::time::{TimeSpec, TimeValLike as _};
use std::sync::Mutex;
use std::sync::{mpsc, Arc};
use std::thread;
pub use std::time::Duration;
@ -220,15 +220,15 @@ impl SimulatedClocks {
impl Clocks for SimulatedClocks {
fn realtime(&self) -> SystemTime {
self.0.boot + *self.0.uptime.lock().unwrap()
self.0.boot + *self.0.uptime.lock()
}
fn monotonic(&self) -> Instant {
Instant(TimeSpec::from(*self.0.uptime.lock().unwrap()))
Instant(TimeSpec::from(*self.0.uptime.lock()))
}
/// Advances the clock by the specified amount without actually sleeping.
fn sleep(&self, how_long: Duration) {
let mut l = self.0.uptime.lock().unwrap();
let mut l = self.0.uptime.lock();
*l += how_long;
}

View File

@ -101,7 +101,7 @@ impl ToErrKind for rusqlite::types::FromSqlError {
impl ToErrKind for nix::Error {
fn err_kind(&self) -> ErrorKind {
use nix::Error;
match self {
match *self {
Error::EACCES | Error::EPERM => ErrorKind::PermissionDenied,
Error::EDQUOT => ErrorKind::ResourceExhausted,
Error::EBUSY

View File

@ -14,3 +14,77 @@ pub use crate::error::{Error, ErrorBuilder, ErrorKind, ResultExt};
pub use ahash::RandomState;
pub type FastHashMap<K, V> = std::collections::HashMap<K, V, ahash::RandomState>;
pub type FastHashSet<K> = std::collections::HashSet<K, ahash::RandomState>;
const NOT_POISONED: &str =
"not poisoned; this is a consequence of an earlier panic while holding this mutex; see logs.";
/// [`std::sync::Mutex`] wrapper which always panics on encountering poison.
#[derive(Default)]
pub struct Mutex<T>(std::sync::Mutex<T>);
impl<T> Mutex<T> {
#[inline]
pub const fn new(value: T) -> Self {
Mutex(std::sync::Mutex::new(value))
}
#[track_caller]
#[inline]
pub fn lock(&self) -> std::sync::MutexGuard<'_, T> {
self.0.lock().expect(NOT_POISONED)
}
#[track_caller]
#[inline]
pub fn into_inner(self) -> T {
self.0.into_inner().expect(NOT_POISONED)
}
}
/// [`std::sync::Condvar`] wrapper which always panics on encountering poison.
#[derive(Default)]
pub struct Condvar(std::sync::Condvar);
impl Condvar {
#[inline]
pub const fn new() -> Self {
Self(std::sync::Condvar::new())
}
#[track_caller]
#[inline]
pub fn wait_timeout_while<'a, T, F>(
&self,
guard: std::sync::MutexGuard<'a, T>,
dur: std::time::Duration,
condition: F,
) -> (std::sync::MutexGuard<'a, T>, std::sync::WaitTimeoutResult)
where
F: FnMut(&mut T) -> bool,
{
self.0
.wait_timeout_while(guard, dur, condition)
.expect(NOT_POISONED)
}
}
impl std::ops::Deref for Condvar {
type Target = std::sync::Condvar;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub fn ensure_malloc_used() {
#[cfg(feature = "mimalloc")]
{
// This is a load-bearing debug line.
// Building `libmimalloc-sys` with the `override` feature will override `malloc` and
// `free` as used through the Rust global allocator, SQLite, and `libc`. But...`cargo`
// doesn't seem to build `libmimalloc-sys` at all if it's not referenced from Rust code.
tracing::debug!("mimalloc version {}", unsafe {
libmimalloc_sys::mi_version()
})
}
}

View File

@ -15,9 +15,10 @@ use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll, Waker};
use crate::Condvar;
use crate::Mutex;
use futures::Future;
use slab::Slab;
use std::sync::{Condvar, Mutex};
#[derive(Debug)]
pub struct ShutdownError;
@ -47,7 +48,6 @@ impl Drop for Sender {
.0
.wakers
.lock()
.unwrap()
.take()
.expect("only the single Sender takes the slab");
for w in wakers.drain() {
@ -78,14 +78,14 @@ const NO_WAKER: usize = usize::MAX;
impl Receiver {
pub fn check(&self) -> Result<(), ShutdownError> {
if self.0.wakers.lock().unwrap().is_none() {
if self.0.wakers.lock().is_none() {
Err(ShutdownError)
} else {
Ok(())
}
}
pub fn as_future(&self) -> ReceiverRefFuture {
pub fn as_future(&self) -> ReceiverRefFuture<'_> {
ReceiverRefFuture {
receiver: self,
waker_i: NO_WAKER,
@ -107,12 +107,11 @@ impl Receiver {
}
pub fn wait_for(&self, timeout: std::time::Duration) -> Result<(), ShutdownError> {
let l = self.0.wakers.lock().unwrap();
let l = self.0.wakers.lock();
let result = self
.0
.condvar
.wait_timeout_while(l, timeout, |wakers| wakers.is_some())
.unwrap();
.wait_timeout_while(l, timeout, |wakers| wakers.is_some());
if result.1.timed_out() {
Ok(())
} else {
@ -122,7 +121,7 @@ impl Receiver {
}
fn poll_impl(inner: &Inner, waker_i: &mut usize, cx: &mut Context<'_>) -> Poll<()> {
let mut l = inner.wakers.lock().unwrap();
let mut l = inner.wakers.lock();
let wakers = match &mut *l {
None => return Poll::Ready(()),
Some(w) => w,
@ -152,7 +151,7 @@ impl Drop for ReceiverRefFuture<'_> {
if self.waker_i == NO_WAKER {
return;
}
let mut l = self.receiver.0.wakers.lock().unwrap();
let mut l = self.receiver.0.wakers.lock();
if let Some(wakers) = &mut *l {
wakers.remove(self.waker_i);
}
@ -173,7 +172,7 @@ impl Drop for ReceiverFuture {
if self.waker_i == NO_WAKER {
return;
}
let mut l = self.receiver.wakers.lock().unwrap();
let mut l = self.receiver.wakers.lock();
if let Some(wakers) = &mut *l {
wakers.remove(self.waker_i);
}

View File

@ -55,7 +55,7 @@ fn fixed_len_num<'a, T: FromStr>(len: usize) -> impl FnMut(&'a str) -> IResult<'
}
/// Parses `YYYY-mm-dd` into pieces.
fn parse_datepart(input: &str) -> IResult<&str, (i16, i8, i8)> {
fn parse_datepart(input: &str) -> IResult<'_, &str, (i16, i8, i8)> {
tuple((
fixed_len_num(4),
preceded(tag("-"), fixed_len_num(2)),
@ -64,7 +64,7 @@ fn parse_datepart(input: &str) -> IResult<&str, (i16, i8, i8)> {
}
/// Parses `HH:MM[:SS[:FFFFF]]` into pieces.
fn parse_timepart(input: &str) -> IResult<&str, (i8, i8, i8, i32)> {
fn parse_timepart(input: &str) -> IResult<'_, &str, (i8, i8, i8, i32)> {
let (input, (hr, _, min)) = tuple((fixed_len_num(2), tag(":"), fixed_len_num(2)))(input)?;
let (input, stuff) = opt(tuple((
preceded(tag(":"), fixed_len_num(2)),
@ -75,7 +75,7 @@ fn parse_timepart(input: &str) -> IResult<&str, (i8, i8, i8, i32)> {
}
/// Parses `Z` (UTC) or `{+,-,}HH:MM` into a time zone offset in seconds.
fn parse_zone(input: &str) -> IResult<&str, i32> {
fn parse_zone(input: &str) -> IResult<'_, &str, i32> {
alt((
nom::combinator::value(0, tag("Z")),
map(

View File

@ -122,33 +122,36 @@ pub fn install() {
match std::env::var("MOONFIRE_FORMAT") {
Ok(s) if s == "systemd" => {
let sub = tracing_subscriber::registry().with(
tracing_subscriber::fmt::Layer::new()
.with_writer(std::io::stderr)
.with_ansi(false)
.event_format(FormatSystemd)
.with_filter(filter),
);
let sub = tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::Layer::new()
.with_writer(std::io::stderr)
.with_ansi(false)
.event_format(FormatSystemd),
)
.with(filter);
tracing::subscriber::set_global_default(sub).unwrap();
}
Ok(s) if s == "json" => {
let sub = tracing_subscriber::registry().with(
tracing_subscriber::fmt::Layer::new()
.with_writer(std::io::stderr)
.with_thread_names(true)
.json()
.with_filter(filter),
);
let sub = tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::Layer::new()
.with_writer(std::io::stderr)
.with_thread_names(true)
.json(),
)
.with(filter);
tracing::subscriber::set_global_default(sub).unwrap();
}
_ => {
let sub = tracing_subscriber::registry().with(
tracing_subscriber::fmt::Layer::new()
.with_writer(std::io::stderr)
.with_timer(JiffTimer)
.with_thread_names(true)
.with_filter(filter),
);
let sub = tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::Layer::new()
.with_writer(std::io::stderr)
.with_timer(JiffTimer)
.with_thread_names(true),
)
.with(filter);
tracing::subscriber::set_global_default(sub).unwrap();
}
}

View File

@ -5,7 +5,7 @@ authors = ["Scott Lamb <slamb@slamb.org>"]
readme = "../README.md"
edition = "2021"
license-file = "../../LICENSE.txt"
rust-version = "1.82"
rust-version = "1.88"
publish = false
[features]
@ -19,7 +19,6 @@ base = { package = "moonfire-base", path = "../base" }
base64 = { workspace = true }
blake3 = "1.0.0"
byteorder = "1.0"
cstr = "0.2.5"
diff = "0.1.12"
futures = "0.3"
h264-reader = { workspace = true }
@ -28,9 +27,11 @@ itertools = { workspace = true }
jiff = { workspace = true }
libc = "0.2"
nix = { workspace = true, features = ["dir", "feature", "fs", "mman"] }
num-rational = { version = "0.4.0", default-features = false, features = ["std"] }
num-rational = { version = "0.4.0", default-features = false, features = [
"std",
] }
pretty-hex = { workspace = true }
protobuf = "3.0"
protobuf = { workspace = true }
ring = { workspace = true }
rusqlite = { workspace = true }
scrypt = "0.11.0"
@ -40,9 +41,8 @@ smallvec = "1.0"
tempfile = "3.2.0"
tokio = { version = "1.24", features = ["macros", "rt-multi-thread", "sync"] }
tracing = { workspace = true }
ulid = "1.0.0"
url = { version = "2.1.1", features = ["serde"] }
uuid = { version = "1.1.2", features = ["serde", "std", "v4"] }
uuid = { workspace = true }
[build-dependencies]
protobuf-codegen = "3.0"
protobuf-codegen = { workspace = true }

View File

@ -1073,8 +1073,8 @@ mod tests {
.unwrap();
assert_eq!(s.use_count, 1);
let mut tx = conn.transaction().unwrap();
state.flush(&mut tx).unwrap();
let tx = conn.transaction().unwrap();
state.flush(&tx).unwrap();
tx.commit().unwrap();
state.post_flush();
@ -1224,8 +1224,8 @@ mod tests {
c.username = "foo".to_owned();
state.apply(&conn, c).unwrap();
assert!(state.users_by_name.get("slamb").is_none());
assert!(state.users_by_name.get("foo").is_some());
assert!(!state.users_by_name.contains_key("slamb"));
assert!(state.users_by_name.contains_key("foo"));
}
#[test]

View File

@ -36,6 +36,7 @@ use crate::schema;
use crate::signal;
use base::clock::{self, Clocks};
use base::strutil::encode_size;
use base::Mutex;
use base::{bail, err, Error};
use base::{FastHashMap, FastHashSet};
use hashlink::LinkedHashMap;
@ -52,7 +53,7 @@ use std::path::PathBuf;
use std::str;
use std::string::String;
use std::sync::Arc;
use std::sync::{Mutex, MutexGuard};
use std::sync::MutexGuard;
use std::vec::Vec;
use tracing::warn;
use tracing::{error, info, trace};
@ -562,7 +563,7 @@ impl Stream {
pub fn days(&self) -> days::Map<days::StreamValue> {
let mut days = self.committed_days.clone();
for u in &self.uncommitted {
let l = u.lock().unwrap();
let l = u.lock();
days.adjust(
l.start..l.start + recording::Duration(i64::from(l.wall_duration_90k)),
1,
@ -650,7 +651,7 @@ pub struct CompositeId(pub i64);
impl CompositeId {
pub fn new(stream_id: i32, recording_id: i32) -> Self {
CompositeId((stream_id as i64) << 32 | recording_id as i64)
CompositeId(((stream_id as i64) << 32) | recording_id as i64)
}
pub fn stream(self) -> i32 {
@ -896,7 +897,7 @@ impl LockedDatabase {
);
match stream.uncommitted.back() {
Some(s) => {
let l = s.lock().unwrap();
let l = s.lock();
r.prev_media_duration =
l.prev_media_duration + recording::Duration(l.media_duration_90k.into());
r.prev_runs = l.prev_runs + if l.run_offset == 0 { 1 } else { 0 };
@ -936,7 +937,7 @@ impl LockedDatabase {
msg("can't sync un-added recording {id}")
);
}
let l = stream.uncommitted[stream.synced_recordings].lock().unwrap();
let l = stream.uncommitted[stream.synced_recordings].lock();
let bytes = i64::from(l.sample_file_bytes);
stream.bytes_to_add += bytes;
stream.fs_bytes_to_add += round_up(bytes);
@ -1014,7 +1015,7 @@ impl LockedDatabase {
let mut new_duration = 0;
let mut new_runs = 0;
for i in 0..s.synced_recordings {
let l = s.uncommitted[i].lock().unwrap();
let l = s.uncommitted[i].lock();
raw::insert_recording(
&tx,
o,
@ -1141,7 +1142,7 @@ impl LockedDatabase {
let u = s.uncommitted.pop_front().unwrap();
log.added
.push(CompositeId::new(stream_id, s.cum_recordings));
let l = u.lock().unwrap();
let l = u.lock();
s.cum_recordings += 1;
let wall_dur = recording::Duration(l.wall_duration_90k.into());
let media_dur = recording::Duration(l.media_duration_90k.into());
@ -1310,7 +1311,7 @@ impl LockedDatabase {
raw::list_recordings_by_time(&self.conn, stream_id, desired_time.clone(), f)?;
for (i, u) in s.uncommitted.iter().enumerate() {
let row = {
let l = u.lock().unwrap();
let l = u.lock();
if l.video_samples > 0 {
let end = l.start + recording::Duration(l.wall_duration_90k as i64);
if l.start > desired_time.end || end < desired_time.start {
@ -1351,7 +1352,7 @@ impl LockedDatabase {
);
for i in start..end {
let row = {
let l = s.uncommitted[i].lock().unwrap();
let l = s.uncommitted[i].lock();
if l.video_samples > 0 {
l.to_list_row(
CompositeId::new(stream_id, s.cum_recordings + i as i32),
@ -1489,7 +1490,7 @@ impl LockedDatabase {
),
);
}
let l = s.uncommitted[i as usize].lock().unwrap();
let l = s.uncommitted[i as usize].lock();
return f(&RecordingPlayback {
video_index: &l.video_index,
});
@ -1797,7 +1798,7 @@ impl LockedDatabase {
pub fn add_sample_file_dir(&mut self, path: PathBuf) -> Result<i32, Error> {
let mut meta = schema::DirMeta::default();
let uuid = Uuid::new_v4();
let uuid = Uuid::now_v7();
let uuid_bytes = &uuid.as_bytes()[..];
let o = self
.open
@ -1906,7 +1907,7 @@ impl LockedDatabase {
/// Adds a camera.
pub fn add_camera(&mut self, mut camera: CameraChange) -> Result<i32, Error> {
let uuid = Uuid::new_v4();
let uuid = Uuid::now_v7();
let uuid_bytes = &uuid.as_bytes()[..];
let tx = self.conn.transaction()?;
let streams;
@ -2227,7 +2228,7 @@ pub fn init(conn: &mut rusqlite::Connection) -> Result<(), Error> {
tx.execute_batch(include_str!("schema.sql"))
.map_err(|e| err!(e, msg("unable to create database schema")))?;
{
let uuid = ::uuid::Uuid::new_v4();
let uuid = ::uuid::Uuid::now_v7();
let uuid_bytes = &uuid.as_bytes()[..];
tx.execute("insert into meta (uuid) values (?)", params![uuid_bytes])?;
}
@ -2319,7 +2320,7 @@ impl<C: Clocks + Clone> Drop for Database<C> {
return; // don't flush while panicking.
}
if let Some(m) = self.db.take() {
if let Err(e) = m.into_inner().unwrap().flush(&self.clocks, "drop") {
if let Err(e) = m.into_inner().flush(&self.clocks, "drop") {
error!(err = %e.chain(), "final database flush failed");
}
}
@ -2353,7 +2354,7 @@ impl<C: Clocks + Clone> Database<C> {
let real = recording::Time::from(clocks.realtime());
let mut stmt = conn
.prepare(" insert into open (uuid, start_time_90k, boot_uuid) values (?, ?, ?)")?;
let open_uuid = SqlUuid(Uuid::new_v4());
let open_uuid = SqlUuid(Uuid::now_v7());
let boot_uuid = match get_boot_uuid() {
Err(e) => {
warn!(err = %e.chain(), "unable to get boot uuid");
@ -2416,9 +2417,9 @@ impl<C: Clocks + Clone> Database<C> {
/// Locks the database; the returned reference is the only way to perform (read or write)
/// operations.
pub fn lock(&self) -> DatabaseGuard<C> {
pub fn lock(&self) -> DatabaseGuard<'_, C> {
let timer = clock::TimerGuard::new(&self.clocks, acquisition);
let db = self.db.as_ref().unwrap().lock().unwrap();
let db = self.db.as_ref().unwrap().lock();
drop(timer);
let _timer = clock::TimerGuard::<C, &'static str, fn() -> &'static str>::new(
&self.clocks,
@ -2435,7 +2436,7 @@ impl<C: Clocks + Clone> Database<C> {
/// This allows verification that a newly opened database is in an acceptable state.
#[cfg(test)]
fn close(mut self) -> rusqlite::Connection {
self.db.take().unwrap().into_inner().unwrap().conn
self.db.take().unwrap().into_inner().conn
}
}
@ -2519,7 +2520,7 @@ mod tests {
rows = 0;
{
let db = db.lock();
let all_time = recording::Time(i64::min_value())..recording::Time(i64::max_value());
let all_time = recording::Time(i64::MIN)..recording::Time(i64::MAX);
db.list_recordings_by_time(stream_id, all_time, &mut |_row| {
rows += 1;
Ok(())
@ -2546,7 +2547,7 @@ mod tests {
let mut recording_id = None;
{
let db = db.lock();
let all_time = recording::Time(i64::min_value())..recording::Time(i64::max_value());
let all_time = recording::Time(i64::MIN)..recording::Time(i64::MAX);
db.list_recordings_by_time(stream_id, all_time, &mut |row| {
rows += 1;
recording_id = Some(row.id);
@ -2868,9 +2869,8 @@ mod tests {
.get(&sample_file_dir_id)
.unwrap()
.garbage_unlinked
.iter()
.copied()
.collect();
.to_vec();
assert_eq!(&g, &[]);
}

View File

@ -7,7 +7,7 @@
//! This mostly includes opening a directory and looking for recordings within it.
//! Updates to the directory happen through [crate::writer].
mod reader;
pub mod reader;
use crate::coding;
use crate::db::CompositeId;
@ -421,12 +421,12 @@ mod tests {
meta.dir_uuid.extend_from_slice(fake_uuid);
{
let o = meta.last_complete_open.mut_or_insert_default();
o.id = u32::max_value();
o.id = u32::MAX;
o.uuid.extend_from_slice(fake_uuid);
}
{
let o = meta.in_progress_open.mut_or_insert_default();
o.id = u32::max_value();
o.id = u32::MAX;
o.uuid.extend_from_slice(fake_uuid);
}
let data = meta

View File

@ -656,13 +656,16 @@ mod bench {
/// Benchmarks the decoder, which is performance-critical for .mp4 serving.
#[bench]
fn bench_decoder(b: &mut test::Bencher) {
crate::testutil::init();
let data = include_bytes!("testdata/video_sample_index.bin");
b.bytes = data.len() as u64;
b.iter(|| {
let mut it = SampleIndexIterator::default();
while it.next(data).unwrap() {}
assert_eq!(30104460, it.pos);
assert_eq!(5399985, it.start_90k);
for _i in 0..100 {
let mut it = SampleIndexIterator::default();
while it.next(data).unwrap() {}
assert_eq!(30104460, it.pos);
assert_eq!(5399985, it.start_90k);
}
});
}
}

View File

@ -81,12 +81,12 @@ impl Point {
}
/// Returns an iterator over state as of immediately before this point.
fn prev(&self) -> PointDataIterator {
fn prev(&self) -> PointDataIterator<'_> {
PointDataIterator::new(&self.data[0..self.changes_off])
}
/// Returns an iterator over changes in this point.
fn changes(&self) -> PointDataIterator {
fn changes(&self) -> PointDataIterator<'_> {
PointDataIterator::new(&self.data[self.changes_off..])
}

View File

@ -37,6 +37,7 @@ pub const TEST_VIDEO_SAMPLE_ENTRY_DATA: &[u8] =
/// * use a fast but insecure password hashing format.
pub fn init() {
INIT.call_once(|| {
base::ensure_malloc_used();
base::tracing_setup::install_for_tests();
base::time::testutil::init_zone();
crate::auth::set_test_config();
@ -198,7 +199,7 @@ pub fn add_dummy_recordings_to_db(db: &db::Database, num: usize) {
wall_duration_90k: 5399985,
video_samples: 1800,
video_sync_samples: 60,
video_sample_entry_id: video_sample_entry_id,
video_sample_entry_id,
video_index: data,
run_offset: 0,
..Default::default()

View File

@ -80,17 +80,17 @@ pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
create index user_session_uid on user_session (user_id);
"#,
)?;
let db_uuid = ::uuid::Uuid::new_v4();
let db_uuid = ::uuid::Uuid::now_v7();
let db_uuid_bytes = &db_uuid.as_bytes()[..];
tx.execute("insert into meta (uuid) values (?)", params![db_uuid_bytes])?;
let open_uuid = ::uuid::Uuid::new_v4();
let open_uuid = ::uuid::Uuid::now_v7();
let open_uuid_bytes = &open_uuid.as_bytes()[..];
tx.execute(
"insert into open (uuid) values (?)",
params![open_uuid_bytes],
)?;
let open_id = tx.last_insert_rowid() as u32;
let dir_uuid = ::uuid::Uuid::new_v4();
let dir_uuid = ::uuid::Uuid::now_v7();
let dir_uuid_bytes = &dir_uuid.as_bytes()[..];
// Write matching metadata to the directory.

View File

@ -27,7 +27,7 @@ fn default_pixel_aspect_ratio(width: u16, height: u16) -> (u16, u16) {
(1, 1)
}
fn parse(data: &[u8]) -> Result<AvcDecoderConfigurationRecord, Error> {
fn parse(data: &[u8]) -> Result<AvcDecoderConfigurationRecord<'_>, Error> {
if data.len() < 94 || &data[4..8] != b"avc1" || &data[90..94] != b"avcC" {
bail!(
DataLoss,

View File

@ -10,13 +10,13 @@ use crate::recording::{self, MAX_RECORDING_WALL_DURATION};
use base::clock::{self, Clocks};
use base::shutdown::ShutdownError;
use base::FastHashMap;
use base::Mutex;
use base::{bail, err, Error};
use std::cmp::{self, Ordering};
use std::convert::TryFrom;
use std::io;
use std::mem;
use std::path::PathBuf;
use std::sync::Mutex;
use std::sync::{mpsc, Arc};
use std::thread;
use tracing::{debug, trace, warn};
@ -880,7 +880,7 @@ impl<F: FileWriter> InnerWriter<F> {
db: &db::Database<C>,
stream_id: i32,
) -> Result<(), Error> {
let mut l = self.r.lock().unwrap();
let mut l = self.r.lock();
// design/time.md explains these time manipulations in detail.
let prev_media_duration_90k = l.media_duration_90k;
@ -969,7 +969,7 @@ impl<F: FileWriter> InnerWriter<F> {
// This always ends a live segment.
let wall_duration;
{
let mut l = self.r.lock().unwrap();
let mut l = self.r.lock();
l.flags = flags;
l.local_time_delta = self.local_start - l.start;
l.sample_file_blake3 = Some(*blake3.as_bytes());
@ -1012,11 +1012,11 @@ mod tests {
use crate::recording;
use crate::testutil;
use base::clock::{Clocks, SimulatedClocks};
use base::Mutex;
use std::collections::VecDeque;
use std::io;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use tracing::{trace, warn};
#[derive(Clone)]
@ -1039,10 +1039,10 @@ mod tests {
MockDir(Arc::new(Mutex::new(VecDeque::new())))
}
fn expect(&self, action: MockDirAction) {
self.0.lock().unwrap().push_back(action);
self.0.lock().push_back(action);
}
fn ensure_done(&self) {
assert_eq!(self.0.lock().unwrap().len(), 0);
assert_eq!(self.0.lock().len(), 0);
}
}
@ -1053,7 +1053,6 @@ mod tests {
match self
.0
.lock()
.unwrap()
.pop_front()
.expect("got create_file with no expectation")
{
@ -1068,7 +1067,6 @@ mod tests {
match self
.0
.lock()
.unwrap()
.pop_front()
.expect("got sync with no expectation")
{
@ -1080,7 +1078,6 @@ mod tests {
match self
.0
.lock()
.unwrap()
.pop_front()
.expect("got unlink_file with no expectation")
{
@ -1096,7 +1093,7 @@ mod tests {
impl Drop for MockDir {
fn drop(&mut self) {
if !::std::thread::panicking() {
assert_eq!(self.0.lock().unwrap().len(), 0);
assert_eq!(self.0.lock().len(), 0);
}
}
}
@ -1106,6 +1103,8 @@ mod tests {
enum MockFileAction {
SyncAll(Box<dyn Fn() -> Result<(), io::Error> + Send>),
#[allow(clippy::type_complexity)]
Write(Box<dyn Fn(&[u8]) -> Result<usize, io::Error> + Send>),
}
@ -1114,10 +1113,10 @@ mod tests {
MockFile(Arc::new(Mutex::new(VecDeque::new())))
}
fn expect(&self, action: MockFileAction) {
self.0.lock().unwrap().push_back(action);
self.0.lock().push_back(action);
}
fn ensure_done(&self) {
assert_eq!(self.0.lock().unwrap().len(), 0);
assert_eq!(self.0.lock().len(), 0);
}
}
@ -1126,7 +1125,6 @@ mod tests {
match self
.0
.lock()
.unwrap()
.pop_front()
.expect("got sync_all with no expectation")
{
@ -1138,7 +1136,6 @@ mod tests {
match self
.0
.lock()
.unwrap()
.pop_front()
.expect("got write with no expectation")
{
@ -1215,7 +1212,7 @@ mod tests {
}
fn eio() -> io::Error {
io::Error::new(io::ErrorKind::Other, "got EIO")
io::Error::other("got EIO")
}
#[test]
@ -1263,7 +1260,7 @@ mod tests {
&mut h.shutdown_rx,
b"2",
recording::Time(2),
i32::max_value() as i64 + 1,
i64::from(i32::MAX) + 1,
true,
video_sample_entry_id,
)

View File

@ -4,6 +4,7 @@
use base::strutil::{decode_size, encode_size};
use base::Error;
use base::Mutex;
use cursive::traits::{Nameable, Resizable};
use cursive::view::Scrollable;
use cursive::Cursive;
@ -11,7 +12,7 @@ use cursive::{views, With};
use db::writer;
use std::collections::BTreeMap;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use tracing::{debug, trace};
use super::tab_complete::TabCompleteEditView;
@ -119,7 +120,7 @@ fn confirm_deletion(model: &Mutex<Model>, siv: &mut Cursive, to_delete: i64) {
}
fn actually_delete(model: &Mutex<Model>, siv: &mut Cursive) {
let model = model.lock().unwrap();
let model = model.lock();
let new_limits: Vec<_> = model
.streams
.iter()
@ -147,7 +148,7 @@ fn actually_delete(model: &Mutex<Model>, siv: &mut Cursive) {
fn press_change(model: &Arc<Mutex<Model>>, siv: &mut Cursive) {
let to_delete = {
let l = model.lock().unwrap();
let l = model.lock();
if l.errors > 0 {
return;
}
@ -185,7 +186,7 @@ fn press_change(model: &Arc<Mutex<Model>>, siv: &mut Cursive) {
siv.add_layer(dialog);
} else {
siv.pop_layer();
update_limits(&model.lock().unwrap(), siv);
update_limits(&model.lock(), siv);
}
}
@ -384,13 +385,13 @@ fn edit_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive, dir_id: i32) {
.child(views::TextView::new("usage").fixed_width(BYTES_WIDTH))
.child(views::TextView::new("limit").fixed_width(BYTES_WIDTH)),
);
let l = model.lock().unwrap();
let l = model.lock();
for (&id, stream) in &l.streams {
let mut record_cb = views::Checkbox::new();
record_cb.set_checked(stream.record);
record_cb.set_on_change({
let model = model.clone();
move |_siv, record| edit_record(&mut model.lock().unwrap(), id, record)
move |_siv, record| edit_record(&mut model.lock(), id, record)
});
list.add_child(
&stream.label,
@ -403,7 +404,7 @@ fn edit_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive, dir_id: i32) {
.on_edit({
let model = model.clone();
move |siv, content, _pos| {
edit_limit(&mut model.lock().unwrap(), siv, id, content)
edit_limit(&mut model.lock(), siv, id, content)
}
})
.on_submit({

View File

@ -2,7 +2,8 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use std::sync::{Arc, Mutex};
use base::Mutex;
use std::sync::Arc;
use cursive::{
direction::Direction,
@ -37,25 +38,25 @@ impl TabCompleteEditView {
}
pub fn get_content(&self) -> Arc<String> {
self.edit_view.lock().unwrap().get_content()
self.edit_view.lock().get_content()
}
}
impl View for TabCompleteEditView {
fn draw(&self, printer: &Printer) {
self.edit_view.lock().unwrap().draw(printer)
self.edit_view.lock().draw(printer)
}
fn layout(&mut self, size: Vec2) {
self.edit_view.lock().unwrap().layout(size)
self.edit_view.lock().layout(size)
}
fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
self.edit_view.lock().unwrap().take_focus(source)
self.edit_view.lock().take_focus(source)
}
fn on_event(&mut self, event: Event) -> EventResult {
if !self.edit_view.lock().unwrap().is_enabled() {
if !self.edit_view.lock().is_enabled() {
return EventResult::Ignored;
}
@ -66,12 +67,12 @@ impl View for TabCompleteEditView {
EventResult::consumed()
}
} else {
self.edit_view.lock().unwrap().on_event(event)
self.edit_view.lock().on_event(event)
}
}
fn important_area(&self, view_size: Vec2) -> Rect {
self.edit_view.lock().unwrap().important_area(view_size)
self.edit_view.lock().important_area(view_size)
}
}
@ -80,10 +81,10 @@ fn tab_complete(
tab_completer: TabCompleteFn,
autofill_one: bool,
) -> EventResult {
let completions = tab_completer(edit_view.lock().unwrap().get_content().as_str());
let completions = tab_completer(edit_view.lock().get_content().as_str());
EventResult::with_cb_once(move |siv| match *completions {
[] => {}
[ref completion] if autofill_one => edit_view.lock().unwrap().set_content(completion)(siv),
[ref completion] if autofill_one => edit_view.lock().set_content(completion)(siv),
[..] => {
siv.add_layer(TabCompletePopup {
popup: views::MenuPopup::new(Arc::new({
@ -91,7 +92,7 @@ fn tab_complete(
for completion in completions {
let edit_view = edit_view.clone();
tree.add_leaf(completion.clone(), move |siv| {
edit_view.lock().unwrap().set_content(&completion)(siv)
edit_view.lock().set_content(&completion)(siv)
})
}
})
@ -114,7 +115,7 @@ impl TabCompletePopup {
let tab_completer = self.tab_completer.clone();
EventResult::with_cb_once(move |s| {
s.pop_layer();
edit_view.lock().unwrap().on_event(event).process(s);
edit_view.lock().on_event(event).process(s);
tab_complete(edit_view, tab_completer, false).process(s);
})
}

View File

@ -77,6 +77,7 @@ fn main() {
}
base::tracing_setup::install();
base::time::init_zone(jiff::tz::TimeZone::system);
base::ensure_malloc_used();
// Get the program name from the OS (e.g. if invoked as `target/debug/nvr`: `nvr`),
// falling back to the crate name if conversion to a path/UTF-8 string fails.

View File

@ -61,10 +61,10 @@ use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use bytes::BytesMut;
use db::dir;
use db::recording::{self, rescale, TIME_UNITS_PER_SEC};
use futures::stream::{self, TryStreamExt};
use futures::Stream;
use http::header::HeaderValue;
use hyper::body::Buf;
use pin_project::pin_project;
use reffers::ARefss;
use smallvec::SmallVec;
use std::cmp;
@ -403,7 +403,7 @@ impl Segment {
.lock()
.with_recording_playback(self.s.id, &mut |playback| self.build_index(playback))
.map_err(|err| {
error!(%err, recording_id = %self.s.id, "unable to build index for segment");
error!(err = %err.chain(), recording_id = %self.s.id, "unable to build index for segment");
})
})
.as_deref()
@ -758,19 +758,37 @@ impl Slice {
}
}
#[pin_project(project = SliceStreamProj)]
enum SliceStream {
Once(Option<Result<Chunk, Error>>),
File(#[pin] db::dir::reader::FileStream),
}
impl futures::stream::Stream for SliceStream {
type Item = Result<Chunk, BoxedError>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
match self.project() {
SliceStreamProj::Once(o) => {
std::task::Poll::Ready(o.take().map(|r| r.map_err(wrap_error)))
}
SliceStreamProj::File(f) => f.poll_next(cx).map_ok(Chunk::from).map_err(wrap_error),
}
}
}
impl slices::Slice for Slice {
type Ctx = File;
type Chunk = Chunk;
type Stream = SliceStream;
fn end(&self) -> u64 {
self.0 & 0xFF_FF_FF_FF_FF
}
fn get_range(
&self,
f: &File,
range: Range<u64>,
len: u64,
) -> Box<dyn Stream<Item = Result<Self::Chunk, BoxedError>> + Send + Sync> {
fn get_range(&self, f: &File, range: Range<u64>, len: u64) -> SliceStream {
trace!("getting mp4 slice {:?}'s range {:?} / {}", self, range, len);
let p = self.p();
let res = match self.t() {
@ -802,22 +820,20 @@ impl slices::Slice for Slice {
SliceType::SubtitleSampleData => f.0.get_subtitle_sample_data(p, range.clone(), len),
SliceType::Truns => self.wrap_truns(f, range.clone(), len as usize),
};
Box::new(stream::once(futures::future::ready(
res.map_err(wrap_error).and_then(move |c| {
if c.remaining() != (range.end - range.start) as usize {
return Err(wrap_error(err!(
Internal,
msg(
"{:?} range {:?} produced incorrect len {}",
self,
range,
c.remaining()
)
)));
}
Ok(c)
}),
)))
SliceStream::Once(Some(res.and_then(move |c| {
if c.remaining() != (range.end - range.start) as usize {
bail!(
Internal,
msg(
"{:?} range {:?} produced incorrect len {}",
self,
range,
c.remaining()
)
);
}
Ok(c)
})))
}
fn get_slices(ctx: &File) -> &Slices<Self> {
@ -1796,32 +1812,20 @@ impl FileInner {
.into())
}
/// Gets a `Chunk` of video sample data from disk.
/// This works by `mmap()`ing in the data. There are a couple caveats:
///
/// * The thread which reads the resulting slice is likely to experience major page faults.
/// Eventually this will likely be rewritten to `mmap()` the memory in another thread, and
/// `mlock()` and send chunks of it to be read and `munlock()`ed to avoid this problem.
///
/// * If the backing file is truncated, the program will crash with `SIGBUS`. This shouldn't
/// happen because nothing should be touching Moonfire NVR's files but itself.
fn get_video_sample_data(
&self,
i: usize,
r: Range<u64>,
) -> Box<dyn Stream<Item = Result<Chunk, BoxedError>> + Send + Sync> {
/// Gets a stream representing a range of segment `i`'s sample data from disk.
fn get_video_sample_data(&self, i: usize, r: Range<u64>) -> SliceStream {
let s = &self.segments[i];
let sr = s.s.sample_file_range();
let f = match self.dirs_by_stream_id.get(&s.s.id.stream()) {
None => {
return Box::new(stream::iter(std::iter::once(Err(wrap_error(err!(
return SliceStream::Once(Some(Err(err!(
NotFound,
msg("{}: stream not found", s.s.id)
))))))
))))
}
Some(d) => d.open_file(s.s.id, (r.start + sr.start)..(r.end + sr.start)),
};
Box::new(f.map_ok(Chunk::from).map_err(wrap_error))
SliceStream::File(f)
}
fn get_subtitle_sample_data(&self, i: usize, r: Range<u64>, len: u64) -> Result<Chunk, Error> {
@ -2005,7 +2009,6 @@ mod tests {
use std::fs;
use std::ops::Range;
use std::path::Path;
use std::pin::Pin;
use std::str;
use tracing::info;
@ -2014,7 +2017,7 @@ mod tests {
E::Error: ::std::fmt::Debug,
{
let mut p = 0;
Pin::from(e.get_range(start..start + slice.len() as u64))
e.get_range(start..start + slice.len() as u64)
.try_for_each(|mut chunk| {
let len = chunk.remaining();
chunk.copy_to_slice(&mut slice[p..p + len]);
@ -2040,7 +2043,7 @@ mod tests {
hasher.update(&b"\r\n"[..]);
}
hasher.update(&b"\r\n"[..]);
Pin::from(e.get_range(0..e.len()))
e.get_range(0..e.len())
.try_fold(hasher, |mut hasher, mut chunk| {
while chunk.has_remaining() {
let c = chunk.chunk();
@ -2153,8 +2156,7 @@ mod tests {
let interior = self.stack.last().expect("at root").interior.clone();
let len = (interior.end - interior.start) as usize;
trace!("get_all: start={}, len={}", interior.start, len);
let mut out = Vec::with_capacity(len);
unsafe { out.set_len(len) };
let mut out = vec![0; len];
fill_slice(&mut out[..], &self.mp4, interior.start).await;
out
}
@ -2346,7 +2348,7 @@ mod tests {
builder
.include_timestamp_subtitle_track(include_subtitles)
.unwrap();
let all_time = recording::Time(i64::min_value())..recording::Time(i64::max_value());
let all_time = recording::Time(i64::MIN)..recording::Time(i64::MAX);
{
let db = tdb.db.lock();
db.list_recordings_by_time(TEST_STREAM_ID, all_time, &mut |r| {
@ -2377,7 +2379,7 @@ mod tests {
.open(&filename)
.unwrap();
use ::std::io::Write;
Pin::from(mp4.get_range(0..mp4.len()))
mp4.get_range(0..mp4.len())
.try_for_each(|mut chunk| {
while chunk.has_remaining() {
let c = chunk.chunk();
@ -2973,13 +2975,16 @@ mod tests {
mod bench {
extern crate test;
use std::convert::Infallible;
use std::net::SocketAddr;
use super::tests::create_mp4_from_db;
use base::clock::RealClocks;
use db::recording;
use db::testutil::{self, TestDb};
use futures::future;
use http_serve;
use hyper;
use hyper::service::service_fn;
use url::Url;
/// An HTTP server for benchmarking.
@ -3000,28 +3005,35 @@ mod bench {
testutil::add_dummy_recordings_to_db(&db.db, 60);
let mp4 = create_mp4_from_db(&db, 0, 0, false);
let p = mp4.0.initial_sample_byte_pos;
let make_svc = hyper::service::make_service_fn(move |_conn| {
future::ok::<_, std::convert::Infallible>(hyper::service::service_fn({
let addr: SocketAddr = ([127, 0, 0, 1], 0).into();
let listener = std::net::TcpListener::bind(addr).unwrap();
listener.set_nonblocking(true).unwrap();
let addr = listener.local_addr().unwrap(); // resolve port 0 to a real ephemeral port number.
let srv = async move {
let listener = tokio::net::TcpListener::from_std(listener).unwrap();
loop {
let (conn, _remote_addr) = listener.accept().await.unwrap();
conn.set_nodelay(true).unwrap();
let io = hyper_util::rt::TokioIo::new(conn);
let mp4 = mp4.clone();
move |req| {
future::ok::<hyper::Response<crate::body::Body>, hyper::Error>(
http_serve::serve(mp4.clone(), &req),
)
}
}))
});
let rt = tokio::runtime::Runtime::new().unwrap();
let srv = {
let _guard = rt.enter();
let addr = ([127, 0, 0, 1], 0).into();
hyper::server::Server::bind(&addr)
.tcp_nodelay(true)
.serve(make_svc)
let svc_fn = service_fn(move |req| {
futures::future::ok::<_, Infallible>(http_serve::serve(mp4.clone(), &req))
});
tokio::spawn(
hyper::server::conn::http1::Builder::new().serve_connection(io, svc_fn),
);
}
};
let addr = srv.local_addr(); // resolve port 0 to a real ephemeral port number.
::std::thread::spawn(move || {
rt.block_on(srv).unwrap();
});
std::thread::Builder::new()
.name("bench-server".to_owned())
.spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(srv)
})
.unwrap();
BenchServer {
url: Url::parse(&format!("http://{}:{}/", addr.ip(), addr.port())).unwrap(),
generated_len: p,
@ -3053,7 +3065,11 @@ mod bench {
db.with_recording_playback(segment.s.id, &mut |playback| {
let v = segment.build_index(playback).unwrap(); // warm.
b.bytes = v.len() as u64; // define the benchmark performance in terms of output bytes.
b.iter(|| segment.build_index(playback).unwrap());
b.iter(|| {
for _i in 0..100 {
segment.build_index(playback).unwrap();
}
});
Ok(())
})
.unwrap();
@ -3067,17 +3083,25 @@ mod bench {
let p = server.generated_len;
b.bytes = p;
let client = reqwest::Client::new();
let rt = tokio::runtime::Runtime::new().unwrap();
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let run = || {
rt.block_on(async {
let resp = client
.get(server.url.clone())
.header(reqwest::header::RANGE, format!("bytes=0-{}", p - 1))
.send()
.await
.unwrap();
let b = resp.bytes().await.unwrap();
assert_eq!(p, b.len() as u64);
for _i in 0..100 {
let mut resp = client
.get(server.url.clone())
.header(reqwest::header::RANGE, format!("bytes=0-{}", p - 1))
.send()
.await
.unwrap();
let mut size = 0u64;
while let Some(b) = resp.chunk().await.unwrap() {
size += u64::try_from(b.len()).unwrap();
}
assert_eq!(p, size);
}
});
};
run(); // warm.
@ -3090,7 +3114,9 @@ mod bench {
let db = TestDb::new(RealClocks {});
testutil::add_dummy_recordings_to_db(&db.db, 60);
b.iter(|| {
create_mp4_from_db(&db, 0, 0, false);
for _i in 0..100 {
create_mp4_from_db(&db, 0, 0, false);
}
});
}
}

View File

@ -17,7 +17,8 @@ use tracing_futures::Instrument;
/// Each `Slice` instance belongs to a single `Slices`.
pub trait Slice: fmt::Debug + Sized + Sync + 'static {
type Ctx: Send + Sync + Clone;
type Chunk: Send + Sync;
type Chunk: Send + Sync + 'static;
type Stream: Stream<Item = Result<Self::Chunk, BoxedError>> + Send + Sync;
/// The byte position (relative to the start of the `Slices`) of the end of this slice,
/// exclusive. Note the starting position (and thus length) are inferred from the previous
@ -27,12 +28,10 @@ pub trait Slice: fmt::Debug + Sized + Sync + 'static {
/// Gets the body bytes indicated by `r`, which is relative to this slice's start.
/// The additional argument `ctx` is as supplied to the `Slices`.
/// The additional argument `l` is the length of this slice, as determined by the `Slices`.
fn get_range(
&self,
ctx: &Self::Ctx,
r: Range<u64>,
len: u64,
) -> Box<dyn Stream<Item = Result<Self::Chunk, BoxedError>> + Sync + Send>;
///
/// Note that unlike [`http_entity::Entity::get_range`], this is called many times per request,
/// so it's worth defining a custom stream type to avoid allocation overhead.
fn get_range(&self, ctx: &Self::Ctx, r: Range<u64>, len: u64) -> Self::Stream;
fn get_slices(ctx: &Self::Ctx) -> &Slices<Self>;
}
@ -127,7 +126,7 @@ where
}
/// Writes `range` to `out`.
/// This interface mirrors `http_serve::Entity::write_to`, with the additional `ctx` argument.
/// This interface mirrors `http_serve::Entity::get_range`, with the additional `ctx` argument.
pub fn get_range(
&self,
ctx: &S::Ctx,
@ -170,7 +169,7 @@ where
let l = s_end - slice_start;
body = s.get_range(&c, start_pos..min_end - slice_start, l);
};
futures::future::ready(Some((Pin::from(body), (c, i + 1, 0, min_end))))
futures::future::ready(Some((body, (c, i + 1, 0, min_end))))
},
);
Box::pin(bodies.flatten().in_current_span())
@ -182,9 +181,8 @@ mod tests {
use super::{Slice, Slices};
use crate::body::BoxedError;
use db::testutil;
use futures::stream::{self, Stream, TryStreamExt};
use futures::stream::{self, TryStreamExt};
use std::ops::Range;
use std::pin::Pin;
#[derive(Debug, Eq, PartialEq)]
pub struct FakeChunk {
@ -201,6 +199,7 @@ mod tests {
impl Slice for FakeSlice {
type Ctx = &'static Slices<FakeSlice>;
type Chunk = FakeChunk;
type Stream = stream::Once<futures::future::Ready<Result<FakeChunk, BoxedError>>>;
fn end(&self) -> u64 {
self.end
@ -211,11 +210,11 @@ mod tests {
_ctx: &&'static Slices<FakeSlice>,
r: Range<u64>,
_l: u64,
) -> Box<dyn Stream<Item = Result<FakeChunk, BoxedError>> + Send + Sync> {
Box::new(stream::once(futures::future::ok(FakeChunk {
) -> Self::Stream {
stream::once(futures::future::ok(FakeChunk {
slice: self.name,
range: r,
})))
}))
}
fn get_slices(ctx: &&'static Slices<FakeSlice>) -> &'static Slices<Self> {
@ -241,10 +240,7 @@ mod tests {
async fn get_range(r: Range<u64>) -> Vec<FakeChunk> {
let slices = slices();
Pin::from(slices.get_range(&slices, r))
.try_collect()
.await
.unwrap()
slices.get_range(&slices, r).try_collect().await.unwrap()
}
#[test]

View File

@ -8,7 +8,6 @@ use futures::StreamExt;
use retina::client::Demuxed;
use retina::codec::CodecItem;
use std::pin::Pin;
use std::result::Result;
use tracing::Instrument;
use url::Url;

View File

@ -6,7 +6,6 @@ use crate::stream;
use base::clock::{Clocks, TimerGuard};
use base::{bail, err, Error};
use db::{dir, recording, writer, Camera, Database, Stream};
use std::result::Result;
use std::str::FromStr;
use std::sync::Arc;
use tracing::{debug, info, trace, warn, Instrument};
@ -289,12 +288,12 @@ where
mod tests {
use crate::stream::{self, Stream};
use base::clock::{self, Clocks};
use base::Mutex;
use base::{bail, Error};
use db::{recording, testutil, CompositeId};
use std::cmp;
use std::convert::TryFrom;
use std::sync::Arc;
use std::sync::Mutex;
use tracing::trace;
struct ProxyingStream {
@ -356,7 +355,7 @@ mod tests {
);
let duration = goal - self.slept;
let buf_part = cmp::min(self.buffered, duration);
self.buffered = self.buffered - buf_part;
self.buffered -= buf_part;
self.clocks.sleep(duration - buf_part);
self.slept = goal;
}
@ -389,7 +388,7 @@ mod tests {
_options: stream::Options,
) -> Result<Box<dyn stream::Stream>, Error> {
assert_eq!(&url, &self.expected_url);
let mut l = self.streams.lock().unwrap();
let mut l = self.streams.lock();
match l.pop() {
Some(stream) => {
trace!("MockOpener returning next stream");
@ -397,7 +396,7 @@ mod tests {
}
None => {
trace!("MockOpener shutting down");
self.shutdown_tx.lock().unwrap().take();
self.shutdown_tx.lock().take();
bail!(Cancelled, msg("done"))
}
}
@ -441,8 +440,8 @@ mod tests {
Box::new(stream),
);
stream.ts_offset = 123456; // starting pts of the input should be irrelevant
stream.ts_offset_pkts_left = u32::max_value();
stream.pkts_left = u32::max_value();
stream.ts_offset_pkts_left = u32::MAX;
stream.pkts_left = u32::MAX;
let (shutdown_tx, shutdown_rx) = base::shutdown::channel();
let opener = MockOpener {
expected_url: url::Url::parse("rtsp://test-camera/main").unwrap(),
@ -479,7 +478,7 @@ mod tests {
.unwrap();
}
stream.run();
assert!(opener.streams.lock().unwrap().is_empty());
assert!(opener.streams.lock().is_empty());
db.syncer_channel.flush();
let db = db.db.lock();
@ -517,7 +516,6 @@ mod tests {
assert_eq!(recording::Time(128700576719993), recordings[1].start);
assert_eq!(db::RecordingFlags::TrailingZero as i32, recordings[1].flags);
drop(env);
drop(opener);
}
}

View File

@ -84,7 +84,8 @@ fn serve_json<R: http_serve::AsRequest, T: serde::ser::Serialize>(
fn csrf_matches(csrf: &str, session: auth::SessionHash) -> bool {
let mut b64 = [0u8; 32];
session.encode_base64(&mut b64);
::ring::constant_time::verify_slices_are_equal(&b64[..], csrf.as_bytes()).is_ok()
use subtle::ConstantTimeEq as _;
b64.ct_eq(csrf.as_bytes()).into()
}
/// Extracts `s` cookie from the HTTP request headers. Does not authenticate.
@ -319,7 +320,7 @@ impl Service {
req: Request<::hyper::body::Incoming>,
conn_data: ConnData,
) -> Result<Response<Body>, std::convert::Infallible> {
let request_id = ulid::Ulid::new();
let request_id = uuid::Uuid::now_v7();
let authreq = auth::Request {
when_sec: Some(self.db.clocks().realtime().as_secs()),
addr: if self.trust_forward_hdrs {
@ -340,7 +341,7 @@ impl Service {
// https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/
let span = tracing::info_span!(
"request",
%request_id,
request_id = %data_encoding::BASE32_NOPAD.encode_display(request_id.as_bytes()),
net.sock.peer.uid = conn_data.client_unix_uid.map(tracing::field::display),
http.client_ip = authreq.addr.map(tracing::field::display),
http.method = %req.method(),
@ -755,7 +756,7 @@ mod tests {
let s = Server::new(None);
let cli = reqwest::Client::new();
let resp = cli
.get(&format!("{}/api/", &s.base_url))
.get(format!("{}/api/", &s.base_url))
.send()
.await
.unwrap();
@ -782,8 +783,11 @@ mod bench {
extern crate test;
use db::testutil::{self, TestDb};
use hyper;
use std::sync::{Arc, OnceLock};
use hyper::{self, service::service_fn};
use std::{
net::SocketAddr,
sync::{Arc, OnceLock},
};
use uuid::Uuid;
struct Server {
@ -807,32 +811,41 @@ mod bench {
})
.unwrap(),
);
let make_svc = hyper::service::make_service_fn(move |_conn| {
futures::future::ok::<_, std::convert::Infallible>(hyper::service::service_fn({
let s = Arc::clone(&service);
move |req| {
Arc::clone(&s).serve(
let addr: SocketAddr = ([127, 0, 0, 1], 0).into();
let listener = std::net::TcpListener::bind(addr).unwrap();
listener.set_nonblocking(true).unwrap();
let addr = listener.local_addr().unwrap(); // resolve port 0 to a real ephemeral port number.
let srv = async move {
let listener = tokio::net::TcpListener::from_std(listener).unwrap();
loop {
let (conn, _remote_addr) = listener.accept().await.unwrap();
conn.set_nodelay(true).unwrap();
let io = hyper_util::rt::TokioIo::new(conn);
let service = Arc::clone(&service);
let svc_fn = service_fn(move |req| {
Arc::clone(&service).serve(
req,
super::accept::ConnData {
client_unix_uid: None,
client_addr: None,
},
)
}
}))
});
let rt = tokio::runtime::Runtime::new().unwrap();
let srv = {
let _guard = rt.enter();
let addr = ([127, 0, 0, 1], 0).into();
hyper::server::Server::bind(&addr)
.tcp_nodelay(true)
.serve(make_svc)
});
tokio::spawn(
hyper::server::conn::http1::Builder::new().serve_connection(io, svc_fn),
);
}
};
let addr = srv.local_addr(); // resolve port 0 to a real ephemeral port number.
::std::thread::spawn(move || {
rt.block_on(srv).unwrap();
});
std::thread::Builder::new()
.name("bench-server".to_owned())
.spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(srv)
})
.unwrap();
Server {
base_url: format!("http://{}:{}", addr.ip(), addr.port()),
test_camera_uuid,
@ -852,13 +865,18 @@ mod bench {
))
.unwrap();
let client = reqwest::Client::new();
let rt = tokio::runtime::Runtime::new().unwrap();
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let f = || {
rt.block_on(async {
let resp = client.get(url.clone()).send().await.unwrap();
assert_eq!(resp.status(), reqwest::StatusCode::OK);
let _b = resp.bytes().await.unwrap();
});
for _i in 0..100 {
rt.block_on(async {
let resp = client.get(url.clone()).send().await.unwrap();
assert_eq!(resp.status(), reqwest::StatusCode::OK);
let _b = resp.bytes().await.unwrap();
});
}
};
f(); // warm.
b.iter(f);

View File

@ -175,7 +175,7 @@ mod tests {
info!("header: {}", cookie.header());
let resp = cli
.get(&format!("{}/api/", &s.base_url))
.get(format!("{}/api/", &s.base_url))
.header(reqwest::header::COOKIE, cookie.header())
.send()
.await
@ -192,7 +192,7 @@ mod tests {
p.insert("username", "slamb");
p.insert("password", "hunter2");
let resp = cli
.post(&format!("{}/api/login", &s.base_url))
.post(format!("{}/api/login", &s.base_url))
.json(&p)
.send()
.await
@ -202,7 +202,7 @@ mod tests {
// A GET shouldn't work.
let resp = cli
.get(&format!("{}/api/logout", &s.base_url))
.get(format!("{}/api/logout", &s.base_url))
.header(reqwest::header::COOKIE, cookie.header())
.send()
.await
@ -211,7 +211,7 @@ mod tests {
// Neither should a POST without a csrf token.
let resp = cli
.post(&format!("{}/api/logout", &s.base_url))
.post(format!("{}/api/logout", &s.base_url))
.header(reqwest::header::COOKIE, cookie.header())
.send()
.await
@ -221,7 +221,7 @@ mod tests {
// But it should work with the csrf token.
// Retrieve that from the toplevel API request.
let toplevel: serde_json::Value = cli
.post(&format!("{}/api/", &s.base_url))
.post(format!("{}/api/", &s.base_url))
.header(reqwest::header::COOKIE, cookie.header())
.send()
.await
@ -240,7 +240,7 @@ mod tests {
let mut p = FastHashMap::default();
p.insert("csrf", csrf);
let resp = cli
.post(&format!("{}/api/logout", &s.base_url))
.post(format!("{}/api/logout", &s.base_url))
.header(reqwest::header::COOKIE, cookie.header())
.json(&p)
.send()
@ -255,7 +255,7 @@ mod tests {
// It should also be invalidated server-side.
let resp = cli
.get(&format!("{}/api/", &s.base_url))
.get(format!("{}/api/", &s.base_url))
.header(reqwest::header::COOKIE, cookie.header())
.send()
.await

View File

@ -291,7 +291,7 @@ mod tests {
let s = Server::new(Some(permissions));
let cli = reqwest::Client::new();
let resp = cli
.get(&format!(
.get(format!(
"{}/api/cameras/{}/main/view.mp4",
&s.base_url, s.db.test_camera_uuid
))

View File

@ -60,7 +60,7 @@ where
.await;
if let Err(err) = handler(&mut ws).await {
// TODO: use a nice JSON message format for errors.
tracing::error!(%err, "closing with error");
tracing::error!(err = %err.chain(), "closing with error");
let _ = ws
.send(tungstenite::Message::Text(err.to_string().into()))
.await;

5
ui/.gitignore vendored
View File

@ -9,10 +9,7 @@
# testing
/coverage
# production, current path
/build
# production, old path
# production
/dist
# misc

34
ui/.prettierignore Normal file
View File

@ -0,0 +1,34 @@
#-------------------------------------------------------------------------------------------------------------------
# Keep this section in sync with .gitignore
#-------------------------------------------------------------------------------------------------------------------
# dependencies
/node_modules
/.pnp
.pnp.js
/.idea
# testing
/coverage
# production
/dist
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.eslintcache
npm-debug.log*
yarn-debug.log*
yarn-error.log*
#-------------------------------------------------------------------------------------------------------------------
# Prettier-specific overrides
#-------------------------------------------------------------------------------------------------------------------
pnpm-lock.yaml

View File

@ -21,10 +21,10 @@
"react-router-dom": "^6.22.3"
},
"scripts": {
"check-format": "prettier --check --ignore-path .gitignore .",
"check-format": "prettier --check --ignore-path .prettierignore .",
"dev": "vite",
"build": "tsc && vite build",
"format": "prettier --write --ignore-path .gitignore .",
"format": "prettier --write --ignore-path .prettierignore .",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test": "vitest"

12729
ui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -78,7 +78,9 @@ export function combine(
(split90k === undefined || cur.endTime90k - r.startTime90k <= split90k)
) {
cur.startId = r.startId;
cur.firstUncommitted == r.firstUncommitted ?? cur.firstUncommitted;
if (r.firstUncommitted !== undefined) {
cur.firstUncommitted = r.firstUncommitted;
}
cur.startTime90k = r.startTime90k;
cur.videoSamples += r.videoSamples;
cur.sampleFileBytes += r.sampleFileBytes;