355
crates/app/src/lib.rs
Normal file
355
crates/app/src/lib.rs
Normal file
@@ -0,0 +1,355 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use gotcha_gitea::Client;
|
||||
use thiserror::Error;
|
||||
|
||||
mod activity;
|
||||
mod api;
|
||||
mod diff;
|
||||
mod domain;
|
||||
mod presentation;
|
||||
mod storage;
|
||||
|
||||
use api::*;
|
||||
use domain::*;
|
||||
pub use presentation::*;
|
||||
use storage::*;
|
||||
|
||||
uniffi::setup_scaffolding!();
|
||||
|
||||
#[derive(Debug, Error, uniffi::Error)]
|
||||
pub enum GotchaError {
|
||||
#[error("{message}")]
|
||||
Message { message: String },
|
||||
}
|
||||
|
||||
impl From<String> for GotchaError {
|
||||
fn from(message: String) -> Self {
|
||||
Self::Message { message }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for GotchaError {
|
||||
fn from(message: &str) -> Self {
|
||||
message.to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct Settings {
|
||||
pub issue_status: String,
|
||||
pub pull_status: String,
|
||||
pub appearance: u32,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct GotchaCore {
|
||||
state: Mutex<State>,
|
||||
startup_error: Option<String>,
|
||||
}
|
||||
|
||||
#[uniffi::export(async_runtime = "tokio")]
|
||||
impl GotchaCore {
|
||||
#[uniffi::constructor]
|
||||
pub fn new() -> Arc<Self> {
|
||||
let (preferences, startup_error) = match load_preferences() {
|
||||
Ok(preferences) => (preferences, None),
|
||||
Err(error) => (Preferences::default(), Some(error)),
|
||||
};
|
||||
let active_server = preferences
|
||||
.last_server
|
||||
.filter(|index| *index < preferences.servers.len())
|
||||
.or_else(|| (preferences.servers.len() == 1).then_some(0));
|
||||
Arc::new(Self {
|
||||
state: Mutex::new(State {
|
||||
preferences,
|
||||
active_server,
|
||||
..Default::default()
|
||||
}),
|
||||
startup_error,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn startup_error(&self) -> Option<String> {
|
||||
self.startup_error.clone()
|
||||
}
|
||||
|
||||
pub fn servers(&self) -> Vec<ServerRow> {
|
||||
self.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.preferences
|
||||
.servers
|
||||
.iter()
|
||||
.map(|server| ServerRow {
|
||||
name: server.name.clone(),
|
||||
url: server.url.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn active_server_index(&self) -> Option<u32> {
|
||||
self.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.active_server
|
||||
.map(|index| index as u32)
|
||||
}
|
||||
|
||||
pub fn active_server_name(&self) -> Option<String> {
|
||||
let state = self.state.lock().unwrap();
|
||||
state
|
||||
.active_server
|
||||
.and_then(|index| state.preferences.servers.get(index))
|
||||
.map(|server| server.name.clone())
|
||||
}
|
||||
|
||||
pub fn select_server(&self, index: u32) -> Result<(), GotchaError> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let index = index as usize;
|
||||
if index >= state.preferences.servers.len() {
|
||||
return Err("That server no longer exists.".into());
|
||||
}
|
||||
state.active_server = Some(index);
|
||||
state.preferences.last_server = Some(index);
|
||||
state.repositories.clear();
|
||||
save_preferences(&state.preferences).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn add_server(
|
||||
&self,
|
||||
name: String,
|
||||
url: String,
|
||||
token: String,
|
||||
) -> Result<u32, GotchaError> {
|
||||
let server = validate_server(&name, &url, &token)?;
|
||||
Client::new(&server.url, Some(&server.token))
|
||||
.map_err(|error| error.to_string())?
|
||||
.current_user()
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
save_server_token(&server)?;
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.preferences.servers.push(server);
|
||||
let index = state.preferences.servers.len() - 1;
|
||||
state.preferences.last_server = Some(index);
|
||||
state.active_server = Some(index);
|
||||
save_preferences(&state.preferences)?;
|
||||
Ok(index as u32)
|
||||
}
|
||||
|
||||
pub fn settings(&self) -> Settings {
|
||||
let state = self.state.lock().unwrap();
|
||||
Settings {
|
||||
issue_status: state.preferences.issue_status.clone(),
|
||||
pull_status: state.preferences.pull_status.clone(),
|
||||
appearance: state.preferences.appearance.index() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_issue_status(&self, status: String) -> Result<(), GotchaError> {
|
||||
if !matches!(status.as_str(), "open" | "closed") {
|
||||
return Err("Unsupported issue status.".into());
|
||||
}
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.preferences.issue_status = status;
|
||||
save_preferences(&state.preferences).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn set_pull_status(&self, status: String) -> Result<(), GotchaError> {
|
||||
if !matches!(status.as_str(), "open" | "closed") {
|
||||
return Err("Unsupported pull-request status.".into());
|
||||
}
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.preferences.pull_status = status;
|
||||
save_preferences(&state.preferences).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn set_appearance(&self, index: u32) -> Result<(), GotchaError> {
|
||||
let appearance =
|
||||
AppearanceMode::from_index(index as i32).ok_or("Unsupported appearance.")?;
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.preferences.appearance = appearance;
|
||||
save_preferences(&state.preferences).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn home(&self) -> Result<HomePage, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let name = server.name.clone();
|
||||
Ok(home_page(name, load_home(&server).await?))
|
||||
}
|
||||
|
||||
pub async fn repositories(&self) -> Result<Vec<RepositoryRow>, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let repositories = load_repositories(&server).await?;
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.repositories = repositories;
|
||||
Ok(self.repository_rows(&state))
|
||||
}
|
||||
|
||||
pub fn toggle_favorite(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
) -> Result<Vec<RepositoryRow>, GotchaError> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let server = state
|
||||
.active_server
|
||||
.and_then(|index| state.preferences.servers.get(index))
|
||||
.ok_or("Select a server first.")?;
|
||||
let key = favorite_key(&server.url, &owner, &repository);
|
||||
if !state.preferences.favorites.remove(&key) {
|
||||
state.preferences.favorites.insert(key);
|
||||
}
|
||||
save_preferences(&state.preferences)?;
|
||||
Ok(self.repository_rows(&state))
|
||||
}
|
||||
|
||||
pub async fn issues(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
) -> Result<Vec<IssueRow>, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let status = self.state.lock().unwrap().preferences.issue_status.clone();
|
||||
Ok(issue_rows(
|
||||
&load_issues(&server, &owner, &repository, &status).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn issue(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
number: i64,
|
||||
) -> Result<IssuePage, GotchaError> {
|
||||
Ok(issue_page(
|
||||
load_issue(&self.server()?, &owner, &repository, number).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn milestones(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
) -> Result<Vec<MilestoneRow>, GotchaError> {
|
||||
Ok(milestone_rows(
|
||||
&load_milestones(&self.server()?, &owner, &repository).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn milestone(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
id: i64,
|
||||
) -> Result<MilestonePage, GotchaError> {
|
||||
Ok(milestone_page(
|
||||
load_milestone(&self.server()?, &owner, &repository, id).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn pulls(&self) -> Result<Vec<PullRow>, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let status = self.state.lock().unwrap().preferences.pull_status.clone();
|
||||
Ok(pull_rows(&load_pulls(&server, &status).await?))
|
||||
}
|
||||
|
||||
pub async fn pull(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
number: i64,
|
||||
) -> Result<PullPage, GotchaError> {
|
||||
Ok(pull_page(
|
||||
load_pull(&self.server()?, &owner, &repository, number).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn commits(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
branch: Option<String>,
|
||||
) -> Result<CommitPage, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let default_branch = self
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.repositories
|
||||
.iter()
|
||||
.find(|candidate| candidate.owner == owner && candidate.name == repository)
|
||||
.map(|candidate| candidate.default_branch.clone())
|
||||
.unwrap_or_else(|| "main".into());
|
||||
let branches = load_branches(&server, &owner, &repository, &default_branch).await?;
|
||||
let commits = match branch.as_deref() {
|
||||
Some(branch) => load_branch_commits(&server, &owner, &repository, branch).await?,
|
||||
None => load_all_commits(&server, &owner, &repository, &branches).await?,
|
||||
};
|
||||
Ok(commit_page(branches, &commits, branch.is_none()))
|
||||
}
|
||||
|
||||
pub async fn commit_files(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
sha: String,
|
||||
) -> Result<Vec<FileRow>, GotchaError> {
|
||||
Ok(commit_file_rows(
|
||||
load_commit_files(&self.server()?, &owner, &repository, &sha).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn commit_diff(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
sha: String,
|
||||
path: String,
|
||||
) -> Result<DiffPage, GotchaError> {
|
||||
Ok(diff_page(
|
||||
&path,
|
||||
load_commit_diff(&self.server()?, &owner, &repository, &sha, &path).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn pull_diff(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
number: i64,
|
||||
path: String,
|
||||
) -> Result<DiffPage, GotchaError> {
|
||||
Ok(diff_page(
|
||||
&path,
|
||||
load_pull_diff(&self.server()?, &owner, &repository, number, &path).await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl GotchaCore {
|
||||
fn server(&self) -> Result<Server, GotchaError> {
|
||||
let state = self.state.lock().unwrap();
|
||||
state
|
||||
.active_server
|
||||
.and_then(|index| state.preferences.servers.get(index))
|
||||
.cloned()
|
||||
.ok_or_else(|| "Select a server first.".to_string().into())
|
||||
}
|
||||
|
||||
fn repository_rows(&self, state: &State) -> Vec<RepositoryRow> {
|
||||
let server_url = state
|
||||
.active_server
|
||||
.and_then(|index| state.preferences.servers.get(index))
|
||||
.map(|server| server.url.as_str())
|
||||
.unwrap_or_default();
|
||||
repository_rows(&state.repositories, |repository| {
|
||||
state.preferences.favorites.contains(&favorite_key(
|
||||
server_url,
|
||||
&repository.owner,
|
||||
&repository.name,
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user