mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 09:36:17 +08:00
feat: add basic tauri client app
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
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}}};
|
||||
|
||||
pub fn invoke_handler() -> impl Fn(tauri::Invoke) + Send + Sync + 'static {
|
||||
tauri::generate_handler![update_y_document, create_workspace, put_blob, get_blob]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use bytes::Bytes;
|
||||
use futures::{
|
||||
stream::{self},
|
||||
StreamExt,
|
||||
};
|
||||
|
||||
use ipc_types::blob::{GetBlob, PutBlob};
|
||||
use jwst::BlobStorage;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn put_blob<'s>(
|
||||
state: tauri::State<'s, AppState>,
|
||||
parameters: PutBlob,
|
||||
) -> Result<String, String> {
|
||||
let blob_storage = &state.0.lock().await.blob_storage;
|
||||
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()),
|
||||
stream::iter::<Vec<Bytes>>(vec![Bytes::from(parameters.blob)]),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(path)
|
||||
} else {
|
||||
Err("Failed to create".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_blob<'s>(
|
||||
state: tauri::State<'s, AppState>,
|
||||
parameters: GetBlob,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
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 {
|
||||
// Read all of the chunks into a vector.
|
||||
let mut stream_contents = Vec::new();
|
||||
let mut error_message = "".to_string();
|
||||
while let Some(chunk) = file_stream.next().await {
|
||||
match chunk {
|
||||
Ok(chunk_bytes) => stream_contents.extend_from_slice(&chunk_bytes),
|
||||
Err(err) => {
|
||||
error_message = format!(
|
||||
"Failed to read blob file {}/{} from stream, error: {}",
|
||||
workspace_id.to_string(),
|
||||
id,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if error_message.len() > 0 {
|
||||
return Err(error_message);
|
||||
}
|
||||
Ok(stream_contents)
|
||||
} else {
|
||||
Err(format!(
|
||||
"Failed to read blob file {}/{} ",
|
||||
workspace_id.to_string(),
|
||||
id
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use ipc_types::{document::YDocumentUpdate, workspace::CreateWorkspace};
|
||||
use jwst::{DocStorage, Workspace};
|
||||
use lib0::any::Any;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_workspace<'s>(
|
||||
state: tauri::State<'s, AppState>,
|
||||
parameters: CreateWorkspace,
|
||||
) -> Result<bool, String> {
|
||||
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
|
||||
.0
|
||||
.lock()
|
||||
.await
|
||||
.doc_storage
|
||||
.write_doc(parameters.id, workspace.doc())
|
||||
.await
|
||||
{
|
||||
Err(error_message.to_string())
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update_y_document(parameters: YDocumentUpdate) -> Result<bool, String> {
|
||||
Ok(true)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
mod commands;
|
||||
mod state;
|
||||
use state::AppState;
|
||||
use tokio::sync::Mutex;
|
||||
use tauri::TitleBarStyle;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tauri::async_runtime::set(tokio::runtime::Handle::current());
|
||||
let preload = include_str!("../../public/preload/index.js");
|
||||
tauri::Builder::default()
|
||||
.manage(AppState(Mutex::new(
|
||||
state::AppStateRaw::new().await.unwrap(),
|
||||
)))
|
||||
// manually create window here, instead of in the tauri.conf.json, to add `initialization_script` here
|
||||
.setup(move |app| {
|
||||
let _window =
|
||||
tauri::WindowBuilder::new(app, "label", tauri::WindowUrl::App("index.html".into()))
|
||||
.title("AFFiNE")
|
||||
.inner_size(1000.0, 800.0)
|
||||
.title_bar_style(TitleBarStyle::Overlay)
|
||||
.hidden_title(true)
|
||||
.initialization_script(&preload)
|
||||
.build()?;
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(commands::invoke_handler())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use jwst_storage::{BlobFsStorage, DBContext, DocFsStorage};
|
||||
use std::path::Path;
|
||||
use tauri::api::path::desktop_dir;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct AppStateRaw {
|
||||
pub blob_storage: BlobFsStorage,
|
||||
pub doc_storage: DocFsStorage,
|
||||
pub metadata_db: DBContext,
|
||||
}
|
||||
|
||||
impl AppStateRaw {
|
||||
pub async fn new() -> Option<AppStateRaw> {
|
||||
let doc_env = Path::new(&desktop_dir()?.into_os_string())
|
||||
.join("affine-dev")
|
||||
.join("doc");
|
||||
let blob_env = Path::new(&(desktop_dir()?.into_os_string()))
|
||||
.join("affine-dev")
|
||||
.join("blob");
|
||||
let db_env = format!(
|
||||
"sqlite://{}?mode=rwc",
|
||||
Path::new(&(desktop_dir()?.into_os_string()))
|
||||
.join("affine-dev")
|
||||
.join("db")
|
||||
.join("metadata.db")
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
Some(Self {
|
||||
doc_storage: DocFsStorage::new(Some(16), 500, Path::new(&doc_env).into()).await,
|
||||
blob_storage: BlobFsStorage::new(Some(16), Path::new(&blob_env).into()).await,
|
||||
metadata_db: DBContext::new(db_env).await,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppState(pub Mutex<AppStateRaw>); // need pub, otherwise will be "field `0` of struct `types::state::AppState` is private"
|
||||
Reference in New Issue
Block a user