typed project id

This commit is contained in:
Nikita Galaiko 2023-10-13 11:00:00 +02:00 committed by GitButler
parent 6cac02fccc
commit 276e2d5b4f
42 changed files with 519 additions and 312 deletions

View File

@ -2,7 +2,7 @@ use std::{str, sync::Arc};
use tauri::AppHandle;
use crate::users::User;
use crate::{projects::ProjectId, users::User};
mod posthog;
@ -13,13 +13,13 @@ pub struct Config<'c> {
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
HeadChange {
project_id: String,
project_id: ProjectId,
reference_name: String,
},
}
impl Event {
pub fn project_id(&self) -> &str {
pub fn project_id(&self) -> &ProjectId {
match self {
Event::HeadChange { project_id, .. } => project_id,
}

View File

@ -9,7 +9,8 @@ use crate::{
keys,
paths::DataDir,
project_repository::{self, conflicts},
projects, reader, search,
projects::{self, ProjectId},
reader, search,
sessions::{self, SessionId},
users,
virtual_branches::{self, target},
@ -75,19 +76,19 @@ impl App {
.with_context(|| "failed to list projects")?
{
if let Err(error) = self.init_project(&project).await {
tracing::error!(project.id, ?error, "failed to init project");
tracing::error!(%project.id, ?error, "failed to init project");
}
}
Ok(())
}
pub fn get_project(&self, id: &str) -> Result<projects::Project, Error> {
pub fn get_project(&self, id: &ProjectId) -> Result<projects::Project, Error> {
self.projects.get(id).map_err(Error::GetProject)
}
pub fn list_sessions(
&self,
project_id: &str,
project_id: &ProjectId,
earliest_timestamp_ms: Option<u128>,
) -> Result<Vec<sessions::Session>> {
self.sessions_database
@ -96,7 +97,7 @@ impl App {
pub fn list_session_files(
&self,
project_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
paths: &Option<Vec<path::PathBuf>>,
) -> Result<HashMap<path::PathBuf, reader::Content>, Error> {
@ -122,7 +123,7 @@ impl App {
.map_err(Error::Other)
}
pub fn mark_resolved(&self, project_id: &str, path: &str) -> Result<(), Error> {
pub fn mark_resolved(&self, project_id: &ProjectId, path: &str) -> Result<(), Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
// mark file as resolved
@ -130,7 +131,7 @@ impl App {
Ok(())
}
pub fn fetch_from_target(&self, project_id: &str) -> Result<(), Error> {
pub fn fetch_from_target(&self, project_id: &ProjectId) -> Result<(), Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
let user = self.users.get_user().context("failed to get user")?;
@ -199,7 +200,7 @@ impl App {
pub fn list_bookmarks(
&self,
project_id: &str,
project_id: &ProjectId,
range: Option<ops::Range<u128>>,
) -> Result<Vec<bookmarks::Bookmark>, Error> {
self.bookmarks_database
@ -209,7 +210,7 @@ impl App {
pub fn list_session_deltas(
&self,
project_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
paths: &Option<Vec<&str>>,
) -> Result<HashMap<String, Vec<deltas::Delta>>, Error> {
@ -220,7 +221,7 @@ impl App {
pub fn git_wd_diff(
&self,
project_id: &str,
project_id: &ProjectId,
context_lines: u32,
) -> Result<HashMap<path::PathBuf, String>, Error> {
let project = self.projects.get(project_id)?;
@ -257,7 +258,7 @@ impl App {
pub fn git_remote_branches(
&self,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<git::RemoteBranchName>, Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
@ -268,7 +269,7 @@ impl App {
pub fn git_remote_branches_data(
&self,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<virtual_branches::RemoteBranch>, Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
@ -283,7 +284,7 @@ impl App {
.map_err(Error::Other)
}
pub fn git_head(&self, project_id: &str) -> Result<String, Error> {
pub fn git_head(&self, project_id: &ProjectId) -> Result<String, Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
let head = project_repository
@ -313,7 +314,7 @@ impl App {
}
}
pub fn git_gb_push(&self, project_id: &str) -> Result<(), Error> {
pub fn git_gb_push(&self, project_id: &ProjectId) -> Result<(), Error> {
let user = self.users.get_user().context("failed to get user")?;
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;

View File

@ -3,7 +3,7 @@ use std::ops;
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::database;
use crate::{database, projects::ProjectId};
use super::Bookmark;
@ -27,7 +27,7 @@ impl From<&AppHandle> for Database {
impl Database {
fn get_by_project_id_timestamp_ms(
&self,
project_id: &str,
project_id: &ProjectId,
timestamp_ms: &u128,
) -> Result<Option<Bookmark>> {
self.database.transaction(|tx| {
@ -98,7 +98,7 @@ impl Database {
fn list_by_project_id_range(
&self,
project_id: &str,
project_id: &ProjectId,
range: ops::Range<u128>,
) -> Result<Vec<Bookmark>> {
self.database.transaction(|tx| {
@ -119,7 +119,7 @@ impl Database {
})
}
fn list_by_project_id_all(&self, project_id: &str) -> Result<Vec<Bookmark>> {
fn list_by_project_id_all(&self, project_id: &ProjectId) -> Result<Vec<Bookmark>> {
self.database.transaction(|tx| {
let mut stmt = list_by_project_id_stmt(tx)
.context("Failed to prepare list_by_project_id statement")?;
@ -136,7 +136,7 @@ impl Database {
pub fn list_by_project_id(
&self,
project_id: &str,
project_id: &ProjectId,
range: Option<ops::Range<u128>>,
) -> Result<Vec<Bookmark>> {
if let Some(range) = range {
@ -250,7 +250,7 @@ mod tests {
let database = Database::from(db);
let bookmark = Bookmark {
project_id: "project_id".to_string(),
project_id: ProjectId::generate(),
timestamp_ms: 123,
created_timestamp_ms: 0,
updated_timestamp_ms: 0,
@ -274,7 +274,7 @@ mod tests {
let database = Database::from(db);
let bookmark_one = Bookmark {
project_id: "project_id".to_string(),
project_id: ProjectId::generate(),
timestamp_ms: 123,
created_timestamp_ms: 0,
updated_timestamp_ms: 0,
@ -284,7 +284,7 @@ mod tests {
database.upsert(&bookmark_one)?;
let bookmark_two = Bookmark {
project_id: "project_id".to_string(),
project_id: ProjectId::generate(),
timestamp_ms: 456,
created_timestamp_ms: 0,
updated_timestamp_ms: 1,
@ -309,7 +309,7 @@ mod tests {
let database = Database::from(db);
let bookmark = Bookmark {
project_id: "project_id".to_string(),
project_id: ProjectId::generate(),
timestamp_ms: 123,
created_timestamp_ms: 0,
updated_timestamp_ms: 0,

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Bookmark {
pub project_id: String,
pub project_id: ProjectId,
pub timestamp_ms: u128,
pub created_timestamp_ms: u128,
pub updated_timestamp_ms: u128,
@ -18,3 +18,5 @@ pub struct Bookmark {
pub use database::Database;
pub use reader::BookmarksReader as Reader;
pub use writer::BookmarksWriter as Writer;
use crate::projects::ProjectId;

View File

@ -24,7 +24,7 @@ impl<'writer> BookmarksWriter<'writer> {
)?;
tracing::debug!(
project_id = self.repository.get_project_id(),
project_id = %self.repository.get_project_id(),
timestamp_ms = bookmark.timestamp_ms,
"wrote bookmark",
);

View File

@ -54,7 +54,11 @@ pub async fn list_sessions(
earliest_timestamp_ms: Option<u128>,
) -> Result<Vec<sessions::Session>, Error> {
let app = handle.state::<app::App>();
let sessions = app.list_sessions(project_id, earliest_timestamp_ms)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let sessions = app.list_sessions(&project_id, earliest_timestamp_ms)?;
Ok(sessions)
}
@ -71,7 +75,11 @@ pub async fn list_session_files(
message: "Malformed session id".to_string(),
code: Code::Sessions,
})?;
let files = app.list_session_files(project_id, &session_id, &paths)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let files = app.list_session_files(&project_id, &session_id, &paths)?;
Ok(files)
}
@ -88,7 +96,11 @@ pub async fn list_deltas(
message: "Malformed session id".to_string(),
code: Code::Sessions,
})?;
let deltas = app.list_session_deltas(project_id, &session_id, &paths)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let deltas = app.list_session_deltas(&project_id, &session_id, &paths)?;
Ok(deltas)
}
@ -100,7 +112,11 @@ pub async fn git_wd_diff(
context_lines: u32,
) -> Result<HashMap<path::PathBuf, String>, Error> {
let app = handle.state::<app::App>();
let diff = app.git_wd_diff(project_id, context_lines)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let diff = app.git_wd_diff(&project_id, context_lines)?;
Ok(diff)
}
@ -111,7 +127,11 @@ pub async fn git_remote_branches(
project_id: &str,
) -> Result<Vec<git::RemoteBranchName>, Error> {
let app = handle.state::<app::App>();
let branches = app.git_remote_branches(project_id)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branches = app.git_remote_branches(&project_id)?;
Ok(branches)
}
@ -122,7 +142,11 @@ pub async fn git_remote_branches_data(
project_id: &str,
) -> Result<Vec<virtual_branches::RemoteBranch>, Error> {
let app = handle.state::<app::App>();
let branches = app.git_remote_branches_data(project_id)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branches = app.git_remote_branches_data(&project_id)?;
let branches = handle
.state::<assets::Proxy>()
.proxy_remote_branches(&branches)
@ -134,7 +158,11 @@ pub async fn git_remote_branches_data(
#[instrument(skip(handle))]
pub async fn git_head(handle: tauri::AppHandle, project_id: &str) -> Result<String, Error> {
let app = handle.state::<app::App>();
let head = app.git_head(project_id)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let head = app.git_head(&project_id)?;
Ok(head)
}
@ -160,6 +188,10 @@ pub async fn upsert_bookmark(
.elapsed()
.context("failed to get time")?
.as_millis();
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let bookmark = bookmarks::Bookmark {
project_id,
timestamp_ms: timestamp_ms
@ -182,7 +214,11 @@ pub async fn list_bookmarks(
range: Option<ops::Range<u128>>,
) -> Result<Vec<bookmarks::Bookmark>, Error> {
let app = handle.state::<app::App>();
let bookmarks = app.list_bookmarks(project_id, range)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let bookmarks = app.list_bookmarks(&project_id, range)?;
Ok(bookmarks)
}
@ -190,7 +226,11 @@ pub async fn list_bookmarks(
#[instrument(skip(handle))]
pub async fn fetch_from_target(handle: tauri::AppHandle, project_id: &str) -> Result<(), Error> {
let app = handle.state::<app::App>();
app.fetch_from_target(project_id)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
app.fetch_from_target(&project_id)?;
Ok(())
}
@ -202,7 +242,11 @@ pub async fn mark_resolved(
path: &str,
) -> Result<(), Error> {
let app = handle.state::<app::App>();
app.mark_resolved(project_id, path)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
app.mark_resolved(&project_id, path)?;
Ok(())
}

View File

@ -3,7 +3,7 @@ use std::{collections::HashMap, path};
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::{database, sessions::SessionId};
use crate::{database, projects::ProjectId, sessions::SessionId};
use super::{delta, operations};
@ -27,7 +27,7 @@ impl From<&AppHandle> for Database {
impl Database {
pub fn insert(
&self,
project_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
file_path: &path::Path,
deltas: &Vec<delta::Delta>,
@ -55,7 +55,7 @@ impl Database {
pub fn list_by_project_id_session_id(
&self,
project_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
file_path_filter: &Option<Vec<&str>>,
) -> Result<HashMap<String, Vec<delta::Delta>>> {
@ -141,7 +141,7 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let project_id = ProjectId::generate();
let session_id = SessionId::generate();
let file_path = path::PathBuf::from("file_path");
let delta1 = delta::Delta {
@ -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()
@ -167,7 +167,7 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let project_id = ProjectId::generate();
let session_id = SessionId::generate();
let file_path = path::PathBuf::from("file_path");
let delta1 = delta::Delta {
@ -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()
@ -200,7 +200,7 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let project_id = ProjectId::generate();
let session_id = SessionId::generate();
let file_path1 = path::PathBuf::from("file_path1");
let file_path2 = path::PathBuf::from("file_path2");
@ -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

@ -30,7 +30,7 @@ impl<'writer> DeltasWriter<'writer> {
.write_string(&format!("session/deltas/{}", path.display()), &raw_deltas)?;
tracing::debug!(
project_id = self.repository.get_project_id(),
project_id = %self.repository.get_project_id(),
path = %path.display(),
"wrote deltas"
);
@ -48,7 +48,7 @@ impl<'writer> DeltasWriter<'writer> {
.write_string(&format!("session/wd/{}", path.display()), contents)?;
tracing::debug!(
project_id = self.repository.get_project_id(),
project_id = %self.repository.get_project_id(),
path = %path.display(),
"wrote session wd file"
);

View File

@ -2,7 +2,9 @@ use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::{
bookmarks, deltas, reader,
bookmarks, deltas,
projects::ProjectId,
reader,
sessions::{self, SessionId},
};
@ -33,7 +35,7 @@ impl Sender {
pub struct Event {
name: String,
payload: serde_json::Value,
project_id: String,
project_id: ProjectId,
}
impl Event {
@ -41,44 +43,44 @@ impl Event {
&self.name
}
pub fn project_id(&self) -> &str {
pub fn project_id(&self) -> &ProjectId {
&self.project_id
}
pub fn git_index(project_id: &str) -> Self {
pub fn git_index(project_id: &ProjectId) -> Self {
Event {
name: format!("project://{}/git/index", project_id),
payload: serde_json::json!({}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn git_fetch(project_id: &str) -> Self {
pub fn git_fetch(project_id: &ProjectId) -> Self {
Event {
name: format!("project://{}/git/fetch", project_id),
payload: serde_json::json!({}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn git_head(project_id: &str, head: &str) -> Self {
pub fn git_head(project_id: &ProjectId, head: &str) -> Self {
Event {
name: format!("project://{}/git/head", project_id),
payload: serde_json::json!({ "head": head }),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn git_activity(project_id: &str) -> Self {
pub fn git_activity(project_id: &ProjectId) -> Self {
Event {
name: format!("project://{}/git/activity", project_id),
payload: serde_json::json!({}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn file(
project_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
file_path: &str,
contents: Option<&reader::Content>,
@ -89,28 +91,28 @@ impl Event {
"filePath": file_path,
"contents": contents,
}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn session(project_id: &str, session: &sessions::Session) -> Self {
pub fn session(project_id: &ProjectId, session: &sessions::Session) -> Self {
Event {
name: format!("project://{}/sessions", project_id),
payload: serde_json::to_value(session).unwrap(),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn bookmark(project_id: &str, bookmark: &bookmarks::Bookmark) -> Self {
pub fn bookmark(project_id: &ProjectId, bookmark: &bookmarks::Bookmark) -> Self {
Event {
name: format!("project://{}/bookmarks", project_id),
payload: serde_json::to_value(bookmark).unwrap(),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn deltas(
project_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
deltas: &Vec<deltas::Delta>,
relative_file_path: &std::path::Path,
@ -121,7 +123,7 @@ impl Event {
"deltas": deltas,
"filePath": relative_file_path,
}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
}

View File

@ -13,7 +13,7 @@ use sha2::{Digest, Sha256};
use crate::{
fs, git, lock,
paths::DataDir,
projects,
projects::{self, ProjectId},
sessions::SessionId,
users,
virtual_branches::{self, target},
@ -57,7 +57,7 @@ impl Repository {
let projects_dir = root.to_path_buf().join("projects");
let path = projects_dir.join(&project.id);
let path = projects_dir.join(project.id.to_string());
let lock_path = projects_dir.join(format!("{}.lock", project.id));
if path.exists() {
let git_repository = git::Repository::open(path.clone())
@ -96,7 +96,7 @@ impl Repository {
.migrate(project)
.context("failed to migrate")?
{
tracing::info!(project_id = gb_repository.project.id, "repository migrated");
tracing::info!(project_id = %gb_repository.project.id, "repository migrated");
return Result::Ok(gb_repository);
}
@ -112,7 +112,7 @@ impl Repository {
}
}
pub fn get_project_id(&self) -> &str {
pub fn get_project_id(&self) -> &ProjectId {
&self.project.id
}
@ -151,7 +151,7 @@ impl Repository {
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.push_update_reference(move |refname, message| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
refname,
message,
"pulling reference"
@ -160,7 +160,7 @@ impl Repository {
});
callbacks.push_transfer_progress(move |one, two, three| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
"transferred {}/{}/{} objects",
one,
two,
@ -182,7 +182,7 @@ impl Repository {
))?;
tracing::info!(
project_id = self.project.id,
project_id = %self.project.id,
remote = %remote.url()?.unwrap(),
"gb repo fetched",
);
@ -200,7 +200,7 @@ impl Repository {
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.push_update_reference(move |refname, message| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
refname,
message,
"pushing reference"
@ -209,7 +209,7 @@ impl Repository {
});
callbacks.push_transfer_progress(move |one, two, three| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
"transferred {}/{}/{} objects",
one,
two,
@ -233,7 +233,7 @@ impl Repository {
remote.url()?.unwrap()
))?;
tracing::info!(project_id = self.project.id, remote = %remote.url()?.unwrap(), "gb repository pushed");
tracing::info!(project_id = %self.project.id, remote = %remote.url()?.unwrap(), "gb repository pushed");
Ok(())
}
@ -340,7 +340,7 @@ impl Repository {
.context("failed to write session")?;
tracing::info!(
project_id = self.project.id,
project_id = %self.project.id,
session_id = %session.id,
"created new session"
);
@ -462,7 +462,7 @@ impl Repository {
write_gb_commit(tree_id, self, user).context("failed to write gb commit")?;
tracing::info!(
project_id = self.project.id,
project_id = %self.project.id,
session_id = %session.id,
%commit_oid,
"flushed session"
@ -544,7 +544,7 @@ impl Repository {
match reference {
Err(git::Error::NotFound(_)) => {
tracing::debug!(
project_id = project.id,
project_id = %project.id,
refname,
"reference not found, no migration"
);
@ -599,8 +599,7 @@ impl Repository {
fn flush_gitbutler_file(&self, session_id: &SessionId) -> Result<()> {
let gb_path = self.git_repository.path();
let project_id = self.project.id.as_str();
let project_id = self.project.id.to_string();
let gb_file_content = serde_json::json!({
"sessionId": session_id,
"repositoryId": project_id,
@ -652,7 +651,7 @@ fn build_wd_tree(
Result::Ok(reader::Content::UTF8(content)) => content,
Result::Ok(reader::Content::Large) => {
tracing::error!(
project_id = gb_repository.project.id,
project_id = %gb_repository.project.id,
path = %abs_path.display(),
"large file in session working directory"
);
@ -660,7 +659,7 @@ fn build_wd_tree(
}
Result::Ok(reader::Content::Binary) => {
tracing::error!(
project_id = gb_repository.project.id,
project_id = %gb_repository.project.id,
path = %abs_path.display(),
"binary file in session working directory"
);
@ -668,7 +667,7 @@ fn build_wd_tree(
}
Err(error) => {
tracing::error!(
project_id = gb_repository.project.id,
project_id = %gb_repository.project.id,
path = %abs_path.display(),
?error,
"failed to read file"
@ -837,7 +836,7 @@ fn add_wd_path(
// TODO: size limit should be configurable
let blob = if metadata.len() > 100_000_000 {
tracing::warn!(
project_id = gb_repository.project.id,
project_id = %gb_repository.project.id,
path = %file_path.display(),
"file too big"
);

View File

@ -5,7 +5,9 @@ use pretty_assertions::assert_eq;
use tempfile::tempdir;
use crate::{
deltas, projects, reader,
deltas,
projects::{self, ProjectId},
reader,
sessions::{self, SessionId},
test_utils::{Case, Suite},
};
@ -224,7 +226,7 @@ fn test_remote_syncronization() -> Result<()> {
"Hello World",
)]));
suite.projects.update(&projects::UpdateRequest {
id: case_one.project.id.clone(),
id: case_one.project.id,
api: Some(api_project.clone()),
..Default::default()
})?;
@ -247,7 +249,7 @@ fn test_remote_syncronization() -> Result<()> {
// create second local project, fetch it and make sure session is there
let case_two = suite.new_case();
suite.projects.update(&projects::UpdateRequest {
id: case_two.project.id.clone(),
id: case_two.project.id,
api: Some(api_project.clone()),
..Default::default()
})?;
@ -303,7 +305,7 @@ fn test_remote_sync_order() -> Result<()> {
let case_one = suite.new_case();
suite.projects.update(&projects::UpdateRequest {
id: case_one.project.id.clone(),
id: case_one.project.id,
api: Some(api_project.clone()),
..Default::default()
})?;
@ -311,7 +313,7 @@ fn test_remote_sync_order() -> Result<()> {
let case_two = suite.new_case();
suite.projects.update(&projects::UpdateRequest {
id: case_two.project.id.clone(),
id: case_two.project.id,
api: Some(api_project.clone()),
..Default::default()
})?;
@ -400,10 +402,9 @@ fn test_gitbutler_file() -> Result<()> {
serde_json::from_str(&std::fs::read_to_string(&gitbutler_file_path)?)?;
let sid: SessionId = file_content["sessionId"].as_str().unwrap().parse()?;
assert_eq!(sid, session.id);
assert_eq!(
file_content["repositoryId"],
project_repository.project().id
);
let pid: ProjectId = file_content["repositoryId"].as_str().unwrap().parse()?;
assert_eq!(pid, project_repository.project().id);
Ok(())
}

View File

@ -1,16 +1,28 @@
use std::{fmt, marker::PhantomData, str};
use std::{fmt, hash::Hash, marker::PhantomData, str};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use uuid::Uuid;
pub struct Id<T>(Uuid, PhantomData<T>);
impl<T> Hash for Id<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl<T> Id<T> {
pub fn generate() -> Self {
Id(Uuid::new_v4(), PhantomData)
}
}
impl<T> Default for Id<T> {
fn default() -> Self {
Self::generate()
}
}
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()?)
@ -31,6 +43,8 @@ impl<T> PartialEq for Id<T> {
}
}
impl<T> Eq for Id<T> {}
impl<T> From<Uuid> for Id<T> {
fn from(value: Uuid) -> Self {
Self(value, PhantomData)

View File

@ -257,7 +257,7 @@ impl Repository {
) {
Ok(()) => {
tracing::info!(
project_id = self.project.id,
project_id = %self.project.id,
remote = %branch.remote(),
%head,
branch = branch.branch(),
@ -266,7 +266,7 @@ impl Repository {
return Ok(());
}
Err(git::Error::AuthenticationFailed(error)) => {
tracing::error!(project_id = self.project.id, ?error, "git push failed",);
tracing::error!(project_id = %self.project.id, ?error, "git push failed",);
continue;
}
Err(error) => return Err(RemoteError::Other(error.into())),
@ -285,7 +285,7 @@ impl Repository {
remote_callbacks.push_update_reference(|refname, message| {
if let Some(msg) = message {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
refname,
msg,
"push update reference",
@ -295,7 +295,7 @@ impl Repository {
});
remote_callbacks.push_negotiation(|proposals| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
proposals = proposals
.iter()
.map(|p| format!(
@ -311,7 +311,7 @@ impl Repository {
});
remote_callbacks.push_transfer_progress(|one, two, three| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
"push transfer progress: {}/{}/{}",
one,
two,
@ -327,11 +327,11 @@ impl Repository {
match remote.fetch(&[refspec], Some(&mut fetch_opts)) {
Ok(()) => {
tracing::info!(project_id = self.project.id, %refspec, "git fetched");
tracing::info!(project_id = %self.project.id, %refspec, "git fetched");
return Ok(());
}
Err(git::Error::AuthenticationFailed(error)) => {
tracing::error!(project_id = self.project.id, ?error, "fetch failed");
tracing::error!(project_id = %self.project.id, ?error, "fetch failed");
continue;
}
Err(error) => return Err(RemoteError::Other(error.into())),
@ -373,7 +373,7 @@ impl Repository {
)?;
tracing::info!(
project_id = self.project.id,
project_id = %self.project.id,
%commit_oid,
message,
"created commit"

View File

@ -91,7 +91,11 @@ impl From<controller::GetError> for Error {
#[tauri::command(async)]
#[instrument(skip(handle))]
pub async fn get_project(handle: tauri::AppHandle, id: &str) -> Result<projects::Project, Error> {
handle.state::<Controller>().get(id).map_err(Into::into)
let id = id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle.state::<Controller>().get(&id).map_err(Into::into)
}
impl From<controller::ListError> for Error {
@ -125,5 +129,9 @@ impl From<controller::DeleteError> for Error {
#[tauri::command(async)]
#[instrument(skip(handle))]
pub async fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> {
handle.state::<Controller>().delete(id).map_err(Into::into)
let id = id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle.state::<Controller>().delete(&id).map_err(Into::into)
}

View File

@ -6,7 +6,7 @@ use tauri::{AppHandle, Manager};
use crate::{paths::DataDir, watcher};
use super::{storage, storage::UpdateRequest, Project};
use super::{storage, storage::UpdateRequest, Project, ProjectId};
#[derive(Clone)]
pub struct Controller {
@ -65,7 +65,7 @@ impl Controller {
.map_or_else(|| id.clone(), |p| p.to_str().unwrap().to_string());
let project = Project {
id: uuid::Uuid::new_v4().to_string(),
id: ProjectId::generate(),
title,
path: path.to_path_buf(),
api: None,
@ -98,7 +98,7 @@ impl Controller {
time::SystemTime::now(),
))) {
tracing::error!(
project_id = &project.id,
project_id = %project.id,
?error,
"failed to post fetch project event"
);
@ -109,7 +109,7 @@ impl Controller {
block_on(watchers.post(watcher::Event::PushGitbutlerData(project.id.clone())))
{
tracing::error!(
project_id = &project.id,
project_id = %project.id,
?error,
"failed to post push project event"
);
@ -120,7 +120,7 @@ impl Controller {
Ok(updated)
}
pub fn get(&self, id: &str) -> Result<Project, GetError> {
pub fn get(&self, id: &ProjectId) -> Result<Project, GetError> {
self.projects_storage.get(id).map_err(|error| match error {
super::storage::Error::NotFound => GetError::NotFound,
error => GetError::Other(error.into()),
@ -133,7 +133,7 @@ impl Controller {
.map_err(|error| ListError::Other(error.into()))
}
pub fn delete(&self, id: &str) -> Result<(), DeleteError> {
pub fn delete(&self, id: &ProjectId) -> Result<(), DeleteError> {
let project = match self.projects_storage.get(id) {
Ok(project) => Ok(project),
Err(super::storage::Error::NotFound) => return Ok(()),
@ -143,7 +143,7 @@ impl Controller {
if let Some(watchers) = &self.watchers {
if let Err(error) = block_on(watchers.stop(id)) {
tracing::error!(
project_id = id,
project_id = %id,
?error,
"failed to stop watcher for project",
);
@ -158,9 +158,9 @@ impl Controller {
self.local_data_dir
.to_path_buf()
.join("projects")
.join(&project.id),
.join(&project.id.to_string()),
) {
tracing::error!(project_id = id, ?error, "failed to remove project data",);
tracing::error!(project_id = %id, ?error, "failed to remove project data",);
}
Ok(())

View File

@ -4,5 +4,5 @@ mod project;
mod storage;
pub use controller::*;
pub use project::{ApiProject, AuthKey, FetchResult, Project};
pub use project::{ApiProject, AuthKey, FetchResult, Project, ProjectId};
pub use storage::UpdateRequest;

View File

@ -3,6 +3,8 @@ use std::{path, time};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::id::Id;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AuthKey {
@ -79,9 +81,11 @@ impl FetchResult {
}
}
pub type ProjectId = Id<Project>;
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct Project {
pub id: String,
pub id: ProjectId,
pub title: String,
pub description: Option<String>,
pub path: path::PathBuf,

View File

@ -1,7 +1,11 @@
use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Manager};
use crate::{paths::DataDir, projects::project, storage};
use crate::{
paths::DataDir,
projects::{project, ProjectId},
storage,
};
const PROJECTS_FILE: &str = "projects.json";
@ -32,7 +36,7 @@ impl From<&AppHandle> for Storage {
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct UpdateRequest {
pub id: String,
pub id: ProjectId,
pub title: Option<String>,
pub description: Option<String>,
pub api: Option<project::ApiProject>,
@ -74,9 +78,9 @@ impl Storage {
}
}
pub fn get(&self, id: &str) -> Result<project::Project, Error> {
pub fn get(&self, id: &ProjectId) -> Result<project::Project, Error> {
let projects = self.list()?;
match projects.into_iter().find(|p| p.id == id) {
match projects.into_iter().find(|p| p.id == *id) {
Some(project) => Ok(project),
None => Err(Error::NotFound),
}
@ -125,9 +129,9 @@ impl Storage {
.clone())
}
pub fn purge(&self, id: &str) -> Result<(), Error> {
pub fn purge(&self, id: &ProjectId) -> Result<(), Error> {
let mut projects = self.list()?;
if let Some(index) = projects.iter().position(|p| p.id == id) {
if let Some(index) = projects.iter().position(|p| p.id == *id) {
projects.remove(index);
self.storage
.write(PROJECTS_FILE, &serde_json::to_string_pretty(&projects)?)?;

View File

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

View File

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

View File

@ -17,6 +17,7 @@ use tauri::AppHandle;
use crate::{
bookmarks, deltas, gb_repository,
paths::DataDir,
projects::ProjectId,
reader,
sessions::{self, SessionId},
};
@ -246,7 +247,7 @@ impl SearcherInner {
self.reader.reload()?;
tracing::debug!(
project_id = bookmark.project_id,
project_id = %bookmark.project_id,
timestamp_ms = bookmark.timestamp_ms,
"bookmark added to search",
);
@ -284,7 +285,7 @@ impl SearcherInner {
.set(repository.get_project_id(), &session.id, index::VERSION)?;
tracing::debug!(
project_id = repository.get_project_id(),
project_id = %repository.get_project_id(),
session_id = %session.id,
"session added to search",
);
@ -293,7 +294,7 @@ impl SearcherInner {
}
}
fn build_id(project_id: &str, timestamp_ms: &u128) -> String {
fn build_id(project_id: &ProjectId, timestamp_ms: &u128) -> String {
format!("{}-{}-{}", index::VERSION, project_id, timestamp_ms)
}
@ -302,7 +303,7 @@ const WRITE_BUFFER_SIZE: usize = 10_000_000; // 10MB
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchResult {
pub project_id: String,
pub project_id: ProjectId,
pub session_id: SessionId,
pub file_path: String,
pub index: u64,
@ -395,7 +396,7 @@ fn index_delta(
writer: &mut IndexWriter,
reader: &tantivy::IndexReader,
session_id: &SessionId,
project_id: &str,
project_id: &ProjectId,
file_text: &mut Vec<char>,
file_path: &path::Path,
i: usize,
@ -446,7 +447,7 @@ fn index_delta(
doc.index = Some(i.try_into()?);
doc.session_id = Some(*session_id);
doc.file_path = Some(file_path.display().to_string());
doc.project_id = Some(project_id.to_string());
doc.project_id = Some(*project_id);
doc.timestamp_ms = Some(delta.timestamp_ms.try_into()?);
doc.diff = Some(changes);

View File

@ -4,6 +4,7 @@ use anyhow::Result;
use crate::{
bookmarks, deltas,
projects::ProjectId,
test_utils::{Case, Suite},
};
@ -75,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(),
project_id: *gb_repository.get_project_id(),
timestamp_ms: 123_456,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -114,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(),
project_id: *gb_repository.get_project_id(),
timestamp_ms: 123_456,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -218,7 +219,7 @@ fn search_by_diff() -> Result<()> {
})?;
assert_eq!(result.total, 1);
assert_eq!(result.page[0].session_id, session.id);
assert_eq!(result.page[0].project_id, gb_repository.get_project_id());
assert_eq!(result.page[0].project_id, *gb_repository.get_project_id());
assert_eq!(result.page[0].file_path, "test.txt");
assert_eq!(result.page[0].index, 1);
@ -229,11 +230,12 @@ fn search_by_diff() -> Result<()> {
fn should_index_bookmark_once() -> Result<()> {
let suite = Suite::default();
let searcher = super::Searcher::try_from(&suite.local_app_data).unwrap();
let project_id = ProjectId::generate();
// should not index deleted non-existing bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -245,7 +247,7 @@ fn should_index_bookmark_once() -> Result<()> {
// should index new non deleted bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -257,7 +259,7 @@ fn should_index_bookmark_once() -> Result<()> {
// should not index existing non deleted bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -269,7 +271,7 @@ fn should_index_bookmark_once() -> Result<()> {
// should index existing deleted bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -281,7 +283,7 @@ fn should_index_bookmark_once() -> Result<()> {
// should not index existing deleted bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -361,7 +363,7 @@ fn search_bookmark_by_phrase() -> Result<()> {
searcher.index_session(&gb_repository, &session)?;
searcher.index_bookmark(&bookmarks::Bookmark {
project_id: gb_repository.get_project_id().to_string(),
project_id: *gb_repository.get_project_id(),
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -428,7 +430,7 @@ fn search_by_filename() -> Result<()> {
.page;
assert_eq!(found_result.len(), 2);
assert_eq!(found_result[0].session_id, session.id);
assert_eq!(found_result[0].project_id, gb_repository.get_project_id());
assert_eq!(found_result[0].project_id, *gb_repository.get_project_id());
assert_eq!(found_result[0].file_path, "test.txt");
let not_found_result = searcher.search(&super::Query {

View File

@ -1,7 +1,7 @@
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::database;
use crate::{database, projects::ProjectId};
use super::session::{self, SessionId};
@ -25,7 +25,7 @@ impl From<&AppHandle> for Database {
}
impl Database {
pub fn insert(&self, project_id: &str, sessions: &[&session::Session]) -> Result<()> {
pub fn insert(&self, project_id: &ProjectId, sessions: &[&session::Session]) -> Result<()> {
self.database.transaction(|tx| -> Result<()> {
let mut stmt = insert_stmt(tx).context("Failed to prepare insert statement")?;
for session in sessions {
@ -48,7 +48,7 @@ impl Database {
pub fn list_by_project_id(
&self,
project_id: &str,
project_id: &ProjectId,
earliest_timestamp_ms: Option<u128>,
) -> Result<Vec<session::Session>> {
self.database.transaction(|tx| {
@ -81,7 +81,7 @@ impl Database {
pub fn get_by_project_id_id(
&self,
project_id: &str,
project_id: &ProjectId,
id: &SessionId,
) -> Result<Option<session::Session>> {
self.database.transaction(|tx| {
@ -199,7 +199,7 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let project_id = ProjectId::generate();
let session1 = session::Session {
id: SessionId::generate(),
hash: None,
@ -222,10 +222,10 @@ mod tests {
};
let sessions = vec![&session1, &session2];
database.insert(project_id, &sessions)?;
database.insert(&project_id, &sessions)?;
assert_eq!(
database.list_by_project_id(project_id, None)?,
database.list_by_project_id(&project_id, None)?,
vec![session2.clone(), session1.clone()]
);
assert_eq!(database.get_by_id(&session1.id)?.unwrap(), session1);
@ -240,7 +240,7 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let project_id = ProjectId::generate();
let session = session::Session {
id: SessionId::generate(),
hash: None,
@ -261,11 +261,11 @@ mod tests {
last_timestamp_ms: 4,
},
};
database.insert(project_id, &[&session])?;
database.insert(project_id, &[&session_updated])?;
database.insert(&project_id, &[&session])?;
database.insert(&project_id, &[&session_updated])?;
assert_eq!(
database.list_by_project_id(project_id, None)?,
database.list_by_project_id(&project_id, None)?,
vec![session_updated.clone()]
);
assert_eq!(database.get_by_id(&session.id)?.unwrap(), session_updated);

View File

@ -2,7 +2,11 @@ use anyhow::Context;
use tauri::{AppHandle, Manager};
use tracing::instrument;
use crate::{assets, error::Error, git};
use crate::{
assets,
error::{Code, Error},
git,
};
use super::{
branch::Ownership,
@ -38,9 +42,13 @@ pub async fn commit_virtual_branch(
message: &str,
ownership: Option<Ownership>,
) -> Result<git::Oid, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.create_commit(project_id, branch, message, ownership.as_ref())
.create_commit(&project_id, branch, message, ownership.as_ref())
.await
.map_err(Into::into)
}
@ -51,9 +59,13 @@ pub async fn list_virtual_branches(
handle: AppHandle,
project_id: &str,
) -> Result<Vec<super::VirtualBranch>, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.list_virtual_branches(project_id)
.list_virtual_branches(&project_id)
.await
.map_err(Into::into)
}
@ -65,9 +77,13 @@ pub async fn create_virtual_branch(
project_id: &str,
branch: super::branch::BranchCreateRequest,
) -> Result<String, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.create_virtual_branch(project_id, &branch)
.create_virtual_branch(&project_id, &branch)
.await
.map_err(Into::into)
}
@ -79,9 +95,13 @@ pub async fn create_virtual_branch_from_branch(
project_id: &str,
branch: git::BranchName,
) -> Result<String, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.create_virtual_branch_from_branch(project_id, &branch)
.create_virtual_branch_from_branch(&project_id, &branch)
.await
.map_err(Into::into)
}
@ -93,9 +113,13 @@ pub async fn merge_virtual_branch_upstream(
project_id: &str,
branch: &str,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.merge_virtual_branch_upstream(project_id, branch)
.merge_virtual_branch_upstream(&project_id, branch)
.await
.map_err(Into::into)
}
@ -106,9 +130,13 @@ pub async fn get_base_branch_data(
handle: AppHandle,
project_id: &str,
) -> Result<Option<super::BaseBranch>, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
if let Some(base_branch) = handle
.state::<Controller>()
.get_base_branch_data(project_id)?
.get_base_branch_data(&project_id)?
{
let proxy = handle.state::<assets::Proxy>();
let base_branch = proxy.proxy_base_branch(&base_branch).await;
@ -125,12 +153,16 @@ pub async fn set_base_branch(
project_id: &str,
branch: &str,
) -> Result<super::BaseBranch, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_name = format!("refs/remotes/{}", branch)
.parse()
.context("Invalid branch name")?;
let base_branch = handle
.state::<Controller>()
.set_base_branch(project_id, &branch_name)?;
.set_base_branch(&project_id, &branch_name)?;
let base_branch = handle
.state::<assets::Proxy>()
.proxy_base_branch(&base_branch)
@ -141,9 +173,13 @@ pub async fn set_base_branch(
#[tauri::command(async)]
#[instrument(skip(handle))]
pub async fn update_base_branch(handle: AppHandle, project_id: &str) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle
.state::<Controller>()
.update_base_branch(project_id)
.update_base_branch(&project_id)
.await
.map_err(Into::into)
}
@ -155,9 +191,13 @@ pub async fn update_virtual_branch(
project_id: &str,
branch: super::branch::BranchUpdateRequest,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.update_virtual_branch(project_id, branch)
.update_virtual_branch(&project_id, branch)
.await
.map_err(Into::into)
}
@ -169,9 +209,13 @@ pub async fn delete_virtual_branch(
project_id: &str,
branch_id: &str,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.delete_virtual_branch(project_id, branch_id)
.delete_virtual_branch(&project_id, branch_id)
.await
.map_err(Into::into)
}
@ -179,9 +223,13 @@ pub async fn delete_virtual_branch(
#[tauri::command(async)]
#[instrument(skip(handle))]
pub async fn apply_branch(handle: AppHandle, project_id: &str, branch: &str) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.apply_virtual_branch(project_id, branch)
.apply_virtual_branch(&project_id, branch)
.await
.map_err(Into::into)
}
@ -193,9 +241,13 @@ pub async fn unapply_branch(
project_id: &str,
branch: &str,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.unapply_virtual_branch(project_id, branch)
.unapply_virtual_branch(&project_id, branch)
.await
.map_err(Into::into)
}
@ -207,9 +259,13 @@ pub async fn unapply_ownership(
project_id: &str,
ownership: Ownership,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.unapply_ownership(project_id, &ownership)
.unapply_ownership(&project_id, &ownership)
.await
.map_err(Into::into)
}
@ -222,9 +278,13 @@ pub async fn push_virtual_branch(
branch_id: &str,
with_force: bool,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.push_virtual_branch(project_id, branch_id, with_force)
.push_virtual_branch(&project_id, branch_id, with_force)
.await
.map_err(Into::into)
}
@ -236,9 +296,13 @@ pub async fn can_apply_virtual_branch(
project_id: &str,
branch_id: &str,
) -> Result<bool, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.can_apply_virtual_branch(project_id, branch_id)
.can_apply_virtual_branch(&project_id, branch_id)
.map_err(Into::into)
}
@ -249,9 +313,13 @@ pub async fn can_apply_remote_branch(
project_id: &str,
branch: git::BranchName,
) -> Result<bool, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.can_apply_remote_branch(project_id, &branch)
.can_apply_remote_branch(&project_id, &branch)
.map_err(Into::into)
}
@ -262,9 +330,13 @@ pub async fn list_remote_commit_files(
project_id: &str,
commit_oid: git::Oid,
) -> Result<Vec<RemoteBranchFile>, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.list_remote_commit_files(project_id, commit_oid)
.list_remote_commit_files(&project_id, commit_oid)
.map_err(Into::into)
}
@ -276,9 +348,13 @@ pub async fn reset_virtual_branch(
branch_id: &str,
target_commit_oid: git::Oid,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.reset_virtual_branch(project_id, branch_id, target_commit_oid)
.reset_virtual_branch(&project_id, branch_id, target_commit_oid)
.await
.map_err(Into::into)
}

View File

@ -8,7 +8,8 @@ use crate::{
gb_repository, git, keys,
paths::DataDir,
project_repository::{self, conflicts},
projects, users,
projects::{self, ProjectId},
users,
};
use super::{branch::Ownership, RemoteBranchFile};
@ -84,7 +85,7 @@ impl Controller {
pub async fn create_commit(
&self,
project_id: &str,
project_id: &ProjectId,
branch: &str,
message: &str,
ownership: Option<&Ownership>,
@ -124,7 +125,7 @@ impl Controller {
pub fn can_apply_remote_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_name: &git::BranchName,
) -> Result<bool, Error> {
let project = self.projects.get(project_id)?;
@ -142,7 +143,7 @@ impl Controller {
pub fn can_apply_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_id: &str,
) -> Result<bool, Error> {
let project = self.projects.get(project_id)?;
@ -160,7 +161,7 @@ impl Controller {
pub async fn list_virtual_branches(
&self,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<super::VirtualBranch>, Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, _| {
@ -173,7 +174,7 @@ impl Controller {
pub async fn create_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
create: &super::branch::BranchCreateRequest,
) -> Result<String, Error> {
self.with_lock(project_id, || {
@ -192,7 +193,7 @@ impl Controller {
pub async fn create_virtual_branch_from_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch: &git::BranchName,
) -> Result<String, Error> {
self.with_lock::<Result<String, Error>>(project_id, || {
@ -237,7 +238,7 @@ impl Controller {
pub fn get_base_branch_data(
&self,
project_id: &str,
project_id: &ProjectId,
) -> Result<Option<super::BaseBranch>, Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
@ -255,7 +256,7 @@ impl Controller {
pub fn list_remote_commit_files(
&self,
project_id: &str,
project_id: &ProjectId,
commit_oid: git::Oid,
) -> Result<Vec<RemoteBranchFile>, Error> {
let project = self.projects.get(project_id)?;
@ -270,7 +271,7 @@ impl Controller {
pub fn set_base_branch(
&self,
project_id: &str,
project_id: &ProjectId,
target_branch: &git::RemoteBranchName,
) -> Result<super::BaseBranch, Error> {
let project = self.projects.get(project_id)?;
@ -298,7 +299,7 @@ impl Controller {
pub async fn merge_virtual_branch_upstream(
&self,
project_id: &str,
project_id: &ProjectId,
branch: &str,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -335,7 +336,7 @@ impl Controller {
.await
}
pub async fn update_base_branch(&self, project_id: &str) -> Result<(), Error> {
pub async fn update_base_branch(&self, project_id: &ProjectId) -> Result<(), Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, user| {
super::update_base_branch(gb_repository, project_repository, user)
@ -347,7 +348,7 @@ impl Controller {
pub async fn update_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_update: super::branch::BranchUpdateRequest,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -361,7 +362,7 @@ impl Controller {
pub async fn delete_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_id: &str,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -375,7 +376,7 @@ impl Controller {
pub async fn apply_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_id: &str,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -408,7 +409,7 @@ impl Controller {
pub async fn unapply_ownership(
&self,
project_id: &str,
project_id: &ProjectId,
ownership: &Ownership,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -422,7 +423,7 @@ impl Controller {
pub async fn reset_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_id: &str,
target_commit_oid: git::Oid,
) -> Result<(), Error> {
@ -442,7 +443,7 @@ impl Controller {
pub async fn unapply_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_id: &str,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -456,7 +457,7 @@ impl Controller {
pub async fn push_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_id: &str,
with_force: bool,
) -> Result<(), Error> {
@ -497,7 +498,7 @@ impl Controller {
fn with_verify_branch<T>(
&self,
project_id: &str,
project_id: &ProjectId,
action: impl FnOnce(
&gb_repository::Repository,
&project_repository::Repository,
@ -518,7 +519,7 @@ impl Controller {
action(&gb_repository, &project_repository, user.as_ref())
}
async fn with_lock<T>(&self, project_id: &str, action: impl FnOnce() -> T) -> T {
async fn with_lock<T>(&self, project_id: &ProjectId, action: impl FnOnce() -> T) -> T {
let mut semaphores = self.semaphores.lock().await;
let semaphore = semaphores
.entry(project_id.to_string())

View File

@ -11,7 +11,7 @@ use tokio::{
task,
};
use crate::{git, watcher::events};
use crate::{git, projects::ProjectId, watcher::events};
#[derive(Debug, Clone)]
pub struct Dispatcher {
@ -29,7 +29,7 @@ impl Dispatcher {
self.watcher.lock().unwrap().take();
}
pub fn run(self, project_id: &str, path: &path::Path) -> Result<Receiver<events::Event>> {
pub fn run(self, project_id: &ProjectId, path: &path::Path) -> Result<Receiver<events::Event>> {
let repo = git::Repository::open(path)
.with_context(|| format!("failed to open project repository: {}", path.display()))?;
@ -64,10 +64,9 @@ impl Dispatcher {
.with_context(|| format!("failed to watch project path: {}", path.display()))?;
self.watcher.lock().unwrap().replace(watcher);
tracing::debug!(project_id, "file watcher started");
tracing::debug!(%project_id, "file watcher started");
let (tx, rx) = channel(1);
let project_id = project_id.to_string();
task::Builder::new()
.name(&format!("{} file watcher", project_id))
.spawn({
@ -79,12 +78,12 @@ impl Dispatcher {
Ok(relative_file_path) => {
let event = if relative_file_path.starts_with(".git") {
tracing::info!(
project_id,
%project_id,
file_path = %relative_file_path.display(),
"git file change",
);
events::Event::GitFileChange(
project_id.clone(),
project_id,
relative_file_path
.strip_prefix(".git")
.unwrap()
@ -92,29 +91,29 @@ impl Dispatcher {
)
} else {
tracing::info!(
project_id,
%project_id,
file_path = %relative_file_path.display(),
"project file change",
);
events::Event::ProjectFileChange(
project_id.clone(),
project_id,
relative_file_path.to_path_buf(),
)
};
if let Err(error) = tx.send(event).await {
tracing::error!(
project_id,
%project_id,
?error,
"failed to send file change event",
);
}
}
Err(error) => {
tracing::error!(project_id, ?error, "failed to strip prefix");
tracing::error!(%project_id, ?error, "failed to strip prefix");
}
}
}
tracing::debug!(project_id, "file watcher stopped");
tracing::debug!(%project_id, "file watcher stopped");
}
})?;

View File

@ -11,6 +11,8 @@ use tokio::{
};
use tokio_util::sync::CancellationToken;
use crate::projects::ProjectId;
use super::events;
#[derive(Clone)]
@ -36,7 +38,7 @@ impl Dispatcher {
pub fn run<P: AsRef<path::Path>>(
self,
project_id: &str,
project_id: &ProjectId,
path: P,
) -> Result<Receiver<events::Event>> {
let path = path.as_ref();
@ -66,17 +68,17 @@ impl Dispatcher {
}
Some(event) = tick_rx.recv() => {
if let Err(error) = tx.send(event).await {
tracing::error!(project_id, ?error,"failed to send tick");
tracing::error!(%project_id, ?error,"failed to send tick");
}
}
Some(event) = file_change_rx.recv() => {
if let Err(error) = tx.send(event).await {
tracing::error!( project_id, ?error,"failed to send file change");
tracing::error!(%project_id, ?error,"failed to send file change");
}
}
}
}
tracing::debug!(project_id, "dispatcher stopped");
tracing::debug!(%project_id, "dispatcher stopped");
})?;
Ok(rx)

View File

@ -7,7 +7,7 @@ use tokio::{
};
use tokio_util::sync::CancellationToken;
use crate::watcher::events;
use crate::{projects::ProjectId, watcher::events};
#[derive(Debug, Clone)]
pub struct Dispatcher {
@ -27,7 +27,7 @@ impl Dispatcher {
pub fn run(
self,
project_id: &str,
project_id: &ProjectId,
interval: time::Duration,
) -> Result<Receiver<events::Event>> {
let (tx, rx) = channel(1);
@ -36,25 +36,22 @@ impl Dispatcher {
task::Builder::new()
.name(&format!("{} ticker", project_id))
.spawn({
let project_id = project_id.to_string();
let project_id = *project_id;
async move {
tracing::debug!(project_id, "ticker started");
tracing::debug!(%project_id, "ticker started");
loop {
ticker.tick().await;
if self.cancellation_token.is_cancelled() {
break;
}
if let Err(error) = tx
.send(events::Event::Tick(
project_id.clone(),
time::SystemTime::now(),
))
.send(events::Event::Tick(project_id, time::SystemTime::now()))
.await
{
tracing::error!(project_id, ?error, "failed to send tick");
tracing::error!(%project_id, ?error, "failed to send tick");
}
}
tracing::debug!(project_id, "ticker stopped");
tracing::debug!(%project_id, "ticker stopped");
}
})?;
@ -64,14 +61,17 @@ impl Dispatcher {
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use super::*;
#[tokio::test]
async fn test_ticker() {
let dispatcher = Dispatcher::new();
let dispatcher2 = dispatcher.clone();
let mut rx = dispatcher2.run("test", Duration::from_millis(10)).unwrap();
let mut rx = dispatcher2
.run(&ProjectId::generate(), Duration::from_millis(10))
.unwrap();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;

View File

@ -1,36 +1,38 @@
use std::{fmt::Display, path, time};
use crate::{
analytics, bookmarks, deltas, events, reader,
analytics, bookmarks, deltas, events,
projects::ProjectId,
reader,
sessions::{self, SessionId},
};
#[derive(Debug, PartialEq, Clone)]
pub enum Event {
Tick(String, time::SystemTime),
Flush(String, sessions::Session),
Tick(ProjectId, time::SystemTime),
Flush(ProjectId, sessions::Session),
FetchGitbutlerData(String, time::SystemTime),
PushGitbutlerData(String),
FetchProjectData(String, time::SystemTime),
FetchGitbutlerData(ProjectId, time::SystemTime),
PushGitbutlerData(ProjectId),
FetchProjectData(ProjectId, time::SystemTime),
GitFileChange(String, path::PathBuf),
GitFileChange(ProjectId, path::PathBuf),
ProjectFileChange(String, path::PathBuf),
ProjectFileChange(ProjectId, path::PathBuf),
Session(String, sessions::Session),
SessionFile((String, SessionId, path::PathBuf, Option<reader::Content>)),
SessionDelta((String, SessionId, path::PathBuf, deltas::Delta)),
Session(ProjectId, sessions::Session),
SessionFile((ProjectId, SessionId, path::PathBuf, Option<reader::Content>)),
SessionDelta((ProjectId, SessionId, path::PathBuf, deltas::Delta)),
Bookmark(bookmarks::Bookmark),
IndexAll(String),
IndexAll(ProjectId),
Emit(events::Event),
Analytics(analytics::Event),
}
impl Event {
pub fn project_id(&self) -> &str {
pub fn project_id(&self) -> &ProjectId {
match self {
Event::Analytics(event) => event.project_id(),
Event::Emit(event) => event.project_id(),

View File

@ -6,8 +6,8 @@ use std::{
use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::paths::DataDir;
use crate::{gb_repository, project_repository, projects, users};
use crate::{paths::DataDir, projects::ProjectId};
use super::events;
@ -28,7 +28,11 @@ impl TryFrom<&AppHandle> for Handler {
}
impl Handler {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
self.inner.handle(project_id, now)
}
}
@ -59,7 +63,11 @@ impl TryFrom<&AppHandle> for HandlerInner {
}
impl HandlerInner {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
let _lock = match self.mutex.try_lock() {
Ok(lock) => lock,
Err(TryLockError::Poisoned(_)) => return Err(anyhow::anyhow!("mutex poisoned")),
@ -71,7 +79,7 @@ impl HandlerInner {
// mark fetching
self.projects
.update(&projects::UpdateRequest {
id: project_id.to_string(),
id: *project_id,
gitbutler_data_last_fetched: Some(projects::FetchResult::Fetching {
timestamp_ms: now.duration_since(time::UNIX_EPOCH)?.as_millis(),
}),
@ -98,7 +106,7 @@ impl HandlerInner {
.collect::<Vec<_>>();
let fetch_result = if let Err(error) = gb_repo.fetch(user.as_ref()) {
tracing::error!(project_id, ?error, "failed to fetch gitbutler data");
tracing::error!(%project_id, ?error, "failed to fetch gitbutler data");
projects::FetchResult::Error {
attempt: project
.gitbutler_data_last_fetched
@ -119,7 +127,7 @@ impl HandlerInner {
self.projects
.update(&projects::UpdateRequest {
id: project_id.to_string(),
id: *project_id,
gitbutler_data_last_fetched: Some(fetch_result),
..Default::default()
})
@ -138,7 +146,7 @@ impl HandlerInner {
let events = new_sessions
.into_iter()
.cloned()
.map(|session| events::Event::Session(project_id.to_string(), session))
.map(|session| events::Event::Session(*project_id, session))
.collect::<Vec<_>>();
Ok(events)

View File

@ -6,7 +6,13 @@ use std::{
use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::{gb_repository, keys, paths::DataDir, project_repository, projects, users};
use crate::{
gb_repository, keys,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
users,
};
use super::events;
@ -27,7 +33,11 @@ impl TryFrom<&AppHandle> for Handler {
}
impl Handler {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
self.inner.handle(project_id, now)
}
}
@ -59,7 +69,11 @@ impl TryFrom<&AppHandle> for HandlerInner {
}
impl HandlerInner {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
let _lock = match self.mutex.try_lock() {
Ok(lock) => lock,
Err(TryLockError::Poisoned(_)) => return Err(anyhow::anyhow!("mutex poisoned")),
@ -71,7 +85,7 @@ impl HandlerInner {
// mark fetching
self.projects
.update(&projects::UpdateRequest {
id: project_id.to_string(),
id: *project_id,
project_data_last_fetched: Some(projects::FetchResult::Fetching {
timestamp_ms: now.duration_since(time::UNIX_EPOCH)?.as_millis(),
}),
@ -109,7 +123,7 @@ impl HandlerInner {
let fetch_result =
if let Err(error) = project_repository.fetch(default_target.branch.remote(), &key) {
tracing::error!(project_id, ?error, "failed to fetch project data");
tracing::error!(%project_id, ?error, "failed to fetch project data");
projects::FetchResult::Error {
attempt: project
.project_data_last_fetched
@ -130,7 +144,7 @@ impl HandlerInner {
self.projects
.update(&projects::UpdateRequest {
id: project_id.to_string(),
id: *project_id,
project_data_last_fetched: Some(fetch_result),
..Default::default()
})

View File

@ -1,7 +1,13 @@
use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::{gb_repository, paths::DataDir, project_repository, projects, sessions, users};
use crate::{
gb_repository,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
sessions, users,
};
use super::events;
@ -27,7 +33,7 @@ impl TryFrom<&AppHandle> for Handler {
impl Handler {
pub fn handle(
&self,
project_id: &str,
project_id: &ProjectId,
session: &sessions::Session,
) -> Result<Vec<events::Event>> {
let project = self
@ -50,8 +56,8 @@ impl Handler {
.context("failed to flush session")?;
Ok(vec![
events::Event::Session(project_id.to_string(), session),
events::Event::PushGitbutlerData(project_id.to_string()),
events::Event::Session(*project_id, session),
events::Event::PushGitbutlerData(*project_id),
])
}
}

View File

@ -2,7 +2,10 @@ use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::{
analytics, events as app_events, gb_repository, paths::DataDir, project_repository, projects,
analytics, events as app_events, gb_repository,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
users,
};
@ -31,7 +34,7 @@ impl Handler {
pub fn handle<P: AsRef<std::path::Path>>(
&self,
path: P,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<events::Event>> {
let project = self
.projects
@ -61,7 +64,7 @@ impl Handler {
if file_path.exists() {
if let Err(e) = std::fs::remove_file(&file_path) {
tracing::error!(project_id, path = %file_path.display(), "GB_FLUSH file delete error: {}", e);
tracing::error!(%project_id, path = %file_path.display(), "GB_FLUSH file delete error: {}", e);
}
if let Some(current_session) = gb_repo
@ -85,7 +88,7 @@ impl Handler {
if let Some(head) = head_ref.name() {
Ok(vec![
events::Event::Analytics(analytics::Event::HeadChange {
project_id: project.id.clone(),
project_id: project.id,
reference_name: head_ref_name.to_string(),
}),
events::Event::Emit(app_events::Event::git_head(&project.id, head)),

View File

@ -6,7 +6,9 @@ use tauri::{AppHandle, Manager};
use crate::{
bookmarks, deltas, events as app_events, gb_repository,
paths::DataDir,
project_repository, projects, search,
project_repository,
projects::{self, ProjectId},
search,
sessions::{self, SessionId},
users,
};
@ -43,7 +45,7 @@ impl TryFrom<&AppHandle> for Handler {
impl Handler {
pub fn index_deltas(
&self,
project_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
file_path: &path::Path,
deltas: &Vec<deltas::Delta>,
@ -56,7 +58,7 @@ impl Handler {
pub fn index_bookmark(
&self,
project_id: &str,
project_id: &ProjectId,
bookmark: &bookmarks::Bookmark,
) -> Result<Vec<events::Event>> {
let updated = self.bookmarks_database.upsert(bookmark)?;
@ -70,7 +72,7 @@ impl Handler {
}
}
pub fn reindex(&self, project_id: &str) -> Result<Vec<events::Event>> {
pub fn reindex(&self, project_id: &ProjectId) -> Result<Vec<events::Event>> {
let user = self.users.get_user()?;
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)
@ -92,7 +94,7 @@ impl Handler {
pub fn index_session(
&self,
project_id: &str,
project_id: &ProjectId,
session: &sessions::Session,
) -> Result<Vec<events::Event>> {
let user = self.users.get_user()?;

View File

@ -6,7 +6,8 @@ use tauri::AppHandle;
use crate::{
deltas, gb_repository,
paths::DataDir,
project_repository, projects,
project_repository,
projects::{self, ProjectId},
reader::{self, Reader},
sessions, users,
};
@ -79,7 +80,7 @@ impl Handler {
pub fn handle<P: AsRef<std::path::Path>>(
&self,
path: P,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<events::Event>> {
let project = self
.projects
@ -146,7 +147,7 @@ impl Handler {
.context("failed to calculate new deltas")?;
if new_delta.is_none() {
tracing::debug!(project_id, path = %path.display(), "no new deltas, ignoring");
tracing::debug!(%project_id, path = %path.display(), "no new deltas, ignoring");
return Ok(vec![]);
}
let new_delta = new_delta.as_ref().unwrap();
@ -166,14 +167,14 @@ impl Handler {
Ok(vec![
events::Event::SessionFile((
project_id.to_string(),
*project_id,
current_session.id,
path.to_path_buf(),
latest_file_content,
)),
events::Event::Session(project_id.to_string(), current_session.clone()),
events::Event::Session(*project_id, current_session.clone()),
events::Event::SessionDelta((
project_id.to_string(),
*project_id,
current_session.id,
path.to_path_buf(),
new_delta.clone(),

View File

@ -4,6 +4,7 @@ use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::paths::DataDir;
use crate::projects::ProjectId;
use crate::{gb_repository, project_repository, projects, users};
use super::events;
@ -24,7 +25,7 @@ impl TryFrom<&AppHandle> for Handler {
}
impl Handler {
pub fn handle(&self, project_id: &str) -> Result<Vec<events::Event>> {
pub fn handle(&self, project_id: &ProjectId) -> Result<Vec<events::Event>> {
self.inner.handle(project_id)
}
}
@ -66,7 +67,7 @@ impl HandlerInner {
}
}
pub fn handle(&self, project_id: &str) -> Result<Vec<events::Event>> {
pub fn handle(&self, project_id: &ProjectId) -> Result<Vec<events::Event>> {
let _lock = match self.mutex.try_lock() {
Ok(lock) => lock,
Err(TryLockError::Poisoned(_)) => return Err(anyhow::anyhow!("mutex poisoned")),

View File

@ -3,7 +3,13 @@ use std::time;
use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::{gb_repository, paths::DataDir, project_repository, projects, sessions, users};
use crate::{
gb_repository,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
sessions, users,
};
use super::events;
@ -27,7 +33,11 @@ impl TryFrom<&AppHandle> for Handler {
}
impl Handler {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
let user = self.users.get_user()?;
let project = self.projects.get(project_id)?;
@ -48,10 +58,7 @@ impl Handler {
.map_or(Ok(true), |f| f.should_fetch(now))
.context("failed to check if gitbutler data should be fetched")?
{
events.push(events::Event::FetchGitbutlerData(
project_id.to_string(),
*now,
));
events.push(events::Event::FetchGitbutlerData(*project_id, *now));
}
if project
@ -60,10 +67,7 @@ impl Handler {
.map_or(Ok(true), |f| f.should_fetch(now))
.context("failed to check if project data should be fetched")?
{
events.push(events::Event::FetchProjectData(
project_id.to_string(),
*now,
));
events.push(events::Event::FetchProjectData(*project_id, *now));
}
if let Some(current_session) = gb_repo
@ -71,10 +75,7 @@ impl Handler {
.context("failed to get current session")?
{
if should_flush(now, &current_session)? {
events.push(events::Event::Flush(
project_id.to_string(),
current_session,
));
events.push(events::Event::Flush(*project_id, current_session));
}
}

View File

@ -17,12 +17,12 @@ use tokio::{
};
use tokio_util::sync::CancellationToken;
use crate::projects;
use crate::projects::{self, ProjectId};
#[derive(Clone)]
pub struct Watchers {
app_handle: AppHandle,
watchers: Arc<Mutex<HashMap<String, Watcher>>>,
watchers: Arc<Mutex<HashMap<ProjectId, Watcher>>>,
}
impl TryFrom<&AppHandle> for Watchers {
@ -48,15 +48,15 @@ impl Watchers {
.name(&format!("{} watcher", project_id))
.spawn(async move {
if let Err(error) = c_watcher.run(&project_path, &project_id).await {
tracing::error!(?error, project_id, "watcher error");
tracing::error!(?error, %project_id, "watcher error");
}
tracing::debug!(project_id, "watcher stopped");
tracing::debug!(%project_id, "watcher stopped");
})?;
self.watchers
.lock()
.await
.insert(project.id.clone(), watcher.clone());
.insert(project.id, watcher.clone());
Ok(())
}
@ -73,7 +73,7 @@ impl Watchers {
}
}
pub async fn stop(&self, project_id: &str) -> Result<()> {
pub async fn stop(&self, project_id: &ProjectId) -> Result<()> {
if let Some((_, watcher)) = self.watchers.lock().await.remove_entry(project_id) {
watcher.stop();
};
@ -105,7 +105,7 @@ impl Watcher {
self.inner.post(event).await
}
pub async fn run<P: AsRef<path::Path>>(&self, path: P, project_id: &str) -> Result<()> {
pub async fn run<P: AsRef<path::Path>>(&self, path: P, project_id: &ProjectId) -> Result<()> {
self.inner.run(path, project_id).await
}
}
@ -149,7 +149,7 @@ impl WatcherInner {
}
}
pub async fn run<P: AsRef<path::Path>>(&self, path: P, project_id: &str) -> Result<()> {
pub async fn run<P: AsRef<path::Path>>(&self, path: P, project_id: &ProjectId) -> Result<()> {
let (proxy_tx, mut proxy_rx) = unbounded_channel();
self.proxy_tx.lock().await.replace(proxy_tx.clone());
@ -159,7 +159,7 @@ impl WatcherInner {
.context("failed to run dispatcher")?;
proxy_tx
.send(Event::IndexAll(project_id.to_string()))
.send(Event::IndexAll(*project_id))
.context("failed to send event")?;
let handle_event = |event: &Event| -> Result<()> {

View File

@ -3,7 +3,7 @@ use std::path;
use tauri::{AppHandle, Manager};
use tracing::instrument;
use crate::error::Error;
use crate::error::{Code, Error};
use super::controller;
@ -25,9 +25,13 @@ pub async fn get_project_archive_path(
handle: AppHandle,
project_id: &str,
) -> Result<path::PathBuf, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle
.state::<controller::Controller>()
.archive(project_id)
.archive(&project_id)
.map_err(Into::into)
}
@ -49,9 +53,13 @@ pub async fn get_project_data_archive_path(
handle: AppHandle,
project_id: &str,
) -> Result<path::PathBuf, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle
.state::<controller::Controller>()
.data_archive(project_id)
.data_archive(&project_id)
.map_err(Into::into)
}

View File

@ -4,7 +4,7 @@ use tauri::AppHandle;
use crate::{
paths::{DataDir, LogsDir},
projects,
projects::{self, ProjectId},
};
use super::Zipper;
@ -32,19 +32,19 @@ impl TryFrom<&AppHandle> for Controller {
}
impl Controller {
pub fn archive(&self, project_id: &str) -> Result<path::PathBuf, ArchiveError> {
pub fn archive(&self, project_id: &ProjectId) -> Result<path::PathBuf, ArchiveError> {
let project = self.projects_controller.get(project_id)?;
self.zipper.zip(project.path).map_err(Into::into)
}
pub fn data_archive(&self, project_id: &str) -> Result<path::PathBuf, DataArchiveError> {
pub fn data_archive(&self, project_id: &ProjectId) -> Result<path::PathBuf, DataArchiveError> {
let project = self.projects_controller.get(project_id)?;
self.zipper
.zip(
self.local_data_dir
.to_path_buf()
.join("projects")
.join(project.id),
.join(project.id.to_string()),
)
.map_err(Into::into)
}

View File

@ -18,7 +18,6 @@ mod add {
let project = controller.add(path).unwrap();
assert_eq!(project.path, path);
assert_eq!(project.title, path.iter().last().unwrap().to_str().unwrap());
assert_eq!(project.id.len(), 36);
}
mod error {

View File

@ -1,7 +1,9 @@
use std::{fs, str::FromStr};
use gitbutler::{
git, keys, projects, users,
git, keys,
projects::{self, ProjectId},
users,
virtual_branches::{Controller, ControllerError},
};
@ -9,7 +11,7 @@ use crate::{common::TestProject, paths};
struct Test {
repository: TestProject,
project_id: String,
project_id: ProjectId,
controller: Controller,
}