From 646fcea8165e55f025cddf9d5a309ce18a9c0e09 Mon Sep 17 00:00:00 2001 From: Lin Onetwo Date: Thu, 5 Jan 2023 18:38:05 +0800 Subject: [PATCH] feat: add some ipc provider method --- client-app/src-OctoBase | 2 +- client-app/src-tauri/Cargo.lock | 51 ++++++++++++ .../src-tauri/examples/generate-jsonschema.rs | 4 +- client-app/src-tauri/src/commands.rs | 15 ++-- client-app/src-tauri/src/commands/blob.rs | 4 +- .../src-tauri/src/commands/workspace.rs | 60 +++++++++----- client-app/src-tauri/types/src/blob.rs | 4 +- client-app/src-tauri/types/src/workspace.rs | 26 +++++- packages/data-center/src/provider/base.ts | 7 +- .../src/provider/tauri-ipc/index.ts | 79 +++++++++++------- .../src/provider/tauri-ipc/ipc/methods.ts | 4 +- .../provider/tauri-ipc/ipc/types/blob.json | 8 +- .../src/provider/tauri-ipc/ipc/types/blob.ts | 4 +- .../tauri-ipc/ipc/types/workspace.json | 81 ++++++++++++++++--- .../provider/tauri-ipc/ipc/types/workspace.ts | 25 +++++- 15 files changed, 290 insertions(+), 84 deletions(-) diff --git a/client-app/src-OctoBase b/client-app/src-OctoBase index 4263134e1..d682a717e 160000 --- a/client-app/src-OctoBase +++ b/client-app/src-OctoBase @@ -1 +1 @@ -Subproject commit 4263134e11dac91cf39846059b40a92d1839050f +Subproject commit d682a717e37b1eb202224891340fb39d68d172db diff --git a/client-app/src-tauri/Cargo.lock b/client-app/src-tauri/Cargo.lock index 0dfe4c141..30308a5df 100644 --- a/client-app/src-tauri/Cargo.lock +++ b/client-app/src-tauri/Cargo.lock @@ -23,6 +23,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "adler" version = "1.0.2" @@ -1714,6 +1724,18 @@ dependencies = [ "yrs", ] +[[package]] +name = "jwst-logger" +version = "0.1.0" +dependencies = [ + "chrono", + "nu-ansi-term", + "tracing", + "tracing-log", + "tracing-stackdriver", + "tracing-subscriber", +] + [[package]] name = "jwst-storage" version = "0.1.0" @@ -1725,6 +1747,7 @@ dependencies = [ "chrono", "futures", "jwst", + "jwst-logger", "serde", "serde_json", "serde_repr", @@ -4157,6 +4180,31 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-stackdriver" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff9dd91761e07727176a3dd3a1d64bbb577ea656b7b82fa4be4021832674c49" +dependencies = [ + "Inflector", + "serde", + "serde_json", + "thiserror", + "time 0.3.17", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "tracing-subscriber" version = "0.3.16" @@ -4167,12 +4215,15 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] diff --git a/client-app/src-tauri/examples/generate-jsonschema.rs b/client-app/src-tauri/examples/generate-jsonschema.rs index ba0101a1f..cdb6fb70b 100644 --- a/client-app/src-tauri/examples/generate-jsonschema.rs +++ b/client-app/src-tauri/examples/generate-jsonschema.rs @@ -1,4 +1,4 @@ -use ipc_types::{blob::IBlobParameters, document::YDocumentUpdate, workspace::CreateWorkspace}; +use ipc_types::{blob::IBlobParameters, document::YDocumentUpdate, workspace::IWorkspaceParameters}; /** * convert serde to jsonschema: https://imfeld.dev/writing/generating_typescript_types_from_rust * with way to optimize @@ -28,6 +28,6 @@ fn main() { "packages/data-center/src/provider/tauri-ipc/ipc/types", ); generate::(Path::join(&data_center_ipc_type_folder, "document.json")); - generate::(Path::join(&data_center_ipc_type_folder, "workspace.json")); + generate::(Path::join(&data_center_ipc_type_folder, "workspace.json")); generate::(Path::join(&data_center_ipc_type_folder, "blob.json")); } diff --git a/client-app/src-tauri/src/commands.rs b/client-app/src-tauri/src/commands.rs index c06a5ac9a..be79a7540 100644 --- a/client-app/src-tauri/src/commands.rs +++ b/client-app/src-tauri/src/commands.rs @@ -1,12 +1,15 @@ pub mod blob; pub mod workspace; -use workspace::{__cmd__create_workspace, __cmd__update_y_document}; -use blob::__cmd__put_blob; -use blob::__cmd__get_blob; - -use crate::{commands::{workspace::{create_workspace, update_y_document}, blob::{put_blob, get_blob}}}; +use blob::*; +use workspace::*; pub fn invoke_handler() -> impl Fn(tauri::Invoke) + Send + Sync + 'static { - tauri::generate_handler![update_y_document, create_workspace, put_blob, get_blob] + tauri::generate_handler![ + update_y_document, + create_workspace, + update_workspace, + put_blob, + get_blob + ] } diff --git a/client-app/src-tauri/src/commands/blob.rs b/client-app/src-tauri/src/commands/blob.rs index ea6b753cb..041116d79 100644 --- a/client-app/src-tauri/src/commands/blob.rs +++ b/client-app/src-tauri/src/commands/blob.rs @@ -18,7 +18,7 @@ pub async fn put_blob<'s>( if let Ok(path) = blob_storage .put_blob( // TODO: ask octobase to accept blob directly or wrap/await tauri command to create a real stream, so we don't need to construct stream manually - Some(parameters.workspace_id.to_string()), + Some(parameters.workspace_id), stream::iter::>(vec![Bytes::from(parameters.blob)]), ) .await @@ -37,7 +37,7 @@ pub async fn get_blob<'s>( let GetBlob { workspace_id, id } = parameters; // TODO: check user permission? Or just assume there will only be one user let blob_storage = &state.0.lock().await.blob_storage; - if let Ok(mut file_stream) = blob_storage.get_blob(Some(workspace_id.to_string()), id.clone()).await { + if let Ok(mut file_stream) = blob_storage.get_blob(Some(workspace_id.clone()), id.clone()).await { // Read all of the chunks into a vector. let mut stream_contents = Vec::new(); let mut error_message = "".to_string(); diff --git a/client-app/src-tauri/src/commands/workspace.rs b/client-app/src-tauri/src/commands/workspace.rs index a042467a0..772640f5b 100644 --- a/client-app/src-tauri/src/commands/workspace.rs +++ b/client-app/src-tauri/src/commands/workspace.rs @@ -1,5 +1,8 @@ -use ipc_types::{document::YDocumentUpdate, workspace::CreateWorkspace}; -use jwst::{DocStorage, Workspace}; +use ipc_types::{ + document::YDocumentUpdate, + workspace::{CreateWorkspace, CreateWorkspaceResult, UpdateWorkspace}, +}; +use jwst::{DocStorage, Workspace as OctoBaseWorkspace}; use lib0::any::Any; use crate::state::AppState; @@ -8,30 +11,51 @@ use crate::state::AppState; pub async fn create_workspace<'s>( state: tauri::State<'s, AppState>, parameters: CreateWorkspace, -) -> Result { - let workspace = Workspace::new(parameters.id.to_string()); - workspace.with_trx(|mut workspace_transaction| { - // TODO: why this Any here? - workspace_transaction.set_metadata("name", Any::String(parameters.name.into_boxed_str())); - workspace_transaction.set_metadata( - "avatar", - Any::String(parameters.avatar.clone().into_boxed_str()), - ); - }); - if let Err(error_message) = &state +) -> Result { + match &state .0 .lock() .await - .doc_storage - .write_doc(parameters.id, workspace.doc()) + .metadata_db + .create_normal_workspace(parameters.user_id) .await { - Err(error_message.to_string()) - } else { - Ok(true) + Ok(new_workspace) => { + let workspace_doc = OctoBaseWorkspace::new(new_workspace.id.to_string()); + + workspace_doc.with_trx(|mut workspace_doc_transaction| { + workspace_doc_transaction + .set_metadata("name", Any::String(parameters.name.clone().into_boxed_str())); + }); + if let Err(error_message) = &state + .0 + .lock() + .await + .doc_storage + .write_doc(new_workspace.id, workspace_doc.doc()) + .await + { + Err(error_message.to_string()) + } else { + Ok(CreateWorkspaceResult { + id: new_workspace.id.to_string(), + name: parameters.name, + }) + } + } + Err(error_message) => Err(error_message.to_string()), } } +#[tauri::command] +pub async fn update_workspace<'s>( + state: tauri::State<'s, AppState>, + parameters: UpdateWorkspace, +) -> Result { + // No thing to update now. The avatar is update in YDoc using websocket or yrs.update + Ok(true) +} + #[tauri::command] pub fn update_y_document(parameters: YDocumentUpdate) -> Result { Ok(true) diff --git a/client-app/src-tauri/types/src/blob.rs b/client-app/src-tauri/types/src/blob.rs index 1ed59fcd0..6c85fa1e2 100644 --- a/client-app/src-tauri/types/src/blob.rs +++ b/client-app/src-tauri/types/src/blob.rs @@ -3,13 +3,13 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct PutBlob { - pub workspace_id: u64, + pub workspace_id: String, pub blob: Vec, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct GetBlob { - pub workspace_id: u64, + pub workspace_id: String, pub id: String, } diff --git a/client-app/src-tauri/types/src/workspace.rs b/client-app/src-tauri/types/src/workspace.rs index 9ec22ad3e..fd120d6a4 100644 --- a/client-app/src-tauri/types/src/workspace.rs +++ b/client-app/src-tauri/types/src/workspace.rs @@ -3,7 +3,29 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct CreateWorkspace { - pub id: i64, + // TODO: make all id string, on Octobase side, and rewrite all related tests + pub user_id: i32, + /** + * only set name, avatar is update in datacenter to yDoc directly + */ pub name: String, - pub avatar: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct CreateWorkspaceResult { + pub id: String, + pub name: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct UpdateWorkspace { + pub id: i64, + pub public: bool, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub enum IWorkspaceParameters { + CreateWorkspace(CreateWorkspace), + UpdateWorkspace(UpdateWorkspace), + CreateWorkspaceResult(CreateWorkspaceResult), } diff --git a/packages/data-center/src/provider/base.ts b/packages/data-center/src/provider/base.ts index 95bd309a1..3bba79fde 100644 --- a/packages/data-center/src/provider/base.ts +++ b/packages/data-center/src/provider/base.ts @@ -2,6 +2,7 @@ import type { Workspace } from '@blocksuite/store'; import type { Apis, Logger, InitialParams, ConfigStore } from './index'; +import type { BlobURL } from '@blocksuite/store/dist/blob/types'; export class BaseProvider { static id = 'base'; @@ -41,8 +42,10 @@ export class BaseProvider { throw Error('Not implemented: initData'); } - // should return a blob url - async getBlob(_id: string): Promise { + /** + * should return a blob url + */ + async getBlob(_id: string): Promise { throw Error('Not implemented: getBlob'); } diff --git a/packages/data-center/src/provider/tauri-ipc/index.ts b/packages/data-center/src/provider/tauri-ipc/index.ts index d2ced1b9e..4b0eff399 100644 --- a/packages/data-center/src/provider/tauri-ipc/index.ts +++ b/packages/data-center/src/provider/tauri-ipc/index.ts @@ -1,35 +1,48 @@ -import type { BlobStorage } from '@blocksuite/store'; +import * as Y from 'yjs'; import assert from 'assert'; import type { ConfigStore, InitialParams } from '../index.js'; -import { BaseProvider } from '../base.js'; -import { IndexedDBProvider } from './indexeddb.js'; +import { LocalProvider } from '../local/index.js'; +import * as ipcMethods from './ipc/methods.js'; -export class LocalProvider extends BaseProvider { +export class TauriIPCProvider extends LocalProvider { static id = 'local'; - private _blobs!: BlobStorage; - private _idb?: IndexedDBProvider = undefined; + #ipc = ipcMethods; - constructor() { - super(); - } async init(params: InitialParams) { super.init(params); - - const blobs = await this._workspace.blobs; - assert(blobs); - this._blobs = blobs; } async initData() { assert(this._workspace.room); this._logger('Loading local data'); - this._idb = new IndexedDBProvider( - this._workspace.room, - this._workspace.doc + const { doc, room } = this._workspace; + doc.on( + 'update', + async ( + update: Uint8Array, + _origin: any, + _yDocument: Y.Doc, + _transaction: Y.Transaction + ) => { + try { + // TODO: need handle potential data race when update is frequent? + // TODO: update seems too frequent upon each keydown, why no batching? + const success = await this.#ipc.updateYDocument({ + update: Array.from(update), + room, + }); + if (!success) { + throw new Error( + `YDoc update failed, id: ${this.workspace.meta.id}` + ); + } + } catch (error) { + // TODO: write error log to disk, and add button to open them in settings panel + console.error("#yDocument.on('update'", error); + } + } ); - - await this._idb.whenSynced; this._logger('Local data loaded'); await this._globalConfig.set(this._workspace.room, true); @@ -37,25 +50,37 @@ export class LocalProvider extends BaseProvider { async clear() { await super.clear(); - await this._blobs.clear(); - await this._idb?.clearData(); - await this._globalConfig.delete(this._workspace.room!); } async destroy(): Promise { super.destroy(); - await this._idb?.destroy(); } - async getBlob(id: string): Promise { - return this._blobs.get(id); + async getBlob(id: string) { + const blobArray = await this.#ipc.getBlob({ + workspace_id: this.id, + id, + }); + // Make a Blob from the bytes + const blob = new Blob([new Uint8Array(blobArray)], { type: 'image/bmp' }); + return window.URL.createObjectURL(blob); } - async setBlob(blob: Blob): Promise { - return this._blobs.set(blob); + async setBlob(blob: Blob) { + return this.#ipc.putBlob({ + blob: Array.from(new Uint8Array(await blob.arrayBuffer())), + workspace_id: this.id, + }); } - static async list( + async createWorkspace( + name: string + ): Promise<{ id: string; name: string } | undefined> { + // TODO: get userID here + return this.#ipc.createWorkspace({ name, user_id: 0 }); + } + + async getWorkspaces( config: Readonly> ): Promise | undefined> { const entries = await config.entries(); diff --git a/packages/data-center/src/provider/tauri-ipc/ipc/methods.ts b/packages/data-center/src/provider/tauri-ipc/ipc/methods.ts index a7a4d0a7c..015739de7 100644 --- a/packages/data-center/src/provider/tauri-ipc/ipc/methods.ts +++ b/packages/data-center/src/provider/tauri-ipc/ipc/methods.ts @@ -1,6 +1,6 @@ import { invoke } from '@tauri-apps/api'; import { YDocumentUpdate } from './types/document'; -import { CreateWorkspace } from './types/workspace'; +import { CreateWorkspace, CreateWorkspaceResult } from './types/workspace'; import { GetBlob, PutBlob } from './types/blob'; export const updateYDocument = async (parameters: YDocumentUpdate) => @@ -9,7 +9,7 @@ export const updateYDocument = async (parameters: YDocumentUpdate) => }); export const createWorkspace = async (parameters: CreateWorkspace) => - await invoke('create_workspace', { + await invoke('create_workspace', { parameters, }); diff --git a/packages/data-center/src/provider/tauri-ipc/ipc/types/blob.json b/packages/data-center/src/provider/tauri-ipc/ipc/types/blob.json index f136a8b4d..1eea646b6 100644 --- a/packages/data-center/src/provider/tauri-ipc/ipc/types/blob.json +++ b/packages/data-center/src/provider/tauri-ipc/ipc/types/blob.json @@ -32,9 +32,7 @@ "type": "string" }, "workspace_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "type": "string" } } }, @@ -51,9 +49,7 @@ } }, "workspace_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "type": "string" } } } diff --git a/packages/data-center/src/provider/tauri-ipc/ipc/types/blob.ts b/packages/data-center/src/provider/tauri-ipc/ipc/types/blob.ts index 388d1611d..549d979f9 100644 --- a/packages/data-center/src/provider/tauri-ipc/ipc/types/blob.ts +++ b/packages/data-center/src/provider/tauri-ipc/ipc/types/blob.ts @@ -15,11 +15,11 @@ export type IBlobParameters = export interface PutBlob { blob: number[]; - workspace_id: number; + workspace_id: string; [k: string]: unknown; } export interface GetBlob { id: string; - workspace_id: number; + workspace_id: string; [k: string]: unknown; } diff --git a/packages/data-center/src/provider/tauri-ipc/ipc/types/workspace.json b/packages/data-center/src/provider/tauri-ipc/ipc/types/workspace.json index e73b360e0..ea1a85595 100644 --- a/packages/data-center/src/provider/tauri-ipc/ipc/types/workspace.json +++ b/packages/data-center/src/provider/tauri-ipc/ipc/types/workspace.json @@ -1,18 +1,77 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CreateWorkspace", - "type": "object", - "required": ["avatar", "id", "name"], - "properties": { - "avatar": { - "type": "string" + "title": "IWorkspaceParameters", + "oneOf": [ + { + "type": "object", + "required": ["CreateWorkspace"], + "properties": { + "CreateWorkspace": { + "$ref": "#/definitions/CreateWorkspace" + } + }, + "additionalProperties": false }, - "id": { - "type": "integer", - "format": "int64" + { + "type": "object", + "required": ["UpdateWorkspace"], + "properties": { + "UpdateWorkspace": { + "$ref": "#/definitions/UpdateWorkspace" + } + }, + "additionalProperties": false }, - "name": { - "type": "string" + { + "type": "object", + "required": ["CreateWorkspaceResult"], + "properties": { + "CreateWorkspaceResult": { + "$ref": "#/definitions/CreateWorkspaceResult" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "CreateWorkspace": { + "type": "object", + "required": ["name", "user_id"], + "properties": { + "name": { + "description": "only set name, avatar is update in datacenter to yDoc directly", + "type": "string" + }, + "user_id": { + "type": "integer", + "format": "int32" + } + } + }, + "CreateWorkspaceResult": { + "type": "object", + "required": ["id", "name"], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "UpdateWorkspace": { + "type": "object", + "required": ["id", "public"], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "public": { + "type": "boolean" + } + } } } } diff --git a/packages/data-center/src/provider/tauri-ipc/ipc/types/workspace.ts b/packages/data-center/src/provider/tauri-ipc/ipc/types/workspace.ts index cf60dcbd5..97918f736 100644 --- a/packages/data-center/src/provider/tauri-ipc/ipc/types/workspace.ts +++ b/packages/data-center/src/provider/tauri-ipc/ipc/types/workspace.ts @@ -5,9 +5,32 @@ * and run json-schema-to-typescript to regenerate this file. */ +export type IWorkspaceParameters = + | { + CreateWorkspace: CreateWorkspace; + } + | { + UpdateWorkspace: UpdateWorkspace; + } + | { + CreateWorkspaceResult: CreateWorkspaceResult; + }; + export interface CreateWorkspace { - avatar: string; + /** + * only set name, avatar is update in datacenter to yDoc directly + */ + name: string; + user_id: number; + [k: string]: unknown; +} +export interface UpdateWorkspace { id: number; + public: boolean; + [k: string]: unknown; +} +export interface CreateWorkspaceResult { + id: string; name: string; [k: string]: unknown; }