mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-07-21 17:06:27 +00:00
Fixed initial logging setup for Flatpak (#874)
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
parent
5e2d252fc6
commit
90988ebea4
@ -6994,6 +6994,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2273492381"] = "We must generate
|
||||
-- Configuration plugin ID:
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2301484629"] = "Configuration plugin ID:"
|
||||
|
||||
-- dirs determines the platform-specific local application data directory. AI Studio uses it so the Flatpak startup log is written to the same application data directory that Tauri uses.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2325338322"] = "dirs determines the platform-specific local application data directory. AI Studio uses it so the Flatpak startup log is written to the same application data directory that Tauri uses."
|
||||
|
||||
-- The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2329884315"] = "The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK."
|
||||
|
||||
|
||||
@ -304,6 +304,7 @@
|
||||
<ThirdPartyComponent Name="futures" Developer="Alex Crichton, Taiki Endo, Taylor Cramer, Nemo157, Josef Brandl, Aaron Turon & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-lang/futures-rs/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-lang/futures-rs" UseCase="@T("This is a library providing the foundations for asynchronous programming in Rust. It includes key trait definitions like Stream, as well as utilities like join!, select!, and various futures combinator methods which enable expressive asynchronous control flow.")"/>
|
||||
<ThirdPartyComponent Name="async-stream" Developer="Carl Lerche, Taiki Endo & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tokio-rs/async-stream/blob/master/LICENSE" RepositoryUrl="https://github.com/tokio-rs/async-stream" UseCase="@T("This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.")"/>
|
||||
<ThirdPartyComponent Name="flexi_logger" Developer="emabee & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/emabee/flexi_logger/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/emabee/flexi_logger" UseCase="@T("This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible.")"/>
|
||||
<ThirdPartyComponent Name="dirs" Developer="soc, Wang Xuerui & Open Source Community" LicenseName="MIT" LicenseUrl="https://codeberg.org/dirs/dirs-rs/src/branch/main/LICENSE-MIT" RepositoryUrl="https://codeberg.org/dirs/dirs-rs" UseCase="@T("dirs determines the platform-specific local application data directory. AI Studio uses it so the Flatpak startup log is written to the same application data directory that Tauri uses.")"/>
|
||||
<ThirdPartyComponent Name="rand" Developer="Rust developers & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-random/rand/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-random/rand" UseCase="@T("We must generate random numbers, e.g., for securing the interprocess communication between the user interface and the runtime. The rand library is great for this purpose.")"/>
|
||||
<ThirdPartyComponent Name="pptx-to-md" Developer="Nils Kruthoff & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/nilskruthoff/pptx-parser/blob/master/LICENCE-MIT" RepositoryUrl="https://github.com/nilskruthoff/pptx-parser" UseCase="@T("We use this library to be able to read PowerPoint files. This allows us to insert content from slides into prompts and take PowerPoint files into account in RAG processes. We thank Nils Kruthoff for his work on this Rust crate.")"/>
|
||||
<ThirdPartyComponent Name="base64" Developer="Marshall Pierce, Alice Maz & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/marshallpierce/rust-base64/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/marshallpierce/rust-base64" UseCase="@T("For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose.")"/>
|
||||
|
||||
@ -6996,6 +6996,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2273492381"] = "Wir müssen Zufa
|
||||
-- Configuration plugin ID:
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2301484629"] = "Konfigurations-Plugin-ID:"
|
||||
|
||||
-- dirs determines the platform-specific local application data directory. AI Studio uses it so the Flatpak startup log is written to the same application data directory that Tauri uses.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2325338322"] = "dirs bestimmt das plattformspezifische lokale Anwendungsdatenverzeichnis. AI Studio verwendet es, damit das Flatpak-Startprotokoll in dasselbe Verzeichnis geschrieben wird, das auch Tauri verwendet."
|
||||
|
||||
-- The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2329884315"] = "Die Programmiersprache C# wird für die Umsetzung der Benutzeroberfläche und des Backends verwendet. Für die Entwicklung der Benutzeroberfläche mit C# kommt die Blazor-Technologie aus ASP.NET Core zum Einsatz. Alle diese Technologien sind im .NET SDK integriert."
|
||||
|
||||
|
||||
@ -6996,6 +6996,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2273492381"] = "We must generate
|
||||
-- Configuration plugin ID:
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2301484629"] = "Configuration plugin ID:"
|
||||
|
||||
-- dirs determines the platform-specific local application data directory. AI Studio uses it so the Flatpak startup log is written to the same application data directory that Tauri uses.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2325338322"] = "dirs determines the platform-specific local application data directory. AI Studio uses it so the Flatpak startup log is written to the same application data directory that Tauri uses."
|
||||
|
||||
-- The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2329884315"] = "The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK."
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ using AIStudio.Tools.PluginSystem.Assistants;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
@ -111,6 +112,32 @@ internal sealed class Program
|
||||
options.FormatterName = TerminalLogger.FORMATTER_NAME;
|
||||
}).AddConsoleFormatter<TerminalLogger, ConsoleFormatterOptions>();
|
||||
|
||||
if(runtimeInfo.LinuxPackageType == "flatpak")
|
||||
{
|
||||
try
|
||||
{
|
||||
var tauriDataDirectory = await rust.GetDataDirectory();
|
||||
if(string.IsNullOrWhiteSpace(tauriDataDirectory))
|
||||
throw new InvalidOperationException("Rust returned an empty Tauri data directory.");
|
||||
|
||||
var dataProtectionKeysDirectory = Path.Combine(tauriDataDirectory, "data-protection-keys");
|
||||
Directory.CreateDirectory(dataProtectionKeysDirectory);
|
||||
var writeTestPath = Path.Combine(dataProtectionKeysDirectory, $".write-test-{Guid.NewGuid():N}");
|
||||
using (new FileStream(writeTestPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 1, FileOptions.DeleteOnClose))
|
||||
{
|
||||
}
|
||||
|
||||
builder.Services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeysDirectory))
|
||||
.SetApplicationName("org.mindworkai.AIStudio");
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
Console.WriteLine($"Error: Failed to configure Flatpak data-protection keys in the Tauri data directory: {exception.Message}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
builder.Services.AddMudExtensions();
|
||||
builder.Services.AddMudServices(config =>
|
||||
{
|
||||
|
||||
@ -113,7 +113,7 @@ public sealed partial class RustService
|
||||
var response = await this.http.GetAsync("/system/directories/data");
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to get the data directory from Rust: '{response.StatusCode}'");
|
||||
this.logger?.LogError($"Failed to get the data directory from Rust: '{response.StatusCode}'");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
1
runtime/Cargo.lock
generated
1
runtime/Cargo.lock
generated
@ -4147,6 +4147,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"dbus-secret-service",
|
||||
"dbus-secret-service-keyring-store",
|
||||
"dirs",
|
||||
"file-format",
|
||||
"flexi_logger",
|
||||
"futures",
|
||||
|
||||
@ -24,6 +24,7 @@ tokio-stream = { version = "0.1.18", features = ["sync"] }
|
||||
futures = "0.3.32"
|
||||
async-stream = "0.3.6"
|
||||
flexi_logger = "0.31.9"
|
||||
dirs = "6.0.0"
|
||||
log = { version = "0.4.33", features = ["kv"] }
|
||||
once_cell = "1.21.4"
|
||||
axum = { version = "0.8.9", features = ["http2", "json", "query", "tokio"] }
|
||||
|
||||
@ -12,7 +12,7 @@ use log::{debug, error, info, trace, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use pdfium_render::prelude::Pdfium;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{DragDropEvent,RunEvent, Manager, WindowEvent, generate_context};
|
||||
use tauri::{DragDropEvent,RunEvent, Manager, WindowEvent};
|
||||
use tauri::path::PathResolver;
|
||||
use tauri::WebviewWindow;
|
||||
use tauri_plugin_updater::{UpdaterExt, Update};
|
||||
@ -52,7 +52,7 @@ static EVENT_BROADCAST: Lazy<Mutex<Option<broadcast::Sender<Event>>>> = Lazy::ne
|
||||
static APPROVED_APP_URL: Lazy<Mutex<Option<tauri::Url>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
/// Starts the Tauri app.
|
||||
pub fn start_tauri() {
|
||||
pub fn start_tauri(tauri_context: tauri::Context<tauri::Wry>) {
|
||||
info!("Starting Tauri app...");
|
||||
|
||||
// Create the event broadcast channel:
|
||||
@ -179,7 +179,7 @@ pub fn start_tauri() {
|
||||
Ok(())
|
||||
})
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.build(generate_context!())
|
||||
.build(tauri_context)
|
||||
.expect("Error while running Tauri application");
|
||||
|
||||
// The app event handler:
|
||||
|
||||
@ -2,7 +2,8 @@ use std::collections::BTreeMap;
|
||||
use std::env::{current_dir, temp_dir};
|
||||
use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
use std::path::{absolute, PathBuf};
|
||||
use std::fs::{create_dir_all, OpenOptions};
|
||||
use std::path::{absolute, Path, PathBuf};
|
||||
use std::sync::OnceLock;
|
||||
use flexi_logger::{DeferredNow, Duplicate, FileSpec, Logger, LoggerHandle};
|
||||
use flexi_logger::writers::FileLogWriter;
|
||||
@ -11,7 +12,9 @@ use log::kv::{Key, Value, VisitSource};
|
||||
use axum::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::api_token::APIToken;
|
||||
use crate::environment::is_dev;
|
||||
use crate::environment::{is_dev, is_flatpak};
|
||||
|
||||
const FLATPAK_PERSISTENT_DATA_DIRECTORY: &str = "/var/data";
|
||||
|
||||
static LOGGER: OnceLock<RuntimeLoggerHandle> = OnceLock::new();
|
||||
|
||||
@ -20,7 +23,7 @@ static LOG_STARTUP_PATH: OnceLock<String> = OnceLock::new();
|
||||
static LOG_APP_PATH: OnceLock<String> = OnceLock::new();
|
||||
|
||||
/// Initialize the logging system.
|
||||
pub fn init_logging() {
|
||||
pub fn init_logging(bundle_identifier: &str) {
|
||||
|
||||
//
|
||||
// Configure the LOGGER:
|
||||
@ -54,14 +57,15 @@ pub fn init_logging() {
|
||||
false => "AI Studio Events",
|
||||
};
|
||||
|
||||
let (startup_log_directory, fallback_warning) = get_startup_log_path(bundle_identifier);
|
||||
let log_path = FileSpec::default()
|
||||
.directory(get_startup_log_path())
|
||||
.directory(startup_log_directory)
|
||||
.basename(log_basename)
|
||||
.suppress_timestamp()
|
||||
.suffix("log");
|
||||
|
||||
// Store the startup log path:
|
||||
let _ = LOG_STARTUP_PATH.set(convert_log_path_to_string(&log_path));
|
||||
store_startup_log_path(&LOG_STARTUP_PATH, &log_path);
|
||||
|
||||
let runtime_logger = Logger::try_with_str(log_config).expect("Cannot create logging")
|
||||
.log_to_file(log_path)
|
||||
@ -78,6 +82,14 @@ pub fn init_logging() {
|
||||
};
|
||||
|
||||
LOGGER.set(runtime_logger).expect("Cannot set LOGGER");
|
||||
|
||||
if let Some(fallback_warning) = fallback_warning {
|
||||
log::warn!("{fallback_warning}");
|
||||
}
|
||||
}
|
||||
|
||||
fn store_startup_log_path(storage: &OnceLock<String>, log_path: &FileSpec) {
|
||||
let _ = storage.set(convert_log_path_to_string(log_path));
|
||||
}
|
||||
|
||||
fn convert_log_path_to_string(log_path: &FileSpec) -> String {
|
||||
@ -106,25 +118,123 @@ fn convert_log_path_to_string(log_path: &FileSpec) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_startup_log_path(bundle_identifier: &str) -> (PathBuf, Option<String>) {
|
||||
if is_flatpak() {
|
||||
return select_flatpak_startup_log_path(
|
||||
bundle_identifier,
|
||||
dirs::data_local_dir(),
|
||||
PathBuf::from(FLATPAK_PERSISTENT_DATA_DIRECTORY),
|
||||
temp_dir(),
|
||||
ensure_log_directory_is_writable,
|
||||
).unwrap_or_else(|error| panic!("Cannot prepare a Flatpak startup log directory: {error}"));
|
||||
}
|
||||
|
||||
(get_non_flatpak_startup_log_path(
|
||||
home_directory(),
|
||||
current_dir().ok(),
|
||||
temp_dir(),
|
||||
), None)
|
||||
}
|
||||
|
||||
// Note: Rust plans to remove the deprecation flag for std::env::home_dir() in Rust 1.86.0.
|
||||
#[allow(deprecated)]
|
||||
fn get_startup_log_path() -> String {
|
||||
match std::env::home_dir() {
|
||||
fn home_directory() -> Option<PathBuf> {
|
||||
std::env::home_dir()
|
||||
}
|
||||
|
||||
fn get_non_flatpak_startup_log_path(
|
||||
home_directory: Option<PathBuf>,
|
||||
working_directory: Option<PathBuf>,
|
||||
temporary_directory: PathBuf,
|
||||
) -> PathBuf {
|
||||
match home_directory {
|
||||
// Case: We could determine the home directory:
|
||||
Some(home_dir) => home_dir.to_str().unwrap().to_string(),
|
||||
Some(home_directory) => home_directory,
|
||||
|
||||
// Case: We could not determine the home directory. Let's try to use the working directory:
|
||||
None => match current_dir() {
|
||||
None => match working_directory {
|
||||
|
||||
// Case: We could determine the working directory:
|
||||
Ok(working_directory) => working_directory.to_str().unwrap().to_string(),
|
||||
Some(working_directory) => working_directory,
|
||||
|
||||
// Case: We could not determine the working directory. Let's use the temporary directory:
|
||||
Err(_) => temp_dir().to_str().unwrap().to_string(),
|
||||
None => temporary_directory,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn select_flatpak_startup_log_path<F>(
|
||||
bundle_identifier: &str,
|
||||
data_local_directory: Option<PathBuf>,
|
||||
persistent_data_directory: PathBuf,
|
||||
temporary_directory: PathBuf,
|
||||
mut ensure_writable: F,
|
||||
) -> Result<(PathBuf, Option<String>), String>
|
||||
where
|
||||
F: FnMut(&Path) -> Result<(), String>,
|
||||
{
|
||||
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 temporary_fallback = temporary_directory.join(bundle_identifier).join("data");
|
||||
let mut failures = Vec::new();
|
||||
|
||||
if let Some(standard_directory) = standard_directory {
|
||||
match ensure_writable(&standard_directory) {
|
||||
Ok(()) => return Ok((standard_directory, None)),
|
||||
Err(error) => failures.push(format!("standard path failed: {error}")),
|
||||
}
|
||||
} else {
|
||||
failures.push(String::from("standard path failed: dirs::data_local_dir() returned no path"));
|
||||
}
|
||||
|
||||
match ensure_writable(&persistent_fallback) {
|
||||
Ok(()) => {
|
||||
let warning = format!(
|
||||
"The standard Flatpak startup log directory was unavailable; using persistent fallback '{}'. {}",
|
||||
persistent_fallback.display(),
|
||||
failures.join("; "),
|
||||
);
|
||||
|
||||
return Ok((persistent_fallback, Some(warning)));
|
||||
},
|
||||
|
||||
Err(error) => failures.push(format!("persistent fallback failed: {error}")),
|
||||
}
|
||||
|
||||
match ensure_writable(&temporary_fallback) {
|
||||
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> {
|
||||
create_dir_all(directory).map_err(|error| format!("could not create '{}': {error}", directory.display()))?;
|
||||
let log_file_path = directory.join(if cfg!(unix) {
|
||||
".AI Studio Events.log"
|
||||
} else {
|
||||
"AI Studio Events.log"
|
||||
});
|
||||
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
.map(|_| ())
|
||||
.map_err(|error| format!("could not write '{}': {error}", log_file_path.display()))
|
||||
}
|
||||
|
||||
/// Switch the logging system to a file-based output inside the given directory.
|
||||
pub fn switch_to_file_logging(logger_path: PathBuf) -> Result<(), Box<dyn Error>>{
|
||||
let log_path = FileSpec::default()
|
||||
@ -317,3 +427,123 @@ pub struct LogEventResponse {
|
||||
success: bool,
|
||||
issue: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const BUNDLE_IDENTIFIER: &str = "org.mindworkai.AIStudio";
|
||||
|
||||
#[test]
|
||||
fn flatpak_standard_path_matches_tauri_local_data_path() {
|
||||
let base_directory = PathBuf::from("/var/data");
|
||||
let expected = base_directory.join(BUNDLE_IDENTIFIER).join("data");
|
||||
|
||||
let (selected, warning) = select_flatpak_startup_log_path(
|
||||
BUNDLE_IDENTIFIER,
|
||||
Some(base_directory),
|
||||
PathBuf::from("/persistent"),
|
||||
PathBuf::from("/temporary"),
|
||||
|_| Ok(()),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(selected, expected);
|
||||
assert!(warning.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatpak_uses_persistent_fallback_when_standard_path_is_unwritable() {
|
||||
let standard = PathBuf::from("/standard").join(BUNDLE_IDENTIFIER).join("data");
|
||||
let persistent = PathBuf::from("/var/data").join(BUNDLE_IDENTIFIER).join("data");
|
||||
|
||||
let (selected, warning) = select_flatpak_startup_log_path(
|
||||
BUNDLE_IDENTIFIER,
|
||||
Some(PathBuf::from("/standard")),
|
||||
PathBuf::from("/var/data"),
|
||||
PathBuf::from("/temporary"),
|
||||
|candidate| {
|
||||
if candidate == standard {
|
||||
Err(String::from("read-only"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(selected, persistent);
|
||||
assert!(warning.unwrap().contains("persistent fallback"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatpak_uses_temporary_fallback_when_persistent_path_is_unwritable() {
|
||||
let temporary = PathBuf::from("/tmp").join(BUNDLE_IDENTIFIER).join("data");
|
||||
|
||||
let (selected, warning) = select_flatpak_startup_log_path(
|
||||
BUNDLE_IDENTIFIER,
|
||||
None,
|
||||
PathBuf::from("/var/data"),
|
||||
PathBuf::from("/tmp"),
|
||||
|candidate| {
|
||||
if candidate == temporary {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(String::from("read-only"))
|
||||
}
|
||||
},
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(selected, temporary);
|
||||
assert!(warning.unwrap().contains("temporary fallback"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_flatpak_path_selection_keeps_existing_fallback_order() {
|
||||
let home = PathBuf::from("/home/user");
|
||||
let working = PathBuf::from("/working");
|
||||
let temporary = PathBuf::from("/tmp");
|
||||
|
||||
assert_eq!(
|
||||
get_non_flatpak_startup_log_path(Some(home.clone()), Some(working.clone()), temporary.clone()),
|
||||
home,
|
||||
);
|
||||
assert_eq!(
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_log_path_storage_uses_selected_fallback_path() {
|
||||
let temporary = PathBuf::from("/tmp").join(BUNDLE_IDENTIFIER).join("data");
|
||||
let (selected, _) = select_flatpak_startup_log_path(
|
||||
BUNDLE_IDENTIFIER,
|
||||
None,
|
||||
PathBuf::from("/var/data"),
|
||||
PathBuf::from("/tmp"),
|
||||
|candidate| {
|
||||
if candidate == temporary {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(String::from("unavailable"))
|
||||
}
|
||||
},
|
||||
).unwrap();
|
||||
let log_path = FileSpec::default()
|
||||
.directory(selected)
|
||||
.basename(".AI Studio Events")
|
||||
.suppress_timestamp()
|
||||
.suffix("log");
|
||||
let storage = OnceLock::new();
|
||||
|
||||
store_startup_log_path(&storage, &log_path);
|
||||
|
||||
assert_eq!(
|
||||
storage.get().unwrap(),
|
||||
"/tmp/org.mindworkai.AIStudio/data/.AI Studio Events.log",
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -28,8 +28,10 @@ use mindwork_ai_studio::secret::init_secret_store;
|
||||
// change requires explicit Linux startup tests; compiling successfully is not sufficient.
|
||||
fn main() {
|
||||
let metadata = MetaData::init_from_string(include_str!("../../metadata.txt"));
|
||||
let tauri_context = tauri::generate_context!();
|
||||
let bundle_identifier = tauri_context.config().identifier.clone();
|
||||
|
||||
init_logging();
|
||||
init_logging(&bundle_identifier);
|
||||
info!("Starting MindWork AI Studio:");
|
||||
|
||||
let working_directory = std::env::current_dir().unwrap();
|
||||
@ -59,5 +61,5 @@ fn main() {
|
||||
generate_runtime_certificate();
|
||||
start_runtime_api();
|
||||
|
||||
start_tauri();
|
||||
start_tauri(tauri_context);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user