mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-17 06:19:41 +02:00
Git support (#143)
This commit is contained in:
7
src-tauri/yaak-sync/bindings/git.ts
Normal file
7
src-tauri/yaak-sync/bindings/git.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type GitCommit = { author: string, when: string, message: string | null, };
|
||||
|
||||
export type GitStatus = "added" | "conflict" | "current" | "modified" | "removed" | "renamed" | "type_change";
|
||||
|
||||
export type GitStatusEntry = { relaPath: string, status: GitStatus, staged: boolean, prev: string | null, next: string | null, };
|
||||
@@ -3,6 +3,8 @@ import { emit } from '@tauri-apps/api/event';
|
||||
import { SyncOp } from './bindings/gen_sync';
|
||||
import { WatchEvent, WatchResult } from './bindings/gen_watch';
|
||||
|
||||
export * from './bindings/gen_models';
|
||||
|
||||
export async function calculateSync(workspaceId: string, syncDir: string) {
|
||||
return invoke<SyncOp[]>('plugin:yaak-sync|calculate', {
|
||||
workspaceId,
|
||||
@@ -27,20 +29,53 @@ export function watchWorkspaceFiles(
|
||||
syncDir: string,
|
||||
callback: (e: WatchEvent) => void,
|
||||
) {
|
||||
console.log('Watching workspace files', workspaceId, syncDir);
|
||||
const channel = new Channel<WatchEvent>();
|
||||
channel.onmessage = callback;
|
||||
const promise = invoke<WatchResult>('plugin:yaak-sync|watch', {
|
||||
const unlistenPromise = invoke<WatchResult>('plugin:yaak-sync|watch', {
|
||||
workspaceId,
|
||||
syncDir,
|
||||
channel,
|
||||
});
|
||||
|
||||
return () => {
|
||||
promise
|
||||
.then(({ unlistenEvent }) => {
|
||||
console.log('Cancelling workspace watch', workspaceId, unlistenEvent);
|
||||
return emit(unlistenEvent);
|
||||
unlistenPromise.then(({ unlistenEvent }) => {
|
||||
addWatchKey(unlistenEvent);
|
||||
});
|
||||
|
||||
return () =>
|
||||
unlistenPromise
|
||||
.then(async ({ unlistenEvent }) => {
|
||||
console.log('Unwatching workspace files', workspaceId, syncDir);
|
||||
unlistenToWatcher(unlistenEvent);
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
}
|
||||
|
||||
function unlistenToWatcher(unlistenEvent: string) {
|
||||
emit(unlistenEvent).then(() => {
|
||||
removeWatchKey(unlistenEvent);
|
||||
});
|
||||
}
|
||||
|
||||
function getWatchKeys() {
|
||||
return sessionStorage.getItem('workspace-file-watchers')?.split(',').filter(Boolean) ?? [];
|
||||
}
|
||||
|
||||
function setWatchKeys(keys: string[]) {
|
||||
sessionStorage.setItem('workspace-file-watchers', keys.join(','));
|
||||
}
|
||||
|
||||
function addWatchKey(key: string) {
|
||||
const keys = getWatchKeys();
|
||||
setWatchKeys([...keys, key]);
|
||||
}
|
||||
|
||||
function removeWatchKey(key: string) {
|
||||
const keys = getWatchKeys();
|
||||
setWatchKeys(keys.filter((k) => k !== key));
|
||||
}
|
||||
|
||||
// On page load, unlisten to all zombie watchers
|
||||
const keys = getWatchKeys();
|
||||
console.log('Unsubscribing to zombie file watchers', keys);
|
||||
keys.forEach(unlistenToWatcher);
|
||||
|
||||
@@ -6,8 +6,8 @@ use tauri::{
|
||||
};
|
||||
|
||||
mod commands;
|
||||
mod error;
|
||||
mod models;
|
||||
pub mod error;
|
||||
pub mod models;
|
||||
mod sync;
|
||||
mod watch;
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ use crate::error::Result;
|
||||
use chrono::NaiveDateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use tokio::fs;
|
||||
use ts_rs::TS;
|
||||
use yaak_models::models::{
|
||||
AnyModel, Environment, Folder, GrpcRequest, HttpRequest, WebsocketRequest, Workspace,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum SyncModel {
|
||||
@@ -23,12 +23,10 @@ pub enum SyncModel {
|
||||
}
|
||||
|
||||
impl SyncModel {
|
||||
pub async fn from_file(file_path: &Path) -> Result<Option<(SyncModel, Vec<u8>, String)>> {
|
||||
let content = match fs::read(file_path).await {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
|
||||
pub fn from_bytes(
|
||||
content: Vec<u8>,
|
||||
file_path: &Path,
|
||||
) -> Result<Option<(SyncModel, Vec<u8>, String)>> {
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(&content);
|
||||
let checksum = hex::encode(hasher.finalize());
|
||||
@@ -39,10 +37,20 @@ impl SyncModel {
|
||||
} else if ext == "json" {
|
||||
Ok(Some((serde_json::from_reader(content.as_slice())?, content, checksum)))
|
||||
} else {
|
||||
Err(InvalidSyncFile(file_path.to_str().unwrap().to_string()))
|
||||
let p = file_path.to_str().unwrap().to_string();
|
||||
Err(InvalidSyncFile(format!("Unknown file extension {p}")))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_file(file_path: &Path) -> Result<Option<(SyncModel, Vec<u8>, String)>> {
|
||||
let content = match fs::read(file_path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
|
||||
Self::from_bytes(content, file_path)
|
||||
}
|
||||
|
||||
pub fn to_file_contents(&self, rel_path: &Path) -> Result<(Vec<u8>, String)> {
|
||||
let ext = rel_path.extension().unwrap_or_default();
|
||||
let content = if ext == "yaml" || ext == "yml" {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::error::Error::InvalidSyncFile;
|
||||
use crate::error::Result;
|
||||
use crate::models::SyncModel;
|
||||
use chrono::Utc;
|
||||
@@ -164,13 +163,12 @@ pub(crate) async fn get_fs_candidates(dir: &Path) -> Result<Vec<FsCandidate>> {
|
||||
};
|
||||
|
||||
let path = dir_entry.path();
|
||||
let (model, _, checksum) = match SyncModel::from_file(&path).await {
|
||||
let (model, _, checksum) = match SyncModel::from_file(&path) {
|
||||
Ok(Some(m)) => m,
|
||||
Ok(None) => continue,
|
||||
Err(InvalidSyncFile(_)) => continue,
|
||||
Err(e) => {
|
||||
warn!("Failed to read sync file {e}");
|
||||
continue;
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -315,7 +313,7 @@ pub(crate) async fn apply_sync_ops<R: Runtime>(
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Sync ops {}",
|
||||
"Applying sync ops {}",
|
||||
sync_ops.iter().map(|op| op.to_string()).collect::<Vec<String>>().join(", ")
|
||||
);
|
||||
let mut sync_state_ops = Vec::new();
|
||||
|
||||
@@ -45,9 +45,19 @@ pub(crate) async fn watch_directory(
|
||||
Some(event_res) = async_rx.recv() => {
|
||||
match event_res {
|
||||
Ok(event) => {
|
||||
// Filter out any ignored directories and see if we still get a result
|
||||
let paths = event.paths.into_iter()
|
||||
.map(|p| p.strip_prefix(&dir).unwrap().to_path_buf())
|
||||
.filter(|p| !p.starts_with(".git") && !p.starts_with("node_modules"))
|
||||
.collect::<Vec<PathBuf>>();
|
||||
|
||||
if paths.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
channel
|
||||
.send(WatchEvent {
|
||||
paths: event.paths,
|
||||
paths,
|
||||
kind: format!("{:?}", event.kind),
|
||||
})
|
||||
.expect("Failed to send watch event");
|
||||
|
||||
Reference in New Issue
Block a user