Ability to open workspace from directory, WorkspaceMeta, and many sync improvements

This commit is contained in:
Gregory Schier
2025-01-08 14:57:13 -08:00
parent 37671a50f2
commit cbc443075a
71 changed files with 1012 additions and 1844 deletions

View File

@@ -16,6 +16,7 @@
"clipboard-manager:allow-read-text",
"dialog:allow-open",
"dialog:allow-save",
"fs:allow-read-dir",
"fs:allow-read-file",
"fs:allow-read-text-file",
{

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"opener:allow-open-url","opener:allow-open-path","opener:allow-default-urls","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","yaak-license:default","yaak-sync:default"]}}
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-dir","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"opener:allow-open-url","opener:allow-open-path","opener:allow-default-urls","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","yaak-license:default","yaak-sync:default"]}}

View File

@@ -5442,16 +5442,6 @@
"type": "string",
"const": "yaak-sync:default"
},
{
"description": "Enables the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-activate"
},
{
"description": "Enables the add command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-add"
},
{
"description": "Enables the apply command without any pre-configured scope.",
"type": "string",
@@ -5463,105 +5453,15 @@
"const": "yaak-sync:allow-calculate"
},
{
"description": "Enables the check command without any pre-configured scope.",
"description": "Enables the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-check"
},
{
"description": "Enables the checkout command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-checkout"
},
{
"description": "Enables the cmd_add command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-add"
},
{
"description": "Enables the cmd_checkout command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-checkout"
},
{
"description": "Enables the cmd_commit command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-commit"
},
{
"description": "Enables the cmd_init command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-init"
},
{
"description": "Enables the cmd_log command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-log"
},
{
"description": "Enables the cmd_status command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-status"
},
{
"description": "Enables the commit command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-commit"
},
{
"description": "Enables the init command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-init"
},
{
"description": "Enables the init_repo command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-init-repo"
},
{
"description": "Enables the initialize command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-initialize"
},
{
"description": "Enables the log command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-log"
},
{
"description": "Enables the status command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-status"
},
{
"description": "Enables the sync command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-sync"
},
{
"description": "Enables the sync_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-sync-fs"
},
{
"description": "Enables the unstage command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-unstage"
"const": "yaak-sync:allow-calculate-fs"
},
{
"description": "Enables the watch command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-watch"
},
{
"description": "Denies the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-activate"
},
{
"description": "Denies the add command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-add"
},
{
"description": "Denies the apply command without any pre-configured scope.",
"type": "string",
@@ -5573,89 +5473,9 @@
"const": "yaak-sync:deny-calculate"
},
{
"description": "Denies the check command without any pre-configured scope.",
"description": "Denies the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-check"
},
{
"description": "Denies the checkout command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-checkout"
},
{
"description": "Denies the cmd_add command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-add"
},
{
"description": "Denies the cmd_checkout command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-checkout"
},
{
"description": "Denies the cmd_commit command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-commit"
},
{
"description": "Denies the cmd_init command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-init"
},
{
"description": "Denies the cmd_log command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-log"
},
{
"description": "Denies the cmd_status command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-status"
},
{
"description": "Denies the commit command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-commit"
},
{
"description": "Denies the init command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-init"
},
{
"description": "Denies the init_repo command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-init-repo"
},
{
"description": "Denies the initialize command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-initialize"
},
{
"description": "Denies the log command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-log"
},
{
"description": "Denies the status command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-status"
},
{
"description": "Denies the sync command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-sync"
},
{
"description": "Denies the sync_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-sync-fs"
},
{
"description": "Denies the unstage command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-unstage"
"const": "yaak-sync:deny-calculate-fs"
},
{
"description": "Denies the watch command without any pre-configured scope.",

View File

@@ -5442,16 +5442,6 @@
"type": "string",
"const": "yaak-sync:default"
},
{
"description": "Enables the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-activate"
},
{
"description": "Enables the add command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-add"
},
{
"description": "Enables the apply command without any pre-configured scope.",
"type": "string",
@@ -5463,105 +5453,15 @@
"const": "yaak-sync:allow-calculate"
},
{
"description": "Enables the check command without any pre-configured scope.",
"description": "Enables the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-check"
},
{
"description": "Enables the checkout command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-checkout"
},
{
"description": "Enables the cmd_add command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-add"
},
{
"description": "Enables the cmd_checkout command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-checkout"
},
{
"description": "Enables the cmd_commit command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-commit"
},
{
"description": "Enables the cmd_init command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-init"
},
{
"description": "Enables the cmd_log command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-log"
},
{
"description": "Enables the cmd_status command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-cmd-status"
},
{
"description": "Enables the commit command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-commit"
},
{
"description": "Enables the init command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-init"
},
{
"description": "Enables the init_repo command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-init-repo"
},
{
"description": "Enables the initialize command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-initialize"
},
{
"description": "Enables the log command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-log"
},
{
"description": "Enables the status command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-status"
},
{
"description": "Enables the sync command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-sync"
},
{
"description": "Enables the sync_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-sync-fs"
},
{
"description": "Enables the unstage command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-unstage"
"const": "yaak-sync:allow-calculate-fs"
},
{
"description": "Enables the watch command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-watch"
},
{
"description": "Denies the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-activate"
},
{
"description": "Denies the add command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-add"
},
{
"description": "Denies the apply command without any pre-configured scope.",
"type": "string",
@@ -5573,89 +5473,9 @@
"const": "yaak-sync:deny-calculate"
},
{
"description": "Denies the check command without any pre-configured scope.",
"description": "Denies the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-check"
},
{
"description": "Denies the checkout command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-checkout"
},
{
"description": "Denies the cmd_add command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-add"
},
{
"description": "Denies the cmd_checkout command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-checkout"
},
{
"description": "Denies the cmd_commit command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-commit"
},
{
"description": "Denies the cmd_init command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-init"
},
{
"description": "Denies the cmd_log command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-log"
},
{
"description": "Denies the cmd_status command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-cmd-status"
},
{
"description": "Denies the commit command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-commit"
},
{
"description": "Denies the init command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-init"
},
{
"description": "Denies the init_repo command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-init-repo"
},
{
"description": "Denies the initialize command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-initialize"
},
{
"description": "Denies the log command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-log"
},
{
"description": "Denies the status command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-status"
},
{
"description": "Denies the sync command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-sync"
},
{
"description": "Denies the sync_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-sync-fs"
},
{
"description": "Denies the unstage command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-unstage"
"const": "yaak-sync:deny-calculate-fs"
},
{
"description": "Denies the watch command without any pre-configured scope.",

View File

@@ -0,0 +1,11 @@
CREATE TABLE workspace_metas
(
id TEXT NOT NULL
PRIMARY KEY,
model TEXT DEFAULT 'workspace_meta' NOT NULL,
workspace_id TEXT NOT NULL
REFERENCES workspaces ON DELETE CASCADE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
setting_sync_dir TEXT
);

View File

@@ -43,7 +43,7 @@ use crate::window_menu::app_menu;
use yaak_models::models::{
CookieJar, Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcConnectionState,
GrpcEvent, GrpcEventType, GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, KeyValue,
ModelType, Plugin, Settings, Workspace,
ModelType, Plugin, Settings, Workspace, WorkspaceMeta,
};
use yaak_models::queries::{
batch_upsert, cancel_pending_grpc_connections, cancel_pending_responses,
@@ -55,14 +55,14 @@ use yaak_models::queries::{
duplicate_http_request, ensure_base_environment, generate_id, generate_model_id,
get_base_environment, get_cookie_jar, get_environment, get_folder, get_grpc_connection,
get_grpc_request, get_http_request, get_http_response, get_key_value_raw,
get_or_create_settings, get_plugin, get_workspace, get_workspace_export_resources,
list_cookie_jars, list_environments, list_folders, list_grpc_connections_for_workspace,
list_grpc_events, list_grpc_requests, list_http_requests, list_http_responses_for_request,
list_http_responses_for_workspace, list_key_values_raw, list_plugins, list_workspaces,
set_key_value_raw, update_response_if_id, update_settings, upsert_cookie_jar,
upsert_environment, upsert_folder, upsert_grpc_connection, upsert_grpc_event,
upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace, BatchUpsertResult,
UpdateSource,
get_or_create_settings, get_or_create_workspace_meta, get_plugin, get_workspace,
get_workspace_export_resources, list_cookie_jars, list_environments, list_folders,
list_grpc_connections_for_workspace, list_grpc_events, list_grpc_requests, list_http_requests,
list_http_responses_for_request, list_http_responses_for_workspace, list_key_values_raw,
list_plugins, list_workspaces, set_key_value_raw, update_response_if_id, update_settings,
upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection,
upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace,
upsert_workspace_meta, BatchUpsertResult, UpdateSource,
};
use yaak_plugins::events::{
BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse,
@@ -1294,6 +1294,16 @@ async fn cmd_update_workspace(workspace: Workspace, w: WebviewWindow) -> Result<
upsert_workspace(&w, workspace, &UpdateSource::Window).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_update_workspace_meta(
workspace_meta: WorkspaceMeta,
w: WebviewWindow,
) -> Result<WorkspaceMeta, String> {
upsert_workspace_meta(&w, workspace_meta, &UpdateSource::Window)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_update_environment(
environment: Environment,
@@ -1497,70 +1507,79 @@ async fn cmd_list_cookie_jars(
}
#[tauri::command]
async fn cmd_list_key_values(w: WebviewWindow) -> Result<Vec<KeyValue>, String> {
list_key_values_raw(&w).await.map_err(|e| e.to_string())
async fn cmd_list_key_values(window: WebviewWindow) -> Result<Vec<KeyValue>, String> {
list_key_values_raw(&window).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_get_environment(id: &str, w: WebviewWindow) -> Result<Environment, String> {
get_environment(&w, id).await.map_err(|e| e.to_string())
async fn cmd_get_environment(id: &str, window: WebviewWindow) -> Result<Environment, String> {
get_environment(&window, id).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_get_workspace(id: &str, w: WebviewWindow) -> Result<Workspace, String> {
get_workspace(&w, id).await.map_err(|e| e.to_string())
async fn cmd_get_workspace(id: &str, window: WebviewWindow) -> Result<Workspace, String> {
get_workspace(&window, id).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_list_http_responses(
workspace_id: &str,
limit: Option<i64>,
w: WebviewWindow,
window: WebviewWindow,
) -> Result<Vec<HttpResponse>, String> {
list_http_responses_for_workspace(&w, workspace_id, limit).await.map_err(|e| e.to_string())
list_http_responses_for_workspace(&window, workspace_id, limit).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_delete_http_response(id: &str, w: WebviewWindow) -> Result<HttpResponse, String> {
delete_http_response(&w, id, &UpdateSource::Window).await.map_err(|e| e.to_string())
async fn cmd_delete_http_response(id: &str, window: WebviewWindow) -> Result<HttpResponse, String> {
delete_http_response(&window, id, &UpdateSource::Window).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_delete_grpc_connection(id: &str, w: WebviewWindow) -> Result<GrpcConnection, String> {
delete_grpc_connection(&w, id, &UpdateSource::Window).await.map_err(|e| e.to_string())
async fn cmd_delete_grpc_connection(
id: &str,
window: WebviewWindow,
) -> Result<GrpcConnection, String> {
delete_grpc_connection(&window, id, &UpdateSource::Window).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_delete_all_grpc_connections(request_id: &str, w: WebviewWindow) -> Result<(), String> {
delete_all_grpc_connections(&w, request_id, &UpdateSource::Window)
async fn cmd_delete_all_grpc_connections(
request_id: &str,
window: WebviewWindow,
) -> Result<(), String> {
delete_all_grpc_connections(&window, request_id, &UpdateSource::Window)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_delete_send_history(workspace_id: &str, w: WebviewWindow) -> Result<(), String> {
delete_all_http_responses_for_workspace(&w, workspace_id, &UpdateSource::Window)
async fn cmd_delete_send_history(workspace_id: &str, window: WebviewWindow) -> Result<(), String> {
delete_all_http_responses_for_workspace(&window, workspace_id, &UpdateSource::Window)
.await
.map_err(|e| e.to_string())?;
delete_all_grpc_connections_for_workspace(&w, workspace_id, &UpdateSource::Window)
delete_all_grpc_connections_for_workspace(&window, workspace_id, &UpdateSource::Window)
.await
.map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
async fn cmd_delete_all_http_responses(request_id: &str, w: WebviewWindow) -> Result<(), String> {
delete_all_http_responses_for_request(&w, request_id, &UpdateSource::Window)
async fn cmd_delete_all_http_responses(
request_id: &str,
window: WebviewWindow,
) -> Result<(), String> {
delete_all_http_responses_for_request(&window, request_id, &UpdateSource::Window)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_list_workspaces(w: WebviewWindow) -> Result<Vec<Workspace>, String> {
let workspaces = list_workspaces(&w).await.expect("Failed to find workspaces");
async fn cmd_list_workspaces(window: WebviewWindow) -> Result<Vec<Workspace>, String> {
let workspaces = list_workspaces(&window).await.expect("Failed to find workspaces");
if workspaces.is_empty() {
let workspace = upsert_workspace(
&w,
&window,
Workspace {
name: "Yaak".to_string(),
setting_follow_redirects: true,
@@ -1577,6 +1596,17 @@ async fn cmd_list_workspaces(w: WebviewWindow) -> Result<Vec<Workspace>, String>
}
}
#[tauri::command]
async fn cmd_get_workspace_meta(
window: WebviewWindow,
workspace_id: &str,
) -> Result<WorkspaceMeta, String> {
let workspace = get_workspace(&window, workspace_id).await.map_err(|e| e.to_string())?;
get_or_create_workspace_meta(&window, &workspace, &UpdateSource::Window)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_new_child_window(
parent_window: WebviewWindow,
@@ -1800,6 +1830,7 @@ pub fn run() {
cmd_get_settings,
cmd_get_sse_events,
cmd_get_workspace,
cmd_get_workspace_meta,
cmd_grpc_go,
cmd_grpc_reflect,
cmd_http_request_actions,
@@ -1839,6 +1870,7 @@ pub fn run() {
cmd_update_http_request,
cmd_update_settings,
cmd_update_workspace,
cmd_update_workspace_meta,
cmd_write_file_dev,
])
.register_uri_scheme_protocol("yaak", |_app, _req| {

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type AnyModel = CookieJar | Environment | Folder | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | Plugin | Settings | KeyValue | Workspace;
export type AnyModel = CookieJar | Environment | Folder | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | Plugin | Settings | KeyValue | Workspace | WorkspaceMeta;
export type Cookie = { raw_cookie: string, domain: CookieDomain, expires: CookieExpires, path: [string, boolean], };
@@ -54,8 +54,12 @@ export type ProxySettingAuth = { user: string, password: string, };
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, telemetry: boolean, theme: string, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, };
export type SyncHistory = { model: "sync_history", id: string, workspaceId: string, createdAt: string, states: Array<SyncState>, checksum: string, relPath: string, syncDir: string, };
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
export type UpdateSource = "sync" | "window" | "plugin" | "background";
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingSyncDir: string | null, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };
export type WorkspaceMeta = { model: "workspace_meta", id: string, workspaceId: string, createdAt: string, updatedAt: string, settingSyncDir: string | null, };

View File

@@ -165,7 +165,6 @@ pub struct Workspace {
#[serde(default = "default_true")]
pub setting_follow_redirects: bool,
pub setting_request_timeout: i32,
pub setting_sync_dir: Option<String>,
}
#[derive(Iden)]
@@ -181,7 +180,6 @@ pub enum WorkspaceIden {
Name,
SettingFollowRedirects,
SettingRequestTimeout,
SettingSyncDir,
SettingValidateCertificates,
}
@@ -198,7 +196,6 @@ impl<'s> TryFrom<&Row<'s>> for Workspace {
description: r.get("description")?,
setting_follow_redirects: r.get("setting_follow_redirects")?,
setting_request_timeout: r.get("setting_request_timeout")?,
setting_sync_dir: r.get("setting_sync_dir")?,
setting_validate_certificates: r.get("setting_validate_certificates")?,
})
}
@@ -216,6 +213,47 @@ impl Workspace {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "models.ts")]
pub struct WorkspaceMeta {
#[ts(type = "\"workspace_meta\"")]
pub model: String,
pub id: String,
pub workspace_id: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub setting_sync_dir: Option<String>,
}
#[derive(Iden)]
pub enum WorkspaceMetaIden {
#[iden = "workspace_metas"]
Table,
Model,
Id,
WorkspaceId,
CreatedAt,
UpdatedAt,
SettingSyncDir,
}
impl<'s> TryFrom<&Row<'s>> for WorkspaceMeta {
type Error = rusqlite::Error;
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
Ok(WorkspaceMeta {
id: r.get("id")?,
workspace_id: r.get("workspace_id")?,
model: r.get("model")?,
created_at: r.get("created_at")?,
updated_at: r.get("updated_at")?,
setting_sync_dir: r.get("setting_sync_dir")?,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "models.ts")]
enum CookieDomain {
@@ -933,6 +971,22 @@ pub struct SyncState {
pub sync_dir: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "models.ts")]
pub struct SyncHistory {
#[ts(type = "\"sync_history\"")]
pub model: String,
pub id: String,
pub workspace_id: String,
pub created_at: NaiveDateTime,
pub states: Vec<SyncState>,
pub checksum: String,
pub rel_path: String,
pub sync_dir: String,
}
#[derive(Iden)]
pub enum SyncStateIden {
#[iden = "sync_states"]
@@ -1030,6 +1084,7 @@ pub enum ModelType {
TypeHttpResponse,
TypePlugin,
TypeWorkspace,
TypeWorkspaceMeta,
TypeSyncState,
}
@@ -1046,6 +1101,7 @@ impl ModelType {
ModelType::TypeHttpResponse => "rs",
ModelType::TypePlugin => "pg",
ModelType::TypeWorkspace => "wk",
ModelType::TypeWorkspaceMeta => "wm",
ModelType::TypeSyncState => "ss",
}
.to_string()
@@ -1068,6 +1124,7 @@ pub enum AnyModel {
Settings(Settings),
KeyValue(KeyValue),
Workspace(Workspace),
WorkspaceMeta(WorkspaceMeta),
}
impl<'de> Deserialize<'de> for AnyModel {

View File

@@ -5,7 +5,8 @@ use crate::models::{
GrpcConnection, GrpcConnectionIden, GrpcConnectionState, GrpcEvent, GrpcEventIden, GrpcRequest,
GrpcRequestIden, HttpRequest, HttpRequestIden, HttpResponse, HttpResponseHeader,
HttpResponseIden, HttpResponseState, KeyValue, KeyValueIden, ModelType, Plugin, PluginIden,
Settings, SettingsIden, SyncState, SyncStateIden, Workspace, WorkspaceIden,
Settings, SettingsIden, SyncState, SyncStateIden, Workspace, WorkspaceIden, WorkspaceMeta,
WorkspaceMetaIden,
};
use crate::plugin::SqliteConnection;
use chrono::NaiveDateTime;
@@ -18,7 +19,7 @@ use sea_query::{Cond, Expr, OnConflict, Order, Query, SqliteQueryBuilder};
use sea_query_rusqlite::RusqliteBinder;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use tauri::{AppHandle, Emitter, Listener, Manager, Runtime, WebviewWindow};
use ts_rs::TS;
@@ -177,6 +178,18 @@ pub async fn list_workspaces<R: Runtime>(mgr: &impl Manager<R>) -> Result<Vec<Wo
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn list_workspace_metas<R: Runtime>(mgr: &impl Manager<R>) -> Result<Vec<WorkspaceMeta>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
.from(WorkspaceMetaIden::Table)
.column(Asterisk)
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn get_workspace<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<Workspace> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -189,6 +202,54 @@ pub async fn get_workspace<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Resul
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn get_workspace_meta<R: Runtime>(
mgr: &impl Manager<R>,
workspace: &Workspace,
) -> Result<Option<WorkspaceMeta>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
.from(WorkspaceMetaIden::Table)
.column(Asterisk)
.cond_where(Expr::col(WorkspaceMetaIden::WorkspaceId).eq(&workspace.id))
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?)
}
pub async fn get_or_create_workspace_meta<R: Runtime>(
window: &WebviewWindow<R>,
workspace: &Workspace,
update_source: &UpdateSource,
) -> Result<WorkspaceMeta> {
let workspace_meta = get_workspace_meta(window, workspace).await?;
if let Some(m) = workspace_meta {
return Ok(m);
}
upsert_workspace_meta(
window,
WorkspaceMeta {
workspace_id: workspace.to_owned().id,
..Default::default()
},
update_source,
)
.await
}
pub async fn exists_workspace<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<bool> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
.from(WorkspaceIden::Table)
.column(Asterisk)
.cond_where(Expr::col(WorkspaceIden::Id).eq(id))
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(sql.as_str())?;
Ok(stmt.exists(&*params.as_params())?)
}
pub async fn upsert_workspace<R: Runtime>(
window: &WebviewWindow<R>,
workspace: Workspace,
@@ -200,56 +261,96 @@ pub async fn upsert_workspace<R: Runtime>(
};
let trimmed_name = workspace.name.trim();
let m: Workspace = {
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::insert()
.into_table(WorkspaceIden::Table)
.columns([
WorkspaceIden::Id,
WorkspaceIden::CreatedAt,
WorkspaceIden::UpdatedAt,
WorkspaceIden::Name,
WorkspaceIden::Description,
WorkspaceIden::SettingFollowRedirects,
WorkspaceIden::SettingRequestTimeout,
WorkspaceIden::SettingSyncDir,
WorkspaceIden::SettingValidateCertificates,
])
.values_panic([
id.as_str().into(),
CurrentTimestamp.into(),
CurrentTimestamp.into(),
trimmed_name.into(),
workspace.description.into(),
workspace.setting_follow_redirects.into(),
workspace.setting_request_timeout.into(),
workspace.setting_sync_dir.into(),
workspace.setting_validate_certificates.into(),
])
.on_conflict(
OnConflict::column(GrpcRequestIden::Id)
.update_columns([
WorkspaceIden::UpdatedAt,
WorkspaceIden::Name,
WorkspaceIden::Description,
WorkspaceIden::SettingRequestTimeout,
WorkspaceIden::SettingFollowRedirects,
WorkspaceIden::SettingRequestTimeout,
WorkspaceIden::SettingSyncDir,
WorkspaceIden::SettingValidateCertificates,
])
.to_owned(),
)
.returning_all()
.build_rusqlite(SqliteQueryBuilder);
let (sql, params) = Query::insert()
.into_table(WorkspaceIden::Table)
.columns([
WorkspaceIden::Id,
WorkspaceIden::CreatedAt,
WorkspaceIden::UpdatedAt,
WorkspaceIden::Name,
WorkspaceIden::Description,
WorkspaceIden::SettingFollowRedirects,
WorkspaceIden::SettingRequestTimeout,
WorkspaceIden::SettingValidateCertificates,
])
.values_panic([
id.as_str().into(),
CurrentTimestamp.into(),
CurrentTimestamp.into(),
trimmed_name.into(),
workspace.description.into(),
workspace.setting_follow_redirects.into(),
workspace.setting_request_timeout.into(),
workspace.setting_validate_certificates.into(),
])
.on_conflict(
OnConflict::column(GrpcRequestIden::Id)
.update_columns([
WorkspaceIden::UpdatedAt,
WorkspaceIden::Name,
WorkspaceIden::Description,
WorkspaceIden::SettingRequestTimeout,
WorkspaceIden::SettingFollowRedirects,
WorkspaceIden::SettingRequestTimeout,
WorkspaceIden::SettingValidateCertificates,
])
.to_owned(),
)
.returning_all()
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(&sql)?;
stmt.query_row(&*params.as_params(), |row| row.try_into())?
};
let mut stmt = db.prepare(&sql)?;
let m: Workspace = stmt.query_row(&*params.as_params(), |row| row.try_into())?;
emit_upserted_model(window, &AnyModel::Workspace(m.to_owned()), update_source);
ensure_base_environment(window, &m.id).await?;
Ok(m)
}
pub async fn upsert_workspace_meta<R: Runtime>(
window: &WebviewWindow<R>,
workspace_meta: WorkspaceMeta,
update_source: &UpdateSource,
) -> Result<WorkspaceMeta> {
let id = match workspace_meta.id.as_str() {
"" => generate_model_id(ModelType::TypeWorkspaceMeta),
_ => workspace_meta.id.to_string(),
};
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::insert()
.into_table(WorkspaceMetaIden::Table)
.columns([
WorkspaceMetaIden::Id,
WorkspaceMetaIden::WorkspaceId,
WorkspaceMetaIden::CreatedAt,
WorkspaceMetaIden::UpdatedAt,
WorkspaceMetaIden::SettingSyncDir,
])
.values_panic([
id.as_str().into(),
workspace_meta.workspace_id.into(),
CurrentTimestamp.into(),
CurrentTimestamp.into(),
workspace_meta.setting_sync_dir.into(),
])
.on_conflict(
OnConflict::column(GrpcRequestIden::Id)
.update_columns([
WorkspaceMetaIden::UpdatedAt,
WorkspaceMetaIden::SettingSyncDir,
])
.to_owned(),
)
.returning_all()
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = db.prepare(&sql)?;
let m: WorkspaceMeta = stmt.query_row(&*params.as_params(), |row| row.try_into())?;
emit_upserted_model(window, &AnyModel::WorkspaceMeta(m.to_owned()), update_source);
Ok(m)
}
@@ -1788,7 +1889,7 @@ pub async fn get_sync_state_for_model<R: Runtime>(
pub async fn list_sync_states_for_workspace<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
sync_dir: PathBuf,
sync_dir: &Path,
) -> Result<Vec<SyncState>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();

View File

@@ -22,4 +22,4 @@ export type HttpResponseState = "initialized" | "connected" | "closed";
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id: string, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingSyncDir: string | null, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };

View File

@@ -20,4 +20,4 @@ export type SyncModel = { "type": "workspace" } & Workspace | { "type": "environ
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingSyncDir: string | null, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };

View File

@@ -1,4 +1,4 @@
const COMMANDS: &[&str] = &["calculate", "apply", "watch"];
const COMMANDS: &[&str] = &["calculate", "calculate_fs", "apply", "watch"];
fn main() {
tauri_plugin::Builder::new(COMMANDS).build();

View File

@@ -1,48 +1,46 @@
import { Channel, invoke } from '@tauri-apps/api/core';
import { emit } from '@tauri-apps/api/event';
import { Workspace } from '@yaakapp-internal/models';
import { useEffect } from 'react';
import { SyncOp } from './bindings/sync';
import { WatchEvent, WatchResult } from './bindings/watch';
export async function calculateSync(workspace: Workspace) {
if (!workspace.settingSyncDir) return;
export async function calculateSync(workspaceId: string, syncDir: string) {
return invoke<SyncOp[]>('plugin:yaak-sync|calculate', {
workspaceId: workspace.id,
dir: workspace.settingSyncDir,
workspaceId,
syncDir,
});
}
export async function applySync(workspace: Workspace, syncOps: SyncOp[]) {
if (!workspace.settingSyncDir) return;
export async function calculateSyncFsOnly(dir: string) {
return invoke<SyncOp[]>('plugin:yaak-sync|calculate_fs', { dir });
}
export async function applySync(workspaceId: string, syncDir: string, syncOps: SyncOp[]) {
return invoke<void>('plugin:yaak-sync|apply', {
workspaceId: workspace.id,
dir: workspace.settingSyncDir,
workspaceId,
syncDir,
syncOps: syncOps,
});
}
export function useWatchWorkspace(workspace: Workspace | null, callback: (e: WatchEvent) => void) {
useEffect(() => {
if (workspace == null) return;
if (!workspace.settingSyncDir) return;
export function watchWorkspaceFiles(
workspaceId: string,
syncDir: string,
callback: (e: WatchEvent) => void,
) {
const channel = new Channel<WatchEvent>();
channel.onmessage = callback;
const promise = invoke<WatchResult>('plugin:yaak-sync|watch', {
workspaceId,
syncDir,
channel,
});
const channel = new Channel<WatchEvent>();
channel.onmessage = callback;
const promise = invoke<WatchResult>('plugin:yaak-sync|watch', {
workspaceId: workspace.id,
channel,
});
return () => {
promise
.then(({ unlistenEvent }) => {
console.log('Cancelling workspace watch', workspace.id, unlistenEvent);
return emit(unlistenEvent);
})
.catch(console.error);
};
}, [workspace]);
return () => {
promise
.then(({ unlistenEvent }) => {
console.log('Cancelling workspace watch', workspaceId, unlistenEvent);
return emit(unlistenEvent);
})
.catch(console.error);
};
}

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-activate"
description = "Enables the activate command without any pre-configured scope."
commands.allow = ["activate"]
[[permission]]
identifier = "deny-activate"
description = "Denies the activate command without any pre-configured scope."
commands.deny = ["activate"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-add"
description = "Enables the add command without any pre-configured scope."
commands.allow = ["add"]
[[permission]]
identifier = "deny-add"
description = "Denies the add command without any pre-configured scope."
commands.deny = ["add"]

View File

@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-calculate-fs"
description = "Enables the calculate_fs command without any pre-configured scope."
commands.allow = ["calculate_fs"]
[[permission]]
identifier = "deny-calculate-fs"
description = "Denies the calculate_fs command without any pre-configured scope."
commands.deny = ["calculate_fs"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-check"
description = "Enables the check command without any pre-configured scope."
commands.allow = ["check"]
[[permission]]
identifier = "deny-check"
description = "Denies the check command without any pre-configured scope."
commands.deny = ["check"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-checkout"
description = "Enables the checkout command without any pre-configured scope."
commands.allow = ["checkout"]
[[permission]]
identifier = "deny-checkout"
description = "Denies the checkout command without any pre-configured scope."
commands.deny = ["checkout"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-cmd-add"
description = "Enables the cmd_add command without any pre-configured scope."
commands.allow = ["cmd_add"]
[[permission]]
identifier = "deny-cmd-add"
description = "Denies the cmd_add command without any pre-configured scope."
commands.deny = ["cmd_add"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-cmd-checkout"
description = "Enables the cmd_checkout command without any pre-configured scope."
commands.allow = ["cmd_checkout"]
[[permission]]
identifier = "deny-cmd-checkout"
description = "Denies the cmd_checkout command without any pre-configured scope."
commands.deny = ["cmd_checkout"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-cmd-commit"
description = "Enables the cmd_commit command without any pre-configured scope."
commands.allow = ["cmd_commit"]
[[permission]]
identifier = "deny-cmd-commit"
description = "Denies the cmd_commit command without any pre-configured scope."
commands.deny = ["cmd_commit"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-cmd-init"
description = "Enables the cmd_init command without any pre-configured scope."
commands.allow = ["cmd_init"]
[[permission]]
identifier = "deny-cmd-init"
description = "Denies the cmd_init command without any pre-configured scope."
commands.deny = ["cmd_init"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-cmd-log"
description = "Enables the cmd_log command without any pre-configured scope."
commands.allow = ["cmd_log"]
[[permission]]
identifier = "deny-cmd-log"
description = "Denies the cmd_log command without any pre-configured scope."
commands.deny = ["cmd_log"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-cmd-status"
description = "Enables the cmd_status command without any pre-configured scope."
commands.allow = ["cmd_status"]
[[permission]]
identifier = "deny-cmd-status"
description = "Denies the cmd_status command without any pre-configured scope."
commands.deny = ["cmd_status"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-commit"
description = "Enables the commit command without any pre-configured scope."
commands.allow = ["commit"]
[[permission]]
identifier = "deny-commit"
description = "Denies the commit command without any pre-configured scope."
commands.deny = ["commit"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-init"
description = "Enables the init command without any pre-configured scope."
commands.allow = ["init"]
[[permission]]
identifier = "deny-init"
description = "Denies the init command without any pre-configured scope."
commands.deny = ["init"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-init-repo"
description = "Enables the init_repo command without any pre-configured scope."
commands.allow = ["init_repo"]
[[permission]]
identifier = "deny-init-repo"
description = "Denies the init_repo command without any pre-configured scope."
commands.deny = ["init_repo"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-initialize"
description = "Enables the initialize command without any pre-configured scope."
commands.allow = ["initialize"]
[[permission]]
identifier = "deny-initialize"
description = "Denies the initialize command without any pre-configured scope."
commands.deny = ["initialize"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-log"
description = "Enables the log command without any pre-configured scope."
commands.allow = ["log"]
[[permission]]
identifier = "deny-log"
description = "Denies the log command without any pre-configured scope."
commands.deny = ["log"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-status"
description = "Enables the status command without any pre-configured scope."
commands.allow = ["status"]
[[permission]]
identifier = "deny-status"
description = "Denies the status command without any pre-configured scope."
commands.deny = ["status"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-sync"
description = "Enables the sync command without any pre-configured scope."
commands.allow = ["sync"]
[[permission]]
identifier = "deny-sync"
description = "Denies the sync command without any pre-configured scope."
commands.deny = ["sync"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-sync-fs"
description = "Enables the sync_fs command without any pre-configured scope."
commands.allow = ["sync_fs"]
[[permission]]
identifier = "deny-sync-fs"
description = "Denies the sync_fs command without any pre-configured scope."
commands.deny = ["sync_fs"]

View File

@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-unstage"
description = "Enables the unstage command without any pre-configured scope."
commands.allow = ["unstage"]
[[permission]]
identifier = "deny-unstage"
description = "Denies the unstage command without any pre-configured scope."
commands.deny = ["unstage"]

View File

@@ -3,6 +3,7 @@
Default permissions for the plugin
- `allow-calculate`
- `allow-calculate-fs`
- `allow-apply`
- `allow-watch`
@@ -15,58 +16,6 @@ Default permissions for the plugin
</tr>
<tr>
<td>
`yaak-sync:allow-activate`
</td>
<td>
Enables the activate command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-activate`
</td>
<td>
Denies the activate command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-add`
</td>
<td>
Enables the add command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-add`
</td>
<td>
Denies the add command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
@@ -122,12 +71,12 @@ Denies the calculate command without any pre-configured scope.
<tr>
<td>
`yaak-sync:allow-check`
`yaak-sync:allow-calculate-fs`
</td>
<td>
Enables the check command without any pre-configured scope.
Enables the calculate_fs command without any pre-configured scope.
</td>
</tr>
@@ -135,428 +84,12 @@ Enables the check command without any pre-configured scope.
<tr>
<td>
`yaak-sync:deny-check`
`yaak-sync:deny-calculate-fs`
</td>
<td>
Denies the check command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-checkout`
</td>
<td>
Enables the checkout command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-checkout`
</td>
<td>
Denies the checkout command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-cmd-add`
</td>
<td>
Enables the cmd_add command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-cmd-add`
</td>
<td>
Denies the cmd_add command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-cmd-checkout`
</td>
<td>
Enables the cmd_checkout command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-cmd-checkout`
</td>
<td>
Denies the cmd_checkout command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-cmd-commit`
</td>
<td>
Enables the cmd_commit command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-cmd-commit`
</td>
<td>
Denies the cmd_commit command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-cmd-init`
</td>
<td>
Enables the cmd_init command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-cmd-init`
</td>
<td>
Denies the cmd_init command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-cmd-log`
</td>
<td>
Enables the cmd_log command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-cmd-log`
</td>
<td>
Denies the cmd_log command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-cmd-status`
</td>
<td>
Enables the cmd_status command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-cmd-status`
</td>
<td>
Denies the cmd_status command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-commit`
</td>
<td>
Enables the commit command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-commit`
</td>
<td>
Denies the commit command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-init`
</td>
<td>
Enables the init command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-init`
</td>
<td>
Denies the init command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-init-repo`
</td>
<td>
Enables the init_repo command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-init-repo`
</td>
<td>
Denies the init_repo command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-initialize`
</td>
<td>
Enables the initialize command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-initialize`
</td>
<td>
Denies the initialize command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-log`
</td>
<td>
Enables the log command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-log`
</td>
<td>
Denies the log command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-status`
</td>
<td>
Enables the status command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-status`
</td>
<td>
Denies the status command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-sync`
</td>
<td>
Enables the sync command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-sync`
</td>
<td>
Denies the sync command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-sync-fs`
</td>
<td>
Enables the sync_fs command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-sync-fs`
</td>
<td>
Denies the sync_fs command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:allow-unstage`
</td>
<td>
Enables the unstage command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-sync:deny-unstage`
</td>
<td>
Denies the unstage command without any pre-configured scope.
Denies the calculate_fs command without any pre-configured scope.
</td>
</tr>

View File

@@ -2,6 +2,7 @@
description = "Default permissions for the plugin"
permissions = [
"allow-calculate",
"allow-calculate-fs",
"allow-apply",
"allow-watch",
]

View File

@@ -294,26 +294,6 @@
"PermissionKind": {
"type": "string",
"oneOf": [
{
"description": "Enables the activate command without any pre-configured scope.",
"type": "string",
"const": "allow-activate"
},
{
"description": "Denies the activate command without any pre-configured scope.",
"type": "string",
"const": "deny-activate"
},
{
"description": "Enables the add command without any pre-configured scope.",
"type": "string",
"const": "allow-add"
},
{
"description": "Denies the add command without any pre-configured scope.",
"type": "string",
"const": "deny-add"
},
{
"description": "Enables the apply command without any pre-configured scope.",
"type": "string",
@@ -335,174 +315,14 @@
"const": "deny-calculate"
},
{
"description": "Enables the check command without any pre-configured scope.",
"description": "Enables the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "allow-check"
"const": "allow-calculate-fs"
},
{
"description": "Denies the check command without any pre-configured scope.",
"description": "Denies the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "deny-check"
},
{
"description": "Enables the checkout command without any pre-configured scope.",
"type": "string",
"const": "allow-checkout"
},
{
"description": "Denies the checkout command without any pre-configured scope.",
"type": "string",
"const": "deny-checkout"
},
{
"description": "Enables the cmd_add command without any pre-configured scope.",
"type": "string",
"const": "allow-cmd-add"
},
{
"description": "Denies the cmd_add command without any pre-configured scope.",
"type": "string",
"const": "deny-cmd-add"
},
{
"description": "Enables the cmd_checkout command without any pre-configured scope.",
"type": "string",
"const": "allow-cmd-checkout"
},
{
"description": "Denies the cmd_checkout command without any pre-configured scope.",
"type": "string",
"const": "deny-cmd-checkout"
},
{
"description": "Enables the cmd_commit command without any pre-configured scope.",
"type": "string",
"const": "allow-cmd-commit"
},
{
"description": "Denies the cmd_commit command without any pre-configured scope.",
"type": "string",
"const": "deny-cmd-commit"
},
{
"description": "Enables the cmd_init command without any pre-configured scope.",
"type": "string",
"const": "allow-cmd-init"
},
{
"description": "Denies the cmd_init command without any pre-configured scope.",
"type": "string",
"const": "deny-cmd-init"
},
{
"description": "Enables the cmd_log command without any pre-configured scope.",
"type": "string",
"const": "allow-cmd-log"
},
{
"description": "Denies the cmd_log command without any pre-configured scope.",
"type": "string",
"const": "deny-cmd-log"
},
{
"description": "Enables the cmd_status command without any pre-configured scope.",
"type": "string",
"const": "allow-cmd-status"
},
{
"description": "Denies the cmd_status command without any pre-configured scope.",
"type": "string",
"const": "deny-cmd-status"
},
{
"description": "Enables the commit command without any pre-configured scope.",
"type": "string",
"const": "allow-commit"
},
{
"description": "Denies the commit command without any pre-configured scope.",
"type": "string",
"const": "deny-commit"
},
{
"description": "Enables the init command without any pre-configured scope.",
"type": "string",
"const": "allow-init"
},
{
"description": "Denies the init command without any pre-configured scope.",
"type": "string",
"const": "deny-init"
},
{
"description": "Enables the init_repo command without any pre-configured scope.",
"type": "string",
"const": "allow-init-repo"
},
{
"description": "Denies the init_repo command without any pre-configured scope.",
"type": "string",
"const": "deny-init-repo"
},
{
"description": "Enables the initialize command without any pre-configured scope.",
"type": "string",
"const": "allow-initialize"
},
{
"description": "Denies the initialize command without any pre-configured scope.",
"type": "string",
"const": "deny-initialize"
},
{
"description": "Enables the log command without any pre-configured scope.",
"type": "string",
"const": "allow-log"
},
{
"description": "Denies the log command without any pre-configured scope.",
"type": "string",
"const": "deny-log"
},
{
"description": "Enables the status command without any pre-configured scope.",
"type": "string",
"const": "allow-status"
},
{
"description": "Denies the status command without any pre-configured scope.",
"type": "string",
"const": "deny-status"
},
{
"description": "Enables the sync command without any pre-configured scope.",
"type": "string",
"const": "allow-sync"
},
{
"description": "Denies the sync command without any pre-configured scope.",
"type": "string",
"const": "deny-sync"
},
{
"description": "Enables the sync_fs command without any pre-configured scope.",
"type": "string",
"const": "allow-sync-fs"
},
{
"description": "Denies the sync_fs command without any pre-configured scope.",
"type": "string",
"const": "deny-sync-fs"
},
{
"description": "Enables the unstage command without any pre-configured scope.",
"type": "string",
"const": "allow-unstage"
},
{
"description": "Denies the unstage command without any pre-configured scope.",
"type": "string",
"const": "deny-unstage"
"const": "deny-calculate-fs"
},
{
"description": "Enables the watch command without any pre-configured scope.",

View File

@@ -1,26 +1,38 @@
use crate::error::Result;
use crate::sync::{
apply_sync_ops, apply_sync_state_ops, compute_sync_ops, get_db_candidates, get_fs_candidates,
get_workspace_sync_dir, SyncOp,
SyncOp,
};
use crate::watch::{watch_directory, WatchEvent};
use chrono::Utc;
use log::warn;
use serde::{Deserialize, Serialize};
use std::path::Path;
use tauri::ipc::Channel;
use tauri::{command, Listener, Runtime, WebviewWindow};
use tokio::sync::watch;
use ts_rs::TS;
use yaak_models::queries::get_workspace;
#[command]
pub async fn calculate<R: Runtime>(
window: WebviewWindow<R>,
workspace_id: &str,
sync_dir: &Path,
) -> Result<Vec<SyncOp>> {
let workspace = get_workspace(&window, workspace_id).await?;
let db_candidates = get_db_candidates(&window, &workspace).await?;
let fs_candidates = get_fs_candidates(&workspace).await?;
let db_candidates = get_db_candidates(&window, workspace_id, sync_dir).await?;
let fs_candidates = get_fs_candidates(sync_dir)
.await?
.into_iter()
// Strip out any non-workspace candidates
.filter(|fs| fs.model.workspace_id() == workspace_id)
.collect();
Ok(compute_sync_ops(db_candidates, fs_candidates))
}
#[command]
pub async fn calculate_fs(dir: &Path) -> Result<Vec<SyncOp>> {
let db_candidates = Vec::new();
let fs_candidates = get_fs_candidates(Path::new(&dir)).await?;
Ok(compute_sync_ops(db_candidates, fs_candidates))
}
@@ -28,11 +40,11 @@ pub async fn calculate<R: Runtime>(
pub async fn apply<R: Runtime>(
window: WebviewWindow<R>,
sync_ops: Vec<SyncOp>,
sync_dir: &Path,
workspace_id: &str,
) -> Result<()> {
let workspace = get_workspace(&window, workspace_id).await?;
let sync_state_ops = apply_sync_ops(&window, &workspace, sync_ops).await?;
apply_sync_state_ops(&window, &workspace, sync_state_ops).await
let sync_state_ops = apply_sync_ops(&window, &workspace_id, sync_dir, sync_ops).await?;
apply_sync_state_ops(&window, workspace_id, sync_dir, sync_state_ops).await
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
@@ -45,11 +57,10 @@ pub(crate) struct WatchResult {
#[command]
pub async fn watch<R: Runtime>(
window: WebviewWindow<R>,
sync_dir: &Path,
workspace_id: &str,
channel: Channel<WatchEvent>,
) -> Result<WatchResult> {
let workspace = get_workspace(&window, workspace_id).await?;
let sync_dir = get_workspace_sync_dir(&workspace)?;
let (cancel_tx, cancel_rx) = watch::channel(());
watch_directory(&sync_dir, channel, cancel_rx).await?;

View File

@@ -1,4 +1,4 @@
use crate::commands::{apply, calculate, watch};
use crate::commands::{apply, calculate, calculate_fs, watch};
use tauri::{
generate_handler,
plugin::{Builder, TauriPlugin},
@@ -12,5 +12,7 @@ mod sync;
mod watch;
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("yaak-sync").invoke_handler(generate_handler![calculate, apply, watch]).build()
Builder::new("yaak-sync")
.invoke_handler(generate_handler![calculate, calculate_fs, apply, watch])
.build()
}

View File

@@ -96,6 +96,7 @@ impl TryFrom<AnyModel> for SyncModel {
AnyModel::GrpcRequest(m) => SyncModel::GrpcRequest(m),
AnyModel::HttpRequest(m) => SyncModel::HttpRequest(m),
AnyModel::Workspace(m) => SyncModel::Workspace(m),
AnyModel::WorkspaceMeta(m) => return Err(UnknownModel(m.model)),
AnyModel::CookieJar(m) => return Err(UnknownModel(m.model)),
AnyModel::GrpcConnection(m) => return Err(UnknownModel(m.model)),
AnyModel::GrpcEvent(m) => return Err(UnknownModel(m.model)),

View File

@@ -1,4 +1,4 @@
use crate::error::Error::{InvalidSyncFile, WorkspaceSyncNotConfigured};
use crate::error::Error::InvalidSyncFile;
use crate::error::Result;
use crate::models::SyncModel;
use chrono::Utc;
@@ -12,12 +12,11 @@ use tokio::fs;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use ts_rs::TS;
use yaak_models::models::{SyncState, Workspace};
use yaak_models::models::{SyncState, WorkspaceMeta};
use yaak_models::queries::{
delete_environment, delete_folder, delete_grpc_request, delete_http_request, delete_sync_state,
delete_workspace, get_workspace_export_resources, list_sync_states_for_workspace,
upsert_environment, upsert_folder, upsert_grpc_request, upsert_http_request, upsert_sync_state,
upsert_workspace, UpdateSource,
batch_upsert, delete_environment, delete_folder, delete_grpc_request, delete_http_request,
delete_sync_state, delete_workspace, get_workspace_export_resources, get_workspace_meta,
list_sync_states_for_workspace, upsert_sync_state, upsert_workspace_meta, UpdateSource,
};
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
@@ -48,6 +47,19 @@ pub(crate) enum SyncOp {
},
}
impl SyncOp {
fn workspace_id(&self) -> String {
match self {
SyncOp::FsCreate { model } => model.workspace_id(),
SyncOp::FsUpdate { state, .. } => state.workspace_id.clone(),
SyncOp::FsDelete { state, .. } => state.workspace_id.clone(),
SyncOp::DbCreate { fs } => fs.model.workspace_id(),
SyncOp::DbUpdate { state, .. } => state.workspace_id.clone(),
SyncOp::DbDelete { model, .. } => model.workspace_id(),
}
}
}
impl Display for SyncOp {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(
@@ -87,24 +99,23 @@ impl DbCandidate {
#[serde(rename_all = "camelCase", tag = "type")]
#[ts(export, export_to = "sync.ts")]
pub(crate) struct FsCandidate {
model: SyncModel,
rel_path: PathBuf,
checksum: String,
pub(crate) model: SyncModel,
pub(crate) rel_path: PathBuf,
pub(crate) checksum: String,
}
pub(crate) async fn get_db_candidates<R: Runtime>(
mgr: &impl Manager<R>,
workspace: &Workspace,
workspace_id: &str,
sync_dir: &Path,
) -> Result<Vec<DbCandidate>> {
let sync_dir = get_workspace_sync_dir(workspace)?;
let models: HashMap<_, _> =
workspace_models(mgr, workspace).await.into_iter().map(|m| (m.id(), m)).collect();
let sync_states: HashMap<_, _> =
list_sync_states_for_workspace(mgr, workspace.id.as_str(), sync_dir)
.await?
.into_iter()
.map(|s| (s.model_id.clone(), s))
.collect();
workspace_models(mgr, workspace_id).await.into_iter().map(|m| (m.id(), m)).collect();
let sync_states: HashMap<_, _> = list_sync_states_for_workspace(mgr, workspace_id, sync_dir)
.await?
.into_iter()
.map(|s| (s.model_id.clone(), s))
.collect();
// 1. Add candidates for models (created/modified/unmodified)
let mut candidates: Vec<DbCandidate> = models
@@ -139,14 +150,9 @@ pub(crate) async fn get_db_candidates<R: Runtime>(
Ok(candidates)
}
pub(crate) async fn get_fs_candidates(workspace: &Workspace) -> Result<Vec<FsCandidate>> {
let dir = match workspace.setting_sync_dir.clone() {
None => return Ok(Vec::new()),
Some(d) => d,
};
pub(crate) async fn get_fs_candidates(dir: &Path) -> Result<Vec<FsCandidate>> {
// Ensure the root directory exists
fs::create_dir_all(dir.clone()).await?;
fs::create_dir_all(dir).await?;
let mut candidates = Vec::new();
let mut entries = fs::read_dir(dir).await?;
@@ -166,12 +172,6 @@ pub(crate) async fn get_fs_candidates(workspace: &Workspace) -> Result<Vec<FsCan
}
};
// Skip models belonging to different workspace
if model.workspace_id() != workspace.id.as_str() {
debug!("Skipping non-workspace file");
continue;
}
let rel_path = Path::new(&dir_entry.file_name()).to_path_buf();
candidates.push(FsCandidate {
rel_path,
@@ -254,7 +254,7 @@ pub(crate) fn compute_sync_ops(
}
}
(Some(DbCandidate::Added(model)), Some(_)) => {
// This would be super rare, so let's follow the user's intention
// This would be super rare (impossible?), so let's follow the user's intention
SyncOp::FsCreate {
model: model.to_owned(),
}
@@ -269,14 +269,17 @@ pub(crate) fn compute_sync_ops(
.collect()
}
async fn workspace_models<R: Runtime>(
mgr: &impl Manager<R>,
workspace: &Workspace,
) -> Vec<SyncModel> {
let workspace_id = workspace.id.as_str();
async fn workspace_models<R: Runtime>(mgr: &impl Manager<R>, workspace_id: &str) -> Vec<SyncModel> {
let resources = get_workspace_export_resources(mgr, vec![workspace_id]).await.resources;
let workspace = resources.workspaces.iter().find(|w| w.id == workspace_id);
let workspace = match workspace {
None => return Vec::new(),
Some(w) => w,
};
let mut sync_models = vec![SyncModel::Workspace(workspace.to_owned())];
for m in resources.environments {
sync_models.push(SyncModel::Environment(m));
}
@@ -295,7 +298,8 @@ async fn workspace_models<R: Runtime>(
pub(crate) async fn apply_sync_ops<R: Runtime>(
window: &WebviewWindow<R>,
workspace: &Workspace,
workspace_id: &str,
sync_dir: &Path,
sync_ops: Vec<SyncOp>,
) -> Result<Vec<SyncStateOp>> {
if sync_ops.is_empty() {
@@ -307,10 +311,145 @@ pub(crate) async fn apply_sync_ops<R: Runtime>(
sync_ops.iter().map(|op| op.to_string()).collect::<Vec<String>>().join(", ")
);
let mut sync_state_ops = Vec::new();
let mut workspaces_to_upsert = Vec::new();
let mut environments_to_upsert = Vec::new();
let mut folders_to_upsert = Vec::new();
let mut http_requests_to_upsert = Vec::new();
let mut grpc_requests_to_upsert = Vec::new();
for op in sync_ops {
let op = apply_sync_op(window, workspace, &op).await?;
sync_state_ops.push(op);
// Only apply things if workspace ID matches
if op.workspace_id() != workspace_id {
continue;
}
sync_state_ops.push(match op {
SyncOp::FsCreate { model } => {
let rel_path = derive_model_filename(&model);
let abs_path = sync_dir.join(rel_path.clone());
let (content, checksum) = model.to_file_contents(&rel_path)?;
let mut f = File::create(&abs_path).await?;
f.write_all(&content).await?;
SyncStateOp::Create {
model_id: model.id(),
checksum,
rel_path,
}
}
SyncOp::FsUpdate { model, state } => {
// Always write the existing path
let rel_path = Path::new(&state.rel_path);
let abs_path = Path::new(&state.sync_dir).join(&rel_path);
let (content, checksum) = model.to_file_contents(&rel_path)?;
let mut f = File::create(&abs_path).await?;
f.write_all(&content).await?;
SyncStateOp::Update {
state: state.to_owned(),
checksum,
rel_path: rel_path.to_owned(),
}
}
SyncOp::FsDelete {
state,
fs: fs_candidate,
} => match fs_candidate {
None => SyncStateOp::Delete {
state: state.to_owned(),
},
Some(_) => {
// Always delete the existing path
let rel_path = Path::new(&state.rel_path);
let abs_path = Path::new(&state.sync_dir).join(&rel_path);
fs::remove_file(&abs_path).await?;
SyncStateOp::Delete {
state: state.to_owned(),
}
}
},
SyncOp::DbCreate { fs } => {
let model_id = fs.model.id();
// Push updates to arrays so we can do them all in a single
// batch upsert to make foreign keys happy
match fs.model {
SyncModel::Workspace(m) => workspaces_to_upsert.push(m),
SyncModel::Environment(m) => environments_to_upsert.push(m),
SyncModel::Folder(m) => folders_to_upsert.push(m),
SyncModel::HttpRequest(m) => http_requests_to_upsert.push(m),
SyncModel::GrpcRequest(m) => grpc_requests_to_upsert.push(m),
};
SyncStateOp::Create {
model_id,
checksum: fs.checksum.to_owned(),
rel_path: fs.rel_path.to_owned(),
}
}
SyncOp::DbUpdate { state, fs } => {
// Push updates to arrays so we can do them all in a single
// batch upsert to make foreign keys happy
match fs.model {
SyncModel::Workspace(m) => workspaces_to_upsert.push(m),
SyncModel::Environment(m) => environments_to_upsert.push(m),
SyncModel::Folder(m) => folders_to_upsert.push(m),
SyncModel::HttpRequest(m) => http_requests_to_upsert.push(m),
SyncModel::GrpcRequest(m) => grpc_requests_to_upsert.push(m),
}
SyncStateOp::Update {
state: state.to_owned(),
checksum: fs.checksum.to_owned(),
rel_path: fs.rel_path.to_owned(),
}
}
SyncOp::DbDelete { model, state } => {
delete_model(window, &model).await?;
SyncStateOp::Delete {
state: state.to_owned(),
}
}
});
}
let upserted_models = batch_upsert(
window,
workspaces_to_upsert,
environments_to_upsert,
folders_to_upsert,
http_requests_to_upsert,
grpc_requests_to_upsert,
)
.await?;
// Ensure we creat WorkspaceMeta models for each new workspace, with the appropriate sync dir
let sync_dir_string = sync_dir.to_string_lossy().to_string();
for workspace in upserted_models.workspaces {
let r = match get_workspace_meta(window, &workspace).await {
Ok(Some(m)) => {
if m.setting_sync_dir == Some(sync_dir_string.clone()) {
// We don't need to update if unchanged
continue;
}
let wm = WorkspaceMeta {
setting_sync_dir: Some(sync_dir.to_string_lossy().to_string()),
..m
};
upsert_workspace_meta(window, wm, &UpdateSource::Sync).await
}
Ok(None) => {
let wm = WorkspaceMeta {
workspace_id: workspace_id.to_string(),
setting_sync_dir: Some(sync_dir.to_string_lossy().to_string()),
..Default::default()
};
upsert_workspace_meta(window, wm, &UpdateSource::Sync).await
}
Err(e) => Err(e),
};
if let Err(e) = r {
warn!("Failed to upsert workspace meta for synced workspace {e:?}");
}
}
Ok(sync_state_ops)
}
@@ -331,178 +470,55 @@ pub(crate) enum SyncStateOp {
},
}
/// Flush a DB model to the filesystem
async fn apply_sync_op<R: Runtime>(
window: &WebviewWindow<R>,
workspace: &Workspace,
op: &SyncOp,
) -> Result<SyncStateOp> {
let sync_state_op = match op {
SyncOp::FsCreate { model } => {
let rel_path = derive_model_filename(&model);
let abs_path = derive_full_model_path(workspace, &model)?;
let (content, checksum) = model.to_file_contents(&rel_path)?;
let mut f = File::create(&abs_path).await?;
f.write_all(&content).await?;
SyncStateOp::Create {
model_id: model.id(),
checksum,
rel_path,
}
}
SyncOp::FsUpdate { model, state } => {
// Always write the existing path
let rel_path = Path::new(&state.rel_path);
let abs_path = Path::new(&state.sync_dir).join(&rel_path);
let (content, checksum) = model.to_file_contents(&rel_path)?;
let mut f = File::create(&abs_path).await?;
f.write_all(&content).await?;
SyncStateOp::Update {
state: state.to_owned(),
checksum,
rel_path: rel_path.to_owned(),
}
}
SyncOp::FsDelete {
state,
fs: fs_candidate,
} => match fs_candidate {
None => SyncStateOp::Delete {
state: state.to_owned(),
},
Some(_) => {
// Always delete the existing path
let rel_path = Path::new(&state.rel_path);
let abs_path = Path::new(&state.sync_dir).join(&rel_path);
fs::remove_file(&abs_path).await?;
SyncStateOp::Delete {
state: state.to_owned(),
}
}
},
SyncOp::DbCreate { fs } => {
upsert_model(window, &fs.model).await?;
SyncStateOp::Create {
model_id: fs.model.id(),
checksum: fs.checksum.to_owned(),
rel_path: fs.rel_path.to_owned(),
}
}
SyncOp::DbUpdate { state, fs } => {
upsert_model(window, &fs.model).await?;
SyncStateOp::Update {
state: state.to_owned(),
checksum: fs.checksum.to_owned(),
rel_path: fs.rel_path.to_owned(),
}
}
SyncOp::DbDelete { model, state } => {
delete_model(window, model).await?;
SyncStateOp::Delete {
state: state.to_owned(),
}
}
};
Ok(sync_state_op)
}
pub(crate) async fn apply_sync_state_ops<R: Runtime>(
window: &WebviewWindow<R>,
workspace: &Workspace,
workspace_id: &str,
sync_dir: &Path,
ops: Vec<SyncStateOp>,
) -> Result<()> {
for op in ops {
apply_sync_state_op(window, workspace, op).await?
}
Ok(())
}
async fn apply_sync_state_op<R: Runtime>(
window: &WebviewWindow<R>,
workspace: &Workspace,
op: SyncStateOp,
) -> Result<()> {
let dir_path = get_workspace_sync_dir(workspace)?;
match op {
SyncStateOp::Create {
checksum,
rel_path,
model_id,
} => {
let sync_state = SyncState {
workspace_id: workspace.to_owned().id,
match op {
SyncStateOp::Create {
checksum,
rel_path,
model_id,
} => {
let sync_state = SyncState {
workspace_id: workspace_id.to_string(),
model_id,
checksum,
sync_dir: sync_dir.to_str().unwrap().to_string(),
rel_path: rel_path.to_str().unwrap().to_string(),
flushed_at: Utc::now().naive_utc(),
..Default::default()
};
upsert_sync_state(window, sync_state).await?;
}
SyncStateOp::Update {
state: sync_state,
checksum,
sync_dir: dir_path.to_str().unwrap().to_string(),
rel_path: rel_path.to_str().unwrap().to_string(),
flushed_at: Utc::now().naive_utc(),
..Default::default()
};
upsert_sync_state(window, sync_state).await?;
}
SyncStateOp::Update {
state: sync_state,
checksum,
rel_path,
} => {
let sync_state = SyncState {
checksum,
sync_dir: dir_path.to_str().unwrap().to_string(),
rel_path: rel_path.to_str().unwrap().to_string(),
flushed_at: Utc::now().naive_utc(),
..sync_state
};
upsert_sync_state(window, sync_state).await?;
}
SyncStateOp::Delete { state } => {
delete_sync_state(window, state.id.as_str()).await?;
rel_path,
} => {
let sync_state = SyncState {
checksum,
sync_dir: sync_dir.to_str().unwrap().to_string(),
rel_path: rel_path.to_str().unwrap().to_string(),
flushed_at: Utc::now().naive_utc(),
..sync_state
};
upsert_sync_state(window, sync_state).await?;
}
SyncStateOp::Delete { state } => {
delete_sync_state(window, state.id.as_str()).await?;
}
}
}
Ok(())
}
pub(crate) fn get_workspace_sync_dir(workspace: &Workspace) -> Result<PathBuf> {
let workspace_id = workspace.to_owned().id;
match workspace.setting_sync_dir.to_owned() {
Some(d) => Ok(Path::new(&d).to_path_buf()),
None => Err(WorkspaceSyncNotConfigured(workspace_id)),
}
}
fn derive_full_model_path(workspace: &Workspace, m: &SyncModel) -> Result<PathBuf> {
let dir = get_workspace_sync_dir(workspace)?;
Ok(dir.join(derive_model_filename(m)))
}
fn derive_model_filename(m: &SyncModel) -> PathBuf {
let rel = format!("yaak.2.{}.yaml", m.id());
let rel = Path::new(&rel).to_path_buf();
// Ensure parent dir exists
rel
}
async fn upsert_model<R: Runtime>(window: &WebviewWindow<R>, m: &SyncModel) -> Result<()> {
match m {
SyncModel::Workspace(m) => {
upsert_workspace(window, m.to_owned(), &UpdateSource::Sync).await?;
}
SyncModel::Environment(m) => {
upsert_environment(window, m.to_owned(), &UpdateSource::Sync).await?;
}
SyncModel::Folder(m) => {
upsert_folder(window, m.to_owned(), &UpdateSource::Sync).await?;
}
SyncModel::HttpRequest(m) => {
upsert_http_request(window, m.to_owned(), &UpdateSource::Sync).await?;
}
SyncModel::GrpcRequest(m) => {
upsert_grpc_request(window, m.to_owned(), &UpdateSource::Sync).await?;
}
};
Ok(())
let rel = format!("yaak.{}.yaml", m.id());
Path::new(&rel).to_path_buf()
}
async fn delete_model<R: Runtime>(window: &WebviewWindow<R>, model: &SyncModel) -> Result<()> {