signer-daemon 0.3.1

Signer daemon package.
Documentation
use std::{collections::HashMap, fs, path::Path, sync::Arc};

use anyhow::Context;
use migration::MigratorTrait;
use sea_orm::{Database, DatabaseConnection};

use signer_core::{SignerFsStore, SignerUser};
use tokio::sync::RwLock;

use crate::{
  ChatStore, CrdtAdapterController, CrdtEventController, MessageStore,
  RemoteController, ServerStore, DeltaBoxNotifier, UserStore,
  signer_remote::SignerRemote,
};

pub struct SignerDaemon {
  pub core: SignerDaemonCore,
  pub state: SignerDaemonState,
  pub option: SignerDaemonOption,
  pub controller: SignerDaemonController,
  pub store: SignerDaemonStore,
}

#[derive(Debug, Clone)]
pub struct SignerDaemonCore {
  pub db: DatabaseConnection,
  pub peer: String,
  pub prv_key: String,
  pub pub_key: String,
  pub change_notifier: DeltaBoxNotifier,
}

pub struct SignerDaemonState {
  pub user: SignerUser,
  pub remotes: HashMap<String, SignerRemote>,
  pub daemon: Option<Arc<RwLock<SignerDaemon>>>,
}

#[derive(Debug, Clone)]
pub struct SignerDaemonOption {
  pub(crate) user_file_path: Option<String>,
}

pub struct SignerDaemonController {
  pub crdt_event: CrdtEventController,
  pub crdt_adapter: CrdtAdapterController,
  pub remote: RemoteController,
}

pub struct SignerDaemonStore {
  pub chat: ChatStore,
  pub message: MessageStore,
  pub user: UserStore,
  pub server: ServerStore,
}

impl SignerDaemon {
  pub async fn from_memory(
    user: &SignerUser,
    peer: &str,
  ) -> anyhow::Result<Arc<RwLock<Self>>> {
    let db = Database::connect("sqlite::memory:").await?;

    migration::Migrator::up(&db, None)
      .await
      .context("running migration failed")?;

    let core = SignerDaemonCore {
      db,
      peer: peer.to_string(),
      prv_key: user.prv_key.clone(),
      pub_key: user.public.pub_key.clone(),
      change_notifier: DeltaBoxNotifier::new(),
    };
    let option = SignerDaemonOption {
      user_file_path: None,
    };
    let daemon = Self {
      core: core.clone(),
      state: SignerDaemonState {
        user: user.clone(),
        remotes: HashMap::new(),
        daemon: None,
      },
      option: option.clone(),
      controller: SignerDaemonController {
        crdt_event: CrdtEventController::new(core.clone()),
        crdt_adapter: CrdtAdapterController::new(core.clone()),
        remote: RemoteController::new(core.clone()),
      },
      store: SignerDaemonStore {
        chat: ChatStore::new(core.clone()),
        message: MessageStore::new(core.clone()),
        user: UserStore::new(core.clone(), option.clone()),
        server: ServerStore::new(core.clone()),
      },
    };

    daemon.start().await
  }

  pub async fn from_path(
    user: &SignerUser,
    path: &str,
    user_file_name: &str,
  ) -> anyhow::Result<Arc<RwLock<Self>>> {
    let db_url = format!("sqlite://{}/signer.sqlite3?mode=rwc", path);
    let db = Database::connect(&db_url).await?;

    migration::Migrator::up(&db, None)
      .await
      .context("running migration failed")?;

    let peer = match fs::read_to_string(Path::new(path).join("peer")) {
      Ok(val) => val,
      Err(_) => {
        let new_peer = uuid::Uuid::new_v4().to_string();
        fs::write(Path::new(path).join("peer"), &new_peer)?;

        new_peer
      }
    };

    let core = SignerDaemonCore {
      db,
      peer: peer.clone(),
      prv_key: user.prv_key.clone(),
      pub_key: user.public.pub_key.clone(),
      change_notifier: DeltaBoxNotifier::new(),
    };
    let option = SignerDaemonOption {
      user_file_path: Some(
        Path::new(path)
          .join(user_file_name)
          .to_str()
          .unwrap()
          .to_string(),
      ),
    };

    let daemon = Self {
      core: core.clone(),
      state: SignerDaemonState {
        user: user.clone(),
        daemon: None,
        remotes: HashMap::new(),
      },
      option: option.clone(),
      controller: SignerDaemonController {
        crdt_event: CrdtEventController::new(core.clone()),
        crdt_adapter: CrdtAdapterController::new(core.clone()),
        remote: RemoteController::new(core.clone()),
      },
      store: SignerDaemonStore {
        chat: ChatStore::new(core.clone()),
        message: MessageStore::new(core.clone()),
        server: ServerStore::new(core.clone()),
        user: UserStore::new(core.clone(), option.clone()),
      },
    };
    daemon.start().await
  }

  pub async fn from_sfs(
    user: &SignerUser,
    sfs: &SignerFsStore,
  ) -> anyhow::Result<Arc<RwLock<Self>>> {
    SignerDaemon::from_path(
      user,
      sfs.root.join(&user.public.pub_key).to_str().unwrap(),
      "user.json",
    )
    .await
  }

  pub(crate) async fn start(self) -> anyhow::Result<Arc<RwLock<Self>>> {
    let daemon = Arc::new(RwLock::new(self));

    {
      let daemon_lock = daemon.clone();
      let daemon = &mut *daemon.write().await;
      daemon.state.daemon = Some(daemon_lock);
      daemon.controller.crdt_adapter.apply_all().await?;
      daemon
        .controller
        .remote
        .reconcile(&mut daemon.state)
        .await?;
      daemon.sync_user_public().await?;
    }

    Ok(daemon)
  }

  pub async fn finalize(&mut self) -> anyhow::Result<()> {
    tracing::info!("closing database {}", self.state.user.public.pub_key);

    for i in self.state.remotes.values_mut() {
      i.close().await;
    }
    self.core.db.close_by_ref().await?;

    Ok(())
  }
}