Compare commits
7 Commits
d35aa2aecb
...
rewrite
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dcfa7a803 | |||
| 78b4338f95 | |||
| 82be15b960 | |||
| 5f79b3c2b0 | |||
| f28091257e | |||
| 8f76dd5b39 | |||
| 8dba0c097d |
Generated
+41
-5
@@ -1700,7 +1700,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "musicfs"
|
name = "musicfs-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -1712,24 +1712,60 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
"musicfs-core",
|
||||||
|
"musicfs-proto",
|
||||||
"notify",
|
"notify",
|
||||||
"prost",
|
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"symphonia",
|
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"time",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tonic",
|
"tonic",
|
||||||
"tonic-health",
|
"tonic-health",
|
||||||
"tonic-prost",
|
|
||||||
"tonic-reflection",
|
"tonic-reflection",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"twox-hash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "musicfs-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"symphonia",
|
||||||
|
"tempfile",
|
||||||
|
"tracing",
|
||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"twox-hash",
|
"twox-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "musicfs-proto"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"prost",
|
||||||
|
"tonic",
|
||||||
|
"tonic-prost",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "musicfs-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"clap",
|
||||||
|
"musicfs-core",
|
||||||
|
"musicfs-proto",
|
||||||
|
"notify",
|
||||||
|
"tempfile",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tonic",
|
||||||
|
"tonic-health",
|
||||||
|
"tonic-reflection",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
|
|||||||
+35
-41
@@ -1,57 +1,51 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "musicfs"
|
members = [
|
||||||
|
"crates/musicfs-proto",
|
||||||
|
"crates/musicfs-core",
|
||||||
|
"crates/musicfs-server",
|
||||||
|
"crates/musicfs-client",
|
||||||
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
# `cargo run` (and devenv's `cargo run -- ...`) picks the FUSE binary, not
|
|
||||||
# musicfs-server, when two [[bin]] targets exist.
|
|
||||||
default-run = "musicfs"
|
|
||||||
|
|
||||||
[lib]
|
[workspace.dependencies]
|
||||||
name = "musicfs"
|
musicfs-proto = { path = "crates/musicfs-proto" }
|
||||||
path = "src/lib.rs"
|
musicfs-core = { path = "crates/musicfs-core" }
|
||||||
|
|
||||||
[[bin]]
|
prost = "0.14"
|
||||||
name = "musicfs"
|
tonic = "0.14"
|
||||||
path = "src/main.rs"
|
tonic-prost = "0.14"
|
||||||
|
tonic-health = "0.14"
|
||||||
|
tonic-reflection = "0.14"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "musicfs-server"
|
|
||||||
path = "src/bin/musicfs-server.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
clap = { version = "4.6.1", features = ["derive"] }
|
|
||||||
fuser = "0.17.0"
|
|
||||||
libc = "0.2.186"
|
|
||||||
notify = "8.2.0"
|
|
||||||
time = "0.3.49"
|
|
||||||
symphonia = { version = "0.5", default-features = false, features = [
|
symphonia = { version = "0.5", default-features = false, features = [
|
||||||
"aac", "alac", "flac", "mp3", "ogg", "vorbis", "wav"
|
"aac", "alac", "flac", "mp3", "ogg", "vorbis", "wav",
|
||||||
] }
|
] }
|
||||||
twox-hash = "2.1.2"
|
twox-hash = "2.1.2"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||||
|
tracing-appender = "0.2.5"
|
||||||
|
|
||||||
sea-orm = { version = "2.0.0-rc", features = [ "sqlx-postgres", "runtime-tokio", "macros" ] }
|
tokio = { version = "1", features = [
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal", "fs", "io-util", "sync"] }
|
"macros", "rt-multi-thread", "signal", "fs", "io-util", "sync",
|
||||||
|
] }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
bytes = "1"
|
clap = { version = "4.6.1", features = ["derive"] }
|
||||||
tonic = "0.14"
|
notify = "8.2.0"
|
||||||
tonic-health = "0.14"
|
|
||||||
tonic-prost = "0.14"
|
|
||||||
tonic-reflection = "0.14"
|
|
||||||
prost = "0.14"
|
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
|
||||||
|
fuser = "0.17.0"
|
||||||
|
libc = "0.2.186"
|
||||||
|
sea-orm = { version = "2.0.0-rc", features = [
|
||||||
|
"sqlx-postgres", "runtime-tokio", "macros",
|
||||||
|
] }
|
||||||
|
log = "0.4"
|
||||||
|
bytes = "1"
|
||||||
http = "1"
|
http = "1"
|
||||||
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||||
|
|
||||||
tracing = "0.1"
|
|
||||||
# For sea_orm's ConnectOptions::sqlx_logging_level, which takes a log::LevelFilter.
|
|
||||||
log = "0.4"
|
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
|
||||||
# Pinned to 0.2.5: Builder::max_log_files (used for retention) landed in the
|
|
||||||
# 0.2.3 Builder API and is current here. An unqualified "0.2" could otherwise
|
|
||||||
# resolve to an older patch without the retention method.
|
|
||||||
tracing-appender = "0.2.5"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|||||||
+116
-19
@@ -1,13 +1,97 @@
|
|||||||
#+title: musicfs
|
#+title: musicfs
|
||||||
|
|
||||||
* Incus server VM
|
A read-only FUSE filesystem that presents a music library reorganised by
|
||||||
|
*metadata* rather than by however the files happen to sit on disk. Point it at a
|
||||||
|
messy ~~/Music~ tree (or a remote ~musicfs-server~) and it mounts a clean
|
||||||
|
=Artist/Album/Track= view, driven entirely by the tags parsed out of the audio
|
||||||
|
files themselves.
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
mountpoint/
|
||||||
|
├── ДДТ/
|
||||||
|
│ └── Творчество в пустоте/
|
||||||
|
│ ├── 01 Intro.flac
|
||||||
|
│ └── 02 ...
|
||||||
|
└── Some Artist/
|
||||||
|
└── Some Album/
|
||||||
|
└── 01 Track.flac
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
The source files are never moved or modified. The directory hierarchy is
|
||||||
|
*virtual*: synthesised from ~album_artist~/~album~ tags, with stable synthetic
|
||||||
|
inodes so the layout survives remounts.
|
||||||
|
|
||||||
|
* How it works
|
||||||
|
|
||||||
|
Two *origins* feed the same FUSE frontend:
|
||||||
|
|
||||||
|
- *LocalOrigin* — walks a directory on the host, parses tags, builds the
|
||||||
|
snapshot. A =notify= watcher keeps it live as files change.
|
||||||
|
- *NetworkOrigin* — talks gRPC to a remote ~musicfs-server~. Metadata and file
|
||||||
|
bytes arrive over the wire; a Postgres table caches both. Reconciliation is
|
||||||
|
hash-based: the client sends the ~(inode, hash)~ pairs it has, the server
|
||||||
|
replies with only what changed or was deleted, so almost nothing crosses the
|
||||||
|
network on a steady-state refresh.
|
||||||
|
|
||||||
|
Which one is used is chosen automatically from the ~--source~ argument: an
|
||||||
|
=http(s)://= URL means network, anything else is treated as a local path.
|
||||||
|
|
||||||
|
State lives in Postgres — parsed metadata, the virtual-path layout, and (for the
|
||||||
|
network origin) a lazy byte cache populated only for files that were actually
|
||||||
|
read.
|
||||||
|
|
||||||
|
* Layout
|
||||||
|
|
||||||
|
| Crate | Responsibility |
|
||||||
|
|------------------+-----------------------------------------------------------------------|
|
||||||
|
| =musicfs-proto= | Protobuf/gRPC definitions (=proto/musicfs.proto=), generated bindings |
|
||||||
|
| =musicfs-core= | Shared logic: tag parsing (FLAC/MP3 via symphonia), hashing, logging |
|
||||||
|
| =musicfs-client= | FUSE mount, origins, Postgres cache/sync — the =musicfs= binary |
|
||||||
|
| =musicfs-server= | gRPC server sharing a library — the =musicfs-server= binary |
|
||||||
|
|
||||||
|
The gRPC surface (see =proto/musicfs.proto=): ~Reconcile~, ~GetMetadata~,
|
||||||
|
~GetManifest~, ~GetFile~ (whole-file or byte-range streaming), and
|
||||||
|
~SubscribeEvents~ (change wake-ups). The client also serves a ~ClientStatus~ RPC
|
||||||
|
for introspection.
|
||||||
|
|
||||||
|
* Running the client
|
||||||
|
|
||||||
|
Everything runs inside the =devenv= shell, which provides the Rust toolchain,
|
||||||
|
Postgres, FUSE tooling, and gRPC utilities.
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
devenv shell
|
||||||
|
devenv up --profile local # starts Postgres + the FUSE mount
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Predefined profiles in =devenv.nix=:
|
||||||
|
|
||||||
|
| Profile | Source | Purpose |
|
||||||
|
|-----------+---------------------------------+-------------------------------|
|
||||||
|
| =local= | a local directory | mount a host music folder |
|
||||||
|
| =remote= | =http://…:50051= | mount a remote musicfs-server |
|
||||||
|
| =e2e= | =http://127.0.0.1:50061= | end-to-end test harness |
|
||||||
|
|
||||||
|
Or run the binary directly:
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
cargo run -p musicfs-client -- \
|
||||||
|
--source /home/you/Music \
|
||||||
|
--mountpoint /tmp/musicfs \
|
||||||
|
--database "postgresql://you@localhost/musicfs?host=$PGHOST"
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Key flags: =--source= (local path or ~http(s)://~ server URL), =--mountpoint=,
|
||||||
|
=--database= (Postgres URL), =--log-dir= (daily-rotated logs, default =./logs=),
|
||||||
|
=--listen= (address for the client's status/health RPCs, default
|
||||||
|
=127.0.0.1:50052=).
|
||||||
|
|
||||||
|
* Running the server in an Incus VM
|
||||||
|
|
||||||
~musicfs-server~ (gRPC) runs inside an Incus VM, live-sharing the host's
|
~musicfs-server~ (gRPC) runs inside an Incus VM, live-sharing the host's
|
||||||
~~/Music~ as a readonly virtiofs mount. =scripts/vm.sh= builds the binary on the
|
~~/Music~ as a read-only virtiofs mount. =scripts/vm.sh= builds the binary on
|
||||||
host, bundles its nix ~glibc~ so it runs unmodified in the VM, ships it in, and
|
the host, bundles its nix ~glibc~ so it runs unmodified in the VM, ships it in,
|
||||||
runs it under systemd.
|
and runs it under systemd.
|
||||||
|
|
||||||
** Setup
|
|
||||||
|
|
||||||
#+begin_src bash
|
#+begin_src bash
|
||||||
devenv shell # cargo, incus, patchelf, grpcurl
|
devenv shell # cargo, incus, patchelf, grpcurl
|
||||||
@@ -16,17 +100,19 @@ scripts/vm.sh up # create VM, share ~/Music, build, ship, start server
|
|||||||
|
|
||||||
Options (env): =VM_NAME=, =IMAGE=, =MUSIC_SOURCE=, =LISTEN_PORT=, =RUST_LOG=.
|
Options (env): =VM_NAME=, =IMAGE=, =MUSIC_SOURCE=, =LISTEN_PORT=, =RUST_LOG=.
|
||||||
|
|
||||||
| Command | Action |
|
| Command | Action |
|
||||||
|--------------------------+-------------------------------|
|
|------------------------+-------------------------------|
|
||||||
| =scripts/vm.sh up= | create + build + ship + start |
|
| =scripts/vm.sh up= | create + build + ship + start |
|
||||||
| =scripts/vm.sh redeploy= | rebuild after a code change |
|
| =scripts/vm.sh redeploy= | rebuild after a code change |
|
||||||
| =scripts/vm.sh logs= | tail server logs |
|
| =scripts/vm.sh logs= | tail server logs |
|
||||||
| =scripts/vm.sh status= | VM + service status |
|
| =scripts/vm.sh status= | VM + service status |
|
||||||
| =scripts/vm.sh shell= | shell inside the VM |
|
| =scripts/vm.sh shell= | shell inside the VM |
|
||||||
| =scripts/vm.sh down= | stop the VM |
|
| =scripts/vm.sh down= | stop the VM |
|
||||||
| =scripts/vm.sh destroy= | delete the VM |
|
| =scripts/vm.sh destroy= | delete the VM |
|
||||||
|
|
||||||
** Test it (Nushell)
|
Reach the server at the VM's bridge IP (=scripts/vm.sh status= prints it).
|
||||||
|
|
||||||
|
** Poke at it (Nushell)
|
||||||
|
|
||||||
#+begin_src nushell
|
#+begin_src nushell
|
||||||
let VMIP = (incus list musicfs -f csv -c 4 | lines | first | split row " " | first)
|
let VMIP = (incus list musicfs -f csv -c 4 | lines | first | split row " " | first)
|
||||||
@@ -46,9 +132,20 @@ incus exec musicfs -- find /music -type f | lines | length
|
|||||||
incus exec musicfs -- cat "/music/DDT/ДДТ - Творчество в пустоте – 2 - 01 Intro.flac" | ^mpv -
|
incus exec musicfs -- cat "/music/DDT/ДДТ - Творчество в пустоте – 2 - 01 Intro.flac" | ^mpv -
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Reach the server at the VM's bridge IP (=scripts/vm.sh status= prints it).
|
|
||||||
Install mpv with =nix profile install nixpkgs#mpv=, or use =^ffplay -= instead.
|
Install mpv with =nix profile install nixpkgs#mpv=, or use =^ffplay -= instead.
|
||||||
|
|
||||||
In Nushell, always put =| lines= between an external command's stdout and a
|
In Nushell, always put =| lines= between an external command's stdout and a
|
||||||
builtin (=find=, =first=, =length=, ...); external-to-external pipes (=cat |
|
builtin (=find=, =first=, =length=, …); external-to-external pipes (=cat | mpv=)
|
||||||
mpv=) are byte-stable and don't need it.
|
are byte-stable and don't need it.
|
||||||
|
|
||||||
|
* Development
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
just build # cargo build
|
||||||
|
cargo test # unit tests
|
||||||
|
just e2e # end-to-end suite (scripts/e2e/run.sh)
|
||||||
|
just e2e-resilience # resilience suite
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Formatting and lint (clippy, treefmt with rustfmt + nixfmt) run as git hooks via
|
||||||
|
=devenv=. Requires Rust edition 2024.
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
[package]
|
||||||
|
name = "musicfs-client"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
default-run = "musicfs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "musicfs"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "musicfs"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
musicfs-core.workspace = true
|
||||||
|
musicfs-proto.workspace = true
|
||||||
|
fuser.workspace = true
|
||||||
|
libc.workspace = true
|
||||||
|
sea-orm.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
async-trait.workspace = true
|
||||||
|
clap.workspace = true
|
||||||
|
notify.workspace = true
|
||||||
|
tokio-stream.workspace = true
|
||||||
|
tonic.workspace = true
|
||||||
|
tonic-health.workspace = true
|
||||||
|
tonic-reflection.workspace = true
|
||||||
|
bytes.workspace = true
|
||||||
|
http.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
twox-hash.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile.workspace = true
|
||||||
|
sea-orm = { workspace = true, features = ["mock"] }
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use fuser::INodeNo;
|
||||||
|
use musicfs_core::music::encoder::MusicMetadataEncoderFactory;
|
||||||
|
use musicfs_proto::{
|
||||||
|
ClientControl, FileEntry, FileMetadata, GetMusicMetadataRequest, GetMusicMetadataResponse,
|
||||||
|
ListFilesRequest, ListFilesResponse, MusicMetadata as ProtoMusicMetadata, PictureDataRange,
|
||||||
|
UpdateMusicMetadataRequest, UpdateMusicMetadataResponse,
|
||||||
|
};
|
||||||
|
use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait};
|
||||||
|
use tonic::{Request, Response, Status};
|
||||||
|
|
||||||
|
use crate::db::entities;
|
||||||
|
use crate::item::{FileType, Item};
|
||||||
|
use crate::music::db::save_music_metadata;
|
||||||
|
use crate::music::metadata::MusicMetadata;
|
||||||
|
use crate::virtual_dirs::{
|
||||||
|
compute_new_layout, ensure_virtual_dirs, find_orphaned_dirs, parent_inode_from_path,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ClientControlServiceImpl {
|
||||||
|
files: Arc<Mutex<BTreeMap<INodeNo, Item>>>,
|
||||||
|
db: DatabaseConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientControlServiceImpl {
|
||||||
|
pub fn new(files: Arc<Mutex<BTreeMap<INodeNo, Item>>>, db: DatabaseConnection) -> Self {
|
||||||
|
ClientControlServiceImpl { files, db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl ClientControl for ClientControlServiceImpl {
|
||||||
|
async fn get_music_metadata(
|
||||||
|
&self,
|
||||||
|
request: Request<GetMusicMetadataRequest>,
|
||||||
|
) -> Result<Response<GetMusicMetadataResponse>, Status> {
|
||||||
|
let inode = INodeNo(request.into_inner().inode);
|
||||||
|
let metadata = {
|
||||||
|
let files = self.files.lock().unwrap();
|
||||||
|
let item = files
|
||||||
|
.get(&inode)
|
||||||
|
.ok_or_else(|| Status::not_found(format!("inode {} not found", inode.0)))?;
|
||||||
|
item.music_metadata.clone().ok_or_else(|| {
|
||||||
|
Status::not_found(format!("inode {} has no music metadata", inode.0))
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
Ok(Response::new(GetMusicMetadataResponse {
|
||||||
|
metadata: Some(music_metadata_to_proto(metadata)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_music_metadata(
|
||||||
|
&self,
|
||||||
|
request: Request<UpdateMusicMetadataRequest>,
|
||||||
|
) -> Result<Response<UpdateMusicMetadataResponse>, Status> {
|
||||||
|
let req = request.into_inner();
|
||||||
|
let inode = INodeNo(req.inode);
|
||||||
|
|
||||||
|
let outcome = {
|
||||||
|
let mut files = self.files.lock().unwrap();
|
||||||
|
|
||||||
|
let (old_local_path, current_name, source_for_virtual_dirs) = {
|
||||||
|
let root = files.get(&INodeNo::ROOT);
|
||||||
|
let item = files
|
||||||
|
.get(&inode)
|
||||||
|
.ok_or_else(|| Status::not_found(format!("inode {} not found", inode.0)))?;
|
||||||
|
let source = root
|
||||||
|
.map(|r| r.original_path.clone())
|
||||||
|
.unwrap_or_else(|| PathBuf::from("/"));
|
||||||
|
(item.local_path.clone(), item.name.clone(), source)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Phase 1: mutate the music metadata and re-encode the header.
|
||||||
|
// Done in its own scope so the mutable borrow on `files` is
|
||||||
|
// released before we touch `files` again for layout changes.
|
||||||
|
let (updated_mm, _encoder_extension_source) = {
|
||||||
|
let item = files
|
||||||
|
.get_mut(&inode)
|
||||||
|
.ok_or_else(|| Status::not_found(format!("inode {} not found", inode.0)))?;
|
||||||
|
let mm = item.music_metadata.as_mut().ok_or_else(|| {
|
||||||
|
Status::not_found(format!("inode {} has no music metadata", inode.0))
|
||||||
|
})?;
|
||||||
|
apply_update(mm, req);
|
||||||
|
let encoder =
|
||||||
|
MusicMetadataEncoderFactory::for_path(&item.local_path).ok_or_else(|| {
|
||||||
|
Status::failed_precondition(format!(
|
||||||
|
"no encoder for file type: {}",
|
||||||
|
item.local_path.display()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
encoder.encode(mm);
|
||||||
|
(mm.clone(), item.local_path.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Phase 2: compute new layout, write it onto the item, then
|
||||||
|
// ensure new virtual dirs and prune orphans. A fresh mutable
|
||||||
|
// borrow is fine because phase 1's borrow has been released.
|
||||||
|
let (new_name, new_local_path) = compute_new_layout(¤t_name, &updated_mm);
|
||||||
|
{
|
||||||
|
let item = files
|
||||||
|
.get_mut(&inode)
|
||||||
|
.ok_or_else(|| Status::not_found(format!("inode {} not found", inode.0)))?;
|
||||||
|
item.name = new_name.clone();
|
||||||
|
item.local_path = new_local_path.clone();
|
||||||
|
item.parent_inode = parent_inode_from_path(&new_local_path);
|
||||||
|
}
|
||||||
|
ensure_virtual_dirs(&new_local_path, &source_for_virtual_dirs, &mut files);
|
||||||
|
|
||||||
|
let orphaned = find_orphaned_dirs(&files, inode, &old_local_path);
|
||||||
|
for dir_inode in &orphaned {
|
||||||
|
files.remove(dir_inode);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateOutcome {
|
||||||
|
metadata: updated_mm,
|
||||||
|
new_name,
|
||||||
|
new_local_path,
|
||||||
|
orphaned_dirs: orphaned,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
save_music_metadata(inode.0 as i64, &outcome.metadata, &self.db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Status::internal(format!("DB save failed: {e}")))?;
|
||||||
|
|
||||||
|
persist_layout_changes(
|
||||||
|
&self.db,
|
||||||
|
inode.0 as i64,
|
||||||
|
&outcome.new_name,
|
||||||
|
&outcome.new_local_path,
|
||||||
|
&outcome.orphaned_dirs,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Status::internal(format!("DB layout save failed: {e}")))?;
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
ino = inode.0,
|
||||||
|
orphaned_dirs = outcome.orphaned_dirs.len(),
|
||||||
|
"control: music metadata updated, layout recomputed"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Response::new(UpdateMusicMetadataResponse {
|
||||||
|
metadata: Some(music_metadata_to_proto(outcome.metadata)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_files(
|
||||||
|
&self,
|
||||||
|
_request: Request<ListFilesRequest>,
|
||||||
|
) -> Result<Response<ListFilesResponse>, Status> {
|
||||||
|
let entries: Vec<FileEntry> = {
|
||||||
|
let files = self.files.lock().unwrap();
|
||||||
|
files
|
||||||
|
.values()
|
||||||
|
.filter(|item| item.file_type == FileType::File)
|
||||||
|
.map(|item| FileEntry {
|
||||||
|
inode: item.inode.0,
|
||||||
|
original_path: item.original_path.to_string_lossy().into_owned(),
|
||||||
|
local_path: item.local_path.to_string_lossy().into_owned(),
|
||||||
|
name: item.name.clone(),
|
||||||
|
metadata: item
|
||||||
|
.music_metadata
|
||||||
|
.as_ref()
|
||||||
|
.map(|mm| music_metadata_to_file_metadata(mm)),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
Ok(Response::new(ListFilesResponse { files: entries }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn music_metadata_to_file_metadata(mm: &MusicMetadata) -> FileMetadata {
|
||||||
|
FileMetadata {
|
||||||
|
artist: mm.artist.clone(),
|
||||||
|
album_artist: mm.album_artist.clone(),
|
||||||
|
album: mm.album.clone(),
|
||||||
|
track_number: mm.track_number,
|
||||||
|
track_title: mm.track_title.clone(),
|
||||||
|
other_tags: mm.other_tags.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn music_metadata_to_proto(mm: crate::music::metadata::MusicMetadata) -> ProtoMusicMetadata {
|
||||||
|
ProtoMusicMetadata {
|
||||||
|
artist: mm.artist,
|
||||||
|
album_artist: mm.album_artist,
|
||||||
|
album: mm.album,
|
||||||
|
track_number: mm.track_number,
|
||||||
|
track_title: mm.track_title,
|
||||||
|
other_tags: mm.other_tags,
|
||||||
|
header: mm.header,
|
||||||
|
picture_block_headers: mm.picture_block_headers,
|
||||||
|
picture_data_ranges: mm
|
||||||
|
.picture_data_ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(|(offset, length)| PictureDataRange { offset, length })
|
||||||
|
.collect(),
|
||||||
|
real_audio_start: mm.real_audio_start,
|
||||||
|
vorbis_comment_offset: mm.vorbis_comment_offset,
|
||||||
|
vorbis_comment_length: mm.vorbis_comment_length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UpdateOutcome {
|
||||||
|
metadata: MusicMetadata,
|
||||||
|
new_name: String,
|
||||||
|
new_local_path: PathBuf,
|
||||||
|
orphaned_dirs: Vec<INodeNo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_update(mm: &mut MusicMetadata, req: UpdateMusicMetadataRequest) {
|
||||||
|
mm.artist = req.artist;
|
||||||
|
mm.album_artist = req.album_artist;
|
||||||
|
mm.album = req.album;
|
||||||
|
mm.track_number = req.track_number;
|
||||||
|
mm.track_title = req.track_title;
|
||||||
|
mm.other_tags = req.other_tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn persist_layout_changes(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
inode: i64,
|
||||||
|
new_name: &str,
|
||||||
|
new_local_path: &std::path::Path,
|
||||||
|
orphaned_dirs: &[INodeNo],
|
||||||
|
) -> Result<(), sea_orm::DbErr> {
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
let new_local_path_str = new_local_path.to_string_lossy().into_owned();
|
||||||
|
let update = entities::ActiveModel {
|
||||||
|
inode: Set(inode),
|
||||||
|
name: Set(new_name.to_string()),
|
||||||
|
local_path: Set(new_local_path_str),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
update.update(db).await?;
|
||||||
|
|
||||||
|
for dir_inode in orphaned_dirs {
|
||||||
|
let _ = entities::Entity::delete_by_id(dir_inode.0 as i64)
|
||||||
|
.exec(db)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::{
|
||||||
|
Arc, Mutex,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
|
use fuser::INodeNo;
|
||||||
|
use musicfs_proto::{
|
||||||
|
ClientControlServer, ClientStatus, ClientStatusResponse, ClientStatusServer,
|
||||||
|
GetClientStatusRequest,
|
||||||
|
};
|
||||||
|
use tokio::time::Duration;
|
||||||
|
use tonic::{Request, Response, transport::Server};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ServerStatus {
|
||||||
|
connected: Arc<AtomicBool>,
|
||||||
|
last_reconcile: Arc<Mutex<Option<i64>>>,
|
||||||
|
origin_type: String,
|
||||||
|
mountpoint: String,
|
||||||
|
server_endpoint: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerStatus {
|
||||||
|
pub fn new_network(mountpoint: String, server_endpoint: String) -> Self {
|
||||||
|
ServerStatus {
|
||||||
|
connected: Arc::new(AtomicBool::new(false)),
|
||||||
|
last_reconcile: Arc::new(Mutex::new(None)),
|
||||||
|
origin_type: "network".to_string(),
|
||||||
|
mountpoint,
|
||||||
|
server_endpoint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_local(mountpoint: String) -> Self {
|
||||||
|
ServerStatus {
|
||||||
|
connected: Arc::new(AtomicBool::new(true)),
|
||||||
|
last_reconcile: Arc::new(Mutex::new(None)),
|
||||||
|
origin_type: "local".to_string(),
|
||||||
|
mountpoint,
|
||||||
|
server_endpoint: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_connected(&self, connected: bool) {
|
||||||
|
self.connected.store(connected, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_reconciled(&self) {
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs() as i64)
|
||||||
|
.unwrap_or(0);
|
||||||
|
*self.last_reconcile.lock().unwrap() = Some(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_server_connected(&self) -> bool {
|
||||||
|
self.connected.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn spawn_grpc_server(
|
||||||
|
addr: SocketAddr,
|
||||||
|
status: ServerStatus,
|
||||||
|
files: Arc<Mutex<BTreeMap<INodeNo, crate::item::Item>>>,
|
||||||
|
db: sea_orm::DatabaseConnection,
|
||||||
|
) {
|
||||||
|
let (reporter, health_service) = tonic_health::server::health_reporter();
|
||||||
|
reporter
|
||||||
|
.set_serving::<ClientStatusServer<ClientStatusServiceImpl>>()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let reporter_clone = reporter.clone();
|
||||||
|
let status_clone = status.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut was_connected = status_clone.is_server_connected();
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
let now_connected = status_clone.is_server_connected();
|
||||||
|
if now_connected != was_connected {
|
||||||
|
if now_connected {
|
||||||
|
reporter_clone
|
||||||
|
.set_serving::<ClientStatusServer<ClientStatusServiceImpl>>()
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
reporter_clone
|
||||||
|
.set_not_serving::<ClientStatusServer<ClientStatusServiceImpl>>()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
was_connected = now_connected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let reflection_v1 = tonic_reflection::server::Builder::configure()
|
||||||
|
.register_encoded_file_descriptor_set(musicfs_proto::musicfs::FILE_DESCRIPTOR_SET)
|
||||||
|
.register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
|
||||||
|
.build_v1()
|
||||||
|
.expect("build reflection v1");
|
||||||
|
|
||||||
|
let reflection_v1alpha = tonic_reflection::server::Builder::configure()
|
||||||
|
.register_encoded_file_descriptor_set(musicfs_proto::musicfs::FILE_DESCRIPTOR_SET)
|
||||||
|
.register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
|
||||||
|
.build_v1alpha()
|
||||||
|
.expect("build reflection v1alpha");
|
||||||
|
|
||||||
|
let status_svc = ClientStatusServer::new(ClientStatusServiceImpl {
|
||||||
|
status,
|
||||||
|
files: files.clone(),
|
||||||
|
});
|
||||||
|
let control_svc =
|
||||||
|
ClientControlServer::new(crate::control::ClientControlServiceImpl::new(files, db));
|
||||||
|
|
||||||
|
tracing::info!(%addr, "health server listening");
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = Server::builder()
|
||||||
|
.add_service(health_service)
|
||||||
|
.add_service(status_svc)
|
||||||
|
.add_service(reflection_v1)
|
||||||
|
.add_service(reflection_v1alpha)
|
||||||
|
.add_service(control_svc)
|
||||||
|
.serve(addr)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!(error = %e, "health server ended with error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ClientStatusServiceImpl {
|
||||||
|
status: ServerStatus,
|
||||||
|
files: Arc<Mutex<BTreeMap<INodeNo, crate::item::Item>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl ClientStatus for ClientStatusServiceImpl {
|
||||||
|
async fn get_client_status(
|
||||||
|
&self,
|
||||||
|
_request: Request<GetClientStatusRequest>,
|
||||||
|
) -> Result<Response<ClientStatusResponse>, tonic::Status> {
|
||||||
|
let file_count = self
|
||||||
|
.files
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.values()
|
||||||
|
.filter(|i| i.file_type == crate::item::FileType::File)
|
||||||
|
.count() as u64;
|
||||||
|
|
||||||
|
let last_reconcile_unix = self.status.last_reconcile.lock().unwrap().unwrap_or(0);
|
||||||
|
|
||||||
|
Ok(Response::new(ClientStatusResponse {
|
||||||
|
origin_type: self.status.origin_type.clone(),
|
||||||
|
mountpoint: self.status.mountpoint.clone(),
|
||||||
|
file_count,
|
||||||
|
server_connected: self.status.is_server_connected(),
|
||||||
|
server_endpoint: self.status.server_endpoint.clone(),
|
||||||
|
last_reconcile_unix,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::{
|
use std::{
|
||||||
hash::Hasher,
|
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
@@ -7,7 +6,6 @@ use std::{
|
|||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
use fuser::INodeNo;
|
use fuser::INodeNo;
|
||||||
use twox_hash::XxHash64;
|
|
||||||
|
|
||||||
use crate::music::metadata::MusicMetadata;
|
use crate::music::metadata::MusicMetadata;
|
||||||
use crate::origins::attrs::FileAttrs;
|
use crate::origins::attrs::FileAttrs;
|
||||||
@@ -38,27 +36,6 @@ fn secs_since_epoch(time: SystemTime) -> u64 {
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hash inputs shared by every Item regardless of origin. Server manifests
|
|
||||||
/// and client Items both feed these same five values through xxhash, so a
|
|
||||||
/// file's hash is identical on both sides — that's what makes `Reconcile`'s
|
|
||||||
/// diff correct.
|
|
||||||
pub fn compute_item_hash(
|
|
||||||
inode: u64,
|
|
||||||
original_path: &[u8],
|
|
||||||
ctime_secs: u64,
|
|
||||||
mtime_secs: u64,
|
|
||||||
crtime_secs: u64,
|
|
||||||
) -> u64 {
|
|
||||||
let seed = 1234;
|
|
||||||
let mut hasher = XxHash64::with_seed(seed);
|
|
||||||
hasher.write_u64(inode);
|
|
||||||
hasher.write(original_path);
|
|
||||||
hasher.write_u64(ctime_secs);
|
|
||||||
hasher.write_u64(mtime_secs);
|
|
||||||
hasher.write_u64(crtime_secs);
|
|
||||||
return hasher.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -88,13 +65,13 @@ impl Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_hash(&self) -> u64 {
|
pub fn compute_hash(&self) -> u64 {
|
||||||
return compute_item_hash(
|
musicfs_core::compute_item_hash(
|
||||||
self.inode.0,
|
self.inode.0,
|
||||||
self.original_path.as_os_str().as_bytes(),
|
self.original_path.as_os_str().as_bytes(),
|
||||||
secs_since_epoch(self.attrs.ctime),
|
secs_since_epoch(self.attrs.ctime),
|
||||||
secs_since_epoch(self.attrs.mtime),
|
secs_since_epoch(self.attrs.mtime),
|
||||||
secs_since_epoch(self.attrs.crtime),
|
secs_since_epoch(self.attrs.crtime),
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
pub mod control;
|
||||||
|
pub mod db;
|
||||||
|
pub mod health;
|
||||||
|
pub mod item;
|
||||||
|
pub mod music;
|
||||||
|
pub mod origins;
|
||||||
|
pub mod virtual_dirs;
|
||||||
|
|
||||||
|
pub use musicfs_core::logging;
|
||||||
|
pub use musicfs_proto as proto;
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::{collections::HashMap, path::Path, sync::Arc};
|
use std::{collections::HashMap, net::SocketAddr, path::Path, sync::Arc};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use fuser::INodeNo;
|
use fuser::INodeNo;
|
||||||
use musicfs::db::entities::{Entity, Model};
|
use musicfs::db::entities::{Entity, Model};
|
||||||
use musicfs::db::sync::sync_items_to_db;
|
use musicfs::db::sync::sync_items_to_db;
|
||||||
|
use musicfs::health::ServerStatus;
|
||||||
use musicfs::item::Item;
|
use musicfs::item::Item;
|
||||||
use musicfs::logging::{LogConfig, init};
|
use musicfs::logging::{LogConfig, init};
|
||||||
use musicfs::music::db::restore_music_metadata_from_db;
|
use musicfs::music::db::restore_music_metadata_from_db;
|
||||||
@@ -32,6 +33,10 @@ struct Args {
|
|||||||
/// Directory for daily-rotated log files.
|
/// Directory for daily-rotated log files.
|
||||||
#[arg(long, default_value = "./logs")]
|
#[arg(long, default_value = "./logs")]
|
||||||
log_dir: std::path::PathBuf,
|
log_dir: std::path::PathBuf,
|
||||||
|
|
||||||
|
/// gRPC server address for health, status, and control RPCs.
|
||||||
|
#[arg(long, default_value = "127.0.0.1:50052")]
|
||||||
|
listen: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -68,21 +73,25 @@ async fn main() {
|
|||||||
});
|
});
|
||||||
info!(database = %args.database, "database connected");
|
info!(database = %args.database, "database connected");
|
||||||
|
|
||||||
let (snapshot, byte_source, watcher) = if looks_like_url(&args.source) {
|
let (snapshot, byte_source, watcher, server_status) = if looks_like_url(&args.source) {
|
||||||
debug!(source = %args.source, "using NetworkOrigin");
|
debug!(source = %args.source, "using NetworkOrigin");
|
||||||
let origin = NetworkOrigin::new(args.source.clone(), mountpoint.clone(), db.clone())
|
let origin = NetworkOrigin::new(args.source.clone(), mountpoint.clone(), db.clone())
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
error!(source = %args.source, error = %e, "network origin init failed");
|
error!(source = %args.source, error = %e, "network origin init failed");
|
||||||
panic!("network origin init: {e}");
|
panic!("network origin init: {e}");
|
||||||
});
|
});
|
||||||
// Await directly on this runtime; the sync `snapshot()` wrapper would
|
let server_status = origin.server_status();
|
||||||
// try to build a nested runtime and panic (we're inside #[tokio::main]).
|
|
||||||
let snapshot = origin.snapshot_async().await.unwrap_or_else(|e| {
|
let snapshot = origin.snapshot_async().await.unwrap_or_else(|e| {
|
||||||
error!(source = %args.source, error = %e, "network initial snapshot failed");
|
error!(source = %args.source, error = %e, "network initial snapshot failed");
|
||||||
panic!("network initial snapshot: {e}");
|
panic!("network initial snapshot: {e}");
|
||||||
});
|
});
|
||||||
info!(files = snapshot.len(), "network snapshot complete");
|
info!(files = snapshot.len(), "network snapshot complete");
|
||||||
(snapshot, origin.byte_source(), origin.watcher())
|
(
|
||||||
|
snapshot,
|
||||||
|
origin.byte_source(),
|
||||||
|
origin.watcher(),
|
||||||
|
server_status,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
debug!(source = %args.source, "using LocalOrigin");
|
debug!(source = %args.source, "using LocalOrigin");
|
||||||
let origin = LocalOrigin::new(args.source.clone(), mountpoint.clone());
|
let origin = LocalOrigin::new(args.source.clone(), mountpoint.clone());
|
||||||
@@ -106,12 +115,20 @@ async fn main() {
|
|||||||
restore_virtual_paths(&mut snapshot, &db_items, Path::new(&args.source));
|
restore_virtual_paths(&mut snapshot, &db_items, Path::new(&args.source));
|
||||||
|
|
||||||
info!(files = snapshot.len(), "local snapshot complete");
|
info!(files = snapshot.len(), "local snapshot complete");
|
||||||
(snapshot, origin.byte_source(), origin.watcher())
|
let server_status = ServerStatus::new_local(mountpoint.clone());
|
||||||
|
(
|
||||||
|
snapshot,
|
||||||
|
origin.byte_source(),
|
||||||
|
origin.watcher(),
|
||||||
|
server_status,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let files: Arc<std::sync::Mutex<std::collections::BTreeMap<INodeNo, Item>>> =
|
let files: Arc<std::sync::Mutex<std::collections::BTreeMap<INodeNo, Item>>> =
|
||||||
Arc::new(std::sync::Mutex::new(snapshot));
|
Arc::new(std::sync::Mutex::new(snapshot));
|
||||||
let watcher_handle = watcher.watch(files.clone());
|
let watcher_handle = watcher.watch(files.clone());
|
||||||
|
let health_files = files.clone();
|
||||||
|
let grpc_db = db.clone();
|
||||||
|
|
||||||
let fs = FuseFs {
|
let fs = FuseFs {
|
||||||
files,
|
files,
|
||||||
@@ -127,6 +144,8 @@ async fn main() {
|
|||||||
});
|
});
|
||||||
info!(mountpoint = %mountpoint, "FUSE mounted");
|
info!(mountpoint = %mountpoint, "FUSE mounted");
|
||||||
|
|
||||||
|
musicfs::health::spawn_grpc_server(args.listen, server_status, health_files, grpc_db).await;
|
||||||
|
|
||||||
let mut sigint = signal(SignalKind::interrupt()).unwrap_or_else(|e| {
|
let mut sigint = signal(SignalKind::interrupt()).unwrap_or_else(|e| {
|
||||||
error!(error = %e, "failed to register SIGINT handler");
|
error!(error = %e, "failed to register SIGINT handler");
|
||||||
panic!("register SIGINT handler: {e}");
|
panic!("register SIGINT handler: {e}");
|
||||||
@@ -6,11 +6,93 @@ use sea_orm::entity::prelude::*;
|
|||||||
use crate::db::entities::Model;
|
use crate::db::entities::Model;
|
||||||
use crate::item::Item;
|
use crate::item::Item;
|
||||||
use crate::music::metadata::MusicMetadata;
|
use crate::music::metadata::MusicMetadata;
|
||||||
use crate::music::metadata::db::{
|
|
||||||
artists, music_metadata as music_metadata_entity, other_tags, pictures,
|
|
||||||
};
|
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
|
pub mod entities {
|
||||||
|
pub mod music_metadata {
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "music_metadata")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub inode: i64,
|
||||||
|
pub track_title: String,
|
||||||
|
pub album: String,
|
||||||
|
pub track_number: i32,
|
||||||
|
#[sea_orm(column_type = "Blob")]
|
||||||
|
pub header: Vec<u8>,
|
||||||
|
pub real_audio_start: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod artists {
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "music_metadata_artists")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub inode: i64,
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub artist: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod other_tags {
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "music_metadata_other_tags")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub inode: i64,
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub position: i32,
|
||||||
|
pub tag: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod pictures {
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "music_metadata_pictures")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub inode: i64,
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub position: i32,
|
||||||
|
#[sea_orm(column_type = "Blob")]
|
||||||
|
pub block_header: Vec<u8>,
|
||||||
|
pub data_offset: i64,
|
||||||
|
pub data_length: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use entities::{artists, music_metadata as music_metadata_entity, other_tags, pictures};
|
||||||
|
|
||||||
pub async fn save_music_metadata(
|
pub async fn save_music_metadata(
|
||||||
inode: i64,
|
inode: i64,
|
||||||
music_metadata: &MusicMetadata,
|
music_metadata: &MusicMetadata,
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod db;
|
||||||
|
pub use musicfs_core::music::{metadata, parse};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
pub mod attrs;
|
pub use musicfs_core::attrs;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
|
||||||
@@ -29,17 +29,11 @@ use self::transport::NetworkTransport;
|
|||||||
pub struct NetworkOrigin {
|
pub struct NetworkOrigin {
|
||||||
pub(crate) endpoint: String,
|
pub(crate) endpoint: String,
|
||||||
pub(crate) destination: PathBuf,
|
pub(crate) destination: PathBuf,
|
||||||
/// Handle to the process-wide runtime (main's `#[tokio::main]`). Used by the
|
|
||||||
/// sync FUSE byte/watcher paths to drive async transport via `block_on`.
|
|
||||||
/// A Handle (not an owned Runtime) so dropping NetworkOrigin in async
|
|
||||||
/// context can't panic, and the runtime outlives any clones we hand out.
|
|
||||||
handle: tokio::runtime::Handle,
|
handle: tokio::runtime::Handle,
|
||||||
transport: NetworkTransport,
|
transport: NetworkTransport,
|
||||||
client: sea_orm::DatabaseConnection,
|
client: sea_orm::DatabaseConnection,
|
||||||
/// In-memory cache of the latest manifest, keyed by server id. Written by
|
|
||||||
/// `snapshot()`, read by the watcher to know what state to reconcile
|
|
||||||
/// against on the next pass without round-tripping through the DB.
|
|
||||||
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
|
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
|
||||||
|
server_status: crate::health::ServerStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for NetworkOrigin {
|
impl std::fmt::Debug for NetworkOrigin {
|
||||||
@@ -58,9 +52,10 @@ impl NetworkOrigin {
|
|||||||
destination: String,
|
destination: String,
|
||||||
client: sea_orm::DatabaseConnection,
|
client: sea_orm::DatabaseConnection,
|
||||||
) -> io::Result<Self> {
|
) -> io::Result<Self> {
|
||||||
// Must be called from within the process runtime (main's #[tokio::main]).
|
|
||||||
let handle = tokio::runtime::Handle::current();
|
let handle = tokio::runtime::Handle::current();
|
||||||
let transport = NetworkTransport::new(endpoint.clone()).map_err(io_err)?;
|
let transport = NetworkTransport::new(endpoint.clone()).map_err(io_err)?;
|
||||||
|
let server_status =
|
||||||
|
crate::health::ServerStatus::new_network(destination.clone(), endpoint.clone());
|
||||||
return Ok(NetworkOrigin {
|
return Ok(NetworkOrigin {
|
||||||
endpoint,
|
endpoint,
|
||||||
destination: destination.into(),
|
destination: destination.into(),
|
||||||
@@ -68,12 +63,17 @@ impl NetworkOrigin {
|
|||||||
transport,
|
transport,
|
||||||
client,
|
client,
|
||||||
latest_manifest: Arc::new(RwLock::new(BTreeMap::new())),
|
latest_manifest: Arc::new(RwLock::new(BTreeMap::new())),
|
||||||
|
server_status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runtime_handle(&self) -> tokio::runtime::Handle {
|
pub fn runtime_handle(&self) -> tokio::runtime::Handle {
|
||||||
return self.handle.clone();
|
return self.handle.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn server_status(&self) -> crate::health::ServerStatus {
|
||||||
|
self.server_status.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Origin for NetworkOrigin {
|
impl Origin for NetworkOrigin {
|
||||||
@@ -96,6 +96,7 @@ impl Origin for NetworkOrigin {
|
|||||||
self.client.clone(),
|
self.client.clone(),
|
||||||
self.destination.clone(),
|
self.destination.clone(),
|
||||||
self.latest_manifest.clone(),
|
self.latest_manifest.clone(),
|
||||||
|
self.server_status.clone(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,10 +267,8 @@ fn manifest_entry_to_item(
|
|||||||
rdev: 0,
|
rdev: 0,
|
||||||
blksize: 4096,
|
blksize: 4096,
|
||||||
};
|
};
|
||||||
let music_metadata: Option<MusicMetadata> = entry
|
let music_metadata: Option<MusicMetadata> =
|
||||||
.music_metadata
|
entry.music_metadata.clone().map(music_metadata_from_proto);
|
||||||
.clone()
|
|
||||||
.map(|mm| MusicMetadata::from(mm));
|
|
||||||
|
|
||||||
let mut local_path = PathBuf::new();
|
let mut local_path = PathBuf::new();
|
||||||
if let Some(mm) = &music_metadata {
|
if let Some(mm) = &music_metadata {
|
||||||
@@ -303,26 +302,24 @@ fn manifest_entry_to_item(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::proto::MusicMetadata> for MusicMetadata {
|
fn music_metadata_from_proto(mm: crate::proto::MusicMetadata) -> MusicMetadata {
|
||||||
fn from(mm: crate::proto::MusicMetadata) -> Self {
|
MusicMetadata {
|
||||||
return MusicMetadata {
|
artist: mm.artist,
|
||||||
artist: mm.artist,
|
album_artist: mm.album_artist,
|
||||||
album_artist: mm.album_artist,
|
album: mm.album,
|
||||||
album: mm.album,
|
track_number: mm.track_number,
|
||||||
track_number: mm.track_number,
|
track_title: mm.track_title,
|
||||||
track_title: mm.track_title,
|
other_tags: mm.other_tags,
|
||||||
other_tags: mm.other_tags,
|
header: mm.header,
|
||||||
header: mm.header,
|
picture_block_headers: mm.picture_block_headers,
|
||||||
picture_block_headers: mm.picture_block_headers,
|
picture_data_ranges: mm
|
||||||
picture_data_ranges: mm
|
.picture_data_ranges
|
||||||
.picture_data_ranges
|
.into_iter()
|
||||||
.into_iter()
|
.map(|p| (p.offset, p.length))
|
||||||
.map(|p| (p.offset, p.length))
|
.collect(),
|
||||||
.collect(),
|
real_audio_start: mm.real_audio_start,
|
||||||
real_audio_start: mm.real_audio_start,
|
vorbis_comment_offset: mm.vorbis_comment_offset,
|
||||||
vorbis_comment_offset: mm.vorbis_comment_offset,
|
vorbis_comment_length: mm.vorbis_comment_length,
|
||||||
vorbis_comment_length: mm.vorbis_comment_length,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+14
-2
@@ -26,6 +26,7 @@ pub struct NetworkOriginFileWatcher {
|
|||||||
client: DatabaseConnection,
|
client: DatabaseConnection,
|
||||||
destination: PathBuf,
|
destination: PathBuf,
|
||||||
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
|
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
|
||||||
|
server_status: crate::health::ServerStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkOriginFileWatcher {
|
impl NetworkOriginFileWatcher {
|
||||||
@@ -36,6 +37,7 @@ impl NetworkOriginFileWatcher {
|
|||||||
client: DatabaseConnection,
|
client: DatabaseConnection,
|
||||||
destination: PathBuf,
|
destination: PathBuf,
|
||||||
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
|
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
|
||||||
|
server_status: crate::health::ServerStatus,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
return NetworkOriginFileWatcher {
|
return NetworkOriginFileWatcher {
|
||||||
transport,
|
transport,
|
||||||
@@ -43,6 +45,7 @@ impl NetworkOriginFileWatcher {
|
|||||||
client,
|
client,
|
||||||
destination,
|
destination,
|
||||||
latest_manifest,
|
latest_manifest,
|
||||||
|
server_status,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,6 +59,7 @@ impl FileWatcher for NetworkOriginFileWatcher {
|
|||||||
client: self.client.clone(),
|
client: self.client.clone(),
|
||||||
destination: self.destination.clone(),
|
destination: self.destination.clone(),
|
||||||
latest_manifest: self.latest_manifest.clone(),
|
latest_manifest: self.latest_manifest.clone(),
|
||||||
|
server_status: self.server_status.clone(),
|
||||||
files,
|
files,
|
||||||
};
|
};
|
||||||
let loop_shutdown = shutdown.clone();
|
let loop_shutdown = shutdown.clone();
|
||||||
@@ -69,12 +73,12 @@ impl FileWatcher for NetworkOriginFileWatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct WatcherState {
|
struct WatcherState {
|
||||||
transport: NetworkTransport,
|
transport: NetworkTransport,
|
||||||
client: DatabaseConnection,
|
client: DatabaseConnection,
|
||||||
destination: PathBuf,
|
destination: PathBuf,
|
||||||
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
|
latest_manifest: Arc<RwLock<BTreeMap<u64, ProtoManifestEntry>>>,
|
||||||
|
server_status: crate::health::ServerStatus,
|
||||||
files: Arc<std::sync::Mutex<BTreeMap<INodeNo, Item>>>,
|
files: Arc<std::sync::Mutex<BTreeMap<INodeNo, Item>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +94,7 @@ impl WatcherState {
|
|||||||
match subscribed {
|
match subscribed {
|
||||||
Ok(mut stream) => {
|
Ok(mut stream) => {
|
||||||
info!("network watcher: subscribed to /events");
|
info!("network watcher: subscribed to /events");
|
||||||
|
self.server_status.set_connected(true);
|
||||||
retry_delay = INITIAL_RETRY_DELAY;
|
retry_delay = INITIAL_RETRY_DELAY;
|
||||||
loop {
|
loop {
|
||||||
let item = tokio::select! {
|
let item = tokio::select! {
|
||||||
@@ -105,13 +110,18 @@ impl WatcherState {
|
|||||||
}
|
}
|
||||||
Some(Err(e)) => {
|
Some(Err(e)) => {
|
||||||
warn!(error = %e, "network watcher: stream error; reconnecting");
|
warn!(error = %e, "network watcher: stream error; reconnecting");
|
||||||
|
self.server_status.set_connected(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.server_status.set_connected(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
None => break,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
self.server_status.set_connected(false);
|
||||||
warn!(error = %e, "network watcher: subscribe failed; will retry");
|
warn!(error = %e, "network watcher: subscribe failed; will retry");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,6 +183,8 @@ impl WatcherState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.server_status.mark_reconciled();
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
changed = response.changed.len(),
|
changed = response.changed.len(),
|
||||||
deleted = response.deleted.len(),
|
deleted = response.deleted.len(),
|
||||||
@@ -0,0 +1,531 @@
|
|||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
fs,
|
||||||
|
hash::Hasher,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use fuser::INodeNo;
|
||||||
|
use twox_hash::XxHash64;
|
||||||
|
|
||||||
|
use crate::db::entities::Model;
|
||||||
|
use crate::item::{FileType, Item};
|
||||||
|
use crate::music::metadata::MusicMetadata;
|
||||||
|
use crate::origins::attrs::FileAttrs;
|
||||||
|
|
||||||
|
pub fn virtual_inode(path: &str) -> INodeNo {
|
||||||
|
let mut hasher = XxHash64::with_seed(5678);
|
||||||
|
hasher.write(path.as_bytes());
|
||||||
|
|
||||||
|
return INodeNo(hasher.finish() | (1u64 << 63));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent_inode_from_path(local_path: &Path) -> INodeNo {
|
||||||
|
let components: Vec<_> = local_path
|
||||||
|
.components()
|
||||||
|
.filter(|c| matches!(c, std::path::Component::Normal(_)))
|
||||||
|
.collect();
|
||||||
|
if components.len() <= 1 {
|
||||||
|
return INodeNo::ROOT;
|
||||||
|
}
|
||||||
|
let mut parent_path = String::new();
|
||||||
|
for (i, comp) in components[..components.len() - 1].iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
parent_path.push('/');
|
||||||
|
}
|
||||||
|
parent_path.push_str(comp.as_os_str().to_str().unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
|
return virtual_inode(&parent_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_virtual_dirs(
|
||||||
|
local_path: &Path,
|
||||||
|
source: &Path,
|
||||||
|
map: &mut BTreeMap<INodeNo, Item>,
|
||||||
|
) -> INodeNo {
|
||||||
|
let components: Vec<_> = local_path
|
||||||
|
.components()
|
||||||
|
.filter(|c| matches!(c, std::path::Component::Normal(_)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if components.len() <= 1 {
|
||||||
|
return INodeNo::ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_parent = INodeNo::ROOT;
|
||||||
|
let mut current_path = String::new();
|
||||||
|
|
||||||
|
for component in &components[..components.len() - 1] {
|
||||||
|
let comp_str = component.as_os_str().to_str().unwrap_or_default();
|
||||||
|
if !current_path.is_empty() {
|
||||||
|
current_path.push('/');
|
||||||
|
}
|
||||||
|
current_path.push_str(comp_str);
|
||||||
|
|
||||||
|
let virt_ino = virtual_inode(¤t_path);
|
||||||
|
|
||||||
|
if !map.contains_key(&virt_ino) {
|
||||||
|
let virt_item = Item::new(
|
||||||
|
virt_ino,
|
||||||
|
current_parent,
|
||||||
|
comp_str.to_string(),
|
||||||
|
source.to_path_buf(),
|
||||||
|
PathBuf::from(¤t_path),
|
||||||
|
FileType::Directory,
|
||||||
|
FileAttrs::from(&fs::metadata(source).unwrap()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
map.insert(virt_ino, virt_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_parent = virt_ino;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current_parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore_virtual_paths(
|
||||||
|
snapshot: &mut BTreeMap<INodeNo, Item>,
|
||||||
|
db_items: &std::collections::HashMap<i64, Model>,
|
||||||
|
source: &Path,
|
||||||
|
) {
|
||||||
|
let restorations: Vec<(INodeNo, String, PathBuf)> = db_items
|
||||||
|
.values()
|
||||||
|
.filter_map(|db_item| {
|
||||||
|
let ino = INodeNo(db_item.inode as u64);
|
||||||
|
if ino == INodeNo::ROOT {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
snapshot
|
||||||
|
.get(&ino)
|
||||||
|
.filter(|item| item.hash as i64 == db_item.hash)
|
||||||
|
.map(|_| {
|
||||||
|
(
|
||||||
|
ino,
|
||||||
|
db_item.name.clone(),
|
||||||
|
PathBuf::from(&db_item.local_path),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for (_, _, local_path) in &restorations {
|
||||||
|
ensure_virtual_dirs(local_path, source, snapshot);
|
||||||
|
}
|
||||||
|
for (ino, name, local_path) in restorations {
|
||||||
|
if let Some(item) = snapshot.get_mut(&ino) {
|
||||||
|
item.name = name;
|
||||||
|
item.parent_inode = parent_inode_from_path(&local_path);
|
||||||
|
item.local_path = local_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the new (name, local_path) for an Item based on its updated
|
||||||
|
/// `MusicMetadata`. Mirrors the layout logic in
|
||||||
|
/// `origins::local::snapshot::read_into_map`:
|
||||||
|
/// - artist dir = `album_artist` if set, else `artists.join("-")`
|
||||||
|
/// - if `track_title` is non-empty, the filename becomes
|
||||||
|
/// `{track_number:02} - {track_title}.{ext}` (zero-padded track number
|
||||||
|
/// when > 0, otherwise just `{track_title}.{ext}`), ext preserved from
|
||||||
|
/// `current_name`
|
||||||
|
/// - otherwise the filename is unchanged
|
||||||
|
/// Returns `(new_name, new_local_path)`.
|
||||||
|
pub fn compute_new_layout(current_name: &str, mm: &MusicMetadata) -> (String, PathBuf) {
|
||||||
|
let artist_dir = artist_dir(mm);
|
||||||
|
let filename = match &mm.track_title {
|
||||||
|
title if !title.is_empty() => {
|
||||||
|
let stem = if mm.track_number > 0 {
|
||||||
|
format!("{:02} - {}", mm.track_number, title)
|
||||||
|
} else {
|
||||||
|
title.clone()
|
||||||
|
};
|
||||||
|
rename_with_extension(current_name, &stem)
|
||||||
|
}
|
||||||
|
_ => current_name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut local_path = PathBuf::new();
|
||||||
|
local_path.push(&artist_dir);
|
||||||
|
local_path.push(&mm.album);
|
||||||
|
local_path.push(&filename);
|
||||||
|
|
||||||
|
(filename, local_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_dir(mm: &MusicMetadata) -> String {
|
||||||
|
match &mm.album_artist {
|
||||||
|
Some(a) if !a.is_empty() => a.clone(),
|
||||||
|
_ => mm.artist.join("-"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename_with_extension(current_name: &str, new_stem: &str) -> String {
|
||||||
|
let current_path = Path::new(current_name);
|
||||||
|
match current_path.extension() {
|
||||||
|
Some(ext) => format!("{new_stem}.{}", ext.to_string_lossy()),
|
||||||
|
None => new_stem.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the virtual inodes of directories in the `old_local_path`'s
|
||||||
|
/// parent chain that will have no remaining files after this item moves
|
||||||
|
/// away. Walks the chain from the deepest directory upward; stops at the
|
||||||
|
/// first directory that still has other files living in it.
|
||||||
|
///
|
||||||
|
/// `moving_inode` is the actual inode of the file being relocated (the
|
||||||
|
/// request's `inode`), used to exclude it from the "any other files here?"
|
||||||
|
/// check. `old_local_path` is the LOCAL (virtual FUSE) path of the file
|
||||||
|
/// before its metadata changed.
|
||||||
|
pub fn find_orphaned_dirs(
|
||||||
|
files: &BTreeMap<INodeNo, Item>,
|
||||||
|
moving_inode: INodeNo,
|
||||||
|
old_local_path: &Path,
|
||||||
|
) -> Vec<INodeNo> {
|
||||||
|
let components: Vec<_> = old_local_path
|
||||||
|
.components()
|
||||||
|
.filter(|c| matches!(c, std::path::Component::Normal(_)))
|
||||||
|
.collect();
|
||||||
|
if components.len() <= 1 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut orphaned = vec![];
|
||||||
|
for depth in (1..components.len()).rev() {
|
||||||
|
let dir_path: PathBuf = components[..depth].iter().map(|c| c.as_os_str()).collect();
|
||||||
|
let dir_path_str = dir_path.to_string_lossy().into_owned();
|
||||||
|
let dir_inode = virtual_inode(&dir_path_str);
|
||||||
|
|
||||||
|
// Path-prefix match: a file at "Artist/Album2/b.flac" still keeps
|
||||||
|
// "Artist" non-orphan even though its direct parent is "Artist/Album2".
|
||||||
|
let prefix = format!("{dir_path_str}/");
|
||||||
|
let has_other_files = files.values().any(|item| {
|
||||||
|
item.inode != moving_inode
|
||||||
|
&& item.file_type == FileType::File
|
||||||
|
&& item.local_path.starts_with(&prefix)
|
||||||
|
});
|
||||||
|
if has_other_files {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
orphaned.push(dir_inode);
|
||||||
|
}
|
||||||
|
orphaned
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// ---- compute_new_layout ----
|
||||||
|
|
||||||
|
fn mm(artist: &str, album: &str, title: &str) -> MusicMetadata {
|
||||||
|
MusicMetadata {
|
||||||
|
artist: vec![artist.to_string()],
|
||||||
|
album_artist: None,
|
||||||
|
album: album.to_string(),
|
||||||
|
track_number: 1,
|
||||||
|
track_title: title.to_string(),
|
||||||
|
other_tags: vec![],
|
||||||
|
header: vec![],
|
||||||
|
picture_block_headers: vec![],
|
||||||
|
picture_data_ranges: vec![],
|
||||||
|
real_audio_start: 0,
|
||||||
|
vorbis_comment_offset: 0,
|
||||||
|
vorbis_comment_length: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_item(inode: u64, name: &str, local_path: &str) -> Item {
|
||||||
|
Item {
|
||||||
|
inode: INodeNo(inode),
|
||||||
|
parent_inode: parent_inode_from_path(Path::new(local_path)),
|
||||||
|
name: name.to_string(),
|
||||||
|
original_path: format!("/torrent/{name}").into(),
|
||||||
|
local_path: local_path.into(),
|
||||||
|
file_type: FileType::File,
|
||||||
|
attrs: FileAttrs {
|
||||||
|
size: 1,
|
||||||
|
blocks: 1,
|
||||||
|
atime: std::time::SystemTime::UNIX_EPOCH,
|
||||||
|
mtime: std::time::SystemTime::UNIX_EPOCH,
|
||||||
|
ctime: std::time::SystemTime::UNIX_EPOCH,
|
||||||
|
crtime: std::time::SystemTime::UNIX_EPOCH,
|
||||||
|
perm: 0o644,
|
||||||
|
nlink: 1,
|
||||||
|
uid: 0,
|
||||||
|
gid: 0,
|
||||||
|
rdev: 0,
|
||||||
|
blksize: 4096,
|
||||||
|
},
|
||||||
|
music_metadata: None,
|
||||||
|
hash: inode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_new_layout_renames_file_when_track_title_provided() {
|
||||||
|
let mm = mm(
|
||||||
|
"Pink Floyd",
|
||||||
|
"Wish You Were Here",
|
||||||
|
"Shine On You Crazy Diamond",
|
||||||
|
);
|
||||||
|
let (name, path) = compute_new_layout("01-track.flac", &mm);
|
||||||
|
assert_eq!(name, "01 - Shine On You Crazy Diamond.flac");
|
||||||
|
assert_eq!(
|
||||||
|
path,
|
||||||
|
PathBuf::from("Pink Floyd/Wish You Were Here/01 - Shine On You Crazy Diamond.flac")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_new_layout_preserves_extension_through_rename() {
|
||||||
|
let mm = mm("A", "B", "New Title");
|
||||||
|
let (name, _) = compute_new_layout("old.mp3", &mm);
|
||||||
|
assert_eq!(name, "01 - New Title.mp3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_new_layout_handles_multi_dot_extensions() {
|
||||||
|
let mm = mm("A", "B", "New");
|
||||||
|
let (name, _) = compute_new_layout("old.tar.gz", &mm);
|
||||||
|
assert_eq!(name, "01 - New.gz", "only the final extension is preserved");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_new_layout_keeps_original_name_when_track_title_empty() {
|
||||||
|
let mut metadata = mm("A", "B", "ignored");
|
||||||
|
metadata.track_title = String::new();
|
||||||
|
let (name, path) = compute_new_layout("original-name.flac", &metadata);
|
||||||
|
assert_eq!(name, "original-name.flac");
|
||||||
|
assert_eq!(path, PathBuf::from("A/B/original-name.flac"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_new_layout_uses_album_artist_when_present() {
|
||||||
|
let mut metadata = mm("Secondary", "Album", "Title");
|
||||||
|
metadata.album_artist = Some("Primary Artist".to_string());
|
||||||
|
let (_, path) = compute_new_layout("track.flac", &metadata);
|
||||||
|
assert_eq!(path, PathBuf::from("Primary Artist/Album/01 - Title.flac"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_new_layout_joins_multiple_artists_with_dash_when_no_album_artist() {
|
||||||
|
let mut metadata = mm("Foo", "Album", "Title");
|
||||||
|
metadata.artist = vec!["Foo".to_string(), "Bar".to_string()];
|
||||||
|
let (_, path) = compute_new_layout("track.flac", &metadata);
|
||||||
|
assert_eq!(path, PathBuf::from("Foo-Bar/Album/01 - Title.flac"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- find_orphaned_dirs ----
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_orphaned_dirs_returns_both_album_and_artist_when_last_file_moves() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
file_item(100, "track.flac", "Artist/Album/track.flac"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let orphaned =
|
||||||
|
find_orphaned_dirs(&files, INodeNo(100), Path::new("Artist/Album/track.flac"));
|
||||||
|
assert_eq!(orphaned.len(), 2);
|
||||||
|
assert!(orphaned.contains(&virtual_inode("Artist/Album")));
|
||||||
|
assert!(orphaned.contains(&virtual_inode("Artist")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_orphaned_dirs_returns_album_only_when_other_album_keeps_artist() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
file_item(100, "a.flac", "Artist/Album1/a.flac"),
|
||||||
|
);
|
||||||
|
files.insert(
|
||||||
|
INodeNo(101),
|
||||||
|
file_item(101, "b.flac", "Artist/Album2/b.flac"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let orphaned = find_orphaned_dirs(&files, INodeNo(100), Path::new("Artist/Album1/a.flac"));
|
||||||
|
assert_eq!(orphaned, vec![virtual_inode("Artist/Album1")]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_orphaned_dirs_returns_nothing_when_other_files_remain_in_same_album() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
file_item(100, "a.flac", "Artist/Album/a.flac"),
|
||||||
|
);
|
||||||
|
files.insert(
|
||||||
|
INodeNo(101),
|
||||||
|
file_item(101, "b.flac", "Artist/Album/b.flac"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let orphaned = find_orphaned_dirs(&files, INodeNo(100), Path::new("Artist/Album/a.flac"));
|
||||||
|
assert!(orphaned.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_orphaned_dirs_returns_nothing_for_file_at_root() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(INodeNo(100), file_item(100, "track.flac", "track.flac"));
|
||||||
|
|
||||||
|
let orphaned = find_orphaned_dirs(&files, INodeNo(100), Path::new("track.flac"));
|
||||||
|
assert!(orphaned.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_orphaned_dirs_excludes_moving_file_from_other_file_check() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
file_item(100, "only.flac", "Artist/Album/only.flac"),
|
||||||
|
);
|
||||||
|
// If we forgot to exclude the moving inode, this test would return
|
||||||
|
// empty (thinking the dir is still populated). Must return both dirs.
|
||||||
|
let orphaned =
|
||||||
|
find_orphaned_dirs(&files, INodeNo(100), Path::new("Artist/Album/only.flac"));
|
||||||
|
assert_eq!(orphaned.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn virtual_inode_deterministic() {
|
||||||
|
let ino1 = virtual_inode("foo");
|
||||||
|
let ino2 = virtual_inode("foo");
|
||||||
|
assert_eq!(ino1, ino2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn virtual_inode_high_bit_set() {
|
||||||
|
let ino = virtual_inode("test");
|
||||||
|
assert_eq!(ino.0 & (1u64 << 63), 1u64 << 63);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn virtual_inode_different_inputs() {
|
||||||
|
let ino1 = virtual_inode("foo");
|
||||||
|
let ino2 = virtual_inode("bar");
|
||||||
|
assert_ne!(ino1, ino2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parent_inode_single_component() {
|
||||||
|
let ino = parent_inode_from_path(Path::new("file.txt"));
|
||||||
|
assert_eq!(ino, INodeNo::ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parent_inode_multi_component() {
|
||||||
|
let ino = parent_inode_from_path(Path::new("a/b/c"));
|
||||||
|
let expected = virtual_inode("a/b");
|
||||||
|
assert_eq!(ino, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parent_inode_absolute_path_filtered() {
|
||||||
|
let ino = parent_inode_from_path(Path::new("/file"));
|
||||||
|
assert_eq!(ino, INodeNo::ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_virtual_dirs_single_component() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let source = tmp.path();
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
|
||||||
|
let ino = ensure_virtual_dirs(Path::new("file.txt"), source, &mut map);
|
||||||
|
assert_eq!(ino, INodeNo::ROOT);
|
||||||
|
assert!(map.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_virtual_dirs_creates_hierarchy() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let source = tmp.path();
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
|
||||||
|
let ino = ensure_virtual_dirs(Path::new("a/b/file"), source, &mut map);
|
||||||
|
|
||||||
|
let a_ino = virtual_inode("a");
|
||||||
|
let ab_ino = virtual_inode("a/b");
|
||||||
|
|
||||||
|
assert_eq!(ino, ab_ino);
|
||||||
|
assert_eq!(map.len(), 2);
|
||||||
|
assert!(map.contains_key(&a_ino));
|
||||||
|
assert!(map.contains_key(&ab_ino));
|
||||||
|
|
||||||
|
let a_item = &map[&a_ino];
|
||||||
|
assert_eq!(a_item.parent_inode, INodeNo::ROOT);
|
||||||
|
|
||||||
|
let ab_item = &map[&ab_ino];
|
||||||
|
assert_eq!(ab_item.parent_inode, a_ino);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_virtual_dirs_idempotent() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let source = tmp.path();
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
|
||||||
|
ensure_virtual_dirs(Path::new("a/b/file"), source, &mut map);
|
||||||
|
let size_after_first = map.len();
|
||||||
|
|
||||||
|
ensure_virtual_dirs(Path::new("a/b/file"), source, &mut map);
|
||||||
|
let size_after_second = map.len();
|
||||||
|
|
||||||
|
assert_eq!(size_after_first, size_after_second);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn restore_virtual_paths_skips_root() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let source = tmp.path();
|
||||||
|
let mut snapshot = BTreeMap::new();
|
||||||
|
|
||||||
|
let real_ino = INodeNo(42);
|
||||||
|
let real_item = Item::new(
|
||||||
|
real_ino,
|
||||||
|
INodeNo::ROOT,
|
||||||
|
"real_file".to_string(),
|
||||||
|
source.to_path_buf(),
|
||||||
|
PathBuf::from("real_file"),
|
||||||
|
FileType::File,
|
||||||
|
FileAttrs::from(&fs::metadata(source).unwrap()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
snapshot.insert(real_ino, real_item);
|
||||||
|
|
||||||
|
let mut db_items = HashMap::new();
|
||||||
|
db_items.insert(
|
||||||
|
1i64,
|
||||||
|
Model {
|
||||||
|
inode: 1,
|
||||||
|
hash: 0,
|
||||||
|
name: "root".to_string(),
|
||||||
|
original_path: "/tmp/test".to_string(),
|
||||||
|
local_path: "/tmp/test".to_string(),
|
||||||
|
file_type: "directory".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
db_items.insert(
|
||||||
|
42i64,
|
||||||
|
Model {
|
||||||
|
inode: 42,
|
||||||
|
hash: 0,
|
||||||
|
name: "real_file".to_string(),
|
||||||
|
original_path: "real_file".to_string(),
|
||||||
|
local_path: "real_file".to_string(),
|
||||||
|
file_type: "file".to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
restore_virtual_paths(&mut snapshot, &db_items, source);
|
||||||
|
|
||||||
|
for item in snapshot.values() {
|
||||||
|
assert_ne!(item.name, "/");
|
||||||
|
assert_ne!(item.name, "tmp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,562 @@
|
|||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
use fuser::INodeNo;
|
||||||
|
use musicfs::control::ClientControlServiceImpl;
|
||||||
|
use musicfs::db::entities;
|
||||||
|
use musicfs::item::{FileType, Item};
|
||||||
|
use musicfs::music::db::entities::{artists, music_metadata as mm_entity};
|
||||||
|
use musicfs::music::metadata::MusicMetadata;
|
||||||
|
use musicfs::origins::attrs::FileAttrs;
|
||||||
|
use musicfs_proto::{
|
||||||
|
ClientControl, GetMusicMetadataRequest, ListFilesRequest, UpdateMusicMetadataRequest,
|
||||||
|
};
|
||||||
|
use sea_orm::{DatabaseBackend, MockDatabase, MockExecResult};
|
||||||
|
use tonic::{Code, Request};
|
||||||
|
|
||||||
|
// ── Test helpers ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn make_attrs() -> FileAttrs {
|
||||||
|
FileAttrs {
|
||||||
|
size: 4096,
|
||||||
|
blocks: 8,
|
||||||
|
atime: SystemTime::UNIX_EPOCH,
|
||||||
|
mtime: SystemTime::UNIX_EPOCH,
|
||||||
|
ctime: SystemTime::UNIX_EPOCH,
|
||||||
|
crtime: SystemTime::UNIX_EPOCH,
|
||||||
|
perm: 0o644,
|
||||||
|
nlink: 1,
|
||||||
|
uid: 0,
|
||||||
|
gid: 0,
|
||||||
|
rdev: 0,
|
||||||
|
blksize: 4096,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_flac_item(inode: u64, name: &str, metadata: Option<MusicMetadata>) -> Item {
|
||||||
|
Item {
|
||||||
|
inode: INodeNo(inode),
|
||||||
|
parent_inode: INodeNo(1),
|
||||||
|
name: name.to_string(),
|
||||||
|
original_path: format!("/{name}").into(),
|
||||||
|
local_path: format!("Artist/Album/{name}").into(),
|
||||||
|
file_type: FileType::File,
|
||||||
|
attrs: make_attrs(),
|
||||||
|
music_metadata: metadata,
|
||||||
|
hash: inode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_flac_metadata() -> MusicMetadata {
|
||||||
|
MusicMetadata {
|
||||||
|
artist: vec!["Test Artist".to_string()],
|
||||||
|
album_artist: Some("Test Artist".to_string()),
|
||||||
|
album: "Test Album".to_string(),
|
||||||
|
track_number: 1,
|
||||||
|
track_title: "Test Track".to_string(),
|
||||||
|
other_tags: vec![],
|
||||||
|
header: vec![],
|
||||||
|
picture_block_headers: vec![],
|
||||||
|
picture_data_ranges: vec![],
|
||||||
|
real_audio_start: 0,
|
||||||
|
vorbis_comment_offset: 0,
|
||||||
|
vorbis_comment_length: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mock DB for get tests (no INSERT operations expected).
|
||||||
|
fn mock_db_readonly() -> sea_orm::DatabaseConnection {
|
||||||
|
MockDatabase::new(DatabaseBackend::Postgres).into_connection()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mock DB for update with one artist.
|
||||||
|
/// save_music_metadata does: DELETE, INSERT music_metadata, INSERT artists.
|
||||||
|
/// persist_layout_changes does: UPDATE items (RETURNING), then one DELETE
|
||||||
|
/// per orphaned virtual directory (worst case = 2: artist + album dirs).
|
||||||
|
fn mock_db_update_with_artist(inode: i64) -> sea_orm::DatabaseConnection {
|
||||||
|
MockDatabase::new(DatabaseBackend::Postgres)
|
||||||
|
.append_exec_results([MockExecResult {
|
||||||
|
rows_affected: 1,
|
||||||
|
..Default::default()
|
||||||
|
}])
|
||||||
|
.append_query_results([vec![mm_entity::Model {
|
||||||
|
inode,
|
||||||
|
track_title: String::new(),
|
||||||
|
album: String::new(),
|
||||||
|
track_number: 0,
|
||||||
|
header: vec![],
|
||||||
|
real_audio_start: 0,
|
||||||
|
}]])
|
||||||
|
.append_query_results([vec![artists::Model {
|
||||||
|
inode,
|
||||||
|
artist: String::new(),
|
||||||
|
}]])
|
||||||
|
.append_query_results([vec![entities::Model {
|
||||||
|
inode,
|
||||||
|
name: String::new(),
|
||||||
|
original_path: String::new(),
|
||||||
|
local_path: String::new(),
|
||||||
|
file_type: "file".to_string(),
|
||||||
|
hash: 0,
|
||||||
|
}]])
|
||||||
|
.append_exec_results([MockExecResult {
|
||||||
|
rows_affected: 1,
|
||||||
|
..Default::default()
|
||||||
|
}])
|
||||||
|
.append_exec_results([MockExecResult {
|
||||||
|
rows_affected: 1,
|
||||||
|
..Default::default()
|
||||||
|
}])
|
||||||
|
.into_connection()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mock DB for update with no artists.
|
||||||
|
/// save_music_metadata does: DELETE, INSERT music_metadata only.
|
||||||
|
/// persist_layout_changes does: UPDATE items (RETURNING), then up to two
|
||||||
|
/// DELETEs for orphaned virtual directories.
|
||||||
|
fn mock_db_update_no_artist(inode: i64) -> sea_orm::DatabaseConnection {
|
||||||
|
MockDatabase::new(DatabaseBackend::Postgres)
|
||||||
|
.append_exec_results([MockExecResult {
|
||||||
|
rows_affected: 1,
|
||||||
|
..Default::default()
|
||||||
|
}])
|
||||||
|
.append_query_results([vec![mm_entity::Model {
|
||||||
|
inode,
|
||||||
|
track_title: String::new(),
|
||||||
|
album: String::new(),
|
||||||
|
track_number: 0,
|
||||||
|
header: vec![],
|
||||||
|
real_audio_start: 0,
|
||||||
|
}]])
|
||||||
|
.append_query_results([vec![entities::Model {
|
||||||
|
inode,
|
||||||
|
name: String::new(),
|
||||||
|
original_path: String::new(),
|
||||||
|
local_path: String::new(),
|
||||||
|
file_type: "file".to_string(),
|
||||||
|
hash: 0,
|
||||||
|
}]])
|
||||||
|
.append_exec_results([MockExecResult {
|
||||||
|
rows_affected: 1,
|
||||||
|
..Default::default()
|
||||||
|
}])
|
||||||
|
.append_exec_results([MockExecResult {
|
||||||
|
rows_affected: 1,
|
||||||
|
..Default::default()
|
||||||
|
}])
|
||||||
|
.into_connection()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_dir_item(inode: u64, name: &str) -> Item {
|
||||||
|
Item {
|
||||||
|
inode: INodeNo(inode),
|
||||||
|
parent_inode: INodeNo(1),
|
||||||
|
name: name.to_string(),
|
||||||
|
original_path: format!("/{name}").into(),
|
||||||
|
local_path: name.into(),
|
||||||
|
file_type: FileType::Directory,
|
||||||
|
attrs: make_attrs(),
|
||||||
|
music_metadata: None,
|
||||||
|
hash: inode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_removes_orphaned_virtual_dirs_when_last_file_moves_out() {
|
||||||
|
let source_dir = tempfile::tempdir().unwrap();
|
||||||
|
let source = source_dir.path();
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(0),
|
||||||
|
Item {
|
||||||
|
inode: INodeNo(0),
|
||||||
|
parent_inode: INodeNo::ROOT,
|
||||||
|
name: "/".to_string(),
|
||||||
|
original_path: source.to_path_buf(),
|
||||||
|
local_path: "/".into(),
|
||||||
|
file_type: FileType::Directory,
|
||||||
|
attrs: make_attrs(),
|
||||||
|
music_metadata: None,
|
||||||
|
hash: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
make_flac_item(100, "song.flac", Some(make_flac_metadata())),
|
||||||
|
);
|
||||||
|
let files = Arc::new(Mutex::new(files));
|
||||||
|
let svc = ClientControlServiceImpl::new(files.clone(), mock_db_update_with_artist(100));
|
||||||
|
|
||||||
|
// Pre-create the virtual dirs for the file's current layout so we can
|
||||||
|
// assert they get pruned after the metadata update moves the file.
|
||||||
|
{
|
||||||
|
use musicfs::virtual_dirs::{ensure_virtual_dirs, virtual_inode};
|
||||||
|
let mut guard = files.lock().unwrap();
|
||||||
|
ensure_virtual_dirs(
|
||||||
|
std::path::Path::new("Artist/Album/song.flac"),
|
||||||
|
source,
|
||||||
|
&mut guard,
|
||||||
|
);
|
||||||
|
assert!(guard.contains_key(&virtual_inode("Artist")));
|
||||||
|
assert!(guard.contains_key(&virtual_inode("Artist/Album")));
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.update_music_metadata(Request::new(UpdateMusicMetadataRequest {
|
||||||
|
inode: 100,
|
||||||
|
artist: vec!["New Artist".to_string()],
|
||||||
|
album_artist: Some("New Artist".to_string()),
|
||||||
|
album: "New Album".to_string(),
|
||||||
|
track_number: 1,
|
||||||
|
track_title: "New Title".to_string(),
|
||||||
|
other_tags: vec![],
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.expect("update should succeed");
|
||||||
|
|
||||||
|
let guard = files.lock().unwrap();
|
||||||
|
use musicfs::virtual_dirs::virtual_inode;
|
||||||
|
assert!(
|
||||||
|
!guard.contains_key(&virtual_inode("Artist")),
|
||||||
|
"old Artist dir must be removed when no files remain under it"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!guard.contains_key(&virtual_inode("Artist/Album")),
|
||||||
|
"old Album dir must be removed"
|
||||||
|
);
|
||||||
|
assert!(guard.contains_key(&virtual_inode("New Artist")));
|
||||||
|
assert!(guard.contains_key(&virtual_inode("New Artist/New Album")));
|
||||||
|
|
||||||
|
let item = guard.get(&INodeNo(100)).expect("file still present");
|
||||||
|
assert_eq!(
|
||||||
|
item.local_path,
|
||||||
|
std::path::PathBuf::from("New Artist/New Album/01 - New Title.flac")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_keeps_shared_artist_dir_when_other_album_remains() {
|
||||||
|
let source_dir = tempfile::tempdir().unwrap();
|
||||||
|
let source = source_dir.path();
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(0),
|
||||||
|
Item {
|
||||||
|
inode: INodeNo(0),
|
||||||
|
parent_inode: INodeNo::ROOT,
|
||||||
|
name: "/".to_string(),
|
||||||
|
original_path: source.to_path_buf(),
|
||||||
|
local_path: "/".into(),
|
||||||
|
file_type: FileType::Directory,
|
||||||
|
attrs: make_attrs(),
|
||||||
|
music_metadata: None,
|
||||||
|
hash: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
make_flac_item(100, "a.flac", Some(make_flac_metadata())),
|
||||||
|
);
|
||||||
|
files.insert(
|
||||||
|
INodeNo(101),
|
||||||
|
make_flac_item(101, "b.flac", Some(make_flac_metadata())),
|
||||||
|
);
|
||||||
|
files.get_mut(&INodeNo(101)).unwrap().local_path = "Artist/Other Album/b.flac".into();
|
||||||
|
|
||||||
|
// Pre-create virtual dirs for both files' layouts: pruning after the
|
||||||
|
// update should remove Album/ (file 100's old parent) but keep Artist/
|
||||||
|
// (still has Other Album/ with file 101 in it).
|
||||||
|
{
|
||||||
|
use musicfs::virtual_dirs::ensure_virtual_dirs;
|
||||||
|
ensure_virtual_dirs(
|
||||||
|
std::path::Path::new("Artist/Album/a.flac"),
|
||||||
|
source,
|
||||||
|
&mut files,
|
||||||
|
);
|
||||||
|
ensure_virtual_dirs(
|
||||||
|
std::path::Path::new("Artist/Other Album/b.flac"),
|
||||||
|
source,
|
||||||
|
&mut files,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let files = Arc::new(Mutex::new(files));
|
||||||
|
let svc = ClientControlServiceImpl::new(files.clone(), mock_db_update_with_artist(100));
|
||||||
|
|
||||||
|
svc.update_music_metadata(Request::new(UpdateMusicMetadataRequest {
|
||||||
|
inode: 100,
|
||||||
|
artist: vec!["New Artist".to_string()],
|
||||||
|
album_artist: Some("New Artist".to_string()),
|
||||||
|
album: "New Album".to_string(),
|
||||||
|
track_number: 1,
|
||||||
|
track_title: "New Title".to_string(),
|
||||||
|
other_tags: vec![],
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.expect("update should succeed");
|
||||||
|
|
||||||
|
let guard = files.lock().unwrap();
|
||||||
|
use musicfs::virtual_dirs::virtual_inode;
|
||||||
|
assert!(
|
||||||
|
guard.contains_key(&virtual_inode("Artist")),
|
||||||
|
"Artist dir must remain while Other Album still has files"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!guard.contains_key(&virtual_inode("Artist/Album")),
|
||||||
|
"Artist/Album dir must be removed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ListFiles ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn list_files_returns_all_files_with_full_field_projection() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
make_flac_item(100, "a.flac", Some(make_flac_metadata())),
|
||||||
|
);
|
||||||
|
files.insert(INodeNo(101), make_flac_item(101, "b.flac", None));
|
||||||
|
// Directory entries must be filtered out.
|
||||||
|
files.insert(INodeNo(2), make_dir_item(2, "Artist"));
|
||||||
|
let files = Arc::new(Mutex::new(files));
|
||||||
|
let svc = ClientControlServiceImpl::new(files, mock_db_readonly());
|
||||||
|
|
||||||
|
let resp = svc
|
||||||
|
.list_files(Request::new(ListFilesRequest {}))
|
||||||
|
.await
|
||||||
|
.expect("list should succeed");
|
||||||
|
|
||||||
|
let mut entries = resp.into_inner().files;
|
||||||
|
assert_eq!(entries.len(), 2, "directories must be filtered out");
|
||||||
|
entries.sort_by_key(|e| e.inode);
|
||||||
|
|
||||||
|
assert_eq!(entries[0].inode, 100);
|
||||||
|
assert_eq!(entries[0].name, "a.flac");
|
||||||
|
assert_eq!(entries[0].original_path, "/a.flac");
|
||||||
|
assert_eq!(entries[0].local_path, "Artist/Album/a.flac");
|
||||||
|
assert!(entries[0].metadata.is_some());
|
||||||
|
|
||||||
|
assert_eq!(entries[1].inode, 101);
|
||||||
|
assert_eq!(entries[1].name, "b.flac");
|
||||||
|
assert!(entries[1].metadata.is_none(), "no metadata on this file");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn list_files_empty_map_returns_empty_list() {
|
||||||
|
let files = Arc::new(Mutex::new(BTreeMap::new()));
|
||||||
|
let svc = ClientControlServiceImpl::new(files, mock_db_readonly());
|
||||||
|
|
||||||
|
let resp = svc
|
||||||
|
.list_files(Request::new(ListFilesRequest {}))
|
||||||
|
.await
|
||||||
|
.expect("list should succeed");
|
||||||
|
|
||||||
|
assert!(resp.into_inner().files.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn list_files_skips_directories_and_root() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(INodeNo(0), make_dir_item(0, "/")); // root
|
||||||
|
files.insert(INodeNo(1), make_dir_item(1, "Artist"));
|
||||||
|
files.insert(INodeNo(2), make_dir_item(2, "Album"));
|
||||||
|
files.insert(INodeNo(100), make_flac_item(100, "track.flac", None));
|
||||||
|
let files = Arc::new(Mutex::new(files));
|
||||||
|
let svc = ClientControlServiceImpl::new(files, mock_db_readonly());
|
||||||
|
|
||||||
|
let resp = svc
|
||||||
|
.list_files(Request::new(ListFilesRequest {}))
|
||||||
|
.await
|
||||||
|
.expect("list should succeed");
|
||||||
|
|
||||||
|
let inodes: Vec<u64> = resp
|
||||||
|
.into_inner()
|
||||||
|
.files
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| e.inode)
|
||||||
|
.collect();
|
||||||
|
assert_eq!(inodes, vec![100]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_returns_metadata_for_valid_inode() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
make_flac_item(100, "song.flac", Some(make_flac_metadata())),
|
||||||
|
);
|
||||||
|
let files = Arc::new(Mutex::new(files));
|
||||||
|
let svc = ClientControlServiceImpl::new(files, mock_db_readonly());
|
||||||
|
|
||||||
|
let resp = svc
|
||||||
|
.get_music_metadata(Request::new(GetMusicMetadataRequest { inode: 100 }))
|
||||||
|
.await
|
||||||
|
.expect("get should succeed");
|
||||||
|
|
||||||
|
let md = resp.into_inner().metadata.expect("metadata present");
|
||||||
|
assert_eq!(md.track_title, "Test Track");
|
||||||
|
assert_eq!(md.album, "Test Album");
|
||||||
|
assert_eq!(md.artist, vec!["Test Artist".to_string()]);
|
||||||
|
assert_eq!(md.track_number, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_returns_not_found_for_missing_inode() {
|
||||||
|
let files = Arc::new(Mutex::new(BTreeMap::new()));
|
||||||
|
let svc = ClientControlServiceImpl::new(files, mock_db_readonly());
|
||||||
|
|
||||||
|
let err = svc
|
||||||
|
.get_music_metadata(Request::new(GetMusicMetadataRequest { inode: 999 }))
|
||||||
|
.await
|
||||||
|
.expect_err("should be NOT_FOUND");
|
||||||
|
|
||||||
|
assert_eq!(err.code(), Code::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_returns_not_found_when_item_has_no_metadata() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(INodeNo(100), make_flac_item(100, "song.flac", None));
|
||||||
|
let files = Arc::new(Mutex::new(files));
|
||||||
|
let svc = ClientControlServiceImpl::new(files, mock_db_readonly());
|
||||||
|
|
||||||
|
let err = svc
|
||||||
|
.get_music_metadata(Request::new(GetMusicMetadataRequest { inode: 100 }))
|
||||||
|
.await
|
||||||
|
.expect_err("should be NOT_FOUND");
|
||||||
|
|
||||||
|
assert_eq!(err.code(), Code::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── UpdateMusicMetadata ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_changes_tags_and_response_reflects_new_values() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
make_flac_item(100, "song.flac", Some(make_flac_metadata())),
|
||||||
|
);
|
||||||
|
let files = Arc::new(Mutex::new(files));
|
||||||
|
let svc = ClientControlServiceImpl::new(files.clone(), mock_db_update_with_artist(100));
|
||||||
|
|
||||||
|
let resp = svc
|
||||||
|
.update_music_metadata(Request::new(UpdateMusicMetadataRequest {
|
||||||
|
inode: 100,
|
||||||
|
artist: vec!["Updated Artist".to_string()],
|
||||||
|
album_artist: Some("Updated Artist".to_string()),
|
||||||
|
album: "Updated Album".to_string(),
|
||||||
|
track_number: 7,
|
||||||
|
track_title: "Updated Title".to_string(),
|
||||||
|
other_tags: vec![],
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.expect("update should succeed");
|
||||||
|
|
||||||
|
let md = resp.into_inner().metadata.expect("metadata present");
|
||||||
|
assert_eq!(md.track_title, "Updated Title");
|
||||||
|
assert_eq!(md.album, "Updated Album");
|
||||||
|
assert_eq!(md.artist, vec!["Updated Artist".to_string()]);
|
||||||
|
assert_eq!(md.track_number, 7);
|
||||||
|
|
||||||
|
let guard = files.lock().unwrap();
|
||||||
|
let item = guard.get(&INodeNo(100)).expect("item still in map");
|
||||||
|
let mm = item.music_metadata.as_ref().expect("metadata present");
|
||||||
|
assert_eq!(mm.track_title, "Updated Title");
|
||||||
|
assert_eq!(mm.album, "Updated Album");
|
||||||
|
assert_eq!(mm.artist, vec!["Updated Artist".to_string()]);
|
||||||
|
assert_eq!(mm.track_number, 7);
|
||||||
|
|
||||||
|
// Layout was recomputed from the new metadata.
|
||||||
|
assert_eq!(item.name, "07 - Updated Title.flac");
|
||||||
|
assert_eq!(
|
||||||
|
item.local_path,
|
||||||
|
std::path::PathBuf::from("Updated Artist/Updated Album/07 - Updated Title.flac")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_returns_not_found_for_missing_inode() {
|
||||||
|
let files = Arc::new(Mutex::new(BTreeMap::new()));
|
||||||
|
let svc = ClientControlServiceImpl::new(files, mock_db_readonly());
|
||||||
|
|
||||||
|
let err = svc
|
||||||
|
.update_music_metadata(Request::new(UpdateMusicMetadataRequest {
|
||||||
|
inode: 999,
|
||||||
|
artist: vec![],
|
||||||
|
album_artist: None,
|
||||||
|
album: String::new(),
|
||||||
|
track_number: 0,
|
||||||
|
track_title: String::new(),
|
||||||
|
other_tags: vec![],
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.expect_err("should be NOT_FOUND");
|
||||||
|
|
||||||
|
assert_eq!(err.code(), Code::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_returns_not_found_when_item_has_no_metadata() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(INodeNo(100), make_flac_item(100, "song.flac", None));
|
||||||
|
let files = Arc::new(Mutex::new(files));
|
||||||
|
let svc = ClientControlServiceImpl::new(files, mock_db_readonly());
|
||||||
|
|
||||||
|
let err = svc
|
||||||
|
.update_music_metadata(Request::new(UpdateMusicMetadataRequest {
|
||||||
|
inode: 100,
|
||||||
|
artist: vec!["X".to_string()],
|
||||||
|
album_artist: None,
|
||||||
|
album: "X".to_string(),
|
||||||
|
track_number: 1,
|
||||||
|
track_title: "X".to_string(),
|
||||||
|
other_tags: vec![],
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.expect_err("should be NOT_FOUND");
|
||||||
|
|
||||||
|
assert_eq!(err.code(), Code::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_clears_tags_when_empty_values_sent() {
|
||||||
|
let mut files = BTreeMap::new();
|
||||||
|
files.insert(
|
||||||
|
INodeNo(100),
|
||||||
|
make_flac_item(100, "song.flac", Some(make_flac_metadata())),
|
||||||
|
);
|
||||||
|
let files = Arc::new(Mutex::new(files));
|
||||||
|
let svc = ClientControlServiceImpl::new(files.clone(), mock_db_update_no_artist(100));
|
||||||
|
|
||||||
|
let resp = svc
|
||||||
|
.update_music_metadata(Request::new(UpdateMusicMetadataRequest {
|
||||||
|
inode: 100,
|
||||||
|
artist: vec![],
|
||||||
|
album_artist: None,
|
||||||
|
album: String::new(),
|
||||||
|
track_number: 0,
|
||||||
|
track_title: String::new(),
|
||||||
|
other_tags: vec![],
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.expect("update should succeed");
|
||||||
|
|
||||||
|
let md = resp.into_inner().metadata.expect("metadata present");
|
||||||
|
assert!(md.artist.is_empty());
|
||||||
|
assert!(md.album.is_empty());
|
||||||
|
assert!(md.track_title.is_empty());
|
||||||
|
|
||||||
|
let guard = files.lock().unwrap();
|
||||||
|
let mm = guard
|
||||||
|
.get(&INodeNo(100))
|
||||||
|
.unwrap()
|
||||||
|
.music_metadata
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
|
assert!(mm.artist.is_empty());
|
||||||
|
assert!(mm.album.is_empty());
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "musicfs-core"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
symphonia.workspace = true
|
||||||
|
twox-hash.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
tracing-appender.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile.workspace = true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use std::hash::Hasher;
|
||||||
|
|
||||||
|
use twox_hash::XxHash64;
|
||||||
|
|
||||||
|
pub fn compute_item_hash(
|
||||||
|
inode: u64,
|
||||||
|
original_path: &[u8],
|
||||||
|
ctime_secs: u64,
|
||||||
|
mtime_secs: u64,
|
||||||
|
crtime_secs: u64,
|
||||||
|
) -> u64 {
|
||||||
|
let seed = 1234;
|
||||||
|
let mut hasher = XxHash64::with_seed(seed);
|
||||||
|
hasher.write_u64(inode);
|
||||||
|
hasher.write(original_path);
|
||||||
|
hasher.write_u64(ctime_secs);
|
||||||
|
hasher.write_u64(mtime_secs);
|
||||||
|
hasher.write_u64(crtime_secs);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
pub mod attrs;
|
||||||
|
pub mod hash;
|
||||||
|
pub mod logging;
|
||||||
|
pub mod music;
|
||||||
|
|
||||||
|
pub use attrs::FileAttrs;
|
||||||
|
pub use hash::compute_item_hash;
|
||||||
|
pub use music::metadata::MusicMetadata;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use symphonia::core::meta::{MetadataRevision, StandardTagKey};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
|
pub struct MusicMetadata {
|
||||||
|
pub artist: Vec<String>,
|
||||||
|
pub album_artist: Option<String>,
|
||||||
|
pub album: String,
|
||||||
|
pub track_number: i32,
|
||||||
|
pub track_title: String,
|
||||||
|
pub other_tags: Vec<String>,
|
||||||
|
pub header: Vec<u8>,
|
||||||
|
pub picture_block_headers: Vec<Vec<u8>>,
|
||||||
|
pub picture_data_ranges: Vec<(u64, u64)>,
|
||||||
|
pub real_audio_start: u64,
|
||||||
|
pub vorbis_comment_offset: u64,
|
||||||
|
pub vorbis_comment_length: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MusicMetadata {
|
||||||
|
pub fn virtual_size(&self, real_file_size: u64) -> u64 {
|
||||||
|
let pictures_size: u64 = self
|
||||||
|
.picture_block_headers
|
||||||
|
.iter()
|
||||||
|
.zip(self.picture_data_ranges.iter())
|
||||||
|
.map(|(prefix, (_, len))| prefix.len() as u64 + len)
|
||||||
|
.sum();
|
||||||
|
self.header.len() as u64 + pictures_size + (real_file_size - self.real_audio_start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn extract_standard_tags(revision: &MetadataRevision, out: &mut MusicMetadata) {
|
||||||
|
for tag in revision.tags() {
|
||||||
|
let value = tag.value.to_string();
|
||||||
|
match tag.std_key {
|
||||||
|
Some(StandardTagKey::Artist) => out.artist.push(value),
|
||||||
|
Some(StandardTagKey::AlbumArtist) => out.album_artist = Some(value),
|
||||||
|
Some(StandardTagKey::Album) => out.album = value,
|
||||||
|
Some(StandardTagKey::TrackNumber) => {
|
||||||
|
out.track_number = value.parse::<i32>().unwrap_or(0)
|
||||||
|
}
|
||||||
|
Some(StandardTagKey::TrackTitle) => out.track_title = value,
|
||||||
|
_ => out.other_tags.push(format!("{}={}", tag.key, value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn virtual_size_calculation() {
|
||||||
|
let mut metadata = MusicMetadata::default();
|
||||||
|
metadata.header = vec![0u8; 100];
|
||||||
|
metadata.picture_block_headers = vec![vec![0u8; 4]];
|
||||||
|
metadata.picture_data_ranges = vec![(0, 50)];
|
||||||
|
metadata.real_audio_start = 200;
|
||||||
|
let file_size = 1000u64;
|
||||||
|
let virtual_size = metadata.virtual_size(file_size);
|
||||||
|
assert_eq!(virtual_size, 954);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
pub mod db;
|
|
||||||
pub mod encoder;
|
pub mod encoder;
|
||||||
pub mod flac;
|
pub mod flac;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "musicfs-proto"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
prost.workspace = true
|
||||||
|
tonic.workspace = true
|
||||||
|
tonic-prost.workspace = true
|
||||||
@@ -0,0 +1,835 @@
|
|||||||
|
// @generated
|
||||||
|
// This file is @generated by prost-build.
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct ReconcileRequest {
|
||||||
|
#[prost(message, repeated, tag = "1")]
|
||||||
|
pub entries: ::prost::alloc::vec::Vec<InodeHash>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct ReconcileResponse {
|
||||||
|
#[prost(message, repeated, tag = "1")]
|
||||||
|
pub changed: ::prost::alloc::vec::Vec<InodeHash>,
|
||||||
|
#[prost(uint64, repeated, tag = "2")]
|
||||||
|
pub deleted: ::prost::alloc::vec::Vec<u64>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct InodeHash {
|
||||||
|
#[prost(uint64, tag = "1")]
|
||||||
|
pub inode: u64,
|
||||||
|
#[prost(uint64, tag = "2")]
|
||||||
|
pub hash: u64,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct GetMetadataRequest {
|
||||||
|
#[prost(uint64, repeated, tag = "1")]
|
||||||
|
pub inodes: ::prost::alloc::vec::Vec<u64>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct GetManifestRequest {}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct GetFileRequest {
|
||||||
|
#[prost(uint64, tag = "1")]
|
||||||
|
pub id: u64,
|
||||||
|
/// When both are zero the server streams the entire file.
|
||||||
|
#[prost(uint64, tag = "2")]
|
||||||
|
pub start: u64,
|
||||||
|
#[prost(uint64, tag = "3")]
|
||||||
|
pub length: u64,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct SubscribeEventsRequest {}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct ManifestEntry {
|
||||||
|
#[prost(uint64, tag = "1")]
|
||||||
|
pub id: u64,
|
||||||
|
#[prost(string, tag = "2")]
|
||||||
|
pub rel_path: ::prost::alloc::string::String,
|
||||||
|
#[prost(uint64, tag = "3")]
|
||||||
|
pub size: u64,
|
||||||
|
#[prost(uint64, tag = "4")]
|
||||||
|
pub mtime: u64,
|
||||||
|
#[prost(uint64, tag = "5")]
|
||||||
|
pub ctime: u64,
|
||||||
|
#[prost(uint64, tag = "6")]
|
||||||
|
pub crtime: u64,
|
||||||
|
#[prost(message, optional, tag = "7")]
|
||||||
|
pub music_metadata: ::core::option::Option<MusicMetadata>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct MusicMetadata {
|
||||||
|
#[prost(string, repeated, tag = "1")]
|
||||||
|
pub artist: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||||
|
#[prost(string, optional, tag = "2")]
|
||||||
|
pub album_artist: ::core::option::Option<::prost::alloc::string::String>,
|
||||||
|
#[prost(string, tag = "3")]
|
||||||
|
pub album: ::prost::alloc::string::String,
|
||||||
|
#[prost(int32, tag = "4")]
|
||||||
|
pub track_number: i32,
|
||||||
|
#[prost(string, tag = "5")]
|
||||||
|
pub track_title: ::prost::alloc::string::String,
|
||||||
|
#[prost(string, repeated, tag = "6")]
|
||||||
|
pub other_tags: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||||
|
#[prost(bytes = "vec", tag = "7")]
|
||||||
|
pub header: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(bytes = "vec", repeated, tag = "8")]
|
||||||
|
pub picture_block_headers: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
|
||||||
|
#[prost(message, repeated, tag = "9")]
|
||||||
|
pub picture_data_ranges: ::prost::alloc::vec::Vec<PictureDataRange>,
|
||||||
|
#[prost(uint64, tag = "10")]
|
||||||
|
pub real_audio_start: u64,
|
||||||
|
#[prost(uint64, tag = "11")]
|
||||||
|
pub vorbis_comment_offset: u64,
|
||||||
|
#[prost(uint64, tag = "12")]
|
||||||
|
pub vorbis_comment_length: u64,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct PictureDataRange {
|
||||||
|
#[prost(uint64, tag = "1")]
|
||||||
|
pub offset: u64,
|
||||||
|
#[prost(uint64, tag = "2")]
|
||||||
|
pub length: u64,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct FileChunk {
|
||||||
|
#[prost(bytes = "vec", tag = "1")]
|
||||||
|
pub data: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(uint64, tag = "2")]
|
||||||
|
pub offset: u64,
|
||||||
|
#[prost(uint64, tag = "3")]
|
||||||
|
pub total_size: u64,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct ChangeEvent {
|
||||||
|
/// "create", "modify", or "remove".
|
||||||
|
#[prost(string, tag = "1")]
|
||||||
|
pub kind: ::prost::alloc::string::String,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct GetClientStatusRequest {}
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct ClientStatusResponse {
|
||||||
|
#[prost(string, tag = "1")]
|
||||||
|
pub origin_type: ::prost::alloc::string::String,
|
||||||
|
#[prost(string, tag = "2")]
|
||||||
|
pub mountpoint: ::prost::alloc::string::String,
|
||||||
|
#[prost(uint64, tag = "3")]
|
||||||
|
pub file_count: u64,
|
||||||
|
#[prost(bool, tag = "4")]
|
||||||
|
pub server_connected: bool,
|
||||||
|
#[prost(string, tag = "5")]
|
||||||
|
pub server_endpoint: ::prost::alloc::string::String,
|
||||||
|
#[prost(int64, tag = "6")]
|
||||||
|
pub last_reconcile_unix: i64,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct GetMusicMetadataRequest {
|
||||||
|
#[prost(uint64, tag = "1")]
|
||||||
|
pub inode: u64,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct GetMusicMetadataResponse {
|
||||||
|
#[prost(message, optional, tag = "1")]
|
||||||
|
pub metadata: ::core::option::Option<MusicMetadata>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct UpdateMusicMetadataRequest {
|
||||||
|
#[prost(uint64, tag = "1")]
|
||||||
|
pub inode: u64,
|
||||||
|
#[prost(string, repeated, tag = "2")]
|
||||||
|
pub artist: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||||
|
#[prost(string, optional, tag = "3")]
|
||||||
|
pub album_artist: ::core::option::Option<::prost::alloc::string::String>,
|
||||||
|
#[prost(string, tag = "4")]
|
||||||
|
pub album: ::prost::alloc::string::String,
|
||||||
|
#[prost(int32, tag = "5")]
|
||||||
|
pub track_number: i32,
|
||||||
|
#[prost(string, tag = "6")]
|
||||||
|
pub track_title: ::prost::alloc::string::String,
|
||||||
|
#[prost(string, repeated, tag = "7")]
|
||||||
|
pub other_tags: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct UpdateMusicMetadataResponse {
|
||||||
|
#[prost(message, optional, tag = "1")]
|
||||||
|
pub metadata: ::core::option::Option<MusicMetadata>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct ListFilesRequest {}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct ListFilesResponse {
|
||||||
|
#[prost(message, repeated, tag = "1")]
|
||||||
|
pub files: ::prost::alloc::vec::Vec<FileEntry>,
|
||||||
|
}
|
||||||
|
/// Human-readable tag fields only. Excludes the binary layout fields
|
||||||
|
/// (header, picture_block_headers, etc.) that only make sense inside
|
||||||
|
/// musicfs's FUSE read-assembly path.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct FileMetadata {
|
||||||
|
#[prost(string, repeated, tag = "1")]
|
||||||
|
pub artist: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||||
|
#[prost(string, optional, tag = "2")]
|
||||||
|
pub album_artist: ::core::option::Option<::prost::alloc::string::String>,
|
||||||
|
#[prost(string, tag = "3")]
|
||||||
|
pub album: ::prost::alloc::string::String,
|
||||||
|
#[prost(int32, tag = "4")]
|
||||||
|
pub track_number: i32,
|
||||||
|
#[prost(string, tag = "5")]
|
||||||
|
pub track_title: ::prost::alloc::string::String,
|
||||||
|
#[prost(string, repeated, tag = "6")]
|
||||||
|
pub other_tags: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct FileEntry {
|
||||||
|
#[prost(uint64, tag = "1")]
|
||||||
|
pub inode: u64,
|
||||||
|
/// Real on-disk path (where tora/librqbit wrote the file).
|
||||||
|
#[prost(string, tag = "2")]
|
||||||
|
pub original_path: ::prost::alloc::string::String,
|
||||||
|
/// Virtual FUSE path ({artist}/{album}/{filename}); updated when metadata
|
||||||
|
/// changes via `UpdateMusicMetadata`.
|
||||||
|
#[prost(string, tag = "3")]
|
||||||
|
pub local_path: ::prost::alloc::string::String,
|
||||||
|
/// Filename component only.
|
||||||
|
#[prost(string, tag = "4")]
|
||||||
|
pub name: ::prost::alloc::string::String,
|
||||||
|
/// Empty if the file has no parsed tags.
|
||||||
|
#[prost(message, optional, tag = "5")]
|
||||||
|
pub metadata: ::core::option::Option<FileMetadata>,
|
||||||
|
}
|
||||||
|
/// Encoded file descriptor set for the `musicfs` package
|
||||||
|
pub const FILE_DESCRIPTOR_SET: &[u8] = &[
|
||||||
|
0x0a, 0xee, 0x4e, 0x0a, 0x0d, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x12, 0x07, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x22, 0x40, 0x0a, 0x10, 0x52,
|
||||||
|
0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||||
|
0x2c, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
||||||
|
0x32, 0x12, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x49, 0x6e, 0x6f, 0x64, 0x65,
|
||||||
|
0x48, 0x61, 0x73, 0x68, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x5b, 0x0a,
|
||||||
|
0x11, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
|
0x73, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x01, 0x20,
|
||||||
|
0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x49, 0x6e,
|
||||||
|
0x6f, 0x64, 0x65, 0x48, 0x61, 0x73, 0x68, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64,
|
||||||
|
0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||||
|
0x04, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x09, 0x49, 0x6e,
|
||||||
|
0x6f, 0x64, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x6f, 0x64, 0x65,
|
||||||
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a,
|
||||||
|
0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x68, 0x61, 0x73,
|
||||||
|
0x68, 0x22, 0x2c, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x6f, 0x64, 0x65,
|
||||||
|
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x06, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22,
|
||||||
|
0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4e, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||||
|
0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74,
|
||||||
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x0a,
|
||||||
|
0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c,
|
||||||
|
0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x18, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||||
|
0x62, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
|
||||||
|
0xd1, 0x01, 0x0a, 0x0d, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72,
|
||||||
|
0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69,
|
||||||
|
0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04,
|
||||||
|
0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65,
|
||||||
|
0x12, 0x14, 0x0a, 0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||||
|
0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, 0x18,
|
||||||
|
0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
||||||
|
0x63, 0x72, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x72,
|
||||||
|
0x74, 0x69, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x0e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x5f, 0x6d, 0x65,
|
||||||
|
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d,
|
||||||
|
0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74, 0x61,
|
||||||
|
0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x64,
|
||||||
|
0x61, 0x74, 0x61, 0x22, 0x82, 0x04, 0x0a, 0x0d, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74,
|
||||||
|
0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x18,
|
||||||
|
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a,
|
||||||
|
0x0c, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x41, 0x72, 0x74, 0x69,
|
||||||
|
0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x18, 0x03,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x74,
|
||||||
|
0x72, 0x61, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||||
|
0x05, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1f,
|
||||||
|
0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12,
|
||||||
|
0x1d, 0x0a, 0x0a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x06, 0x20,
|
||||||
|
0x03, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x12, 0x16,
|
||||||
|
0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72,
|
||||||
|
0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18,
|
||||||
|
0x08, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x13, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x42, 0x6c,
|
||||||
|
0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x49, 0x0a, 0x13, 0x70, 0x69,
|
||||||
|
0x63, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65,
|
||||||
|
0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66,
|
||||||
|
0x73, 0x2e, 0x50, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x61, 0x6e,
|
||||||
|
0x67, 0x65, 0x52, 0x11, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52,
|
||||||
|
0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x61, 0x75,
|
||||||
|
0x64, 0x69, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||||
|
0x0e, 0x72, 0x65, 0x61, 0x6c, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12,
|
||||||
|
0x32, 0x0a, 0x15, 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e,
|
||||||
|
0x74, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13,
|
||||||
|
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x66, 0x66,
|
||||||
|
0x73, 0x65, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x5f, 0x63, 0x6f,
|
||||||
|
0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01,
|
||||||
|
0x28, 0x04, 0x52, 0x13, 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e,
|
||||||
|
0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x61, 0x6c, 0x62, 0x75,
|
||||||
|
0x6d, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x22, 0x42, 0x0a, 0x10, 0x50, 0x69, 0x63, 0x74,
|
||||||
|
0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
||||||
|
0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66,
|
||||||
|
0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x02,
|
||||||
|
0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x56, 0x0a, 0x09,
|
||||||
|
0x46, 0x69, 0x6c, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74,
|
||||||
|
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a,
|
||||||
|
0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f,
|
||||||
|
0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73,
|
||||||
|
0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c,
|
||||||
|
0x53, 0x69, 0x7a, 0x65, 0x22, 0x21, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x76,
|
||||||
|
0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
|
0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43, 0x6c,
|
||||||
|
0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
|
0x74, 0x22, 0xfa, 0x01, 0x0a, 0x14, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74,
|
||||||
|
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72,
|
||||||
|
0x69, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6d,
|
||||||
|
0x6f, 0x75, 0x6e, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x66,
|
||||||
|
0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||||
|
0x09, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x65,
|
||||||
|
0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04,
|
||||||
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6e,
|
||||||
|
0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
|
||||||
|
0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e,
|
||||||
|
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2e,
|
||||||
|
0x0a, 0x13, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65,
|
||||||
|
0x5f, 0x75, 0x6e, 0x69, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x6c, 0x61, 0x73,
|
||||||
|
0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x22, 0x2f,
|
||||||
|
0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||||
|
0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x6f,
|
||||||
|
0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x22,
|
||||||
|
0x4e, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x64,
|
||||||
|
0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x6d,
|
||||||
|
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
|
||||||
|
0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74,
|
||||||
|
0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22,
|
||||||
|
0xfc, 0x01, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d,
|
||||||
|
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14,
|
||||||
|
0x0a, 0x05, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69,
|
||||||
|
0x6e, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x18, 0x02,
|
||||||
|
0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0c,
|
||||||
|
0x61, 0x6c, 0x62, 0x75, 0x6d, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01,
|
||||||
|
0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x41, 0x72, 0x74, 0x69, 0x73,
|
||||||
|
0x74, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x18, 0x04, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x72,
|
||||||
|
0x61, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05,
|
||||||
|
0x52, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1f, 0x0a,
|
||||||
|
0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01,
|
||||||
|
0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1d,
|
||||||
|
0x0a, 0x0a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03,
|
||||||
|
0x28, 0x09, 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x42, 0x0f, 0x0a,
|
||||||
|
0x0d, 0x5f, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x22, 0x51,
|
||||||
|
0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74,
|
||||||
|
0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a,
|
||||||
|
0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
|
0x16, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d,
|
||||||
|
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||||
|
0x61, 0x22, 0x12, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3d, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x6c,
|
||||||
|
0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x66, 0x69,
|
||||||
|
0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x75, 0x73, 0x69,
|
||||||
|
0x63, 0x66, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x66,
|
||||||
|
0x69, 0x6c, 0x65, 0x73, 0x22, 0xd8, 0x01, 0x0a, 0x0c, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74,
|
||||||
|
0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x18,
|
||||||
|
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a,
|
||||||
|
0x0c, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x41, 0x72, 0x74, 0x69,
|
||||||
|
0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x18, 0x03,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x74,
|
||||||
|
0x72, 0x61, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||||
|
0x05, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1f,
|
||||||
|
0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12,
|
||||||
|
0x1d, 0x0a, 0x0a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x06, 0x20,
|
||||||
|
0x03, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x42, 0x0f,
|
||||||
|
0x0a, 0x0d, 0x5f, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x22,
|
||||||
|
0xbe, 0x01, 0x0a, 0x09, 0x46, 0x69, 0x6c, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x14, 0x0a,
|
||||||
|
0x05, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e,
|
||||||
|
0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f,
|
||||||
|
0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x72, 0x69, 0x67,
|
||||||
|
0x69, 0x6e, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61,
|
||||||
|
0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x6f,
|
||||||
|
0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
|
||||||
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x08, 0x6d,
|
||||||
|
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,
|
||||||
|
0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61,
|
||||||
|
0x64, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
||||||
|
0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
||||||
|
0x32, 0xdf, 0x02, 0x0a, 0x07, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x46, 0x73, 0x12, 0x42, 0x0a, 0x09,
|
||||||
|
0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x2e, 0x6d, 0x75, 0x73, 0x69,
|
||||||
|
0x63, 0x66, 0x73, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x52,
|
||||||
|
0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
|
0x12, 0x44, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
|
||||||
|
0x1b, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74,
|
||||||
|
0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6d,
|
||||||
|
0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x45,
|
||||||
|
0x6e, 0x74, 0x72, 0x79, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e,
|
||||||
|
0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e,
|
||||||
|
0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
|
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x4d, 0x61, 0x6e,
|
||||||
|
0x69, 0x66, 0x65, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07,
|
||||||
|
0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x17, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66,
|
||||||
|
0x73, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
|
0x1a, 0x12, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x43,
|
||||||
|
0x68, 0x75, 0x6e, 0x6b, 0x30, 0x01, 0x12, 0x4a, 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
|
||||||
|
0x69, 0x62, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6d, 0x75, 0x73, 0x69,
|
||||||
|
0x63, 0x66, 0x73, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x45, 0x76, 0x65,
|
||||||
|
0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6d, 0x75, 0x73,
|
||||||
|
0x69, 0x63, 0x66, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74,
|
||||||
|
0x30, 0x01, 0x32, 0x61, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74,
|
||||||
|
0x75, 0x73, 0x12, 0x51, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53,
|
||||||
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e,
|
||||||
|
0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
|
||||||
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73,
|
||||||
|
0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8e, 0x02, 0x0a, 0x0d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||||
|
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x57, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x75,
|
||||||
|
0x73, 0x69, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x20, 0x2e, 0x6d, 0x75,
|
||||||
|
0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65,
|
||||||
|
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e,
|
||||||
|
0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x75, 0x73, 0x69, 0x63,
|
||||||
|
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
|
0x12, 0x60, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d,
|
||||||
|
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66,
|
||||||
|
0x73, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74,
|
||||||
|
0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d,
|
||||||
|
0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x75, 0x73,
|
||||||
|
0x69, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
|
0x73, 0x65, 0x12, 0x42, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12,
|
||||||
|
0x19, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69,
|
||||||
|
0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6d, 0x75, 0x73,
|
||||||
|
0x69, 0x63, 0x66, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65,
|
||||||
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4a, 0x80, 0x35, 0x0a, 0x07, 0x12, 0x05, 0x00, 0x00, 0xb4,
|
||||||
|
0x01, 0x01, 0x0a, 0x08, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x00, 0x00, 0x12, 0x0a, 0x08, 0x0a, 0x01,
|
||||||
|
0x02, 0x12, 0x03, 0x02, 0x00, 0x10, 0x0a, 0x62, 0x0a, 0x02, 0x06, 0x00, 0x12, 0x04, 0x06, 0x00,
|
||||||
|
0x20, 0x01, 0x1a, 0x56, 0x20, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x46, 0x73, 0x20, 0x73, 0x65, 0x72,
|
||||||
|
0x76, 0x69, 0x63, 0x65, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73,
|
||||||
|
0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x73, 0x69, 0x64, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x69, 0x63,
|
||||||
|
0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x72, 0x65,
|
||||||
|
0x6d, 0x6f, 0x74, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x0a, 0x20, 0x46, 0x55,
|
||||||
|
0x53, 0x45, 0x20, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x06, 0x00,
|
||||||
|
0x01, 0x12, 0x03, 0x06, 0x08, 0x0f, 0x0a, 0xac, 0x02, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x00, 0x12,
|
||||||
|
0x03, 0x0b, 0x02, 0x3e, 0x1a, 0x9e, 0x02, 0x20, 0x48, 0x61, 0x73, 0x68, 0x2d, 0x62, 0x61, 0x73,
|
||||||
|
0x65, 0x64, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x69, 0x61, 0x74, 0x69, 0x6f,
|
||||||
|
0x6e, 0x2e, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x73, 0x20,
|
||||||
|
0x74, 0x68, 0x65, 0x20, 0x28, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x68, 0x61, 0x73, 0x68,
|
||||||
|
0x29, 0x20, 0x70, 0x61, 0x69, 0x72, 0x73, 0x20, 0x69, 0x74, 0x0a, 0x20, 0x63, 0x75, 0x72, 0x72,
|
||||||
|
0x65, 0x6e, 0x74, 0x6c, 0x79, 0x20, 0x68, 0x61, 0x73, 0x3b, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65,
|
||||||
|
0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20,
|
||||||
|
0x74, 0x68, 0x65, 0x20, 0x73, 0x75, 0x62, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20,
|
||||||
|
0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x20, 0x28, 0x6e, 0x65, 0x77, 0x20, 0x6f, 0x72, 0x0a,
|
||||||
|
0x20, 0x68, 0x61, 0x73, 0x68, 0x2d, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x29,
|
||||||
|
0x20, 0x70, 0x6c, 0x75, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x73,
|
||||||
|
0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x68, 0x61, 0x73, 0x20,
|
||||||
|
0x62, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x6e,
|
||||||
|
0x6f, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x0a, 0x20, 0x68, 0x61, 0x73, 0x2e, 0x20, 0x43,
|
||||||
|
0x68, 0x65, 0x61, 0x70, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x69, 0x72, 0x65,
|
||||||
|
0x20, 0xe2, 0x80, 0x94, 0x20, 0x6e, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6d, 0x65, 0x74,
|
||||||
|
0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x65, 0x73, 0x20, 0x75, 0x6e,
|
||||||
|
0x74, 0x69, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x61,
|
||||||
|
0x73, 0x6b, 0x73, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03,
|
||||||
|
0x0b, 0x06, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x0b, 0x10,
|
||||||
|
0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0b, 0x2b, 0x3c, 0x0a,
|
||||||
|
0x94, 0x01, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x01, 0x12, 0x03, 0x0f, 0x02, 0x45, 0x1a, 0x86, 0x01,
|
||||||
|
0x20, 0x46, 0x65, 0x74, 0x63, 0x68, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x6d, 0x65, 0x74, 0x61,
|
||||||
|
0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69,
|
||||||
|
0x66, 0x69, 0x63, 0x20, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x6e, 0x6f, 0x64, 0x65,
|
||||||
|
0x73, 0x2e, 0x20, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20,
|
||||||
|
0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x0a, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72,
|
||||||
|
0x6e, 0x73, 0x20, 0x61, 0x20, 0x6e, 0x6f, 0x6e, 0x2d, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x60,
|
||||||
|
0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x60, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x2c, 0x20, 0x66,
|
||||||
|
0x6f, 0x72, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, 0x20, 0x69, 0x6e,
|
||||||
|
0x6f, 0x64, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x01, 0x12,
|
||||||
|
0x03, 0x0f, 0x06, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, 0x0f,
|
||||||
|
0x12, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x06, 0x12, 0x03, 0x0f, 0x2f, 0x35,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x0f, 0x36, 0x43, 0x0a, 0xa8,
|
||||||
|
0x01, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x02, 0x12, 0x03, 0x14, 0x02, 0x45, 0x1a, 0x9a, 0x01, 0x20,
|
||||||
|
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x65, 0x76, 0x65, 0x72, 0x79, 0x20, 0x66, 0x69, 0x6c,
|
||||||
|
0x65, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c,
|
||||||
|
0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65,
|
||||||
|
0x6e, 0x63, 0x65, 0x20, 0x52, 0x50, 0x43, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x66, 0x69, 0x72, 0x73,
|
||||||
|
0x74, 0x2d, 0x72, 0x75, 0x6e, 0x0a, 0x20, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70,
|
||||||
|
0x70, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x72, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x2d, 0x72, 0x65, 0x66,
|
||||||
|
0x72, 0x65, 0x73, 0x68, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x64, 0x69, 0x66, 0x66, 0x2d, 0x62,
|
||||||
|
0x61, 0x73, 0x65, 0x64, 0x20, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x20, 0x70,
|
||||||
|
0x61, 0x74, 0x68, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x6e, 0x6f, 0x72, 0x6d,
|
||||||
|
0x61, 0x6c, 0x20, 0x63, 0x61, 0x73, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02,
|
||||||
|
0x02, 0x01, 0x12, 0x03, 0x14, 0x06, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x02,
|
||||||
|
0x12, 0x03, 0x14, 0x12, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x06, 0x12, 0x03,
|
||||||
|
0x14, 0x2f, 0x35, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x14, 0x36,
|
||||||
|
0x43, 0x0a, 0xf8, 0x01, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x03, 0x12, 0x03, 0x1a, 0x02, 0x39, 0x1a,
|
||||||
|
0xea, 0x01, 0x20, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x79,
|
||||||
|
0x74, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e,
|
||||||
|
0x20, 0x49, 0x66, 0x20, 0x60, 0x73, 0x74, 0x61, 0x72, 0x74, 0x60, 0x20, 0x61, 0x6e, 0x64, 0x20,
|
||||||
|
0x60, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x60, 0x20, 0x61, 0x72, 0x65, 0x20, 0x62, 0x6f, 0x74,
|
||||||
|
0x68, 0x20, 0x7a, 0x65, 0x72, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x73, 0x65, 0x72, 0x76,
|
||||||
|
0x65, 0x72, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65,
|
||||||
|
0x6e, 0x74, 0x69, 0x72, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x3b, 0x20, 0x6f, 0x74, 0x68, 0x65,
|
||||||
|
0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72,
|
||||||
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x20,
|
||||||
|
0x54, 0x68, 0x65, 0x0a, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b,
|
||||||
|
0x20, 0x63, 0x61, 0x72, 0x72, 0x69, 0x65, 0x73, 0x20, 0x60, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
|
||||||
|
0x73, 0x69, 0x7a, 0x65, 0x60, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x60, 0x6f, 0x66, 0x66, 0x73, 0x65,
|
||||||
|
0x74, 0x60, 0x3b, 0x20, 0x73, 0x75, 0x62, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x20, 0x63,
|
||||||
|
0x68, 0x75, 0x6e, 0x6b, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x0a, 0x20,
|
||||||
|
0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05,
|
||||||
|
0x06, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x1a, 0x06, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00,
|
||||||
|
0x02, 0x03, 0x02, 0x12, 0x03, 0x1a, 0x0e, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x03,
|
||||||
|
0x06, 0x12, 0x03, 0x1a, 0x27, 0x2d, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x03, 0x03, 0x12,
|
||||||
|
0x03, 0x1a, 0x2e, 0x37, 0x0a, 0xc2, 0x01, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x04, 0x12, 0x03, 0x1f,
|
||||||
|
0x02, 0x4b, 0x1a, 0xb4, 0x01, 0x20, 0x4c, 0x6f, 0x6e, 0x67, 0x2d, 0x6c, 0x69, 0x76, 0x65, 0x64,
|
||||||
|
0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x6f, 0x66, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73,
|
||||||
|
0x79, 0x73, 0x74, 0x65, 0x6d, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x65, 0x76, 0x65,
|
||||||
|
0x6e, 0x74, 0x73, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20,
|
||||||
|
0x75, 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, 0x20, 0x61, 0x73, 0x0a, 0x20, 0x77,
|
||||||
|
0x61, 0x6b, 0x65, 0x2d, 0x75, 0x70, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, 0x66, 0x65, 0x74,
|
||||||
|
0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x3b,
|
||||||
|
0x20, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6c, 0x77,
|
||||||
|
0x61, 0x79, 0x73, 0x20, 0x72, 0x65, 0x73, 0x74, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65,
|
||||||
|
0x20, 0x68, 0x61, 0x73, 0x68, 0x0a, 0x20, 0x64, 0x69, 0x66, 0x66, 0x2c, 0x20, 0x6e, 0x65, 0x76,
|
||||||
|
0x65, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20,
|
||||||
|
0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02,
|
||||||
|
0x04, 0x01, 0x12, 0x03, 0x1f, 0x06, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x02,
|
||||||
|
0x12, 0x03, 0x1f, 0x16, 0x2c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x06, 0x12, 0x03,
|
||||||
|
0x1f, 0x37, 0x3d, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x03, 0x12, 0x03, 0x1f, 0x3e,
|
||||||
|
0x49, 0x0a, 0x4f, 0x0a, 0x02, 0x06, 0x01, 0x12, 0x04, 0x23, 0x00, 0x25, 0x01, 0x1a, 0x43, 0x20,
|
||||||
|
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x64, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74,
|
||||||
|
0x75, 0x73, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x28, 0x73, 0x65,
|
||||||
|
0x72, 0x76, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x46, 0x55, 0x53, 0x45,
|
||||||
|
0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x29,
|
||||||
|
0x2e, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x06, 0x01, 0x01, 0x12, 0x03, 0x23, 0x08, 0x14, 0x0a, 0x0b,
|
||||||
|
0x0a, 0x04, 0x06, 0x01, 0x02, 0x00, 0x12, 0x03, 0x24, 0x02, 0x4d, 0x0a, 0x0c, 0x0a, 0x05, 0x06,
|
||||||
|
0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x24, 0x06, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x01, 0x02,
|
||||||
|
0x00, 0x02, 0x12, 0x03, 0x24, 0x16, 0x2c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x01, 0x02, 0x00, 0x03,
|
||||||
|
0x12, 0x03, 0x24, 0x37, 0x4b, 0x0a, 0x0a, 0x0a, 0x02, 0x06, 0x02, 0x12, 0x04, 0x27, 0x00, 0x2e,
|
||||||
|
0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x06, 0x02, 0x01, 0x12, 0x03, 0x27, 0x08, 0x15, 0x0a, 0x0b, 0x0a,
|
||||||
|
0x04, 0x06, 0x02, 0x02, 0x00, 0x12, 0x03, 0x28, 0x02, 0x53, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x02,
|
||||||
|
0x02, 0x00, 0x01, 0x12, 0x03, 0x28, 0x06, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x02, 0x02, 0x00,
|
||||||
|
0x02, 0x12, 0x03, 0x28, 0x17, 0x2e, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x02, 0x02, 0x00, 0x03, 0x12,
|
||||||
|
0x03, 0x28, 0x39, 0x51, 0x0a, 0x0b, 0x0a, 0x04, 0x06, 0x02, 0x02, 0x01, 0x12, 0x03, 0x29, 0x02,
|
||||||
|
0x5c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, 0x29, 0x06, 0x19, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x05, 0x06, 0x02, 0x02, 0x01, 0x02, 0x12, 0x03, 0x29, 0x1a, 0x34, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x05, 0x06, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x29, 0x3f, 0x5a, 0x0a, 0xb4, 0x01, 0x0a, 0x04,
|
||||||
|
0x06, 0x02, 0x02, 0x02, 0x12, 0x03, 0x2d, 0x02, 0x3e, 0x1a, 0xa6, 0x01, 0x20, 0x45, 0x76, 0x65,
|
||||||
|
0x72, 0x79, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x20,
|
||||||
|
0x6b, 0x6e, 0x6f, 0x77, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x2e, 0x20, 0x43, 0x61, 0x6c,
|
||||||
|
0x6c, 0x65, 0x72, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x20, 0x62, 0x79, 0x20, 0x60,
|
||||||
|
0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x60, 0x0a, 0x20,
|
||||||
|
0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x74,
|
||||||
|
0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x6e, 0x74, 0x65,
|
||||||
|
0x72, 0x65, 0x73, 0x74, 0x20, 0x28, 0x65, 0x2e, 0x67, 0x2e, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x66,
|
||||||
|
0x69, 0x6c, 0x65, 0x73, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e,
|
||||||
|
0x67, 0x6c, 0x65, 0x0a, 0x20, 0x74, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x27, 0x73, 0x20, 0x6f,
|
||||||
|
0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x29,
|
||||||
|
0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x02, 0x02, 0x02, 0x01, 0x12, 0x03, 0x2d, 0x06, 0x0f,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x02, 0x02, 0x02, 0x02, 0x12, 0x03, 0x2d, 0x10, 0x20, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x06, 0x02, 0x02, 0x02, 0x03, 0x12, 0x03, 0x2d, 0x2b, 0x3c, 0x0a, 0x0a, 0x0a, 0x02,
|
||||||
|
0x04, 0x00, 0x12, 0x04, 0x30, 0x00, 0x32, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12,
|
||||||
|
0x03, 0x30, 0x08, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x31,
|
||||||
|
0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x31, 0x02, 0x21, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x31, 0x0b, 0x14, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x31, 0x15, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
||||||
|
0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x31, 0x1f, 0x20, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x01, 0x12,
|
||||||
|
0x04, 0x34, 0x00, 0x37, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x34, 0x08,
|
||||||
|
0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, 0x35, 0x02, 0x0a, 0x0a,
|
||||||
|
0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x35, 0x02, 0x21, 0x0a, 0x0c, 0x0a, 0x05,
|
||||||
|
0x04, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x35, 0x0b, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01,
|
||||||
|
0x02, 0x00, 0x01, 0x12, 0x03, 0x35, 0x15, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00,
|
||||||
|
0x03, 0x12, 0x03, 0x35, 0x1f, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x04, 0x12,
|
||||||
|
0x03, 0x36, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x36, 0x02,
|
||||||
|
0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x05, 0x12, 0x03, 0x36, 0x0b, 0x11, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x36, 0x12, 0x19, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x36, 0x1c, 0x1d, 0x0a, 0x0a, 0x0a, 0x02, 0x04,
|
||||||
|
0x02, 0x12, 0x04, 0x39, 0x00, 0x3c, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, 0x03,
|
||||||
|
0x39, 0x08, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x05, 0x12, 0x03, 0x3a, 0x02,
|
||||||
|
0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x00, 0x12, 0x03, 0x3a, 0x02, 0x13, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x3a, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05,
|
||||||
|
0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x3a, 0x11, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02,
|
||||||
|
0x02, 0x01, 0x05, 0x12, 0x03, 0x3b, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01,
|
||||||
|
0x12, 0x03, 0x3b, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03,
|
||||||
|
0x3b, 0x09, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x3b, 0x10,
|
||||||
|
0x11, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x03, 0x12, 0x04, 0x3e, 0x00, 0x40, 0x01, 0x0a, 0x0a, 0x0a,
|
||||||
|
0x03, 0x04, 0x03, 0x01, 0x12, 0x03, 0x3e, 0x08, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02,
|
||||||
|
0x00, 0x04, 0x12, 0x03, 0x3f, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x03, 0x02, 0x00, 0x12,
|
||||||
|
0x03, 0x3f, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x05, 0x12, 0x03, 0x3f,
|
||||||
|
0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x01, 0x12, 0x03, 0x3f, 0x12, 0x18,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x03, 0x12, 0x03, 0x3f, 0x1b, 0x1c, 0x0a, 0x09,
|
||||||
|
0x0a, 0x02, 0x04, 0x04, 0x12, 0x03, 0x42, 0x00, 0x1d, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x04, 0x01,
|
||||||
|
0x12, 0x03, 0x42, 0x08, 0x1a, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x05, 0x12, 0x04, 0x44, 0x00, 0x49,
|
||||||
|
0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x05, 0x01, 0x12, 0x03, 0x44, 0x08, 0x16, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x05, 0x04, 0x05, 0x02, 0x00, 0x05, 0x12, 0x03, 0x45, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04,
|
||||||
|
0x05, 0x02, 0x00, 0x12, 0x03, 0x45, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00,
|
||||||
|
0x01, 0x12, 0x03, 0x45, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x03, 0x12,
|
||||||
|
0x03, 0x45, 0x0e, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x05, 0x12, 0x03, 0x47,
|
||||||
|
0x02, 0x08, 0x0a, 0x45, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x01, 0x12, 0x03, 0x47, 0x02, 0x13, 0x1a,
|
||||||
|
0x38, 0x20, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x62, 0x6f, 0x74, 0x68, 0x20, 0x61, 0x72, 0x65, 0x20,
|
||||||
|
0x7a, 0x65, 0x72, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20,
|
||||||
|
0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x74, 0x69,
|
||||||
|
0x72, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02,
|
||||||
|
0x01, 0x01, 0x12, 0x03, 0x47, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x03,
|
||||||
|
0x12, 0x03, 0x47, 0x11, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, 0x05, 0x12, 0x03,
|
||||||
|
0x48, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x02, 0x12, 0x03, 0x48, 0x02, 0x14,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, 0x01, 0x12, 0x03, 0x48, 0x09, 0x0f, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, 0x03, 0x12, 0x03, 0x48, 0x12, 0x13, 0x0a, 0x09, 0x0a, 0x02,
|
||||||
|
0x04, 0x06, 0x12, 0x03, 0x4b, 0x00, 0x21, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x06, 0x01, 0x12, 0x03,
|
||||||
|
0x4b, 0x08, 0x1e, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x07, 0x12, 0x04, 0x4d, 0x00, 0x55, 0x01, 0x0a,
|
||||||
|
0x0a, 0x0a, 0x03, 0x04, 0x07, 0x01, 0x12, 0x03, 0x4d, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
||||||
|
0x07, 0x02, 0x00, 0x05, 0x12, 0x03, 0x4e, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02,
|
||||||
|
0x00, 0x12, 0x03, 0x4e, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x01, 0x12,
|
||||||
|
0x03, 0x4e, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x03, 0x12, 0x03, 0x4e,
|
||||||
|
0x0e, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x01, 0x05, 0x12, 0x03, 0x4f, 0x02, 0x08,
|
||||||
|
0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x01, 0x12, 0x03, 0x4f, 0x02, 0x16, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x05, 0x04, 0x07, 0x02, 0x01, 0x01, 0x12, 0x03, 0x4f, 0x09, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
||||||
|
0x07, 0x02, 0x01, 0x03, 0x12, 0x03, 0x4f, 0x14, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02,
|
||||||
|
0x02, 0x05, 0x12, 0x03, 0x50, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x02, 0x12,
|
||||||
|
0x03, 0x50, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x02, 0x01, 0x12, 0x03, 0x50,
|
||||||
|
0x09, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x02, 0x03, 0x12, 0x03, 0x50, 0x10, 0x11,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x03, 0x05, 0x12, 0x03, 0x51, 0x02, 0x08, 0x0a, 0x0b,
|
||||||
|
0x0a, 0x04, 0x04, 0x07, 0x02, 0x03, 0x12, 0x03, 0x51, 0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
||||||
|
0x07, 0x02, 0x03, 0x01, 0x12, 0x03, 0x51, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02,
|
||||||
|
0x03, 0x03, 0x12, 0x03, 0x51, 0x11, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x04, 0x05,
|
||||||
|
0x12, 0x03, 0x52, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x04, 0x12, 0x03, 0x52,
|
||||||
|
0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x04, 0x01, 0x12, 0x03, 0x52, 0x09, 0x0e,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x04, 0x03, 0x12, 0x03, 0x52, 0x11, 0x12, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x04, 0x07, 0x02, 0x05, 0x05, 0x12, 0x03, 0x53, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04,
|
||||||
|
0x04, 0x07, 0x02, 0x05, 0x12, 0x03, 0x53, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02,
|
||||||
|
0x05, 0x01, 0x12, 0x03, 0x53, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x05, 0x03,
|
||||||
|
0x12, 0x03, 0x53, 0x12, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x06, 0x06, 0x12, 0x03,
|
||||||
|
0x54, 0x02, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x06, 0x12, 0x03, 0x54, 0x02, 0x23,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x06, 0x01, 0x12, 0x03, 0x54, 0x10, 0x1e, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x04, 0x07, 0x02, 0x06, 0x03, 0x12, 0x03, 0x54, 0x21, 0x22, 0x0a, 0x0a, 0x0a, 0x02,
|
||||||
|
0x04, 0x08, 0x12, 0x04, 0x57, 0x00, 0x64, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x08, 0x01, 0x12,
|
||||||
|
0x03, 0x57, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x00, 0x04, 0x12, 0x03, 0x58,
|
||||||
|
0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x00, 0x12, 0x03, 0x58, 0x02, 0x1d, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x00, 0x05, 0x12, 0x03, 0x58, 0x0b, 0x11, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x05, 0x04, 0x08, 0x02, 0x00, 0x01, 0x12, 0x03, 0x58, 0x12, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
||||||
|
0x08, 0x02, 0x00, 0x03, 0x12, 0x03, 0x58, 0x1b, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02,
|
||||||
|
0x01, 0x04, 0x12, 0x03, 0x59, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x01, 0x12,
|
||||||
|
0x03, 0x59, 0x02, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x05, 0x12, 0x03, 0x59,
|
||||||
|
0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x01, 0x12, 0x03, 0x59, 0x12, 0x1e,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x03, 0x12, 0x03, 0x59, 0x21, 0x22, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x04, 0x08, 0x02, 0x02, 0x05, 0x12, 0x03, 0x5a, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04,
|
||||||
|
0x04, 0x08, 0x02, 0x02, 0x12, 0x03, 0x5a, 0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02,
|
||||||
|
0x02, 0x01, 0x12, 0x03, 0x5a, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x02, 0x03,
|
||||||
|
0x12, 0x03, 0x5a, 0x11, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x03, 0x05, 0x12, 0x03,
|
||||||
|
0x5b, 0x02, 0x07, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x03, 0x12, 0x03, 0x5b, 0x02, 0x19,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x03, 0x01, 0x12, 0x03, 0x5b, 0x08, 0x14, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x04, 0x08, 0x02, 0x03, 0x03, 0x12, 0x03, 0x5b, 0x17, 0x18, 0x0a, 0x0c, 0x0a, 0x05,
|
||||||
|
0x04, 0x08, 0x02, 0x04, 0x05, 0x12, 0x03, 0x5c, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08,
|
||||||
|
0x02, 0x04, 0x12, 0x03, 0x5c, 0x02, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x04, 0x01,
|
||||||
|
0x12, 0x03, 0x5c, 0x09, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x04, 0x03, 0x12, 0x03,
|
||||||
|
0x5c, 0x17, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x05, 0x04, 0x12, 0x03, 0x5d, 0x02,
|
||||||
|
0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x05, 0x12, 0x03, 0x5d, 0x02, 0x21, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x04, 0x08, 0x02, 0x05, 0x05, 0x12, 0x03, 0x5d, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05,
|
||||||
|
0x04, 0x08, 0x02, 0x05, 0x01, 0x12, 0x03, 0x5d, 0x12, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08,
|
||||||
|
0x02, 0x05, 0x03, 0x12, 0x03, 0x5d, 0x1f, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x06,
|
||||||
|
0x05, 0x12, 0x03, 0x5e, 0x02, 0x07, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x06, 0x12, 0x03,
|
||||||
|
0x5e, 0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x06, 0x01, 0x12, 0x03, 0x5e, 0x08,
|
||||||
|
0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x06, 0x03, 0x12, 0x03, 0x5e, 0x11, 0x12, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x07, 0x04, 0x12, 0x03, 0x5f, 0x02, 0x0a, 0x0a, 0x0b, 0x0a,
|
||||||
|
0x04, 0x04, 0x08, 0x02, 0x07, 0x12, 0x03, 0x5f, 0x02, 0x2b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08,
|
||||||
|
0x02, 0x07, 0x05, 0x12, 0x03, 0x5f, 0x0b, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x07,
|
||||||
|
0x01, 0x12, 0x03, 0x5f, 0x11, 0x26, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x07, 0x03, 0x12,
|
||||||
|
0x03, 0x5f, 0x29, 0x2a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x08, 0x04, 0x12, 0x03, 0x60,
|
||||||
|
0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x08, 0x12, 0x03, 0x60, 0x02, 0x34, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x08, 0x06, 0x12, 0x03, 0x60, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x05, 0x04, 0x08, 0x02, 0x08, 0x01, 0x12, 0x03, 0x60, 0x1c, 0x2f, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
||||||
|
0x08, 0x02, 0x08, 0x03, 0x12, 0x03, 0x60, 0x32, 0x33, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02,
|
||||||
|
0x09, 0x05, 0x12, 0x03, 0x61, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x09, 0x12,
|
||||||
|
0x03, 0x61, 0x02, 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x09, 0x01, 0x12, 0x03, 0x61,
|
||||||
|
0x09, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x09, 0x03, 0x12, 0x03, 0x61, 0x1c, 0x1e,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x0a, 0x05, 0x12, 0x03, 0x62, 0x02, 0x08, 0x0a, 0x0b,
|
||||||
|
0x0a, 0x04, 0x04, 0x08, 0x02, 0x0a, 0x12, 0x03, 0x62, 0x02, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
||||||
|
0x08, 0x02, 0x0a, 0x01, 0x12, 0x03, 0x62, 0x09, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02,
|
||||||
|
0x0a, 0x03, 0x12, 0x03, 0x62, 0x21, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x0b, 0x05,
|
||||||
|
0x12, 0x03, 0x63, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x0b, 0x12, 0x03, 0x63,
|
||||||
|
0x02, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x0b, 0x01, 0x12, 0x03, 0x63, 0x09, 0x1e,
|
||||||
|
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x0b, 0x03, 0x12, 0x03, 0x63, 0x21, 0x23, 0x0a, 0x0a,
|
||||||
|
0x0a, 0x02, 0x04, 0x09, 0x12, 0x04, 0x66, 0x00, 0x69, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x09,
|
||||||
|
0x01, 0x12, 0x03, 0x66, 0x08, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x05, 0x12,
|
||||||
|
0x03, 0x67, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x09, 0x02, 0x00, 0x12, 0x03, 0x67, 0x02,
|
||||||
|
0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x01, 0x12, 0x03, 0x67, 0x09, 0x0f, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x03, 0x12, 0x03, 0x67, 0x12, 0x13, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x05, 0x04, 0x09, 0x02, 0x01, 0x05, 0x12, 0x03, 0x68, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04,
|
||||||
|
0x09, 0x02, 0x01, 0x12, 0x03, 0x68, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x01,
|
||||||
|
0x01, 0x12, 0x03, 0x68, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x01, 0x03, 0x12,
|
||||||
|
0x03, 0x68, 0x12, 0x13, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x0a, 0x12, 0x04, 0x6b, 0x00, 0x6f, 0x01,
|
||||||
|
0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0a, 0x01, 0x12, 0x03, 0x6b, 0x08, 0x11, 0x0a, 0x0c, 0x0a, 0x05,
|
||||||
|
0x04, 0x0a, 0x02, 0x00, 0x05, 0x12, 0x03, 0x6c, 0x02, 0x07, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0a,
|
||||||
|
0x02, 0x00, 0x12, 0x03, 0x6c, 0x02, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x00, 0x01,
|
||||||
|
0x12, 0x03, 0x6c, 0x08, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x00, 0x03, 0x12, 0x03,
|
||||||
|
0x6c, 0x0f, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x01, 0x05, 0x12, 0x03, 0x6d, 0x02,
|
||||||
|
0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x01, 0x12, 0x03, 0x6d, 0x02, 0x14, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x04, 0x0a, 0x02, 0x01, 0x01, 0x12, 0x03, 0x6d, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05,
|
||||||
|
0x04, 0x0a, 0x02, 0x01, 0x03, 0x12, 0x03, 0x6d, 0x12, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a,
|
||||||
|
0x02, 0x02, 0x05, 0x12, 0x03, 0x6e, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x02,
|
||||||
|
0x12, 0x03, 0x6e, 0x02, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x02, 0x01, 0x12, 0x03,
|
||||||
|
0x6e, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x02, 0x03, 0x12, 0x03, 0x6e, 0x16,
|
||||||
|
0x17, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x0b, 0x12, 0x04, 0x71, 0x00, 0x74, 0x01, 0x0a, 0x0a, 0x0a,
|
||||||
|
0x03, 0x04, 0x0b, 0x01, 0x12, 0x03, 0x71, 0x08, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02,
|
||||||
|
0x00, 0x05, 0x12, 0x03, 0x73, 0x02, 0x08, 0x0a, 0x2f, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x00, 0x12,
|
||||||
|
0x03, 0x73, 0x02, 0x12, 0x1a, 0x22, 0x20, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x22, 0x2c,
|
||||||
|
0x20, 0x22, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x22, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x22, 0x72,
|
||||||
|
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x22, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00,
|
||||||
|
0x01, 0x12, 0x03, 0x73, 0x09, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x03, 0x12,
|
||||||
|
0x03, 0x73, 0x10, 0x11, 0x0a, 0x09, 0x0a, 0x02, 0x04, 0x0c, 0x12, 0x03, 0x76, 0x00, 0x21, 0x0a,
|
||||||
|
0x0a, 0x0a, 0x03, 0x04, 0x0c, 0x01, 0x12, 0x03, 0x76, 0x08, 0x1e, 0x0a, 0x0a, 0x0a, 0x02, 0x04,
|
||||||
|
0x0d, 0x12, 0x04, 0x78, 0x00, 0x7f, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0d, 0x01, 0x12, 0x03,
|
||||||
|
0x78, 0x08, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x00, 0x05, 0x12, 0x03, 0x79, 0x02,
|
||||||
|
0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0d, 0x02, 0x00, 0x12, 0x03, 0x79, 0x02, 0x19, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x05, 0x04, 0x0d, 0x02, 0x00, 0x01, 0x12, 0x03, 0x79, 0x09, 0x14, 0x0a, 0x0c, 0x0a, 0x05,
|
||||||
|
0x04, 0x0d, 0x02, 0x00, 0x03, 0x12, 0x03, 0x79, 0x17, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d,
|
||||||
|
0x02, 0x01, 0x05, 0x12, 0x03, 0x7a, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0d, 0x02, 0x01,
|
||||||
|
0x12, 0x03, 0x7a, 0x02, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x01, 0x01, 0x12, 0x03,
|
||||||
|
0x7a, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x01, 0x03, 0x12, 0x03, 0x7a, 0x16,
|
||||||
|
0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x02, 0x05, 0x12, 0x03, 0x7b, 0x02, 0x08, 0x0a,
|
||||||
|
0x0b, 0x0a, 0x04, 0x04, 0x0d, 0x02, 0x02, 0x12, 0x03, 0x7b, 0x02, 0x18, 0x0a, 0x0c, 0x0a, 0x05,
|
||||||
|
0x04, 0x0d, 0x02, 0x02, 0x01, 0x12, 0x03, 0x7b, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d,
|
||||||
|
0x02, 0x02, 0x03, 0x12, 0x03, 0x7b, 0x16, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x03,
|
||||||
|
0x05, 0x12, 0x03, 0x7c, 0x02, 0x06, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0d, 0x02, 0x03, 0x12, 0x03,
|
||||||
|
0x7c, 0x02, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x03, 0x01, 0x12, 0x03, 0x7c, 0x07,
|
||||||
|
0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x03, 0x03, 0x12, 0x03, 0x7c, 0x1a, 0x1b, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x04, 0x05, 0x12, 0x03, 0x7d, 0x02, 0x08, 0x0a, 0x0b, 0x0a,
|
||||||
|
0x04, 0x04, 0x0d, 0x02, 0x04, 0x12, 0x03, 0x7d, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d,
|
||||||
|
0x02, 0x04, 0x01, 0x12, 0x03, 0x7d, 0x09, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x04,
|
||||||
|
0x03, 0x12, 0x03, 0x7d, 0x1b, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x05, 0x05, 0x12,
|
||||||
|
0x03, 0x7e, 0x02, 0x07, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0d, 0x02, 0x05, 0x12, 0x03, 0x7e, 0x02,
|
||||||
|
0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x05, 0x01, 0x12, 0x03, 0x7e, 0x08, 0x1b, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x05, 0x04, 0x0d, 0x02, 0x05, 0x03, 0x12, 0x03, 0x7e, 0x1e, 0x1f, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x02, 0x04, 0x0e, 0x12, 0x06, 0x81, 0x01, 0x00, 0x83, 0x01, 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04,
|
||||||
|
0x0e, 0x01, 0x12, 0x04, 0x81, 0x01, 0x08, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x00,
|
||||||
|
0x05, 0x12, 0x04, 0x82, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0e, 0x02, 0x00, 0x12,
|
||||||
|
0x04, 0x82, 0x01, 0x02, 0x13, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x00, 0x01, 0x12, 0x04,
|
||||||
|
0x82, 0x01, 0x09, 0x0e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x00, 0x03, 0x12, 0x04, 0x82,
|
||||||
|
0x01, 0x11, 0x12, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x0f, 0x12, 0x06, 0x85, 0x01, 0x00, 0x87, 0x01,
|
||||||
|
0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x0f, 0x01, 0x12, 0x04, 0x85, 0x01, 0x08, 0x20, 0x0a, 0x0d,
|
||||||
|
0x0a, 0x05, 0x04, 0x0f, 0x02, 0x00, 0x06, 0x12, 0x04, 0x86, 0x01, 0x02, 0x0f, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x04, 0x04, 0x0f, 0x02, 0x00, 0x12, 0x04, 0x86, 0x01, 0x02, 0x1d, 0x0a, 0x0d, 0x0a, 0x05, 0x04,
|
||||||
|
0x0f, 0x02, 0x00, 0x01, 0x12, 0x04, 0x86, 0x01, 0x10, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0f,
|
||||||
|
0x02, 0x00, 0x03, 0x12, 0x04, 0x86, 0x01, 0x1b, 0x1c, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x10, 0x12,
|
||||||
|
0x06, 0x89, 0x01, 0x00, 0x91, 0x01, 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x10, 0x01, 0x12, 0x04,
|
||||||
|
0x89, 0x01, 0x08, 0x22, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, 0x05, 0x12, 0x04, 0x8a,
|
||||||
|
0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x10, 0x02, 0x00, 0x12, 0x04, 0x8a, 0x01, 0x02,
|
||||||
|
0x13, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, 0x01, 0x12, 0x04, 0x8a, 0x01, 0x09, 0x0e,
|
||||||
|
0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, 0x03, 0x12, 0x04, 0x8a, 0x01, 0x11, 0x12, 0x0a,
|
||||||
|
0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x01, 0x04, 0x12, 0x04, 0x8b, 0x01, 0x02, 0x0a, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x04, 0x04, 0x10, 0x02, 0x01, 0x12, 0x04, 0x8b, 0x01, 0x02, 0x1d, 0x0a, 0x0d, 0x0a, 0x05,
|
||||||
|
0x04, 0x10, 0x02, 0x01, 0x05, 0x12, 0x04, 0x8b, 0x01, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04,
|
||||||
|
0x10, 0x02, 0x01, 0x01, 0x12, 0x04, 0x8b, 0x01, 0x12, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10,
|
||||||
|
0x02, 0x01, 0x03, 0x12, 0x04, 0x8b, 0x01, 0x1b, 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02,
|
||||||
|
0x02, 0x04, 0x12, 0x04, 0x8c, 0x01, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x10, 0x02, 0x02,
|
||||||
|
0x12, 0x04, 0x8c, 0x01, 0x02, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x02, 0x05, 0x12,
|
||||||
|
0x04, 0x8c, 0x01, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x02, 0x01, 0x12, 0x04,
|
||||||
|
0x8c, 0x01, 0x12, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x02, 0x03, 0x12, 0x04, 0x8c,
|
||||||
|
0x01, 0x21, 0x22, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x03, 0x05, 0x12, 0x04, 0x8d, 0x01,
|
||||||
|
0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x10, 0x02, 0x03, 0x12, 0x04, 0x8d, 0x01, 0x02, 0x13,
|
||||||
|
0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x03, 0x01, 0x12, 0x04, 0x8d, 0x01, 0x09, 0x0e, 0x0a,
|
||||||
|
0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x03, 0x03, 0x12, 0x04, 0x8d, 0x01, 0x11, 0x12, 0x0a, 0x0d,
|
||||||
|
0x0a, 0x05, 0x04, 0x10, 0x02, 0x04, 0x05, 0x12, 0x04, 0x8e, 0x01, 0x02, 0x07, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x04, 0x04, 0x10, 0x02, 0x04, 0x12, 0x04, 0x8e, 0x01, 0x02, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04,
|
||||||
|
0x10, 0x02, 0x04, 0x01, 0x12, 0x04, 0x8e, 0x01, 0x08, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10,
|
||||||
|
0x02, 0x04, 0x03, 0x12, 0x04, 0x8e, 0x01, 0x17, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02,
|
||||||
|
0x05, 0x05, 0x12, 0x04, 0x8f, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x10, 0x02, 0x05,
|
||||||
|
0x12, 0x04, 0x8f, 0x01, 0x02, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x05, 0x01, 0x12,
|
||||||
|
0x04, 0x8f, 0x01, 0x09, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x05, 0x03, 0x12, 0x04,
|
||||||
|
0x8f, 0x01, 0x17, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x06, 0x04, 0x12, 0x04, 0x90,
|
||||||
|
0x01, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x10, 0x02, 0x06, 0x12, 0x04, 0x90, 0x01, 0x02,
|
||||||
|
0x21, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x06, 0x05, 0x12, 0x04, 0x90, 0x01, 0x0b, 0x11,
|
||||||
|
0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x06, 0x01, 0x12, 0x04, 0x90, 0x01, 0x12, 0x1c, 0x0a,
|
||||||
|
0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x06, 0x03, 0x12, 0x04, 0x90, 0x01, 0x1f, 0x20, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x02, 0x04, 0x11, 0x12, 0x06, 0x93, 0x01, 0x00, 0x95, 0x01, 0x01, 0x0a, 0x0b, 0x0a, 0x03,
|
||||||
|
0x04, 0x11, 0x01, 0x12, 0x04, 0x93, 0x01, 0x08, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02,
|
||||||
|
0x00, 0x06, 0x12, 0x04, 0x94, 0x01, 0x02, 0x0f, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x00,
|
||||||
|
0x12, 0x04, 0x94, 0x01, 0x02, 0x1d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x00, 0x01, 0x12,
|
||||||
|
0x04, 0x94, 0x01, 0x10, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x00, 0x03, 0x12, 0x04,
|
||||||
|
0x94, 0x01, 0x1b, 0x1c, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x12, 0x12, 0x04, 0x97, 0x01, 0x00, 0x1b,
|
||||||
|
0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x12, 0x01, 0x12, 0x04, 0x97, 0x01, 0x08, 0x18, 0x0a, 0x0c, 0x0a,
|
||||||
|
0x02, 0x04, 0x13, 0x12, 0x06, 0x99, 0x01, 0x00, 0x9b, 0x01, 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04,
|
||||||
|
0x13, 0x01, 0x12, 0x04, 0x99, 0x01, 0x08, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x13, 0x02, 0x00,
|
||||||
|
0x04, 0x12, 0x04, 0x9a, 0x01, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x13, 0x02, 0x00, 0x12,
|
||||||
|
0x04, 0x9a, 0x01, 0x02, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x13, 0x02, 0x00, 0x06, 0x12, 0x04,
|
||||||
|
0x9a, 0x01, 0x0b, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x13, 0x02, 0x00, 0x01, 0x12, 0x04, 0x9a,
|
||||||
|
0x01, 0x15, 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x13, 0x02, 0x00, 0x03, 0x12, 0x04, 0x9a, 0x01,
|
||||||
|
0x1d, 0x1e, 0x0a, 0xb9, 0x01, 0x0a, 0x02, 0x04, 0x14, 0x12, 0x06, 0xa0, 0x01, 0x00, 0xa7, 0x01,
|
||||||
|
0x01, 0x1a, 0xaa, 0x01, 0x20, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x2d, 0x72, 0x65, 0x61, 0x64, 0x61,
|
||||||
|
0x62, 0x6c, 0x65, 0x20, 0x74, 0x61, 0x67, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x6f,
|
||||||
|
0x6e, 0x6c, 0x79, 0x2e, 0x20, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x20, 0x74, 0x68,
|
||||||
|
0x65, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x20,
|
||||||
|
0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x0a, 0x20, 0x28, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2c,
|
||||||
|
0x20, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68,
|
||||||
|
0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x65, 0x74, 0x63, 0x2e, 0x29, 0x20, 0x74, 0x68,
|
||||||
|
0x61, 0x74, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6d, 0x61, 0x6b, 0x65, 0x20, 0x73, 0x65, 0x6e,
|
||||||
|
0x73, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x0a, 0x20, 0x6d, 0x75, 0x73, 0x69, 0x63,
|
||||||
|
0x66, 0x73, 0x27, 0x73, 0x20, 0x46, 0x55, 0x53, 0x45, 0x20, 0x72, 0x65, 0x61, 0x64, 0x2d, 0x61,
|
||||||
|
0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x79, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x0a, 0x0a, 0x0b,
|
||||||
|
0x0a, 0x03, 0x04, 0x14, 0x01, 0x12, 0x04, 0xa0, 0x01, 0x08, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04,
|
||||||
|
0x14, 0x02, 0x00, 0x04, 0x12, 0x04, 0xa1, 0x01, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x14,
|
||||||
|
0x02, 0x00, 0x12, 0x04, 0xa1, 0x01, 0x02, 0x1d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x00,
|
||||||
|
0x05, 0x12, 0x04, 0xa1, 0x01, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x00, 0x01,
|
||||||
|
0x12, 0x04, 0xa1, 0x01, 0x12, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x00, 0x03, 0x12,
|
||||||
|
0x04, 0xa1, 0x01, 0x1b, 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x01, 0x04, 0x12, 0x04,
|
||||||
|
0xa2, 0x01, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x14, 0x02, 0x01, 0x12, 0x04, 0xa2, 0x01,
|
||||||
|
0x02, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x01, 0x05, 0x12, 0x04, 0xa2, 0x01, 0x0b,
|
||||||
|
0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x01, 0x01, 0x12, 0x04, 0xa2, 0x01, 0x12, 0x1e,
|
||||||
|
0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x01, 0x03, 0x12, 0x04, 0xa2, 0x01, 0x21, 0x22, 0x0a,
|
||||||
|
0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x02, 0x05, 0x12, 0x04, 0xa3, 0x01, 0x02, 0x08, 0x0a, 0x0c,
|
||||||
|
0x0a, 0x04, 0x04, 0x14, 0x02, 0x02, 0x12, 0x04, 0xa3, 0x01, 0x02, 0x13, 0x0a, 0x0d, 0x0a, 0x05,
|
||||||
|
0x04, 0x14, 0x02, 0x02, 0x01, 0x12, 0x04, 0xa3, 0x01, 0x09, 0x0e, 0x0a, 0x0d, 0x0a, 0x05, 0x04,
|
||||||
|
0x14, 0x02, 0x02, 0x03, 0x12, 0x04, 0xa3, 0x01, 0x11, 0x12, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14,
|
||||||
|
0x02, 0x03, 0x05, 0x12, 0x04, 0xa4, 0x01, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x14, 0x02,
|
||||||
|
0x03, 0x12, 0x04, 0xa4, 0x01, 0x02, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x03, 0x01,
|
||||||
|
0x12, 0x04, 0xa4, 0x01, 0x08, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x03, 0x03, 0x12,
|
||||||
|
0x04, 0xa4, 0x01, 0x17, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x04, 0x05, 0x12, 0x04,
|
||||||
|
0xa5, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x14, 0x02, 0x04, 0x12, 0x04, 0xa5, 0x01,
|
||||||
|
0x02, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x04, 0x01, 0x12, 0x04, 0xa5, 0x01, 0x09,
|
||||||
|
0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x04, 0x03, 0x12, 0x04, 0xa5, 0x01, 0x17, 0x18,
|
||||||
|
0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x05, 0x04, 0x12, 0x04, 0xa6, 0x01, 0x02, 0x0a, 0x0a,
|
||||||
|
0x0c, 0x0a, 0x04, 0x04, 0x14, 0x02, 0x05, 0x12, 0x04, 0xa6, 0x01, 0x02, 0x21, 0x0a, 0x0d, 0x0a,
|
||||||
|
0x05, 0x04, 0x14, 0x02, 0x05, 0x05, 0x12, 0x04, 0xa6, 0x01, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05,
|
||||||
|
0x04, 0x14, 0x02, 0x05, 0x01, 0x12, 0x04, 0xa6, 0x01, 0x12, 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04,
|
||||||
|
0x14, 0x02, 0x05, 0x03, 0x12, 0x04, 0xa6, 0x01, 0x1f, 0x20, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x15,
|
||||||
|
0x12, 0x06, 0xa9, 0x01, 0x00, 0xb4, 0x01, 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x15, 0x01, 0x12,
|
||||||
|
0x04, 0xa9, 0x01, 0x08, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x00, 0x05, 0x12, 0x04,
|
||||||
|
0xaa, 0x01, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x15, 0x02, 0x00, 0x12, 0x04, 0xaa, 0x01,
|
||||||
|
0x02, 0x13, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x00, 0x01, 0x12, 0x04, 0xaa, 0x01, 0x09,
|
||||||
|
0x0e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x00, 0x03, 0x12, 0x04, 0xaa, 0x01, 0x11, 0x12,
|
||||||
|
0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x01, 0x05, 0x12, 0x04, 0xac, 0x01, 0x02, 0x08, 0x0a,
|
||||||
|
0x47, 0x0a, 0x04, 0x04, 0x15, 0x02, 0x01, 0x12, 0x04, 0xac, 0x01, 0x02, 0x1b, 0x1a, 0x39, 0x20,
|
||||||
|
0x52, 0x65, 0x61, 0x6c, 0x20, 0x6f, 0x6e, 0x2d, 0x64, 0x69, 0x73, 0x6b, 0x20, 0x70, 0x61, 0x74,
|
||||||
|
0x68, 0x20, 0x28, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x74, 0x6f, 0x72, 0x61, 0x2f, 0x6c, 0x69,
|
||||||
|
0x62, 0x72, 0x71, 0x62, 0x69, 0x74, 0x20, 0x77, 0x72, 0x6f, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65,
|
||||||
|
0x20, 0x66, 0x69, 0x6c, 0x65, 0x29, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x01,
|
||||||
|
0x01, 0x12, 0x04, 0xac, 0x01, 0x09, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x01, 0x03,
|
||||||
|
0x12, 0x04, 0xac, 0x01, 0x19, 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x02, 0x05, 0x12,
|
||||||
|
0x04, 0xaf, 0x01, 0x02, 0x08, 0x0a, 0x7a, 0x0a, 0x04, 0x04, 0x15, 0x02, 0x02, 0x12, 0x04, 0xaf,
|
||||||
|
0x01, 0x02, 0x18, 0x1a, 0x6c, 0x20, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x20, 0x46, 0x55,
|
||||||
|
0x53, 0x45, 0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x28, 0x7b, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74,
|
||||||
|
0x7d, 0x2f, 0x7b, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x7d, 0x2f, 0x7b, 0x66, 0x69, 0x6c, 0x65, 0x6e,
|
||||||
|
0x61, 0x6d, 0x65, 0x7d, 0x29, 0x3b, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77,
|
||||||
|
0x68, 0x65, 0x6e, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x0a, 0x20, 0x63, 0x68,
|
||||||
|
0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x76, 0x69, 0x61, 0x20, 0x60, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||||
|
0x65, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x60, 0x2e,
|
||||||
|
0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x02, 0x01, 0x12, 0x04, 0xaf, 0x01, 0x09, 0x13,
|
||||||
|
0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x02, 0x03, 0x12, 0x04, 0xaf, 0x01, 0x16, 0x17, 0x0a,
|
||||||
|
0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x03, 0x05, 0x12, 0x04, 0xb1, 0x01, 0x02, 0x08, 0x0a, 0x28,
|
||||||
|
0x0a, 0x04, 0x04, 0x15, 0x02, 0x03, 0x12, 0x04, 0xb1, 0x01, 0x02, 0x12, 0x1a, 0x1a, 0x20, 0x46,
|
||||||
|
0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e,
|
||||||
|
0x74, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x03,
|
||||||
|
0x01, 0x12, 0x04, 0xb1, 0x01, 0x09, 0x0d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x03, 0x03,
|
||||||
|
0x12, 0x04, 0xb1, 0x01, 0x10, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15, 0x02, 0x04, 0x04, 0x12,
|
||||||
|
0x04, 0xb3, 0x01, 0x02, 0x0a, 0x0a, 0x35, 0x0a, 0x04, 0x04, 0x15, 0x02, 0x04, 0x12, 0x04, 0xb3,
|
||||||
|
0x01, 0x02, 0x25, 0x1a, 0x27, 0x20, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x69, 0x66, 0x20, 0x74,
|
||||||
|
0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x68, 0x61, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x70,
|
||||||
|
0x61, 0x72, 0x73, 0x65, 0x64, 0x20, 0x74, 0x61, 0x67, 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05,
|
||||||
|
0x04, 0x15, 0x02, 0x04, 0x06, 0x12, 0x04, 0xb3, 0x01, 0x0b, 0x17, 0x0a, 0x0d, 0x0a, 0x05, 0x04,
|
||||||
|
0x15, 0x02, 0x04, 0x01, 0x12, 0x04, 0xb3, 0x01, 0x18, 0x20, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x15,
|
||||||
|
0x02, 0x04, 0x03, 0x12, 0x04, 0xb3, 0x01, 0x23, 0x24, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
|
0x33,
|
||||||
|
];
|
||||||
|
include!("musicfs.tonic.rs");
|
||||||
|
// @@protoc_insertion_point(module)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
|||||||
|
pub mod musicfs {
|
||||||
|
include!("generated/musicfs/musicfs.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use musicfs::{
|
||||||
|
ChangeEvent, ClientStatusResponse, FileChunk, FileEntry, FileMetadata, GetClientStatusRequest,
|
||||||
|
GetFileRequest, GetManifestRequest, GetMetadataRequest, GetMusicMetadataRequest,
|
||||||
|
GetMusicMetadataResponse, InodeHash, ListFilesRequest, ListFilesResponse, ManifestEntry,
|
||||||
|
MusicMetadata, PictureDataRange, ReconcileRequest, ReconcileResponse, SubscribeEventsRequest,
|
||||||
|
UpdateMusicMetadataRequest, UpdateMusicMetadataResponse,
|
||||||
|
client_control_server::{ClientControl, ClientControlServer},
|
||||||
|
client_status_server::{ClientStatus, ClientStatusServer},
|
||||||
|
music_fs_client::MusicFsClient,
|
||||||
|
music_fs_server::{MusicFs, MusicFsServer},
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "musicfs-server"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
default-run = "musicfs-server"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "musicfs-server"
|
||||||
|
path = "src/bin/musicfs-server.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
musicfs-core.workspace = true
|
||||||
|
musicfs-proto.workspace = true
|
||||||
|
tonic.workspace = true
|
||||||
|
tonic-health.workspace = true
|
||||||
|
tonic-reflection.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
async-trait.workspace = true
|
||||||
|
clap.workspace = true
|
||||||
|
notify.workspace = true
|
||||||
|
tokio-stream.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile.workspace = true
|
||||||
@@ -2,8 +2,8 @@ use std::{net::SocketAddr, path::PathBuf};
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use musicfs::logging::{LogConfig, init};
|
use musicfs_core::logging::{LogConfig, init};
|
||||||
use musicfs::server::{
|
use musicfs_server::server::{
|
||||||
state::ServerState,
|
state::ServerState,
|
||||||
transport::{self, TransportArgs},
|
transport::{self, TransportArgs},
|
||||||
watcher::ServerWatcher,
|
watcher::ServerWatcher,
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod server;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::item::compute_item_hash;
|
use musicfs_core::compute_item_hash;
|
||||||
use crate::music::metadata::MusicMetadata;
|
use musicfs_core::music::metadata::MusicMetadata;
|
||||||
|
|
||||||
/// One row of the in-memory manifest: every field the client needs to
|
/// One row of the in-memory manifest: every field the client needs to
|
||||||
/// reconstruct an `Item` whose hash matches the server's hash.
|
/// reconstruct an `Item` whose hash matches the server's hash.
|
||||||
@@ -8,9 +8,9 @@ use std::{
|
|||||||
|
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
use crate::music::parse::parse_music_metadata_for_path;
|
|
||||||
use crate::origins::attrs::FileAttrs;
|
|
||||||
use crate::server::manifest::ManifestEntry;
|
use crate::server::manifest::ManifestEntry;
|
||||||
|
use musicfs_core::FileAttrs;
|
||||||
|
use musicfs_core::music::parse::parse_music_metadata_for_path;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
/// Server-side entry for one file. Built once on startup from a directory
|
/// Server-side entry for one file. Built once on startup from a directory
|
||||||
@@ -20,7 +20,7 @@ pub struct FileEntry {
|
|||||||
pub abs_path: PathBuf,
|
pub abs_path: PathBuf,
|
||||||
pub rel_path: String,
|
pub rel_path: String,
|
||||||
pub attrs: FileAttrs,
|
pub attrs: FileAttrs,
|
||||||
pub music_metadata: Option<crate::music::metadata::MusicMetadata>,
|
pub music_metadata: Option<musicfs_core::music::metadata::MusicMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -10,19 +10,19 @@ use async_trait::async_trait;
|
|||||||
use tokio::sync::broadcast::Receiver;
|
use tokio::sync::broadcast::Receiver;
|
||||||
use tonic::{Request, Response, Status, transport::Server};
|
use tonic::{Request, Response, Status, transport::Server};
|
||||||
|
|
||||||
use crate::music::metadata::MusicMetadata;
|
use crate::server::manifest::ManifestEntry as DomainManifestEntry;
|
||||||
use crate::proto as proto_types;
|
use crate::server::state::ServerState;
|
||||||
use crate::proto::MusicFs as MusicFsTrait;
|
use crate::server::transport::{MusicTransport, TransportArgs};
|
||||||
use crate::proto::{
|
use crate::server::watcher::{ChangeEvent, ChangeKind};
|
||||||
|
use musicfs_core::music::metadata::MusicMetadata;
|
||||||
|
use musicfs_proto as proto_types;
|
||||||
|
use musicfs_proto::MusicFs as MusicFsTrait;
|
||||||
|
use musicfs_proto::{
|
||||||
ChangeEvent as ProtoChangeEvent, FileChunk, GetFileRequest, GetManifestRequest,
|
ChangeEvent as ProtoChangeEvent, FileChunk, GetFileRequest, GetManifestRequest,
|
||||||
GetMetadataRequest, InodeHash, ManifestEntry, MusicFsServer,
|
GetMetadataRequest, InodeHash, ManifestEntry, MusicFsServer,
|
||||||
MusicMetadata as ProtoMusicMetadata, PictureDataRange, ReconcileRequest, ReconcileResponse,
|
MusicMetadata as ProtoMusicMetadata, PictureDataRange, ReconcileRequest, ReconcileResponse,
|
||||||
SubscribeEventsRequest,
|
SubscribeEventsRequest,
|
||||||
};
|
};
|
||||||
use crate::server::manifest::ManifestEntry as DomainManifestEntry;
|
|
||||||
use crate::server::state::ServerState;
|
|
||||||
use crate::server::transport::{MusicTransport, TransportArgs};
|
|
||||||
use crate::server::watcher::{ChangeEvent, ChangeKind};
|
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
pub struct GrpcTransport {
|
pub struct GrpcTransport {
|
||||||
@@ -53,11 +53,11 @@ impl MusicTransport for GrpcTransport {
|
|||||||
|
|
||||||
// v1alpha covers older clients; v1 is the current standard.
|
// v1alpha covers older clients; v1 is the current standard.
|
||||||
let reflection_v1 = tonic_reflection::server::Builder::configure()
|
let reflection_v1 = tonic_reflection::server::Builder::configure()
|
||||||
.register_encoded_file_descriptor_set(crate::proto::musicfs::FILE_DESCRIPTOR_SET)
|
.register_encoded_file_descriptor_set(musicfs_proto::musicfs::FILE_DESCRIPTOR_SET)
|
||||||
.register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
|
.register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
|
||||||
.build_v1()?;
|
.build_v1()?;
|
||||||
let reflection_v1alpha = tonic_reflection::server::Builder::configure()
|
let reflection_v1alpha = tonic_reflection::server::Builder::configure()
|
||||||
.register_encoded_file_descriptor_set(crate::proto::musicfs::FILE_DESCRIPTOR_SET)
|
.register_encoded_file_descriptor_set(musicfs_proto::musicfs::FILE_DESCRIPTOR_SET)
|
||||||
.register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
|
.register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
|
||||||
.build_v1alpha()?;
|
.build_v1alpha()?;
|
||||||
|
|
||||||
@@ -298,31 +298,29 @@ impl From<DomainManifestEntry> for ManifestEntry {
|
|||||||
mtime: entry.mtime,
|
mtime: entry.mtime,
|
||||||
ctime: entry.ctime,
|
ctime: entry.ctime,
|
||||||
crtime: entry.crtime,
|
crtime: entry.crtime,
|
||||||
music_metadata: entry.music_metadata.map(Into::into),
|
music_metadata: entry.music_metadata.map(music_metadata_to_proto),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<MusicMetadata> for ProtoMusicMetadata {
|
fn music_metadata_to_proto(mm: MusicMetadata) -> ProtoMusicMetadata {
|
||||||
fn from(mm: MusicMetadata) -> Self {
|
ProtoMusicMetadata {
|
||||||
return ProtoMusicMetadata {
|
artist: mm.artist,
|
||||||
artist: mm.artist,
|
album_artist: mm.album_artist,
|
||||||
album_artist: mm.album_artist,
|
album: mm.album,
|
||||||
album: mm.album,
|
track_number: mm.track_number,
|
||||||
track_number: mm.track_number,
|
track_title: mm.track_title,
|
||||||
track_title: mm.track_title,
|
other_tags: mm.other_tags,
|
||||||
other_tags: mm.other_tags,
|
header: mm.header,
|
||||||
header: mm.header,
|
picture_block_headers: mm.picture_block_headers,
|
||||||
picture_block_headers: mm.picture_block_headers,
|
picture_data_ranges: mm
|
||||||
picture_data_ranges: mm
|
.picture_data_ranges
|
||||||
.picture_data_ranges
|
.into_iter()
|
||||||
.into_iter()
|
.map(|(offset, length)| PictureDataRange { offset, length })
|
||||||
.map(|(offset, length)| PictureDataRange { offset, length })
|
.collect(),
|
||||||
.collect(),
|
real_audio_start: mm.real_audio_start,
|
||||||
real_audio_start: mm.real_audio_start,
|
vorbis_comment_offset: mm.vorbis_comment_offset,
|
||||||
vorbis_comment_offset: mm.vorbis_comment_offset,
|
vorbis_comment_length: mm.vorbis_comment_length,
|
||||||
vorbis_comment_length: mm.vorbis_comment_length,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+38
-5
@@ -6,6 +6,33 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
# devenv's `languages.rust.import` only builds single root-package crates
|
||||||
|
# (it hardcodes `cargoNix.rootCrate.build`). musicfs is a virtual workspace with
|
||||||
|
# no root package, so we drive crate2nix directly and select the `musicfs-client`
|
||||||
|
# member out of `workspaceMembers`. src = ./. gives crate2nix the whole workspace,
|
||||||
|
# so the member's path-dependencies (musicfs-core, musicfs-proto) resolve.
|
||||||
|
crate2nixTools = pkgs.callPackage "${inputs.crate2nix}/tools.nix" { };
|
||||||
|
cargoNix =
|
||||||
|
pkgs.callPackage
|
||||||
|
(crate2nixTools.generatedCargoNix {
|
||||||
|
name = "musicfs";
|
||||||
|
src = ./.;
|
||||||
|
})
|
||||||
|
{
|
||||||
|
# Build with the toolchain configured for this dev environment,
|
||||||
|
# matching what languages.rust.import does internally.
|
||||||
|
buildRustCrateForPkgs =
|
||||||
|
_:
|
||||||
|
pkgs.buildRustCrate.override {
|
||||||
|
rustc = config.languages.rust.toolchainPackage;
|
||||||
|
cargo = config.languages.rust.toolchainPackage;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# musicfs-client's binary is named `musicfs` (see [[bin]] in its Cargo.toml).
|
||||||
|
musicfs = cargoNix.workspaceMembers."musicfs-client".build;
|
||||||
|
in
|
||||||
{
|
{
|
||||||
languages.rust.enable = true;
|
languages.rust.enable = true;
|
||||||
git-hooks.hooks = {
|
git-hooks.hooks = {
|
||||||
@@ -42,18 +69,24 @@
|
|||||||
processes.musicfs = {
|
processes.musicfs = {
|
||||||
after = [ "devenv:processes:postgres" ];
|
after = [ "devenv:processes:postgres" ];
|
||||||
exec = lib.mkDefault ''
|
exec = lib.mkDefault ''
|
||||||
cargo run -- \
|
${musicfs}/bin/musicfs \
|
||||||
--source /home/fujin/Music \
|
--source /home/fujin/Music \
|
||||||
--mountpoint /tmp/rust-fuse \
|
--mountpoint /tmp/rust-fuse \
|
||||||
--database "postgresql://fujin@localhost/musicfs?host=$PGHOST"
|
--database "postgresql://fujin@localhost/musicfs?host=$PGHOST"
|
||||||
'';
|
'';
|
||||||
|
# Probes the standard gRPC Health Checking Protocol exposed on the
|
||||||
|
# musicfs-client status server (default --listen 127.0.0.1:50052).
|
||||||
|
# SERVING implies the FUSE mount succeeded and, for NetworkOrigin, the
|
||||||
|
# upstream musicfs-server is reachable (health.rs toggles not_serving on
|
||||||
|
# upstream loss). Inherited by profiles.* since they only override `exec`.
|
||||||
|
ready.exec = "grpcurl -plaintext -max-time 2 127.0.0.1:50052 grpc.health.v1.Health/Check | grep -q SERVING";
|
||||||
};
|
};
|
||||||
|
|
||||||
profiles.local.module = {
|
profiles.local.module = {
|
||||||
processes.musicfs = {
|
processes.musicfs = {
|
||||||
after = [ "devenv:processes:postgres" ];
|
after = [ "devenv:processes:postgres" ];
|
||||||
exec = ''
|
exec = ''
|
||||||
cargo run -- \
|
${musicfs}/bin/musicfs \
|
||||||
--source /home/fujin/Music \
|
--source /home/fujin/Music \
|
||||||
--mountpoint /tmp/rust-fuse \
|
--mountpoint /tmp/rust-fuse \
|
||||||
--database "postgresql://fujin@localhost/musicfs?host=$PGHOST"
|
--database "postgresql://fujin@localhost/musicfs?host=$PGHOST"
|
||||||
@@ -65,7 +98,7 @@
|
|||||||
processes.musicfs = {
|
processes.musicfs = {
|
||||||
after = [ "devenv:processes:postgres" ];
|
after = [ "devenv:processes:postgres" ];
|
||||||
exec = ''
|
exec = ''
|
||||||
cargo run -- \
|
${musicfs}/bin/musicfs \
|
||||||
--source http://10.185.226.145:50051 \
|
--source http://10.185.226.145:50051 \
|
||||||
--mountpoint /tmp/rust-fuse \
|
--mountpoint /tmp/rust-fuse \
|
||||||
--database "postgresql://fujin@localhost/musicfs?host=$PGHOST"
|
--database "postgresql://fujin@localhost/musicfs?host=$PGHOST"
|
||||||
@@ -77,7 +110,7 @@
|
|||||||
processes.musicfs = {
|
processes.musicfs = {
|
||||||
after = [ "devenv:processes:postgres" ];
|
after = [ "devenv:processes:postgres" ];
|
||||||
exec = ''
|
exec = ''
|
||||||
cargo run -- \
|
${musicfs}/bin/musicfs \
|
||||||
--source http://127.0.0.1:50061 \
|
--source http://127.0.0.1:50061 \
|
||||||
--mountpoint ./target/e2e/mnt \
|
--mountpoint ./target/e2e/mnt \
|
||||||
--database "postgresql://fujin@localhost/musicfs_e2e?host=$PGHOST" \
|
--database "postgresql://fujin@localhost/musicfs_e2e?host=$PGHOST" \
|
||||||
@@ -101,6 +134,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
rust-app = config.languages.rust.import ./. { };
|
musicfs = musicfs;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
version: v2
|
version: v2
|
||||||
plugins:
|
plugins:
|
||||||
- remote: buf.build/community/neoeinstein-prost
|
- remote: buf.build/community/neoeinstein-prost
|
||||||
out: ../src/proto/generated
|
out: ../crates/musicfs-proto/src/generated
|
||||||
opt:
|
opt:
|
||||||
- file_descriptor_set
|
- file_descriptor_set
|
||||||
- remote: buf.build/community/neoeinstein-tonic
|
- remote: buf.build/community/neoeinstein-tonic
|
||||||
out: ../src/proto/generated
|
out: ../crates/musicfs-proto/src/generated
|
||||||
|
|||||||
@@ -32,6 +32,20 @@ service MusicFs {
|
|||||||
rpc SubscribeEvents(SubscribeEventsRequest) returns (stream ChangeEvent);
|
rpc SubscribeEvents(SubscribeEventsRequest) returns (stream ChangeEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client-side status reporting (served by the FUSE client process).
|
||||||
|
service ClientStatus {
|
||||||
|
rpc GetClientStatus(GetClientStatusRequest) returns (ClientStatusResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
service ClientControl {
|
||||||
|
rpc GetMusicMetadata(GetMusicMetadataRequest) returns (GetMusicMetadataResponse);
|
||||||
|
rpc UpdateMusicMetadata(UpdateMusicMetadataRequest) returns (UpdateMusicMetadataResponse);
|
||||||
|
// Every file musicfs knows about. Caller filters by `original_path`
|
||||||
|
// prefix to find the files of interest (e.g. all files under a single
|
||||||
|
// torrent's output directory).
|
||||||
|
rpc ListFiles(ListFilesRequest) returns (ListFilesResponse);
|
||||||
|
}
|
||||||
|
|
||||||
message ReconcileRequest {
|
message ReconcileRequest {
|
||||||
repeated InodeHash entries = 1;
|
repeated InodeHash entries = 1;
|
||||||
}
|
}
|
||||||
@@ -101,3 +115,67 @@ message ChangeEvent {
|
|||||||
// "create", "modify", or "remove".
|
// "create", "modify", or "remove".
|
||||||
string kind = 1;
|
string kind = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetClientStatusRequest {}
|
||||||
|
|
||||||
|
message ClientStatusResponse {
|
||||||
|
string origin_type = 1;
|
||||||
|
string mountpoint = 2;
|
||||||
|
uint64 file_count = 3;
|
||||||
|
bool server_connected = 4;
|
||||||
|
string server_endpoint = 5;
|
||||||
|
int64 last_reconcile_unix = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetMusicMetadataRequest {
|
||||||
|
uint64 inode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetMusicMetadataResponse {
|
||||||
|
MusicMetadata metadata = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateMusicMetadataRequest {
|
||||||
|
uint64 inode = 1;
|
||||||
|
repeated string artist = 2;
|
||||||
|
optional string album_artist = 3;
|
||||||
|
string album = 4;
|
||||||
|
int32 track_number = 5;
|
||||||
|
string track_title = 6;
|
||||||
|
repeated string other_tags = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateMusicMetadataResponse {
|
||||||
|
MusicMetadata metadata = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListFilesRequest {}
|
||||||
|
|
||||||
|
message ListFilesResponse {
|
||||||
|
repeated FileEntry files = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Human-readable tag fields only. Excludes the binary layout fields
|
||||||
|
// (header, picture_block_headers, etc.) that only make sense inside
|
||||||
|
// musicfs's FUSE read-assembly path.
|
||||||
|
message FileMetadata {
|
||||||
|
repeated string artist = 1;
|
||||||
|
optional string album_artist = 2;
|
||||||
|
string album = 3;
|
||||||
|
int32 track_number = 4;
|
||||||
|
string track_title = 5;
|
||||||
|
repeated string other_tags = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileEntry {
|
||||||
|
uint64 inode = 1;
|
||||||
|
// Real on-disk path (where tora/librqbit wrote the file).
|
||||||
|
string original_path = 2;
|
||||||
|
// Virtual FUSE path ({artist}/{album}/{filename}); updated when metadata
|
||||||
|
// changes via `UpdateMusicMetadata`.
|
||||||
|
string local_path = 3;
|
||||||
|
// Filename component only.
|
||||||
|
string name = 4;
|
||||||
|
// Empty if the file has no parsed tags.
|
||||||
|
optional FileMetadata metadata = 5;
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,9 +65,19 @@ stop_postgres() {
|
|||||||
# ──────────────────────── VM management ────────────────────────────────
|
# ──────────────────────── VM management ────────────────────────────────
|
||||||
vm_ip() { incus list "$VM_NAME" -f csv -c 4 2>/dev/null | head -1 | awk '{print $1}'; }
|
vm_ip() { incus list "$VM_NAME" -f csv -c 4 2>/dev/null | head -1 | awk '{print $1}'; }
|
||||||
|
|
||||||
|
detect_e2e_ip() {
|
||||||
|
local bridge_cidr
|
||||||
|
bridge_cidr="$(incus network get incusbr0 ipv4.address 2>/dev/null || true)"
|
||||||
|
if [[ -z "$bridge_cidr" ]]; then echo ""; return; fi
|
||||||
|
local gateway="${bridge_cidr%%/*}"
|
||||||
|
echo "${gateway%.*}.146"
|
||||||
|
}
|
||||||
|
|
||||||
start_vm() {
|
start_vm() {
|
||||||
log "starting VM '$VM_NAME' (port $PORT)…"
|
log "starting VM '$VM_NAME' (port $PORT)…"
|
||||||
|
local e2e_ip; e2e_ip="$(detect_e2e_ip)"
|
||||||
VM_NAME="$VM_NAME" \
|
VM_NAME="$VM_NAME" \
|
||||||
|
VM_IP="$e2e_ip" \
|
||||||
MUSIC_SOURCE="$MUSIC_DIR" \
|
MUSIC_SOURCE="$MUSIC_DIR" \
|
||||||
LISTEN_PORT="$PORT" \
|
LISTEN_PORT="$PORT" \
|
||||||
RUST_LOG=debug \
|
RUST_LOG=debug \
|
||||||
|
|||||||
@@ -62,9 +62,21 @@ stop_postgres() {
|
|||||||
# ──────────────────────── VM management ────────────────────────────────
|
# ──────────────────────── VM management ────────────────────────────────
|
||||||
vm_ip() { incus list "$VM_NAME" -f csv -c 4 2>/dev/null | head -1 | awk '{print $1}'; }
|
vm_ip() { incus list "$VM_NAME" -f csv -c 4 2>/dev/null | head -1 | awk '{print $1}'; }
|
||||||
|
|
||||||
|
detect_e2e_ip() {
|
||||||
|
local bridge_cidr
|
||||||
|
bridge_cidr="$(incus network get incusbr0 ipv4.address 2>/dev/null || true)"
|
||||||
|
if [[ -z "$bridge_cidr" ]]; then echo ""; return; fi
|
||||||
|
local gateway="${bridge_cidr%%/*}"
|
||||||
|
echo "${gateway%.*}.146"
|
||||||
|
}
|
||||||
|
|
||||||
start_vm() {
|
start_vm() {
|
||||||
log "starting VM '$VM_NAME' (port $PORT)…"
|
log "starting VM '$VM_NAME' (port $PORT)…"
|
||||||
|
# e2e VM uses .146 to avoid colliding with the dev VM (.145)
|
||||||
|
local e2e_ip
|
||||||
|
e2e_ip="$(detect_e2e_ip)"
|
||||||
VM_NAME="$VM_NAME" \
|
VM_NAME="$VM_NAME" \
|
||||||
|
VM_IP="$e2e_ip" \
|
||||||
MUSIC_SOURCE="$MUSIC_DIR" \
|
MUSIC_SOURCE="$MUSIC_DIR" \
|
||||||
LISTEN_PORT="$PORT" \
|
LISTEN_PORT="$PORT" \
|
||||||
RUST_LOG=debug \
|
RUST_LOG=debug \
|
||||||
|
|||||||
+1
-5
@@ -179,11 +179,7 @@ wait_for_vm() {
|
|||||||
deploy() {
|
deploy() {
|
||||||
local bundle; bundle="$(build_bundle)"
|
local bundle; bundle="$(build_bundle)"
|
||||||
log "deploying bundle to $VM_NAME:$SERVER_DIR ..."
|
log "deploying bundle to $VM_NAME:$SERVER_DIR ..."
|
||||||
# Stop first: overwriting a running binary fails with ETXTBSY ("text file busy").
|
incus exec "$VM_NAME" -- systemctl stop "$UNIT_NAME" 2>/dev/null || true
|
||||||
if incus exec "$VM_NAME" -- systemctl is-active --quiet "$UNIT_NAME" 2>/dev/null; then
|
|
||||||
log "stopping $UNIT_NAME (binary in use)..."
|
|
||||||
incus exec "$VM_NAME" -- systemctl stop "$UNIT_NAME"
|
|
||||||
fi
|
|
||||||
incus exec "$VM_NAME" -- mkdir -p "$SERVER_DIR/lib" "$LOG_DIR"
|
incus exec "$VM_NAME" -- mkdir -p "$SERVER_DIR/lib" "$LOG_DIR"
|
||||||
incus file push "$bundle/server" "$VM_NAME$SERVER_DIR/server" --mode=0755
|
incus file push "$bundle/server" "$VM_NAME$SERVER_DIR/server" --mode=0755
|
||||||
# Ship the bundled libs one by one (robust across incus file-push versions).
|
# Ship the bundled libs one by one (robust across incus file-push versions).
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
pub mod db;
|
|
||||||
pub mod item;
|
|
||||||
pub mod logging;
|
|
||||||
pub mod music;
|
|
||||||
pub mod origins;
|
|
||||||
pub mod proto;
|
|
||||||
pub mod server;
|
|
||||||
pub mod virtual_dirs;
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
use symphonia::core::meta::{MetadataRevision, StandardTagKey};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
||||||
pub struct MusicMetadata {
|
|
||||||
pub artist: Vec<String>,
|
|
||||||
pub album_artist: Option<String>,
|
|
||||||
pub album: String,
|
|
||||||
pub track_number: i32,
|
|
||||||
pub track_title: String,
|
|
||||||
pub other_tags: Vec<String>,
|
|
||||||
pub header: Vec<u8>,
|
|
||||||
/// Per-externalized-region reinsert prefix kept in memory. FLAC stores its
|
|
||||||
/// 4-byte PICTURE block header here; MP3 keeps whole frames in the original
|
|
||||||
/// file and stores an empty prefix.
|
|
||||||
pub picture_block_headers: Vec<Vec<u8>>,
|
|
||||||
pub picture_data_ranges: Vec<(u64, u64)>,
|
|
||||||
pub real_audio_start: u64,
|
|
||||||
pub vorbis_comment_offset: u64,
|
|
||||||
pub vorbis_comment_length: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MusicMetadata {
|
|
||||||
/// Size of the virtual file: rebuilt header + externalized regions + the
|
|
||||||
/// original audio tail. Format-agnostic.
|
|
||||||
pub fn virtual_size(&self, real_file_size: u64) -> u64 {
|
|
||||||
let pictures_size: u64 = self
|
|
||||||
.picture_block_headers
|
|
||||||
.iter()
|
|
||||||
.zip(self.picture_data_ranges.iter())
|
|
||||||
.map(|(prefix, (_, len))| prefix.len() as u64 + len)
|
|
||||||
.sum();
|
|
||||||
self.header.len() as u64 + pictures_size + (real_file_size - self.real_audio_start)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map symphonia's normalized tags onto the shared `MusicMetadata` fields.
|
|
||||||
/// Used by every format parser so the field mapping lives in one place.
|
|
||||||
pub(crate) fn extract_standard_tags(revision: &MetadataRevision, out: &mut MusicMetadata) {
|
|
||||||
for tag in revision.tags() {
|
|
||||||
let value = tag.value.to_string();
|
|
||||||
match tag.std_key {
|
|
||||||
Some(StandardTagKey::Artist) => out.artist.push(value),
|
|
||||||
Some(StandardTagKey::AlbumArtist) => out.album_artist = Some(value),
|
|
||||||
Some(StandardTagKey::Album) => out.album = value,
|
|
||||||
Some(StandardTagKey::TrackNumber) => {
|
|
||||||
out.track_number = value.parse::<i32>().unwrap_or(0)
|
|
||||||
}
|
|
||||||
Some(StandardTagKey::TrackTitle) => out.track_title = value,
|
|
||||||
_ => out.other_tags.push(format!("{}={}", tag.key, value)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod db {
|
|
||||||
pub mod music_metadata {
|
|
||||||
use sea_orm::entity::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
|
||||||
#[sea_orm(table_name = "music_metadata")]
|
|
||||||
pub struct Model {
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub inode: i64,
|
|
||||||
pub track_title: String,
|
|
||||||
pub album: String,
|
|
||||||
pub track_number: i32,
|
|
||||||
#[sea_orm(column_type = "Blob")]
|
|
||||||
pub header: Vec<u8>,
|
|
||||||
pub real_audio_start: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {}
|
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod artists {
|
|
||||||
use sea_orm::entity::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
|
||||||
#[sea_orm(table_name = "music_metadata_artists")]
|
|
||||||
pub struct Model {
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub inode: i64,
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub artist: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {}
|
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod other_tags {
|
|
||||||
use sea_orm::entity::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
|
||||||
#[sea_orm(table_name = "music_metadata_other_tags")]
|
|
||||||
pub struct Model {
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub inode: i64,
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub position: i32,
|
|
||||||
pub tag: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {}
|
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod pictures {
|
|
||||||
use sea_orm::entity::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
|
||||||
#[sea_orm(table_name = "music_metadata_pictures")]
|
|
||||||
pub struct Model {
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub inode: i64,
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub position: i32,
|
|
||||||
#[sea_orm(column_type = "Blob")]
|
|
||||||
pub block_header: Vec<u8>,
|
|
||||||
pub data_offset: i64,
|
|
||||||
pub data_length: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {}
|
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn virtual_size_calculation() {
|
|
||||||
let mut metadata = MusicMetadata::default();
|
|
||||||
metadata.header = vec![0u8; 100];
|
|
||||||
metadata.picture_block_headers = vec![vec![0u8; 4]];
|
|
||||||
metadata.picture_data_ranges = vec![(0, 50)];
|
|
||||||
metadata.real_audio_start = 200;
|
|
||||||
let file_size = 1000u64;
|
|
||||||
let virtual_size = metadata.virtual_size(file_size);
|
|
||||||
// 100 (header) + 4 (prefix) + 50 (picture data) + 800 (audio tail)
|
|
||||||
assert_eq!(virtual_size, 954);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,459 +0,0 @@
|
|||||||
// @generated
|
|
||||||
// This file is @generated by prost-build.
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct ReconcileRequest {
|
|
||||||
#[prost(message, repeated, tag = "1")]
|
|
||||||
pub entries: ::prost::alloc::vec::Vec<InodeHash>,
|
|
||||||
}
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct ReconcileResponse {
|
|
||||||
#[prost(message, repeated, tag = "1")]
|
|
||||||
pub changed: ::prost::alloc::vec::Vec<InodeHash>,
|
|
||||||
#[prost(uint64, repeated, tag = "2")]
|
|
||||||
pub deleted: ::prost::alloc::vec::Vec<u64>,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
|
||||||
pub struct InodeHash {
|
|
||||||
#[prost(uint64, tag = "1")]
|
|
||||||
pub inode: u64,
|
|
||||||
#[prost(uint64, tag = "2")]
|
|
||||||
pub hash: u64,
|
|
||||||
}
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
|
||||||
pub struct GetMetadataRequest {
|
|
||||||
#[prost(uint64, repeated, tag = "1")]
|
|
||||||
pub inodes: ::prost::alloc::vec::Vec<u64>,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
|
||||||
pub struct GetManifestRequest {}
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
|
||||||
pub struct GetFileRequest {
|
|
||||||
#[prost(uint64, tag = "1")]
|
|
||||||
pub id: u64,
|
|
||||||
/// When both are zero the server streams the entire file.
|
|
||||||
#[prost(uint64, tag = "2")]
|
|
||||||
pub start: u64,
|
|
||||||
#[prost(uint64, tag = "3")]
|
|
||||||
pub length: u64,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
|
||||||
pub struct SubscribeEventsRequest {}
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct ManifestEntry {
|
|
||||||
#[prost(uint64, tag = "1")]
|
|
||||||
pub id: u64,
|
|
||||||
#[prost(string, tag = "2")]
|
|
||||||
pub rel_path: ::prost::alloc::string::String,
|
|
||||||
#[prost(uint64, tag = "3")]
|
|
||||||
pub size: u64,
|
|
||||||
#[prost(uint64, tag = "4")]
|
|
||||||
pub mtime: u64,
|
|
||||||
#[prost(uint64, tag = "5")]
|
|
||||||
pub ctime: u64,
|
|
||||||
#[prost(uint64, tag = "6")]
|
|
||||||
pub crtime: u64,
|
|
||||||
#[prost(message, optional, tag = "7")]
|
|
||||||
pub music_metadata: ::core::option::Option<MusicMetadata>,
|
|
||||||
}
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct MusicMetadata {
|
|
||||||
#[prost(string, repeated, tag = "1")]
|
|
||||||
pub artist: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
|
||||||
#[prost(string, optional, tag = "2")]
|
|
||||||
pub album_artist: ::core::option::Option<::prost::alloc::string::String>,
|
|
||||||
#[prost(string, tag = "3")]
|
|
||||||
pub album: ::prost::alloc::string::String,
|
|
||||||
#[prost(int32, tag = "4")]
|
|
||||||
pub track_number: i32,
|
|
||||||
#[prost(string, tag = "5")]
|
|
||||||
pub track_title: ::prost::alloc::string::String,
|
|
||||||
#[prost(string, repeated, tag = "6")]
|
|
||||||
pub other_tags: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
|
||||||
#[prost(bytes = "vec", tag = "7")]
|
|
||||||
pub header: ::prost::alloc::vec::Vec<u8>,
|
|
||||||
#[prost(bytes = "vec", repeated, tag = "8")]
|
|
||||||
pub picture_block_headers: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
|
|
||||||
#[prost(message, repeated, tag = "9")]
|
|
||||||
pub picture_data_ranges: ::prost::alloc::vec::Vec<PictureDataRange>,
|
|
||||||
#[prost(uint64, tag = "10")]
|
|
||||||
pub real_audio_start: u64,
|
|
||||||
#[prost(uint64, tag = "11")]
|
|
||||||
pub vorbis_comment_offset: u64,
|
|
||||||
#[prost(uint64, tag = "12")]
|
|
||||||
pub vorbis_comment_length: u64,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
|
|
||||||
pub struct PictureDataRange {
|
|
||||||
#[prost(uint64, tag = "1")]
|
|
||||||
pub offset: u64,
|
|
||||||
#[prost(uint64, tag = "2")]
|
|
||||||
pub length: u64,
|
|
||||||
}
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
|
||||||
pub struct FileChunk {
|
|
||||||
#[prost(bytes = "vec", tag = "1")]
|
|
||||||
pub data: ::prost::alloc::vec::Vec<u8>,
|
|
||||||
#[prost(uint64, tag = "2")]
|
|
||||||
pub offset: u64,
|
|
||||||
#[prost(uint64, tag = "3")]
|
|
||||||
pub total_size: u64,
|
|
||||||
}
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
|
||||||
pub struct ChangeEvent {
|
|
||||||
/// "create", "modify", or "remove".
|
|
||||||
#[prost(string, tag = "1")]
|
|
||||||
pub kind: ::prost::alloc::string::String,
|
|
||||||
}
|
|
||||||
/// Encoded file descriptor set for the `musicfs` package
|
|
||||||
pub const FILE_DESCRIPTOR_SET: &[u8] = &[
|
|
||||||
0x0a, 0xb0, 0x2b, 0x0a, 0x0d, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x70, 0x72, 0x6f,
|
|
||||||
0x74, 0x6f, 0x12, 0x07, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x22, 0x40, 0x0a, 0x10, 0x52,
|
|
||||||
0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
|
||||||
0x2c, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
|
||||||
0x32, 0x12, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x49, 0x6e, 0x6f, 0x64, 0x65,
|
|
||||||
0x48, 0x61, 0x73, 0x68, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x5b, 0x0a,
|
|
||||||
0x11, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
|
||||||
0x73, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x01, 0x20,
|
|
||||||
0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x49, 0x6e,
|
|
||||||
0x6f, 0x64, 0x65, 0x48, 0x61, 0x73, 0x68, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64,
|
|
||||||
0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28,
|
|
||||||
0x04, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x09, 0x49, 0x6e,
|
|
||||||
0x6f, 0x64, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x6f, 0x64, 0x65,
|
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a,
|
|
||||||
0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x68, 0x61, 0x73,
|
|
||||||
0x68, 0x22, 0x2c, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x6f, 0x64, 0x65,
|
|
||||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x06, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22,
|
|
||||||
0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65,
|
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4e, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65,
|
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
|
|
||||||
0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74,
|
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x0a,
|
|
||||||
0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c,
|
|
||||||
0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x18, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
|
||||||
0x62, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
|
|
||||||
0xd1, 0x01, 0x0a, 0x0d, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72,
|
|
||||||
0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69,
|
|
||||||
0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04,
|
|
||||||
0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65,
|
|
||||||
0x12, 0x14, 0x0a, 0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52,
|
|
||||||
0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, 0x18,
|
|
||||||
0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
|
||||||
0x63, 0x72, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x72,
|
|
||||||
0x74, 0x69, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x0e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x5f, 0x6d, 0x65,
|
|
||||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d,
|
|
||||||
0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74, 0x61,
|
|
||||||
0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x64,
|
|
||||||
0x61, 0x74, 0x61, 0x22, 0x82, 0x04, 0x0a, 0x0d, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x74,
|
|
||||||
0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x18,
|
|
||||||
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a,
|
|
||||||
0x0c, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x41, 0x72, 0x74, 0x69,
|
|
||||||
0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x18, 0x03,
|
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x62, 0x75, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x74,
|
|
||||||
0x72, 0x61, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
|
|
||||||
0x05, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1f,
|
|
||||||
0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12,
|
|
||||||
0x1d, 0x0a, 0x0a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x06, 0x20,
|
|
||||||
0x03, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x12, 0x16,
|
|
||||||
0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06,
|
|
||||||
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72,
|
|
||||||
0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18,
|
|
||||||
0x08, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x13, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x42, 0x6c,
|
|
||||||
0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x49, 0x0a, 0x13, 0x70, 0x69,
|
|
||||||
0x63, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65,
|
|
||||||
0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66,
|
|
||||||
0x73, 0x2e, 0x50, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x61, 0x6e,
|
|
||||||
0x67, 0x65, 0x52, 0x11, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52,
|
|
||||||
0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x61, 0x75,
|
|
||||||
0x64, 0x69, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52,
|
|
||||||
0x0e, 0x72, 0x65, 0x61, 0x6c, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12,
|
|
||||||
0x32, 0x0a, 0x15, 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e,
|
|
||||||
0x74, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13,
|
|
||||||
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x66, 0x66,
|
|
||||||
0x73, 0x65, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x5f, 0x63, 0x6f,
|
|
||||||
0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01,
|
|
||||||
0x28, 0x04, 0x52, 0x13, 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e,
|
|
||||||
0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x61, 0x6c, 0x62, 0x75,
|
|
||||||
0x6d, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x73, 0x74, 0x22, 0x42, 0x0a, 0x10, 0x50, 0x69, 0x63, 0x74,
|
|
||||||
0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
|
||||||
0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66,
|
|
||||||
0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x02,
|
|
||||||
0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x56, 0x0a, 0x09,
|
|
||||||
0x46, 0x69, 0x6c, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74,
|
|
||||||
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a,
|
|
||||||
0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f,
|
|
||||||
0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73,
|
|
||||||
0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c,
|
|
||||||
0x53, 0x69, 0x7a, 0x65, 0x22, 0x21, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x76,
|
|
||||||
0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x32, 0xdf, 0x02, 0x0a, 0x07, 0x4d, 0x75, 0x73, 0x69,
|
|
||||||
0x63, 0x46, 0x73, 0x12, 0x42, 0x0a, 0x09, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65,
|
|
||||||
0x12, 0x19, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6e,
|
|
||||||
0x63, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6d, 0x75,
|
|
||||||
0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x52,
|
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x65,
|
|
||||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73,
|
|
||||||
0x2e, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75,
|
|
||||||
0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x4d, 0x61,
|
|
||||||
0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x30, 0x01, 0x12, 0x44, 0x0a,
|
|
||||||
0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x2e, 0x6d,
|
|
||||||
0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65,
|
|
||||||
0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6d, 0x75, 0x73, 0x69,
|
|
||||||
0x63, 0x66, 0x73, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72,
|
|
||||||
0x79, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x17,
|
|
||||||
0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65,
|
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66,
|
|
||||||
0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x30, 0x01, 0x12, 0x4a, 0x0a,
|
|
||||||
0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73,
|
|
||||||
0x12, 0x1f, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63,
|
|
||||||
0x72, 0x69, 0x62, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
|
||||||
0x74, 0x1a, 0x14, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x6e,
|
|
||||||
0x67, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x4a, 0x8f, 0x1e, 0x0a, 0x06, 0x12, 0x04,
|
|
||||||
0x00, 0x00, 0x66, 0x01, 0x0a, 0x08, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x00, 0x00, 0x12, 0x0a, 0x08,
|
|
||||||
0x0a, 0x01, 0x02, 0x12, 0x03, 0x02, 0x00, 0x10, 0x0a, 0x62, 0x0a, 0x02, 0x06, 0x00, 0x12, 0x04,
|
|
||||||
0x06, 0x00, 0x20, 0x01, 0x1a, 0x56, 0x20, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x46, 0x73, 0x20, 0x73,
|
|
||||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x73, 0x20, 0x61,
|
|
||||||
0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x73, 0x69, 0x64, 0x65, 0x20, 0x6d, 0x75, 0x73,
|
|
||||||
0x69, 0x63, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20,
|
|
||||||
0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x66, 0x73, 0x0a, 0x20,
|
|
||||||
0x46, 0x55, 0x53, 0x45, 0x20, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x0a, 0x0a, 0x03,
|
|
||||||
0x06, 0x00, 0x01, 0x12, 0x03, 0x06, 0x08, 0x0f, 0x0a, 0xac, 0x02, 0x0a, 0x04, 0x06, 0x00, 0x02,
|
|
||||||
0x00, 0x12, 0x03, 0x0b, 0x02, 0x3e, 0x1a, 0x9e, 0x02, 0x20, 0x48, 0x61, 0x73, 0x68, 0x2d, 0x62,
|
|
||||||
0x61, 0x73, 0x65, 0x64, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x69, 0x61, 0x74,
|
|
||||||
0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x65, 0x6e, 0x64,
|
|
||||||
0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x28, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x68, 0x61,
|
|
||||||
0x73, 0x68, 0x29, 0x20, 0x70, 0x61, 0x69, 0x72, 0x73, 0x20, 0x69, 0x74, 0x0a, 0x20, 0x63, 0x75,
|
|
||||||
0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x20, 0x68, 0x61, 0x73, 0x3b, 0x20, 0x73, 0x65, 0x72,
|
|
||||||
0x76, 0x65, 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x77, 0x69, 0x74,
|
|
||||||
0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x75, 0x62, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x61,
|
|
||||||
0x74, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x20, 0x28, 0x6e, 0x65, 0x77, 0x20, 0x6f,
|
|
||||||
0x72, 0x0a, 0x20, 0x68, 0x61, 0x73, 0x68, 0x2d, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e,
|
|
||||||
0x74, 0x29, 0x20, 0x70, 0x6c, 0x75, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x6f, 0x64,
|
|
||||||
0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x68, 0x61,
|
|
||||||
0x73, 0x20, 0x62, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
|
||||||
0x20, 0x6e, 0x6f, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x0a, 0x20, 0x68, 0x61, 0x73, 0x2e,
|
|
||||||
0x20, 0x43, 0x68, 0x65, 0x61, 0x70, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x69,
|
|
||||||
0x72, 0x65, 0x20, 0xe2, 0x80, 0x94, 0x20, 0x6e, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6d,
|
|
||||||
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x65, 0x73, 0x20,
|
|
||||||
0x75, 0x6e, 0x74, 0x69, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
|
||||||
0x20, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x01,
|
|
||||||
0x12, 0x03, 0x0b, 0x06, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03,
|
|
||||||
0x0b, 0x10, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0b, 0x2b,
|
|
||||||
0x3c, 0x0a, 0x94, 0x01, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x01, 0x12, 0x03, 0x0f, 0x02, 0x45, 0x1a,
|
|
||||||
0x86, 0x01, 0x20, 0x46, 0x65, 0x74, 0x63, 0x68, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x6d, 0x65,
|
|
||||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x73, 0x70, 0x65,
|
|
||||||
0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x6e, 0x6f,
|
|
||||||
0x64, 0x65, 0x73, 0x2e, 0x20, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x61, 0x66, 0x74, 0x65,
|
|
||||||
0x72, 0x20, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x0a, 0x20, 0x72, 0x65, 0x74,
|
|
||||||
0x75, 0x72, 0x6e, 0x73, 0x20, 0x61, 0x20, 0x6e, 0x6f, 0x6e, 0x2d, 0x65, 0x6d, 0x70, 0x74, 0x79,
|
|
||||||
0x20, 0x60, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x60, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x2c,
|
|
||||||
0x20, 0x66, 0x6f, 0x72, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, 0x20,
|
|
||||||
0x69, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01,
|
|
||||||
0x01, 0x12, 0x03, 0x0f, 0x06, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x02, 0x12,
|
|
||||||
0x03, 0x0f, 0x12, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x06, 0x12, 0x03, 0x0f,
|
|
||||||
0x2f, 0x35, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x0f, 0x36, 0x43,
|
|
||||||
0x0a, 0xa8, 0x01, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x02, 0x12, 0x03, 0x14, 0x02, 0x45, 0x1a, 0x9a,
|
|
||||||
0x01, 0x20, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x65, 0x76, 0x65, 0x72, 0x79, 0x20, 0x66,
|
|
||||||
0x69, 0x6c, 0x65, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65,
|
|
||||||
0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x6e,
|
|
||||||
0x69, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x52, 0x50, 0x43, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x66, 0x69,
|
|
||||||
0x72, 0x73, 0x74, 0x2d, 0x72, 0x75, 0x6e, 0x0a, 0x20, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72,
|
|
||||||
0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x72, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x2d, 0x72,
|
|
||||||
0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x64, 0x69, 0x66, 0x66,
|
|
||||||
0x2d, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65,
|
|
||||||
0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x6e, 0x6f,
|
|
||||||
0x72, 0x6d, 0x61, 0x6c, 0x20, 0x63, 0x61, 0x73, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06,
|
|
||||||
0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x14, 0x06, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02,
|
|
||||||
0x02, 0x02, 0x12, 0x03, 0x14, 0x12, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x06,
|
|
||||||
0x12, 0x03, 0x14, 0x2f, 0x35, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03,
|
|
||||||
0x14, 0x36, 0x43, 0x0a, 0xf8, 0x01, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x03, 0x12, 0x03, 0x1a, 0x02,
|
|
||||||
0x39, 0x1a, 0xea, 0x01, 0x20, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20,
|
|
||||||
0x62, 0x79, 0x74, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x66, 0x69, 0x6c,
|
|
||||||
0x65, 0x2e, 0x20, 0x49, 0x66, 0x20, 0x60, 0x73, 0x74, 0x61, 0x72, 0x74, 0x60, 0x20, 0x61, 0x6e,
|
|
||||||
0x64, 0x20, 0x60, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x60, 0x20, 0x61, 0x72, 0x65, 0x20, 0x62,
|
|
||||||
0x6f, 0x74, 0x68, 0x20, 0x7a, 0x65, 0x72, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x73, 0x65,
|
|
||||||
0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x20, 0x74, 0x68, 0x65,
|
|
||||||
0x20, 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x3b, 0x20, 0x6f, 0x74,
|
|
||||||
0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x74, 0x68, 0x65,
|
|
||||||
0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65,
|
|
||||||
0x2e, 0x20, 0x54, 0x68, 0x65, 0x0a, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x63, 0x68, 0x75,
|
|
||||||
0x6e, 0x6b, 0x20, 0x63, 0x61, 0x72, 0x72, 0x69, 0x65, 0x73, 0x20, 0x60, 0x74, 0x6f, 0x74, 0x61,
|
|
||||||
0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x60, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x60, 0x6f, 0x66, 0x66,
|
|
||||||
0x73, 0x65, 0x74, 0x60, 0x3b, 0x20, 0x73, 0x75, 0x62, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74,
|
|
||||||
0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65,
|
|
||||||
0x0a, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x2e, 0x0a, 0x0a, 0x0c,
|
|
||||||
0x0a, 0x05, 0x06, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x1a, 0x06, 0x0d, 0x0a, 0x0c, 0x0a, 0x05,
|
|
||||||
0x06, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x1a, 0x0e, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00,
|
|
||||||
0x02, 0x03, 0x06, 0x12, 0x03, 0x1a, 0x27, 0x2d, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x03,
|
|
||||||
0x03, 0x12, 0x03, 0x1a, 0x2e, 0x37, 0x0a, 0xc2, 0x01, 0x0a, 0x04, 0x06, 0x00, 0x02, 0x04, 0x12,
|
|
||||||
0x03, 0x1f, 0x02, 0x4b, 0x1a, 0xb4, 0x01, 0x20, 0x4c, 0x6f, 0x6e, 0x67, 0x2d, 0x6c, 0x69, 0x76,
|
|
||||||
0x65, 0x64, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x6f, 0x66, 0x20, 0x66, 0x69, 0x6c,
|
|
||||||
0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x65,
|
|
||||||
0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
|
||||||
0x74, 0x20, 0x75, 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, 0x20, 0x61, 0x73, 0x0a,
|
|
||||||
0x20, 0x77, 0x61, 0x6b, 0x65, 0x2d, 0x75, 0x70, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, 0x66,
|
|
||||||
0x65, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73,
|
|
||||||
0x74, 0x3b, 0x20, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x61,
|
|
||||||
0x6c, 0x77, 0x61, 0x79, 0x73, 0x20, 0x72, 0x65, 0x73, 0x74, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74,
|
|
||||||
0x68, 0x65, 0x20, 0x68, 0x61, 0x73, 0x68, 0x0a, 0x20, 0x64, 0x69, 0x66, 0x66, 0x2c, 0x20, 0x6e,
|
|
||||||
0x65, 0x76, 0x65, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e,
|
|
||||||
0x74, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x06,
|
|
||||||
0x00, 0x02, 0x04, 0x01, 0x12, 0x03, 0x1f, 0x06, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02,
|
|
||||||
0x04, 0x02, 0x12, 0x03, 0x1f, 0x16, 0x2c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x06,
|
|
||||||
0x12, 0x03, 0x1f, 0x37, 0x3d, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x04, 0x03, 0x12, 0x03,
|
|
||||||
0x1f, 0x3e, 0x49, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x22, 0x00, 0x24, 0x01, 0x0a,
|
|
||||||
0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x22, 0x08, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
|
||||||
0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x23, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02,
|
|
||||||
0x00, 0x12, 0x03, 0x23, 0x02, 0x21, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12,
|
|
||||||
0x03, 0x23, 0x0b, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x23,
|
|
||||||
0x15, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x23, 0x1f, 0x20,
|
|
||||||
0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x26, 0x00, 0x29, 0x01, 0x0a, 0x0a, 0x0a, 0x03,
|
|
||||||
0x04, 0x01, 0x01, 0x12, 0x03, 0x26, 0x08, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00,
|
|
||||||
0x04, 0x12, 0x03, 0x27, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03,
|
|
||||||
0x27, 0x02, 0x21, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x27, 0x0b,
|
|
||||||
0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x27, 0x15, 0x1c, 0x0a,
|
|
||||||
0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x27, 0x1f, 0x20, 0x0a, 0x0c, 0x0a,
|
|
||||||
0x05, 0x04, 0x01, 0x02, 0x01, 0x04, 0x12, 0x03, 0x28, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04,
|
|
||||||
0x01, 0x02, 0x01, 0x12, 0x03, 0x28, 0x02, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01,
|
|
||||||
0x05, 0x12, 0x03, 0x28, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12,
|
|
||||||
0x03, 0x28, 0x12, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x28,
|
|
||||||
0x1c, 0x1d, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x2b, 0x00, 0x2e, 0x01, 0x0a, 0x0a,
|
|
||||||
0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, 0x03, 0x2b, 0x08, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02,
|
|
||||||
0x02, 0x00, 0x05, 0x12, 0x03, 0x2c, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x00,
|
|
||||||
0x12, 0x03, 0x2c, 0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03,
|
|
||||||
0x2c, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x2c, 0x11,
|
|
||||||
0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x05, 0x12, 0x03, 0x2d, 0x02, 0x08, 0x0a,
|
|
||||||
0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x03, 0x2d, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05,
|
|
||||||
0x04, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, 0x2d, 0x09, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02,
|
|
||||||
0x02, 0x01, 0x03, 0x12, 0x03, 0x2d, 0x10, 0x11, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x03, 0x12, 0x04,
|
|
||||||
0x30, 0x00, 0x32, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x03, 0x01, 0x12, 0x03, 0x30, 0x08, 0x1a,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x04, 0x12, 0x03, 0x31, 0x02, 0x0a, 0x0a, 0x0b,
|
|
||||||
0x0a, 0x04, 0x04, 0x03, 0x02, 0x00, 0x12, 0x03, 0x31, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
|
||||||
0x03, 0x02, 0x00, 0x05, 0x12, 0x03, 0x31, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02,
|
|
||||||
0x00, 0x01, 0x12, 0x03, 0x31, 0x12, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x03,
|
|
||||||
0x12, 0x03, 0x31, 0x1b, 0x1c, 0x0a, 0x09, 0x0a, 0x02, 0x04, 0x04, 0x12, 0x03, 0x34, 0x00, 0x1d,
|
|
||||||
0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x04, 0x01, 0x12, 0x03, 0x34, 0x08, 0x1a, 0x0a, 0x0a, 0x0a, 0x02,
|
|
||||||
0x04, 0x05, 0x12, 0x04, 0x36, 0x00, 0x3b, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x05, 0x01, 0x12,
|
|
||||||
0x03, 0x36, 0x08, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x05, 0x12, 0x03, 0x37,
|
|
||||||
0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x00, 0x12, 0x03, 0x37, 0x02, 0x10, 0x0a,
|
|
||||||
0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x01, 0x12, 0x03, 0x37, 0x09, 0x0b, 0x0a, 0x0c, 0x0a,
|
|
||||||
0x05, 0x04, 0x05, 0x02, 0x00, 0x03, 0x12, 0x03, 0x37, 0x0e, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
|
||||||
0x05, 0x02, 0x01, 0x05, 0x12, 0x03, 0x39, 0x02, 0x08, 0x0a, 0x45, 0x0a, 0x04, 0x04, 0x05, 0x02,
|
|
||||||
0x01, 0x12, 0x03, 0x39, 0x02, 0x13, 0x1a, 0x38, 0x20, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x62, 0x6f,
|
|
||||||
0x74, 0x68, 0x20, 0x61, 0x72, 0x65, 0x20, 0x7a, 0x65, 0x72, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20,
|
|
||||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x20, 0x74,
|
|
||||||
0x68, 0x65, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x0a,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x01, 0x12, 0x03, 0x39, 0x09, 0x0e, 0x0a, 0x0c,
|
|
||||||
0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x03, 0x12, 0x03, 0x39, 0x11, 0x12, 0x0a, 0x0c, 0x0a, 0x05,
|
|
||||||
0x04, 0x05, 0x02, 0x02, 0x05, 0x12, 0x03, 0x3a, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05,
|
|
||||||
0x02, 0x02, 0x12, 0x03, 0x3a, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, 0x01,
|
|
||||||
0x12, 0x03, 0x3a, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, 0x03, 0x12, 0x03,
|
|
||||||
0x3a, 0x12, 0x13, 0x0a, 0x09, 0x0a, 0x02, 0x04, 0x06, 0x12, 0x03, 0x3d, 0x00, 0x21, 0x0a, 0x0a,
|
|
||||||
0x0a, 0x03, 0x04, 0x06, 0x01, 0x12, 0x03, 0x3d, 0x08, 0x1e, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x07,
|
|
||||||
0x12, 0x04, 0x3f, 0x00, 0x47, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x07, 0x01, 0x12, 0x03, 0x3f,
|
|
||||||
0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x05, 0x12, 0x03, 0x40, 0x02, 0x08,
|
|
||||||
0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x00, 0x12, 0x03, 0x40, 0x02, 0x10, 0x0a, 0x0c, 0x0a,
|
|
||||||
0x05, 0x04, 0x07, 0x02, 0x00, 0x01, 0x12, 0x03, 0x40, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
|
||||||
0x07, 0x02, 0x00, 0x03, 0x12, 0x03, 0x40, 0x0e, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02,
|
|
||||||
0x01, 0x05, 0x12, 0x03, 0x41, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x01, 0x12,
|
|
||||||
0x03, 0x41, 0x02, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x01, 0x01, 0x12, 0x03, 0x41,
|
|
||||||
0x09, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x01, 0x03, 0x12, 0x03, 0x41, 0x14, 0x15,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x02, 0x05, 0x12, 0x03, 0x42, 0x02, 0x08, 0x0a, 0x0b,
|
|
||||||
0x0a, 0x04, 0x04, 0x07, 0x02, 0x02, 0x12, 0x03, 0x42, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
|
||||||
0x07, 0x02, 0x02, 0x01, 0x12, 0x03, 0x42, 0x09, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02,
|
|
||||||
0x02, 0x03, 0x12, 0x03, 0x42, 0x10, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x03, 0x05,
|
|
||||||
0x12, 0x03, 0x43, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x03, 0x12, 0x03, 0x43,
|
|
||||||
0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x03, 0x01, 0x12, 0x03, 0x43, 0x09, 0x0e,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x03, 0x03, 0x12, 0x03, 0x43, 0x11, 0x12, 0x0a, 0x0c,
|
|
||||||
0x0a, 0x05, 0x04, 0x07, 0x02, 0x04, 0x05, 0x12, 0x03, 0x44, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04,
|
|
||||||
0x04, 0x07, 0x02, 0x04, 0x12, 0x03, 0x44, 0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02,
|
|
||||||
0x04, 0x01, 0x12, 0x03, 0x44, 0x09, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x04, 0x03,
|
|
||||||
0x12, 0x03, 0x44, 0x11, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x05, 0x05, 0x12, 0x03,
|
|
||||||
0x45, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x05, 0x12, 0x03, 0x45, 0x02, 0x14,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x05, 0x01, 0x12, 0x03, 0x45, 0x09, 0x0f, 0x0a, 0x0c,
|
|
||||||
0x0a, 0x05, 0x04, 0x07, 0x02, 0x05, 0x03, 0x12, 0x03, 0x45, 0x12, 0x13, 0x0a, 0x0c, 0x0a, 0x05,
|
|
||||||
0x04, 0x07, 0x02, 0x06, 0x06, 0x12, 0x03, 0x46, 0x02, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x07,
|
|
||||||
0x02, 0x06, 0x12, 0x03, 0x46, 0x02, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x06, 0x01,
|
|
||||||
0x12, 0x03, 0x46, 0x10, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x06, 0x03, 0x12, 0x03,
|
|
||||||
0x46, 0x21, 0x22, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x08, 0x12, 0x04, 0x49, 0x00, 0x56, 0x01, 0x0a,
|
|
||||||
0x0a, 0x0a, 0x03, 0x04, 0x08, 0x01, 0x12, 0x03, 0x49, 0x08, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
|
||||||
0x08, 0x02, 0x00, 0x04, 0x12, 0x03, 0x4a, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02,
|
|
||||||
0x00, 0x12, 0x03, 0x4a, 0x02, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x00, 0x05, 0x12,
|
|
||||||
0x03, 0x4a, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x00, 0x01, 0x12, 0x03, 0x4a,
|
|
||||||
0x12, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x00, 0x03, 0x12, 0x03, 0x4a, 0x1b, 0x1c,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x04, 0x12, 0x03, 0x4b, 0x02, 0x0a, 0x0a, 0x0b,
|
|
||||||
0x0a, 0x04, 0x04, 0x08, 0x02, 0x01, 0x12, 0x03, 0x4b, 0x02, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
|
||||||
0x08, 0x02, 0x01, 0x05, 0x12, 0x03, 0x4b, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02,
|
|
||||||
0x01, 0x01, 0x12, 0x03, 0x4b, 0x12, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x03,
|
|
||||||
0x12, 0x03, 0x4b, 0x21, 0x22, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x02, 0x05, 0x12, 0x03,
|
|
||||||
0x4c, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x02, 0x12, 0x03, 0x4c, 0x02, 0x13,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x02, 0x01, 0x12, 0x03, 0x4c, 0x09, 0x0e, 0x0a, 0x0c,
|
|
||||||
0x0a, 0x05, 0x04, 0x08, 0x02, 0x02, 0x03, 0x12, 0x03, 0x4c, 0x11, 0x12, 0x0a, 0x0c, 0x0a, 0x05,
|
|
||||||
0x04, 0x08, 0x02, 0x03, 0x05, 0x12, 0x03, 0x4d, 0x02, 0x07, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08,
|
|
||||||
0x02, 0x03, 0x12, 0x03, 0x4d, 0x02, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x03, 0x01,
|
|
||||||
0x12, 0x03, 0x4d, 0x08, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x03, 0x03, 0x12, 0x03,
|
|
||||||
0x4d, 0x17, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x04, 0x05, 0x12, 0x03, 0x4e, 0x02,
|
|
||||||
0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x04, 0x12, 0x03, 0x4e, 0x02, 0x19, 0x0a, 0x0c,
|
|
||||||
0x0a, 0x05, 0x04, 0x08, 0x02, 0x04, 0x01, 0x12, 0x03, 0x4e, 0x09, 0x14, 0x0a, 0x0c, 0x0a, 0x05,
|
|
||||||
0x04, 0x08, 0x02, 0x04, 0x03, 0x12, 0x03, 0x4e, 0x17, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08,
|
|
||||||
0x02, 0x05, 0x04, 0x12, 0x03, 0x4f, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x05,
|
|
||||||
0x12, 0x03, 0x4f, 0x02, 0x21, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x05, 0x05, 0x12, 0x03,
|
|
||||||
0x4f, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x05, 0x01, 0x12, 0x03, 0x4f, 0x12,
|
|
||||||
0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x05, 0x03, 0x12, 0x03, 0x4f, 0x1f, 0x20, 0x0a,
|
|
||||||
0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x06, 0x05, 0x12, 0x03, 0x50, 0x02, 0x07, 0x0a, 0x0b, 0x0a,
|
|
||||||
0x04, 0x04, 0x08, 0x02, 0x06, 0x12, 0x03, 0x50, 0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08,
|
|
||||||
0x02, 0x06, 0x01, 0x12, 0x03, 0x50, 0x08, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x06,
|
|
||||||
0x03, 0x12, 0x03, 0x50, 0x11, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x07, 0x04, 0x12,
|
|
||||||
0x03, 0x51, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x07, 0x12, 0x03, 0x51, 0x02,
|
|
||||||
0x2b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x07, 0x05, 0x12, 0x03, 0x51, 0x0b, 0x10, 0x0a,
|
|
||||||
0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x07, 0x01, 0x12, 0x03, 0x51, 0x11, 0x26, 0x0a, 0x0c, 0x0a,
|
|
||||||
0x05, 0x04, 0x08, 0x02, 0x07, 0x03, 0x12, 0x03, 0x51, 0x29, 0x2a, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
|
||||||
0x08, 0x02, 0x08, 0x04, 0x12, 0x03, 0x52, 0x02, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02,
|
|
||||||
0x08, 0x12, 0x03, 0x52, 0x02, 0x34, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x08, 0x06, 0x12,
|
|
||||||
0x03, 0x52, 0x0b, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x08, 0x01, 0x12, 0x03, 0x52,
|
|
||||||
0x1c, 0x2f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x08, 0x03, 0x12, 0x03, 0x52, 0x32, 0x33,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x09, 0x05, 0x12, 0x03, 0x53, 0x02, 0x08, 0x0a, 0x0b,
|
|
||||||
0x0a, 0x04, 0x04, 0x08, 0x02, 0x09, 0x12, 0x03, 0x53, 0x02, 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x04,
|
|
||||||
0x08, 0x02, 0x09, 0x01, 0x12, 0x03, 0x53, 0x09, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02,
|
|
||||||
0x09, 0x03, 0x12, 0x03, 0x53, 0x1c, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x0a, 0x05,
|
|
||||||
0x12, 0x03, 0x54, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x0a, 0x12, 0x03, 0x54,
|
|
||||||
0x02, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x0a, 0x01, 0x12, 0x03, 0x54, 0x09, 0x1e,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x0a, 0x03, 0x12, 0x03, 0x54, 0x21, 0x23, 0x0a, 0x0c,
|
|
||||||
0x0a, 0x05, 0x04, 0x08, 0x02, 0x0b, 0x05, 0x12, 0x03, 0x55, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04,
|
|
||||||
0x04, 0x08, 0x02, 0x0b, 0x12, 0x03, 0x55, 0x02, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02,
|
|
||||||
0x0b, 0x01, 0x12, 0x03, 0x55, 0x09, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x0b, 0x03,
|
|
||||||
0x12, 0x03, 0x55, 0x21, 0x23, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x09, 0x12, 0x04, 0x58, 0x00, 0x5b,
|
|
||||||
0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x09, 0x01, 0x12, 0x03, 0x58, 0x08, 0x18, 0x0a, 0x0c, 0x0a,
|
|
||||||
0x05, 0x04, 0x09, 0x02, 0x00, 0x05, 0x12, 0x03, 0x59, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04,
|
|
||||||
0x09, 0x02, 0x00, 0x12, 0x03, 0x59, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00,
|
|
||||||
0x01, 0x12, 0x03, 0x59, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x03, 0x12,
|
|
||||||
0x03, 0x59, 0x12, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x01, 0x05, 0x12, 0x03, 0x5a,
|
|
||||||
0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x09, 0x02, 0x01, 0x12, 0x03, 0x5a, 0x02, 0x14, 0x0a,
|
|
||||||
0x0c, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x01, 0x01, 0x12, 0x03, 0x5a, 0x09, 0x0f, 0x0a, 0x0c, 0x0a,
|
|
||||||
0x05, 0x04, 0x09, 0x02, 0x01, 0x03, 0x12, 0x03, 0x5a, 0x12, 0x13, 0x0a, 0x0a, 0x0a, 0x02, 0x04,
|
|
||||||
0x0a, 0x12, 0x04, 0x5d, 0x00, 0x61, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0a, 0x01, 0x12, 0x03,
|
|
||||||
0x5d, 0x08, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x00, 0x05, 0x12, 0x03, 0x5e, 0x02,
|
|
||||||
0x07, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x00, 0x12, 0x03, 0x5e, 0x02, 0x11, 0x0a, 0x0c,
|
|
||||||
0x0a, 0x05, 0x04, 0x0a, 0x02, 0x00, 0x01, 0x12, 0x03, 0x5e, 0x08, 0x0c, 0x0a, 0x0c, 0x0a, 0x05,
|
|
||||||
0x04, 0x0a, 0x02, 0x00, 0x03, 0x12, 0x03, 0x5e, 0x0f, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a,
|
|
||||||
0x02, 0x01, 0x05, 0x12, 0x03, 0x5f, 0x02, 0x08, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x01,
|
|
||||||
0x12, 0x03, 0x5f, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x01, 0x01, 0x12, 0x03,
|
|
||||||
0x5f, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x01, 0x03, 0x12, 0x03, 0x5f, 0x12,
|
|
||||||
0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x02, 0x05, 0x12, 0x03, 0x60, 0x02, 0x08, 0x0a,
|
|
||||||
0x0b, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x02, 0x12, 0x03, 0x60, 0x02, 0x18, 0x0a, 0x0c, 0x0a, 0x05,
|
|
||||||
0x04, 0x0a, 0x02, 0x02, 0x01, 0x12, 0x03, 0x60, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0a,
|
|
||||||
0x02, 0x02, 0x03, 0x12, 0x03, 0x60, 0x16, 0x17, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x0b, 0x12, 0x04,
|
|
||||||
0x63, 0x00, 0x66, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x0b, 0x01, 0x12, 0x03, 0x63, 0x08, 0x13,
|
|
||||||
0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x05, 0x12, 0x03, 0x65, 0x02, 0x08, 0x0a, 0x2f,
|
|
||||||
0x0a, 0x04, 0x04, 0x0b, 0x02, 0x00, 0x12, 0x03, 0x65, 0x02, 0x12, 0x1a, 0x22, 0x20, 0x22, 0x63,
|
|
||||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x22, 0x2c, 0x20, 0x22, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x22,
|
|
||||||
0x2c, 0x20, 0x6f, 0x72, 0x20, 0x22, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x22, 0x2e, 0x0a, 0x0a,
|
|
||||||
0x0c, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x01, 0x12, 0x03, 0x65, 0x09, 0x0d, 0x0a, 0x0c, 0x0a,
|
|
||||||
0x05, 0x04, 0x0b, 0x02, 0x00, 0x03, 0x12, 0x03, 0x65, 0x10, 0x11, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
|
||||||
0x74, 0x6f, 0x33,
|
|
||||||
];
|
|
||||||
include!("musicfs.tonic.rs");
|
|
||||||
// @@protoc_insertion_point(module)
|
|
||||||
@@ -1,544 +0,0 @@
|
|||||||
// @generated
|
|
||||||
/// Generated client implementations.
|
|
||||||
pub mod music_fs_client {
|
|
||||||
#![allow(
|
|
||||||
unused_variables,
|
|
||||||
dead_code,
|
|
||||||
missing_docs,
|
|
||||||
clippy::wildcard_imports,
|
|
||||||
clippy::let_unit_value
|
|
||||||
)]
|
|
||||||
use tonic::codegen::http::Uri;
|
|
||||||
use tonic::codegen::*;
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct MusicFsClient<T> {
|
|
||||||
inner: tonic::client::Grpc<T>,
|
|
||||||
}
|
|
||||||
impl MusicFsClient<tonic::transport::Channel> {
|
|
||||||
/// Attempt to create a new client by connecting to a given endpoint.
|
|
||||||
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
|
||||||
where
|
|
||||||
D: TryInto<tonic::transport::Endpoint>,
|
|
||||||
D::Error: Into<StdError>,
|
|
||||||
{
|
|
||||||
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
|
|
||||||
Ok(Self::new(conn))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> MusicFsClient<T>
|
|
||||||
where
|
|
||||||
T: tonic::client::GrpcService<tonic::body::Body>,
|
|
||||||
T::Error: Into<StdError>,
|
|
||||||
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
|
|
||||||
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
|
|
||||||
{
|
|
||||||
pub fn new(inner: T) -> Self {
|
|
||||||
let inner = tonic::client::Grpc::new(inner);
|
|
||||||
Self { inner }
|
|
||||||
}
|
|
||||||
pub fn with_origin(inner: T, origin: Uri) -> Self {
|
|
||||||
let inner = tonic::client::Grpc::with_origin(inner, origin);
|
|
||||||
Self { inner }
|
|
||||||
}
|
|
||||||
pub fn with_interceptor<F>(
|
|
||||||
inner: T,
|
|
||||||
interceptor: F,
|
|
||||||
) -> MusicFsClient<InterceptedService<T, F>>
|
|
||||||
where
|
|
||||||
F: tonic::service::Interceptor,
|
|
||||||
T::ResponseBody: Default,
|
|
||||||
T: tonic::codegen::Service<
|
|
||||||
http::Request<tonic::body::Body>,
|
|
||||||
Response = http::Response<
|
|
||||||
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
<T as tonic::codegen::Service<http::Request<tonic::body::Body>>>::Error:
|
|
||||||
Into<StdError> + std::marker::Send + std::marker::Sync,
|
|
||||||
{
|
|
||||||
MusicFsClient::new(InterceptedService::new(inner, interceptor))
|
|
||||||
}
|
|
||||||
/// Compress requests with the given encoding.
|
|
||||||
///
|
|
||||||
/// This requires the server to support it otherwise it might respond with an
|
|
||||||
/// error.
|
|
||||||
#[must_use]
|
|
||||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
|
||||||
self.inner = self.inner.send_compressed(encoding);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Enable decompressing responses.
|
|
||||||
#[must_use]
|
|
||||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
|
||||||
self.inner = self.inner.accept_compressed(encoding);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Limits the maximum size of a decoded message.
|
|
||||||
///
|
|
||||||
/// Default: `4MB`
|
|
||||||
#[must_use]
|
|
||||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
|
||||||
self.inner = self.inner.max_decoding_message_size(limit);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Limits the maximum size of an encoded message.
|
|
||||||
///
|
|
||||||
/// Default: `usize::MAX`
|
|
||||||
#[must_use]
|
|
||||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
|
||||||
self.inner = self.inner.max_encoding_message_size(limit);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub async fn reconcile(
|
|
||||||
&mut self,
|
|
||||||
request: impl tonic::IntoRequest<super::ReconcileRequest>,
|
|
||||||
) -> std::result::Result<tonic::Response<super::ReconcileResponse>, tonic::Status> {
|
|
||||||
self.inner.ready().await.map_err(|e| {
|
|
||||||
tonic::Status::unknown(format!("Service was not ready: {}", e.into()))
|
|
||||||
})?;
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let path = http::uri::PathAndQuery::from_static("/musicfs.MusicFs/Reconcile");
|
|
||||||
let mut req = request.into_request();
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert(GrpcMethod::new("musicfs.MusicFs", "Reconcile"));
|
|
||||||
self.inner.unary(req, path, codec).await
|
|
||||||
}
|
|
||||||
pub async fn get_metadata(
|
|
||||||
&mut self,
|
|
||||||
request: impl tonic::IntoRequest<super::GetMetadataRequest>,
|
|
||||||
) -> std::result::Result<
|
|
||||||
tonic::Response<tonic::codec::Streaming<super::ManifestEntry>>,
|
|
||||||
tonic::Status,
|
|
||||||
> {
|
|
||||||
self.inner.ready().await.map_err(|e| {
|
|
||||||
tonic::Status::unknown(format!("Service was not ready: {}", e.into()))
|
|
||||||
})?;
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let path = http::uri::PathAndQuery::from_static("/musicfs.MusicFs/GetMetadata");
|
|
||||||
let mut req = request.into_request();
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert(GrpcMethod::new("musicfs.MusicFs", "GetMetadata"));
|
|
||||||
self.inner.server_streaming(req, path, codec).await
|
|
||||||
}
|
|
||||||
pub async fn get_manifest(
|
|
||||||
&mut self,
|
|
||||||
request: impl tonic::IntoRequest<super::GetManifestRequest>,
|
|
||||||
) -> std::result::Result<
|
|
||||||
tonic::Response<tonic::codec::Streaming<super::ManifestEntry>>,
|
|
||||||
tonic::Status,
|
|
||||||
> {
|
|
||||||
self.inner.ready().await.map_err(|e| {
|
|
||||||
tonic::Status::unknown(format!("Service was not ready: {}", e.into()))
|
|
||||||
})?;
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let path = http::uri::PathAndQuery::from_static("/musicfs.MusicFs/GetManifest");
|
|
||||||
let mut req = request.into_request();
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert(GrpcMethod::new("musicfs.MusicFs", "GetManifest"));
|
|
||||||
self.inner.server_streaming(req, path, codec).await
|
|
||||||
}
|
|
||||||
pub async fn get_file(
|
|
||||||
&mut self,
|
|
||||||
request: impl tonic::IntoRequest<super::GetFileRequest>,
|
|
||||||
) -> std::result::Result<
|
|
||||||
tonic::Response<tonic::codec::Streaming<super::FileChunk>>,
|
|
||||||
tonic::Status,
|
|
||||||
> {
|
|
||||||
self.inner.ready().await.map_err(|e| {
|
|
||||||
tonic::Status::unknown(format!("Service was not ready: {}", e.into()))
|
|
||||||
})?;
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let path = http::uri::PathAndQuery::from_static("/musicfs.MusicFs/GetFile");
|
|
||||||
let mut req = request.into_request();
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert(GrpcMethod::new("musicfs.MusicFs", "GetFile"));
|
|
||||||
self.inner.server_streaming(req, path, codec).await
|
|
||||||
}
|
|
||||||
pub async fn subscribe_events(
|
|
||||||
&mut self,
|
|
||||||
request: impl tonic::IntoRequest<super::SubscribeEventsRequest>,
|
|
||||||
) -> std::result::Result<
|
|
||||||
tonic::Response<tonic::codec::Streaming<super::ChangeEvent>>,
|
|
||||||
tonic::Status,
|
|
||||||
> {
|
|
||||||
self.inner.ready().await.map_err(|e| {
|
|
||||||
tonic::Status::unknown(format!("Service was not ready: {}", e.into()))
|
|
||||||
})?;
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let path = http::uri::PathAndQuery::from_static("/musicfs.MusicFs/SubscribeEvents");
|
|
||||||
let mut req = request.into_request();
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert(GrpcMethod::new("musicfs.MusicFs", "SubscribeEvents"));
|
|
||||||
self.inner.server_streaming(req, path, codec).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Generated server implementations.
|
|
||||||
pub mod music_fs_server {
|
|
||||||
#![allow(
|
|
||||||
unused_variables,
|
|
||||||
dead_code,
|
|
||||||
missing_docs,
|
|
||||||
clippy::wildcard_imports,
|
|
||||||
clippy::let_unit_value
|
|
||||||
)]
|
|
||||||
use tonic::codegen::*;
|
|
||||||
/// Generated trait containing gRPC methods that should be implemented for use with MusicFsServer.
|
|
||||||
#[async_trait]
|
|
||||||
pub trait MusicFs: std::marker::Send + std::marker::Sync + 'static {
|
|
||||||
async fn reconcile(
|
|
||||||
&self,
|
|
||||||
request: tonic::Request<super::ReconcileRequest>,
|
|
||||||
) -> std::result::Result<tonic::Response<super::ReconcileResponse>, tonic::Status>;
|
|
||||||
/// Server streaming response type for the GetMetadata method.
|
|
||||||
type GetMetadataStream: tonic::codegen::tokio_stream::Stream<
|
|
||||||
Item = std::result::Result<super::ManifestEntry, tonic::Status>,
|
|
||||||
> + std::marker::Send
|
|
||||||
+ 'static;
|
|
||||||
async fn get_metadata(
|
|
||||||
&self,
|
|
||||||
request: tonic::Request<super::GetMetadataRequest>,
|
|
||||||
) -> std::result::Result<tonic::Response<Self::GetMetadataStream>, tonic::Status>;
|
|
||||||
/// Server streaming response type for the GetManifest method.
|
|
||||||
type GetManifestStream: tonic::codegen::tokio_stream::Stream<
|
|
||||||
Item = std::result::Result<super::ManifestEntry, tonic::Status>,
|
|
||||||
> + std::marker::Send
|
|
||||||
+ 'static;
|
|
||||||
async fn get_manifest(
|
|
||||||
&self,
|
|
||||||
request: tonic::Request<super::GetManifestRequest>,
|
|
||||||
) -> std::result::Result<tonic::Response<Self::GetManifestStream>, tonic::Status>;
|
|
||||||
/// Server streaming response type for the GetFile method.
|
|
||||||
type GetFileStream: tonic::codegen::tokio_stream::Stream<
|
|
||||||
Item = std::result::Result<super::FileChunk, tonic::Status>,
|
|
||||||
> + std::marker::Send
|
|
||||||
+ 'static;
|
|
||||||
async fn get_file(
|
|
||||||
&self,
|
|
||||||
request: tonic::Request<super::GetFileRequest>,
|
|
||||||
) -> std::result::Result<tonic::Response<Self::GetFileStream>, tonic::Status>;
|
|
||||||
/// Server streaming response type for the SubscribeEvents method.
|
|
||||||
type SubscribeEventsStream: tonic::codegen::tokio_stream::Stream<
|
|
||||||
Item = std::result::Result<super::ChangeEvent, tonic::Status>,
|
|
||||||
> + std::marker::Send
|
|
||||||
+ 'static;
|
|
||||||
async fn subscribe_events(
|
|
||||||
&self,
|
|
||||||
request: tonic::Request<super::SubscribeEventsRequest>,
|
|
||||||
) -> std::result::Result<tonic::Response<Self::SubscribeEventsStream>, tonic::Status>;
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct MusicFsServer<T> {
|
|
||||||
inner: Arc<T>,
|
|
||||||
accept_compression_encodings: EnabledCompressionEncodings,
|
|
||||||
send_compression_encodings: EnabledCompressionEncodings,
|
|
||||||
max_decoding_message_size: Option<usize>,
|
|
||||||
max_encoding_message_size: Option<usize>,
|
|
||||||
}
|
|
||||||
impl<T> MusicFsServer<T> {
|
|
||||||
pub fn new(inner: T) -> Self {
|
|
||||||
Self::from_arc(Arc::new(inner))
|
|
||||||
}
|
|
||||||
pub fn from_arc(inner: Arc<T>) -> Self {
|
|
||||||
Self {
|
|
||||||
inner,
|
|
||||||
accept_compression_encodings: Default::default(),
|
|
||||||
send_compression_encodings: Default::default(),
|
|
||||||
max_decoding_message_size: None,
|
|
||||||
max_encoding_message_size: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F>
|
|
||||||
where
|
|
||||||
F: tonic::service::Interceptor,
|
|
||||||
{
|
|
||||||
InterceptedService::new(Self::new(inner), interceptor)
|
|
||||||
}
|
|
||||||
/// Enable decompressing requests with the given encoding.
|
|
||||||
#[must_use]
|
|
||||||
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
|
||||||
self.accept_compression_encodings.enable(encoding);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Compress responses with the given encoding, if the client supports it.
|
|
||||||
#[must_use]
|
|
||||||
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
|
|
||||||
self.send_compression_encodings.enable(encoding);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Limits the maximum size of a decoded message.
|
|
||||||
///
|
|
||||||
/// Default: `4MB`
|
|
||||||
#[must_use]
|
|
||||||
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
|
|
||||||
self.max_decoding_message_size = Some(limit);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Limits the maximum size of an encoded message.
|
|
||||||
///
|
|
||||||
/// Default: `usize::MAX`
|
|
||||||
#[must_use]
|
|
||||||
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
|
|
||||||
self.max_encoding_message_size = Some(limit);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T, B> tonic::codegen::Service<http::Request<B>> for MusicFsServer<T>
|
|
||||||
where
|
|
||||||
T: MusicFs,
|
|
||||||
B: Body + std::marker::Send + 'static,
|
|
||||||
B::Error: Into<StdError> + std::marker::Send + 'static,
|
|
||||||
{
|
|
||||||
type Response = http::Response<tonic::body::Body>;
|
|
||||||
type Error = std::convert::Infallible;
|
|
||||||
type Future = BoxFuture<Self::Response, Self::Error>;
|
|
||||||
fn poll_ready(
|
|
||||||
&mut self,
|
|
||||||
_cx: &mut Context<'_>,
|
|
||||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
|
||||||
match req.uri().path() {
|
|
||||||
"/musicfs.MusicFs/Reconcile" => {
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
struct ReconcileSvc<T: MusicFs>(pub Arc<T>);
|
|
||||||
impl<T: MusicFs> tonic::server::UnaryService<super::ReconcileRequest> for ReconcileSvc<T> {
|
|
||||||
type Response = super::ReconcileResponse;
|
|
||||||
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
|
|
||||||
fn call(
|
|
||||||
&mut self,
|
|
||||||
request: tonic::Request<super::ReconcileRequest>,
|
|
||||||
) -> Self::Future {
|
|
||||||
let inner = Arc::clone(&self.0);
|
|
||||||
let fut =
|
|
||||||
async move { <T as MusicFs>::reconcile(&inner, request).await };
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let accept_compression_encodings = self.accept_compression_encodings;
|
|
||||||
let send_compression_encodings = self.send_compression_encodings;
|
|
||||||
let max_decoding_message_size = self.max_decoding_message_size;
|
|
||||||
let max_encoding_message_size = self.max_encoding_message_size;
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
let fut = async move {
|
|
||||||
let method = ReconcileSvc(inner);
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec)
|
|
||||||
.apply_compression_config(
|
|
||||||
accept_compression_encodings,
|
|
||||||
send_compression_encodings,
|
|
||||||
)
|
|
||||||
.apply_max_message_size_config(
|
|
||||||
max_decoding_message_size,
|
|
||||||
max_encoding_message_size,
|
|
||||||
);
|
|
||||||
let res = grpc.unary(method, req).await;
|
|
||||||
Ok(res)
|
|
||||||
};
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
"/musicfs.MusicFs/GetMetadata" => {
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
struct GetMetadataSvc<T: MusicFs>(pub Arc<T>);
|
|
||||||
impl<T: MusicFs>
|
|
||||||
tonic::server::ServerStreamingService<super::GetMetadataRequest>
|
|
||||||
for GetMetadataSvc<T>
|
|
||||||
{
|
|
||||||
type Response = super::ManifestEntry;
|
|
||||||
type ResponseStream = T::GetMetadataStream;
|
|
||||||
type Future =
|
|
||||||
BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>;
|
|
||||||
fn call(
|
|
||||||
&mut self,
|
|
||||||
request: tonic::Request<super::GetMetadataRequest>,
|
|
||||||
) -> Self::Future {
|
|
||||||
let inner = Arc::clone(&self.0);
|
|
||||||
let fut =
|
|
||||||
async move { <T as MusicFs>::get_metadata(&inner, request).await };
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let accept_compression_encodings = self.accept_compression_encodings;
|
|
||||||
let send_compression_encodings = self.send_compression_encodings;
|
|
||||||
let max_decoding_message_size = self.max_decoding_message_size;
|
|
||||||
let max_encoding_message_size = self.max_encoding_message_size;
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
let fut = async move {
|
|
||||||
let method = GetMetadataSvc(inner);
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec)
|
|
||||||
.apply_compression_config(
|
|
||||||
accept_compression_encodings,
|
|
||||||
send_compression_encodings,
|
|
||||||
)
|
|
||||||
.apply_max_message_size_config(
|
|
||||||
max_decoding_message_size,
|
|
||||||
max_encoding_message_size,
|
|
||||||
);
|
|
||||||
let res = grpc.server_streaming(method, req).await;
|
|
||||||
Ok(res)
|
|
||||||
};
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
"/musicfs.MusicFs/GetManifest" => {
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
struct GetManifestSvc<T: MusicFs>(pub Arc<T>);
|
|
||||||
impl<T: MusicFs>
|
|
||||||
tonic::server::ServerStreamingService<super::GetManifestRequest>
|
|
||||||
for GetManifestSvc<T>
|
|
||||||
{
|
|
||||||
type Response = super::ManifestEntry;
|
|
||||||
type ResponseStream = T::GetManifestStream;
|
|
||||||
type Future =
|
|
||||||
BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>;
|
|
||||||
fn call(
|
|
||||||
&mut self,
|
|
||||||
request: tonic::Request<super::GetManifestRequest>,
|
|
||||||
) -> Self::Future {
|
|
||||||
let inner = Arc::clone(&self.0);
|
|
||||||
let fut =
|
|
||||||
async move { <T as MusicFs>::get_manifest(&inner, request).await };
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let accept_compression_encodings = self.accept_compression_encodings;
|
|
||||||
let send_compression_encodings = self.send_compression_encodings;
|
|
||||||
let max_decoding_message_size = self.max_decoding_message_size;
|
|
||||||
let max_encoding_message_size = self.max_encoding_message_size;
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
let fut = async move {
|
|
||||||
let method = GetManifestSvc(inner);
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec)
|
|
||||||
.apply_compression_config(
|
|
||||||
accept_compression_encodings,
|
|
||||||
send_compression_encodings,
|
|
||||||
)
|
|
||||||
.apply_max_message_size_config(
|
|
||||||
max_decoding_message_size,
|
|
||||||
max_encoding_message_size,
|
|
||||||
);
|
|
||||||
let res = grpc.server_streaming(method, req).await;
|
|
||||||
Ok(res)
|
|
||||||
};
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
"/musicfs.MusicFs/GetFile" => {
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
struct GetFileSvc<T: MusicFs>(pub Arc<T>);
|
|
||||||
impl<T: MusicFs> tonic::server::ServerStreamingService<super::GetFileRequest> for GetFileSvc<T> {
|
|
||||||
type Response = super::FileChunk;
|
|
||||||
type ResponseStream = T::GetFileStream;
|
|
||||||
type Future =
|
|
||||||
BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>;
|
|
||||||
fn call(
|
|
||||||
&mut self,
|
|
||||||
request: tonic::Request<super::GetFileRequest>,
|
|
||||||
) -> Self::Future {
|
|
||||||
let inner = Arc::clone(&self.0);
|
|
||||||
let fut =
|
|
||||||
async move { <T as MusicFs>::get_file(&inner, request).await };
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let accept_compression_encodings = self.accept_compression_encodings;
|
|
||||||
let send_compression_encodings = self.send_compression_encodings;
|
|
||||||
let max_decoding_message_size = self.max_decoding_message_size;
|
|
||||||
let max_encoding_message_size = self.max_encoding_message_size;
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
let fut = async move {
|
|
||||||
let method = GetFileSvc(inner);
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec)
|
|
||||||
.apply_compression_config(
|
|
||||||
accept_compression_encodings,
|
|
||||||
send_compression_encodings,
|
|
||||||
)
|
|
||||||
.apply_max_message_size_config(
|
|
||||||
max_decoding_message_size,
|
|
||||||
max_encoding_message_size,
|
|
||||||
);
|
|
||||||
let res = grpc.server_streaming(method, req).await;
|
|
||||||
Ok(res)
|
|
||||||
};
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
"/musicfs.MusicFs/SubscribeEvents" => {
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
struct SubscribeEventsSvc<T: MusicFs>(pub Arc<T>);
|
|
||||||
impl<T: MusicFs>
|
|
||||||
tonic::server::ServerStreamingService<super::SubscribeEventsRequest>
|
|
||||||
for SubscribeEventsSvc<T>
|
|
||||||
{
|
|
||||||
type Response = super::ChangeEvent;
|
|
||||||
type ResponseStream = T::SubscribeEventsStream;
|
|
||||||
type Future =
|
|
||||||
BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>;
|
|
||||||
fn call(
|
|
||||||
&mut self,
|
|
||||||
request: tonic::Request<super::SubscribeEventsRequest>,
|
|
||||||
) -> Self::Future {
|
|
||||||
let inner = Arc::clone(&self.0);
|
|
||||||
let fut = async move {
|
|
||||||
<T as MusicFs>::subscribe_events(&inner, request).await
|
|
||||||
};
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let accept_compression_encodings = self.accept_compression_encodings;
|
|
||||||
let send_compression_encodings = self.send_compression_encodings;
|
|
||||||
let max_decoding_message_size = self.max_decoding_message_size;
|
|
||||||
let max_encoding_message_size = self.max_encoding_message_size;
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
let fut = async move {
|
|
||||||
let method = SubscribeEventsSvc(inner);
|
|
||||||
let codec = tonic_prost::ProstCodec::default();
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec)
|
|
||||||
.apply_compression_config(
|
|
||||||
accept_compression_encodings,
|
|
||||||
send_compression_encodings,
|
|
||||||
)
|
|
||||||
.apply_max_message_size_config(
|
|
||||||
max_decoding_message_size,
|
|
||||||
max_encoding_message_size,
|
|
||||||
);
|
|
||||||
let res = grpc.server_streaming(method, req).await;
|
|
||||||
Ok(res)
|
|
||||||
};
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
_ => Box::pin(async move {
|
|
||||||
let mut response = http::Response::new(tonic::body::Body::default());
|
|
||||||
let headers = response.headers_mut();
|
|
||||||
headers.insert(
|
|
||||||
tonic::Status::GRPC_STATUS,
|
|
||||||
(tonic::Code::Unimplemented as i32).into(),
|
|
||||||
);
|
|
||||||
headers.insert(
|
|
||||||
http::header::CONTENT_TYPE,
|
|
||||||
tonic::metadata::GRPC_CONTENT_TYPE,
|
|
||||||
);
|
|
||||||
Ok(response)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> Clone for MusicFsServer<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
Self {
|
|
||||||
inner,
|
|
||||||
accept_compression_encodings: self.accept_compression_encodings,
|
|
||||||
send_compression_encodings: self.send_compression_encodings,
|
|
||||||
max_decoding_message_size: self.max_decoding_message_size,
|
|
||||||
max_encoding_message_size: self.max_encoding_message_size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Generated gRPC service name
|
|
||||||
pub const SERVICE_NAME: &str = "musicfs.MusicFs";
|
|
||||||
impl<T> tonic::server::NamedService for MusicFsServer<T> {
|
|
||||||
const NAME: &'static str = SERVICE_NAME;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
//! Generated protobuf + tonic bindings for the musicfs service.
|
|
||||||
//!
|
|
||||||
//! `buf generate` writes prost messages to `generated/musicfs/musicfs.rs` and
|
|
||||||
//! the tonic service to `generated/musicfs/musicfs.tonic.rs`. The prost plugin
|
|
||||||
//! appends `include!("musicfs.tonic.rs")` to the end of `musicfs.rs`, so
|
|
||||||
//! including that one file here also pulls in the service. The only glue buf
|
|
||||||
//! does not generate is the module named after the proto package (`musicfs`),
|
|
||||||
//! which we provide below before re-exporting the types callers use.
|
|
||||||
pub mod musicfs {
|
|
||||||
include!("generated/musicfs/musicfs.rs");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use musicfs::{
|
|
||||||
ChangeEvent, FileChunk, GetFileRequest, GetManifestRequest, GetMetadataRequest, InodeHash,
|
|
||||||
ManifestEntry, MusicMetadata, PictureDataRange, ReconcileRequest, ReconcileResponse,
|
|
||||||
SubscribeEventsRequest,
|
|
||||||
music_fs_client::MusicFsClient,
|
|
||||||
music_fs_server::{MusicFs, MusicFsServer},
|
|
||||||
};
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::BTreeMap,
|
|
||||||
fs,
|
|
||||||
hash::Hasher,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use fuser::INodeNo;
|
|
||||||
use twox_hash::XxHash64;
|
|
||||||
|
|
||||||
use crate::db::entities::Model;
|
|
||||||
use crate::item::{FileType, Item};
|
|
||||||
use crate::origins::attrs::FileAttrs;
|
|
||||||
|
|
||||||
pub fn virtual_inode(path: &str) -> INodeNo {
|
|
||||||
let mut hasher = XxHash64::with_seed(5678);
|
|
||||||
hasher.write(path.as_bytes());
|
|
||||||
|
|
||||||
return INodeNo(hasher.finish() | (1u64 << 63));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parent_inode_from_path(local_path: &Path) -> INodeNo {
|
|
||||||
let components: Vec<_> = local_path
|
|
||||||
.components()
|
|
||||||
.filter(|c| matches!(c, std::path::Component::Normal(_)))
|
|
||||||
.collect();
|
|
||||||
if components.len() <= 1 {
|
|
||||||
return INodeNo::ROOT;
|
|
||||||
}
|
|
||||||
let mut parent_path = String::new();
|
|
||||||
for (i, comp) in components[..components.len() - 1].iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
parent_path.push('/');
|
|
||||||
}
|
|
||||||
parent_path.push_str(comp.as_os_str().to_str().unwrap_or_default());
|
|
||||||
}
|
|
||||||
|
|
||||||
return virtual_inode(&parent_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ensure_virtual_dirs(
|
|
||||||
local_path: &Path,
|
|
||||||
source: &Path,
|
|
||||||
map: &mut BTreeMap<INodeNo, Item>,
|
|
||||||
) -> INodeNo {
|
|
||||||
let components: Vec<_> = local_path
|
|
||||||
.components()
|
|
||||||
.filter(|c| matches!(c, std::path::Component::Normal(_)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if components.len() <= 1 {
|
|
||||||
return INodeNo::ROOT;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut current_parent = INodeNo::ROOT;
|
|
||||||
let mut current_path = String::new();
|
|
||||||
|
|
||||||
for component in &components[..components.len() - 1] {
|
|
||||||
let comp_str = component.as_os_str().to_str().unwrap_or_default();
|
|
||||||
if !current_path.is_empty() {
|
|
||||||
current_path.push('/');
|
|
||||||
}
|
|
||||||
current_path.push_str(comp_str);
|
|
||||||
|
|
||||||
let virt_ino = virtual_inode(¤t_path);
|
|
||||||
|
|
||||||
if !map.contains_key(&virt_ino) {
|
|
||||||
let virt_item = Item::new(
|
|
||||||
virt_ino,
|
|
||||||
current_parent,
|
|
||||||
comp_str.to_string(),
|
|
||||||
source.to_path_buf(),
|
|
||||||
PathBuf::from(¤t_path),
|
|
||||||
FileType::Directory,
|
|
||||||
FileAttrs::from(&fs::metadata(source).unwrap()),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
map.insert(virt_ino, virt_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
current_parent = virt_ino;
|
|
||||||
}
|
|
||||||
|
|
||||||
return current_parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore_virtual_paths(
|
|
||||||
snapshot: &mut BTreeMap<INodeNo, Item>,
|
|
||||||
db_items: &std::collections::HashMap<i64, Model>,
|
|
||||||
source: &Path,
|
|
||||||
) {
|
|
||||||
let restorations: Vec<(INodeNo, String, PathBuf)> = db_items
|
|
||||||
.values()
|
|
||||||
.filter_map(|db_item| {
|
|
||||||
let ino = INodeNo(db_item.inode as u64);
|
|
||||||
if ino == INodeNo::ROOT {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
snapshot
|
|
||||||
.get(&ino)
|
|
||||||
.filter(|item| item.hash as i64 == db_item.hash)
|
|
||||||
.map(|_| {
|
|
||||||
(
|
|
||||||
ino,
|
|
||||||
db_item.name.clone(),
|
|
||||||
PathBuf::from(&db_item.local_path),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for (_, _, local_path) in &restorations {
|
|
||||||
ensure_virtual_dirs(local_path, source, snapshot);
|
|
||||||
}
|
|
||||||
for (ino, name, local_path) in restorations {
|
|
||||||
if let Some(item) = snapshot.get_mut(&ino) {
|
|
||||||
item.name = name;
|
|
||||||
item.parent_inode = parent_inode_from_path(&local_path);
|
|
||||||
item.local_path = local_path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn virtual_inode_deterministic() {
|
|
||||||
let ino1 = virtual_inode("foo");
|
|
||||||
let ino2 = virtual_inode("foo");
|
|
||||||
assert_eq!(ino1, ino2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn virtual_inode_high_bit_set() {
|
|
||||||
let ino = virtual_inode("test");
|
|
||||||
assert_eq!(ino.0 & (1u64 << 63), 1u64 << 63);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn virtual_inode_different_inputs() {
|
|
||||||
let ino1 = virtual_inode("foo");
|
|
||||||
let ino2 = virtual_inode("bar");
|
|
||||||
assert_ne!(ino1, ino2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parent_inode_single_component() {
|
|
||||||
let ino = parent_inode_from_path(Path::new("file.txt"));
|
|
||||||
assert_eq!(ino, INodeNo::ROOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parent_inode_multi_component() {
|
|
||||||
let ino = parent_inode_from_path(Path::new("a/b/c"));
|
|
||||||
let expected = virtual_inode("a/b");
|
|
||||||
assert_eq!(ino, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parent_inode_absolute_path_filtered() {
|
|
||||||
let ino = parent_inode_from_path(Path::new("/file"));
|
|
||||||
assert_eq!(ino, INodeNo::ROOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ensure_virtual_dirs_single_component() {
|
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
|
||||||
let source = tmp.path();
|
|
||||||
let mut map = BTreeMap::new();
|
|
||||||
|
|
||||||
let ino = ensure_virtual_dirs(Path::new("file.txt"), source, &mut map);
|
|
||||||
assert_eq!(ino, INodeNo::ROOT);
|
|
||||||
assert!(map.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ensure_virtual_dirs_creates_hierarchy() {
|
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
|
||||||
let source = tmp.path();
|
|
||||||
let mut map = BTreeMap::new();
|
|
||||||
|
|
||||||
let ino = ensure_virtual_dirs(Path::new("a/b/file"), source, &mut map);
|
|
||||||
|
|
||||||
let a_ino = virtual_inode("a");
|
|
||||||
let ab_ino = virtual_inode("a/b");
|
|
||||||
|
|
||||||
assert_eq!(ino, ab_ino);
|
|
||||||
assert_eq!(map.len(), 2);
|
|
||||||
assert!(map.contains_key(&a_ino));
|
|
||||||
assert!(map.contains_key(&ab_ino));
|
|
||||||
|
|
||||||
let a_item = &map[&a_ino];
|
|
||||||
assert_eq!(a_item.parent_inode, INodeNo::ROOT);
|
|
||||||
|
|
||||||
let ab_item = &map[&ab_ino];
|
|
||||||
assert_eq!(ab_item.parent_inode, a_ino);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ensure_virtual_dirs_idempotent() {
|
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
|
||||||
let source = tmp.path();
|
|
||||||
let mut map = BTreeMap::new();
|
|
||||||
|
|
||||||
ensure_virtual_dirs(Path::new("a/b/file"), source, &mut map);
|
|
||||||
let size_after_first = map.len();
|
|
||||||
|
|
||||||
ensure_virtual_dirs(Path::new("a/b/file"), source, &mut map);
|
|
||||||
let size_after_second = map.len();
|
|
||||||
|
|
||||||
assert_eq!(size_after_first, size_after_second);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn restore_virtual_paths_skips_root() {
|
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
|
||||||
let source = tmp.path();
|
|
||||||
let mut snapshot = BTreeMap::new();
|
|
||||||
|
|
||||||
let real_ino = INodeNo(42);
|
|
||||||
let real_item = Item::new(
|
|
||||||
real_ino,
|
|
||||||
INodeNo::ROOT,
|
|
||||||
"real_file".to_string(),
|
|
||||||
source.to_path_buf(),
|
|
||||||
PathBuf::from("real_file"),
|
|
||||||
FileType::File,
|
|
||||||
FileAttrs::from(&fs::metadata(source).unwrap()),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
snapshot.insert(real_ino, real_item);
|
|
||||||
|
|
||||||
let mut db_items = HashMap::new();
|
|
||||||
db_items.insert(
|
|
||||||
1i64,
|
|
||||||
Model {
|
|
||||||
inode: 1,
|
|
||||||
hash: 0,
|
|
||||||
name: "root".to_string(),
|
|
||||||
original_path: "/tmp/test".to_string(),
|
|
||||||
local_path: "/tmp/test".to_string(),
|
|
||||||
file_type: "directory".to_string(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
db_items.insert(
|
|
||||||
42i64,
|
|
||||||
Model {
|
|
||||||
inode: 42,
|
|
||||||
hash: 0,
|
|
||||||
name: "real_file".to_string(),
|
|
||||||
original_path: "real_file".to_string(),
|
|
||||||
local_path: "real_file".to_string(),
|
|
||||||
file_type: "file".to_string(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
restore_virtual_paths(&mut snapshot, &db_items, source);
|
|
||||||
|
|
||||||
for item in snapshot.values() {
|
|
||||||
assert_ne!(item.name, "/");
|
|
||||||
assert_ne!(item.name, "tmp");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user