mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-24 20:52:11 +00:00
fixed the opening of the log-files in the file explorer
This commit is contained in:
parent
347b07d2e1
commit
804a650e13
@ -7,19 +7,6 @@ use tauri_plugin_dialog::{DialogExt, FileDialogBuilder};
|
|||||||
use crate::api_token::APIToken;
|
use crate::api_token::APIToken;
|
||||||
use crate::app_window::MAIN_WINDOW;
|
use crate::app_window::MAIN_WINDOW;
|
||||||
|
|
||||||
#[cfg(any(windows, target_os = "macos"))]
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest};
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
use std::os::windows::process::CommandExt;
|
|
||||||
|
|
||||||
/// Microsoft documents CREATE_NO_WINDOW as a process creation flag with value 0x08000000.
|
|
||||||
#[cfg(windows)]
|
|
||||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
#[derive(Clone, Deserialize)]
|
||||||
pub struct PreviousDirectory {
|
pub struct PreviousDirectory {
|
||||||
path: String,
|
path: String,
|
||||||
@ -325,7 +312,7 @@ pub async fn open_path_in_file_manager(
|
|||||||
|
|
||||||
let Some(target) = resolve_file_manager_target(&requested_path) else {
|
let Some(target) = resolve_file_manager_target(&requested_path) else {
|
||||||
let issue = format!(
|
let issue = format!(
|
||||||
"The path does not exist and its parent folder could not be found: {}",
|
"The path does not exist or is not a file or folder: {}",
|
||||||
requested_path.to_string_lossy(),
|
requested_path.to_string_lossy(),
|
||||||
);
|
);
|
||||||
error!(Source = "Tauri"; "{issue}");
|
error!(Source = "Tauri"; "{issue}");
|
||||||
@ -335,51 +322,35 @@ pub async fn open_path_in_file_manager(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
info!(
|
||||||
{
|
Source = "Tauri";
|
||||||
return match open_path_in_linux_file_manager(&target).await {
|
"Opening resolved file manager target: requested='{}', target='{}', reveal_file={}.",
|
||||||
Ok(()) => {
|
requested_path.display(),
|
||||||
info!("Opened file manager for path: {:?}", target.path);
|
target.path.display(),
|
||||||
Json(OpenPathResponse {
|
target.reveal_file,
|
||||||
success: true,
|
);
|
||||||
issue: String::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(issue) => {
|
match open_file_manager_target(&target) {
|
||||||
error!(Source = "Tauri"; "{issue}");
|
Ok(()) => {
|
||||||
Json(OpenPathResponse {
|
info!(
|
||||||
success: false,
|
Source = "Tauri";
|
||||||
issue,
|
"Opened file manager target: requested='{}', target='{}', reveal_file={}.",
|
||||||
})
|
requested_path.display(),
|
||||||
}
|
target.path.display(),
|
||||||
};
|
target.reveal_file,
|
||||||
}
|
);
|
||||||
|
Json(OpenPathResponse {
|
||||||
|
success: true,
|
||||||
|
issue: String::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(windows, target_os = "macos"))]
|
Err(issue) => {
|
||||||
{
|
error!(Source = "Tauri"; "{issue}");
|
||||||
let mut command = create_file_manager_command(&target);
|
Json(OpenPathResponse {
|
||||||
|
success: false,
|
||||||
#[cfg(windows)]
|
issue,
|
||||||
command.creation_flags(CREATE_NO_WINDOW);
|
})
|
||||||
|
|
||||||
match command.spawn() {
|
|
||||||
Ok(_) => {
|
|
||||||
info!("Opened file manager for path: {:?}", target.path);
|
|
||||||
Json(OpenPathResponse {
|
|
||||||
success: true,
|
|
||||||
issue: String::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(error) => {
|
|
||||||
let issue = format!("Failed to open the file manager: {error}");
|
|
||||||
error!(Source = "Tauri"; "{issue}");
|
|
||||||
Json(OpenPathResponse {
|
|
||||||
success: false,
|
|
||||||
issue,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -402,126 +373,34 @@ struct FileManagerTarget {
|
|||||||
reveal_file: bool,
|
reveal_file: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", test))]
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
enum LinuxPortalOperation {
|
|
||||||
RevealFile,
|
|
||||||
OpenDirectory,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_file_manager_target(requested_path: &Path) -> Option<FileManagerTarget> {
|
fn resolve_file_manager_target(requested_path: &Path) -> Option<FileManagerTarget> {
|
||||||
if requested_path.is_file() {
|
let metadata = requested_path.metadata().ok()?;
|
||||||
|
|
||||||
|
if metadata.is_file() {
|
||||||
return Some(FileManagerTarget {
|
return Some(FileManagerTarget {
|
||||||
path: requested_path.to_path_buf(),
|
path: requested_path.to_path_buf(),
|
||||||
reveal_file: true,
|
reveal_file: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if requested_path.is_dir() {
|
if metadata.is_dir() {
|
||||||
return Some(FileManagerTarget {
|
return Some(FileManagerTarget {
|
||||||
path: requested_path.to_path_buf(),
|
path: requested_path.to_path_buf(),
|
||||||
reveal_file: false,
|
reveal_file: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
requested_path.parent()
|
None
|
||||||
.filter(|parent| parent.is_dir())
|
|
||||||
.map(|parent| FileManagerTarget {
|
|
||||||
path: parent.to_path_buf(),
|
|
||||||
reveal_file: false,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", test))]
|
fn open_file_manager_target(target: &FileManagerTarget) -> Result<(), String> {
|
||||||
fn linux_portal_operation(target: &FileManagerTarget) -> LinuxPortalOperation {
|
|
||||||
if target.reveal_file {
|
if target.reveal_file {
|
||||||
LinuxPortalOperation::RevealFile
|
return tauri_plugin_opener::reveal_item_in_dir(&target.path)
|
||||||
} else {
|
.map_err(|error| format!("Failed to reveal '{}' in the file manager: {error}", target.path.display()));
|
||||||
LinuxPortalOperation::OpenDirectory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", test))]
|
|
||||||
fn xdg_open_fallback_path(target: &FileManagerTarget) -> &Path {
|
|
||||||
if target.reveal_file {
|
|
||||||
target.path.parent().unwrap_or(&target.path)
|
|
||||||
} else {
|
|
||||||
&target.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
enum LinuxPortalError {
|
|
||||||
Unavailable(String),
|
|
||||||
RequestFailed(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
async fn open_path_with_linux_portal(target: &FileManagerTarget) -> Result<(), LinuxPortalError> {
|
|
||||||
let file = std::fs::File::open(&target.path)
|
|
||||||
.map_err(|error| LinuxPortalError::Unavailable(format!("Failed to open the path for the desktop portal: {error}")))?;
|
|
||||||
|
|
||||||
let request = match linux_portal_operation(target) {
|
|
||||||
LinuxPortalOperation::RevealFile => OpenDirectoryRequest::default().send(&file).await,
|
|
||||||
LinuxPortalOperation::OpenDirectory => OpenFileRequest::default().send_file(&file).await,
|
|
||||||
}
|
|
||||||
.map_err(|error| LinuxPortalError::Unavailable(format!("Desktop portal invocation failed: {error}")))?;
|
|
||||||
|
|
||||||
request.response()
|
|
||||||
.map_err(|error| LinuxPortalError::RequestFailed(format!("Desktop portal request failed: {error}")))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
async fn open_path_with_xdg_open(target: &FileManagerTarget) -> Result<(), String> {
|
|
||||||
let fallback_path = xdg_open_fallback_path(target);
|
|
||||||
let status = tokio::process::Command::new("xdg-open")
|
|
||||||
.arg(fallback_path)
|
|
||||||
.status()
|
|
||||||
.await
|
|
||||||
.map_err(|error| format!("xdg-open failed to start for '{}': {error}", fallback_path.to_string_lossy()))?;
|
|
||||||
|
|
||||||
if status.success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(format!("xdg-open failed for '{}' with exit status {status}", fallback_path.to_string_lossy()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
async fn open_path_in_linux_file_manager(target: &FileManagerTarget) -> Result<(), String> {
|
|
||||||
match open_path_with_linux_portal(target).await {
|
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(LinuxPortalError::RequestFailed(error)) => Err(error),
|
|
||||||
Err(LinuxPortalError::Unavailable(portal_error)) => {
|
|
||||||
match open_path_with_xdg_open(target).await {
|
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(fallback_error) => Err(format!("{portal_error} Fallback failed: {fallback_error}")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn create_file_manager_command(target: &FileManagerTarget) -> Command {
|
|
||||||
let mut command = Command::new("explorer.exe");
|
|
||||||
if target.reveal_file {
|
|
||||||
command.arg(format!("/select,{}", target.path.to_string_lossy()));
|
|
||||||
} else {
|
|
||||||
command.arg(&target.path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
command
|
tauri_plugin_opener::open_path(&target.path, None::<&str>)
|
||||||
}
|
.map_err(|error| format!("Failed to open '{}' in the file manager: {error}", target.path.display()))
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
fn create_file_manager_command(target: &FileManagerTarget) -> Command {
|
|
||||||
let mut command = Command::new("open");
|
|
||||||
if target.reveal_file {
|
|
||||||
command.arg("-R");
|
|
||||||
}
|
|
||||||
|
|
||||||
command.arg(&target.path);
|
|
||||||
command
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -530,7 +409,7 @@ mod tests {
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn existing_file_is_revealed_and_falls_back_to_its_parent() {
|
fn existing_file_is_revealed() {
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let file_path = temp_dir.path().join("application.log");
|
let file_path = temp_dir.path().join("application.log");
|
||||||
fs::write(&file_path, "log").unwrap();
|
fs::write(&file_path, "log").unwrap();
|
||||||
@ -539,8 +418,6 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(target.path, file_path);
|
assert_eq!(target.path, file_path);
|
||||||
assert!(target.reveal_file);
|
assert!(target.reveal_file);
|
||||||
assert_eq!(linux_portal_operation(&target), LinuxPortalOperation::RevealFile);
|
|
||||||
assert_eq!(xdg_open_fallback_path(&target), temp_dir.path());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -551,21 +428,14 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(target.path, temp_dir.path());
|
assert_eq!(target.path, temp_dir.path());
|
||||||
assert!(!target.reveal_file);
|
assert!(!target.reveal_file);
|
||||||
assert_eq!(linux_portal_operation(&target), LinuxPortalOperation::OpenDirectory);
|
|
||||||
assert_eq!(xdg_open_fallback_path(&target), temp_dir.path());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_file_uses_its_existing_parent_directory() {
|
fn missing_file_with_existing_parent_is_rejected() {
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let missing_file = temp_dir.path().join("missing.log");
|
let missing_file = temp_dir.path().join("missing.log");
|
||||||
|
|
||||||
let target = resolve_file_manager_target(&missing_file).unwrap();
|
assert!(resolve_file_manager_target(&missing_file).is_none());
|
||||||
|
|
||||||
assert_eq!(target.path, temp_dir.path());
|
|
||||||
assert!(!target.reveal_file);
|
|
||||||
assert_eq!(linux_portal_operation(&target), LinuxPortalOperation::OpenDirectory);
|
|
||||||
assert_eq!(xdg_open_fallback_path(&target), temp_dir.path());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -65,7 +65,7 @@ pub fn init_logging(bundle_identifier: &str) {
|
|||||||
.suffix("log");
|
.suffix("log");
|
||||||
|
|
||||||
// Store the startup log path:
|
// Store the startup log path:
|
||||||
store_startup_log_path(&LOG_STARTUP_PATH, &log_path);
|
let startup_log_path = store_startup_log_path(&LOG_STARTUP_PATH, &log_path);
|
||||||
|
|
||||||
let runtime_logger = Logger::try_with_str(log_config).expect("Cannot create logging")
|
let runtime_logger = Logger::try_with_str(log_config).expect("Cannot create logging")
|
||||||
.log_to_file(log_path)
|
.log_to_file(log_path)
|
||||||
@ -83,13 +83,17 @@ pub fn init_logging(bundle_identifier: &str) {
|
|||||||
|
|
||||||
LOGGER.set(runtime_logger).expect("Cannot set LOGGER");
|
LOGGER.set(runtime_logger).expect("Cannot set LOGGER");
|
||||||
|
|
||||||
|
log::info!(Source = "Tauri"; "Startup log file path: {startup_log_path}");
|
||||||
|
|
||||||
if let Some(fallback_warning) = fallback_warning {
|
if let Some(fallback_warning) = fallback_warning {
|
||||||
log::warn!("{fallback_warning}");
|
log::warn!("{fallback_warning}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_startup_log_path(storage: &OnceLock<String>, log_path: &FileSpec) {
|
fn store_startup_log_path(storage: &OnceLock<String>, log_path: &FileSpec) -> String {
|
||||||
let _ = storage.set(convert_log_path_to_string(log_path));
|
let log_path = convert_log_path_to_string(log_path);
|
||||||
|
let _ = storage.set(log_path.clone());
|
||||||
|
log_path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_log_path_to_string(log_path: &FileSpec) -> String {
|
fn convert_log_path_to_string(log_path: &FileSpec) -> String {
|
||||||
@ -129,11 +133,14 @@ fn get_startup_log_path(bundle_identifier: &str) -> (PathBuf, Option<String>) {
|
|||||||
).unwrap_or_else(|error| panic!("Cannot prepare a Flatpak startup log directory: {error}"));
|
).unwrap_or_else(|error| panic!("Cannot prepare a Flatpak startup log directory: {error}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
(get_non_flatpak_startup_log_path(
|
get_non_flatpak_startup_log_path(
|
||||||
|
bundle_identifier,
|
||||||
|
dirs::data_local_dir(),
|
||||||
home_directory(),
|
home_directory(),
|
||||||
current_dir().ok(),
|
current_dir().ok(),
|
||||||
temp_dir(),
|
temp_dir(),
|
||||||
), None)
|
ensure_log_directory_is_writable,
|
||||||
|
).unwrap_or_else(|error| panic!("Cannot prepare a startup log directory: {error}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Rust plans to remove the deprecation flag for std::env::home_dir() in Rust 1.86.0.
|
// Note: Rust plans to remove the deprecation flag for std::env::home_dir() in Rust 1.86.0.
|
||||||
@ -142,25 +149,35 @@ fn home_directory() -> Option<PathBuf> {
|
|||||||
std::env::home_dir()
|
std::env::home_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_non_flatpak_startup_log_path(
|
fn get_non_flatpak_startup_log_path<F>(
|
||||||
|
bundle_identifier: &str,
|
||||||
|
data_local_directory: Option<PathBuf>,
|
||||||
home_directory: Option<PathBuf>,
|
home_directory: Option<PathBuf>,
|
||||||
working_directory: Option<PathBuf>,
|
working_directory: Option<PathBuf>,
|
||||||
temporary_directory: PathBuf,
|
temporary_directory: PathBuf,
|
||||||
) -> PathBuf {
|
mut ensure_writable: F,
|
||||||
match home_directory {
|
) -> Result<(PathBuf, Option<String>), String>
|
||||||
// Case: We could determine the home directory:
|
where
|
||||||
Some(home_directory) => home_directory,
|
F: FnMut(&Path) -> Result<(), String>,
|
||||||
|
{
|
||||||
// Case: We could not determine the home directory. Let's try to use the working directory:
|
let standard_directory = data_local_directory.map(|directory| directory.join(bundle_identifier).join("data"));
|
||||||
None => match working_directory {
|
let mut fallback_candidates = Vec::new();
|
||||||
|
if let Some(home_directory) = home_directory {
|
||||||
// Case: We could determine the working directory:
|
fallback_candidates.push(("home directory", home_directory));
|
||||||
Some(working_directory) => working_directory,
|
|
||||||
|
|
||||||
// Case: We could not determine the working directory. Let's use the temporary directory:
|
|
||||||
None => temporary_directory,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(working_directory) = working_directory {
|
||||||
|
fallback_candidates.push(("working directory", working_directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
fallback_candidates.push(("temporary directory", temporary_directory));
|
||||||
|
|
||||||
|
select_startup_log_path(
|
||||||
|
"standard path",
|
||||||
|
standard_directory,
|
||||||
|
fallback_candidates,
|
||||||
|
&mut ensure_writable,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_flatpak_startup_log_path<F>(
|
fn select_flatpak_startup_log_path<F>(
|
||||||
@ -174,49 +191,56 @@ where
|
|||||||
F: FnMut(&Path) -> Result<(), String>,
|
F: FnMut(&Path) -> Result<(), String>,
|
||||||
{
|
{
|
||||||
let standard_directory = data_local_directory.map(|directory| directory.join(bundle_identifier).join("data"));
|
let standard_directory = data_local_directory.map(|directory| directory.join(bundle_identifier).join("data"));
|
||||||
let persistent_fallback = persistent_data_directory.join(bundle_identifier).join("data");
|
let fallback_candidates = vec![
|
||||||
let temporary_fallback = temporary_directory.join(bundle_identifier).join("data");
|
("persistent fallback", persistent_data_directory.join(bundle_identifier).join("data")),
|
||||||
|
("temporary fallback", temporary_directory.join(bundle_identifier).join("data")),
|
||||||
|
];
|
||||||
|
|
||||||
|
select_startup_log_path(
|
||||||
|
"standard Flatpak path",
|
||||||
|
standard_directory,
|
||||||
|
fallback_candidates,
|
||||||
|
&mut ensure_writable,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_startup_log_path<F>(
|
||||||
|
standard_name: &str,
|
||||||
|
standard_directory: Option<PathBuf>,
|
||||||
|
fallback_candidates: Vec<(&'static str, PathBuf)>,
|
||||||
|
ensure_writable: &mut F,
|
||||||
|
) -> Result<(PathBuf, Option<String>), String>
|
||||||
|
where
|
||||||
|
F: FnMut(&Path) -> Result<(), String>,
|
||||||
|
{
|
||||||
let mut failures = Vec::new();
|
let mut failures = Vec::new();
|
||||||
|
|
||||||
if let Some(standard_directory) = standard_directory {
|
if let Some(standard_directory) = standard_directory {
|
||||||
match ensure_writable(&standard_directory) {
|
match ensure_writable(&standard_directory) {
|
||||||
Ok(()) => return Ok((standard_directory, None)),
|
Ok(()) => return Ok((standard_directory, None)),
|
||||||
Err(error) => failures.push(format!("standard path failed: {error}")),
|
Err(error) => failures.push(format!("{standard_name} failed: {error}")),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
failures.push(String::from("standard path failed: dirs::data_local_dir() returned no path"));
|
failures.push(format!("{standard_name} failed: dirs::data_local_dir() returned no path"));
|
||||||
}
|
}
|
||||||
|
|
||||||
match ensure_writable(&persistent_fallback) {
|
for (fallback_name, fallback_directory) in fallback_candidates {
|
||||||
Ok(()) => {
|
match ensure_writable(&fallback_directory) {
|
||||||
let warning = format!(
|
Ok(()) => {
|
||||||
"The standard Flatpak startup log directory was unavailable; using persistent fallback '{}'. {}",
|
let warning = format!(
|
||||||
persistent_fallback.display(),
|
"The standard startup log directory was unavailable; using {fallback_name} '{}'. {}",
|
||||||
failures.join("; "),
|
fallback_directory.display(),
|
||||||
);
|
failures.join("; "),
|
||||||
|
);
|
||||||
return Ok((persistent_fallback, Some(warning)));
|
|
||||||
},
|
return Ok((fallback_directory, Some(warning)));
|
||||||
|
}
|
||||||
Err(error) => failures.push(format!("persistent fallback failed: {error}")),
|
|
||||||
|
Err(error) => failures.push(format!("{fallback_name} failed: {error}")),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match ensure_writable(&temporary_fallback) {
|
Err(failures.join("; "))
|
||||||
Ok(()) => {
|
|
||||||
let warning = format!(
|
|
||||||
"The standard and persistent Flatpak startup log directories were unavailable; using temporary fallback '{}'. {}",
|
|
||||||
temporary_fallback.display(),
|
|
||||||
failures.join("; "),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok((temporary_fallback, Some(warning)))
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(error) => {
|
|
||||||
failures.push(format!("temporary fallback failed: {error}"));
|
|
||||||
Err(failures.join("; "))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_log_directory_is_writable(directory: &Path) -> Result<(), String> {
|
fn ensure_log_directory_is_writable(directory: &Path) -> Result<(), String> {
|
||||||
@ -242,8 +266,10 @@ pub fn switch_to_file_logging(logger_path: PathBuf) -> Result<(), Box<dyn Error>
|
|||||||
.basename("events")
|
.basename("events")
|
||||||
.suppress_timestamp()
|
.suppress_timestamp()
|
||||||
.suffix("log");
|
.suffix("log");
|
||||||
let _ = LOG_APP_PATH.set(convert_log_path_to_string(&log_path));
|
let app_log_path = convert_log_path_to_string(&log_path);
|
||||||
|
let _ = LOG_APP_PATH.set(app_log_path.clone());
|
||||||
LOGGER.get().expect("No LOGGER was set").handle.reset_flw(&FileLogWriter::builder(log_path))?;
|
LOGGER.get().expect("No LOGGER was set").handle.reset_flw(&FileLogWriter::builder(log_path))?;
|
||||||
|
log::info!(Source = "Tauri"; "Usage log file path: {app_log_path}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -338,9 +364,18 @@ fn file_logger_format(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_log_paths(_token: APIToken) -> Json<LogPathsResponse> {
|
pub async fn get_log_paths(_token: APIToken) -> Json<LogPathsResponse> {
|
||||||
|
let log_startup_path = LOG_STARTUP_PATH.get().expect("No startup log path was set").clone();
|
||||||
|
let log_app_path = LOG_APP_PATH.get().expect("No app log path was set").clone();
|
||||||
|
log::info!(
|
||||||
|
Source = "Tauri";
|
||||||
|
"Returning log file paths: startup='{}', usage='{}'.",
|
||||||
|
log_startup_path,
|
||||||
|
log_app_path,
|
||||||
|
);
|
||||||
|
|
||||||
Json(LogPathsResponse {
|
Json(LogPathsResponse {
|
||||||
log_startup_path: LOG_STARTUP_PATH.get().expect("No startup log path was set").clone(),
|
log_startup_path,
|
||||||
log_app_path: LOG_APP_PATH.get().expect("No app log path was set").clone(),
|
log_app_path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,23 +532,51 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn non_flatpak_path_selection_keeps_existing_fallback_order() {
|
fn non_flatpak_path_selection_prefers_local_app_data_path() {
|
||||||
|
let local_data = PathBuf::from("/local-data");
|
||||||
|
let expected = local_data.join(BUNDLE_IDENTIFIER).join("data");
|
||||||
let home = PathBuf::from("/home/user");
|
let home = PathBuf::from("/home/user");
|
||||||
let working = PathBuf::from("/working");
|
let working = PathBuf::from("/working");
|
||||||
let temporary = PathBuf::from("/tmp");
|
let temporary = PathBuf::from("/tmp");
|
||||||
|
|
||||||
assert_eq!(
|
let (selected, warning) = get_non_flatpak_startup_log_path(
|
||||||
get_non_flatpak_startup_log_path(Some(home.clone()), Some(working.clone()), temporary.clone()),
|
BUNDLE_IDENTIFIER,
|
||||||
home,
|
Some(local_data),
|
||||||
);
|
Some(home),
|
||||||
assert_eq!(
|
Some(working),
|
||||||
get_non_flatpak_startup_log_path(None, Some(working.clone()), temporary.clone()),
|
|
||||||
working,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
get_non_flatpak_startup_log_path(None, None, temporary.clone()),
|
|
||||||
temporary,
|
temporary,
|
||||||
);
|
|_| Ok(()),
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(selected, expected);
|
||||||
|
assert!(warning.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_flatpak_path_selection_falls_back_to_home_when_local_app_data_is_unwritable() {
|
||||||
|
let local_data = PathBuf::from("/local-data");
|
||||||
|
let standard = local_data.join(BUNDLE_IDENTIFIER).join("data");
|
||||||
|
let home = PathBuf::from("/home/user");
|
||||||
|
let working = PathBuf::from("/working");
|
||||||
|
let temporary = PathBuf::from("/tmp");
|
||||||
|
|
||||||
|
let (selected, warning) = get_non_flatpak_startup_log_path(
|
||||||
|
BUNDLE_IDENTIFIER,
|
||||||
|
Some(local_data),
|
||||||
|
Some(home.clone()),
|
||||||
|
Some(working),
|
||||||
|
temporary,
|
||||||
|
|candidate| {
|
||||||
|
if candidate == standard {
|
||||||
|
Err(String::from("read-only"))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(selected, home);
|
||||||
|
assert!(warning.unwrap().contains("home directory"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -533,7 +596,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let log_path = FileSpec::default()
|
let log_path = FileSpec::default()
|
||||||
.directory(selected)
|
.directory(selected.clone())
|
||||||
.basename(".AI Studio Events")
|
.basename(".AI Studio Events")
|
||||||
.suppress_timestamp()
|
.suppress_timestamp()
|
||||||
.suffix("log");
|
.suffix("log");
|
||||||
@ -542,8 +605,8 @@ mod tests {
|
|||||||
store_startup_log_path(&storage, &log_path);
|
store_startup_log_path(&storage, &log_path);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
storage.get().unwrap(),
|
PathBuf::from(storage.get().unwrap()),
|
||||||
"/tmp/org.mindworkai.AIStudio/data/.AI Studio Events.log",
|
absolute(selected.join(".AI Studio Events.log")).unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user