feat: add some ipc provider method

This commit is contained in:
Lin Onetwo
2023-01-05 18:38:05 +08:00
parent 1cd17ea31b
commit 646fcea816
15 changed files with 290 additions and 84 deletions

View File

@@ -23,6 +23,16 @@ dependencies = [
"tokio", "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]] [[package]]
name = "adler" name = "adler"
version = "1.0.2" version = "1.0.2"
@@ -1714,6 +1724,18 @@ dependencies = [
"yrs", "yrs",
] ]
[[package]]
name = "jwst-logger"
version = "0.1.0"
dependencies = [
"chrono",
"nu-ansi-term",
"tracing",
"tracing-log",
"tracing-stackdriver",
"tracing-subscriber",
]
[[package]] [[package]]
name = "jwst-storage" name = "jwst-storage"
version = "0.1.0" version = "0.1.0"
@@ -1725,6 +1747,7 @@ dependencies = [
"chrono", "chrono",
"futures", "futures",
"jwst", "jwst",
"jwst-logger",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
@@ -4157,6 +4180,31 @@ dependencies = [
"tracing-core", "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]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.16" version = "0.3.16"
@@ -4167,12 +4215,15 @@ dependencies = [
"nu-ansi-term", "nu-ansi-term",
"once_cell", "once_cell",
"regex", "regex",
"serde",
"serde_json",
"sharded-slab", "sharded-slab",
"smallvec", "smallvec",
"thread_local", "thread_local",
"tracing", "tracing",
"tracing-core", "tracing-core",
"tracing-log", "tracing-log",
"tracing-serde",
] ]
[[package]] [[package]]

View File

@@ -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 * convert serde to jsonschema: https://imfeld.dev/writing/generating_typescript_types_from_rust
* with way to optimize * with way to optimize
@@ -28,6 +28,6 @@ fn main() {
"packages/data-center/src/provider/tauri-ipc/ipc/types", "packages/data-center/src/provider/tauri-ipc/ipc/types",
); );
generate::<YDocumentUpdate>(Path::join(&data_center_ipc_type_folder, "document.json")); generate::<YDocumentUpdate>(Path::join(&data_center_ipc_type_folder, "document.json"));
generate::<CreateWorkspace>(Path::join(&data_center_ipc_type_folder, "workspace.json")); generate::<IWorkspaceParameters>(Path::join(&data_center_ipc_type_folder, "workspace.json"));
generate::<IBlobParameters>(Path::join(&data_center_ipc_type_folder, "blob.json")); generate::<IBlobParameters>(Path::join(&data_center_ipc_type_folder, "blob.json"));
} }

View File

@@ -1,12 +1,15 @@
pub mod blob; pub mod blob;
pub mod workspace; pub mod workspace;
use workspace::{__cmd__create_workspace, __cmd__update_y_document}; use blob::*;
use blob::__cmd__put_blob; use workspace::*;
use blob::__cmd__get_blob;
use crate::{commands::{workspace::{create_workspace, update_y_document}, blob::{put_blob, get_blob}}};
pub fn invoke_handler() -> impl Fn(tauri::Invoke) + Send + Sync + 'static { 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
]
} }

View File

@@ -18,7 +18,7 @@ pub async fn put_blob<'s>(
if let Ok(path) = blob_storage if let Ok(path) = blob_storage
.put_blob( .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 // 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>>(vec![Bytes::from(parameters.blob)]), stream::iter::<Vec<Bytes>>(vec![Bytes::from(parameters.blob)]),
) )
.await .await
@@ -37,7 +37,7 @@ pub async fn get_blob<'s>(
let GetBlob { workspace_id, id } = parameters; let GetBlob { workspace_id, id } = parameters;
// TODO: check user permission? Or just assume there will only be one user // TODO: check user permission? Or just assume there will only be one user
let blob_storage = &state.0.lock().await.blob_storage; 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. // Read all of the chunks into a vector.
let mut stream_contents = Vec::new(); let mut stream_contents = Vec::new();
let mut error_message = "".to_string(); let mut error_message = "".to_string();

View File

@@ -1,5 +1,8 @@
use ipc_types::{document::YDocumentUpdate, workspace::CreateWorkspace}; use ipc_types::{
use jwst::{DocStorage, Workspace}; document::YDocumentUpdate,
workspace::{CreateWorkspace, CreateWorkspaceResult, UpdateWorkspace},
};
use jwst::{DocStorage, Workspace as OctoBaseWorkspace};
use lib0::any::Any; use lib0::any::Any;
use crate::state::AppState; use crate::state::AppState;
@@ -8,28 +11,49 @@ use crate::state::AppState;
pub async fn create_workspace<'s>( pub async fn create_workspace<'s>(
state: tauri::State<'s, AppState>, state: tauri::State<'s, AppState>,
parameters: CreateWorkspace, parameters: CreateWorkspace,
) -> Result<bool, String> { ) -> Result<CreateWorkspaceResult, String> {
let workspace = Workspace::new(parameters.id.to_string()); match &state
workspace.with_trx(|mut workspace_transaction| { .0
// TODO: why this Any here? .lock()
workspace_transaction.set_metadata("name", Any::String(parameters.name.into_boxed_str())); .await
workspace_transaction.set_metadata( .metadata_db
"avatar", .create_normal_workspace(parameters.user_id)
Any::String(parameters.avatar.clone().into_boxed_str()), .await
); {
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 if let Err(error_message) = &state
.0 .0
.lock() .lock()
.await .await
.doc_storage .doc_storage
.write_doc(parameters.id, workspace.doc()) .write_doc(new_workspace.id, workspace_doc.doc())
.await .await
{ {
Err(error_message.to_string()) Err(error_message.to_string())
} else { } else {
Ok(true) 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<bool, String> {
// No thing to update now. The avatar is update in YDoc using websocket or yrs.update
Ok(true)
} }
#[tauri::command] #[tauri::command]

View File

@@ -3,13 +3,13 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct PutBlob { pub struct PutBlob {
pub workspace_id: u64, pub workspace_id: String,
pub blob: Vec<u8>, pub blob: Vec<u8>,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct GetBlob { pub struct GetBlob {
pub workspace_id: u64, pub workspace_id: String,
pub id: String, pub id: String,
} }

View File

@@ -3,7 +3,29 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CreateWorkspace { 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 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),
} }

View File

@@ -2,6 +2,7 @@
import type { Workspace } from '@blocksuite/store'; import type { Workspace } from '@blocksuite/store';
import type { Apis, Logger, InitialParams, ConfigStore } from './index'; import type { Apis, Logger, InitialParams, ConfigStore } from './index';
import type { BlobURL } from '@blocksuite/store/dist/blob/types';
export class BaseProvider { export class BaseProvider {
static id = 'base'; static id = 'base';
@@ -41,8 +42,10 @@ export class BaseProvider {
throw Error('Not implemented: initData'); throw Error('Not implemented: initData');
} }
// should return a blob url /**
async getBlob(_id: string): Promise<string | null> { * should return a blob url
*/
async getBlob(_id: string): Promise<BlobURL | null> {
throw Error('Not implemented: getBlob'); throw Error('Not implemented: getBlob');
} }

View File

@@ -1,35 +1,48 @@
import type { BlobStorage } from '@blocksuite/store'; import * as Y from 'yjs';
import assert from 'assert'; import assert from 'assert';
import type { ConfigStore, InitialParams } from '../index.js'; import type { ConfigStore, InitialParams } from '../index.js';
import { BaseProvider } from '../base.js'; import { LocalProvider } from '../local/index.js';
import { IndexedDBProvider } from './indexeddb.js'; import * as ipcMethods from './ipc/methods.js';
export class LocalProvider extends BaseProvider { export class TauriIPCProvider extends LocalProvider {
static id = 'local'; static id = 'local';
private _blobs!: BlobStorage; #ipc = ipcMethods;
private _idb?: IndexedDBProvider = undefined;
constructor() {
super();
}
async init(params: InitialParams) { async init(params: InitialParams) {
super.init(params); super.init(params);
const blobs = await this._workspace.blobs;
assert(blobs);
this._blobs = blobs;
} }
async initData() { async initData() {
assert(this._workspace.room); assert(this._workspace.room);
this._logger('Loading local data'); this._logger('Loading local data');
this._idb = new IndexedDBProvider( const { doc, room } = this._workspace;
this._workspace.room, doc.on(
this._workspace.doc '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'); this._logger('Local data loaded');
await this._globalConfig.set(this._workspace.room, true); await this._globalConfig.set(this._workspace.room, true);
@@ -37,25 +50,37 @@ export class LocalProvider extends BaseProvider {
async clear() { async clear() {
await super.clear(); await super.clear();
await this._blobs.clear();
await this._idb?.clearData();
await this._globalConfig.delete(this._workspace.room!);
} }
async destroy(): Promise<void> { async destroy(): Promise<void> {
super.destroy(); super.destroy();
await this._idb?.destroy();
} }
async getBlob(id: string): Promise<string | null> { async getBlob(id: string) {
return this._blobs.get(id); 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<string> { async setBlob(blob: Blob) {
return this._blobs.set(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<ConfigStore<boolean>> config: Readonly<ConfigStore<boolean>>
): Promise<Map<string, boolean> | undefined> { ): Promise<Map<string, boolean> | undefined> {
const entries = await config.entries(); const entries = await config.entries();

View File

@@ -1,6 +1,6 @@
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import { YDocumentUpdate } from './types/document'; import { YDocumentUpdate } from './types/document';
import { CreateWorkspace } from './types/workspace'; import { CreateWorkspace, CreateWorkspaceResult } from './types/workspace';
import { GetBlob, PutBlob } from './types/blob'; import { GetBlob, PutBlob } from './types/blob';
export const updateYDocument = async (parameters: YDocumentUpdate) => export const updateYDocument = async (parameters: YDocumentUpdate) =>
@@ -9,7 +9,7 @@ export const updateYDocument = async (parameters: YDocumentUpdate) =>
}); });
export const createWorkspace = async (parameters: CreateWorkspace) => export const createWorkspace = async (parameters: CreateWorkspace) =>
await invoke<boolean>('create_workspace', { await invoke<CreateWorkspaceResult>('create_workspace', {
parameters, parameters,
}); });

View File

@@ -32,9 +32,7 @@
"type": "string" "type": "string"
}, },
"workspace_id": { "workspace_id": {
"type": "integer", "type": "string"
"format": "uint64",
"minimum": 0.0
} }
} }
}, },
@@ -51,9 +49,7 @@
} }
}, },
"workspace_id": { "workspace_id": {
"type": "integer", "type": "string"
"format": "uint64",
"minimum": 0.0
} }
} }
} }

View File

@@ -15,11 +15,11 @@ export type IBlobParameters =
export interface PutBlob { export interface PutBlob {
blob: number[]; blob: number[];
workspace_id: number; workspace_id: string;
[k: string]: unknown; [k: string]: unknown;
} }
export interface GetBlob { export interface GetBlob {
id: string; id: string;
workspace_id: number; workspace_id: string;
[k: string]: unknown; [k: string]: unknown;
} }

View File

@@ -1,18 +1,77 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "CreateWorkspace", "title": "IWorkspaceParameters",
"oneOf": [
{
"type": "object", "type": "object",
"required": ["avatar", "id", "name"], "required": ["CreateWorkspace"],
"properties": { "properties": {
"avatar": { "CreateWorkspace": {
"$ref": "#/definitions/CreateWorkspace"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": ["UpdateWorkspace"],
"properties": {
"UpdateWorkspace": {
"$ref": "#/definitions/UpdateWorkspace"
}
},
"additionalProperties": false
},
{
"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" "type": "string"
}, },
"id": { "user_id": {
"type": "integer", "type": "integer",
"format": "int64" "format": "int32"
}
}
},
"CreateWorkspaceResult": {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": {
"type": "string"
}, },
"name": { "name": {
"type": "string" "type": "string"
} }
} }
},
"UpdateWorkspace": {
"type": "object",
"required": ["id", "public"],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"public": {
"type": "boolean"
}
}
}
}
} }

View File

@@ -5,9 +5,32 @@
* and run json-schema-to-typescript to regenerate this file. * and run json-schema-to-typescript to regenerate this file.
*/ */
export type IWorkspaceParameters =
| {
CreateWorkspace: CreateWorkspace;
}
| {
UpdateWorkspace: UpdateWorkspace;
}
| {
CreateWorkspaceResult: CreateWorkspaceResult;
};
export interface CreateWorkspace { 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; id: number;
public: boolean;
[k: string]: unknown;
}
export interface CreateWorkspaceResult {
id: string;
name: string; name: string;
[k: string]: unknown; [k: string]: unknown;
} }