DAV file management delete
This commit is contained in:
@@ -87,6 +87,7 @@ pub struct DefaultFolder {
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Clone, Copy, PartialEq, Eq, Hash, Debug,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub enum SpecialUse {
|
||||
Inbox,
|
||||
Trash,
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::{sync::Arc, time::Duration};
|
||||
use directory::{Directory, QueryBy, Type, backend::internal::manage::ManageDirectory};
|
||||
use jmap_proto::types::{
|
||||
blob::BlobId, collection::Collection, property::Property, state::StateChange,
|
||||
type_state::DataType,
|
||||
};
|
||||
use sieve::Sieve;
|
||||
use store::{
|
||||
@@ -379,16 +380,15 @@ impl Server {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_properties<U, I, P>(
|
||||
pub async fn get_properties<U, I>(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
iterate: &I,
|
||||
property: P,
|
||||
property: Property,
|
||||
) -> trc::Result<Vec<(u32, U)>>
|
||||
where
|
||||
I: DocumentSet + Send + Sync,
|
||||
P: AsRef<Property> + Sync + Send,
|
||||
U: Deserialize + 'static,
|
||||
{
|
||||
let property: u8 = property.as_ref().into();
|
||||
@@ -604,6 +604,17 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn broadcast_single_state_change(
|
||||
&self,
|
||||
account_id: u32,
|
||||
change_id: u64,
|
||||
data_type: DataType,
|
||||
) {
|
||||
self.broadcast_state_change(StateChange::new(account_id).with_change(data_type, change_id))
|
||||
.await;
|
||||
}
|
||||
|
||||
#[allow(clippy::blocks_in_conditions)]
|
||||
pub async fn put_blob(
|
||||
&self,
|
||||
|
||||
@@ -45,7 +45,7 @@ use rustls::sign::CertifiedKey;
|
||||
use tokio::sync::{Notify, Semaphore, mpsc};
|
||||
use tokio_rustls::TlsConnector;
|
||||
use utils::{
|
||||
bimap::IdBimap,
|
||||
bimap::{IdBimap, IdBimapItem},
|
||||
cache::{Cache, CacheItemWeight, CacheWithTtl},
|
||||
snowflake::SnowflakeIdGenerator,
|
||||
};
|
||||
@@ -250,11 +250,19 @@ pub struct NameWrapper(pub String);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Files {
|
||||
pub files: IdBimap,
|
||||
pub files: IdBimap<FileItem>,
|
||||
pub size: u64,
|
||||
pub modseq: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FileItem {
|
||||
pub document_id: u32,
|
||||
pub parent_id: Option<u32>,
|
||||
pub name: String,
|
||||
pub is_container: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Core {
|
||||
pub storage: Storage,
|
||||
@@ -480,3 +488,30 @@ pub fn ip_to_bytes_prefix(prefix: u8, ip: &IpAddr) -> Vec<u8> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Files {
|
||||
pub fn subtree(&self, search_path: &str) -> impl Iterator<Item = &FileItem> {
|
||||
let prefix = format!("{search_path}/");
|
||||
self.files
|
||||
.iter()
|
||||
.filter(move |item| item.name.starts_with(&prefix) || item.name == search_path)
|
||||
}
|
||||
|
||||
pub fn is_ancestor_of(&self, ancestor: u32, descendant: u32) -> bool {
|
||||
let ancestor = &self.files.by_id(ancestor).unwrap().name;
|
||||
let descendant = &self.files.by_id(descendant).unwrap().name;
|
||||
|
||||
let prefix = format!("{ancestor}/");
|
||||
descendant.starts_with(&prefix) || descendant == ancestor
|
||||
}
|
||||
}
|
||||
|
||||
impl IdBimapItem for FileItem {
|
||||
fn id(&self) -> &u32 {
|
||||
&self.document_id
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,22 @@ use utils::topological::{TopologicalSort, TopologicalSortIterator};
|
||||
use crate::Server;
|
||||
|
||||
pub struct ExpandedFolders {
|
||||
names: AHashMap<u32, (String, u32)>,
|
||||
names: AHashMap<u32, (String, u32, bool)>,
|
||||
iter: TopologicalSortIterator<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExpandedFolder {
|
||||
pub name: String,
|
||||
pub document_id: u32,
|
||||
pub parent_id: Option<u32>,
|
||||
pub is_container: bool,
|
||||
}
|
||||
|
||||
pub trait FolderHierarchy: Sync + Send {
|
||||
fn name(&self) -> String;
|
||||
fn parent_id(&self) -> u32;
|
||||
fn is_container(&self) -> bool;
|
||||
}
|
||||
|
||||
pub trait TopologyBuilder: Sync + Send {
|
||||
@@ -72,7 +81,10 @@ impl Server {
|
||||
let parent_id = folder.parent_id();
|
||||
|
||||
topological_sort.insert(parent_id, document_id);
|
||||
names.insert(document_id, (folder.name(), parent_id));
|
||||
names.insert(
|
||||
document_id,
|
||||
(folder.name(), parent_id, folder.is_container()),
|
||||
);
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
@@ -154,7 +166,7 @@ impl ExpandedFolders {
|
||||
where
|
||||
T: Fn(u32, &str) -> Option<String>,
|
||||
{
|
||||
for (document_id, (name, _)) in &mut self.names {
|
||||
for (document_id, (name, _, _)) in &mut self.names {
|
||||
if let Some(new_name) = formatter(*document_id - 1, name) {
|
||||
*name = new_name;
|
||||
}
|
||||
@@ -162,22 +174,36 @@ impl ExpandedFolders {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_iterator(mut self) -> impl Iterator<Item = (u32, String)> + Sync + Send {
|
||||
pub fn into_iterator(mut self) -> impl Iterator<Item = ExpandedFolder> + Sync + Send {
|
||||
for folder_id in self.iter.by_ref() {
|
||||
if folder_id != 0 {
|
||||
if let Some((name, parent_name, parent_id)) =
|
||||
self.names.get(&folder_id).and_then(|(name, parent_id)| {
|
||||
self.names
|
||||
.get(parent_id)
|
||||
.map(|(parent_name, _)| (name, parent_name, *parent_id))
|
||||
if let Some((name, parent_name, parent_id, is_container)) = self
|
||||
.names
|
||||
.get(&folder_id)
|
||||
.and_then(|(name, parent_id, is_container)| {
|
||||
self.names.get(parent_id).map(|(parent_name, _, _)| {
|
||||
(name, parent_name, *parent_id, *is_container)
|
||||
})
|
||||
})
|
||||
{
|
||||
let name = format!("{parent_name}/{name}");
|
||||
self.names.insert(folder_id, (name, parent_id));
|
||||
self.names
|
||||
.insert(folder_id, (name, parent_id, is_container));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.names.into_iter().map(|(id, (name, _))| (id - 1, name))
|
||||
self.names
|
||||
.into_iter()
|
||||
.map(|(id, (name, parent_id, is_container))| ExpandedFolder {
|
||||
name,
|
||||
document_id: id - 1,
|
||||
is_container,
|
||||
parent_id: if parent_id == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(parent_id - 1)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use store::{
|
||||
Serialize, SerializeInfallible,
|
||||
write::{
|
||||
Archiver, BatchBuilder, BitmapClass, BlobOp, DirectoryClass, IntoOperations, Operation,
|
||||
ValueOp, assert::HashedValue,
|
||||
assert::HashedValue,
|
||||
},
|
||||
};
|
||||
use utils::BlobHash;
|
||||
@@ -22,18 +22,19 @@ pub enum IndexValue<'x> {
|
||||
Text { field: u8, value: Cow<'x, str> },
|
||||
U32 { field: u8, value: Option<u32> },
|
||||
U64 { field: u8, value: Option<u64> },
|
||||
U32List { field: u8, value: &'x [u32] },
|
||||
U32List { field: u8, value: Cow<'x, [u32]> },
|
||||
Tag { field: u8, is_set: bool },
|
||||
Blob { value: BlobHash },
|
||||
Quota { used: u32 },
|
||||
Acl { value: &'x [AclGrant] },
|
||||
Acl { value: Cow<'x, [AclGrant]> },
|
||||
}
|
||||
|
||||
pub trait IndexableObject:
|
||||
Debug
|
||||
+ Eq
|
||||
+ Sync
|
||||
+ Send
|
||||
pub trait IndexableObject: Sync + Send {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>>;
|
||||
}
|
||||
|
||||
pub trait IndexableAndSerializableObject:
|
||||
IndexableObject
|
||||
+ rkyv::Archive
|
||||
+ for<'a> rkyv::Serialize<
|
||||
rkyv::api::high::HighSerializer<
|
||||
@@ -43,23 +44,22 @@ pub trait IndexableObject:
|
||||
>,
|
||||
>
|
||||
{
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ObjectIndexBuilder<T: IndexableObject> {
|
||||
pub struct ObjectIndexBuilder<C: IndexableObject, N: IndexableAndSerializableObject> {
|
||||
tenant_id: Option<u32>,
|
||||
current: Option<HashedValue<T>>,
|
||||
changes: Option<T>,
|
||||
current: Option<HashedValue<C>>,
|
||||
changes: Option<N>,
|
||||
}
|
||||
|
||||
impl<T: IndexableObject> Default for ObjectIndexBuilder<T> {
|
||||
impl<C: IndexableObject, N: IndexableAndSerializableObject> Default for ObjectIndexBuilder<C, N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IndexableObject> ObjectIndexBuilder<T> {
|
||||
impl<C: IndexableObject, N: IndexableAndSerializableObject> ObjectIndexBuilder<C, N> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current: None,
|
||||
@@ -68,30 +68,30 @@ impl<T: IndexableObject> ObjectIndexBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_current(mut self, current: HashedValue<T>) -> Self {
|
||||
pub fn with_current(mut self, current: HashedValue<C>) -> Self {
|
||||
self.current = Some(current);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_changes(mut self, changes: T) -> Self {
|
||||
pub fn with_changes(mut self, changes: N) -> Self {
|
||||
self.changes = Some(changes);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_current_opt(mut self, current: Option<HashedValue<T>>) -> Self {
|
||||
pub fn with_current_opt(mut self, current: Option<HashedValue<C>>) -> Self {
|
||||
self.current = current;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn changes(&self) -> Option<&T> {
|
||||
pub fn changes(&self) -> Option<&N> {
|
||||
self.changes.as_ref()
|
||||
}
|
||||
|
||||
pub fn changes_mut(&mut self) -> Option<&mut T> {
|
||||
pub fn changes_mut(&mut self) -> Option<&mut N> {
|
||||
self.changes.as_mut()
|
||||
}
|
||||
|
||||
pub fn current(&self) -> Option<&HashedValue<T>> {
|
||||
pub fn current(&self) -> Option<&HashedValue<C>> {
|
||||
self.current.as_ref()
|
||||
}
|
||||
|
||||
@@ -101,23 +101,35 @@ impl<T: IndexableObject> ObjectIndexBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IndexableObject> IntoOperations for ObjectIndexBuilder<T> {
|
||||
impl<C: IndexableObject, N: IndexableAndSerializableObject> IntoOperations
|
||||
for ObjectIndexBuilder<C, N>
|
||||
{
|
||||
fn build(self, batch: &mut BatchBuilder) -> trc::Result<()> {
|
||||
match (self.current, self.changes) {
|
||||
(None, Some(changes)) => {
|
||||
// Insertion
|
||||
build_batch(batch, &changes, self.tenant_id, true);
|
||||
for item in changes.index_values() {
|
||||
build_index(batch, item, self.tenant_id, true);
|
||||
}
|
||||
batch.set(Property::Value, Archiver::new(changes).serialize()?);
|
||||
}
|
||||
(Some(current), Some(changes)) => {
|
||||
// Update
|
||||
batch.assert_value(Property::Value, ¤t);
|
||||
merge_batch(batch, current.inner, changes, self.tenant_id)?;
|
||||
for (current, change) in current.inner.index_values().zip(changes.index_values()) {
|
||||
if current != change {
|
||||
merge_index(batch, current, change, self.tenant_id)?;
|
||||
}
|
||||
}
|
||||
batch.set(Property::Value, Archiver::new(changes).serialize()?);
|
||||
}
|
||||
(Some(current), None) => {
|
||||
// Deletion
|
||||
batch.assert_value(Property::Value, ¤t);
|
||||
build_batch(batch, ¤t.inner, self.tenant_id, false);
|
||||
for item in current.inner.index_values() {
|
||||
build_index(batch, item, self.tenant_id, false);
|
||||
}
|
||||
|
||||
batch.clear(Property::Value);
|
||||
}
|
||||
(None, None) => unreachable!(),
|
||||
@@ -127,316 +139,301 @@ impl<T: IndexableObject> IntoOperations for ObjectIndexBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_batch<T: IndexableObject>(
|
||||
batch: &mut BatchBuilder,
|
||||
object: &T,
|
||||
tenant_id: Option<u32>,
|
||||
set: bool,
|
||||
) {
|
||||
for item in object.index_values() {
|
||||
match item {
|
||||
IndexValue::Text { field, value } => {
|
||||
if !value.is_empty() {
|
||||
batch.ops.push(Operation::Index {
|
||||
fn build_index(batch: &mut BatchBuilder, item: IndexValue<'_>, tenant_id: Option<u32>, set: bool) {
|
||||
match item {
|
||||
IndexValue::Text { field, value } => {
|
||||
if !value.is_empty() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.into_owned().into_bytes(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::U32 { field, value } => {
|
||||
if let Some(value) = value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::U64 { field, value } => {
|
||||
if let Some(value) = value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::U32List { field, value } => {
|
||||
for item in value.as_ref() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: (*item).serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::Tag { field, is_set } => {
|
||||
if is_set {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::Tag {
|
||||
field,
|
||||
key: value.into_owned().into_bytes(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
value: ().into(),
|
||||
},
|
||||
set,
|
||||
});
|
||||
}
|
||||
IndexValue::U32 { field, value } => {
|
||||
if let Some(value) = value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::Blob { value } => {
|
||||
if set {
|
||||
batch.set(BlobOp::Link { hash: value }, vec![]);
|
||||
} else {
|
||||
batch.clear(BlobOp::Link { hash: value });
|
||||
}
|
||||
IndexValue::U64 { field, value } => {
|
||||
if let Some(value) = value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::Acl { value } => {
|
||||
for item in value.as_ref() {
|
||||
batch.ops.push(Operation::acl(
|
||||
item.account_id,
|
||||
if set {
|
||||
item.grants.bitmap.serialize().into()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
));
|
||||
}
|
||||
IndexValue::U32List { field, value } => {
|
||||
for item in value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: (*item).serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::Tag { field, is_set } => {
|
||||
if is_set {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::Tag {
|
||||
field,
|
||||
value: ().into(),
|
||||
},
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexValue::Blob { value } => {
|
||||
if set {
|
||||
batch.set(BlobOp::Link { hash: value }, vec![]);
|
||||
} else {
|
||||
batch.clear(BlobOp::Link { hash: value });
|
||||
}
|
||||
}
|
||||
IndexValue::Acl { value } => {
|
||||
for item in value {
|
||||
batch.ops.push(Operation::acl(
|
||||
item.account_id,
|
||||
if set {
|
||||
item.grants.bitmap.serialize().into()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
IndexValue::Quota { used } => {
|
||||
let value = if set { used as i64 } else { -(used as i64) };
|
||||
}
|
||||
IndexValue::Quota { used } => {
|
||||
let value = if set { used as i64 } else { -(used as i64) };
|
||||
|
||||
if let Some(account_id) = batch.last_account_id() {
|
||||
batch.add(DirectoryClass::UsedQuota(account_id), value);
|
||||
}
|
||||
if let Some(account_id) = batch.last_account_id() {
|
||||
batch.add(DirectoryClass::UsedQuota(account_id), value);
|
||||
}
|
||||
|
||||
if let Some(tenant_id) = tenant_id {
|
||||
batch.add(DirectoryClass::UsedQuota(tenant_id), value);
|
||||
}
|
||||
if let Some(tenant_id) = tenant_id {
|
||||
batch.add(DirectoryClass::UsedQuota(tenant_id), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_batch<T: IndexableObject>(
|
||||
fn merge_index(
|
||||
batch: &mut BatchBuilder,
|
||||
current: T,
|
||||
changes: T,
|
||||
current: IndexValue<'_>,
|
||||
change: IndexValue<'_>,
|
||||
tenant_id: Option<u32>,
|
||||
) -> trc::Result<()> {
|
||||
let mut has_changes = current != changes;
|
||||
match (current, change) {
|
||||
(
|
||||
IndexValue::Text {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::Text {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
if !old_value.is_empty() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: old_value.into_owned().into_bytes(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
|
||||
for (current, change) in current.index_values().zip(changes.index_values()) {
|
||||
if current == change {
|
||||
continue;
|
||||
if !new_value.is_empty() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: new_value.into_owned().into_bytes(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
has_changes = true;
|
||||
|
||||
match (current, change) {
|
||||
(
|
||||
IndexValue::Text {
|
||||
(
|
||||
IndexValue::U32 {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::U32 {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
if let Some(value) = old_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::Text {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
if !old_value.is_empty() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: old_value.into_owned().into_bytes(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
key: value.serialize(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if let Some(value) = new_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
(
|
||||
IndexValue::U64 {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::U64 {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
if let Some(value) = old_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if let Some(value) = new_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
(
|
||||
IndexValue::U32List {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::U32List {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
let mut add_values = HashSet::new();
|
||||
let mut remove_values = HashSet::new();
|
||||
|
||||
if !new_value.is_empty() {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: new_value.into_owned().into_bytes(),
|
||||
set: true,
|
||||
});
|
||||
for current_value in old_value.as_ref() {
|
||||
remove_values.insert(current_value);
|
||||
}
|
||||
for value in new_value.as_ref() {
|
||||
if !remove_values.remove(&value) {
|
||||
add_values.insert(value);
|
||||
}
|
||||
}
|
||||
(
|
||||
IndexValue::U32 {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::U32 {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
if let Some(value) = old_value {
|
||||
|
||||
for (values, set) in [(add_values, true), (remove_values, false)] {
|
||||
for value in values {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: false,
|
||||
set,
|
||||
});
|
||||
}
|
||||
if let Some(value) = new_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
}
|
||||
}
|
||||
(
|
||||
IndexValue::Tag {
|
||||
field,
|
||||
is_set: was_set,
|
||||
},
|
||||
IndexValue::Tag { is_set, .. },
|
||||
) => {
|
||||
if was_set {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::Tag {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
value: ().into(),
|
||||
},
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
(
|
||||
IndexValue::U64 {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::U64 {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
if let Some(value) = old_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
if is_set {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::Tag {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if let Some(value) = new_value {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
value: ().into(),
|
||||
},
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
(
|
||||
IndexValue::U32List {
|
||||
field,
|
||||
value: old_value,
|
||||
},
|
||||
IndexValue::U32List {
|
||||
value: new_value, ..
|
||||
},
|
||||
) => {
|
||||
let mut add_values = HashSet::new();
|
||||
let mut remove_values = HashSet::new();
|
||||
|
||||
for current_value in old_value {
|
||||
remove_values.insert(current_value);
|
||||
}
|
||||
for value in new_value {
|
||||
if !remove_values.remove(&value) {
|
||||
add_values.insert(value);
|
||||
}
|
||||
}
|
||||
|
||||
for (values, set) in [(add_values, true), (remove_values, false)] {
|
||||
for value in values {
|
||||
batch.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value.serialize(),
|
||||
set,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
(
|
||||
IndexValue::Tag {
|
||||
field,
|
||||
is_set: was_set,
|
||||
},
|
||||
IndexValue::Tag { is_set, .. },
|
||||
) => {
|
||||
if was_set {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::Tag {
|
||||
field,
|
||||
value: ().into(),
|
||||
},
|
||||
set: false,
|
||||
});
|
||||
}
|
||||
if is_set {
|
||||
batch.ops.push(Operation::Bitmap {
|
||||
class: BitmapClass::Tag {
|
||||
field,
|
||||
value: ().into(),
|
||||
},
|
||||
set: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
(IndexValue::Blob { value: old_hash }, IndexValue::Blob { value: new_hash }) => {
|
||||
batch.clear(BlobOp::Link { hash: old_hash });
|
||||
batch.set(BlobOp::Link { hash: new_hash }, vec![]);
|
||||
}
|
||||
(IndexValue::Acl { value: old_acl }, IndexValue::Acl { value: new_acl }) => {
|
||||
match (!old_acl.is_empty(), !new_acl.is_empty()) {
|
||||
(true, true) => {
|
||||
// Remove deleted ACLs
|
||||
for current_item in old_acl {
|
||||
if !new_acl
|
||||
.iter()
|
||||
.any(|item| item.account_id == current_item.account_id)
|
||||
{
|
||||
batch
|
||||
.ops
|
||||
.push(Operation::acl(current_item.account_id, None));
|
||||
}
|
||||
}
|
||||
(IndexValue::Blob { value: old_hash }, IndexValue::Blob { value: new_hash }) => {
|
||||
batch.clear(BlobOp::Link { hash: old_hash });
|
||||
batch.set(BlobOp::Link { hash: new_hash }, vec![]);
|
||||
}
|
||||
(IndexValue::Acl { value: old_acl }, IndexValue::Acl { value: new_acl }) => {
|
||||
match (!old_acl.is_empty(), !new_acl.is_empty()) {
|
||||
(true, true) => {
|
||||
// Remove deleted ACLs
|
||||
for current_item in old_acl.as_ref() {
|
||||
if !new_acl
|
||||
.iter()
|
||||
.any(|item| item.account_id == current_item.account_id)
|
||||
{
|
||||
batch
|
||||
.ops
|
||||
.push(Operation::acl(current_item.account_id, None));
|
||||
}
|
||||
}
|
||||
|
||||
// Update ACLs
|
||||
for item in new_acl {
|
||||
let mut add_item = true;
|
||||
for current_item in old_acl {
|
||||
if item.account_id == current_item.account_id {
|
||||
if item.grants == current_item.grants {
|
||||
add_item = false;
|
||||
}
|
||||
break;
|
||||
// Update ACLs
|
||||
for item in new_acl.as_ref() {
|
||||
let mut add_item = true;
|
||||
for current_item in old_acl.as_ref() {
|
||||
if item.account_id == current_item.account_id {
|
||||
if item.grants == current_item.grants {
|
||||
add_item = false;
|
||||
}
|
||||
}
|
||||
if add_item {
|
||||
batch.ops.push(Operation::acl(
|
||||
item.account_id,
|
||||
item.grants.bitmap.serialize().into(),
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
// Add all ACLs
|
||||
for item in new_acl {
|
||||
if add_item {
|
||||
batch.ops.push(Operation::acl(
|
||||
item.account_id,
|
||||
item.grants.bitmap.serialize().into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
// Remove all ACLs
|
||||
for item in old_acl {
|
||||
batch.ops.push(Operation::acl(item.account_id, None));
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
// Add all ACLs
|
||||
for item in new_acl.as_ref() {
|
||||
batch.ops.push(Operation::acl(
|
||||
item.account_id,
|
||||
item.grants.bitmap.serialize().into(),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
(true, false) => {
|
||||
// Remove all ACLs
|
||||
for item in old_acl.as_ref() {
|
||||
batch.ops.push(Operation::acl(item.account_id, None));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
(IndexValue::Quota { used: old_used }, IndexValue::Quota { used: new_used }) => {
|
||||
let value = new_used as i64 - old_used as i64;
|
||||
if let Some(account_id) = batch.last_account_id() {
|
||||
batch.add(DirectoryClass::UsedQuota(account_id), value);
|
||||
}
|
||||
|
||||
if let Some(tenant_id) = tenant_id {
|
||||
batch.add(DirectoryClass::UsedQuota(tenant_id), value);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
(IndexValue::Quota { used: old_used }, IndexValue::Quota { used: new_used }) => {
|
||||
let value = new_used as i64 - old_used as i64;
|
||||
if let Some(account_id) = batch.last_account_id() {
|
||||
batch.add(DirectoryClass::UsedQuota(account_id), value);
|
||||
}
|
||||
|
||||
if has_changes {
|
||||
batch.ops.push(Operation::Value {
|
||||
class: Property::Value.into(),
|
||||
op: ValueOp::Set(Archiver::new(changes).serialize()?.into()),
|
||||
});
|
||||
if let Some(tenant_id) = tenant_id {
|
||||
batch.add(DirectoryClass::UsedQuota(tenant_id), value);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl IndexableObject for () {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
std::iter::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableAndSerializableObject for () {}
|
||||
|
||||
Reference in New Issue
Block a user