Compare commits

...

7 Commits

Author SHA1 Message Date
Alexander 1dcfa7a803 Add readiness probe to devenv 2026-07-25 12:44:06 +02:00
Alexander 78b4338f95 Show files as {track_number} - {track_title} 2026-07-25 11:56:30 +02:00
Alexander 82be15b960 Implement list files rpc 2026-07-24 20:17:38 +02:00
Alexander 5f79b3c2b0 Configure devenv to build output 2026-07-03 22:13:16 +02:00
Alexander f28091257e Define endpoints to update metadata 2026-07-01 20:23:10 +02:00
Alexander 8f76dd5b39 Add health endpoint, add rpc to client 2026-07-01 16:56:56 +02:00
Alexander 8dba0c097d Refactor to split client from server 2026-07-01 16:31:55 +02:00
65 changed files with 4286 additions and 1635 deletions
Generated
+41 -5
View File
@@ -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
View File
@@ -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"
+108 -11
View File
@@ -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
@@ -17,7 +101,7 @@ 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 |
@@ -26,7 +110,9 @@ Options (env): =VM_NAME=, =IMAGE=, =MUSIC_SOURCE=, =LISTEN_PORT=, =RUST_LOG=.
| =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.
+39
View File
@@ -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"] }
+248
View File
@@ -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(&current_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(())
}
+165
View File
@@ -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),
); )
} }
} }
+10
View File
@@ -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,
+2
View File
@@ -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,9 +302,8 @@ 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,
@@ -322,7 +320,6 @@ impl From<crate::proto::MusicMetadata> for MusicMetadata {
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,
};
} }
} }
@@ -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(),
+531
View File
@@ -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(&current_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(&current_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");
}
}
}
+562
View File
@@ -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());
}
+14
View File
@@ -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
+20
View File
@@ -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()
}
+8
View File
@@ -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;
+62
View File
@@ -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;
+9
View File
@@ -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
+15
View File
@@ -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},
};
+26
View File
@@ -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,
+1
View File
@@ -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,14 +298,13 @@ 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,
@@ -322,7 +321,6 @@ impl From<MusicMetadata> for ProtoMusicMetadata {
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
View File
@@ -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
View File
@@ -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
+78
View File
@@ -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;
}
+10
View File
@@ -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 \
+12
View File
@@ -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
View File
@@ -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).
-8
View File
@@ -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;
-153
View File
@@ -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);
}
}
-459
View File
@@ -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;
}
}
-19
View File
@@ -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},
};
-267
View File
@@ -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(&current_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(&current_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");
}
}
}