create tauri command to list deltas

This commit is contained in:
Nikita Galaiko 2023-02-07 10:06:44 +01:00
parent 3346f2f65c
commit 2eab889720
No known key found for this signature in database
GPG Key ID: EBAB54E845BA519D
7 changed files with 103 additions and 98 deletions

19
src-tauri/Cargo.lock generated
View File

@ -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"

View File

@ -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]

View File

@ -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))

View File

@ -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<PathBuf>,
) {
// 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<crdt::Delta> = 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();
}
}

19
src-tauri/src/fs.rs Normal file
View File

@ -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<String> {
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
}

View File

@ -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<String> {
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<Vec<String>, InvokeError> {
@ -58,7 +38,7 @@ fn read_dir(path: &str) -> Result<Vec<String>, InvokeError> {
// reads file contents and returns it
#[tauri::command]
fn read_file(file_path: &str) -> Result<String, InvokeError> {
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<HashMap<PathBuf, TextDocument>>);
#[tauri::command]
fn list_deltas(
state: State<'_, AppState>,
project_id: &str,
) -> Result<HashMap<String, Vec<Delta>>, 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");

View File

@ -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<Delta>) {
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<Vec<Delta>> {
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<Delta> = 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<String, Vec<Delta>> {
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";