Skip to content

Commit

Permalink
refactor: Use ClientBuilder pattern in SDK FFI
Browse files Browse the repository at this point in the history
Co-authored-by: Jonas Platte <jplatte@matrix.org>
  • Loading branch information
Anderas and jplatte authored Jun 29, 2022
1 parent 464bc43 commit 3c6d159
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 85 deletions.
21 changes: 6 additions & 15 deletions bindings/apple/MatrixRustSDKTests/MatrixRustSDKTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,14 @@ import XCTest

class MatrixRustSDKTests: XCTestCase {

static var client: Client!

override class func setUp() {
client = try! guestClient(basePath: basePath, homeserver: "https://matrix.org")
}

func testClientProperties() {
XCTAssertTrue(Self.client.isGuest())

XCTAssertNotNil(try? Self.client.restoreToken())
XCTAssertNotNil(try? Self.client.deviceId())
XCTAssertNotNil(try? Self.client.displayName())
}

func testReadOnlyFileSystemError() {
do {
let _ = try loginNewClient(basePath: "", username: "test", password: "test")
let client = try ClientBuilder()
.basePath(path: "")
.username(username: "@test:domain")
.build()

try client.login(username: "@test:domain", password: "test")
} catch ClientError.Generic(let message) {
XCTAssertNotNil(message.range(of: "Read-only file system"))
} catch {
Expand Down
31 changes: 22 additions & 9 deletions bindings/matrix-sdk-ffi/src/api.udl
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
namespace sdk {
[Throws=ClientError]
Client login_new_client(string base_path, string username, string password);

[Throws=ClientError]
Client guest_client(string base_path, string homeserver);

[Throws=ClientError]
Client login_with_token(string base_path, string restore_token);

MediaSource media_source_from_url(string url);
MessageEventContent message_event_content_from_markdown(string md);
string gen_transaction_id();
Expand All @@ -22,9 +13,31 @@ callback interface ClientDelegate {
void did_receive_sync_update();
};

interface ClientBuilder {
constructor();

[Self=ByArc]
ClientBuilder base_path(string path);

[Self=ByArc]
ClientBuilder username(string username);

[Self=ByArc]
ClientBuilder homeserver_url(string url);

[Throws=ClientError, Self=ByArc]
Client build();
};

interface Client {
void set_delegate(ClientDelegate? delegate);

[Throws=ClientError]
void login(string username, string password);

[Throws=ClientError]
void restore_login(string restore_token);

void start_sync();

[Throws=ClientError]
Expand Down
17 changes: 17 additions & 0 deletions bindings/matrix-sdk-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ impl Client {
}
}

pub fn login(&self, username: String, password: String) -> anyhow::Result<()> {
RUNTIME.block_on(async move {
self.client.login_username(&username, &password).send().await?;
Ok(())
})
}

pub fn restore_login(&self, restore_token: String) -> anyhow::Result<()> {
let RestoreToken { session, homeurl: _, is_guest: _ } =
serde_json::from_str(&restore_token)?;

RUNTIME.block_on(async move {
self.client.restore_login(session).await?;
Ok(())
})
}

pub fn set_delegate(&self, delegate: Option<Box<dyn ClientDelegate>>) {
*self.delegate.write() = delegate;
}
Expand Down
87 changes: 87 additions & 0 deletions bindings/matrix-sdk-ffi/src/client_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use std::{fs, path::PathBuf, sync::Arc};

use anyhow::Context;
use matrix_sdk::{
ruma::UserId, store::make_store_config, Client as MatrixClient,
ClientBuilder as MatrixClientBuilder,
};
use sanitize_filename_reader_friendly::sanitize;

use super::{client::Client, ClientState, RUNTIME};

#[derive(Clone)]
pub struct ClientBuilder {
base_path: Option<String>,
username: Option<String>,
homeserver_url: Option<String>,
inner: MatrixClientBuilder,
}

impl ClientBuilder {
pub fn new() -> Self {
Self {
base_path: None,
username: None,
homeserver_url: None,
inner: MatrixClient::builder().user_agent("rust-sdk-ios"),
}
}

pub fn base_path(self: Arc<Self>, path: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.base_path = Some(path);
Arc::new(builder)
}

pub fn username(self: Arc<Self>, username: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.username = Some(username);
Arc::new(builder)
}

pub fn homeserver_url(self: Arc<Self>, url: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.homeserver_url = Some(url);
Arc::new(builder)
}

pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<Client>> {
let builder = unwrap_or_clone_arc(self);

let base_path = builder.base_path.context("Base path was not set")?;
let username = builder
.username
.context("Username to determine homeserver and home path was not set")?;

// Determine store path
let data_path = PathBuf::from(base_path).join(sanitize(&username));
fs::create_dir_all(&data_path)?;
let store_config = make_store_config(&data_path, None)?;

let mut inner_builder = builder.inner.store_config(store_config);

// Determine server either from explicitly set homeserver or from userId
if let Some(homeserver_url) = builder.homeserver_url {
inner_builder = inner_builder.homeserver_url(homeserver_url);
} else {
let user = UserId::parse(username)?;
inner_builder = inner_builder.server_name(user.server_name());
}

RUNTIME.block_on(async move {
let client = inner_builder.build().await?;
let c = Client::new(client, ClientState::default());
Ok(Arc::new(c))
})
}
}

impl Default for ClientBuilder {
fn default() -> Self {
Self::new()
}
}

fn unwrap_or_clone_arc<T: Clone>(arc: Arc<T>) -> T {
Arc::try_unwrap(arc).unwrap_or_else(|x| (*x).clone())
}
64 changes: 3 additions & 61 deletions bindings/matrix-sdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@

pub mod backward_stream;
pub mod client;
pub mod client_builder;
pub mod messages;
pub mod room;
mod uniffi_api;

use std::{fs, path, sync::Arc};

use client::Client;
use matrix_sdk::{store::make_store_config, Client as MatrixClient, ClientBuilder, Session};
use client_builder::ClientBuilder;
use matrix_sdk::Session;
use once_cell::sync::Lazy;
use sanitize_filename_reader_friendly::sanitize;
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;
pub use uniffi_api::*;
Expand All @@ -25,63 +24,6 @@ pub use matrix_sdk::ruma::{api::client::account::register, UserId};

pub use self::{backward_stream::*, client::*, messages::*, room::*};

pub fn guest_client(base_path: String, homeurl: String) -> anyhow::Result<Arc<Client>> {
let builder = new_client_builder(base_path, homeurl.clone())?.homeserver_url(&homeurl);
let mut guest_registration = register::v3::Request::new();
guest_registration.kind = register::RegistrationKind::Guest;
RUNTIME.block_on(async move {
let client = builder.build().await?;
let register = client.register(guest_registration).await?;
let session = Session {
access_token: register.access_token.expect("no access token given"),
user_id: register.user_id,
device_id: register.device_id.clone().expect("device ID is given by server"),
};
client.restore_login(session).await?;
let c = Client::new(client, ClientState { is_guest: true, ..ClientState::default() });
Ok(Arc::new(c))
})
}

pub fn login_with_token(base_path: String, restore_token: String) -> anyhow::Result<Arc<Client>> {
let RestoreToken { session, homeurl, is_guest } = serde_json::from_str(&restore_token)?;
let builder = new_client_builder(base_path, session.user_id.to_string())?
.homeserver_url(&homeurl)
.user_id(&session.user_id);
// First we need to log in.
RUNTIME.block_on(async move {
let client = builder.build().await?;
client.restore_login(session).await?;
let c = Client::new(client, ClientState { is_guest, ..ClientState::default() });
Ok(Arc::new(c))
})
}

pub fn login_new_client(
base_path: String,
username: String,
password: String,
) -> anyhow::Result<Arc<Client>> {
let builder = new_client_builder(base_path, username.clone())?;
let user = UserId::parse(username)?;
// First we need to log in.
RUNTIME.block_on(async move {
let client = builder.user_id(&user).build().await?;
client.login_username(user.as_str(), &password).send().await?;
let c = Client::new(client, ClientState { is_guest: false, ..ClientState::default() });
Ok(Arc::new(c))
})
}

fn new_client_builder(base_path: String, home: String) -> anyhow::Result<ClientBuilder> {
let data_path = path::PathBuf::from(base_path).join(sanitize(&home));

fs::create_dir_all(&data_path)?;
let store_config = make_store_config(&data_path, None)?;

Ok(MatrixClient::builder().user_agent("rust-sdk-ios").store_config(store_config))
}

#[derive(Default, Debug)]
pub struct ClientState {
is_guest: bool,
Expand Down

0 comments on commit 3c6d159

Please sign in to comment.