You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mediarepo/mediarepo-api/src/tauri_plugin/commands/file.rs

269 lines
7.4 KiB
Rust

use crate::tauri_plugin::commands::{ApiAccess, BufferAccess};
use crate::tauri_plugin::error::PluginResult;
use crate::tauri_plugin::utils::system_time_to_naive_date_time;
use crate::types::files::{
FileBasicDataResponse, FileMetadataResponse, FileOSMetadata, FileStatus,
ThumbnailMetadataResponse,
};
use crate::types::filtering::{FilterExpression, SortKey};
use crate::types::identifier::FileIdentifier;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::SystemTime;
use tokio::fs;
use tokio::fs::DirEntry;
#[derive(Serialize, Deserialize, Debug)]
pub struct AddFileOptions {
pub read_tags_from_txt: bool,
pub delete_after_import: bool,
}
#[tauri::command]
pub async fn get_all_files(api_state: ApiAccess<'_>) -> PluginResult<Vec<FileBasicDataResponse>> {
let api = api_state.api().await?;
let all_files = api.file.all_files().await?;
Ok(all_files)
}
#[tauri::command]
pub async fn get_files(
api_state: ApiAccess<'_>,
ids: Vec<i64>,
) -> PluginResult<Vec<FileBasicDataResponse>> {
let api = api_state.api().await?;
let ids = ids.into_iter().map(|id| FileIdentifier::ID(id)).collect();
let files = api.file.get_files(ids).await?;
Ok(files)
}
#[tauri::command]
pub async fn add_local_file(
api_state: ApiAccess<'_>,
metadata: FileOSMetadata,
options: AddFileOptions,
) -> PluginResult<FileBasicDataResponse> {
let api = api_state.api().await?;
let path = PathBuf::from(&metadata.path);
let mut tags = Vec::new();
let txt_path = PathBuf::from(format!("{}.txt", path.to_string_lossy()));
if options.read_tags_from_txt {
if txt_path.exists() {
let content = fs::read_to_string(&txt_path).await?;
tags.append(
&mut content
.split('\n')
.map(|line| line.to_owned())
.collect::<Vec<String>>(),
);
}
}
let file_content = fs::read(&path).await?;
let file = api.file.add_file(metadata, tags, file_content).await?;
if options.delete_after_import {
fs::remove_file(path).await?;
if options.read_tags_from_txt {
fs::remove_file(txt_path).await?;
}
}
Ok(file)
}
#[tauri::command]
pub async fn find_files(
filters: Vec<FilterExpression>,
sort_by: Vec<SortKey>,
api_state: ApiAccess<'_>,
) -> PluginResult<Vec<FileBasicDataResponse>> {
let api = api_state.api().await?;
let files = api.file.find_files(filters, sort_by).await?;
Ok(files)
}
#[tauri::command]
pub async fn get_file_thumbnails(
api_state: ApiAccess<'_>,
id: i64,
) -> PluginResult<Vec<ThumbnailMetadataResponse>> {
let api = api_state.api().await?;
let thumbs = api.file.get_file_thumbnails(FileIdentifier::ID(id)).await?;
Ok(thumbs)
}
#[tauri::command]
pub async fn get_file_metadata(
api_state: ApiAccess<'_>,
id: i64,
) -> PluginResult<FileMetadataResponse> {
let api = api_state.api().await?;
let metadata = api.file.get_file_metadata(FileIdentifier::ID(id)).await?;
Ok(metadata)
}
#[tauri::command]
pub async fn update_file_name(
api_state: ApiAccess<'_>,
id: i64,
name: String,
) -> PluginResult<FileMetadataResponse> {
let api = api_state.api().await?;
let metadata = api
.file
.update_file_name(FileIdentifier::ID(id), name)
.await?;
Ok(metadata)
}
#[tauri::command]
pub async fn update_file_status(
api_state: ApiAccess<'_>,
id: i64,
status: FileStatus,
) -> PluginResult<FileBasicDataResponse> {
let api = api_state.api().await?;
let file = api
.file
.update_file_status(FileIdentifier::ID(id), status)
.await?;
Ok(file)
}
#[tauri::command]
pub async fn delete_file(api_state: ApiAccess<'_>, id: i64) -> PluginResult<()> {
let api = api_state.api().await?;
api.file.delete_file(FileIdentifier::ID(id)).await?;
Ok(())
}
#[tauri::command]
pub async fn read_file(
api_state: ApiAccess<'_>,
buffer_state: BufferAccess<'_>,
hash: String,
) -> PluginResult<Vec<u8>> {
if let Some(buffer) = buffer_state.get_entry(&hash) {
Ok(buffer.buf)
} else {
let api = api_state.api().await?;
let content = api.file.read_file(FileIdentifier::CD(hash.clone())).await?;
Ok(content)
}
}
/// Saves a file on the local system
#[tauri::command]
pub async fn save_file_locally(
api_state: ApiAccess<'_>,
id: i64,
path: String,
) -> PluginResult<()> {
let api = api_state.api().await?;
let content = api.file.read_file(FileIdentifier::ID(id)).await?;
fs::write(PathBuf::from(path), content).await?;
Ok(())
}
#[tauri::command]
pub async fn delete_thumbnails(api_state: ApiAccess<'_>, id: i64) -> PluginResult<()> {
let api = api_state.api().await?;
api.file.delete_thumbnails(FileIdentifier::ID(id)).await?;
Ok(())
}
#[tauri::command]
pub async fn resolve_paths_to_files(paths: Vec<String>) -> PluginResult<Vec<FileOSMetadata>> {
let mut files = Vec::new();
for path in paths {
let path = PathBuf::from(path);
if path.exists() {
files.append(&mut resolve_path_to_files(path).await?);
}
}
Ok(files)
}
/// Resolves a path into several file metadata objects
#[tracing::instrument(level = "debug")]
async fn resolve_path_to_files(path: PathBuf) -> PluginResult<Vec<FileOSMetadata>> {
let mut files = Vec::new();
if path.is_dir() {
let mut read_dir = fs::read_dir(path).await?;
while let Some(entry) = read_dir.next_entry().await? {
let subdir_entries = resolve_subdir(entry).await?;
for entry in subdir_entries.into_iter().filter(|e| !e.path().is_dir()) {
let metadata = retrieve_file_information(entry.path()).await?;
files.push(metadata);
}
}
} else {
let metadata = retrieve_file_information(path).await?;
files.push(metadata);
}
Ok(files)
}
/// Iteratively resolves a directory into its sub components
#[tracing::instrument(level = "debug")]
async fn resolve_subdir(entry: DirEntry) -> PluginResult<Vec<DirEntry>> {
let mut entries = vec![entry];
let mut i = 0;
while i < entries.len() {
let entry = &entries[i];
if entry.path().is_dir() {
let mut read_dir = fs::read_dir(entry.path()).await?;
while let Some(entry) = read_dir.next_entry().await? {
entries.push(entry);
}
}
i += 1;
}
Ok(entries)
}
/// Retrieves information about a path that MUST be a file and returns
/// metadata for it
#[tracing::instrument(level = "trace")]
async fn retrieve_file_information(path: PathBuf) -> PluginResult<FileOSMetadata> {
let mime = mime_guess::from_path(&path).first();
let metadata = fs::metadata(&path).await?;
let creation_time = metadata.created().unwrap_or(SystemTime::now());
let change_time = metadata.modified().unwrap_or(SystemTime::now());
let name = path
.file_name()
.ok_or_else(|| "Could not retrieve file name")?;
let os_metadata = FileOSMetadata {
path: path.to_string_lossy().to_string(),
name: name.to_string_lossy().to_string(),
mime_type: mime.map(|m| m.to_string()),
creation_time: system_time_to_naive_date_time(creation_time),
change_time: system_time_to_naive_date_time(change_time),
};
Ok(os_metadata)
}