| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -7,6 +7,21 @@
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				use super::*;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				use hashlink::LruCache;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#[derive(Debug, Clone)]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				/// A dead record that is yet to be purged from disk and statistics
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				struct DeadRecord<D>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    D: fmt::Debug + Clone + Serialize + for<'d> Deserialize<'d>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // The key used in the record_index
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    key: RecordTableKey,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // The actual record
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    record: Record<D>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // True if this record is accounted for in the total storage
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // and needs to have the statistics updated or not when purged
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    in_total_storage: bool,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				pub struct RecordStore<D>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    D: fmt::Debug + Clone + Serialize + for<'d> Deserialize<'d>,
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -15,16 +30,24 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    name: String,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    limits: RecordStoreLimits,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    /// The tabledb used for record data
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    record_table: Option<TableDB>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    /// The tabledb used for subkey data
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    subkey_table: Option<TableDB>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    /// The in-memory index that keeps track of what records are in the tabledb
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    record_index: LruCache<RecordTableKey, Record<D>>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    /// The in-memory cache of commonly accessed subkey data so we don't have to keep hitting the db
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    subkey_cache: LruCache<SubkeyTableKey, RecordData>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    /// Total storage space or subkey data inclusive of structures in memory
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    subkey_cache_total_size: LimitedSize<usize>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    /// Total storage space of records in the tabledb inclusive of subkey data and structures
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    total_storage_space: LimitedSize<u64>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    dead_records: Vec<(RecordTableKey, Record<D>)>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    /// Records to be removed from the tabledb upon next purge
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    dead_records: Vec<DeadRecord<D>>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    /// The list of records that have changed since last flush to disk (optimization for batched writes)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    changed_records: HashSet<RecordTableKey>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    /// A mutex to ensure we handle this concurrently
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    purge_dead_records_mutex: Arc<AsyncMutex<()>>,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -97,33 +120,45 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Sort the record index by last touched time and insert in sorted order
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        record_index_saved.sort_by(|a, b| a.1.last_touched().cmp(&b.1.last_touched()));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let mut dead_records = Vec::new();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let mut dead_records = Vec::<DeadRecord<D>>::new();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for ri in record_index_saved {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // total the storage space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.total_storage_space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                .add(mem::size_of::<RecordTableKey>() as u64)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                .unwrap();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.total_storage_space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                .add(ri.1.total_size() as u64)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                .add((mem::size_of::<RecordTableKey>() + ri.1.total_size()) as u64)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                .unwrap();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if let Err(_) = self.total_storage_space.commit() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // If we overflow the limit, kill off the record
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                dead_records.push((ri.0, ri.1));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // Revert the total storage space because the commit failed
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.total_storage_space.rollback();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // If we overflow the limit, kill off the record, noting that it has not yet been added to the total storage space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                dead_records.push(DeadRecord {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    key: ri.0,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    record: ri.1,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    in_total_storage: false,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                continue;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // add to index and ensure we deduplicate in the case of an error
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if let Some(v) = self.record_index.insert_with_callback(ri.0, ri.1, |k, v| {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // If the configuration change, we only want to keep the 'limits.max_records' records
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                dead_records.push((k, v));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                dead_records.push(DeadRecord {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    key: k,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    record: v,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    in_total_storage: true,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // This shouldn't happen, but deduplicate anyway
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                log_stor!(warn "duplicate record in table: {:?}", ri.0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                dead_records.push((ri.0, v));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                dead_records.push(DeadRecord {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    key: ri.0,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    record: v,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    in_total_storage: true,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for (k, v) in dead_records {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.add_dead_record(k, v);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for dr in dead_records {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.dead_records.push(dr);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.record_table = Some(record_table);
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -132,7 +167,11 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    fn add_dead_record(&mut self, key: RecordTableKey, record: Record<D>) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.dead_records.push((key, record));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.dead_records.push(DeadRecord {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            key,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            record,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            in_total_storage: true,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    fn mark_record_changed(&mut self, key: RecordTableKey) {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -208,23 +247,23 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let rt_xact = record_table.transact();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let st_xact = subkey_table.transact();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let dead_records = mem::take(&mut self.dead_records);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for (k, v) in dead_records {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for dr in dead_records {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Record should already be gone from index
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if self.record_index.contains_key(&k) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                log_stor!(error "dead record found in index: {:?}", k);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if self.record_index.contains_key(&dr.key) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                log_stor!(error "dead record found in index: {:?}", dr.key);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Delete record
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if let Err(e) = rt_xact.delete(0, &k.bytes()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if let Err(e) = rt_xact.delete(0, &dr.key.bytes()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                log_stor!(error "record could not be deleted: {}", e);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Delete subkeys
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            let stored_subkeys = v.stored_subkeys();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            let stored_subkeys = dr.record.stored_subkeys();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            for sk in stored_subkeys.iter() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // From table
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                let stk = SubkeyTableKey {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    key: k.key,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    key: dr.key.key,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    subkey: sk,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                let stkb = stk.bytes();
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -237,11 +276,12 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Remove from total size
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.total_storage_space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                .saturating_sub(mem::size_of::<RecordTableKey>() as u64);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.total_storage_space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                .saturating_sub(v.total_size() as u64);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.total_storage_space.commit().unwrap();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if dr.in_total_storage {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.total_storage_space.saturating_sub(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    (mem::size_of::<RecordTableKey>() + dr.record.total_size()) as u64,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.total_storage_space.commit().unwrap();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if let Err(e) = rt_xact.commit().await {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            log_stor!(error "failed to commit record table transaction: {}", e);
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -306,6 +346,9 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .await
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .map_err(VeilidAPIError::internal)?;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Update storage space (won't fail due to check_limit above)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.total_storage_space.commit().unwrap();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Save to record index
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let mut dead_records = Vec::new();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if let Some(v) = self.record_index.insert_with_callback(rtk, record, |k, v| {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -319,9 +362,6 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.add_dead_record(dr.0, dr.1);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Update storage space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.total_storage_space.commit().unwrap();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        Ok(())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -407,7 +447,7 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        subkey: ValueSubkey,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        want_descriptor: bool,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    ) -> VeilidAPIResult<Option<SubkeyResult>> {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // record from index
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Get record from index
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let Some((subkey_count, has_subkey, opt_descriptor)) = self.with_record(key, |record| {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            (record.subkey_count(), record.stored_subkeys().contains(subkey), if want_descriptor {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                Some(record.descriptor().clone())
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -545,9 +585,9 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Get record from index
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let Some((subkey_count, total_size)) = self.with_record(key, |record| {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            (record.subkey_count(), record.total_size())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Get record subkey count and total size of all record subkey data exclusive of structures
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let Some((subkey_count, prior_record_data_size)) = self.with_record(key, |record| {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            (record.subkey_count(), record.record_data_size())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }) else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            apibail_invalid_argument!("no record at this key", "key", key);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        };
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -563,14 +603,14 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Get the previous subkey and ensure we aren't going over the record size limit
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let mut prior_record_data_size = 0usize;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let mut prior_subkey_size = 0usize;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // If subkey exists in subkey cache, use that
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let stk = SubkeyTableKey { key, subkey };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let stk_bytes = stk.bytes();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if let Some(record_data) = self.subkey_cache.peek(&stk) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            prior_record_data_size = record_data.total_size();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            prior_subkey_size = record_data.data_size();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // If not in cache, try to pull from table store
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if let Some(record_data) = subkey_table
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -578,26 +618,26 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                .await
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                .map_err(VeilidAPIError::internal)?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                prior_record_data_size = record_data.total_size();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                prior_subkey_size = record_data.data_size();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Make new record data
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let record_data = RecordData::new(signed_value_data);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let subkey_record_data = RecordData::new(signed_value_data);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Check new total record size
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let new_record_data_size = record_data.total_size();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let new_total_size = total_size + new_record_data_size - prior_record_data_size;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if new_total_size > self.limits.max_record_total_size {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let new_subkey_size = subkey_record_data.data_size();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let new_record_data_size = prior_record_data_size - prior_subkey_size + new_subkey_size;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if new_record_data_size > self.limits.max_record_total_size {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            apibail_generic!("dht record too large");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Check new total storage space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.total_storage_space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .sub(prior_record_data_size as u64)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .sub(prior_subkey_size as u64)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .unwrap();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.total_storage_space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .add(new_record_data_size as u64)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .add(new_subkey_size as u64)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .unwrap();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if !self.total_storage_space.check_limit() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            apibail_try_again!();
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -605,17 +645,17 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Write subkey
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        subkey_table
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .store_json(0, &stk_bytes, &record_data)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .store_json(0, &stk_bytes, &subkey_record_data)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .await
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            .map_err(VeilidAPIError::internal)?;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Write to subkey cache
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.add_to_subkey_cache(stk, record_data);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.add_to_subkey_cache(stk, subkey_record_data);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Update record
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.with_record_mut(key, |record| {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            record.store_subkey(subkey);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            record.set_record_data_size(new_total_size);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            record.set_record_data_size(new_record_data_size);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        .expect("record should still be here");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -666,7 +706,7 @@ where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out += &format!("Total Storage Space: {}\n", self.total_storage_space.get());
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out += &format!("Dead Records: {}\n", self.dead_records.len());
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for dr in &self.dead_records {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out += &format!("  {}\n", dr.0.key.to_string());
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            out += &format!("  {}\n", dr.key.key.to_string());
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        out += &format!("Changed Records: {}\n", self.changed_records.len());
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        for cr in &self.changed_records {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				 
 |