aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-02-27 20:56:20 +0100
committermetamuffin <metamuffin@disroot.org>2026-02-27 20:56:20 +0100
commit7930d543a2aa68d4ad2958605827d7eb1baa91f8 (patch)
treefe59d1f549e303a96b78d3e925d75abb70b73af0
parentc05bfcc2775f0e11db6e856bfcf06d0419c35d54 (diff)
downloadjellything-7930d543a2aa68d4ad2958605827d7eb1baa91f8.tar
jellything-7930d543a2aa68d4ad2958605827d7eb1baa91f8.tar.bz2
jellything-7930d543a2aa68d4ad2958605827d7eb1baa91f8.tar.zst
reimplement Object as slice type
-rw-r--r--common/object/src/buffer.rs76
-rw-r--r--common/object/src/debug.rs14
-rw-r--r--common/object/src/json.rs4
-rw-r--r--common/object/src/lib.rs276
-rw-r--r--common/object/src/path.rs18
-rw-r--r--common/object/src/registry.rs39
-rw-r--r--common/object/src/tag.rs73
-rw-r--r--common/object/src/tests.rs36
-rw-r--r--common/object/src/value.rs139
-rw-r--r--common/src/api.rs7
-rw-r--r--common/src/internal.rs2
-rw-r--r--common/src/lib.rs1
-rw-r--r--common/src/node.rs78
-rw-r--r--common/src/user.rs8
-rw-r--r--database/src/helper.rs2
-rw-r--r--database/src/kv/binning.rs4
-rw-r--r--database/src/kv/index.rs2
-rw-r--r--database/src/kv/mod.rs43
-rw-r--r--database/src/kv/tests.rs6
-rw-r--r--database/src/lib.rs12
-rw-r--r--database/src/test_shared.rs36
-rw-r--r--import/src/helpers.rs4
-rw-r--r--import/src/lib.rs33
-rw-r--r--import/src/plugins/acoustid.rs10
-rw-r--r--import/src/plugins/infojson.rs42
-rw-r--r--import/src/plugins/media_info.rs108
-rw-r--r--import/src/plugins/misc.rs31
-rw-r--r--import/src/plugins/musicbrainz.rs45
-rw-r--r--import/src/plugins/omdb.rs21
-rw-r--r--import/src/plugins/tags.rs10
-rw-r--r--import/src/plugins/tmdb.rs53
-rw-r--r--import/src/plugins/trakt.rs80
-rw-r--r--import/src/plugins/vgmdb.rs8
-rw-r--r--import/src/plugins/wikidata.rs8
-rw-r--r--import/src/source_rank.rs28
-rw-r--r--server/src/auth.rs11
-rw-r--r--server/src/compat/youtube.rs5
-rw-r--r--server/src/logic/stream.rs2
-rw-r--r--server/src/main.rs14
-rw-r--r--server/src/request_info.rs18
-rw-r--r--server/src/ui/account/mod.rs6
-rw-r--r--server/src/ui/account/settings.rs6
-rw-r--r--server/src/ui/admin/users.rs4
-rw-r--r--server/src/ui/home.rs70
-rw-r--r--server/src/ui/items.rs7
-rw-r--r--server/src/ui/node.rs295
-rw-r--r--server/src/ui/player.rs15
-rw-r--r--ui/src/components/admin.rs4
-rw-r--r--ui/src/components/home.rs2
-rw-r--r--ui/src/components/node_card.rs14
-rw-r--r--ui/src/components/node_page.rs18
-rw-r--r--ui/src/components/props.rs23
-rw-r--r--ui/src/components/user.rs2
-rw-r--r--ui/src/lib.rs3
54 files changed, 881 insertions, 995 deletions
diff --git a/common/object/src/buffer.rs b/common/object/src/buffer.rs
index 1f8cec6..4d3af34 100644
--- a/common/object/src/buffer.rs
+++ b/common/object/src/buffer.rs
@@ -4,56 +4,12 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::{Object, Tag, TypedTag, ValueStore};
+use crate::{Object, Tag, TypedTag, ValueMarker, value::ValueSer};
use bytemuck::{try_cast_slice, try_cast_vec};
+use core::marker::Sized;
use log::trace;
use std::borrow::Cow;
-#[derive(PartialEq, Eq, Hash)]
-pub struct ObjectBuffer(pub Vec<u32>);
-
-impl ObjectBuffer {
- pub fn empty() -> Self {
- Self(vec![0])
- }
- pub fn as_object<'a>(&'a self) -> Object<'a> {
- Object::load(&self.0).unwrap()
- }
- pub fn to_bytes(self) -> Vec<u8> {
- self.0.into_iter().flat_map(u32::to_le_bytes).collect()
- }
- pub fn new(fields: &mut [(Tag, &dyn ValueStore)]) -> ObjectBuffer {
- let mut tags = Vec::new();
- let mut offsets = Vec::new();
- let mut values = Vec::new();
- fields.sort_by_key(|(t, _)| t.0);
- let mut temp = Vec::new();
- for (tag, val) in fields {
- tags.push(tag.0);
- let ty = val.get_type();
- let off = (values.len() as u32) << 5 | (ty as u32) << 2;
- if ty.is_aligned() {
- offsets.push(off);
- val.store_aligned(&mut values);
- } else {
- temp.clear();
- val.store_unaligned(&mut temp);
- let pad = pad_vec(&mut temp);
- offsets.push(off | pad);
- values.extend(&*slice_u8_to_u32(&temp));
- }
- }
- ObjectBuffer(
- [tags.len() as u32]
- .into_iter()
- .chain(tags)
- .chain(offsets)
- .chain(values)
- .collect(),
- )
- }
-}
-
pub type OBB = ObjectBufferBuilder;
#[derive(Default)]
@@ -62,10 +18,9 @@ impl ObjectBufferBuilder {
pub fn new() -> Self {
Self::default()
}
- pub fn push<T: ValueStore>(&mut self, tag: TypedTag<T>, value: T) {
- let ty = value.get_type();
- let tyb = (ty as u32) << 2;
- if ty.is_aligned() {
+ pub fn push<'a, T: ValueMarker<'a> + ?Sized>(&mut self, tag: TypedTag<T>, value: T::Inner) {
+ let tyb = (T::Inner::TYPE as u32) << 2;
+ if T::Inner::TYPE.is_aligned() {
let mut buf = Vec::new();
value.store_aligned(&mut buf);
self.0.push((tag.0, tyb, buf));
@@ -76,11 +31,15 @@ impl ObjectBufferBuilder {
self.0.push((tag.0, tyb | pad, vec_u8_to_u32(buf)));
}
}
- pub fn with<T: ValueStore>(mut self, tag: TypedTag<T>, value: T) -> Self {
+ pub fn with<'a, T: ValueMarker<'a> + ?Sized>(
+ mut self,
+ tag: TypedTag<T>,
+ value: T::Inner,
+ ) -> Self {
self.push(tag, value);
self
}
- pub fn finish(mut self) -> ObjectBuffer {
+ pub fn finish(mut self) -> Box<Object> {
let mut tags = Vec::new();
let mut offsets = Vec::new();
let mut values = Vec::new();
@@ -91,7 +50,7 @@ impl ObjectBufferBuilder {
offsets.push(off);
values.extend(buf);
}
- ObjectBuffer(
+ vec_to_ob(
[tags.len() as u32]
.into_iter()
.chain(tags)
@@ -102,10 +61,13 @@ impl ObjectBufferBuilder {
}
}
-impl From<Vec<u8>> for ObjectBuffer {
- fn from(value: Vec<u8>) -> Self {
- ObjectBuffer(vec_u8_to_u32(value))
- }
+pub fn vec_to_ob(v: Vec<u32>) -> Box<Object> {
+ //? safe way to do this?
+ unsafe { std::mem::transmute(v.into_boxed_slice()) }
+}
+pub const fn slice_to_ob<'a>(v: &'a [u32]) -> &'a Object {
+ //? safe way to do this?
+ unsafe { std::mem::transmute(v) }
}
#[inline]
diff --git a/common/object/src/debug.rs b/common/object/src/debug.rs
index b45d5bf..196da20 100644
--- a/common/object/src/debug.rs
+++ b/common/object/src/debug.rs
@@ -4,16 +4,10 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::{Object, ObjectBuffer, Tag, TypedTag, ValueType};
+use crate::{Object, Tag, TypedTag, ValueType};
use std::{any::type_name, fmt::Debug};
-impl Debug for ObjectBuffer {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- self.as_object().fmt(f)
- }
-}
-
-impl Debug for Object<'_> {
+impl Debug for Object {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("Object");
let mut nonexhaustive = false;
@@ -23,8 +17,8 @@ impl Debug for Object<'_> {
let ty = self.offset_type(i);
let sz = self.size(i);
match (ty, sz) {
- (ValueType::String, _) => s.field(k, &self.get_typed::<&str>(i).unwrap()),
- (ValueType::Binary, _) => s.field(k, &self.get_typed::<&[u8]>(i).unwrap()),
+ (ValueType::String, _) => s.field(k, &self.get_typed::<str>(i).unwrap()),
+ (ValueType::Binary, _) => s.field(k, &self.get_typed::<[u8]>(i).unwrap()),
(ValueType::Object, _) => s.field(k, &self.get_typed::<Object>(i).unwrap()),
(ValueType::UInt, 4) => s.field(k, &self.get_typed::<u32>(i).unwrap()),
(ValueType::UInt, 8) => s.field(k, &self.get_typed::<u64>(i).unwrap()),
diff --git a/common/object/src/json.rs b/common/object/src/json.rs
index a18ecf5..32b7009 100644
--- a/common/object/src/json.rs
+++ b/common/object/src/json.rs
@@ -7,7 +7,7 @@
use crate::{Object, Tag, ValueType};
use serde_json::{Map, Value};
-pub fn object_to_json(ob: Object<'_>) -> Value {
+pub fn object_to_json(ob: &Object) -> Value {
let mut o = Map::new();
let mut nonexhaustive = false;
for (i, tag) in ob.keys().enumerate() {
@@ -16,7 +16,7 @@ pub fn object_to_json(ob: Object<'_>) -> Value {
let ty = ob.offset_type(i);
let sz = ob.size(i);
let val = match (ty, sz) {
- (ValueType::String, _) => ob.get_typed::<&str>(i).unwrap().to_string().into(),
+ (ValueType::String, _) => ob.get_typed::<str>(i).unwrap().to_string().into(),
(ValueType::Object, _) => object_to_json(ob.get_typed::<Object>(i).unwrap()),
(ValueType::Tag, 4) => ob.get_typed::<Tag>(i).unwrap().to_string().into(),
(ValueType::UInt, 4) => ob.get_typed::<u32>(i).unwrap().into(),
diff --git a/common/object/src/lib.rs b/common/object/src/lib.rs
index ae8d1cd..783871b 100644
--- a/common/object/src/lib.rs
+++ b/common/object/src/lib.rs
@@ -3,102 +3,66 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-#![feature(iter_array_chunks, strip_circumfix)]
+#![feature(iter_array_chunks, strip_circumfix, phantom_variance_markers)]
mod buffer;
pub mod debug;
#[cfg(feature = "json")]
pub mod json;
mod path;
-mod registry;
+pub mod tag;
#[cfg(test)]
mod tests;
mod value;
pub use buffer::*;
pub use path::*;
-pub use registry::*;
+pub use tag::*;
pub use value::*;
-use std::{collections::BTreeSet, fmt::Display, hash::Hash, marker::PhantomData};
+use core::{marker::Sized, todo};
+use std::{collections::BTreeSet, hash::Hash, marker::PhantomData};
+#[derive(Hash, PartialEq, Eq)]
#[repr(transparent)]
-#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct Tag(pub u32);
+pub struct Object([u32]);
-impl Default for Tag {
- fn default() -> Self {
- Self::new(b"1111")
- }
-}
-impl Tag {
- pub const fn new(fourcc: &[u8; 4]) -> Self {
- Self(u32::from_le_bytes(*fourcc))
- }
-}
-impl Display for Tag {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_str(str::from_utf8(&self.0.to_le_bytes()).unwrap())
+impl ToOwned for Object {
+ type Owned = Box<Object>;
+ fn to_owned(&self) -> Self::Owned {
+ vec_to_ob(self.0.to_vec())
}
}
-#[derive(PartialEq, Eq, PartialOrd, Ord)]
-pub struct TypedTag<T>(pub Tag, pub PhantomData<T>);
-impl<T> Display for TypedTag<T> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- self.0.fmt(f)
- }
-}
+pub const EMPTY: &'static Object = slice_to_ob(&[0]);
-impl<T> Clone for TypedTag<T> {
- fn clone(&self) -> Self {
- Self(self.0, PhantomData)
+impl Object {
+ #[inline]
+ fn len(&self) -> usize {
+ self.0[0] as usize
}
-}
-impl<T> Copy for TypedTag<T> {}
-
-#[derive(Clone, Copy, Hash, PartialEq, Eq)]
-pub struct Object<'a> {
- tags: &'a [u32],
- offsets: &'a [u32],
- values: &'a [u32],
-}
-
-impl<'a> Default for Object<'a> {
- fn default() -> Self {
- Self::EMPTY
+ #[inline]
+ fn tags(&self) -> &[u32] {
+ &self.0[1..1 + self.len()]
}
-}
-impl<'a> Object<'a> {
- pub const EMPTY: Object<'static> = Object {
- offsets: &[],
- tags: &[],
- values: &[],
- };
-
- pub fn load(buf: &'a [u32]) -> Option<Self> {
- let nf = *buf.get(0)? as usize;
- if buf.len() < 1 + nf * 2 {
- return None;
- }
- Some(Self {
- tags: &buf[1..1 + nf],
- offsets: &buf[1 + nf..1 + nf + nf],
- values: &buf[1 + nf + nf..],
- })
+ #[inline]
+ fn offsets(&self) -> &[u32] {
+ let nf = self.len();
+ &self.0[1 + nf..1 + nf + nf]
}
- pub fn dump(&self) -> ObjectBuffer {
- let mut out = Vec::new();
- out.push(self.tags.len() as u32);
- out.extend(self.tags);
- out.extend(self.offsets);
- out.extend(self.values);
- ObjectBuffer(out)
+ #[inline]
+ fn values(&self) -> &[u32] {
+ let nf = self.len();
+ &self.0[1 + nf + nf..]
}
+ pub fn export(&self) -> &[u32] {
+ &self.0
+ }
+
pub fn find_field(&self, tag: Tag) -> Option<usize> {
// using partition as binary search for the first field (instead of regular binary_search that returns any)
- let first = self.tags.partition_point(|&x| x < tag.0);
- self.tags
+ let first = self.tags().partition_point(|&x| x < tag.0);
+ self.tags()
.get(first)
.is_some_and(|&x| x == tag.0)
.then_some(first)
@@ -108,94 +72,97 @@ impl<'a> Object<'a> {
}
#[inline]
fn offset(&self, i: usize) -> usize {
- self.offsets
+ self.offsets()
.get(i)
.map(|&v| v >> 5)
- .unwrap_or(self.values.len() as u32) as usize
+ .unwrap_or(self.values().len() as u32) as usize
}
fn offset_type(&self, i: usize) -> ValueType {
- let raw = (self.offsets[i] >> 2) & 0b111;
+ let raw = (self.offsets()[i] >> 2) & 0b111;
ValueType::from_num(raw)
}
fn size(&self, i: usize) -> u32 {
- let start_raw = self.offsets[i];
+ let start_raw = self.offsets()[i];
let end_raw = self
- .offsets
+ .offsets()
.get(i + 1)
.copied()
- .unwrap_or((self.values.len() as u32) << 5);
+ .unwrap_or((self.values().len() as u32) << 5);
let u32_len = (end_raw >> 5) - (start_raw >> 5);
let padding = start_raw & 0b11;
u32_len * 4 - padding
}
- fn get_aligned(&self, index: usize) -> Option<&'a [u32]> {
- let start_raw = self.offsets[index];
+ fn get_aligned<'a>(&'a self, index: usize) -> Option<&'a [u32]> {
+ let start_raw = self.offsets()[index];
let end_raw = self
- .offsets
+ .offsets()
.get(index + 1)
.copied()
- .unwrap_or((self.values.len() as u32) << 5);
+ .unwrap_or((self.values().len() as u32) << 5);
let start = start_raw >> 5;
let end = end_raw >> 5;
- Some(&self.values[start as usize..end as usize])
+ Some(&self.values()[start as usize..end as usize])
}
- fn get_unaligned(&self, index: usize) -> Option<&'a [u8]> {
- let start_raw = self.offsets[index];
+ fn get_unaligned<'a>(&'a self, index: usize) -> Option<&'a [u8]> {
+ let start_raw = self.offsets()[index];
let end_raw = self
- .offsets
+ .offsets()
.get(index + 1)
.copied()
- .unwrap_or((self.values.len() as u32) << 5);
+ .unwrap_or((self.values().len() as u32) << 5);
let start = (start_raw >> 5) * 4;
let padding = start_raw & 0b11;
let end = (end_raw >> 5) * 4 - padding;
- let values_u8: &[u8] = bytemuck::cast_slice(self.values);
+ let values_u8: &[u8] = bytemuck::cast_slice(self.values());
Some(&values_u8[start as usize..end as usize])
}
#[inline]
- pub fn get_typed<T: ValueLoad<'a>>(&self, index: usize) -> Option<T> {
- if T::TYPE.is_aligned() {
- T::load_aligned(self.get_aligned(index)?)
+ pub fn get_typed<'a, T: ValueMarker<'a> + ?Sized>(&'a self, index: usize) -> Option<T::Inner> {
+ if T::Inner::TYPE.is_aligned() {
+ T::Inner::load_aligned(self.get_aligned(index)?)
} else {
- T::load_unaligned(self.get_unaligned(index)?)
+ T::Inner::load_unaligned(self.get_unaligned(index)?)
}
}
- pub fn get<T: ValueLoad<'a>>(&self, tag: TypedTag<T>) -> Option<T> {
- self.get_typed(self.find_field(tag.0)?)
+ pub fn get<'a, T: ValueMarker<'a> + ?Sized>(&'a self, tag: TypedTag<T>) -> Option<T::Inner> {
+ self.get_typed::<T>(self.find_field(tag.0)?)
}
- pub fn keys(&self) -> KeysIter<'a> {
+ pub fn keys<'a>(&'a self) -> KeysIter<'a> {
KeysIter {
- object: *self,
+ object: self,
index: 0,
}
}
- pub fn entries<T>(&self) -> EntriesIter<'a, T> {
+ pub fn entries<'a, T: ?Sized>(&'a self) -> EntriesIter<'a, T> {
EntriesIter {
- object: *self,
+ object: self,
index: 0,
ty: PhantomData,
}
}
- pub fn iter<T>(&self, tag: TypedTag<T>) -> FieldIter<'a, T> {
+ pub fn iter<'a, T: ?Sized>(&'a self, tag: TypedTag<T>) -> FieldIter<'a, T> {
FieldIter {
- object: *self,
- index: self.tags.partition_point(|&x| x < tag.0.0),
+ object: self,
+ index: self.tags().partition_point(|&x| x < tag.0.0),
tag: tag.0.0,
ty: PhantomData,
}
}
#[must_use]
- pub fn extend<T: ValueLoad<'a> + Eq + Ord>(
- &self,
+ pub fn extend<'a, T: ValueMarker<'a> + ?Sized>(
+ &'a self,
tag: TypedTag<T>,
- values: impl IntoIterator<Item = T>,
- ) -> ObjectBuffer {
+ values: impl IntoIterator<Item = T::Inner>,
+ ) -> Box<Object>
+ where
+ T::Inner: Eq + Ord,
+ {
self.insert_multi(
tag,
&self
@@ -209,71 +176,75 @@ impl<'a> Object<'a> {
#[must_use]
pub fn extend_object(
&self,
- tag: TypedTag<Object<'static>>,
+ tag: TypedTag<Object>,
ident: Tag,
- values: impl IntoIterator<Item = ObjectBuffer>,
- ) -> ObjectBuffer {
- let ident = TypedTag(ident, PhantomData::<&[u8]>);
+ values: &[Box<Object>],
+ ) -> Box<Object> {
+ let ident = TypedTag::<[u8]>::new(ident);
let mut new_vals = Vec::new();
for ob in self.iter(tag) {
- new_vals.push(ob.dump());
+ new_vals.push(ob);
}
let mut any_new = false;
for val in values {
- if new_vals
- .iter()
- .all(|rhs| rhs.as_object().get(ident) != val.as_object().get(ident))
- {
+ if new_vals.iter().all(|rhs| rhs.get(ident) != val.get(ident)) {
any_new = true;
- new_vals.push(val);
+ new_vals.push(&val);
}
}
if any_new {
- self.insert_multi(TypedTag(tag.0, PhantomData), &new_vals)
+ self.insert_multi(TypedTag::<Object>::new(tag.0), &new_vals)
} else {
- self.dump()
+ todo!()
}
}
#[must_use]
#[inline]
- pub fn insert<T: ValueStore>(&self, tag: TypedTag<T>, value: T) -> ObjectBuffer {
+ pub fn insert<'a, T: ValueMarker<'a> + ?Sized>(
+ &'a self,
+ tag: TypedTag<T>,
+ value: T::Inner,
+ ) -> Box<Object> {
self.insert_multi(tag, &[value])
}
#[must_use]
#[inline]
- pub fn remove<T: ValueStore>(&self, tag: TypedTag<T>) -> ObjectBuffer {
+ pub fn remove<'a, T: ValueMarker<'a>>(&'a self, tag: TypedTag<T>) -> Box<Object> {
self.insert_multi(tag, &[])
}
#[must_use]
- pub fn insert_multi<T: ValueStore>(&self, tag: TypedTag<T>, values: &[T]) -> ObjectBuffer {
- let prefix = self.tags.partition_point(|&x| x < tag.0.0);
- let suffix = self.tags.partition_point(|&x| x <= tag.0.0);
+ pub fn insert_multi<'a, T: ValueMarker<'a> + ?Sized>(
+ &'a self,
+ tag: TypedTag<T>,
+ values: &[T::Inner],
+ ) -> Box<Object> {
+ let prefix = self.tags().partition_point(|&x| x < tag.0.0);
+ let suffix = self.tags().partition_point(|&x| x <= tag.0.0);
let values_prefix = self.offset(prefix);
let values_suffix = self.offset(suffix);
let mut buf = Vec::new();
let cut_size = suffix - prefix;
- buf.push((self.tags.len() - cut_size + values.len()) as u32);
+ buf.push((self.tags().len() - cut_size + values.len()) as u32);
- buf.extend(&self.tags[..prefix]);
+ buf.extend(&self.tags()[..prefix]);
buf.extend(values.iter().map(|_| tag.0.0));
- buf.extend(&self.tags[suffix..]);
+ buf.extend(&self.tags()[suffix..]);
- buf.extend(&self.offsets[..prefix]);
+ buf.extend(&self.offsets()[..prefix]);
let new_offs = buf.len();
buf.extend(values.iter().map(|_| 0)); // placeholder
let suffix_offs = buf.len();
- buf.extend(&self.offsets[suffix..]); // need offsetting later
+ buf.extend(&self.offsets()[suffix..]); // need offsetting later
let suffix_end = buf.len();
let values_start = buf.len() as u32;
- buf.extend(&self.values[..values_prefix]);
+ buf.extend(&self.values()[..values_prefix]);
let mut temp = Vec::new();
let values_new = buf.len();
for (i, val) in values.iter().enumerate() {
- let ty = val.get_type();
- let off = (buf.len() as u32 - values_start) << 5 | (ty as u32) << 2;
- if ty.is_aligned() {
+ let off = (buf.len() as u32 - values_start) << 5 | (T::Inner::TYPE as u32) << 2;
+ if T::Inner::TYPE.is_aligned() {
buf[new_offs + i] = off;
val.store_aligned(&mut buf);
} else {
@@ -292,77 +263,78 @@ impl<'a> Object<'a> {
.iter_mut()
.for_each(|e| *e = e.strict_add_signed(suffix_offset));
}
- buf.extend(&self.values[values_suffix..]);
+ buf.extend(&self.values()[values_suffix..]);
- ObjectBuffer(buf)
+ vec_to_ob(buf)
}
#[must_use]
pub fn update(
&self,
- tag: TypedTag<Object<'static>>,
- update: impl FnOnce(Object<'a>) -> ObjectBuffer,
- ) -> ObjectBuffer {
- self.insert(tag, update(self.get(tag).unwrap_or_default()).as_object())
+ tag: TypedTag<Object>,
+ update: impl FnOnce(&Object) -> Box<Object>,
+ ) -> Box<Object> {
+ let ob = update(self.get(tag).unwrap_or(EMPTY));
+ self.insert(tag, &ob)
}
}
pub struct KeysIter<'a> {
- object: Object<'a>,
+ object: &'a Object,
index: usize,
}
impl Iterator for KeysIter<'_> {
type Item = Tag;
fn next(&mut self) -> Option<Self::Item> {
- if self.index >= self.object.tags.len() {
+ if self.index >= self.object.tags().len() {
return None;
} else {
self.index += 1;
- Some(Tag(self.object.tags[self.index - 1]))
+ Some(Tag(self.object.tags()[self.index - 1]))
}
}
}
-pub struct EntriesIter<'a, T> {
- object: Object<'a>,
+pub struct EntriesIter<'a, T: ?Sized> {
+ object: &'a Object,
index: usize,
ty: PhantomData<T>,
}
-impl<'a, T: ValueLoad<'a>> Iterator for EntriesIter<'a, T> {
- type Item = (Tag, T);
+impl<'a, T: ValueMarker<'a> + ?Sized> Iterator for EntriesIter<'a, T> {
+ type Item = (Tag, T::Inner);
fn next(&mut self) -> Option<Self::Item> {
loop {
- if self.index >= self.object.tags.len() {
+ if self.index >= self.object.tags().len() {
return None;
}
- if T::TYPE != self.object.offset_type(self.index) {
+ if T::Inner::TYPE != self.object.offset_type(self.index) {
self.index += 1;
continue;
}
- let value = self.object.get_typed(self.index)?;
- let tag = self.object.tags[self.index];
+ let value = self.object.get_typed::<T>(self.index)?;
+ let tag = self.object.tags()[self.index];
self.index += 1;
return Some((Tag(tag), value));
}
}
}
-pub struct FieldIter<'a, T> {
- object: Object<'a>,
+pub struct FieldIter<'a, T: ?Sized> {
+ object: &'a Object,
index: usize,
tag: u32,
ty: PhantomData<T>,
}
-impl<'a, T: ValueLoad<'a>> Iterator for FieldIter<'a, T> {
- type Item = T;
+impl<'a, T: ValueMarker<'a> + ?Sized> Iterator for FieldIter<'a, T> {
+ type Item = T::Inner;
fn next(&mut self) -> Option<Self::Item> {
- if self.index >= self.object.tags.len() {
+ if self.index >= self.object.tags().len() {
return None;
}
- if self.object.tags[self.index] != self.tag {
+ if self.object.tags()[self.index] != self.tag {
return None;
}
- let val = self.object.get_typed(self.index);
+ let val = self.object.get_typed::<T>(self.index);
self.index += 1;
val
}
diff --git a/common/object/src/path.rs b/common/object/src/path.rs
index 793400c..83b2c43 100644
--- a/common/object/src/path.rs
+++ b/common/object/src/path.rs
@@ -5,30 +5,30 @@
*/
use crate::{Object, Tag, TypedTag};
-use std::{fmt::Display, marker::PhantomData, str::FromStr};
+use std::{fmt::Display, str::FromStr};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Path(pub Vec<Tag>);
impl Path {
- pub fn get_matching_value<'a>(&self, ob: Object<'a>) -> Option<&'a [u8]> {
- fn recurse<'a>(ob: Object<'a>, path: &[Tag]) -> Option<&'a [u8]> {
+ pub fn get_matching_value<'a>(&self, ob: &'a Object) -> Option<&'a [u8]> {
+ fn recurse<'a>(ob: &'a Object, path: &[Tag]) -> Option<&'a [u8]> {
if path.len() > 1 {
- recurse(ob.get(TypedTag(path[0], PhantomData))?, &path[1..])
+ recurse(ob.get(TypedTag::<Object>::new(path[0]))?, &path[1..])
} else {
- ob.get(TypedTag(path[0], PhantomData))
+ ob.get(TypedTag::<[u8]>::new(path[0]))
}
}
recurse(ob, &self.0)
}
- pub fn get_matching_values<'a>(&self, ob: Object<'a>) -> Vec<&'a [u8]> {
- fn recurse<'a>(ob: Object<'a>, out: &mut Vec<&'a [u8]>, path: &[Tag]) {
+ pub fn get_matching_values<'a>(&self, ob: &'a Object) -> Vec<&'a [u8]> {
+ fn recurse<'a>(ob: &'a Object, out: &mut Vec<&'a [u8]>, path: &[Tag]) {
if path.len() > 1 {
- for nested in ob.iter(TypedTag(path[0], PhantomData::<Object>)) {
+ for nested in ob.iter(TypedTag::<Object>::new(path[0])) {
recurse(nested, out, &path[1..]);
}
} else {
- out.extend(ob.iter(TypedTag(path[0], PhantomData::<&[u8]>)));
+ out.extend(ob.iter(TypedTag::<[u8]>::new(path[0])));
}
}
let mut out = Vec::new();
diff --git a/common/object/src/registry.rs b/common/object/src/registry.rs
deleted file mode 100644
index af028e8..0000000
--- a/common/object/src/registry.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2026 metamuffin <metamuffin.org>
-*/
-
-use std::any::TypeId;
-
-pub mod types {
- use crate::Object;
- use std::any::TypeId;
-
- pub const OBJECT: TypeId = TypeId::of::<Object>();
- pub const STR: TypeId = TypeId::of::<&str>();
- pub const BINARY: TypeId = TypeId::of::<&[u8]>();
- pub const U32: TypeId = TypeId::of::<u32>();
- pub const U64: TypeId = TypeId::of::<u64>();
- pub const I64: TypeId = TypeId::of::<i64>();
- pub const F64: TypeId = TypeId::of::<f64>();
-}
-
-#[derive(Clone)]
-pub struct TagInfo {
- pub name: &'static str,
- pub r#type: Option<TypeId>,
-}
-
-#[macro_export]
-macro_rules! fields {
- ($($id:ident: $type:ty = $tag:literal;)*) => {
- $(pub const $id: $crate::TypedTag<$type> = $crate::TypedTag($crate::Tag::new($tag), std::marker::PhantomData);)*
- };
-}
-#[macro_export]
-macro_rules! enums {
- ($($id:ident = $tag:literal;)*) => {
- $(pub const $id: $crate::Tag = $crate::Tag::new($tag);)*
- };
-}
diff --git a/common/object/src/tag.rs b/common/object/src/tag.rs
new file mode 100644
index 0000000..c09af27
--- /dev/null
+++ b/common/object/src/tag.rs
@@ -0,0 +1,73 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2026 metamuffin <metamuffin.org>
+*/
+
+use core::marker::{PhantomData, Sized};
+use std::fmt::Display;
+
+#[repr(transparent)]
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Tag(pub u32);
+
+impl Default for Tag {
+ fn default() -> Self {
+ Self::new(b"1111")
+ }
+}
+impl Tag {
+ pub const fn new(fourcc: &[u8; 4]) -> Self {
+ Self(u32::from_le_bytes(*fourcc))
+ }
+}
+impl Display for Tag {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(str::from_utf8(&self.0.to_le_bytes()).unwrap())
+ }
+}
+
+#[derive(PartialEq, Eq, PartialOrd, Ord)]
+pub struct TypedTag<T: ?Sized>(pub Tag, pub PhantomData<T>);
+impl<T: ?Sized> Display for TypedTag<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+impl<T: ?Sized> Clone for TypedTag<T> {
+ fn clone(&self) -> Self {
+ Self(self.0, PhantomData)
+ }
+}
+impl<T: ?Sized> Copy for TypedTag<T> {}
+impl<T: ?Sized> TypedTag<T> {
+ pub const fn new(tag: Tag) -> Self {
+ Self(tag, PhantomData)
+ }
+}
+
+pub mod types {
+ use crate::Object;
+ use std::any::TypeId;
+
+ pub const OBJECT: TypeId = TypeId::of::<Object>();
+ pub const STR: TypeId = TypeId::of::<&str>();
+ pub const BINARY: TypeId = TypeId::of::<&[u8]>();
+ pub const U32: TypeId = TypeId::of::<u32>();
+ pub const U64: TypeId = TypeId::of::<u64>();
+ pub const I64: TypeId = TypeId::of::<i64>();
+ pub const F64: TypeId = TypeId::of::<f64>();
+}
+
+#[macro_export]
+macro_rules! fields {
+ ($($id:ident: $type:ty = $tag:literal;)*) => {
+ $(pub const $id: $crate::TypedTag<$type> = $crate::TypedTag::new($crate::Tag::new($tag));)*
+ };
+}
+#[macro_export]
+macro_rules! enums {
+ ($($id:ident = $tag:literal;)*) => {
+ $(pub const $id: $crate::Tag = $crate::Tag::new($tag);)*
+ };
+}
diff --git a/common/object/src/tests.rs b/common/object/src/tests.rs
index 73f7b6f..b6455f5 100644
--- a/common/object/src/tests.rs
+++ b/common/object/src/tests.rs
@@ -4,28 +4,27 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::{Object, ObjectBuffer, fields};
+use crate::{EMPTY, OBB, Object, fields};
fields! {
- NAME: &str = b"name";
+ NAME: str = b"name";
AGE: u32 = b"age1";
- FRIEND: &str = b"frnd";
+ FRIEND: str = b"frnd";
STUFF: Object = b"stff";
}
-fn test_object() -> ObjectBuffer {
- ObjectBuffer::new(&mut [
- (NAME.0, &"Bob"),
- (AGE.0, &35_u32),
- (FRIEND.0, &"Alice"),
- (FRIEND.0, &"Charlie"),
- ])
+fn test_object() -> Box<Object> {
+ OBB::new()
+ .with(NAME, "Bob")
+ .with(AGE, 35)
+ .with(FRIEND, "Alice")
+ .with(FRIEND, "Charlie")
+ .finish()
}
#[test]
fn read_single_field() {
let bob = test_object();
- let bob = bob.as_object();
assert_eq!(bob.get(NAME), Some("Bob"));
assert_eq!(bob.get(AGE), Some(35));
}
@@ -33,7 +32,6 @@ fn read_single_field() {
#[test]
fn read_multi_field() {
let bob = test_object();
- let bob = bob.as_object();
let mut friends = bob.iter(FRIEND);
assert_eq!(friends.next(), Some("Alice"));
@@ -56,10 +54,10 @@ fn vec_align_test() {
#[test]
fn insert() {
let bob = test_object();
- let edward = bob.as_object().insert(NAME, "Edward");
+ let edward = bob.insert(NAME, "Edward");
- eprintln!("{:#?}", bob.as_object());
- let edward = edward.as_object();
+ eprintln!("{:#?}", bob);
+ let edward = edward;
eprintln!("{edward:#?}");
assert_eq!(edward.get(NAME), Some("Edward"));
assert_eq!(edward.get(AGE), Some(35));
@@ -72,11 +70,11 @@ fn insert() {
#[test]
fn insert_empty() {
- let ob = Object::EMPTY.insert(NAME, "Romeo");
- assert_eq!(ob.as_object().get(NAME), Some("Romeo"));
+ let ob = EMPTY.insert(NAME, "Romeo");
+ assert_eq!(ob.get(NAME), Some("Romeo"));
- let ob = Object::EMPTY.insert(STUFF, Object::EMPTY);
- assert_eq!(ob.as_object().get(STUFF), Some(Object::EMPTY));
+ let ob = EMPTY.insert(STUFF, EMPTY);
+ assert_eq!(ob.get(STUFF), Some(EMPTY));
}
#[test]
diff --git a/common/object/src/value.rs b/common/object/src/value.rs
index 9b9a438..0a1ceb9 100644
--- a/common/object/src/value.rs
+++ b/common/object/src/value.rs
@@ -4,19 +4,58 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::{Object, ObjectBuffer, Tag};
+use crate::{Object, Tag, slice_to_ob};
+use core::{marker::Sized, unimplemented};
use std::{borrow::Cow, fmt::Display, str::FromStr};
-pub trait ValueLoad<'a>: ValueStore + Sized {
+pub trait ValueMarker<'a> {
+ type Inner: ValueSer<'a>;
+}
+
+impl<'a> ValueMarker<'a> for str {
+ type Inner = &'a str;
+}
+impl<'a> ValueMarker<'a> for Object {
+ type Inner = &'a Object;
+}
+impl<'a> ValueMarker<'a> for [u8] {
+ type Inner = &'a [u8];
+}
+
+impl<'a> ValueMarker<'a> for Tag {
+ type Inner = Tag;
+}
+impl<'a> ValueMarker<'a> for u32 {
+ type Inner = u32;
+}
+impl<'a> ValueMarker<'a> for i64 {
+ type Inner = i64;
+}
+impl<'a> ValueMarker<'a> for u64 {
+ type Inner = u64;
+}
+impl<'a> ValueMarker<'a> for () {
+ type Inner = ();
+}
+impl<'a> ValueMarker<'a> for f64 {
+ type Inner = f64;
+}
+
+pub trait ValueSer<'a>: Sized {
const TYPE: ValueType;
- fn load_aligned(buf: &'a [u32]) -> Option<Self> {
- let _ = buf;
+ fn load_aligned(_buf: &'a [u32]) -> Option<Self> {
unimplemented!()
}
- fn load_unaligned(buf: &'a [u8]) -> Option<Self> {
- let _ = buf;
+ fn load_unaligned(_buf: &'a [u8]) -> Option<Self> {
+ unimplemented!()
+ }
+ fn store_aligned(&self, _buf: &mut Vec<u32>) {
unimplemented!()
}
+ fn store_unaligned(&self, _buf: &mut Vec<u8>) {
+ unimplemented!()
+ }
+ fn size(&self) -> usize;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -126,22 +165,11 @@ impl FromStr for Value<'static> {
}
}
-pub trait ValueStore {
- fn get_type(&self) -> ValueType;
- fn store_aligned(&self, _buf: &mut Vec<u32>) {}
- fn store_unaligned(&self, _buf: &mut Vec<u8>) {}
- fn size(&self) -> usize;
-}
-impl<'a> ValueLoad<'a> for &'a str {
+impl<'a> ValueSer<'a> for &'a str {
const TYPE: ValueType = ValueType::String;
fn load_unaligned(buf: &'a [u8]) -> Option<Self> {
str::from_utf8(buf).ok()
}
-}
-impl ValueStore for &str {
- fn get_type(&self) -> ValueType {
- ValueType::String
- }
fn store_unaligned(&self, buf: &mut Vec<u8>) {
buf.extend(self.as_bytes());
}
@@ -149,16 +177,11 @@ impl ValueStore for &str {
self.len()
}
}
-impl ValueLoad<'_> for u32 {
+impl ValueSer<'_> for u32 {
const TYPE: ValueType = ValueType::UInt;
fn load_aligned(buf: &[u32]) -> Option<Self> {
buf.get(0).copied().map(u32::from_be)
}
-}
-impl ValueStore for u32 {
- fn get_type(&self) -> ValueType {
- ValueType::UInt
- }
fn store_aligned(&self, buf: &mut Vec<u32>) {
buf.push(self.to_be());
}
@@ -166,16 +189,11 @@ impl ValueStore for u32 {
4
}
}
-impl ValueLoad<'_> for Tag {
+impl ValueSer<'_> for Tag {
const TYPE: ValueType = ValueType::Tag;
fn load_aligned(buf: &[u32]) -> Option<Self> {
buf.get(0).copied().map(u32::from_be).map(Tag)
}
-}
-impl ValueStore for Tag {
- fn get_type(&self) -> ValueType {
- ValueType::Tag
- }
fn store_aligned(&self, buf: &mut Vec<u32>) {
buf.push(self.0.to_be());
}
@@ -183,18 +201,13 @@ impl ValueStore for Tag {
4
}
}
-impl ValueLoad<'_> for u64 {
+impl ValueSer<'_> for u64 {
const TYPE: ValueType = ValueType::UInt;
fn load_aligned(buf: &[u32]) -> Option<Self> {
let hi = u32::from_be(*buf.get(0)?) as u64;
let lo = u32::from_be(*buf.get(1)?) as u64;
Some(hi << 32 | lo)
}
-}
-impl ValueStore for u64 {
- fn get_type(&self) -> ValueType {
- ValueType::UInt
- }
fn store_aligned(&self, buf: &mut Vec<u32>) {
buf.push(((self >> 32) as u32).to_be());
buf.push((*self as u32).to_be());
@@ -203,7 +216,7 @@ impl ValueStore for u64 {
8
}
}
-impl ValueLoad<'_> for f64 {
+impl ValueSer<'_> for f64 {
const TYPE: ValueType = ValueType::Float;
fn load_aligned(buf: &[u32]) -> Option<Self> {
if buf.len() < 2 {
@@ -215,11 +228,6 @@ impl ValueLoad<'_> for f64 {
a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3],
]))
}
-}
-impl ValueStore for f64 {
- fn get_type(&self) -> ValueType {
- ValueType::Float
- }
fn store_aligned(&self, buf: &mut Vec<u32>) {
let b = self.to_be_bytes();
buf.push(u32::from_be_bytes([b[0], b[1], b[2], b[3]]));
@@ -229,16 +237,11 @@ impl ValueStore for f64 {
8
}
}
-impl ValueLoad<'_> for i64 {
+impl ValueSer<'_> for i64 {
const TYPE: ValueType = ValueType::Int;
fn load_aligned(buf: &[u32]) -> Option<Self> {
u64::load_aligned(buf).map(|x| x as i64)
}
-}
-impl ValueStore for i64 {
- fn get_type(&self) -> ValueType {
- ValueType::Int
- }
fn store_aligned(&self, buf: &mut Vec<u32>) {
(*self as u64).store_aligned(buf);
}
@@ -246,47 +249,24 @@ impl ValueStore for i64 {
8
}
}
-impl<'a> ValueLoad<'a> for Object<'a> {
+impl<'a> ValueSer<'a> for &'a Object {
const TYPE: ValueType = ValueType::Object;
fn load_aligned(buf: &'a [u32]) -> Option<Self> {
- Self::load(buf)
- }
-}
-impl ValueStore for Object<'_> {
- fn get_type(&self) -> ValueType {
- ValueType::Object
- }
- fn store_aligned(&self, buf: &mut Vec<u32>) {
- buf.push(self.tags.len() as u32);
- buf.extend(self.tags);
- buf.extend(self.offsets);
- buf.extend(self.values);
- }
- fn size(&self) -> usize {
- (self.tags.len() + self.offsets.len() + self.values.len()) * size_of::<u32>()
- }
-}
-impl ValueStore for ObjectBuffer {
- fn get_type(&self) -> ValueType {
- ValueType::Object
+ // TODO validate
+ Some(slice_to_ob(buf))
}
fn store_aligned(&self, buf: &mut Vec<u32>) {
buf.extend(&self.0);
}
fn size(&self) -> usize {
- self.0.len() * 4
+ self.0.len() * size_of::<u32>()
}
}
-impl<'a> ValueLoad<'a> for &'a [u8] {
+impl<'a> ValueSer<'a> for &'a [u8] {
const TYPE: ValueType = ValueType::Binary;
fn load_unaligned(buf: &'a [u8]) -> Option<Self> {
Some(buf)
}
-}
-impl ValueStore for &[u8] {
- fn get_type(&self) -> ValueType {
- ValueType::Binary
- }
fn store_unaligned(&self, buf: &mut Vec<u8>) {
buf.extend(*self);
}
@@ -294,16 +274,11 @@ impl ValueStore for &[u8] {
self.len()
}
}
-impl<'a> ValueLoad<'a> for () {
+impl<'a> ValueSer<'a> for () {
const TYPE: ValueType = ValueType::Int;
fn load_aligned(_buf: &'a [u32]) -> Option<Self> {
Some(())
}
-}
-impl ValueStore for () {
- fn get_type(&self) -> ValueType {
- ValueType::Binary
- }
fn store_aligned(&self, _buf: &mut Vec<u32>) {}
fn size(&self) -> usize {
0
diff --git a/common/src/api.rs b/common/src/api.rs
index d9f27c5..bf79cc0 100644
--- a/common/src/api.rs
+++ b/common/src/api.rs
@@ -5,12 +5,13 @@
*/
use jellyobject::{Object, Tag};
+use std::borrow::Cow;
use std::collections::BTreeMap;
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug)]
pub struct Nku<'a> {
- pub node: Object<'a>,
- pub userdata: Object<'a>,
+ pub node: Cow<'a, Object>,
+ pub userdata: Cow<'a, Object>,
pub role: Option<&'a str>,
}
diff --git a/common/src/internal.rs b/common/src/internal.rs
index 5aedfc7..844468d 100644
--- a/common/src/internal.rs
+++ b/common/src/internal.rs
@@ -10,7 +10,7 @@ use jellyobject::fields;
use serde::{Deserialize, Serialize};
fields! {
- IM_PATH: &str = b"Ipth";
+ IM_PATH: str = b"Ipth";
IM_MTIME: u64 = b"Imtm";
}
diff --git a/common/src/lib.rs b/common/src/lib.rs
index d94c72c..417acee 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -3,6 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
+#![feature(phantom_variance_markers)]
pub mod api;
pub mod internal;
pub mod node;
diff --git a/common/src/node.rs b/common/src/node.rs
index 92b5a5b..ff3289f 100644
--- a/common/src/node.rs
+++ b/common/src/node.rs
@@ -10,51 +10,51 @@ fields! {
// Tag counter: 111
NO_KIND: Tag = b"kind";
- NO_TITLE: &str = b"titl";
+ NO_TITLE: str = b"titl";
NO_PARENT: u64 = b"prnt"; // multi
- NO_SUBTITLE: &str = b"sbtl";
- NO_TAGLINE: &str = b"tgln";
- NO_DESCRIPTION: &str = b"desc";
+ NO_SUBTITLE: str = b"sbtl";
+ NO_TAGLINE: str = b"tgln";
+ NO_DESCRIPTION: str = b"desc";
NO_RELEASEDATE: i64 = b"rldt";
NO_DURATION: f64 = b"durn";
NO_INDEX: u64 = b"indx";
NO_SEASON_INDEX: u64 = b"sidx";
NO_TRACK: Object = b"trak"; // multi
NO_CHAPTER: Object = b"chpt"; // multi
- NO_TAG: &str = b"tag1"; // multi
+ NO_TAG: str = b"tag1"; // multi
NO_RATINGS: Object = b"rtng";
NO_PICTURES: Object = b"pict";
NO_IDENTIFIERS: Object = b"iden";
NO_VISIBILITY: Tag = b"visi";
NO_STORAGE_SIZE: u64 = b"stsz";
NO_CREDIT: Object = b"crdt"; // multi
- NO_SLUG: &str = b"slug";
+ NO_SLUG: str = b"slug";
NO_METASOURCE: Object = b"msrc";
CR_NODE: u64 = b"node";
CR_KIND: Tag = b"kind";
- CR_ROLE: &str = b"role"; // multi
+ CR_ROLE: str = b"role"; // multi
TR_KIND: Tag = b"kind";
TR_SOURCE: Object = b"sour";
- TR_NAME: &str = b"name";
- TR_CODEC: &str = b"codc";
- TR_LANGUAGE: &str = b"lang"; // BCP 47
+ TR_NAME: str = b"name";
+ TR_CODEC: str = b"codc";
+ TR_LANGUAGE: str = b"lang"; // BCP 47
TR_RATE: f64 = b"rate";
TR_BIT_DEPTH: u32 = b"bdep";
TR_CHANNELS: u32 = b"chnl";
TR_PIXEL_WIDTH: u32 = b"pwid";
TR_PIXEL_HEIGHT: u32 = b"phei";
- TRSOURCE_LOCAL_PATH: &str = b"lpat";
+ TRSOURCE_LOCAL_PATH: str = b"lpat";
TRSOURCE_LOCAL_TRACKNUM: u64 = b"ltrn";
CH_START: f64 = b"strt";
CH_END: f64 = b"end1";
- CH_NAME: &str = b"name";
+ CH_NAME: str = b"name";
- PICT_COVER: &str = b"covr";
- PICT_BACKDROP: &str = b"bdro";
+ PICT_COVER: str = b"covr";
+ PICT_BACKDROP: str = b"bdro";
RTYP_IMDB: f64 = b"imdb";
RTYP_TMDB: f64 = b"tmdb";
@@ -65,31 +65,31 @@ fields! {
RTYP_YOUTUBE_SUBSCRIBERS: f64 = b"ytsu";
RTYP_TRAKT: f64 = b"trkt";
- IDENT_MUSICBRAINZ_RECORDING: &str = b"mbrc";
- IDENT_MUSICBRAINZ_ARTIST: &str = b"mbar";
- IDENT_MUSICBRAINZ_RELEASE: &str = b"mbrl";
- IDENT_MUSICBRAINZ_RELEASE_GROUP: &str = b"mbrg";
- IDENT_ACOUST_ID_TRACK: &str = b"actr";
- IDENT_YOUTUBE_VIDEO: &str = b"ytvi";
- IDENT_YOUTUBE_CHANNEL: &str = b"ytc1";
- IDENT_YOUTUBE_CHANNEL_HANDLE: &str = b"ytch";
- IDENT_BANDCAMP: &str = b"bcmp";
- IDENT_ISRC: &str = b"isrc";
- IDENT_BARCODE: &str = b"barc";
- IDENT_TRAKT_MOVIE: &str = b"trmv";
- IDENT_TRAKT_SHOW: &str = b"trsh";
- IDENT_TRAKT_SEASON: &str = b"trse";
- IDENT_TRAKT_EPISODE: &str = b"trep";
- IDENT_TRAKT_PERSON: &str = b"trpe";
- IDENT_IMDB: &str = b"imdb";
- IDENT_IMDB_PERSON: &str = b"impe";
- IDENT_TMDB_SERIES: &str = b"tmse";
- IDENT_TMDB_MOVIE: &str = b"tmmv";
- IDENT_TMDB_PERSON: &str = b"tmpe";
- IDENT_TVDB: &str = b"tvdb";
- IDENT_OMDB: &str = b"omdb";
- IDENT_VGMDB_ARTIST: &str = b"vgar";
- IDENT_WIKIDATA: &str = b"wkdt";
+ IDENT_MUSICBRAINZ_RECORDING: str = b"mbrc";
+ IDENT_MUSICBRAINZ_ARTIST: str = b"mbar";
+ IDENT_MUSICBRAINZ_RELEASE: str = b"mbrl";
+ IDENT_MUSICBRAINZ_RELEASE_GROUP: str = b"mbrg";
+ IDENT_ACOUST_ID_TRACK: str = b"actr";
+ IDENT_YOUTUBE_VIDEO: str = b"ytvi";
+ IDENT_YOUTUBE_CHANNEL: str = b"ytc1";
+ IDENT_YOUTUBE_CHANNEL_HANDLE: str = b"ytch";
+ IDENT_BANDCAMP: str = b"bcmp";
+ IDENT_ISRC: str = b"isrc";
+ IDENT_BARCODE: str = b"barc";
+ IDENT_TRAKT_MOVIE: str = b"trmv";
+ IDENT_TRAKT_SHOW: str = b"trsh";
+ IDENT_TRAKT_SEASON: str = b"trse";
+ IDENT_TRAKT_EPISODE: str = b"trep";
+ IDENT_TRAKT_PERSON: str = b"trpe";
+ IDENT_IMDB: str = b"imdb";
+ IDENT_IMDB_PERSON: str = b"impe";
+ IDENT_TMDB_SERIES: str = b"tmse";
+ IDENT_TMDB_MOVIE: str = b"tmmv";
+ IDENT_TMDB_PERSON: str = b"tmpe";
+ IDENT_TVDB: str = b"tvdb";
+ IDENT_OMDB: str = b"omdb";
+ IDENT_VGMDB_ARTIST: str = b"vgar";
+ IDENT_WIKIDATA: str = b"wkdt";
}
diff --git a/common/src/user.rs b/common/src/user.rs
index 6511b10..b83cc9c 100644
--- a/common/src/user.rs
+++ b/common/src/user.rs
@@ -6,13 +6,13 @@
use jellyobject::{Object, Tag, enums, fields};
-pub type User<'a> = Object<'a>;
+pub type User = Object;
fields! {
- USER_LOGIN: &str = b"Ulgn";
- USER_PASSWORD: &[u8] = b"Upwd";
+ USER_LOGIN: str = b"Ulgn";
+ USER_PASSWORD: [u8] = b"Upwd";
USER_PASSWORD_REQUIRE_CHANGE: () = b"Upwc";
- USER_NAME: &str = b"Unam";
+ USER_NAME: str = b"Unam";
USER_ADMIN: () = b"Uadm";
USER_THEME_PRESET: Tag = b"Utpr";
USER_THEME_ACCENT: u32 = b"Utac";
diff --git a/database/src/helper.rs b/database/src/helper.rs
index 28ab1fa..d1fc29a 100644
--- a/database/src/helper.rs
+++ b/database/src/helper.rs
@@ -20,4 +20,4 @@ pub trait DatabaseReturnExt: Database {
Ok(ret.unwrap())
}
}
-impl<D: Database> DatabaseReturnExt for D {}
+impl<D: Database + ?Sized> DatabaseReturnExt for D {}
diff --git a/database/src/kv/binning.rs b/database/src/kv/binning.rs
index 253bb5c..d9b173d 100644
--- a/database/src/kv/binning.rs
+++ b/database/src/kv/binning.rs
@@ -22,14 +22,14 @@ impl Binning {
comps.sort();
Self(comps)
}
- pub fn apply(&self, ob: Object<'_>, keys: &mut Vec<Vec<u8>>) {
+ pub fn apply(&self, ob: &Object, keys: &mut Vec<Vec<u8>>) {
for f in &self.0 {
f.apply(ob, keys);
}
}
}
impl BinningComponent {
- pub fn apply(&self, ob: Object<'_>, keys: &mut Vec<Vec<u8>>) {
+ pub fn apply(&self, ob: &Object, keys: &mut Vec<Vec<u8>>) {
match self {
BinningComponent::Has(path) => {
if path.get_matching_value(ob).is_none() {
diff --git a/database/src/kv/index.rs b/database/src/kv/index.rs
index ee14d77..ba5784b 100644
--- a/database/src/kv/index.rs
+++ b/database/src/kv/index.rs
@@ -22,7 +22,7 @@ pub fn update_index(
is: SubtreeNum,
ik: &IndexKey,
row: RowNum,
- ob: Object<'_>,
+ ob: &Object,
remove: bool,
) -> Result<()> {
let mut ks = vec![is.to_be_bytes().to_vec()];
diff --git a/database/src/kv/mod.rs b/database/src/kv/mod.rs
index 837aa51..784cbef 100644
--- a/database/src/kv/mod.rs
+++ b/database/src/kv/mod.rs
@@ -24,7 +24,7 @@ use crate::{
},
};
use anyhow::{Result, anyhow};
-use jellyobject::ObjectBuffer;
+use jellyobject::{Object, vec_to_ob, vec_u8_to_u32};
use log::{debug, info};
use std::{borrow::Cow, fmt::Write};
@@ -44,22 +44,17 @@ impl<T: jellykv::Store> Database for T {
}
impl Transaction for &mut dyn jellykv::Transaction {
- fn insert(&mut self, entry: ObjectBuffer) -> Result<RowNum> {
+ fn insert(&mut self, entry: Box<Object>) -> Result<RowNum> {
debug!("insert {entry:?}");
let mut id_counter = read_counter(*self, &T_ROW_COUNTER.to_be_bytes(), 0)?;
let row = id_counter;
id_counter += 1;
write_counter(*self, &T_ROW_COUNTER.to_be_bytes(), id_counter)?;
- jellykv::Transaction::set(
- *self,
- &row_key(row),
- bytemuck::cast_slice(entry.0.as_slice()),
- )?;
+ jellykv::Transaction::set(*self, &row_key(row), bytemuck::cast_slice(entry.export()))?;
- let ob = entry.as_object();
for (is, ik) in list_indices(*self)? {
- update_index(*self, is, &ik, row, ob, false)?;
+ update_index(*self, is, &ik, row, &entry, false)?;
}
Ok(row)
@@ -67,34 +62,28 @@ impl Transaction for &mut dyn jellykv::Transaction {
fn remove(&mut self, row: RowNum) -> Result<()> {
debug!("remove {row}");
let entry = self.get(row)?.ok_or(anyhow!("row did not exist"))?;
- let ob = entry.as_object();
for (is, ik) in list_indices(*self)? {
- update_index(*self, is, &ik, row, ob, true)?;
+ update_index(*self, is, &ik, row, &entry, true)?;
}
jellykv::Transaction::del(*self, &row_key(row))
}
- fn update(&mut self, row: RowNum, entry: ObjectBuffer) -> Result<()> {
+ fn update(&mut self, row: RowNum, entry: Box<Object>) -> Result<()> {
debug!("update {row}");
let before = self.get(row)?.ok_or(anyhow!("row to update missing"))?;
- let before = before.as_object();
- let after = entry.as_object();
-
- jellykv::Transaction::set(
- *self,
- &row_key(row),
- bytemuck::cast_slice(entry.0.as_slice()),
- )?;
+ jellykv::Transaction::set(*self, &row_key(row), bytemuck::cast_slice(entry.export()))?;
for (is, ik) in list_indices(*self)? {
- update_index(*self, is, &ik, row, before, true)?;
- update_index(*self, is, &ik, row, after, false)?;
+ update_index(*self, is, &ik, row, &before, true)?;
+ update_index(*self, is, &ik, row, &entry, false)?;
}
Ok(())
}
- fn get(&self, row: RowNum) -> Result<Option<ObjectBuffer>> {
- Ok(jellykv::Transaction::get(*self, &row_key(row))?.map(ObjectBuffer::from))
+ fn get(&self, row: RowNum) -> Result<Option<Box<Object>>> {
+ Ok(jellykv::Transaction::get(*self, &row_key(row))?
+ .map(vec_u8_to_u32)
+ .map(vec_to_ob))
}
fn query<'a>(
@@ -181,8 +170,10 @@ fn create_index(txn: &mut dyn jellykv::Transaction, is: SubtreeNum, ik: &IndexKe
for rowkey in rowkeys {
let row = inv_row_key(&rowkey);
- let buf = ObjectBuffer::from(jellykv::Transaction::get(txn, &rowkey)?.unwrap());
- update_index(txn, is, ik, row, buf.as_object(), false)?;
+ let buf = vec_to_ob(vec_u8_to_u32(
+ jellykv::Transaction::get(txn, &rowkey)?.unwrap(),
+ ));
+ update_index(txn, is, ik, row, &buf, false)?;
}
Ok(())
diff --git a/database/src/kv/tests.rs b/database/src/kv/tests.rs
index e3e6b3b..5e65b4b 100644
--- a/database/src/kv/tests.rs
+++ b/database/src/kv/tests.rs
@@ -21,7 +21,7 @@ pub fn insert_get() -> Result<()> {
Ok(())
})?;
- assert_eq!(bob.unwrap().as_object().get(NAME).unwrap(), "Bob");
+ assert_eq!(bob.unwrap().get(NAME).unwrap(), "Bob");
Ok(())
}
@@ -37,7 +37,7 @@ pub fn update() -> Result<()> {
Ok(())
})?;
db.transaction(&mut |txn| {
- let better_bob = new_bob().as_object().insert(NAME, "Better Bob");
+ let better_bob = new_bob().insert(NAME, "Better Bob");
txn.update(bob_row, better_bob)?;
Ok(())
})?;
@@ -46,7 +46,7 @@ pub fn update() -> Result<()> {
Ok(())
})?;
- assert_eq!(bob.unwrap().as_object().get(NAME).unwrap(), "Better Bob");
+ assert_eq!(bob.unwrap().get(NAME).unwrap(), "Better Bob");
Ok(())
}
diff --git a/database/src/lib.rs b/database/src/lib.rs
index b8a67c9..263c2fc 100644
--- a/database/src/lib.rs
+++ b/database/src/lib.rs
@@ -3,15 +3,15 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-#![feature(if_let_guard)]
+#![feature(phantom_variance_markers)]
+pub mod helper;
pub mod kv;
pub mod query_syntax;
#[cfg(test)]
pub mod test_shared;
-pub mod helper;
use anyhow::Result;
-use jellyobject::{ObjectBuffer, Path, Value};
+use jellyobject::{Object, Path, Value};
pub type RowNum = u64;
pub type RowIter = Box<dyn Iterator<Item = Result<(RowNum, Vec<u8>)>>>;
@@ -21,10 +21,10 @@ pub trait Database: Send + Sync {
}
pub trait Transaction {
- fn insert(&mut self, entry: ObjectBuffer) -> Result<RowNum>;
+ fn insert(&mut self, entry: Box<Object>) -> Result<RowNum>;
fn remove(&mut self, row: RowNum) -> Result<()>;
- fn update(&mut self, row: RowNum, entry: ObjectBuffer) -> Result<()>;
- fn get(&self, row: RowNum) -> Result<Option<ObjectBuffer>>;
+ fn update(&mut self, row: RowNum, entry: Box<Object>) -> Result<()>;
+ fn get(&self, row: RowNum) -> Result<Option<Box<Object>>>;
fn query<'a>(
&'a mut self,
query: Query,
diff --git a/database/src/test_shared.rs b/database/src/test_shared.rs
index 81d2e60..4d06a1f 100644
--- a/database/src/test_shared.rs
+++ b/database/src/test_shared.rs
@@ -4,25 +4,33 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use jellyobject::{ObjectBuffer, fields};
+use jellyobject::{OBB, Object, fields};
fields! {
- NAME: &str = b"name";
+ NAME: str = b"name";
AGE: u32 = b"age1";
- FRIEND: &str = b"frnd";
+ FRIEND: str = b"frnd";
}
-pub(crate) fn new_bob() -> ObjectBuffer {
- ObjectBuffer::new(&mut [
- (NAME.0, &"Bob"),
- (AGE.0, &35_u32),
- (FRIEND.0, &"Alice"),
- (FRIEND.0, &"Charlie"),
- ])
+pub(crate) fn new_bob() -> Box<Object> {
+ OBB::new()
+ .with(NAME, "Bob")
+ .with(AGE, 35)
+ .with(FRIEND, "Alice")
+ .with(FRIEND, "Charlie")
+ .finish()
}
-pub(crate) fn new_alice() -> ObjectBuffer {
- ObjectBuffer::new(&mut [(NAME.0, &"Alice"), (AGE.0, &31_u32), (FRIEND.0, &"Bob")])
+pub(crate) fn new_alice() -> Box<Object> {
+ OBB::new()
+ .with(NAME, "Alice")
+ .with(AGE, 31)
+ .with(FRIEND, "Bob")
+ .finish()
}
-pub(crate) fn new_charlie() -> ObjectBuffer {
- ObjectBuffer::new(&mut [(NAME.0, &"Charlie"), (AGE.0, &31_u32), (FRIEND.0, &"Bob")])
+pub(crate) fn new_charlie() -> Box<Object> {
+ OBB::new()
+ .with(NAME, "Charlie")
+ .with(AGE, 31)
+ .with(FRIEND, "Bob")
+ .finish()
}
diff --git a/import/src/helpers.rs b/import/src/helpers.rs
index e42ea8e..d25957f 100644
--- a/import/src/helpers.rs
+++ b/import/src/helpers.rs
@@ -7,7 +7,7 @@
use anyhow::Result;
use jellycommon::{
NO_SLUG,
- jellyobject::{ObjectBuffer, Path},
+ jellyobject::{OBB, Path},
};
use jellydb::{Filter, Query, RowNum, Transaction};
@@ -17,6 +17,6 @@ pub fn get_or_insert_slug(txn: &mut dyn Transaction, slug: &str) -> Result<RowNu
..Default::default()
})? {
Some(r) => Ok(r),
- None => txn.insert(ObjectBuffer::new(&mut [(NO_SLUG.0, &slug)])),
+ None => txn.insert(OBB::new().with(NO_SLUG, slug).finish()),
}
}
diff --git a/import/src/lib.rs b/import/src/lib.rs
index 684ef07..98f7216 100644
--- a/import/src/lib.rs
+++ b/import/src/lib.rs
@@ -3,7 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-#![feature(duration_constants, exit_status_error)]
+#![feature(duration_constants, exit_status_error, phantom_variance_markers)]
pub mod helpers;
pub mod plugins;
@@ -18,7 +18,7 @@ use anyhow::{Context, Result, anyhow};
use jellycache::{Cache, HashKey};
use jellycommon::{
internal::{IM_MTIME, IM_PATH},
- jellyobject::{self, ObjectBuffer, Path as TagPath, Tag},
+ jellyobject::{self, OBB, Object, Path as TagPath, Tag},
*,
};
use jellydb::{Database, Filter, Query, RowNum};
@@ -92,7 +92,7 @@ impl ImportConfig {
pub fn update_node(
&self,
node: RowNum,
- mut update: impl FnMut(ObjectBuffer) -> ObjectBuffer,
+ mut update: impl FnMut(Box<Object>) -> Box<Object>,
) -> Result<()> {
self.db.transaction(&mut |txn| {
let ob_before = txn.get(node)?.unwrap();
@@ -105,24 +105,24 @@ impl ImportConfig {
pub fn update_node_slug(
&self,
slug: &str,
- mut update: impl FnMut(ObjectBuffer) -> ObjectBuffer,
+ mut update: impl FnMut(Box<Object>) -> Box<Object>,
) -> Result<RowNum> {
let mut row = 0;
self.db.transaction(&mut |txn| {
row = match txn.query_single(node_slug_query(slug))? {
Some(r) => r,
- None => txn.insert(ObjectBuffer::new(&mut [(NO_SLUG.0, &slug)]))?,
+ None => txn.insert(OBB::new().with(NO_SLUG, slug).finish())?,
};
let node = txn.get(row)?.unwrap();
let node = update(node);
- let node = node.as_object().insert(NO_SLUG, slug);
+ let node = node.insert(NO_SLUG, slug);
txn.update(row, node)?;
Ok(())
})?;
Ok(row)
}
- pub fn get_node(&self, node: RowNum) -> Result<Option<ObjectBuffer>> {
+ pub fn get_node(&self, node: RowNum) -> Result<Option<Box<Object>>> {
let mut buf = None;
self.db.transaction(&mut |txn| {
buf = txn.get(node)?;
@@ -235,9 +235,9 @@ fn import_traverse(
let row = ic.update_node_slug(&slug, |mut node| {
if let Some(parent) = parent {
- node = node.as_object().extend(NO_PARENT, [parent]);
+ node = node.extend(NO_PARENT, [parent]);
}
- node = node.as_object().insert(
+ node = node.insert(
NO_VISIBILITY,
if iflags.hidden {
VISI_HIDDEN
@@ -325,8 +325,8 @@ fn import_file(
let slug = get_node_slug(ic, path).unwrap();
let Some(row) = reporting::catch(ic.update_node_slug(&slug, |mut node| {
- node = node.as_object().extend(NO_PARENT, [parent]);
- node = node.as_object().insert(
+ node = node.extend(NO_PARENT, [parent]);
+ node = node.insert(
NO_VISIBILITY,
if iflags.hidden {
VISI_HIDDEN
@@ -415,7 +415,7 @@ fn process_node(
let mut slug = String::new();
reporting::catch(dba.db.transaction(&mut |txn| {
let no = txn.get(node)?.unwrap();
- if let Some(s) = no.as_object().get(NO_SLUG) {
+ if let Some(s) = no.get(NO_SLUG) {
slug = s.to_owned();
}
Ok(())
@@ -460,7 +460,7 @@ fn compare_mtime(dba: &ImportConfig, path: &Path) -> Result<bool> {
None => was_changed = true,
Some(row) => {
let meta = txn.get(row)?.unwrap();
- let prev_mtime = meta.as_object().get(IM_MTIME).unwrap_or_default();
+ let prev_mtime = meta.get(IM_MTIME).unwrap_or_default();
was_changed = mtime > prev_mtime
}
}
@@ -481,14 +481,11 @@ fn update_mtime(dba: &ImportConfig, path: &Path) -> Result<()> {
..Default::default()
})? {
Some(row) => row,
- None => txn.insert(ObjectBuffer::new(&mut [(
- IM_PATH.0,
- &path.to_string_lossy().as_bytes(),
- )]))?,
+ None => txn.insert(OBB::new().with(IM_PATH, &path.to_string_lossy()).finish())?,
};
let mut ob = txn.get(row)?.unwrap();
- ob = ob.as_object().insert(IM_MTIME, mtime);
+ ob = ob.insert(IM_MTIME, mtime);
txn.update(row, ob)?;
Ok(())
diff --git a/import/src/plugins/acoustid.rs b/import/src/plugins/acoustid.rs
index e0d1a96..caeeea5 100644
--- a/import/src/plugins/acoustid.rs
+++ b/import/src/plugins/acoustid.rs
@@ -192,10 +192,12 @@ impl ImportPlugin for AcoustID {
};
ct.ic.update_node(node, |node| {
- node.as_object().update(NO_IDENTIFIERS, |ids| {
- ids.insert_s(ct.is, IDENT_ACOUST_ID_TRACK, &atid)
- .as_object()
- .insert_s(ct.is, IDENT_MUSICBRAINZ_RECORDING, &mbid)
+ node.update(NO_IDENTIFIERS, |ids| {
+ ids.insert_s(ct.is, IDENT_ACOUST_ID_TRACK, &atid).insert_s(
+ ct.is,
+ IDENT_MUSICBRAINZ_RECORDING,
+ &mbid,
+ )
})
})?;
diff --git a/import/src/plugins/infojson.rs b/import/src/plugins/infojson.rs
index 8a3ba5d..dfa5279 100644
--- a/import/src/plugins/infojson.rs
+++ b/import/src/plugins/infojson.rs
@@ -181,23 +181,23 @@ impl ImportPlugin for Infojson {
ct.ic.db.transaction(&mut |txn| {
let mut node = txn.get(parent)?.unwrap();
- node = node.as_object().insert_s(ct.is, NO_KIND, KIND_CHANNEL);
- node = node.as_object().insert_s(ct.is, NO_TITLE, title);
+ node = node.insert_s(ct.is, NO_KIND, KIND_CHANNEL);
+ node = node.insert_s(ct.is, NO_TITLE, title);
if let Some(cid) = &data.channel_id {
- node = node.as_object().update(NO_IDENTIFIERS, |ids| {
+ node = node.update(NO_IDENTIFIERS, |ids| {
ids.insert_s(ct.is, IDENT_YOUTUBE_CHANNEL, &cid)
});
}
if let Some(uid) = &data.uploader_id {
- node = node.as_object().update(NO_IDENTIFIERS, |ids| {
+ node = node.update(NO_IDENTIFIERS, |ids| {
ids.insert_s(ct.is, IDENT_YOUTUBE_CHANNEL_HANDLE, &uid)
})
}
if let Some(desc) = &data.description {
- node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &desc);
+ node = node.insert_s(ct.is, NO_DESCRIPTION, &desc);
}
if let Some(followers) = data.channel_follower_count {
- node = node.as_object().update(NO_RATINGS, |rat| {
+ node = node.update(NO_RATINGS, |rat| {
rat.insert_s(ct.is, RTYP_YOUTUBE_SUBSCRIBERS, followers as f64)
});
}
@@ -242,45 +242,41 @@ impl ImportPlugin for Infojson {
ct.ic.db.transaction(&mut |txn| {
let mut node = txn.get(row)?.unwrap();
- node = node.as_object().insert_s(ct.is, NO_KIND, kind);
- node = node.as_object().insert_s(ct.is, NO_TITLE, &infojson.title);
+ node = node.insert_s(ct.is, NO_KIND, kind);
+ node = node.insert_s(ct.is, NO_TITLE, &infojson.title);
if let Some(title) = &infojson.alt_title
&& title != &infojson.title
- && !node.as_object().has(NO_SUBTITLE.0)
+ && !node.has(NO_SUBTITLE.0)
{
- node = node.as_object().insert_s(ct.is, NO_SUBTITLE, &title);
+ node = node.insert_s(ct.is, NO_SUBTITLE, &title);
}
if let Some(up) = &infojson.uploader
- && !node.as_object().has(NO_SUBTITLE.0)
+ && !node.has(NO_SUBTITLE.0)
{
- node = node
- .as_object()
- .insert_s(ct.is, NO_SUBTITLE, &clean_uploader_name(&up));
+ node = node.insert_s(ct.is, NO_SUBTITLE, &clean_uploader_name(&up));
}
if let Some(desc) = &infojson.description {
- node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &desc);
+ node = node.insert_s(ct.is, NO_DESCRIPTION, &desc);
}
if let Some(tag) = infojson.tags.clone() {
- node = node
- .as_object()
- .extend(NO_TAG, tag.iter().map(String::as_str));
+ node = node.extend(NO_TAG, tag.iter().map(String::as_str));
}
if let Some(rd) = release_date {
- node = node.as_object().insert_s(ct.is, NO_RELEASEDATE, rd);
+ node = node.insert_s(ct.is, NO_RELEASEDATE, rd);
}
match infojson.extractor.as_str() {
"youtube" => {
- node = node.as_object().update(NO_IDENTIFIERS, |rat| {
+ node = node.update(NO_IDENTIFIERS, |rat| {
rat.insert_s(ct.is, IDENT_YOUTUBE_VIDEO, &infojson.id)
});
- node = node.as_object().update(NO_RATINGS, |rat| {
+ node = node.update(NO_RATINGS, |rat| {
rat.insert_s(
ct.is,
RTYP_YOUTUBE_VIEWS,
infojson.view_count.unwrap_or_default() as f64,
)
});
- node = node.as_object().update(NO_RATINGS, |rat| {
+ node = node.update(NO_RATINGS, |rat| {
rat.insert_s(
ct.is,
RTYP_YOUTUBE_LIKES,
@@ -289,7 +285,7 @@ impl ImportPlugin for Infojson {
});
}
"Bandcamp" => {
- node = node.as_object().update(NO_IDENTIFIERS, |rat| {
+ node = node.update(NO_IDENTIFIERS, |rat| {
rat.insert_s(ct.is, IDENT_BANDCAMP, &infojson.id)
});
}
diff --git a/import/src/plugins/media_info.rs b/import/src/plugins/media_info.rs
index f21386e..5daa3ba 100644
--- a/import/src/plugins/media_info.rs
+++ b/import/src/plugins/media_info.rs
@@ -9,10 +9,7 @@ use crate::{
source_rank::ObjectImportSourceExt,
};
use anyhow::Result;
-use jellycommon::{
- jellyobject::{Object, ObjectBuffer},
- *,
-};
+use jellycommon::{jellyobject::OBB, *};
use jellydb::RowNum;
use jellyremuxer::matroska::{Segment, TrackType};
use std::path::Path;
@@ -32,84 +29,83 @@ impl ImportPlugin for MediaInfo {
ct.ic.db.transaction(&mut |txn| {
let mut node = txn.get(row)?.unwrap();
if let Some(tracks) = &seg.tracks {
- node = node.as_object().extend_object(
+ node = node.extend_object(
NO_TRACK,
TR_SOURCE.0,
- tracks.entries.iter().map(|t| {
- let mut track = ObjectBuffer::empty();
- track = track.as_object().insert(TR_CODEC, &t.codec_id);
- track = track.as_object().insert(TR_LANGUAGE, t.language.as_str());
- if let Some(name) = &t.name {
- track = track.as_object().insert(TR_NAME, name);
- }
- track = track.as_object().insert(
- TR_KIND,
- match t.track_type {
- TrackType::Audio => TRKIND_AUDIO,
- TrackType::Video => TRKIND_VIDEO,
- TrackType::Subtitle => TRKIND_TEXT,
- _ => TRKIND_UNKNOWN,
- },
- );
- if let Some(v) = &t.video {
- track = track
- .as_object()
- .insert(TR_PIXEL_WIDTH, v.pixel_width as u32);
- track = track
- .as_object()
- .insert(TR_PIXEL_HEIGHT, v.pixel_height as u32);
- if let Some(fr) = v.frame_rate {
- track = track.as_object().insert(TR_RATE, fr);
+ &tracks
+ .entries
+ .iter()
+ .map(|t| {
+ let mut track = OBB::new();
+ track.push(TR_CODEC, &t.codec_id);
+ track.push(TR_LANGUAGE, &t.language);
+ if let Some(name) = &t.name {
+ track.push(TR_NAME, name);
+ }
+ track.push(
+ TR_KIND,
+ match t.track_type {
+ TrackType::Audio => TRKIND_AUDIO,
+ TrackType::Video => TRKIND_VIDEO,
+ TrackType::Subtitle => TRKIND_TEXT,
+ _ => TRKIND_UNKNOWN,
+ },
+ );
+ if let Some(v) = &t.video {
+ track.push(TR_PIXEL_WIDTH, v.pixel_width as u32);
+ track.push(TR_PIXEL_HEIGHT, v.pixel_height as u32);
+ if let Some(fr) = v.frame_rate {
+ track.push(TR_RATE, fr);
+ }
}
- }
- if let Some(a) = &t.audio {
- track = track.as_object().insert(TR_CHANNELS, a.channels as u32);
- track = track.as_object().insert(TR_RATE, a.sampling_frequency);
- if let Some(d) = a.bit_depth {
- track = track.as_object().insert(TR_BIT_DEPTH, d as u32);
+ if let Some(a) = &t.audio {
+ track.push(TR_CHANNELS, a.channels as u32);
+ track.push(TR_RATE, a.sampling_frequency);
+ if let Some(d) = a.bit_depth {
+ track.push(TR_BIT_DEPTH, d as u32);
+ }
}
- }
- let source = Object::EMPTY
- .insert(TRSOURCE_LOCAL_PATH, &path.to_string_lossy())
- .as_object()
- .insert(TRSOURCE_LOCAL_TRACKNUM, t.track_number);
- track = track.as_object().insert(TR_SOURCE, source.as_object());
+ let source = OBB::new()
+ .with(TRSOURCE_LOCAL_PATH, &path.to_string_lossy())
+ .with(TRSOURCE_LOCAL_TRACKNUM, t.track_number)
+ .finish();
+ track.push(TR_SOURCE, &source);
- track
- }),
+ track.finish()
+ })
+ .collect::<Vec<_>>(),
);
}
if let Some(chapters) = &seg.chapters {
- node = node.as_object().extend_object(
+ node = node.extend_object(
NO_CHAPTER,
CH_NAME.0,
- chapters
+ &chapters
.edition_entries
.iter()
.flat_map(|e| &e.chapter_atoms)
.map(|cha| {
- let mut chapter = ObjectBuffer::empty();
- chapter = chapter
- .as_object()
- .insert(CH_START, cha.time_start as f64 * 1e-9);
+ let mut chapter = OBB::new();
+ chapter.push(CH_START, cha.time_start as f64 * 1e-9);
if let Some(end) = cha.time_end {
- chapter = chapter.as_object().insert(CH_END, end as f64 * 1e-9);
+ chapter.push(CH_END, end as f64 * 1e-9);
}
if let Some(display) = cha.displays.iter().next() {
- chapter = chapter.as_object().insert(CH_NAME, &display.string);
+ chapter.push(CH_NAME, &display.string);
}
- chapter
- }),
+ chapter.finish()
+ })
+ .collect::<Vec<_>>(),
);
}
let runtime = fix_invalid_runtime(
seg.info.duration.unwrap_or_default() * seg.info.timestamp_scale as f64 * 1e-9,
);
- node = node.as_object().insert_s(ct.is, NO_DURATION, runtime);
- node = node.as_object().insert_s(ct.is, NO_STORAGE_SIZE, size);
+ node = node.insert_s(ct.is, NO_DURATION, runtime);
+ node = node.insert_s(ct.is, NO_STORAGE_SIZE, size);
txn.update(row, node)?;
diff --git a/import/src/plugins/misc.rs b/import/src/plugins/misc.rs
index 2c56dae..db9a8ab 100644
--- a/import/src/plugins/misc.rs
+++ b/import/src/plugins/misc.rs
@@ -49,9 +49,7 @@ impl ImportPlugin for ImageFiles {
)?;
ct.ic.db.transaction(&mut |txn| {
let mut node = txn.get(row)?.unwrap();
- node = node
- .as_object()
- .update(NO_PICTURES, |picts| picts.insert_s(ct.is, slot, &asset));
+ node = node.update(NO_PICTURES, |picts| picts.insert_s(ct.is, slot, &asset));
txn.update(row, node)?;
Ok(())
})?;
@@ -84,7 +82,7 @@ impl ImportPlugin for ImageAttachments {
};
ct.ic.update_node(row, |node| {
- node.as_object().update(NO_PICTURES, |picts| {
+ node.update(NO_PICTURES, |picts| {
picts.insert_s(ct.is, PICT_COVER, &cover)
})
})?;
@@ -105,13 +103,12 @@ impl ImportPlugin for General {
fn instruction(&self, ct: &PluginContext, node: RowNum, line: &str) -> Result<()> {
if line == "hidden" {
ct.ic.update_node(node, |node| {
- node.as_object().insert_s(ct.is, NO_VISIBILITY, VISI_HIDDEN)
+ node.insert_s(ct.is, NO_VISIBILITY, VISI_HIDDEN)
})?;
}
if line == "reduced" {
ct.ic.update_node(node, |node| {
- node.as_object()
- .insert_s(ct.is, NO_VISIBILITY, VISI_REDUCED)
+ node.insert_s(ct.is, NO_VISIBILITY, VISI_REDUCED)
})?;
}
if let Some(kind) = line.strip_prefix("kind-").or(line.strip_prefix("kind=")) {
@@ -129,18 +126,16 @@ impl ImportPlugin for General {
_ => bail!("unknown node kind"),
};
ct.ic
- .update_node(node, |node| node.as_object().insert_s(ct.is, NO_KIND, kind))?;
+ .update_node(node, |node| node.insert_s(ct.is, NO_KIND, kind))?;
}
if let Some(title) = line.strip_prefix("title=") {
- ct.ic.update_node(node, |node| {
- node.as_object().insert_s(ct.is, NO_TITLE, title)
- })?;
+ ct.ic
+ .update_node(node, |node| node.insert_s(ct.is, NO_TITLE, title))?;
}
if let Some(index) = line.strip_prefix("index=") {
let index = index.parse().context("parse index")?;
- ct.ic.update_node(node, |node| {
- node.as_object().insert_s(ct.is, NO_INDEX, index)
- })?;
+ ct.ic
+ .update_node(node, |node| node.insert_s(ct.is, NO_INDEX, index))?;
}
Ok(())
}
@@ -166,7 +161,7 @@ impl ImportPlugin for Children {
continue;
}
ct.ic
- .update_node_slug(line, |n| n.as_object().extend(NO_PARENT, [parent]))?;
+ .update_node_slug(line, |n| n.extend(NO_PARENT, [parent]))?;
}
}
Ok(())
@@ -198,9 +193,9 @@ impl ImportPlugin for EpisodeIndex {
.context("parse season num")?;
ct.ic.update_node(node, |mut node| {
- node = node.as_object().insert_s(ct.is, NO_SEASON_INDEX, season);
- node = node.as_object().insert_s(ct.is, NO_INDEX, episode);
- node = node.as_object().insert_s(ct.is, NO_KIND, KIND_EPISODE);
+ node = node.insert_s(ct.is, NO_SEASON_INDEX, season);
+ node = node.insert_s(ct.is, NO_INDEX, episode);
+ node = node.insert_s(ct.is, NO_KIND, KIND_EPISODE);
node
})?;
}
diff --git a/import/src/plugins/musicbrainz.rs b/import/src/plugins/musicbrainz.rs
index ec44d11..deed38b 100644
--- a/import/src/plugins/musicbrainz.rs
+++ b/import/src/plugins/musicbrainz.rs
@@ -15,7 +15,10 @@ use crate::{
};
use anyhow::{Context, Result};
use jellycache::Cache;
-use jellycommon::{jellyobject::ObjectBuffer, *};
+use jellycommon::{
+ jellyobject::{EMPTY, OBB},
+ *,
+};
use jellydb::RowNum;
use log::info;
use reqwest::{
@@ -354,11 +357,11 @@ impl ImportPlugin for MusicBrainz {
impl MusicBrainz {
fn process_recording(&self, ct: &PluginContext, node_row: RowNum) -> Result<()> {
let data = ct.ic.get_node(node_row)?.unwrap();
- let data = data.as_object();
+ let data = data;
let Some(mbid) = data
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_MUSICBRAINZ_RECORDING)
else {
return Ok(());
@@ -369,13 +372,11 @@ impl MusicBrainz {
ct.ic.db.transaction(&mut |txn| {
let mut node = txn.get(node_row)?.unwrap();
- node = node.as_object().insert_s(ct.is, NO_TITLE, &rec.title);
+ node = node.insert_s(ct.is, NO_TITLE, &rec.title);
if let Some(a) = rec.artist_credit.first() {
- node = node
- .as_object()
- .insert_s(ct.is, NO_SUBTITLE, &a.artist.name);
+ node = node.insert_s(ct.is, NO_SUBTITLE, &a.artist.name);
}
- node = node.as_object().update(NO_IDENTIFIERS, |ids| {
+ node = node.update(NO_IDENTIFIERS, |ids| {
ids.insert_multi(
IDENT_ISRC,
&rec.isrcs.iter().map(|e| e.as_str()).collect::<Vec<_>>(),
@@ -401,21 +402,19 @@ impl MusicBrainz {
let artist_row = get_or_insert_slug(txn, &artist_slug)?;
let mut artist_node = txn.get(artist_row)?.unwrap();
- artist_node = artist_node.as_object().update(NO_IDENTIFIERS, |ids| {
+ artist_node = artist_node.update(NO_IDENTIFIERS, |ids| {
ids.insert_s(ct.is, IDENT_MUSICBRAINZ_ARTIST, &artist.id)
});
- artist_node = artist_node.as_object().insert(NO_KIND, KIND_PERSON);
+ artist_node = artist_node.insert(NO_KIND, KIND_PERSON);
txn.update(artist_row, artist_node)?;
ct.pending_nodes.lock().unwrap().insert(artist_row);
- let credit = ObjectBuffer::new(&mut [
- (CR_NODE.0, &artist_row),
- (CR_KIND.0, &cat),
- (CR_ROLE.0, &role),
- ]);
- node = node
- .as_object()
- .extend_object(NO_CREDIT, CR_NODE.0, Some(credit))
+ let credit = OBB::new()
+ .with(CR_NODE, artist_row)
+ .with(CR_KIND, cat)
+ .with(CR_ROLE, role)
+ .finish();
+ node = node.extend_object(NO_CREDIT, CR_NODE.0, &[credit])
}
txn.update(node_row, node)
@@ -426,11 +425,11 @@ impl MusicBrainz {
fn process_artist(&self, ct: &PluginContext, node_row: RowNum) -> Result<()> {
let data = ct.ic.get_node(node_row)?.unwrap();
- let data = data.as_object();
+ let data = data;
let Some(mbid) = data
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_MUSICBRAINZ_ARTIST)
else {
return Ok(());
@@ -440,7 +439,7 @@ impl MusicBrainz {
ct.ic.db.transaction(&mut |txn| {
let mut node = txn.get(node_row)?.unwrap();
- node = node.as_object().insert_s(ct.is, NO_TITLE, &artist.name);
+ node = node.insert_s(ct.is, NO_TITLE, &artist.name);
for rel in &artist.relations {
let url = rel.url.as_ref().map(|u| u.resource.clone());
@@ -449,7 +448,7 @@ impl MusicBrainz {
if let Some(url) = url
&& let Some(id) = url.strip_prefix("https://www.wikidata.org/wiki/")
{
- node = node.as_object().update(NO_IDENTIFIERS, |ids| {
+ node = node.update(NO_IDENTIFIERS, |ids| {
ids.insert_s(ct.is, IDENT_WIKIDATA, id)
})
}
@@ -458,7 +457,7 @@ impl MusicBrainz {
if let Some(url) = url
&& let Some(id) = url.strip_prefix("https://vgmdb.net/artist/")
{
- node = node.as_object().update(NO_IDENTIFIERS, |ids| {
+ node = node.update(NO_IDENTIFIERS, |ids| {
ids.insert_s(ct.is, IDENT_VGMDB_ARTIST, id)
})
}
diff --git a/import/src/plugins/omdb.rs b/import/src/plugins/omdb.rs
index 3b97a9d..5d99fcb 100644
--- a/import/src/plugins/omdb.rs
+++ b/import/src/plugins/omdb.rs
@@ -4,14 +4,9 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use std::sync::Arc;
-
use anyhow::{Context, Result, anyhow};
use jellycache::Cache;
-use jellycommon::{
- IDENT_IMDB, MSOURCE_OMDB, NO_DESCRIPTION, NO_IDENTIFIERS, NO_RATINGS, NO_TITLE, RTYP_IMDB,
- RTYP_METACRITIC, RTYP_ROTTEN_TOMATOES,
-};
+use jellycommon::{jellyobject::EMPTY, *};
use jellydb::RowNum;
use log::info;
use reqwest::{
@@ -19,6 +14,7 @@ use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
};
use serde::{Deserialize, Serialize};
+use std::sync::Arc;
use tokio::runtime::Handle;
use crate::{
@@ -124,9 +120,8 @@ impl ImportPlugin for Omdb {
}
fn process(&self, ct: &PluginContext, node: RowNum) -> Result<()> {
let data = ct.ic.get_node(node)?.unwrap();
- let data = data.as_object();
- let Some(id) = data.get(NO_IDENTIFIERS).unwrap_or_default().get(IDENT_IMDB) else {
+ let Some(id) = data.get(NO_IDENTIFIERS).unwrap_or(EMPTY).get(IDENT_IMDB) else {
return Ok(());
};
@@ -154,10 +149,8 @@ impl ImportPlugin for Omdb {
.transpose()?;
ct.ic.update_node(node, |mut node| {
- node = node.as_object().insert_s(ct.is, NO_TITLE, &entry.title);
- node = node
- .as_object()
- .insert_s(ct.is, NO_DESCRIPTION, &entry.plot);
+ node = node.insert_s(ct.is, NO_TITLE, &entry.title);
+ node = node.insert_s(ct.is, NO_DESCRIPTION, &entry.plot);
for (typ, val) in [
(RTYP_METACRITIC, metascore),
@@ -165,9 +158,7 @@ impl ImportPlugin for Omdb {
(RTYP_ROTTEN_TOMATOES, rotten_tomatoes),
] {
if let Some(x) = val {
- node = node
- .as_object()
- .update(NO_RATINGS, |rts| rts.insert_s(ct.is, typ, x));
+ node = node.update(NO_RATINGS, |rts| rts.insert_s(ct.is, typ, x));
}
}
node
diff --git a/import/src/plugins/tags.rs b/import/src/plugins/tags.rs
index c27ea70..901bcc6 100644
--- a/import/src/plugins/tags.rs
+++ b/import/src/plugins/tags.rs
@@ -39,16 +39,16 @@ impl ImportPlugin for Tags {
ct.ic.update_node(node, |mut node| {
if let Some(title) = &seg.info.title {
- node = node.as_object().insert_s(ct.is, NO_TITLE, title);
+ node = node.insert_s(ct.is, NO_TITLE, title);
}
for (key, value) in &tags {
match key.as_str() {
"DESCRIPTION" | "SYNOPSIS" => {
- node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &value)
+ node = node.insert_s(ct.is, NO_DESCRIPTION, &value)
}
- "COMMENT" => node = node.as_object().insert(NO_TAGLINE, &value),
+ "COMMENT" => node = node.insert(NO_TAGLINE, &value),
"CONTENT_TYPE" => {
- node = node.as_object().insert_s(
+ node = node.insert_s(
ct.is,
NO_KIND,
match value.to_lowercase().trim() {
@@ -69,7 +69,7 @@ impl ImportPlugin for Tags {
"BARCODE" => IDENT_BARCODE,
_ => continue,
};
- node = node.as_object().update(NO_IDENTIFIERS, |idents| {
+ node = node.update(NO_IDENTIFIERS, |idents| {
idents.insert_s(ct.is, idty, &value)
});
}
diff --git a/import/src/plugins/tmdb.rs b/import/src/plugins/tmdb.rs
index 39f8115..832e240 100644
--- a/import/src/plugins/tmdb.rs
+++ b/import/src/plugins/tmdb.rs
@@ -11,7 +11,7 @@ use crate::{
use anyhow::{Context, Result, anyhow, bail};
use chrono::{Utc, format::Parsed};
use jellycache::{Cache, EscapeKey, HashKey};
-use jellycommon::*;
+use jellycommon::{jellyobject::EMPTY, *};
use jellydb::RowNum;
use log::info;
use reqwest::{
@@ -196,17 +196,16 @@ impl ImportPlugin for Tmdb {
impl Tmdb {
fn process_primary(&self, ct: &PluginContext, node: RowNum) -> Result<()> {
let data = ct.ic.get_node(node)?.unwrap();
- let data = data.as_object();
let (tmdb_kind, tmdb_id): (_, u64) = if let Some(id) = data
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_TMDB_SERIES)
{
(TmdbKind::Tv, id.parse()?)
} else if let Some(id) = data
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_TMDB_MOVIE)
{
(TmdbKind::Movie, id.parse()?)
@@ -237,31 +236,25 @@ impl Tmdb {
ct.ic.update_node(node, |mut node| {
if let Some(title) = &details.title {
- node = node.as_object().insert_s(ct.is, NO_TITLE, &title);
+ node = node.insert_s(ct.is, NO_TITLE, &title);
}
if let Some(tagline) = &details.tagline {
- node = node.as_object().insert_s(ct.is, NO_TAGLINE, &tagline);
+ node = node.insert_s(ct.is, NO_TAGLINE, &tagline);
}
- node = node
- .as_object()
- .insert_s(ct.is, NO_DESCRIPTION, &details.overview);
- node = node.as_object().update(NO_RATINGS, |rat| {
+ node = node.insert_s(ct.is, NO_DESCRIPTION, &details.overview);
+ node = node.update(NO_RATINGS, |rat| {
rat.insert_s(ct.is, RTYP_TMDB, details.vote_average)
});
if let Some(poster) = &poster {
- node = node
- .as_object()
- .update(NO_PICTURES, |rat| rat.insert_s(ct.is, PICT_COVER, &poster));
+ node = node.update(NO_PICTURES, |rat| rat.insert_s(ct.is, PICT_COVER, &poster));
}
if let Some(backdrop) = &backdrop {
- node = node.as_object().update(NO_PICTURES, |rat| {
+ node = node.update(NO_PICTURES, |rat| {
rat.insert_s(ct.is, PICT_BACKDROP, &backdrop)
});
}
if let Some(releasedate) = release_date {
- node = node
- .as_object()
- .insert_s(ct.is, NO_RELEASEDATE, releasedate);
+ node = node.insert_s(ct.is, NO_RELEASEDATE, releasedate);
}
node
})?;
@@ -269,7 +262,7 @@ impl Tmdb {
}
fn process_episode(&self, ct: &PluginContext, node: RowNum) -> Result<()> {
let data = ct.ic.get_node(node)?.unwrap();
- let data = data.as_object();
+ let data = data;
let (Some(episode), Some(season)) = (data.get(NO_INDEX), data.get(NO_SEASON_INDEX)) else {
return Ok(());
@@ -279,9 +272,8 @@ impl Tmdb {
for parent in data.iter(NO_PARENT) {
let parent_data = txn.get(parent)?.ok_or(anyhow!("parent missing"))?;
if let Some(id) = parent_data
- .as_object()
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_TMDB_SERIES)
{
series_id = Some(id.parse::<u64>()?);
@@ -302,20 +294,16 @@ impl Tmdb {
.context("still image download")?;
let release_date = parse_release_date(&details.air_date)?;
ct.ic.update_node(node, |mut node| {
- node = node.as_object().insert_s(ct.is, NO_TITLE, &details.name);
- node = node
- .as_object()
- .insert_s(ct.is, NO_DESCRIPTION, &details.overview);
+ node = node.insert_s(ct.is, NO_TITLE, &details.name);
+ node = node.insert_s(ct.is, NO_DESCRIPTION, &details.overview);
if let Some(release_date) = release_date {
- node = node
- .as_object()
- .insert_s(ct.is, NO_RELEASEDATE, release_date)
+ node = node.insert_s(ct.is, NO_RELEASEDATE, release_date)
}
- node = node.as_object().update(NO_RATINGS, |rat| {
+ node = node.update(NO_RATINGS, |rat| {
rat.insert_s(ct.is, RTYP_TMDB, details.vote_average)
});
if let Some(cover) = &cover {
- node = node.as_object().update(NO_PICTURES, |picts| {
+ node = node.update(NO_PICTURES, |picts| {
picts.insert_s(ct.is, PICT_COVER, &cover)
});
}
@@ -325,11 +313,11 @@ impl Tmdb {
fn process_person(&self, ct: &PluginContext, node: RowNum) -> Result<()> {
let data = ct.ic.get_node(node)?.unwrap();
- let data = data.as_object();
+ let data = data;
let Some(id) = data
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_TMDB_PERSON)
else {
return Ok(());
@@ -344,8 +332,7 @@ impl Tmdb {
let image = self.image(&ct.ic.cache, &prof.file_path, ct.rt)?;
ct.ic.update_node(node, |node| {
- node.as_object()
- .update(NO_PICTURES, |pict| pict.insert_s(ct.is, PICT_COVER, &image))
+ node.update(NO_PICTURES, |pict| pict.insert_s(ct.is, PICT_COVER, &image))
})?;
Ok(())
diff --git a/import/src/plugins/trakt.rs b/import/src/plugins/trakt.rs
index c7ad53e..9c5786c 100644
--- a/import/src/plugins/trakt.rs
+++ b/import/src/plugins/trakt.rs
@@ -12,7 +12,7 @@ use crate::{
use anyhow::{Context, Result, anyhow, bail};
use jellycache::{Cache, HashKey};
use jellycommon::{
- jellyobject::{ObjectBuffer, Tag},
+ jellyobject::{EMPTY, OBB, Tag},
*,
};
use jellydb::RowNum;
@@ -423,8 +423,7 @@ impl ImportPlugin for Trakt {
_ => bail!("unknown trakt kind"),
};
ct.ic.update_node(node, |node| {
- node.as_object()
- .update(NO_IDENTIFIERS, |idents| idents.insert(ty, id))
+ node.update(NO_IDENTIFIERS, |idents| idents.insert(ty, id))
})?;
}
Ok(())
@@ -439,16 +438,15 @@ impl ImportPlugin for Trakt {
impl Trakt {
fn process_primary(&self, ct: &PluginContext, node_row: RowNum) -> Result<()> {
let data = ct.ic.get_node(node_row)?.unwrap();
- let data = data.as_object();
let (trakt_kind, trakt_id): (_, u64) = if let Some(id) = data
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_TRAKT_SHOW)
{
(TraktKind::Show, id.parse()?)
} else if let Some(id) = data
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_TRAKT_MOVIE)
{
(TraktKind::Movie, id.parse()?)
@@ -472,23 +470,21 @@ impl Trakt {
ct.ic.db.transaction(&mut |txn| {
let mut node = txn.get(node_row)?.unwrap();
- node = node
- .as_object()
- .insert_s(ct.is, NO_KIND, trakt_kind.as_node_kind());
- node = node.as_object().insert_s(ct.is, NO_TITLE, &details.title);
+ node = node.insert_s(ct.is, NO_KIND, trakt_kind.as_node_kind());
+ node = node.insert_s(ct.is, NO_TITLE, &details.title);
if let Some(overview) = &details.overview {
- node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &overview);
+ node = node.insert_s(ct.is, NO_DESCRIPTION, &overview);
}
if let Some(tagline) = &details.tagline {
- node = node.as_object().insert_s(ct.is, NO_TAGLINE, &tagline);
+ node = node.insert_s(ct.is, NO_TAGLINE, &tagline);
}
if let Some(x) = details.ids.imdb.clone() {
- node = node.as_object().update(NO_IDENTIFIERS, |idents| {
+ node = node.update(NO_IDENTIFIERS, |idents| {
idents.insert_s(ct.is, IDENT_IMDB, &x)
});
}
if let Some(x) = details.ids.tvdb.clone() {
- node = node.as_object().update(NO_IDENTIFIERS, |idents| {
+ node = node.update(NO_IDENTIFIERS, |idents| {
idents.insert_s(ct.is, IDENT_TVDB, &x.to_string())
});
}
@@ -498,15 +494,11 @@ impl Trakt {
TraktKind::Show => Some(IDENT_TMDB_SERIES),
_ => None,
} {
- node = node
- .as_object()
- .update(NO_IDENTIFIERS, |idents| idents.insert(key, &x.to_string()));
+ node = node.update(NO_IDENTIFIERS, |idents| idents.insert(key, &x.to_string()));
};
}
if let Some(rating) = details.rating {
- node = node
- .as_object()
- .update(NO_RATINGS, |idents| idents.insert(RTYP_TRAKT, rating));
+ node = node.update(NO_RATINGS, |idents| idents.insert(RTYP_TRAKT, rating));
}
let mut credits = Vec::new();
@@ -524,39 +516,35 @@ impl Trakt {
let row = get_or_insert_slug(txn, &slug)?;
let mut c = txn.get(row)?.unwrap();
- c = c.as_object().insert_s(ct.is, NO_KIND, KIND_PERSON);
- c = c.as_object().insert_s(ct.is, NO_VISIBILITY, VISI_VISIBLE);
- c = c.as_object().insert_s(ct.is, NO_TITLE, &ap.person.name);
- c = c.as_object().update(NO_IDENTIFIERS, |ids| {
+ c = c.insert_s(ct.is, NO_KIND, KIND_PERSON);
+ c = c.insert_s(ct.is, NO_VISIBILITY, VISI_VISIBLE);
+ c = c.insert_s(ct.is, NO_TITLE, &ap.person.name);
+ c = c.update(NO_IDENTIFIERS, |ids| {
let mut ids = ids.insert(IDENT_TRAKT_PERSON, &traktid.to_string());
if let Some(tmdbid) = ap.person.ids.tmdb {
- ids = ids.as_object().insert_s(
- ct.is,
- IDENT_TMDB_PERSON,
- &tmdbid.to_string(),
- );
+ ids = ids.insert_s(ct.is, IDENT_TMDB_PERSON, &tmdbid.to_string());
}
if let Some(imdbid) = &ap.person.ids.imdb {
- ids = ids.as_object().insert_s(ct.is, IDENT_IMDB_PERSON, imdbid);
+ ids = ids.insert_s(ct.is, IDENT_IMDB_PERSON, imdbid);
}
ids
});
txn.update(row, c)?;
- credits.push(ObjectBuffer::new(&mut [
- (CR_KIND.0, crcat),
- (CR_ROLE.0, &role.as_str()),
- (CR_NODE.0, &row),
- ]));
+ credits.push(
+ OBB::new()
+ .with(CR_KIND, *crcat)
+ .with(CR_ROLE, &role)
+ .with(CR_NODE, row)
+ .finish(),
+ );
ct.pending_nodes.lock().unwrap().insert(row);
}
}
- node = node
- .as_object()
- .extend_object(NO_CREDIT, CR_NODE.0, credits);
+ node = node.extend_object(NO_CREDIT, CR_NODE.0, &credits);
txn.update(node_row, node)?;
Ok(())
@@ -565,7 +553,6 @@ impl Trakt {
}
fn process_episode(&self, ct: &PluginContext, node: RowNum) -> Result<()> {
let node_data = ct.ic.get_node(node)?.unwrap();
- let node_data = node_data.as_object();
let (Some(episode), Some(season)) =
(node_data.get(NO_INDEX), node_data.get(NO_SEASON_INDEX))
@@ -577,9 +564,8 @@ impl Trakt {
for parent in node_data.iter(NO_PARENT) {
let parent_data = txn.get(parent)?.ok_or(anyhow!("parent missing"))?;
if let Some(id) = parent_data
- .as_object()
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_TRAKT_SHOW)
{
show_id = Some(id.parse::<u64>()?);
@@ -597,16 +583,14 @@ impl Trakt {
let episodes = self.show_season_episodes(&ct.ic.cache, show_id, season, ct.rt)?;
if let Some(episode) = episodes.get(episode.saturating_sub(1) as usize) {
ct.ic.update_node(node, |mut node| {
- node = node.as_object().insert_s(ct.is, NO_KIND, KIND_EPISODE);
- node = node.as_object().insert_s(ct.is, NO_INDEX, episode.number);
- node = node.as_object().insert_s(ct.is, NO_TITLE, &episode.title);
+ node = node.insert_s(ct.is, NO_KIND, KIND_EPISODE);
+ node = node.insert_s(ct.is, NO_INDEX, episode.number);
+ node = node.insert_s(ct.is, NO_TITLE, &episode.title);
if let Some(overview) = &episode.overview {
- node = node.as_object().insert_s(ct.is, NO_DESCRIPTION, &overview);
+ node = node.insert_s(ct.is, NO_DESCRIPTION, &overview);
}
if let Some(r) = episode.rating {
- node = node
- .as_object()
- .update(NO_RATINGS, |rats| rats.insert_s(ct.is, RTYP_TRAKT, r));
+ node = node.update(NO_RATINGS, |rats| rats.insert_s(ct.is, RTYP_TRAKT, r));
}
node
})?;
diff --git a/import/src/plugins/vgmdb.rs b/import/src/plugins/vgmdb.rs
index 4e1b273..f93e84c 100644
--- a/import/src/plugins/vgmdb.rs
+++ b/import/src/plugins/vgmdb.rs
@@ -11,7 +11,7 @@ use crate::{
};
use anyhow::{Context, Result};
use jellycache::{Cache, HashKey};
-use jellycommon::*;
+use jellycommon::{jellyobject::EMPTY, *};
use jellydb::RowNum;
use log::info;
use regex::Regex;
@@ -149,11 +149,10 @@ impl ImportPlugin for Vgmdb {
fn process(&self, ct: &PluginContext, node: RowNum) -> Result<()> {
let data = ct.ic.get_node(node)?.unwrap();
- let data = data.as_object();
let Some(id) = data
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_VGMDB_ARTIST)
else {
return Ok(());
@@ -165,8 +164,7 @@ impl ImportPlugin for Vgmdb {
};
ct.ic.update_node(node, |node| {
- node.as_object()
- .update(NO_PICTURES, |pics| pics.insert_s(ct.is, PICT_COVER, &image))
+ node.update(NO_PICTURES, |pics| pics.insert_s(ct.is, PICT_COVER, &image))
})?;
Ok(())
diff --git a/import/src/plugins/wikidata.rs b/import/src/plugins/wikidata.rs
index b92fdbb..5b79592 100644
--- a/import/src/plugins/wikidata.rs
+++ b/import/src/plugins/wikidata.rs
@@ -11,7 +11,7 @@ use crate::{
};
use anyhow::{Context, Result, bail};
use jellycache::{Cache, EscapeKey};
-use jellycommon::*;
+use jellycommon::{jellyobject::EMPTY, *};
use jellydb::RowNum;
use log::info;
use reqwest::{
@@ -203,11 +203,10 @@ impl ImportPlugin for Wikidata {
}
fn process(&self, ct: &PluginContext, node: RowNum) -> Result<()> {
let data = ct.ic.get_node(node)?.unwrap();
- let data = data.as_object();
let Some(id) = data
.get(NO_IDENTIFIERS)
- .unwrap_or_default()
+ .unwrap_or(EMPTY)
.get(IDENT_WIKIDATA)
else {
return Ok(());
@@ -222,8 +221,7 @@ impl ImportPlugin for Wikidata {
.image_by_filename(&ct.ic.cache, filename, ct.rt)?;
ct.ic.update_node(node, |node| {
- node.as_object()
- .update(NO_PICTURES, |pics| pics.insert_s(ct.is, PICT_COVER, &image))
+ node.update(NO_PICTURES, |pics| pics.insert_s(ct.is, PICT_COVER, &image))
})?;
Ok(())
diff --git a/import/src/source_rank.rs b/import/src/source_rank.rs
index 28ab4f7..3058df4 100644
--- a/import/src/source_rank.rs
+++ b/import/src/source_rank.rs
@@ -5,10 +5,9 @@
*/
use jellycommon::{
- jellyobject::{Object, ObjectBuffer, Tag, TypedTag, ValueStore},
+ jellyobject::{EMPTY, Object, Tag, TypedTag, ValueMarker},
*,
};
-use std::marker::PhantomData;
pub struct SourceRanks {
list: Vec<Tag>,
@@ -21,27 +20,30 @@ pub struct ImportSource<'a> {
}
pub trait ObjectImportSourceExt {
- fn insert_s<T: ValueStore>(&self, is: ImportSource, key: TypedTag<T>, value: T)
- -> ObjectBuffer;
+ fn insert_s<'a, T: ValueMarker<'a> + ?Sized>(
+ &'a self,
+ is: ImportSource,
+ key: TypedTag<T>,
+ value: T::Inner,
+ ) -> Box<Object>;
}
-impl<'a> ObjectImportSourceExt for Object<'a> {
- fn insert_s<T: ValueStore>(
- &self,
+impl ObjectImportSourceExt for Object {
+ fn insert_s<'a, T: ValueMarker<'a> + ?Sized>(
+ &'a self,
is: ImportSource,
key: TypedTag<T>,
- value: T,
- ) -> ObjectBuffer {
- let ms = self.get(NO_METASOURCE).unwrap_or_default();
- let ms_key = TypedTag::<Tag>(key.0, PhantomData);
+ value: T::Inner,
+ ) -> Box<Object> {
+ let ms = self.get(NO_METASOURCE).unwrap_or(EMPTY);
+ let ms_key = TypedTag::<Tag>::new(key.0);
if let Some(current_source) = ms.get(ms_key) {
if !is.ranks.compare(key.0, current_source, is.tag) {
- return self.dump();
+ return self.to_owned();
}
}
self.insert(key, value)
- .as_object()
.update(NO_METASOURCE, |ms| ms.insert(ms_key, is.tag))
}
}
diff --git a/server/src/auth.rs b/server/src/auth.rs
index d973dfc..0174e13 100644
--- a/server/src/auth.rs
+++ b/server/src/auth.rs
@@ -7,13 +7,10 @@
use crate::State;
use anyhow::{Result, anyhow, bail};
use argon2::{Argon2, PasswordHasher, password_hash::Salt};
-use jellycommon::{
- jellyobject::{ObjectBuffer, Path},
- *,
-};
+use jellycommon::{jellyobject::Path, *};
use jellydb::{Filter, Query};
-pub fn token_to_user(state: &State, token: &str) -> Result<ObjectBuffer> {
+pub fn token_to_user(state: &State, token: &str) -> Result<Box<User>> {
let user_row = token::validate(&state.session_key, token)?;
let mut user = None;
@@ -49,7 +46,7 @@ pub fn login(
let (Some(user_row), Some(user)) = (user_row, user) else {
bail!("unknown user");
};
- let Some(correct_pw) = user.as_object().get(USER_PASSWORD) else {
+ let Some(correct_pw) = user.get(USER_PASSWORD) else {
bail!("password login is disabled")
};
if password != correct_pw {
@@ -62,7 +59,7 @@ pub fn login(
user_row,
expire.unwrap_or(60 * 60 * 24 * 30),
),
- user.as_object().has(USER_PASSWORD_REQUIRE_CHANGE.0),
+ user.has(USER_PASSWORD_REQUIRE_CHANGE.0),
))
}
diff --git a/server/src/compat/youtube.rs b/server/src/compat/youtube.rs
index 4ba3dc8..917adc0 100644
--- a/server/src/compat/youtube.rs
+++ b/server/src/compat/youtube.rs
@@ -30,10 +30,7 @@ pub fn r_youtube_watch(ri: RequestInfo<'_>, v: &str) -> MyResult<Redirect> {
Ok(())
})?;
let node = res.ok_or(anyhow!("video not found"))?;
- let slug = node
- .as_object()
- .get(NO_SLUG)
- .ok_or(anyhow!("node has no slug"))?;
+ let slug = node.get(NO_SLUG).ok_or(anyhow!("node has no slug"))?;
Ok(Redirect::found(u_node_id(slug)))
}
diff --git a/server/src/logic/stream.rs b/server/src/logic/stream.rs
index e332811..38d2b7b 100644
--- a/server/src/logic/stream.rs
+++ b/server/src/logic/stream.rs
@@ -65,7 +65,7 @@ pub async fn r_stream(
Ok(())
})?;
- let Some(node) = node.as_ref().map(|n| n.as_object()) else {
+ let Some(node) = node else {
Err(anyhow!("node not found"))?
};
diff --git a/server/src/main.rs b/server/src/main.rs
index 8209879..d6f1e1e 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -15,7 +15,7 @@ use anyhow::{Result, anyhow};
use jellycache::Cache;
use jellycommon::{
USER_ADMIN, USER_LOGIN, USER_PASSWORD,
- jellyobject::{ObjectBuffer, Path},
+ jellyobject::{OBB, Path},
};
use jellydb::{Database, Filter, Query};
use log::{error, info};
@@ -105,11 +105,13 @@ fn create_admin_user(state: &State) -> Result<()> {
if admin_row.is_none() {
info!("Creating new admin user");
let pwhash = hash_password("admin", &state.config.admin_password);
- txn.insert(ObjectBuffer::new(&mut [
- (USER_LOGIN.0, &"admin"),
- (USER_PASSWORD.0, &pwhash.as_slice()),
- (USER_ADMIN.0, &()),
- ]))?;
+ txn.insert(
+ OBB::new()
+ .with(USER_LOGIN, "admin")
+ .with(USER_PASSWORD, &pwhash)
+ .with(USER_ADMIN, ())
+ .finish(),
+ )?;
}
Ok(())
diff --git a/server/src/request_info.rs b/server/src/request_info.rs
index 442c7fb..0adfe96 100644
--- a/server/src/request_info.rs
+++ b/server/src/request_info.rs
@@ -10,10 +10,7 @@ use crate::{
ui::error::{MyError, MyResult},
};
use anyhow::anyhow;
-use jellycommon::{
- USER_ADMIN,
- jellyobject::{Object, ObjectBuffer},
-};
+use jellycommon::{USER_ADMIN, User, jellyobject::Object};
use jellyui::{Page, RenderInfo, Scaffold};
use rocket::{
Request, async_trait,
@@ -27,7 +24,7 @@ pub struct RequestInfo<'a> {
pub lang: &'a str,
pub accept: Accept,
pub debug: &'a str,
- pub user: Option<ObjectBuffer>,
+ pub user: Option<Box<User>>,
pub state: Arc<State>,
pub flash: Option<FlashMessage<'a>>,
}
@@ -59,13 +56,12 @@ impl<'a> RequestInfo<'a> {
flash: FlashMessage::from_request(request).await.succeeded(),
})
}
- pub fn require_user(&'a self) -> MyResult<Object<'a>> {
+ pub fn require_user(&'a self) -> MyResult<&'a Object> {
self.user
- .as_ref()
- .map(|u| u.as_object())
+ .as_deref()
.ok_or(MyError(anyhow!("user required")))
}
- pub fn require_admin(&'a self) -> MyResult<Object<'a>> {
+ pub fn require_admin(&'a self) -> MyResult<&'a Object> {
let user = self.require_user()?;
if !user.has(USER_ADMIN.0) {
Err(anyhow!("admin required"))?
@@ -76,7 +72,7 @@ impl<'a> RequestInfo<'a> {
RenderInfo {
lang: self.lang,
status_message: None,
- user: self.user.as_ref().map(|u| u.as_object()),
+ user: self.user.as_deref(),
config: &self.state.config.ui,
message: self.flash.as_ref().map(|f| (f.kind(), f.message())),
}
@@ -129,7 +125,7 @@ fn accept_language<'a>(request: &'a Request<'_>) -> &'a str {
.unwrap_or("en")
}
-fn user_from_request(state: &State, req: &Request<'_>) -> Result<Option<ObjectBuffer>, MyError> {
+fn user_from_request(state: &State, req: &Request<'_>) -> Result<Option<Box<User>>, MyError> {
let Some(token) = req
.query_value("session")
.map(|e| e.unwrap())
diff --git a/server/src/ui/account/mod.rs b/server/src/ui/account/mod.rs
index b106fc7..9e5fa39 100644
--- a/server/src/ui/account/mod.rs
+++ b/server/src/ui/account/mod.rs
@@ -82,10 +82,10 @@ pub fn r_account_login_post(
})?;
if let Some(ur) = user_row {
let mut user = txn.get(ur)?.unwrap();
- user = user.as_object().remove(USER_PASSWORD_REQUIRE_CHANGE);
- user = user.as_object().insert(USER_PASSWORD, &password_hash);
+ user = user.remove(USER_PASSWORD_REQUIRE_CHANGE);
+ user = user.insert(USER_PASSWORD, &password_hash);
if let Some(name) = &form.display_name {
- user = user.as_object().insert(USER_NAME, &name);
+ user = user.insert(USER_NAME, &name);
}
txn.update(ur, user)?;
}
diff --git a/server/src/ui/account/settings.rs b/server/src/ui/account/settings.rs
index 9a98c09..2585061 100644
--- a/server/src/ui/account/settings.rs
+++ b/server/src/ui/account/settings.rs
@@ -7,7 +7,7 @@ use super::format_form_error;
use crate::{auth::hash_password, request_info::RequestInfo, ui::error::MyResult};
use anyhow::anyhow;
use jellycommon::{
- jellyobject::{Object, ObjectBuffer, Path, Tag},
+ jellyobject::{Object, Path, Tag},
routes::u_account_settings,
*,
};
@@ -104,7 +104,7 @@ pub fn r_account_settings_post(
Ok(Flash::success(Redirect::to(u_account_settings()), out))
}
-fn update_user(ri: &RequestInfo, update: impl Fn(Object) -> ObjectBuffer) -> MyResult<()> {
+fn update_user(ri: &RequestInfo, update: impl Fn(&Object) -> Box<Object>) -> MyResult<()> {
let login = ri
.require_user()?
.get(USER_LOGIN)
@@ -118,7 +118,7 @@ fn update_user(ri: &RequestInfo, update: impl Fn(Object) -> ObjectBuffer) -> MyR
.ok_or(anyhow!("user vanished"))?;
let user = txn.get(user_row)?.unwrap();
- let new_user = update(user.as_object());
+ let new_user = update(&user);
txn.update(user_row, new_user)?;
Ok(())
diff --git a/server/src/ui/admin/users.rs b/server/src/ui/admin/users.rs
index 85f241b..4b11279 100644
--- a/server/src/ui/admin/users.rs
+++ b/server/src/ui/admin/users.rs
@@ -46,7 +46,7 @@ pub fn r_admin_users(ri: RequestInfo) -> MyResult<RawHtml<String>> {
Ok(ri.respond_ui(&AdminUserList {
ri: &ri.render_info(),
- users: &users.iter().map(|u| u.as_object()).collect::<Vec<_>>(),
+ users: &users.iter().map(|u| &**u).collect::<Vec<_>>(),
}))
}
@@ -97,7 +97,7 @@ pub fn r_admin_user(ri: RequestInfo<'_>, name: &str) -> MyResult<RawHtml<String>
Ok(ri.respond_ui(&AdminUser {
ri: &ri.render_info(),
- user: user.as_object(),
+ user: &user,
}))
}
diff --git a/server/src/ui/home.rs b/server/src/ui/home.rs
index 6cb6a77..17cac83 100644
--- a/server/src/ui/home.rs
+++ b/server/src/ui/home.rs
@@ -7,11 +7,11 @@
use super::error::MyResult;
use crate::request_info::RequestInfo;
use anyhow::{Context, Result};
-use jellycommon::jellyobject::{Object, ObjectBuffer};
-use jellydb::Query;
-use jellyui::components::home::HomeRow;
+use jellycommon::{Nku, jellyobject::EMPTY};
+use jellydb::{Query, helper::DatabaseReturnExt};
+use jellyui::components::home::{Home, HomeRow};
use rocket::{get, response::content::RawHtml};
-use std::str::FromStr;
+use std::{borrow::Cow, str::FromStr};
#[get("/home")]
pub fn r_home(ri: RequestInfo<'_>) -> MyResult<RawHtml<String>> {
@@ -28,7 +28,7 @@ pub fn r_home(ri: RequestInfo<'_>) -> MyResult<RawHtml<String>> {
"home.bin.latest_music",
"FILTER (visi = visi AND kind = musi) SORT DESCENDING BY FIRST rldt",
)?);
- rows.push(home_row_highlight(
+ rows.extend(home_row_highlight(
&ri,
"home.bin.daily_random",
"FILTER (visi = visi AND kind = movi) SORT RANDOM",
@@ -38,51 +38,59 @@ pub fn r_home(ri: RequestInfo<'_>) -> MyResult<RawHtml<String>> {
"home.bin.max_rating",
"SORT DESCENDING BY FIRST rtng.imdb",
)?);
- rows.push(home_row_highlight(
+ rows.extend(home_row_highlight(
&ri,
"home.bin.daily_random",
"FILTER (visi = visi AND kind = show) SORT RANDOM",
)?);
- Ok(ri.respond_ui(rows))
+ Ok(ri.respond_ui(&Home {
+ ri: &ri.render_info(),
+ rows: &rows,
+ }))
}
-fn home_row(ri: &RequestInfo<'_>, title: &str, query: &str) -> Result<HomeRow> {
+fn home_row(
+ ri: &RequestInfo<'_>,
+ title: &'static str,
+ query: &str,
+) -> Result<(&'static str, HomeRow<'static>)> {
let q = Query::from_str(query).context("parse query")?;
- let mut res = ObjectBuffer::empty();
- ri.state.database.transaction(&mut |txn| {
+ ri.state.database.transaction_ret(|txn| {
let rows = txn.query(q.clone())?.take(16).collect::<Result<Vec<_>>>()?;
let mut nkus = Vec::new();
for (row, _) in rows {
let node = txn.get(row)?.unwrap();
- nkus.push(ObjectBuffer::new(&mut [(NKU_NODE.0, &node.as_object())]));
+ nkus.push(Nku {
+ node: Cow::Owned(node),
+ role: None,
+ userdata: Cow::Borrowed(EMPTY),
+ });
}
- let nkus = nkus.iter().map(|n| n.as_object()).collect::<Vec<_>>(); // TODO -_-
-
- res = Object::EMPTY.insert(NODELIST_DISPLAYSTYLE, NLSTYLE_INLINE);
- res = res.as_object().insert(NODELIST_TITLE, title);
- res = res.as_object().insert_multi(NODELIST_ITEM, &nkus);
-
- Ok(())
- })?;
- Ok(res)
+ Ok((title, HomeRow::Inline(nkus)))
+ })
}
-fn home_row_highlight(ri: &RequestInfo<'_>, title: &str, query: &str) -> Result<HomeRow> {
+fn home_row_highlight(
+ ri: &RequestInfo<'_>,
+ title: &'static str,
+ query: &str,
+) -> Result<Option<(&'static str, HomeRow<'static>)>> {
let q = Query::from_str(query).context("parse query")?;
- let mut res = ObjectBuffer::empty();
- ri.state.database.transaction(&mut |txn| {
+ ri.state.database.transaction_ret(|txn| {
let Some(row) = txn.query(q.clone())?.next() else {
- return Ok(());
+ return Ok(None);
};
let row = row?.0;
let node = txn.get(row)?.unwrap();
- let nku = ObjectBuffer::new(&mut [(NKU_NODE.0, &node.as_object())]);
- res = Object::EMPTY.insert(NODELIST_DISPLAYSTYLE, NLSTYLE_HIGHLIGHT);
- res = res.as_object().insert(NODELIST_TITLE, title);
- res = res.as_object().insert(NODELIST_ITEM, nku.as_object());
- Ok(())
- })?;
- Ok(res)
+ Ok(Some((
+ title,
+ HomeRow::Highlight(Nku {
+ node: Cow::Owned(node),
+ role: None,
+ userdata: Cow::Borrowed(EMPTY),
+ }),
+ )))
+ })
}
diff --git a/server/src/ui/items.rs b/server/src/ui/items.rs
index b800914..6071bcc 100644
--- a/server/src/ui/items.rs
+++ b/server/src/ui/items.rs
@@ -8,12 +8,13 @@ use crate::{request_info::RequestInfo, ui::error::MyResult};
use anyhow::anyhow;
use base64::{Engine, prelude::BASE64_URL_SAFE};
use jellycommon::{
- jellyobject::{Object, Path},
+ jellyobject::{EMPTY, Path},
*,
};
use jellydb::{Filter, Query};
use jellyui::components::items::Items;
use rocket::{get, response::content::RawHtml};
+use std::borrow::Cow;
#[get("/items?<cont>")]
pub fn r_items(ri: RequestInfo, cont: Option<&str>) -> MyResult<RawHtml<String>> {
@@ -49,8 +50,8 @@ pub fn r_items(ri: RequestInfo, cont: Option<&str>) -> MyResult<RawHtml<String>>
items: &items
.iter()
.map(|node| Nku {
- node: node.as_object(),
- userdata: Object::EMPTY,
+ node: Cow::Borrowed(&node),
+ userdata: Cow::Borrowed(EMPTY),
role: None,
})
.collect::<Vec<_>>(),
diff --git a/server/src/ui/node.rs b/server/src/ui/node.rs
index 55a1d09..ca07bac 100644
--- a/server/src/ui/node.rs
+++ b/server/src/ui/node.rs
@@ -5,186 +5,183 @@
*/
use super::error::MyResult;
-use crate::{request_info::RequestInfo, ui_responder::UiResponse};
-use anyhow::Result;
+use crate::request_info::RequestInfo;
+use anyhow::anyhow;
use jellycommon::{
- jellyobject::{OBB, Object, ObjectBuffer, ObjectBufferBuilder, Path},
+ jellyobject::{EMPTY, Path},
*,
};
-use jellydb::{Filter, MultiBehaviour, Query, Sort, SortOrder, Transaction, ValueSort};
-use rocket::get;
-use std::collections::BTreeMap;
+use jellydb::{Filter, Query};
+use jellyui::components::node_page::NodePage;
+use rocket::{get, response::content::RawHtml};
+use std::borrow::Cow;
#[get("/n/<slug>")]
-pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult<UiResponse> {
+pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult<RawHtml<String>> {
ri.require_user()?;
- let mut page = OBB::new();
+ let mut nku = None;
ri.state.database.transaction(&mut |txn| {
if let Some(row) = txn.query_single(Query {
filter: Filter::Match(Path(vec![NO_SLUG.0]), slug.into()),
..Default::default()
})? {
let n = txn.get(row)?.unwrap();
- let nku = Object::EMPTY.insert(NKU_NODE, n.as_object());
- let nku = nku.as_object();
-
- page = OBB::new();
- let title = nku
- .get(NKU_NODE)
- .unwrap_or_default()
- .get(NO_TITLE)
- .unwrap_or_default();
-
- page.push(VIEW_TITLE, title);
- page.push(VIEW_NODE_PAGE, nku);
- c_children(&mut page, txn, row, &nku)?;
- c_credits(&mut page, txn, &nku)?;
- c_credited(&mut page, txn, row)?;
+ nku = Some(Nku {
+ node: Cow::Owned(n),
+ userdata: Cow::Borrowed(EMPTY),
+ role: None,
+ });
}
Ok(())
})?;
+ let Some(nku) = nku else {
+ Err(anyhow!("no such node"))?
+ };
- Ok(ri.respond_ui(page))
+ Ok(ri.respond_ui(&NodePage {
+ ri: &ri.render_info(),
+ nku,
+ }))
}
-fn c_children(
- page: &mut ObjectBufferBuilder,
- txn: &mut dyn Transaction,
- row: u64,
- nku: &Object,
-) -> Result<()> {
- let kind = nku
- .get(NKU_NODE)
- .unwrap_or_default()
- .get(NO_KIND)
- .unwrap_or(KIND_COLLECTION);
+// fn c_children(
+// page: &mut ObjectBufferBuilder,
+// txn: &mut dyn Transaction,
+// row: u64,
+// nku: &Object,
+// ) -> Result<()> {
+// let kind = nku
+// .get(NKU_NODE)
+// .unwrap_or_default()
+// .get(NO_KIND)
+// .unwrap_or(KIND_COLLECTION);
- let (order, path) = match kind {
- KIND_CHANNEL => (SortOrder::Descending, Path(vec![NO_RELEASEDATE.0])),
- KIND_SEASON | KIND_SHOW => (SortOrder::Ascending, Path(vec![NO_INDEX.0])),
- _ => (SortOrder::Ascending, Path(vec![NO_TITLE.0])),
- };
+// let (order, path) = match kind {
+// KIND_CHANNEL => (SortOrder::Descending, Path(vec![NO_RELEASEDATE.0])),
+// KIND_SEASON | KIND_SHOW => (SortOrder::Ascending, Path(vec![NO_INDEX.0])),
+// _ => (SortOrder::Ascending, Path(vec![NO_TITLE.0])),
+// };
- let children_rows = txn
- .query(Query {
- sort: Sort::Value(ValueSort {
- multi: MultiBehaviour::First,
- offset: None,
- order,
- path,
- }),
- filter: Filter::All(vec![
- Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()),
- Filter::Match(Path(vec![NO_PARENT.0]), row.into()),
- ]),
- ..Default::default()
- })?
- .collect::<Result<Vec<_>>>()?;
+// let children_rows = txn
+// .query(Query {
+// sort: Sort::Value(ValueSort {
+// multi: MultiBehaviour::First,
+// offset: None,
+// order,
+// path,
+// }),
+// filter: Filter::All(vec![
+// Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()),
+// Filter::Match(Path(vec![NO_PARENT.0]), row.into()),
+// ]),
+// ..Default::default()
+// })?
+// .collect::<Result<Vec<_>>>()?;
- if children_rows.is_empty() {
- return Ok(());
- }
+// if children_rows.is_empty() {
+// return Ok(());
+// }
- let mut list = ObjectBufferBuilder::default();
+// let mut list = ObjectBufferBuilder::default();
- list.push(
- NODELIST_DISPLAYSTYLE,
- match kind {
- KIND_SEASON | KIND_SHOW => NLSTYLE_LIST,
- _ => NLSTYLE_GRID,
- },
- );
+// list.push(
+// NODELIST_DISPLAYSTYLE,
+// match kind {
+// KIND_SEASON | KIND_SHOW => NLSTYLE_LIST,
+// _ => NLSTYLE_GRID,
+// },
+// );
- for (row, _) in children_rows {
- list.push(
- NODELIST_ITEM,
- Object::EMPTY
- .insert(NKU_NODE, txn.get(row)?.unwrap().as_object())
- .as_object(),
- );
- }
+// for (row, _) in children_rows {
+// list.push(
+// NODELIST_ITEM,
+// Object::EMPTY
+// .insert(NKU_NODE, txn.get(row)?.unwrap().as_object())
+// .as_object(),
+// );
+// }
- page.push(VIEW_NODE_LIST, list.finish().as_object());
- Ok(())
-}
+// page.push(VIEW_NODE_LIST, list.finish().as_object());
+// Ok(())
+// }
-fn c_credits(
- page: &mut ObjectBufferBuilder,
- txn: &mut dyn Transaction,
- nku: &Object,
-) -> Result<()> {
- if !nku.get(NKU_NODE).unwrap_or_default().has(NO_CREDIT.0) {
- return Ok(());
- }
+// fn c_credits(
+// page: &mut ObjectBufferBuilder,
+// txn: &mut dyn Transaction,
+// nku: &Object,
+// ) -> Result<()> {
+// if !nku.get(NKU_NODE).unwrap_or_default().has(NO_CREDIT.0) {
+// return Ok(());
+// }
- let mut cats = BTreeMap::<_, Vec<_>>::new();
- for cred in nku.get(NKU_NODE).unwrap_or_default().iter(NO_CREDIT) {
- let mut o = ObjectBuffer::empty();
- if let Some(row) = cred.get(CR_NODE) {
- let node = txn.get(row)?.unwrap();
- o = o.as_object().insert(NKU_NODE, node.as_object());
- }
- if let Some(role) = cred.get(CR_ROLE) {
- o = o.as_object().insert(NKU_ROLE, role)
- }
- cats.entry(cred.get(CR_KIND).unwrap_or(CRCAT_CREW))
- .or_default()
- .push(o);
- }
- let mut cats = cats.into_iter().collect::<Vec<_>>();
- cats.sort_by_key(|(c, _)| match *c {
- CRCAT_CAST => 0,
- CRCAT_CREW => 1,
- _ => 100,
- });
- for (cat, elems) in cats {
- let mut list = ObjectBufferBuilder::default();
- list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_INLINE);
- list.push(NODELIST_TITLE, &format!("tag.cred.kind.{cat}"));
- for item in elems {
- list.push(NODELIST_ITEM, item.as_object());
- }
- page.push(VIEW_NODE_LIST, list.finish().as_object());
- }
+// let mut cats = BTreeMap::<_, Vec<_>>::new();
+// for cred in nku.get(NKU_NODE).unwrap_or_default().iter(NO_CREDIT) {
+// let mut o = ObjectBuffer::empty();
+// if let Some(row) = cred.get(CR_NODE) {
+// let node = txn.get(row)?.unwrap();
+// o = o.as_object().insert(NKU_NODE, node.as_object());
+// }
+// if let Some(role) = cred.get(CR_ROLE) {
+// o = o.as_object().insert(NKU_ROLE, role)
+// }
+// cats.entry(cred.get(CR_KIND).unwrap_or(CRCAT_CREW))
+// .or_default()
+// .push(o);
+// }
+// let mut cats = cats.into_iter().collect::<Vec<_>>();
+// cats.sort_by_key(|(c, _)| match *c {
+// CRCAT_CAST => 0,
+// CRCAT_CREW => 1,
+// _ => 100,
+// });
+// for (cat, elems) in cats {
+// let mut list = ObjectBufferBuilder::default();
+// list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_INLINE);
+// list.push(NODELIST_TITLE, &format!("tag.cred.kind.{cat}"));
+// for item in elems {
+// list.push(NODELIST_ITEM, item.as_object());
+// }
+// page.push(VIEW_NODE_LIST, list.finish().as_object());
+// }
- Ok(())
-}
+// Ok(())
+// }
-fn c_credited(page: &mut ObjectBufferBuilder, txn: &mut dyn Transaction, row: u64) -> Result<()> {
- let children_rows = txn
- .query(Query {
- sort: Sort::Value(ValueSort {
- multi: MultiBehaviour::First,
- offset: None,
- order: SortOrder::Ascending,
- path: Path(vec![NO_TITLE.0]),
- }),
- filter: Filter::All(vec![
- Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()),
- Filter::Match(Path(vec![NO_CREDIT.0, CR_NODE.0]), row.into()),
- ]),
- ..Default::default()
- })?
- .collect::<Result<Vec<_>>>()?;
+// fn c_credited(page: &mut ObjectBufferBuilder, txn: &mut dyn Transaction, row: u64) -> Result<()> {
+// let children_rows = txn
+// .query(Query {
+// sort: Sort::Value(ValueSort {
+// multi: MultiBehaviour::First,
+// offset: None,
+// order: SortOrder::Ascending,
+// path: Path(vec![NO_TITLE.0]),
+// }),
+// filter: Filter::All(vec![
+// Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()),
+// Filter::Match(Path(vec![NO_CREDIT.0, CR_NODE.0]), row.into()),
+// ]),
+// ..Default::default()
+// })?
+// .collect::<Result<Vec<_>>>()?;
- if children_rows.is_empty() {
- return Ok(());
- }
+// if children_rows.is_empty() {
+// return Ok(());
+// }
- let mut list = ObjectBufferBuilder::default();
- list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_GRID);
- list.push(NODELIST_TITLE, "node.credited");
+// let mut list = ObjectBufferBuilder::default();
+// list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_GRID);
+// list.push(NODELIST_TITLE, "node.credited");
- for (row, _) in children_rows {
- list.push(
- NODELIST_ITEM,
- Object::EMPTY
- .insert(NKU_NODE, txn.get(row)?.unwrap().as_object())
- .as_object(),
- );
- }
+// for (row, _) in children_rows {
+// list.push(
+// NODELIST_ITEM,
+// Object::EMPTY
+// .insert(NKU_NODE, txn.get(row)?.unwrap().as_object())
+// .as_object(),
+// );
+// }
- page.push(VIEW_NODE_LIST, list.finish().as_object());
- Ok(())
-}
+// page.push(VIEW_NODE_LIST, list.finish().as_object());
+// Ok(())
+// }
diff --git a/server/src/ui/player.rs b/server/src/ui/player.rs
index 1050abb..c6c177e 100644
--- a/server/src/ui/player.rs
+++ b/server/src/ui/player.rs
@@ -5,13 +5,15 @@
*/
use super::error::MyResult;
use crate::request_info::RequestInfo;
+use anyhow::anyhow;
use jellycommon::{
- jellyobject::{Object, ObjectBuffer, Path},
+ jellyobject::{EMPTY, Path},
*,
};
use jellydb::{Filter, Query};
use jellyui::components::node_page::Player;
use rocket::{get, response::content::RawHtml};
+use std::borrow::Cow;
// fn jellynative_url(action: &str, seek: f64, secret: &str, node: &str, session: &str) -> String {
// let protocol = if CONF.tls { "https" } else { "http" };
@@ -31,22 +33,25 @@ pub fn r_player(ri: RequestInfo<'_>, t: Option<f64>, slug: &str) -> MyResult<Raw
ri.require_user()?;
let _ = t;
- let mut node = ObjectBuffer::empty();
+ let mut node = None;
ri.state.database.transaction(&mut |txn| {
if let Some(row) = txn.query_single(Query {
filter: Filter::Match(Path(vec![NO_SLUG.0]), slug.into()),
..Default::default()
})? {
- node = txn.get(row)?.unwrap();
+ node = Some(txn.get(row)?.unwrap());
}
Ok(())
})?;
+ let Some(node) = node else {
+ Err(anyhow!("no such node"))?
+ };
Ok(ri.respond_ui(&Player {
ri: &ri.render_info(),
nku: Nku {
- node: node.as_object(),
- userdata: Object::EMPTY,
+ node: Cow::Borrowed(&node),
+ userdata: Cow::Borrowed(EMPTY),
role: None,
},
}))
diff --git a/ui/src/components/admin.rs b/ui/src/components/admin.rs
index 76dfe6a..ddf49ca 100644
--- a/ui/src/components/admin.rs
+++ b/ui/src/components/admin.rs
@@ -61,7 +61,7 @@ markup::define!(
}
}
- AdminUserList<'a>(ri: &'a RenderInfo<'a>, users: &'a [User<'a>]) {
+ AdminUserList<'a>(ri: &'a RenderInfo<'a>, users: &'a [&'a User]) {
h1 { @tr(ri.lang, "admin.users") }
form[method="POST", action=u_admin_new_user()] {
label[for="login"] { "Login: " }
@@ -73,7 +73,7 @@ markup::define!(
}}
}
- AdminUser<'a>(ri: &'a RenderInfo<'a>, user: User<'a>) {
+ AdminUser<'a>(ri: &'a RenderInfo<'a>, user: &'a User) {
h2 { @user.get(USER_NAME).unwrap_or("nameless user") }
p { @tr(ri.lang, "tag.Ulgn") ": " @user.get(USER_LOGIN) }
form[method="POST", action=u_admin_user_remove(user.get(USER_LOGIN).unwrap())] {
diff --git a/ui/src/components/home.rs b/ui/src/components/home.rs
index 5ae6a66..f7444eb 100644
--- a/ui/src/components/home.rs
+++ b/ui/src/components/home.rs
@@ -12,7 +12,7 @@ use crate::{
use jellycommon::Nku;
use jellyui_locale::tr;
-pub enum HomeRow {
+pub enum HomeRow<'a> {
Inline(Vec<Nku<'a>>),
Highlight(Nku<'a>),
}
diff --git a/ui/src/components/node_card.rs b/ui/src/components/node_card.rs
index e1baec1..2f5bae7 100644
--- a/ui/src/components/node_card.rs
+++ b/ui/src/components/node_card.rs
@@ -9,16 +9,16 @@ use crate::{
components::{node_page::aspect_class, props::Props},
};
use jellycommon::{
- jellyobject::Object,
+ jellyobject::{EMPTY, Object},
routes::{u_image, u_image_fallback_person, u_node_slug, u_node_slug_player},
*,
};
markup::define! {
NodeCard<'a>(ri: &'a RenderInfo<'a>, nku: &'a Nku<'a>) {
- @let node = nku.node;
+ @let node = &nku.node;
@let slug = node.get(NO_SLUG).unwrap_or_default();
- div[class=&format!("card {}", aspect_class(node))] {
+ div[class=&format!("card {}", aspect_class(&node))] {
.poster {
a[href=u_node_slug(&slug)] {
img[src=cover_image(&node, 512), loading="lazy"];
@@ -44,7 +44,7 @@ markup::define! {
}
NodeCardWide<'a>(ri: &'a RenderInfo<'a>, nku: Nku<'a>) {
- @let node = nku.node;
+ @let node = &nku.node;
@let slug = node.get(NO_SLUG).unwrap_or_default();
div[class="card wide"] {
div[class=&format!("poster {}", aspect_class(node))] {
@@ -66,9 +66,9 @@ markup::define! {
}
NodeCardHightlight<'a>(ri: &'a RenderInfo<'a>, nku: &'a Nku<'a>) {
- @let node = nku.node;
+ @let node = &nku.node;
@let slug = node.get(NO_SLUG).unwrap_or_default();
- @let backdrop = u_image(node.get(NO_PICTURES).unwrap_or_default().get(PICT_BACKDROP).unwrap_or_default(), 2048);
+ @let backdrop = u_image(node.get(NO_PICTURES).unwrap_or(EMPTY).get(PICT_BACKDROP).unwrap_or_default(), 2048);
div[class="card highlight", style=format!("background-image: url(\"{backdrop}\")")] {
.inner {
div.overview {
@@ -92,7 +92,7 @@ markup::define! {
}
fn cover_image(node: &Object, size: usize) -> String {
- if let Some(cover) = node.get(NO_PICTURES).unwrap_or_default().get(PICT_COVER) {
+ if let Some(cover) = node.get(NO_PICTURES).unwrap_or(EMPTY).get(PICT_COVER) {
return u_image(cover, size);
}
if let Some(title) = node.get(NO_TITLE)
diff --git a/ui/src/components/node_page.rs b/ui/src/components/node_page.rs
index 5cd71ce..2a5848b 100644
--- a/ui/src/components/node_page.rs
+++ b/ui/src/components/node_page.rs
@@ -6,7 +6,7 @@
use crate::{RenderInfo, components::props::Props, page};
use jellycommon::{
- jellyobject::{Object, Tag, TypedTag},
+ jellyobject::{EMPTY, Object, Tag, TypedTag},
routes::{u_image, u_node_slug_player},
*,
};
@@ -30,9 +30,9 @@ page!(Player<'_>, |x| x
markup::define! {
NodePage<'a>(ri: &'a RenderInfo<'a>, nku: Nku<'a>) {
- @let node = nku.node;
+ @let node = &nku.node;
@let slug = node.get(NO_SLUG).unwrap_or_default();
- @let pics = node.get(NO_PICTURES).unwrap_or_default();
+ @let pics = node.get(NO_PICTURES).unwrap_or(EMPTY);
@if let Some(path) = pics.get(PICT_BACKDROP) {
img.backdrop[src=u_image(path, 2048)];
}
@@ -106,7 +106,7 @@ markup::define! {
details {
summary { @tr(ri.lang, "tag.iden") }
table {
- @for (key, value) in idents.entries::<&str>() { tr {
+ @for (key, value) in idents.entries::<str>() { tr {
td { @tr(ri.lang, &format!("tag.iden.{key}")) }
@if let Some(url) = external_id_url(key, value) {
td { a[href=url] { pre { @value } } }
@@ -130,13 +130,13 @@ markup::define! {
summary { @tr(ri.lang, "tag.msrc") }
table {
tr { th {"Attribute"} th {"Source"} }
- @for (key, source) in node.get(NO_METASOURCE).unwrap_or_default().entries::<Tag>() { tr {
+ @for (key, source) in node.get(NO_METASOURCE).unwrap_or(EMPTY).entries::<Tag>() { tr {
td { @tr(ri.lang, &format!("tag.{key}")) }
td { @tr(ri.lang, &format!("tag.msrc.{source}")) }
}}
@for nkey in [NO_PICTURES, NO_IDENTIFIERS, NO_RATINGS] {
- @let nob = node.get(nkey).unwrap_or_default();
- @for (key, source) in nob.get(NO_METASOURCE).unwrap_or_default().entries::<Tag>() { tr {
+ @let nob = node.get(nkey).unwrap_or(EMPTY);
+ @for (key, source) in nob.get(NO_METASOURCE).unwrap_or(EMPTY).entries::<Tag>() { tr {
td { @tr(ri.lang, &format!("tag.{nkey}")) ": " @tr(ri.lang, &format!("tag.{nkey}.{key}")) }
td { @tr(ri.lang, &format!("tag.msrc.{source}")) }
}}
@@ -158,7 +158,7 @@ markup::define! {
Player<'a>(ri: &'a RenderInfo<'a>, nku: Nku<'a>) {
@let _ = ri;
- @let pics = nku.node.get(NO_PICTURES).unwrap_or_default();
+ @let pics = nku.node.get(NO_PICTURES).unwrap_or(EMPTY);
video[id="player", poster=pics.get(PICT_COVER).map(|p| u_image(p, 2048))] {}
}
}
@@ -169,7 +169,7 @@ markup::define! {
// start * 0.8 + end * 0.2
// }
-pub fn aspect_class(node: Object<'_>) -> &'static str {
+pub fn aspect_class(node: &Object) -> &'static str {
let kind = node.get(NO_KIND).unwrap_or(KIND_COLLECTION);
match kind {
KIND_VIDEO | KIND_EPISODE => "aspect-thumb",
diff --git a/ui/src/components/props.rs b/ui/src/components/props.rs
index e672919..db59019 100644
--- a/ui/src/components/props.rs
+++ b/ui/src/components/props.rs
@@ -9,38 +9,39 @@ use crate::{
format::{format_count, format_duration},
};
use chrono::DateTime;
-use jellycommon::{jellyobject::TypedTag, *};
+use jellycommon::{
+ jellyobject::{EMPTY, TypedTag},
+ *,
+};
use jellyui_locale::tr;
-use std::marker::PhantomData;
markup::define! {
Props<'a>(ri: &'a RenderInfo<'a>, nku: &'a Nku<'a>, full: bool) {
- @let node = nku.node;
.props {
- @if let Some(dur) = node.get(NO_DURATION) {
+ @if let Some(dur) = nku.node.get(NO_DURATION) {
p { @format_duration(dur) }
}
- // @if let Some(res) = node.get(NO_TRACK) {
+ // @if let Some(res) = nku.node.get(NO_TRACK) {
// p { @m.resolution_name() }
// }
- @if let Some(d) = node.get(NO_RELEASEDATE) {
+ @if let Some(d) = nku.node.get(NO_RELEASEDATE) {
p { @if *full {
@DateTime::from_timestamp_millis(d).unwrap().naive_utc().to_string()
} else {
@DateTime::from_timestamp_millis(d).unwrap().date_naive().to_string()
}}
}
- @match node.get(NO_VISIBILITY).unwrap_or(VISI_VISIBLE) {
+ @match nku.node.get(NO_VISIBILITY).unwrap_or(VISI_VISIBLE) {
VISI_REDUCED => {p.visibility{@tr(ri.lang, "prop.vis.reduced")}}
VISI_HIDDEN => {p.visibility{@tr(ri.lang, "prop.vis.hidden")}}
VISI_VISIBLE | _ => {}
}
// TODO
- // @if !node.children.is_empty() {
- // p { @format!("{} items", node.children.len()) }
+ // @if !nku.node.children.is_empty() {
+ // p { @format!("{} items", nku.node.children.len()) }
// }
- @for (kind, value) in node.get(NO_RATINGS).unwrap_or_default().entries::<f64>() {
- @match TypedTag(kind, PhantomData) {
+ @for (kind, value) in nku.node.get(NO_RATINGS).unwrap_or(EMPTY).entries::<f64>() {
+ @match TypedTag::new(kind) {
RTYP_YOUTUBE_LIKES => {p.likes{ @format_count(value as usize) " Likes" }}
RTYP_YOUTUBE_VIEWS => {p{ @format_count(value as usize) " Views" }}
RTYP_YOUTUBE_SUBSCRIBERS => {p{ @format_count(value as usize) " Subscribers" }}
diff --git a/ui/src/components/user.rs b/ui/src/components/user.rs
index 9dabffc..a964696 100644
--- a/ui/src/components/user.rs
+++ b/ui/src/components/user.rs
@@ -14,7 +14,7 @@ use jellyui_locale::tr;
page!(UserSettings<'_>, |x| tr(x.ri.lang, "settings"));
markup::define! {
- UserSettings<'a>(ri: &'a RenderInfo<'a>, user: User<'a>) {
+ UserSettings<'a>(ri: &'a RenderInfo<'a>, user: &'a User) {
h1 { @tr(ri.lang, "settings") }
h2 { @tr(ri.lang, "settings.account") }
diff --git a/ui/src/lib.rs b/ui/src/lib.rs
index 93dd38a..76e2018 100644
--- a/ui/src/lib.rs
+++ b/ui/src/lib.rs
@@ -3,6 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
+#![feature(phantom_variance_markers)]
pub mod components;
pub(crate) mod format;
mod scaffold;
@@ -33,7 +34,7 @@ pub trait Page {
}
pub struct RenderInfo<'a> {
- pub user: Option<Object<'a>>,
+ pub user: Option<&'a Object>,
pub message: Option<(&'a str, &'a str)>,
pub lang: &'a str,
pub status_message: Option<&'a str>,