display errors when adding project

This commit is contained in:
Nikita Galaiko 2023-02-24 16:27:28 +01:00
parent a6a3df025e
commit 1ff419a8c8
7 changed files with 168 additions and 209 deletions

1
src-tauri/Cargo.lock generated
View File

@ -1016,6 +1016,7 @@ dependencies = [
"tauri-plugin-log", "tauri-plugin-log",
"tauri-plugin-window-state", "tauri-plugin-window-state",
"tempfile", "tempfile",
"thiserror",
"urlencoding", "urlencoding",
"uuid 1.3.0", "uuid 1.3.0",
"walkdir", "walkdir",

View File

@ -34,6 +34,7 @@ tempfile = "3.3.0"
reqwest = "0.11.14" reqwest = "0.11.14"
md5 = "0.7.0" md5 = "0.7.0"
urlencoding = "2.1.2" urlencoding = "2.1.2"
thiserror = "1.0.38"
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode

View File

@ -11,7 +11,7 @@ mod watchers;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use deltas::Delta; use deltas::Delta;
use log; use log;
use serde::{Deserialize, Serialize}; use serde::{ser::SerializeMap, Serialize};
use std::{collections::HashMap, sync::mpsc, thread}; use std::{collections::HashMap, sync::mpsc, thread};
use storage::Storage; use storage::Storage;
use tauri::{generate_context, Manager}; use tauri::{generate_context, Manager};
@ -19,10 +19,40 @@ use tauri_plugin_log::{
fern::colors::{Color, ColoredLevelConfig}, fern::colors::{Color, ColoredLevelConfig},
LogTarget, LogTarget,
}; };
use thiserror::Error;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Error)]
pub struct Error { pub enum Error {
pub message: String, #[error("{0}")]
ProjectError(projects::CreateError),
#[error("Project already exists")]
ProjectAlreadyExists,
#[error("Something went wrong")]
Unknown,
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("message", &self.to_string())?;
map.end()
}
}
impl From<projects::CreateError> for Error {
fn from(e: projects::CreateError) -> Self {
Error::ProjectError(e)
}
}
impl From<anyhow::Error> for Error {
fn from(e: anyhow::Error) -> Self {
log::error!("{:#}", e);
Error::Unknown
}
} }
const IS_DEV: bool = cfg!(debug_assertions); const IS_DEV: bool = cfg!(debug_assertions);
@ -85,19 +115,11 @@ fn list_sessions(
let users_storage = users::Storage::new(storage); let users_storage = users::Storage::new(storage);
let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id) let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)
.map_err(|e| { .with_context(|| format!("Failed to open repository for project {}", project_id))?;
log::error!("{:#}", e);
Error {
message: "Failed to open project".to_string(),
}
})?;
let sessions = repo.sessions().map_err(|e| { let sessions = repo
log::error!("{:#}", e); .sessions()
Error { .with_context(|| format!("Failed to list sessions for project {}", project_id))?;
message: "Failed to list sessions".to_string(),
}
})?;
Ok(sessions) Ok(sessions)
} }
@ -108,12 +130,10 @@ fn get_user(handle: tauri::AppHandle) -> Result<Option<users::User>, Error> {
let storage = storage::Storage::from_path_resolver(&path_resolver); let storage = storage::Storage::from_path_resolver(&path_resolver);
let users_storage = users::Storage::new(storage); let users_storage = users::Storage::new(storage);
match users_storage.get().map_err(|e| { match users_storage
log::error!("{:#}", e); .get()
Error { .with_context(|| "Failed to get user".to_string())?
message: "Failed to get user".to_string(), {
}
})? {
Some(user) => { Some(user) => {
let local_picture = match proxy_image(handle, &user.picture) { let local_picture = match proxy_image(handle, &user.picture) {
Ok(picture) => picture, Ok(picture) => picture,
@ -140,12 +160,9 @@ fn set_user(handle: tauri::AppHandle, user: users::User) -> Result<(), Error> {
let storage = storage::Storage::from_path_resolver(&path_resolver); let storage = storage::Storage::from_path_resolver(&path_resolver);
let users_storage = users::Storage::new(storage); let users_storage = users::Storage::new(storage);
users_storage.set(&user).map_err(|e| { users_storage
log::error!("{:#}", e); .set(&user)
Error { .with_context(|| "Failed to set user".to_string())?;
message: "Failed to save user".to_string(),
}
})?;
sentry::configure_scope(|scope| scope.set_user(Some(user.clone().into()))); sentry::configure_scope(|scope| scope.set_user(Some(user.clone().into())));
@ -158,12 +175,9 @@ fn delete_user(handle: tauri::AppHandle) -> Result<(), Error> {
let storage = storage::Storage::from_path_resolver(&path_resolver); let storage = storage::Storage::from_path_resolver(&path_resolver);
let users_storage = users::Storage::new(storage); let users_storage = users::Storage::new(storage);
users_storage.delete().map_err(|e| { users_storage
log::error!("{:#}", e); .delete()
Error { .with_context(|| "Failed to delete user".to_string())?;
message: "Failed to delete user".to_string(),
}
})?;
sentry::configure_scope(|scope| scope.set_user(None)); sentry::configure_scope(|scope| scope.set_user(None));
@ -179,12 +193,11 @@ fn update_project(
let storage = storage::Storage::from_path_resolver(&path_resolver); let storage = storage::Storage::from_path_resolver(&path_resolver);
let projects_storage = projects::Storage::new(storage); let projects_storage = projects::Storage::new(storage);
projects_storage.update_project(&project).map_err(|e| { let project = projects_storage
log::error!("{:#}", e); .update_project(&project)
Error { .with_context(|| format!("Failed to update project {}", project.id))?;
message: "Failed to update project".to_string(),
} Ok(project)
})
} }
#[tauri::command] #[tauri::command]
@ -200,63 +213,32 @@ fn add_project(handle: tauri::AppHandle, path: &str) -> Result<projects::Project
users_storage.clone(), users_storage.clone(),
); );
for project in projects_storage.list_projects().map_err(|e| { for project in projects_storage
log::error!("{:#}", e); .list_projects()
Error { .with_context(|| "Failed to list projects".to_string())?
message: "Failed to list projects".to_string(), {
}
})? {
if project.path == path { if project.path == path {
if !project.deleted { if !project.deleted {
return Err(Error { return Err(Error::ProjectAlreadyExists);
message: "Project already exists".to_string(),
});
} else { } else {
projects_storage projects_storage.update_project(&projects::UpdateRequest {
.update_project(&projects::UpdateRequest {
id: project.id.clone(), id: project.id.clone(),
deleted: Some(false), deleted: Some(false),
..Default::default() ..Default::default()
})
.map_err(|e| {
log::error!("{:#}", e);
Error {
message: "Failed to undelete project".to_string(),
}
})?; })?;
return Ok(project); return Ok(project);
} }
} }
} }
let project = projects::Project::from_path(path.to_string()); let project = projects::Project::from_path(path.to_string())?;
if project.is_ok() { projects_storage.add_project(&project)?;
let project = project.unwrap();
projects_storage.add_project(&project).map_err(|e| {
log::error!("{:#}", e);
Error {
message: "Failed to add project".to_string(),
}
})?;
let (tx, rx): (mpsc::Sender<events::Event>, mpsc::Receiver<events::Event>) =
mpsc::channel();
watchers.watch(tx, &project).map_err(|e| {
log::error!("{:#}", e);
Error {
message: "Failed to watch project".to_string(),
}
})?;
let (tx, rx): (mpsc::Sender<events::Event>, mpsc::Receiver<events::Event>) = mpsc::channel();
watchers.watch(tx, &project)?;
watch_events(handle, rx); watch_events(handle, rx);
return Ok(project); Ok(project)
} else {
return Err(Error {
message: "Failed to add project".to_string(),
});
}
} }
#[tauri::command] #[tauri::command]
@ -265,12 +247,9 @@ fn list_projects(handle: tauri::AppHandle) -> Result<Vec<projects::Project>, Err
let storage = storage::Storage::from_path_resolver(&path_resolver); let storage = storage::Storage::from_path_resolver(&path_resolver);
let projects_storage = projects::Storage::new(storage); let projects_storage = projects::Storage::new(storage);
projects_storage.list_projects().map_err(|e| { let projects = projects_storage.list_projects()?;
log::error!("{:#}", e);
Error { Ok(projects)
message: "Failed to list projects".to_string(),
}
})
} }
#[tauri::command] #[tauri::command]
@ -286,37 +265,19 @@ fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> {
users_storage.clone(), users_storage.clone(),
); );
match projects_storage.get_project(id) { match projects_storage.get_project(id)? {
Ok(Some(project)) => { Some(project) => {
watchers.unwatch(project).map_err(|e| { watchers.unwatch(project)?;
log::error!("{:#}", e);
Error {
message: "Failed to unwatch project".to_string(),
}
})?;
projects_storage projects_storage.update_project(&projects::UpdateRequest {
.update_project(&projects::UpdateRequest {
id: id.to_string(), id: id.to_string(),
deleted: Some(true), deleted: Some(true),
..Default::default() ..Default::default()
})
.map_err(|e| {
log::error!("{:#}", e);
Error {
message: "Failed to delete project".to_string(),
}
})?; })?;
Ok(()) Ok(())
} }
Ok(None) => Ok(()), None => Ok(()),
Err(e) => {
log::error!("{:#}", e);
Err(Error {
message: "Failed to get project".to_string(),
})
}
} }
} }
@ -332,20 +293,9 @@ fn list_session_files(
let projects_storage = projects::Storage::new(storage.clone()); let projects_storage = projects::Storage::new(storage.clone());
let users_storage = users::Storage::new(storage); let users_storage = users::Storage::new(storage);
let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id) let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)?;
.map_err(|e| {
log::error!("{:#}", e);
Error {
message: "Failed to open project".to_string(),
}
})?;
let files = repo.files(session_id, paths).map_err(|e| { let files = repo.files(session_id, paths)?;
log::error!("{:#}", e);
Error {
message: "Failed to list files".to_string(),
}
})?;
Ok(files) Ok(files)
} }
@ -361,20 +311,9 @@ fn list_deltas(
let projects_storage = projects::Storage::new(storage.clone()); let projects_storage = projects::Storage::new(storage.clone());
let users_storage = users::Storage::new(storage); let users_storage = users::Storage::new(storage);
let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id) let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)?;
.map_err(|e| {
log::error!("{:#}", e);
Error {
message: "Failed to open project".to_string(),
}
})?;
let deltas = repo.deltas(session_id).map_err(|e| { let deltas = repo.deltas(session_id)?;
log::error!("{:#}", e);
Error {
message: "Failed to list deltas".to_string(),
}
})?;
Ok(deltas) Ok(deltas)
} }

View File

@ -1,6 +1,5 @@
mod project; mod project;
mod storage; mod storage;
pub use project::Project; pub use project::{CreateError, Project};
pub use storage::Storage; pub use storage::{Storage, UpdateRequest};
pub use storage::UpdateRequest;

View File

@ -1,7 +1,7 @@
use std::path::PathBuf; use anyhow::Result;
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ApiProject { pub struct ApiProject {
@ -31,6 +31,16 @@ impl AsRef<Project> for Project {
} }
} }
#[derive(Error, Debug)]
pub enum CreateError {
#[error("{0} does not exist")]
PathNotFound(String),
#[error("{0} is not a directory")]
NotADirectory(String),
#[error("{0} is not a git repository")]
NotAGitRepository(String),
}
impl Project { impl Project {
pub fn refname(&self) -> String { pub fn refname(&self) -> String {
format!("refs/gitbutler-{}/current", self.id) format!("refs/gitbutler-{}/current", self.id)
@ -47,34 +57,40 @@ impl Project {
self.session_path().join("deltas") self.session_path().join("deltas")
} }
pub fn from_path(path: String) -> Result<Self> { pub fn from_path(fpath: String) -> Result<Self, CreateError> {
// make sure path exists // make sure path exists
let path = std::path::Path::new(&path); let path = std::path::Path::new(&fpath);
if !path.exists() { if !path.exists() {
return Err(anyhow!("path {} does not exist", path.display())); return Err(CreateError::PathNotFound(fpath).into());
} }
// make sure path is a directory // make sure path is a directory
if !path.is_dir() { if !path.is_dir() {
return Err(anyhow!("path {} is not a directory", path.display())); return Err(CreateError::NotADirectory(fpath).into());
} }
// make sure it's a git repository // make sure it's a git repository
if !path.join(".git").exists() { if !path.join(".git").exists() {
return Err(anyhow!("path {} is not a git repository", path.display())); return Err(CreateError::NotAGitRepository(fpath).into());
}; };
let id = uuid::Uuid::new_v4().to_string();
// title is the base name of the file // title is the base name of the file
path.into_iter() let title = path
.into_iter()
.last() .last()
.map(|p| p.to_str().unwrap().to_string()) .map(|p| p.to_str().unwrap().to_string())
.map(|title| Self { .unwrap_or_else(|| id.clone());
let project = Project {
id: uuid::Uuid::new_v4().to_string(), id: uuid::Uuid::new_v4().to_string(),
deleted: false, deleted: false,
title, title,
path: path.to_str().unwrap().to_string(), path: path.to_str().unwrap().to_string(),
api: None, api: None,
}) };
.ok_or_else(|| anyhow!("failed to get title from path"))
Ok(project)
} }
} }

View File

@ -1,6 +1,8 @@
<script lang="ts"> <script lang="ts">
import { open } from '@tauri-apps/api/dialog'; import { open } from '@tauri-apps/api/dialog';
import type { LayoutData } from './$types'; import type { LayoutData } from './$types';
import { toasts } from '$lib';
export let data: LayoutData; export let data: LayoutData;
const { projects } = data; const { projects } = data;
@ -14,10 +16,11 @@
if (Array.isArray(selectedPath) && selectedPath.length !== 1) return; if (Array.isArray(selectedPath) && selectedPath.length !== 1) return;
const projectPath = Array.isArray(selectedPath) ? selectedPath[0] : selectedPath; const projectPath = Array.isArray(selectedPath) ? selectedPath[0] : selectedPath;
const projectExists = $projects.some((p) => p.path === projectPath); try {
if (projectExists) return;
await projects.add({ path: projectPath }); await projects.add({ path: projectPath });
} catch (e: any) {
toasts.error(e.message);
}
}; };
</script> </script>