Move commands to api crate

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/4/head
trivernis 3 years ago
parent 8fbc4436e3
commit c19423a271

File diff suppressed because it is too large Load Diff

@ -18,21 +18,20 @@ tauri-build = { version = "1.0.0-beta.4" }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.0.0-beta.8", features = ["api-all"] }
rmp-ipc = "0.7.0"
thiserror = "1.0.30"
directories = "4.0.1"
toml = "0.5.8"
typemap_rev = "0.1.5"
[dependencies.tokio]
version = "1.12.0"
features = ["fs", "io-std", "io-util"]
[dependencies.mediarepo]
git = "https://github.com/Trivernis/mediarepo-daemon"
rev = "ce93a5793348a05052b324562730d97f5c6e2128"
features = ["library"]
default-features=false
[dependencies.tracing-subscriber]
version = "0.3.0"
features = ["env-filter"]
[dependencies.mediarepo-api]
git = "https://github.com/Trivernis/mediarepo-api.git"
rev = "2b6a968e6d5343326e20c73f4fcbbb88848b0c35"
features = ["tauri-plugin"]
[features]
default = [ "custom-protocol" ]

@ -1,92 +0,0 @@
use crate::commands::{add_once_buffer, get_ipc};
use mediarepo::requests::{FindFilesByTagsRequest, GetFileThumbnailsRequest, ReadFileRequest};
use mediarepo::responses::{FileResponse, ThumbnailResponse};
use crate::context::Context;
use crate::error::AppResult;
#[tauri::command]
pub async fn get_all_files(context: tauri::State<'_, Context>) -> AppResult<Vec<FileResponse>> {
let ipc = get_ipc(&context).await?;
let response = ipc
.emitter
.emit_to("files", "all_files", ())
.await?
.await_reply(&ipc)
.await?;
Ok(response.data::<Vec<FileResponse>>()?)
}
#[tauri::command]
pub async fn find_files(
tags: Vec<String>,
context: tauri::State<'_, Context>,
) -> AppResult<Vec<FileResponse>> {
let ipc = get_ipc(&context).await?;
let response = ipc
.emitter
.emit_to("files", "find_files", FindFilesByTagsRequest { tags })
.await?
.await_reply(&ipc)
.await?;
Ok(response.data::<Vec<FileResponse>>()?)
}
#[tauri::command]
pub async fn read_file_by_hash(
hash: String,
mime: String,
context: tauri::State<'_, Context>,
) -> AppResult<String> {
let ipc = get_ipc(&context).await?;
let response = ipc
.emitter
.emit_to("files", "read_file", ReadFileRequest::Hash(hash.clone()))
.await?
.await_reply(&ipc)
.await?;
let raw_data = response.data_raw().to_vec();
let uri = add_once_buffer(&context, hash, mime, raw_data);
Ok(uri)
}
#[tauri::command]
pub async fn get_thumbnails(
hash: String,
context: tauri::State<'_, Context>,
) -> AppResult<Vec<ThumbnailResponse>> {
let ipc = get_ipc(&context).await?;
let response = ipc
.emitter
.emit_to(
"files",
"get_thumbnails",
GetFileThumbnailsRequest::Hash(hash),
)
.await?
.await_reply(&ipc)
.await?;
Ok(response.data::<Vec<ThumbnailResponse>>()?)
}
#[tauri::command]
pub async fn read_thumbnail(
hash: String,
mime: String,
context: tauri::State<'_, Context>,
) -> AppResult<String> {
let ipc = get_ipc(&context).await?;
let response = ipc
.emitter
.emit_to("files", "read_thumbnail", hash.clone())
.await?
.await_reply(&ipc)
.await?;
let raw_data = response.data_raw().to_vec();
let uri = add_once_buffer(&context, hash, mime, raw_data);
Ok(uri)
}

@ -1,41 +0,0 @@
use rmp_ipc::ipc::context::Context as IPCContext;
use crate::context::{Context, OnceBuffer};
use crate::error::{AppError, AppResult};
pub mod files;
pub mod repo;
pub mod tags;
#[tauri::command]
pub async fn emit_info(context: tauri::State<'_, Context>) -> AppResult<()> {
let ipc = context.ipc.read().await;
if let Some(ipc) = &*ipc {
ipc.emitter.emit("info", ()).await?;
println!("Emitted info event.");
} else {
println!("No IPC Context");
}
Ok(())
}
pub async fn get_ipc(context: &tauri::State<'_, Context>) -> AppResult<IPCContext> {
let ipc = context.ipc.read().await;
(ipc.clone()).ok_or(AppError::new("No ipc connection."))
}
/// Adds a once-buffer to the buffer store
pub fn add_once_buffer(
context: &tauri::State<'_, Context>,
key: String,
mime: String,
buf: Vec<u8>,
) -> String {
let uri = format!("once://{}", key);
let once_buffer = OnceBuffer { mime, buf };
let mut once_buffers = context.once_buffers.lock().unwrap();
once_buffers.insert(key, once_buffer);
uri
}

@ -1,104 +0,0 @@
use crate::context::Context;
use crate::error::{AppError, AppResult};
use crate::ipc::build_ipc_context;
use crate::settings::save_settings;
use rmp_ipc::ipc::context::Context as IPCContext;
use serde::{Deserialize, Serialize};
use std::mem;
use std::path::PathBuf;
use tauri::Window;
use tokio::fs;
static REPO_CONFIG_FILE: &str = "repo.toml";
#[derive(Serialize, Deserialize, Clone)]
pub struct Repository {
name: String,
path: Option<String>,
address: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RepoConfig {
pub listen_address: String,
pub database_path: String,
pub default_file_store: String,
}
#[tauri::command]
pub async fn get_repositories(context: tauri::State<'_, Context>) -> AppResult<Vec<Repository>> {
let settings = context.settings.read().await;
Ok(settings.repositories.values().cloned().collect())
}
#[tauri::command]
pub async fn get_active_repository(
context: tauri::State<'_, Context>,
) -> AppResult<Option<Repository>> {
let repo = context.active_repository.read().await;
Ok(repo.clone())
}
#[tauri::command]
pub async fn add_repository(
name: String,
path: String,
context: tauri::State<'_, Context>,
) -> AppResult<Vec<Repository>> {
let repo_path = path.clone();
let path = PathBuf::from(path);
let RepoConfig { listen_address, .. } = read_repo_config(path.join(REPO_CONFIG_FILE)).await?;
let repo = Repository {
name,
path: Some(repo_path),
address: listen_address,
};
let mut repositories = Vec::new();
{
let mut settings = context.settings.write().await;
settings.repositories.insert(repo.name.clone(), repo);
save_settings(&settings)?;
repositories.append(&mut settings.repositories.values().cloned().collect());
}
Ok(repositories)
}
#[tauri::command]
pub async fn select_repository(
window: Window,
name: String,
context: tauri::State<'_, Context>,
) -> AppResult<()> {
let settings = context.settings.read().await;
let repo = settings
.repositories
.get(&name)
.ok_or(AppError::new(format!("Repository '{}' not found", name)))?;
let ipc = connect(window, &repo.address).await?;
let mut ipc_ctx = context.ipc.write().await;
let old_ipc = mem::replace(&mut *ipc_ctx, Some(ipc));
if let Some(old_ctx) = old_ipc {
old_ctx.stop().await?;
}
let mut active_repo = context.active_repository.write().await;
*active_repo = Some(repo.clone());
Ok(())
}
async fn read_repo_config(path: PathBuf) -> AppResult<RepoConfig> {
let toml_str = fs::read_to_string(path).await?;
let config = toml::from_str(&toml_str)?;
Ok(config)
}
/// Connects to the IPC Server
async fn connect(window: Window, address: &str) -> AppResult<IPCContext> {
build_ipc_context(window, address).await
}

@ -1,21 +0,0 @@
use crate::commands::get_ipc;
use crate::context::Context;
use crate::error::AppResult;
use mediarepo::requests::FileIdentifier;
use mediarepo::responses::TagResponse;
#[tauri::command]
pub async fn get_tags_for_file(
hash: String,
context: tauri::State<'_, Context>,
) -> AppResult<Vec<TagResponse>> {
let ipc = get_ipc(&context).await?;
let response = ipc
.emitter
.emit_to("tags", "tags_for_file", FileIdentifier::Hash(hash))
.await?
.await_reply(&ipc)
.await?;
Ok(response.data::<Vec<TagResponse>>()?)
}

@ -1,31 +0,0 @@
use crate::commands::repo::Repository;
use crate::settings::Settings;
use rmp_ipc::ipc::context::Context as IPCContext;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex as StdMutex;
use tokio::sync::RwLock;
pub struct OnceBuffer {
pub mime: String,
pub buf: Vec<u8>,
}
#[derive(Clone)]
pub struct Context {
pub active_repository: Arc<RwLock<Option<Repository>>>,
pub ipc: Arc<RwLock<Option<IPCContext>>>,
pub settings: Arc<RwLock<Settings>>,
pub once_buffers: Arc<StdMutex<HashMap<String, OnceBuffer>>>,
}
impl Context {
pub fn new(settings: Settings) -> Self {
Self {
ipc: Arc::new(RwLock::new(None)),
active_repository: Arc::new(RwLock::new(None)),
settings: Arc::new(RwLock::new(settings)),
once_buffers: Arc::new(StdMutex::new(HashMap::new())),
}
}
}

@ -1,62 +0,0 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
use serde::Serialize;
pub type AppResult<T> = Result<T, AppError>;
#[derive(Debug, Serialize)]
pub struct AppError {
message: String
}
impl AppError {
pub fn new<S: ToString>(msg: S) -> Self {
Self {
message: msg.to_string()
}
}
}
impl Display for AppError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.message.fmt(f)
}
}
impl Error for AppError {}
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self {
Self::new(e)
}
}
impl From<toml::de::Error> for AppError {
fn from(e: toml::de::Error) -> Self {
Self::new(format!("Failed to deserialize toml: {:?}", e))
}
}
impl From<toml::ser::Error> for AppError {
fn from(e: toml::ser::Error) -> Self {
Self::new(format!("Failed to serialize to toml: {:?}", e))
}
}
impl From<rmp_ipc::error::Error> for AppError {
fn from(e: rmp_ipc::error::Error) -> Self {
Self::new(format!("Daemon Error: {:?}", e))
}
}
impl From<tauri::Error> for AppError {
fn from(e: tauri::Error) -> Self {
Self::new(format!("Tauri error: {:?}", e))
}
}
impl From<AppError> for rmp_ipc::error::Error {
fn from(e: AppError) -> Self {
rmp_ipc::error::Error::Message(e.message)
}
}

@ -1,51 +0,0 @@
mod files;
use crate::error::AppResult;
use mediarepo::responses::InfoResponse;
use rmp_ipc::error::Result;
use rmp_ipc::error_event::{ErrorEventData, ERROR_EVENT_NAME};
use rmp_ipc::event::Event;
use rmp_ipc::ipc::context::Context as IPCContext;
use rmp_ipc::IPCBuilder;
use tauri::Window;
use typemap_rev::TypeMapKey;
pub struct WindowKey;
impl TypeMapKey for WindowKey {
type Value = Window;
}
pub async fn build_ipc_context(window: Window, address: &str) -> AppResult<IPCContext> {
let ctx = IPCBuilder::new()
.address(address)
.insert::<WindowKey>(window)
.on(ERROR_EVENT_NAME, |c, e| Box::pin(handle_error(c, e)))
.on("info", |c, e| Box::pin(handle_info(c, e)))
.build_client()
.await?;
Ok(ctx)
}
async fn handle_error(ctx: &IPCContext, event: Event) -> Result<()> {
let error_data = event.data::<ErrorEventData>()?;
let data = ctx.data.read().await;
let window = data.get::<WindowKey>().unwrap();
window
.emit("error", error_data)
.expect("Failed to emit error event");
Ok(())
}
async fn handle_info(ctx: &IPCContext, event: Event) -> Result<()> {
let info_data = event.data::<InfoResponse>()?;
let data = ctx.data.read().await;
let window = data.get::<WindowKey>().unwrap();
window
.emit("info", info_data)
.expect("Failed to emit info event");
Ok(())
}

@ -3,61 +3,17 @@
windows_subsystem = "windows"
)]
use crate::commands::emit_info;
use crate::commands::files::*;
use crate::commands::repo::{
add_repository, get_active_repository, get_repositories, select_repository,
};
use crate::commands::tags::*;
use crate::context::Context;
use crate::settings::load_settings;
use tauri::http::ResponseBuilder;
use tauri::Manager;
mod commands;
pub mod context;
pub mod error;
mod ipc;
mod settings;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::EnvFilter;
fn main() {
let settings = load_settings().expect("Failed to load settings");
let context = Context::new(settings);
tauri::Builder::default()
.manage(context)
.register_uri_scheme_protocol("once", |app, request| {
let context = app.state::<Context>();
let resource_key = request.uri().trim_start_matches("once://");
let buffer = {
let mut buffers = context.once_buffers.lock().unwrap();
buffers.remove(resource_key)
};
if let Some(buffer) = buffer {
ResponseBuilder::new()
.mimetype(&buffer.mime)
.status(200)
.body(buffer.buf)
} else {
ResponseBuilder::new()
.mimetype("text/plain")
.status(404)
.body("Resource not found".as_bytes().to_vec())
}
})
.invoke_handler(tauri::generate_handler![
get_repositories,
add_repository,
select_repository,
get_active_repository,
emit_info,
get_all_files,
read_file_by_hash,
get_thumbnails,
read_thumbnail,
get_tags_for_file,
find_files,
])
tracing_subscriber::fmt::SubscriberBuilder::default()
.with_env_filter(EnvFilter::from_default_env())
.with_writer(std::io::stdout)
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.compact()
.init();
mediarepo_api::tauri_plugin::register_plugin(tauri::Builder::default())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

@ -1,48 +0,0 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use crate::commands::repo::Repository;
use crate::error::AppResult;
use directories::ProjectDirs;
use std::fs;
use std::path::PathBuf;
static SETTINGS_FILE: &str = "settings.toml";
#[derive(Default, Serialize, Deserialize)]
pub struct Settings {
pub repositories: HashMap<String, Repository>,
}
fn get_settings_path() -> PathBuf {
let dirs = ProjectDirs::from("com", "trivernis", "mediarepo").unwrap();
let config_path = dirs.config_dir().to_path_buf();
config_path.join(SETTINGS_FILE)
}
/// Writes the settings to the file
pub fn save_settings(settings: &Settings) -> AppResult<()> {
let settings_path = get_settings_path();
let settings_string = toml::to_string(&settings)?;
fs::write(&settings_path, &settings_string.into_bytes())?;
Ok(())
}
/// Loads the settings from the file
pub fn load_settings() -> AppResult<Settings> {
let dirs = ProjectDirs::from("com", "trivernis", "mediarepo").unwrap();
let config_path = dirs.config_dir().to_path_buf();
if !config_path.exists() {
fs::create_dir_all(&config_path)?;
}
let settings_path = config_path.join(SETTINGS_FILE);
if !settings_path.exists() {
let settings = Settings::default();
save_settings(&settings)?;
}
let config_str = fs::read_to_string(settings_path)?;
let settings = toml::from_str(&config_str)?;
Ok(settings)
}

@ -2,5 +2,5 @@ export type Thumbnail = {
hash: string,
height: number,
width: number,
mime: string | undefined
mime_type: string | undefined
}

@ -98,8 +98,7 @@ export class HomeComponent implements OnInit {
}
private async openLightbox(file: File): Promise<void> {
let url = await this.fileService.readFile(file.hash,
file.mime_type ?? "image/png");
let url = await this.fileService.readFile(file);
let albums = [
{

@ -17,31 +17,26 @@ export class FileService {
}
public async getFiles() {
let all_files = await invoke<File[]>("get_all_files");
let all_files = await invoke<File[]>("plugin:mediarepo|get_all_files");
this.displayedFiles.next(all_files);
}
public async findFiles(tags: string[]) {
let files = await invoke<File[]>("find_files", {tags});
let files = await invoke<File[]>("plugin:mediarepo|find_files", {tags});
this.displayedFiles.next(files);
}
public async readFile(hash: string, mime: string): Promise<SafeResourceUrl> {
const once_uri = await invoke<string>("read_file_by_hash", {hash, mime});
public async readFile(file: File): Promise<SafeResourceUrl> {
const once_uri = await invoke<string>("plugin:mediarepo|read_file_by_hash", {hash: file.hash, mimeType: file.mime_type});
return this.sanitizer.bypassSecurityTrustResourceUrl(once_uri);
}
public async readThumbnail(thumbnail: Thumbnail): Promise<SafeResourceUrl> {
let once_uri = await invoke<string>("read_thumbnail", {hash: thumbnail.hash, mime: thumbnail.mime});
let once_uri = await invoke<string>("plugin:mediarepo|read_thumbnail", {hash: thumbnail.hash, mimeType: thumbnail.mime_type });
return this.sanitizer.bypassSecurityTrustResourceUrl(once_uri);
}
public async getThumbnails(hash: string): Promise<Thumbnail[]> {
return await invoke<Thumbnail[]>("get_thumbnails", {hash});
}
createSafeObjectUrl(blob: Blob): SafeResourceUrl {
const url = URL?.createObjectURL(blob);
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
return await invoke<Thumbnail[]>("plugin:mediarepo|get_file_thumbnails", {hash});
}
}

@ -25,21 +25,20 @@ export class RepositoryService {
}
public async loadRepositories() {
let active_repo = await invoke<Repository | undefined>("get_active_repository");
let active_repo = await invoke<Repository | undefined>("plugin:mediarepo|get_active_repository");
this.selectedRepository.next(active_repo);
let repos = await invoke<Repository[]>("get_repositories");
let repos = await invoke<Repository[]>("plugin:mediarepo|get_repositories");
this.repositories.next(repos);
}
public async setRepository(repo: Repository) {
await invoke("select_repository", {name: repo.name});
await invoke("emit_info");
await invoke("plugin:mediarepo|select_repository", {name: repo.name});
this.selectedRepository.next(repo);
}
public async addRepository(name: string, path: string) {
let repos = await invoke<Repository[]>("add_repository", {name, path});
let repos = await invoke<Repository[]>("plugin:mediarepo|add_repository", {name, path});
this.repositories.next(repos);
}
}

@ -10,7 +10,7 @@ export class TagService {
constructor() { }
public async getTagsForFile(hash: string): Promise<Tag[]> {
const tags = await invoke<Tag[]>("get_tags_for_file", {hash});
const tags = await invoke<Tag[]>("plugin:mediarepo|get_tags_for_file", {hash});
return tags.map(t => new Tag(t.id, t.name, t.namespace));
}
}

Loading…
Cancel
Save