mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-06 01:27:24 +03:00
typed session ids
This commit is contained in:
parent
8bba4766ef
commit
6cac02fccc
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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])
|
||||
|
@ -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"),
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
84
packages/tauri/src/id.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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()?);
|
||||
|
@ -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(),
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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")
|
||||
|
@ -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());
|
||||
|
@ -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")?;
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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>> {
|
||||
|
@ -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(),
|
||||
)),
|
||||
|
@ -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(),
|
||||
|
Loading…
Reference in New Issue
Block a user