// Copyright 2018 Mozilla // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use // this file except in compliance with the License. You may obtain a copy of the // License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. use std::sync::{ Arc, Weak, }; use std::sync::mpsc::{ channel, Receiver, RecvError, Sender, }; use std::thread; use indexmap::{ IndexMap, }; use mentat_core::{ Entid, Schema, TypedValue, }; use edn::entities::{ OpType, }; use errors::{ Result, }; use types::{ AttributeSet, }; use watcher::TransactWatcher; pub struct TxObserver { notify_fn: Arc) + Send + Sync>>, attributes: AttributeSet, } impl TxObserver { pub fn new(attributes: AttributeSet, notify_fn: F) -> TxObserver where F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync { TxObserver { notify_fn: Arc::new(Box::new(notify_fn)), attributes, } } pub fn applicable_reports<'r>(&self, reports: &'r IndexMap) -> IndexMap<&'r Entid, &'r AttributeSet> { reports.into_iter() .filter(|&(_txid, attrs)| !self.attributes.is_disjoint(attrs)) .collect() } fn notify(&self, key: &str, reports: IndexMap<&Entid, &AttributeSet>) { (*self.notify_fn)(key, reports); } } pub trait Command { fn execute(&mut self); } pub struct TxCommand { reports: IndexMap, observers: Weak>>, } impl TxCommand { fn new(observers: &Arc>>, reports: IndexMap) -> Self { TxCommand { reports, observers: Arc::downgrade(observers), } } } impl Command for TxCommand { fn execute(&mut self) { self.observers.upgrade().map(|observers| { for (key, observer) in observers.iter() { let applicable_reports = observer.applicable_reports(&self.reports); if !applicable_reports.is_empty() { observer.notify(&key, applicable_reports); } } }); } } pub struct TxObservationService { observers: Arc>>, executor: Option>>, } impl TxObservationService { pub fn new() -> Self { TxObservationService { observers: Arc::new(IndexMap::new()), executor: None, } } // For testing purposes pub fn is_registered(&self, key: &String) -> bool { self.observers.contains_key(key) } pub fn register(&mut self, key: String, observer: Arc) { Arc::make_mut(&mut self.observers).insert(key, observer); } pub fn deregister(&mut self, key: &String) { Arc::make_mut(&mut self.observers).remove(key); } pub fn has_observers(&self) -> bool { !self.observers.is_empty() } pub fn in_progress_did_commit(&mut self, txes: IndexMap) { // Don't spawn a thread only to say nothing. if !self.has_observers() { return; } let executor = self.executor.get_or_insert_with(|| { let (tx, rx): (Sender>, Receiver>) = channel(); let mut worker = CommandExecutor::new(rx); thread::spawn(move || { worker.main(); }); tx }); let cmd = Box::new(TxCommand::new(&self.observers, txes)); executor.send(cmd).unwrap(); } } impl Drop for TxObservationService { fn drop(&mut self) { self.executor = None; } } pub struct InProgressObserverTransactWatcher { collected_attributes: AttributeSet, pub txes: IndexMap, } impl InProgressObserverTransactWatcher { pub fn new() -> InProgressObserverTransactWatcher { InProgressObserverTransactWatcher { collected_attributes: Default::default(), txes: Default::default(), } } } impl TransactWatcher for InProgressObserverTransactWatcher { fn datom(&mut self, _op: OpType, _e: Entid, a: Entid, _v: &TypedValue) { self.collected_attributes.insert(a); } fn done(&mut self, t: &Entid, _schema: &Schema) -> Result<()> { let collected_attributes = ::std::mem::replace(&mut self.collected_attributes, Default::default()); self.txes.insert(*t, collected_attributes); Ok(()) } } struct CommandExecutor { receiver: Receiver>, } impl CommandExecutor { fn new(rx: Receiver>) -> Self { CommandExecutor { receiver: rx, } } fn main(&mut self) { loop { match self.receiver.recv() { Err(RecvError) => { // "The recv operation can only fail if the sending half of a channel (or // sync_channel) is disconnected, implying that no further messages will ever be // received." // No need to log here. return }, Ok(mut cmd) => { cmd.execute() }, } } } }