typed session ids

This commit is contained in:
Nikita Galaiko 2023-10-13 09:48:24 +02:00 committed by GitButler
parent 8bba4766ef
commit 6cac02fccc
23 changed files with 236 additions and 110 deletions

View File

@ -9,7 +9,9 @@ use crate::{
keys,
paths::DataDir,
project_repository::{self, conflicts},
projects, reader, search, sessions, users,
projects, reader, search,
sessions::{self, SessionId},
users,
virtual_branches::{self, target},
watcher,
};
@ -95,7 +97,7 @@ impl App {
pub fn list_session_files(
&self,
project_id: &str,
session_id: &str,
session_id: &SessionId,
paths: &Option<Vec<path::PathBuf>>,
) -> Result<HashMap<path::PathBuf, reader::Content>, Error> {
let session = self
@ -208,7 +210,7 @@ impl App {
pub fn list_session_deltas(
&self,
project_id: &str,
session_id: &str,
session_id: &SessionId,
paths: &Option<Vec<&str>>,
) -> Result<HashMap<String, Vec<deltas::Delta>>, Error> {
self.deltas_database

View File

@ -5,7 +5,11 @@ use tauri::Manager;
use tracing::instrument;
use crate::{
app, assets, bookmarks, deltas, error::Error, git, reader, search, sessions, virtual_branches,
app, assets, bookmarks, deltas,
error::{Code, Error},
git, reader, search,
sessions::{self, SessionId},
virtual_branches,
};
impl From<app::Error> for Error {
@ -63,7 +67,11 @@ pub async fn list_session_files(
paths: Option<Vec<path::PathBuf>>,
) -> Result<HashMap<path::PathBuf, reader::Content>, Error> {
let app = handle.state::<app::App>();
let files = app.list_session_files(project_id, session_id, &paths)?;
let session_id: SessionId = session_id.parse().map_err(|_| Error::UserError {
message: "Malformed session id".to_string(),
code: Code::Sessions,
})?;
let files = app.list_session_files(project_id, &session_id, &paths)?;
Ok(files)
}
@ -76,7 +84,11 @@ pub async fn list_deltas(
paths: Option<Vec<&str>>,
) -> Result<HashMap<String, Vec<deltas::Delta>>, Error> {
let app = handle.state::<app::App>();
let deltas = app.list_session_deltas(project_id, session_id, &paths)?;
let session_id = session_id.parse().map_err(|_| Error::UserError {
message: "Malformed session id".to_string(),
code: Code::Sessions,
})?;
let deltas = app.list_session_deltas(project_id, &session_id, &paths)?;
Ok(deltas)
}

View File

@ -3,7 +3,7 @@ use std::{collections::HashMap, path};
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::database;
use crate::{database, sessions::SessionId};
use super::{delta, operations};
@ -28,7 +28,7 @@ impl Database {
pub fn insert(
&self,
project_id: &str,
session_id: &str,
session_id: &SessionId,
file_path: &path::Path,
deltas: &Vec<delta::Delta>,
) -> Result<()> {
@ -56,7 +56,7 @@ impl Database {
pub fn list_by_project_id_session_id(
&self,
project_id: &str,
session_id: &str,
session_id: &SessionId,
file_path_filter: &Option<Vec<&str>>,
) -> Result<HashMap<String, Vec<delta::Delta>>> {
self.database
@ -142,7 +142,7 @@ mod tests {
let database = Database::from(db);
let project_id = "project_id";
let session_id = "session_id";
let session_id = SessionId::generate();
let file_path = path::PathBuf::from("file_path");
let delta1 = delta::Delta {
timestamp_ms: 0,
@ -150,10 +150,10 @@ mod tests {
};
let deltas = vec![delta1.clone()];
database.insert(project_id, session_id, &file_path, &deltas)?;
database.insert(project_id, &session_id, &file_path, &deltas)?;
assert_eq!(
database.list_by_project_id_session_id(project_id, session_id, &None)?,
database.list_by_project_id_session_id(project_id, &session_id, &None)?,
vec![(file_path.display().to_string(), vec![delta1])]
.into_iter()
.collect()
@ -168,7 +168,7 @@ mod tests {
let database = Database::from(db);
let project_id = "project_id";
let session_id = "session_id";
let session_id = SessionId::generate();
let file_path = path::PathBuf::from("file_path");
let delta1 = delta::Delta {
timestamp_ms: 0,
@ -182,11 +182,11 @@ mod tests {
))],
};
database.insert(project_id, session_id, &file_path, &vec![delta1])?;
database.insert(project_id, session_id, &file_path, &vec![delta2.clone()])?;
database.insert(project_id, &session_id, &file_path, &vec![delta1])?;
database.insert(project_id, &session_id, &file_path, &vec![delta2.clone()])?;
assert_eq!(
database.list_by_project_id_session_id(project_id, session_id, &None)?,
database.list_by_project_id_session_id(project_id, &session_id, &None)?,
vec![(file_path.display().to_string(), vec![delta2])]
.into_iter()
.collect()
@ -201,7 +201,7 @@ mod tests {
let database = Database::from(db);
let project_id = "project_id";
let session_id = "session_id";
let session_id = SessionId::generate();
let file_path1 = path::PathBuf::from("file_path1");
let file_path2 = path::PathBuf::from("file_path2");
let delta1 = delta::Delta {
@ -216,12 +216,12 @@ mod tests {
))],
};
database.insert(project_id, session_id, &file_path1, &vec![delta1.clone()])?;
database.insert(project_id, session_id, &file_path2, &vec![delta1.clone()])?;
database.insert(project_id, session_id, &file_path2, &vec![delta2.clone()])?;
database.insert(project_id, &session_id, &file_path1, &vec![delta1.clone()])?;
database.insert(project_id, &session_id, &file_path2, &vec![delta1.clone()])?;
database.insert(project_id, &session_id, &file_path2, &vec![delta2.clone()])?;
assert_eq!(
database.list_by_project_id_session_id(project_id, session_id, &None)?,
database.list_by_project_id_session_id(project_id, &session_id, &None)?,
vec![
(file_path1.display().to_string(), vec![delta1.clone()]),
(file_path2.display().to_string(), vec![delta1, delta2])

View File

@ -5,6 +5,7 @@ use serde::{ser::SerializeMap, Serialize};
#[derive(Debug)]
pub enum Code {
Unknown,
Sessions,
Projects,
ProjectGitAuth,
ProjectGitRemote,
@ -16,6 +17,7 @@ impl fmt::Display for Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Code::Unknown => write!(f, "errors.unknown"),
Code::Sessions => write!(f, "errors.sessions"),
Code::Projects => write!(f, "errors.projects"),
Code::ProjectGitAuth => write!(f, "errors.projects.git.auth"),
Code::ProjectGitRemote => write!(f, "errors.projects.git.remote"),

View File

@ -1,7 +1,10 @@
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::{bookmarks, deltas, reader, sessions};
use crate::{
bookmarks, deltas, reader,
sessions::{self, SessionId},
};
#[derive(Clone)]
pub struct Sender {
@ -76,7 +79,7 @@ impl Event {
pub fn file(
project_id: &str,
session_id: &str,
session_id: &SessionId,
file_path: &str,
contents: Option<&reader::Content>,
) -> Self {
@ -108,7 +111,7 @@ impl Event {
pub fn deltas(
project_id: &str,
session_id: &str,
session_id: &SessionId,
deltas: &Vec<deltas::Delta>,
relative_file_path: &std::path::Path,
) -> Self {

View File

@ -9,12 +9,13 @@ use std::{
use anyhow::{anyhow, Context, Ok, Result};
use filetime::FileTime;
use sha2::{Digest, Sha256};
use uuid::Uuid;
use crate::{
fs, git, lock,
paths::DataDir,
projects, users,
projects,
sessions::SessionId,
users,
virtual_branches::{self, target},
};
@ -328,7 +329,7 @@ impl Repository {
};
let session = sessions::Session {
id: Uuid::new_v4().to_string(),
id: SessionId::generate(),
hash: None,
meta,
};
@ -340,7 +341,7 @@ impl Repository {
tracing::info!(
project_id = self.project.id,
session_id = session.id,
session_id = %session.id,
"created new session"
);
@ -462,7 +463,7 @@ impl Repository {
tracing::info!(
project_id = self.project.id,
session_id = session.id,
session_id = %session.id,
%commit_oid,
"flushed session"
);
@ -596,7 +597,7 @@ impl Repository {
}
}
fn flush_gitbutler_file(&self, session_id: &str) -> Result<()> {
fn flush_gitbutler_file(&self, session_id: &SessionId) -> Result<()> {
let gb_path = self.git_repository.path();
let project_id = self.project.id.as_str();

View File

@ -5,7 +5,8 @@ use pretty_assertions::assert_eq;
use tempfile::tempdir;
use crate::{
deltas, projects, reader, sessions,
deltas, projects, reader,
sessions::{self, SessionId},
test_utils::{Case, Suite},
};
@ -109,13 +110,11 @@ fn test_list_deltas_from_current_session() -> Result<()> {
assert_eq!(deltas.len(), 1);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap()[0]
.operations
.len(),
deltas[&path::PathBuf::from("test.txt")][0].operations.len(),
1
);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap()[0].operations[0],
deltas[&path::PathBuf::from("test.txt")][0].operations[0],
deltas::Operation::Insert((0, "Hello World".to_string()))
);
@ -146,13 +145,11 @@ fn test_list_deltas_from_flushed_session() -> Result<()> {
assert_eq!(deltas.len(), 1);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap()[0]
.operations
.len(),
deltas[&path::PathBuf::from("test.txt")][0].operations.len(),
1
);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap()[0].operations[0],
deltas[&path::PathBuf::from("test.txt")][0].operations[0],
deltas::Operation::Insert((0, "Hello World".to_string()))
);
@ -172,8 +169,8 @@ fn test_list_files_from_current_session() -> Result<()> {
assert_eq!(files.len(), 1);
assert_eq!(
files.get(&path::PathBuf::from("test.txt")).unwrap(),
&reader::Content::UTF8("Hello World".to_string())
files[&path::PathBuf::from("test.txt")],
reader::Content::UTF8("Hello World".to_string())
);
Ok(())
@ -197,8 +194,8 @@ fn test_list_files_from_flushed_session() -> Result<()> {
assert_eq!(files.len(), 1);
assert_eq!(
files.get(&path::PathBuf::from("test.txt")).unwrap(),
&reader::Content::UTF8("Hello World".to_string())
files[&path::PathBuf::from("test.txt")],
reader::Content::UTF8("Hello World".to_string())
);
Ok(())
@ -213,8 +210,8 @@ fn test_remote_syncronization() -> Result<()> {
description: None,
repository_id: "123".to_string(),
git_url: cloud.path().to_str().unwrap().to_string(),
created_at: 0.to_string(),
updated_at: 0.to_string(),
created_at: 0_i32.to_string(),
updated_at: 0_i32.to_string(),
sync: true,
};
@ -262,7 +259,7 @@ fn test_remote_syncronization() -> Result<()> {
let sessions_two = case_two
.gb_repository
.get_sessions_iterator()?
.map(|s| s.unwrap())
.map(Result::unwrap)
.collect::<Vec<_>>();
assert_eq!(sessions_two.len(), 1);
assert_eq!(sessions_two[0].id, session_one.id);
@ -274,12 +271,12 @@ fn test_remote_syncronization() -> Result<()> {
assert_eq!(deltas.len(), 1);
assert_eq!(files.len(), 1);
assert_eq!(
files.get(&path::PathBuf::from("test.txt")).unwrap(),
&reader::Content::UTF8("Hello World".to_string())
files[&path::PathBuf::from("test.txt")],
reader::Content::UTF8("Hello World".to_string())
);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap(),
&vec![deltas::Delta {
deltas[&path::PathBuf::from("test.txt")],
vec![deltas::Delta {
operations: vec![deltas::Operation::Insert((0, "Hello World".to_string()))],
timestamp_ms: 0,
}]
@ -297,8 +294,8 @@ fn test_remote_sync_order() -> Result<()> {
description: None,
repository_id: "123".to_string(),
git_url: cloud.path().to_str().unwrap().to_string(),
created_at: 0.to_string(),
updated_at: 0.to_string(),
created_at: 0_i32.to_string(),
updated_at: 0_i32.to_string(),
sync: true,
};
@ -364,14 +361,14 @@ fn test_remote_sync_order() -> Result<()> {
let sessions_one = case_one
.gb_repository
.get_sessions_iterator()?
.map(|s| s.unwrap())
.map(Result::unwrap)
.collect::<Vec<_>>();
case_two.gb_repository.fetch(Some(&user))?;
let sessions_two = case_two
.gb_repository
.get_sessions_iterator()?
.map(|s| s.unwrap())
.map(Result::unwrap)
.collect::<Vec<_>>();
// make sure the sessions are the same on both repos
@ -401,8 +398,8 @@ fn test_gitbutler_file() -> Result<()> {
let file_content: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&gitbutler_file_path)?)?;
assert_eq!(file_content["sessionId"], session.id);
let sid: SessionId = file_content["sessionId"].as_str().unwrap().parse()?;
assert_eq!(sid, session.id);
assert_eq!(
file_content["repositoryId"],
project_repository.project().id

84
packages/tauri/src/id.rs Normal file
View File

@ -0,0 +1,84 @@
use std::{fmt, marker::PhantomData, str};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use uuid::Uuid;
pub struct Id<T>(Uuid, PhantomData<T>);
impl<T> Id<T> {
pub fn generate() -> Self {
Id(Uuid::new_v4(), PhantomData)
}
}
impl<T> rusqlite::types::FromSql for Id<T> {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
Uuid::parse_str(value.as_str()?)
.map(Into::into)
.map_err(|error| rusqlite::types::FromSqlError::Other(Box::new(error)))
}
}
impl<T> rusqlite::ToSql for Id<T> {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(self.0.to_string()))
}
}
impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl<T> From<Uuid> for Id<T> {
fn from(value: Uuid) -> Self {
Self(value, PhantomData)
}
}
impl<'de, T> Deserialize<'de> for Id<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Uuid::deserialize(deserializer).map(Into::into)
}
}
impl<T> Serialize for Id<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<T> Clone for Id<T> {
fn clone(&self) -> Self {
Self(self.0, PhantomData)
}
}
impl<T> fmt::Display for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T> Copy for Id<T> {}
impl<T> str::FromStr for Id<T> {
type Err = uuid::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Uuid::parse_str(s).map(Into::into)
}
}

View File

@ -98,6 +98,7 @@ pub mod fs;
pub mod gb_repository;
pub mod git;
pub mod github;
pub mod id;
pub mod keys;
pub mod lock;
pub mod logs;

View File

@ -3,6 +3,8 @@ use tantivy::{
Document,
};
use crate::sessions::SessionId;
#[derive(Debug, Default)]
pub struct IndexDocument {
pub version: u64,
@ -10,7 +12,7 @@ pub struct IndexDocument {
pub index: Option<u64>,
pub id: String,
pub project_id: Option<String>,
pub session_id: Option<String>,
pub session_id: Option<SessionId>,
pub file_path: Option<String>,
pub diff: Option<String>,
pub note: Option<String>,
@ -70,7 +72,7 @@ impl IndexDocument {
.map(|v| v.as_text().unwrap().to_string());
let session_id = doc
.get_first(schema.get_field("session_id").unwrap())
.map(|v| v.as_text().unwrap().to_string());
.map(|v| v.as_text().unwrap().parse().unwrap());
let file_path = doc
.get_first(schema.get_field("file_path").unwrap())
.map(|v| v.as_text().unwrap().to_string());

View File

@ -2,7 +2,7 @@ use std::path;
use anyhow::Result;
use crate::{paths::DataDir, storage};
use crate::{paths::DataDir, sessions::SessionId, storage};
use super::index;
@ -29,12 +29,12 @@ impl Storage {
Ok(())
}
pub fn get(&self, project_id: &str, session_hash: &str) -> Result<Option<u64>> {
pub fn get(&self, project_id: &str, session_id: &SessionId) -> Result<Option<u64>> {
let filepath = path::Path::new("indexes")
.join(format!("v{}", index::VERSION))
.join("meta")
.join(project_id)
.join(session_hash);
.join(session_id.to_string());
let meta = match self.storage.read(filepath.to_str().unwrap())? {
None => None,
Some(meta) => meta.parse::<u64>().ok(),
@ -42,12 +42,12 @@ impl Storage {
Ok(meta)
}
pub fn set(&self, project_id: &str, session_hash: &str, version: u64) -> Result<()> {
pub fn set(&self, project_id: &str, session_id: &SessionId, version: u64) -> Result<()> {
let filepath = path::Path::new("indexes")
.join(format!("v{}", index::VERSION))
.join("meta")
.join(project_id)
.join(session_hash);
.join(session_id.to_string());
self.storage.write(filepath, &version.to_string())?;
Ok(())
}

View File

@ -1,4 +1,5 @@
use std::{
cmp::Ordering,
fs, path,
sync::{Arc, RwLock},
time, vec,
@ -7,14 +8,18 @@ use std::{
use anyhow::{Context, Result};
use serde::Serialize;
use similar::{ChangeTag, TextDiff};
use std::cmp::Ordering;
use tantivy::query::TermQuery;
use tantivy::{collector, directory::MmapDirectory, IndexWriter};
use tantivy::{query::QueryParser, Term};
use tantivy::{schema::IndexRecordOption, tokenizer};
use tauri::AppHandle;
use crate::{bookmarks, deltas, gb_repository, paths::DataDir, reader, sessions};
use crate::{
bookmarks, deltas, gb_repository,
paths::DataDir,
reader,
sessions::{self, SessionId},
};
use super::{index, meta};
@ -280,7 +285,7 @@ impl SearcherInner {
tracing::debug!(
project_id = repository.get_project_id(),
session_id = session.id,
session_id = %session.id,
"session added to search",
);
@ -298,7 +303,7 @@ const WRITE_BUFFER_SIZE: usize = 10_000_000; // 10MB
#[serde(rename_all = "camelCase")]
pub struct SearchResult {
pub project_id: String,
pub session_id: String,
pub session_id: SessionId,
pub file_path: String,
pub index: u64,
}
@ -389,7 +394,7 @@ fn index_delta(
index: &tantivy::Index,
writer: &mut IndexWriter,
reader: &tantivy::IndexReader,
session_id: &str,
session_id: &SessionId,
project_id: &str,
file_text: &mut Vec<char>,
file_path: &path::Path,
@ -439,7 +444,7 @@ fn index_delta(
.join(" ");
doc.index = Some(i.try_into()?);
doc.session_id = Some(session_id.to_string());
doc.session_id = Some(*session_id);
doc.file_path = Some(file_path.display().to_string());
doc.project_id = Some(project_id.to_string());
doc.timestamp_ms = Some(delta.timestamp_ms.try_into()?);

View File

@ -66,7 +66,7 @@ fn search_by_bookmark_note() -> Result<()> {
Path::new("test.txt"),
&vec![deltas::Delta {
operations: vec![deltas::Operation::Insert((0, "Hello".to_string()))],
timestamp_ms: 123456,
timestamp_ms: 123_456,
}],
)?;
let session = gb_repository.flush(&project_repository, None)?.unwrap();
@ -76,7 +76,7 @@ fn search_by_bookmark_note() -> Result<()> {
// first we index bookmark
searcher.index_bookmark(&bookmarks::Bookmark {
project_id: gb_repository.get_project_id().to_string(),
timestamp_ms: 123456,
timestamp_ms: 123_456,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
note: "bookmark note".to_string(),
@ -115,7 +115,7 @@ fn search_by_bookmark_note() -> Result<()> {
// then update the note
searcher.index_bookmark(&bookmarks::Bookmark {
project_id: gb_repository.get_project_id().to_string(),
timestamp_ms: 123456,
timestamp_ms: 123_456,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
note: "updated bookmark note".to_string(),

View File

@ -3,7 +3,7 @@ use tauri::{AppHandle, Manager};
use crate::database;
use super::session;
use super::session::{self, SessionId};
#[derive(Clone)]
pub struct Database {
@ -82,7 +82,7 @@ impl Database {
pub fn get_by_project_id_id(
&self,
project_id: &str,
id: &str,
id: &SessionId,
) -> Result<Option<session::Session>> {
self.database.transaction(|tx| {
let mut stmt = get_by_project_id_id_stmt(tx)
@ -104,7 +104,7 @@ impl Database {
})
}
pub fn get_by_id(&self, id: &str) -> Result<Option<session::Session>> {
pub fn get_by_id(&self, id: &SessionId) -> Result<Option<session::Session>> {
self.database.transaction(|tx| {
let mut stmt = get_by_id_stmt(tx).context("Failed to prepare get_by_id statement")?;
let mut rows = stmt
@ -201,7 +201,7 @@ mod tests {
let project_id = "project_id";
let session1 = session::Session {
id: "id1".to_string(),
id: SessionId::generate(),
hash: None,
meta: session::Meta {
branch: None,
@ -211,7 +211,7 @@ mod tests {
},
};
let session2 = session::Session {
id: "id2".to_string(),
id: SessionId::generate(),
hash: Some("hash2".to_string()),
meta: session::Meta {
branch: Some("branch2".to_string()),
@ -228,9 +228,9 @@ mod tests {
database.list_by_project_id(project_id, None)?,
vec![session2.clone(), session1.clone()]
);
assert_eq!(database.get_by_id("id1")?.unwrap(), session1);
assert_eq!(database.get_by_id("id2")?.unwrap(), session2);
assert_eq!(database.get_by_id("id3")?, None);
assert_eq!(database.get_by_id(&session1.id)?.unwrap(), session1);
assert_eq!(database.get_by_id(&session2.id)?.unwrap(), session2);
assert_eq!(database.get_by_id(&SessionId::generate())?, None);
Ok(())
}
@ -241,8 +241,8 @@ mod tests {
let database = Database::from(db);
let project_id = "project_id";
let session1 = session::Session {
id: "id1".to_string(),
let session = session::Session {
id: SessionId::generate(),
hash: None,
meta: session::Meta {
branch: None,
@ -252,7 +252,7 @@ mod tests {
},
};
let session_updated = session::Session {
id: "id1".to_string(),
id: session.id,
hash: Some("hash2".to_string()),
meta: session::Meta {
branch: Some("branch2".to_string()),
@ -261,14 +261,14 @@ mod tests {
last_timestamp_ms: 4,
},
};
database.insert(project_id, &[&session1])?;
database.insert(project_id, &[&session])?;
database.insert(project_id, &[&session_updated])?;
assert_eq!(
database.list_by_project_id(project_id, None)?,
vec![session_updated.clone()]
);
assert_eq!(database.get_by_id("id1")?.unwrap(), session_updated);
assert_eq!(database.get_by_id(&session.id)?.unwrap(), session_updated);
Ok(())
}

View File

@ -10,5 +10,5 @@ mod tests;
pub use database::Database;
pub use iterator::SessionsIterator;
pub use reader::SessionReader as Reader;
pub use session::{Meta, Session, SessionError};
pub use session::{Meta, Session, SessionError, SessionId};
pub use writer::SessionWriter as Writer;

View File

@ -41,7 +41,7 @@ impl<'reader> SessionReader<'reader> {
if let Ok(reader::Content::UTF8(current_session_id)) =
wd_reader.read(&repository.session_path().join("meta").join("id"))
{
if current_session_id == session.id {
if current_session_id == session.id.to_string() {
let head_commit = repository.git_repository.head()?.peel_to_commit()?;
return Ok(SessionReader {
reader: Box::new(wd_reader),

View File

@ -4,7 +4,7 @@ use anyhow::{Context, Result};
use serde::Serialize;
use thiserror::Error;
use crate::reader;
use crate::{id::Id, reader};
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
@ -19,10 +19,12 @@ pub struct Meta {
pub commit: Option<String>,
}
pub type SessionId = Id<Session>;
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Session {
pub id: String,
pub id: SessionId,
// if hash is not set, the session is not saved aka current
pub hash: Option<String>,
pub meta: Meta,
@ -51,6 +53,11 @@ impl TryFrom<&dyn reader::Reader> for Session {
.try_into()
.context("failed to parse session id")
.map_err(SessionError::Err)?;
let id: SessionId = id
.parse()
.context("failed to parse session id")
.map_err(SessionError::Err)?;
let start_timestamp_ms = reader
.read(path::Path::new("session/meta/start"))
.context("failed to read session start timestamp")

View File

@ -1,18 +1,18 @@
use anyhow::Result;
use crate::{
sessions,
sessions::{self, session::SessionId},
test_utils::{Case, Suite},
};
use super::Writer;
#[test]
fn test_should_not_write_session_with_hash() -> Result<()> {
fn test_should_not_write_session_with_hash() {
let Case { gb_repository, .. } = Suite::default().new_case();
let session = sessions::Session {
id: "session_id".to_string(),
id: SessionId::generate(),
hash: Some("hash".to_string()),
meta: sessions::Meta {
start_timestamp_ms: 0,
@ -23,8 +23,6 @@ fn test_should_not_write_session_with_hash() -> Result<()> {
};
assert!(Writer::new(&gb_repository).write(&session).is_err());
Ok(())
}
#[test]
@ -32,7 +30,7 @@ fn test_should_write_full_session() -> Result<()> {
let Case { gb_repository, .. } = Suite::default().new_case();
let session = sessions::Session {
id: "session_id".to_string(),
id: SessionId::generate(),
hash: None,
meta: sessions::Meta {
start_timestamp_ms: 0,
@ -46,7 +44,7 @@ fn test_should_write_full_session() -> Result<()> {
assert_eq!(
std::fs::read_to_string(gb_repository.session_path().join("meta/id"))?,
"session_id"
session.id.to_string()
);
assert_eq!(
std::fs::read_to_string(gb_repository.session_path().join("meta/commit"))?,
@ -73,7 +71,7 @@ fn test_should_write_partial_session() -> Result<()> {
let Case { gb_repository, .. } = Suite::default().new_case();
let session = sessions::Session {
id: "session_id".to_string(),
id: SessionId::generate(),
hash: None,
meta: sessions::Meta {
start_timestamp_ms: 0,
@ -87,7 +85,7 @@ fn test_should_write_partial_session() -> Result<()> {
assert_eq!(
std::fs::read_to_string(gb_repository.session_path().join("meta/id"))?,
"session_id"
session.id.to_string()
);
assert!(!gb_repository.session_path().join("meta/commit").exists());
assert!(!gb_repository.session_path().join("meta/branch").exists());

View File

@ -36,7 +36,9 @@ impl<'writer> SessionWriter<'writer> {
None
};
if current_session_id.is_some() && current_session_id.as_ref() != Some(&session.id) {
if current_session_id.is_some()
&& current_session_id.as_ref() != Some(&session.id.to_string())
{
return Err(anyhow!(
"{}: can not open writer for {} because a writer for {} is still open",
self.repository.get_project_id(),
@ -57,7 +59,9 @@ impl<'writer> SessionWriter<'writer> {
)
.context("failed to write last timestamp")?;
if current_session_id.is_some() && current_session_id.as_ref() == Some(&session.id) {
if current_session_id.is_some()
&& current_session_id.as_ref() == Some(&session.id.to_string())
{
return Ok(());
}
@ -69,7 +73,7 @@ impl<'writer> SessionWriter<'writer> {
.join("id")
.to_str()
.unwrap(),
session.id.as_str(),
&session.id.to_string(),
)
.context("failed to write id")?;

View File

@ -1,6 +1,9 @@
use std::{fmt::Display, path, time};
use crate::{analytics, bookmarks, deltas, events, reader, sessions};
use crate::{
analytics, bookmarks, deltas, events, reader,
sessions::{self, SessionId},
};
#[derive(Debug, PartialEq, Clone)]
pub enum Event {
@ -16,8 +19,8 @@ pub enum Event {
ProjectFileChange(String, path::PathBuf),
Session(String, sessions::Session),
SessionFile((String, String, path::PathBuf, Option<reader::Content>)),
SessionDelta((String, String, path::PathBuf, deltas::Delta)),
SessionFile((String, SessionId, path::PathBuf, Option<reader::Content>)),
SessionDelta((String, SessionId, path::PathBuf, deltas::Delta)),
Bookmark(bookmarks::Bookmark),
IndexAll(String),

View File

@ -4,8 +4,11 @@ use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::{
bookmarks, deltas, events as app_events, gb_repository, paths::DataDir, project_repository,
projects, search, sessions, users,
bookmarks, deltas, events as app_events, gb_repository,
paths::DataDir,
project_repository, projects, search,
sessions::{self, SessionId},
users,
};
use super::events;
@ -41,7 +44,7 @@ impl Handler {
pub fn index_deltas(
&self,
project_id: &str,
session_id: &str,
session_id: &SessionId,
file_path: &path::Path,
deltas: &Vec<deltas::Delta>,
) -> Result<Vec<events::Event>> {

View File

@ -167,14 +167,14 @@ impl Handler {
Ok(vec![
events::Event::SessionFile((
project_id.to_string(),
current_session.id.clone(),
current_session.id,
path.to_path_buf(),
latest_file_content,
)),
events::Event::Session(project_id.to_string(), current_session.clone()),
events::Event::SessionDelta((
project_id.to_string(),
current_session.id.clone(),
current_session.id,
path.to_path_buf(),
new_delta.clone(),
)),

View File

@ -104,6 +104,8 @@ fn is_session_active(now: &time::SystemTime, session: &sessions::Session) -> Res
#[cfg(test)]
mod tests {
use crate::sessions::SessionId;
use super::*;
const ONE_MILLISECOND: time::Duration = time::Duration::from_millis(1);
@ -130,7 +132,7 @@ mod tests {
.into_iter()
.for_each(|(start, last, expected)| {
let session = sessions::Session {
id: "session-id".to_string(),
id: SessionId::generate(),
hash: None,
meta: sessions::Meta {
start_timestamp_ms: start.duration_since(time::UNIX_EPOCH).unwrap().as_millis(),