1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Re-export Identity to the rest of the crate
pub use ratman::Identity;

use crate::{
    api::{Contacts, Messages, Services, Users},
    auth::AuthStore,
    contacts::ContactStore,
    discover::Discovery,
    messages::MsgStore,
    security::Sec,
    services::ServiceRegistry,
    users::{Announcer, UserStore},
};

use alexandria::{Builder, Library};
use ratman::Router;
use std::{path::Path, sync::Arc};
use tracing::{error, info};

/// An atomic reference counted pointer to a running libqaul instance
pub type QaulRef = Arc<Qaul>;

/// Primary context structure for `libqaul`
///
/// Handles user state, secret storage, network state,
/// I/O and services. Check `api` for the extended
/// service API
///
/// ## Bootstrapping
///
/// Starting an instance of `libqaul` requires several steps.
/// For one, it needs to be initialised with a valid config
/// for the routing-layer (`RATMAN`). This requires choosing
/// of network backends and client configuration.
///
/// Secondly, `libqaul` by itself does very little, except handle
/// service requests. The service API exposes various workloads
/// available, but the consuming services also need to be configured,
/// externally to `libqaul` and this instance.
///
/// A bootstrapping procedure should thus look as follows:
///
/// 1. RATMAN + netmod initialisation
/// 2. `libqaul` startup (this struct, call `new(...)`)
/// 3. Initialise services with a `libqaul` instance reference
/// 4. Your application is now ready for use
#[derive(Clone)]
pub struct Qaul {
    /// Store available user profile data
    pub(crate) users: UserStore,

    /// A user profile changes service announcer
    // TODO: this might work better as part of the user-store?
    pub(crate) announcer: Arc<Announcer>,

    /// Handles user tokens and pw hashes
    pub(crate) auth: AuthStore,

    /// Handles user-local contact books
    pub(crate) contacts: ContactStore,

    /// Provide a persistent interface to query messages
    pub(crate) messages: MsgStore,

    /// An ephemeral (non persistent) store for external services
    pub(crate) services: ServiceRegistry,

    /// A reference to the underlying routing code
    pub(crate) router: Arc<Router>,

    /// A security subsystem
    pub(crate) sec: Arc<Sec>,

    /// Main library handle for storage
    pub(crate) store: Arc<Library>,
}

impl Qaul {
    /// This function exists entirely for doc tests
    #[doc(hidden)]
    #[allow(warnings)]
    #[cfg(feature = "testing")]
    pub fn dummy() -> QaulRef {
        use tempfile;
        let router = Router::new();
        let temp = tempfile::tempdir().unwrap();
        let store = Builder::new().build().unwrap();

        Arc::new(Self {
            router,
            users: UserStore::new(Arc::clone(&store)),
            announcer: Announcer::new(),
            auth: AuthStore::new(),
            contacts: ContactStore::new(),
            messages: MsgStore::new(Arc::clone(&store)),
            services: ServiceRegistry::new(Arc::clone(&store)),
            sec: Arc::new(Sec::new()),
            store,
        })
    }

    /// Get access to the inner Router
    // TODO: figure out a better way of doing this!
    #[doc(hidden)]
    pub fn router(&self) -> &Router {
        &self.router
    }

    /// Create new qaul context, with pre-initialised `Router`
    ///
    /// This function sets up discovery and API handler threads, as
    /// well as local storage. Stopping a qaul instance is currently
    /// not possible (woops). This call is non-blocking and assumes
    /// that the main thread will take over execution of some other
    /// application loop so to enable further API abstractions to hook
    /// into the service API.
    #[tracing::instrument(skip(router), level = "info")]
    pub fn new(router: Arc<Router>) -> QaulRef {
        // let store = Builder::inspect_path(store_path.into(), "").map_or_else(
        //     |b| match b.build() {
        //         Ok(s) => {
        //             info!("Creating new backing store");
        //             s
        //         },
        //         Err(e) => {
        //             error!("Failed to create backing store: {}", e.to_string());
        //             std::process::exit(2);
        //         }
        //     },
        //     |s| {
        //         info!("Loading existing store from disk");
        //         s
        //     },
        // );

        let store = Builder::new().build().unwrap();

        let q = Arc::new(Self {
            router: Arc::clone(&router),
            users: UserStore::new(Arc::clone(&store)),
            announcer: Announcer::new(),
            auth: AuthStore::new(),
            contacts: ContactStore::new(),
            messages: MsgStore::new(Arc::clone(&store)),
            services: ServiceRegistry::new(Arc::clone(&store)),
            sec: Arc::new(Sec::new()),
            store,
        });

        // TODO: Where to store this?!
        Discovery::start(Arc::clone(&q), router);
        q
    }

    /// Get messages function scope
    pub fn messages(&self) -> Messages {
        Messages { q: self }
    }

    /// Get users function scope
    pub fn users(&self) -> Users {
        Users { q: self }
    }

    /// Get contact book function scope
    pub fn contacts(&self) -> Contacts {
        Contacts { q: self }
    }

    /// Get service management function scope
    pub fn services(&self) -> Services {
        Services { q: self }
    }
}