mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 22:20:51 +01:00
fixed build
This commit is contained in:
9
src-tauri/.cargo/config.toml
Normal file
9
src-tauri/.cargo/config.toml
Normal file
@ -0,0 +1,9 @@
|
||||
# Nur dieser Inhalt in src-tauri/.cargo/config.toml
|
||||
|
||||
[target.aarch64-linux-android]
|
||||
# Ersetze die Pfade durch deine tatsächlichen NDK-Pfade
|
||||
# Dein NDK-Basispfad: /home/haex/Android/Sdk/ndk/29.0.13113456
|
||||
# Stelle sicher, dass der clang-Name (mit API-Level, z.B. ...24-clang) korrekt ist.
|
||||
linker = "/home/haex/Android/Sdk/ndk/29.0.13113456/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang"
|
||||
#ar = "/home/haex/Android/Sdk/ndk/29.0.13113456/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
|
||||
#ranlib = "/home/haex/Android/Sdk/ndk/29.0.13113456/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib"
|
||||
@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<!-- AndroidTV support -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
|
||||
|
||||
BIN
src-tauri/gen/android/app/src/main/assets/database/vault.db
Normal file
BIN
src-tauri/gen/android/app/src/main/assets/database/vault.db
Normal file
Binary file not shown.
@ -1,11 +1,15 @@
|
||||
// database/mod.rs
|
||||
pub mod core;
|
||||
|
||||
use rusqlite::Connection;
|
||||
use rusqlite::{Connection, OpenFlags, Result as RusqliteResult};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use tauri::{path::BaseDirectory, AppHandle, Manager, State};
|
||||
use tauri::utils::resources::Resource;
|
||||
use tauri::{path::BaseDirectory, AppHandle, Manager, State, Wry};
|
||||
use tokio::io::join;
|
||||
|
||||
pub struct DbConnection(pub Mutex<Option<Connection>>);
|
||||
|
||||
// Öffentliche Funktionen für direkten Datenbankzugriff
|
||||
@ -29,7 +33,7 @@ pub async fn sql_execute(
|
||||
|
||||
/// Erstellt eine verschlüsselte Datenbank
|
||||
#[tauri::command]
|
||||
pub fn create_encrypted_database(
|
||||
pub fn create_encrypted_database_old(
|
||||
app_handle: AppHandle,
|
||||
path: String,
|
||||
key: String,
|
||||
@ -39,7 +43,7 @@ pub fn create_encrypted_database(
|
||||
|
||||
let resource_path = app_handle
|
||||
.path()
|
||||
.resolve("resources/vault.db", BaseDirectory::Resource)
|
||||
.resolve("vault.db", BaseDirectory::Resource)
|
||||
.map_err(|e| format!("Fehler beim Auflösen des Ressourcenpfads: {}", e))?;
|
||||
|
||||
// Prüfen, ob die Ressourcendatei existiert
|
||||
@ -229,3 +233,325 @@ pub fn open_encrypted_database(
|
||||
|
||||
Ok(format!("success"))
|
||||
}
|
||||
|
||||
// Notwendige Imports an den Anfang des Moduls stellen
|
||||
//use tauri::{AppHandle, Manager, State, path::BaseDirectory, Wry};
|
||||
//use rusqlite::{Connection, OpenFlags, Result as RusqliteResult};
|
||||
//use std::fs;
|
||||
//use std::path::{Path, PathBuf};
|
||||
//use std::sync::Mutex; // Für den State
|
||||
|
||||
// Stelle sicher, dass dein DbConnection-Typ hier bekannt ist.
|
||||
// z.B. durch pub struct DbConnection(pub Mutex<Option<Connection>>);
|
||||
// oder wenn es in einem anderen Modul ist: use crate::path_to::DbConnection;
|
||||
// Für dieses Beispiel gehe ich davon aus, dass es in crate::DbConnection liegt.
|
||||
// Ersetze `crate::DbConnection` mit dem korrekten Pfad zu deiner Definition.
|
||||
//type SharedDbConnectionState = State<'_, crate::DbConnection>;
|
||||
|
||||
/// Hilfsfunktion: Lädt ein Asset und kopiert es in eine temporäre Datei.
|
||||
/// Gibt den Pfad zur temporären Datei zurück.
|
||||
fn prepare_temporary_asset_db(
|
||||
app_handle: &AppHandle<Wry>,
|
||||
asset_name: &str,
|
||||
temp_base_dir: BaseDirectory,
|
||||
) -> Result<PathBuf, String> {
|
||||
println!("Lade Asset '{}' aus dem App-Bundle...", asset_name);
|
||||
|
||||
//.resolve("vault.db", BaseDirectory::Resource)
|
||||
let asset_bytes = app_handle
|
||||
.asset_resolver()
|
||||
.get(asset_name.to_string())
|
||||
.ok_or_else(|| format!("Asset '{}' wurde nicht im Bundle gefunden.", asset_name))?
|
||||
.bytes()
|
||||
.to_vec();
|
||||
|
||||
println!(
|
||||
"Asset '{}' erfolgreich geladen ({} bytes).",
|
||||
asset_name,
|
||||
asset_bytes.len()
|
||||
);
|
||||
|
||||
let temp_db_filename = format!("temp_unencrypted_{}", asset_name);
|
||||
let temp_db_path = app_handle
|
||||
.path()
|
||||
.resolve(&temp_db_filename, temp_base_dir)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Auflösen des Pfads für die temporäre DB '{}': {}",
|
||||
temp_db_filename, e
|
||||
)
|
||||
})?;
|
||||
|
||||
println!(
|
||||
"Temporärer Pfad für unverschlüsselte DB: {}",
|
||||
temp_db_path.display()
|
||||
);
|
||||
|
||||
if let Some(parent) = temp_db_path.parent() {
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Erstellen des temporären Verzeichnisses '{}': {}",
|
||||
parent.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
println!("Temporäres Verzeichnis '{}' erstellt.", parent.display());
|
||||
}
|
||||
}
|
||||
|
||||
if temp_db_path.exists() {
|
||||
fs::remove_file(&temp_db_path).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Löschen der alten temporären DB '{}': {}",
|
||||
temp_db_path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
println!("Alte temporäre DB '{}' gelöscht.", temp_db_path.display());
|
||||
}
|
||||
|
||||
fs::write(&temp_db_path, &asset_bytes).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Schreiben der Asset-DB nach '{}': {}",
|
||||
temp_db_path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
println!(
|
||||
"Asset-DB erfolgreich nach '{}' geschrieben.",
|
||||
temp_db_path.display()
|
||||
);
|
||||
|
||||
Ok(temp_db_path)
|
||||
}
|
||||
|
||||
/// Hilfsfunktion: Verschlüsselt eine Quelldatenbank in eine Zieldatenbank.
|
||||
fn encrypt_database_from_source(
|
||||
unencrypted_source_path: &Path,
|
||||
target_encrypted_path_str: &str,
|
||||
key: &str,
|
||||
) -> Result<(), String> {
|
||||
println!(
|
||||
"Öffne temporäre Quelldatenbank '{}'...",
|
||||
unencrypted_source_path.display()
|
||||
);
|
||||
let source_conn = Connection::open(unencrypted_source_path).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Öffnen der Quelldatenbank '{}': {}",
|
||||
unencrypted_source_path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
println!(
|
||||
"Verbindung zur Quelldatenbank '{}' geöffnet.",
|
||||
unencrypted_source_path.display()
|
||||
);
|
||||
|
||||
let final_encrypted_db_path = PathBuf::from(target_encrypted_path_str);
|
||||
println!(
|
||||
"Zielpfad für verschlüsselte DB: {}",
|
||||
final_encrypted_db_path.display()
|
||||
);
|
||||
|
||||
if let Some(parent) = final_encrypted_db_path.parent() {
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Erstellen des Zielverzeichnisses '{}': {}",
|
||||
parent.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
println!("Zielverzeichnis '{}' erstellt.", parent.display());
|
||||
}
|
||||
}
|
||||
if final_encrypted_db_path.exists() {
|
||||
fs::remove_file(&final_encrypted_db_path).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Löschen der alten verschlüsselten DB '{}': {}",
|
||||
final_encrypted_db_path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
println!(
|
||||
"Alte verschlüsselte DB '{}' gelöscht.",
|
||||
final_encrypted_db_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let attach_path_str = final_encrypted_db_path.to_str().ok_or_else(|| {
|
||||
format!(
|
||||
"Ungültiger UTF-8 Pfad für ATTACH: {}",
|
||||
final_encrypted_db_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
println!(
|
||||
"Hänge neue verschlüsselte DB an: '{}' mit KEY '{}'",
|
||||
attach_path_str, key
|
||||
);
|
||||
source_conn
|
||||
.execute(
|
||||
"ATTACH DATABASE ?1 AS encrypted_vault KEY ?2;",
|
||||
&[attach_path_str, key],
|
||||
)
|
||||
.map_err(|e| format!("Fehler bei ATTACH DATABASE an '{}': {}", attach_path_str, e))?;
|
||||
println!("Verschlüsselte DB 'encrypted_vault' erfolgreich angehängt.");
|
||||
|
||||
println!("Exportiere Daten von 'main' (Quelle) nach 'encrypted_vault'...");
|
||||
if let Err(e) = source_conn.execute("SELECT sqlcipher_export('encrypted_vault');", []) {
|
||||
eprintln!("!!! FEHLER während sqlcipher_export: {}", e);
|
||||
source_conn
|
||||
.execute("DETACH DATABASE encrypted_vault;", [])
|
||||
.ok(); // Best-effort cleanup
|
||||
return Err(format!("Fehler bei sqlcipher_export: {}", e));
|
||||
}
|
||||
println!("SQLCipher Export nach 'encrypted_vault' erfolgreich.");
|
||||
|
||||
println!("Löse 'encrypted_vault'...");
|
||||
source_conn
|
||||
.execute("DETACH DATABASE encrypted_vault;", [])
|
||||
.map_err(|e| format!("Fehler bei DETACH DATABASE 'encrypted_vault': {}", e))?;
|
||||
println!("'encrypted_vault' erfolgreich gelöst.");
|
||||
|
||||
// Verbindung zur Quelldatenbank wird hier durch drop(source_conn) geschlossen.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Hilfsfunktion: Öffnet eine verschlüsselte Datenbank und verifiziert sie.
|
||||
/// Gibt die geöffnete und verifizierte Verbindung zurück.
|
||||
fn open_and_verify_encrypted_db(db_path: &Path, key: &str) -> Result<Connection, String> {
|
||||
println!(
|
||||
"Öffne verschlüsselte DB '{}' zur Überprüfung...",
|
||||
db_path.display()
|
||||
);
|
||||
let conn = Connection::open(db_path).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Öffnen der verschlüsselten DB '{}' für Check: {}",
|
||||
db_path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
conn.pragma_update(None, "key", key).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Setzen des PRAGMA key für DB '{}': {}",
|
||||
db_path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
println!("PRAGMA key für DB '{}' gesetzt.", db_path.display());
|
||||
|
||||
println!("Prüfe SQLCipher-Version auf DB '{}'...", db_path.display());
|
||||
match conn.query_row("PRAGMA cipher_version;", [], |row| row.get::<_, String>(0)) {
|
||||
Ok(version) => {
|
||||
println!(
|
||||
"SQLCipher ist aktiv auf DB '{}'! Version: {}",
|
||||
db_path.display(),
|
||||
version
|
||||
);
|
||||
match conn.query_row(
|
||||
"SELECT count(*) FROM sqlite_master WHERE type='table';",
|
||||
[],
|
||||
|row| row.get::<_, i32>(0),
|
||||
) {
|
||||
Ok(count) => println!(
|
||||
"Testabfrage erfolgreich: {} Tabelle(n) in DB '{}' gefunden.",
|
||||
count,
|
||||
db_path.display()
|
||||
),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Fehler bei Testabfrage auf verschlüsselter DB '{}': {}",
|
||||
db_path.display(),
|
||||
e
|
||||
);
|
||||
return Err(format!(
|
||||
"Testabfrage auf verschlüsselter DB '{}' fehlgeschlagen: {}",
|
||||
db_path.display(),
|
||||
e
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"FEHLER: SQLCipher scheint NICHT aktiv zu sein auf DB '{}'!",
|
||||
db_path.display()
|
||||
);
|
||||
eprintln!("'PRAGMA cipher_version;' schlug fehl: {}", e);
|
||||
return Err(format!(
|
||||
"SQLCipher Aktivitätscheck für DB '{}' fehlgeschlagen: {}",
|
||||
db_path.display(),
|
||||
e
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
/// Hauptfunktion: Erstellt eine verschlüsselte Datenbank aus einem gebündelten Asset.
|
||||
#[tauri::command]
|
||||
pub fn create_encrypted_database(
|
||||
app_handle: AppHandle<Wry>,
|
||||
path: String,
|
||||
key: String,
|
||||
state: State<'_, DbConnection>,
|
||||
) -> Result<String, String> {
|
||||
let asset_name = "database/vault.db";
|
||||
let temp_db_path: PathBuf; // Muss deklariert werden, um im Fehlerfall aufgeräumt werden zu können
|
||||
|
||||
// Schritt 1: Asset vorbereiten
|
||||
match prepare_temporary_asset_db(&app_handle, &asset_name, BaseDirectory::AppData) {
|
||||
Ok(path) => temp_db_path = path,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
// Schritt 2: Datenbank verschlüsseln
|
||||
// Wir geben einen String-Slice für path, da die Funktion das erwartet.
|
||||
if let Err(e) = encrypt_database_from_source(&temp_db_path, &path, &key) {
|
||||
// Versuche, die temporäre Datei auch im Fehlerfall zu löschen
|
||||
let _ = fs::remove_file(&temp_db_path); // Fehler beim Löschen hier ignorieren
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// Schritt 3: Temporäre Datei aufräumen
|
||||
if let Err(e) = fs::remove_file(&temp_db_path) {
|
||||
// Logge den Fehler, aber fahre fort, da die verschlüsselte DB erstellt wurde
|
||||
eprintln!("Warnung: Fehler beim Löschen der temporären DB '{}': {}. Die verschlüsselte DB wurde jedoch erstellt.", temp_db_path.display(), e);
|
||||
} else {
|
||||
println!(
|
||||
"Temporäre DB '{}' erfolgreich gelöscht.",
|
||||
temp_db_path.display()
|
||||
);
|
||||
}
|
||||
println!("Datenbank erfolgreich nach '{}' verschlüsselt.", path);
|
||||
|
||||
// Schritt 4: Neu erstellte verschlüsselte Datenbank öffnen und verifizieren
|
||||
let final_encrypted_db_path = PathBuf::from(path.clone()); // Klonen, da String für Pfad benötigt wird
|
||||
let final_conn = match open_and_verify_encrypted_db(&final_encrypted_db_path, &key) {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
// Wenn das Öffnen/Verifizieren fehlschlägt, existiert die Datei vielleicht, ist aber unbrauchbar.
|
||||
// Je nach Strategie könnte man hier die `final_encrypted_db_path` löschen.
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Schritt 5: Datenbankverbindung im State aktualisieren
|
||||
println!(
|
||||
"Aktualisiere DB-Verbindung im State mit '{}'",
|
||||
final_encrypted_db_path.display()
|
||||
);
|
||||
let mut db_state_lock = state
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|e| format!("Mutex-Fehler beim Sperren des DB-Status: {}", e.to_string()))?;
|
||||
*db_state_lock = Some(final_conn);
|
||||
|
||||
Ok(format!(
|
||||
"Verschlüsselte Datenbank erfolgreich erstellt, geprüft und im State gespeichert unter: {}",
|
||||
final_encrypted_db_path.display()
|
||||
))
|
||||
}
|
||||
|
||||
@ -12,6 +12,73 @@ pub fn run() {
|
||||
let protocol_name = "haex-extension";
|
||||
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
// --- START DER ASSET-INVENTUR (Korrigierte Version) ---
|
||||
println!("\n[INVENTUR] App-Setup wird ausgeführt. Liste alle Assets im Bundle auf...");
|
||||
|
||||
let mut found_assets_count = 0;
|
||||
|
||||
// KORREKTE METHODE: Direkt über eine Referenz auf den AssetResolver iterieren
|
||||
for asset_name in app.asset_resolver().iter() {
|
||||
found_assets_count += 1;
|
||||
// Wir geben jeden gefundenen Asset-Namen aus
|
||||
println!("[INVENTUR] Gefundenes Asset: '{}'", asset_name.0);
|
||||
}
|
||||
|
||||
if found_assets_count == 0 {
|
||||
println!("[INVENTUR] Es wurden KEINE Assets im Bundle gefunden!");
|
||||
} else {
|
||||
println!(
|
||||
"[INVENTUR] Inventur abgeschlossen. {} Assets gefunden.",
|
||||
found_assets_count
|
||||
);
|
||||
}
|
||||
|
||||
println!("[INVENTUR] --- ENDE DER INVENTUR ---\n");
|
||||
// --- ENDE DER ASSET-INVENTUR ---
|
||||
// --- START DES DEFINITIVEN ASSET-TESTS ---
|
||||
println!("\n[DEBUG] App-Setup wird ausgeführt. Versuche, die Datenbank zu laden...");
|
||||
|
||||
// BITTE SICHERSTELLEN: Dieser String muss EXAKT dem SCHLÜSSEL (KEY)
|
||||
// in deiner tauri.conf.json entsprechen!
|
||||
let asset_to_find = "database/vault.db";
|
||||
|
||||
println!(
|
||||
"[DEBUG] Suche nach Asset mit dem Alias: '{}'",
|
||||
asset_to_find
|
||||
);
|
||||
|
||||
match app.asset_resolver().get(asset_to_find.to_string()) {
|
||||
Some(asset) => {
|
||||
// ERFOLG! Das Asset wurde gefunden.
|
||||
println!("\n✅ ✅ ✅ ERFOLG! ✅ ✅ ✅");
|
||||
println!(
|
||||
"[DEBUG] Asset '{}' wurde im Bundle gefunden.",
|
||||
asset_to_find
|
||||
);
|
||||
println!("[DEBUG] Größe der Datenbank: {} Bytes.", asset.bytes.len());
|
||||
}
|
||||
None => {
|
||||
// FEHLER! Das Asset wurde nicht gefunden.
|
||||
println!("\n❌ ❌ ❌ FEHLER! ❌ ❌ ❌");
|
||||
println!(
|
||||
"[DEBUG] Asset '{}' wurde NICHT im Bundle gefunden.",
|
||||
asset_to_find
|
||||
);
|
||||
println!("[DEBUG] Mögliche Ursachen:");
|
||||
println!("[DEBUG] 1. Der Alias-String im Code ist falsch (Tippfehler?).");
|
||||
println!("[DEBUG] 2. Der Schlüssel in 'tauri.conf.json' ist anders.");
|
||||
println!(
|
||||
"[DEBUG] 3. Der Build-Cache ist veraltet (lösche 'src-tauri/target')."
|
||||
);
|
||||
}
|
||||
}
|
||||
println!("[DEBUG] --- ENDE DES ASSET-TESTS ---\n");
|
||||
// --- ENDE DES DEFINITIVEN ASSET-TESTS ---
|
||||
|
||||
// Hier kann dein restlicher Setup-Code stehen bleiben
|
||||
Ok(())
|
||||
})
|
||||
.register_uri_scheme_protocol(protocol_name, move |context, request| {
|
||||
match extension::core::extension_protocol_handler(&context, &request) {
|
||||
Ok(response) => response, // Wenn der Handler Ok ist, gib die Response direkt zurück
|
||||
@ -42,11 +109,11 @@ pub fn run() {
|
||||
}
|
||||
}
|
||||
})
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.manage(DbConnection(Mutex::new(None)))
|
||||
.manage(ExtensionState::default())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
"identifier": "space.haex.hub",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"devUrl": "http://0.0.0.0:3001",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeBuildCommand": "pnpm generate",
|
||||
"frontendDist": "../.output/public"
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
@ -40,7 +40,7 @@
|
||||
},
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": ["*"]
|
||||
"scope": ["$APPDATA", "$RESOURCE"]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -54,22 +54,6 @@
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": {
|
||||
"database/vault.db": "resources/vault.db"
|
||||
},
|
||||
"linux": {
|
||||
"appimage": {
|
||||
"bundleMediaFramework": false,
|
||||
"files": {}
|
||||
},
|
||||
"deb": {
|
||||
"files": {}
|
||||
},
|
||||
"rpm": {
|
||||
"epoch": 0,
|
||||
"files": {},
|
||||
"release": "1"
|
||||
}
|
||||
}
|
||||
"resources": ["test.txt"]
|
||||
}
|
||||
}
|
||||
|
||||
0
src-tauri/test.txt
Normal file
0
src-tauri/test.txt
Normal file
Reference in New Issue
Block a user