From 2eab889720287bee54faf418fb9d666dcdd40441 Mon Sep 17 00:00:00 2001 From: Nikita Galaiko Date: Tue, 7 Feb 2023 10:06:44 +0100 Subject: [PATCH] create tauri command to list deltas --- src-tauri/Cargo.lock | 19 ----------- src-tauri/Cargo.toml | 1 - src-tauri/src/crdt.rs | 1 - src-tauri/src/delta_watchers.rs | 60 +++++++++------------------------ src-tauri/src/fs.rs | 19 +++++++++++ src-tauri/src/main.rs | 48 +++++++++++--------------- src-tauri/src/projects.rs | 53 +++++++++++++++++++++++++++-- 7 files changed, 103 insertions(+), 98 deletions(-) create mode 100644 src-tauri/src/fs.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2b2010be4..23266c7e9 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1026,7 +1026,6 @@ dependencies = [ "git2", "log", "notify", - "path-absolutize", "serde", "serde_json", "tauri", @@ -1976,24 +1975,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" -[[package]] -name = "path-absolutize" -version = "3.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1d4993b16f7325d90c18c3c6a3327db7808752db8d208cea0acee0abd52c52" -dependencies = [ - "path-dedot", -] - -[[package]] -name = "path-dedot" -version = "3.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a81540d94551664b72b72829b12bd167c73c9d25fbac0e04fafa8023f7e4901" -dependencies = [ - "once_cell", -] - [[package]] name = "percent-encoding" version = "2.2.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7958acceb..ed1e20565 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -27,7 +27,6 @@ yrs = "0.16.1" difference = "2.0.0" futures = "0.3.26" git2 = "0.16.1" -path-absolutize = "3.0.14" chrono = "0.4.23" [features] diff --git a/src-tauri/src/crdt.rs b/src-tauri/src/crdt.rs index 7bee4880c..3ec2883d5 100644 --- a/src-tauri/src/crdt.rs +++ b/src-tauri/src/crdt.rs @@ -274,7 +274,6 @@ fn test_document_complex() { assert_eq!(document.to_string(), "held!"); assert_eq!(document.get_deltas().len(), 3); assert_eq!(document.get_deltas()[2].operations.len(), 2); - println!("{:?}", document.get_deltas()[2].operations); assert_eq!( document.get_deltas()[2].operations[0], Operation::Delete((3, 7)) diff --git a/src-tauri/src/delta_watchers.rs b/src-tauri/src/delta_watchers.rs index 48a30c175..bbc57758c 100644 --- a/src-tauri/src/delta_watchers.rs +++ b/src-tauri/src/delta_watchers.rs @@ -1,12 +1,11 @@ -use crate::crdt::{self, TextDocument}; +use crate::crdt::TextDocument; use crate::projects::Project; use futures::{ channel::mpsc::{channel, Receiver}, SinkExt, StreamExt, }; -use git2::{Commit, Oid, Repository}; +use git2::{Commit, Repository}; use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; -use path_absolutize::*; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; @@ -66,24 +65,18 @@ fn register_file_change( kind: EventKind, files: Vec, ) { + // update meta files every time file change is detected write_beginning_meta_files(&repo); - let project_path = PathBuf::from(&project.path); for file_path in files { if !file_path.is_file() { + // only handle file changes continue; } - let relative_file_path = file_path - .absolutize() - .unwrap() - .strip_prefix(project_path.clone()) - .unwrap() - .to_str() - .unwrap() - .to_string(); - + let relative_file_path = Path::new(file_path.strip_prefix(&project.path).unwrap()); if repo.is_path_ignored(&relative_file_path).unwrap_or(true) { + // make sure we're not watching ignored files continue; } @@ -95,23 +88,6 @@ fn register_file_change( log::info!("File removed: {:?}", file_path); } - let relative_deltas_path = PathBuf::from(".git/gb/session/deltas/"); - let delta_path = project_path - .join(relative_deltas_path) - .join(relative_file_path.clone()) - .clone(); - std::fs::create_dir_all(delta_path.parent().unwrap()).unwrap(); - - let deltas = if delta_path.exists() { - let raw_deltas = std::fs::read_to_string(delta_path.clone()) - .expect(format!("Failed to read {}", delta_path.to_str().unwrap()).as_str()); - let deltas: Vec = serde_json::from_str(&raw_deltas) - .expect(format!("Failed to parse {}", delta_path.to_str().unwrap()).as_str()); - Some(deltas) - } else { - None - }; - // first, we need to check if the file exists in the meta commit let meta_commit = get_meta_commit(&repo); let tree = meta_commit.tree().unwrap(); @@ -124,40 +100,34 @@ fn register_file_change( None }; + // second, get non-flushed file deltas + let deltas = project.get_file_deltas(Path::new(&relative_file_path)); + + // depending on the above, we can create TextDocument let mut text_doc = match (commit_blob, deltas) { (Some(contents), Some(deltas)) => { - println!("found deltas and commit blob"); TextDocument::new(&contents, deltas) } (Some(contents), None) => { - println!("found commit blob, no deltas"); TextDocument::new(&contents, vec![]) } (None, Some(deltas)) => { - println!("found deltas, no commit blob"); TextDocument::from_deltas(deltas) } (None, None) => { - println!("no deltas or commit blob"); TextDocument::from_deltas(vec![]) } }; + // update the TextDocument with the new file contents let contents = std::fs::read_to_string(file_path.clone()) .expect(format!("Failed to read {}", file_path.to_str().unwrap()).as_str()); - if !text_doc.update(&contents) { - // if the document hasn't changed, we don't need to write a delta. - continue; + if text_doc.update(&contents) { + // if the file was modified, save the deltas + let deltas = text_doc.get_deltas(); + project.save_file_deltas(relative_file_path, deltas); } - - let deltas = text_doc.get_deltas(); - - log::info!("Writing delta to {}", delta_path.to_str().unwrap()); - - let mut file = File::create(delta_path).unwrap(); - file.write_all(serde_json::to_string(&deltas).unwrap().as_bytes()) - .unwrap(); } } diff --git a/src-tauri/src/fs.rs b/src-tauri/src/fs.rs new file mode 100644 index 000000000..2fdcef6a2 --- /dev/null +++ b/src-tauri/src/fs.rs @@ -0,0 +1,19 @@ +use std::{fs::read_dir, path::Path}; + +// return a list of files in directory recursively +pub fn list_files(path: &Path) -> Vec { + let mut files = Vec::new(); + if path.is_dir() { + for entry in read_dir(path).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.is_dir() { + files.append(&mut list_files(&path)); + } else { + files.push(path.to_str().unwrap().to_string()); + } + } + } + files.sort(); + files +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 656245b1c..1a85130d6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,19 +1,17 @@ mod crdt; mod delta_watchers; +mod fs; mod projects; mod storage; -use crdt::TextDocument; +use crdt::Delta; use delta_watchers::watch; +use fs::list_files; use log; use projects::Project; +use std::collections::HashMap; use std::thread; -use std::{ - collections::HashMap, - fs, - path::{Path, PathBuf}, - sync::Mutex, -}; +use std::{fs::read_to_string, path::Path}; use storage::Storage; use tauri::{InvokeError, Manager, State}; use tauri_plugin_log::{ @@ -25,24 +23,6 @@ struct AppState { projects_storage: projects::Storage, } -// return a list of files in directory recursively -fn list_files(path: &Path) -> Vec { - let mut files = Vec::new(); - if path.is_dir() { - for entry in fs::read_dir(path).unwrap() { - let entry = entry.unwrap(); - let path = entry.path(); - if path.is_dir() { - files.append(&mut list_files(&path)); - } else { - files.push(path.to_str().unwrap().to_string()); - } - } - } - files.sort(); - files -} - // returns a list of files in directory recursively #[tauri::command] fn read_dir(path: &str) -> Result, InvokeError> { @@ -58,7 +38,7 @@ fn read_dir(path: &str) -> Result, InvokeError> { // reads file contents and returns it #[tauri::command] fn read_file(file_path: &str) -> Result { - let contents = fs::read_to_string(file_path); + let contents = read_to_string(file_path); if contents.is_ok() { return Ok(contents.unwrap()); } else { @@ -100,8 +80,17 @@ fn delete_project(state: State<'_, AppState>, id: &str) -> Result<(), InvokeErro .map_err(|e| e.into()) } -#[derive(Default)] -pub struct CRDTSCollection(Mutex>); +#[tauri::command] +fn list_deltas( + state: State<'_, AppState>, + project_id: &str, +) -> Result>, InvokeError> { + if let Some(project) = state.projects_storage.get_project(project_id)? { + Ok(project.list_deltas()) + } else { + Err("Project not found".into()) + } +} fn watch_project(project: &Project) { log::info!("Watching project: {}", project.path); @@ -155,7 +144,8 @@ fn main() { read_dir, add_project, list_projects, - delete_project + delete_project, + list_deltas ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/projects.rs b/src-tauri/src/projects.rs index 2b5f8b6c7..3d9bd2c3c 100644 --- a/src-tauri/src/projects.rs +++ b/src-tauri/src/projects.rs @@ -1,7 +1,9 @@ -use std::path::PathBuf; - -use crate::storage; +use crate::{crdt::Delta, fs::list_files, storage}; use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Project { @@ -38,6 +40,51 @@ impl Project { }) .ok_or("Unable to get title".to_string()) } + + fn deltas_path(&self) -> PathBuf { + let path = PathBuf::from(&self.path).join(PathBuf::from(".git/gb/session/deltas")); + std::fs::create_dir_all(path.clone()).unwrap(); + path + } + + pub fn save_file_deltas(&self, file_path: &Path, deltas: Vec) { + if deltas.is_empty() { + return; + } + let project_deltas_path = self.deltas_path(); + let delta_path = project_deltas_path.join(file_path.to_path_buf()); + log::info!("Writing delta to {}", delta_path.to_str().unwrap()); + let raw_deltas = serde_json::to_string(&deltas).unwrap(); + std::fs::write(delta_path, raw_deltas).unwrap(); + } + + pub fn get_file_deltas(&self, file_path: &Path) -> Option> { + let project_deltas_path = self.deltas_path(); + let delta_path = project_deltas_path.join(file_path.to_path_buf()); + if delta_path.exists() { + let raw_deltas = std::fs::read_to_string(delta_path.clone()) + .expect(format!("Failed to read {}", delta_path.to_str().unwrap()).as_str()); + let deltas: Vec = serde_json::from_str(&raw_deltas) + .expect(format!("Failed to parse {}", delta_path.to_str().unwrap()).as_str()); + Some(deltas) + } else { + None + } + } + + pub fn list_deltas(&self) -> HashMap> { + let deltas_path = self.deltas_path(); + let file_paths = list_files(&deltas_path); + let mut deltas = HashMap::new(); + for file_path in file_paths { + let file_path = Path::new(&file_path); + let file_deltas = self.get_file_deltas(file_path); + if let Some(file_deltas) = file_deltas { + deltas.insert(file_path.to_str().unwrap().to_string(), file_deltas); + } + } + deltas + } } const PROJECTS_FILE: &str = "projects.json";