mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 06:30:50 +01:00
polyfill for spa added. works now on android
This commit is contained in:
@ -8,10 +8,11 @@ use crate::extension::database::executor::SqlExecutor;
|
||||
use crate::extension::error::ExtensionError;
|
||||
use crate::extension::permissions::manager::PermissionManager;
|
||||
use crate::extension::permissions::types::ExtensionPermission;
|
||||
use crate::table_names::TABLE_EXTENSIONS;
|
||||
use crate::table_names::{TABLE_EXTENSIONS, TABLE_EXTENSION_PERMISSIONS};
|
||||
use crate::AppState;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
use std::fs;
|
||||
use std::io::Cursor;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@ -33,6 +34,7 @@ pub struct MissingExtension {
|
||||
}
|
||||
|
||||
struct ExtensionDataFromDb {
|
||||
full_extension_id: String,
|
||||
manifest: ExtensionManifest,
|
||||
enabled: bool,
|
||||
}
|
||||
@ -64,19 +66,19 @@ impl ExtensionManager {
|
||||
|
||||
/// Extrahiert eine Extension-ZIP-Datei und validiert das Manifest
|
||||
fn extract_and_validate_extension(
|
||||
source_path: &str,
|
||||
bytes: Vec<u8>,
|
||||
temp_prefix: &str,
|
||||
) -> Result<ExtractedExtension, ExtensionError> {
|
||||
let source = PathBuf::from(source_path);
|
||||
let temp = std::env::temp_dir().join(format!("{}_{}", temp_prefix, uuid::Uuid::new_v4()));
|
||||
|
||||
std::fs::create_dir_all(&temp).map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
fs::create_dir_all(&temp)
|
||||
.map_err(|e| ExtensionError::filesystem_with_path(temp.display().to_string(), e))?;
|
||||
|
||||
let file = File::open(&source).map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
let mut archive =
|
||||
ZipArchive::new(file).map_err(|e| ExtensionError::InstallationFailed {
|
||||
let mut archive = ZipArchive::new(Cursor::new(bytes)).map_err(|e| {
|
||||
ExtensionError::InstallationFailed {
|
||||
reason: format!("Invalid ZIP: {}", e),
|
||||
})?;
|
||||
}
|
||||
})?;
|
||||
|
||||
archive
|
||||
.extract(&temp)
|
||||
@ -84,7 +86,30 @@ impl ExtensionManager {
|
||||
reason: format!("Cannot extract ZIP: {}", e),
|
||||
})?;
|
||||
|
||||
// Check if manifest.json is directly in temp or in a subdirectory
|
||||
let manifest_path = temp.join("manifest.json");
|
||||
let actual_dir = if manifest_path.exists() {
|
||||
temp.clone()
|
||||
} else {
|
||||
// manifest.json is in a subdirectory - find it
|
||||
let mut found_dir = None;
|
||||
for entry in fs::read_dir(&temp)
|
||||
.map_err(|e| ExtensionError::filesystem_with_path(temp.display().to_string(), e))?
|
||||
{
|
||||
let entry = entry.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() && path.join("manifest.json").exists() {
|
||||
found_dir = Some(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
found_dir.ok_or_else(|| ExtensionError::ManifestError {
|
||||
reason: "manifest.json not found in extension archive".to_string(),
|
||||
})?
|
||||
};
|
||||
|
||||
let manifest_path = actual_dir.join("manifest.json");
|
||||
let manifest_content =
|
||||
std::fs::read_to_string(&manifest_path).map_err(|e| ExtensionError::ManifestError {
|
||||
reason: format!("Cannot read manifest: {}", e),
|
||||
@ -92,14 +117,14 @@ impl ExtensionManager {
|
||||
|
||||
let manifest: ExtensionManifest = serde_json::from_str(&manifest_content)?;
|
||||
|
||||
let content_hash = ExtensionCrypto::hash_directory(&temp).map_err(|e| {
|
||||
let content_hash = ExtensionCrypto::hash_directory(&actual_dir).map_err(|e| {
|
||||
ExtensionError::SignatureVerificationFailed {
|
||||
reason: e.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(ExtractedExtension {
|
||||
temp_dir: temp,
|
||||
temp_dir: actual_dir,
|
||||
manifest,
|
||||
content_hash,
|
||||
})
|
||||
@ -119,7 +144,8 @@ impl ExtensionManager {
|
||||
|
||||
// Sicherstellen, dass das Basisverzeichnis existiert
|
||||
if !path.exists() {
|
||||
fs::create_dir_all(&path).map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
fs::create_dir_all(&path)
|
||||
.map_err(|e| ExtensionError::filesystem_with_path(path.display().to_string(), e))?;
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
@ -145,9 +171,34 @@ impl ExtensionManager {
|
||||
app_handle: &AppHandle,
|
||||
full_extension_id: &str,
|
||||
) -> Result<PathBuf, ExtensionError> {
|
||||
// Parse full_extension_id: key_hash_name_version
|
||||
// Split on first underscore to get key_hash
|
||||
let first_underscore =
|
||||
full_extension_id
|
||||
.find('_')
|
||||
.ok_or_else(|| ExtensionError::ValidationError {
|
||||
reason: format!("Invalid full_extension_id format: {}", full_extension_id),
|
||||
})?;
|
||||
|
||||
let key_hash = &full_extension_id[..first_underscore];
|
||||
let rest = &full_extension_id[first_underscore + 1..];
|
||||
|
||||
// Split on last underscore to get version
|
||||
let last_underscore = rest
|
||||
.rfind('_')
|
||||
.ok_or_else(|| ExtensionError::ValidationError {
|
||||
reason: format!("Invalid full_extension_id format: {}", full_extension_id),
|
||||
})?;
|
||||
|
||||
let name = &rest[..last_underscore];
|
||||
let version = &rest[last_underscore + 1..];
|
||||
|
||||
// Build hierarchical path: key_hash/name/version/
|
||||
let specific_extension_dir = self
|
||||
.get_base_extension_dir(app_handle)?
|
||||
.join(full_extension_id);
|
||||
.join(key_hash)
|
||||
.join(name)
|
||||
.join(version);
|
||||
|
||||
Ok(specific_extension_dir)
|
||||
}
|
||||
@ -220,14 +271,44 @@ impl ExtensionManager {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn remove_extension_by_full_id(
|
||||
&self,
|
||||
app_handle: &AppHandle,
|
||||
full_extension_id: &str,
|
||||
state: &State<'_, AppState>,
|
||||
) -> Result<(), ExtensionError> {
|
||||
// Parse full_extension_id: key_hash_name_version
|
||||
// Since _ is not allowed in name and version, we can split safely
|
||||
let parts: Vec<&str> = full_extension_id.split('_').collect();
|
||||
|
||||
if parts.len() != 3 {
|
||||
return Err(ExtensionError::ValidationError {
|
||||
reason: format!(
|
||||
"Invalid full_extension_id format (expected 3 parts): {}",
|
||||
full_extension_id
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
let key_hash = parts[0];
|
||||
let name = parts[1];
|
||||
let version = parts[2];
|
||||
|
||||
self.remove_extension_internal(app_handle, key_hash, name, version, state)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn remove_extension_internal(
|
||||
&self,
|
||||
app_handle: &AppHandle,
|
||||
key_hash: &str,
|
||||
extension_id: &str,
|
||||
extension_name: &str,
|
||||
extension_version: &str,
|
||||
state: &State<'_, AppState>,
|
||||
) -> Result<(), ExtensionError> {
|
||||
// Erstelle full_extension_id: key_hash_name_version
|
||||
let full_extension_id = format!("{}_{}_{}",key_hash, extension_name, extension_version);
|
||||
|
||||
// Lösche Permissions und Extension-Eintrag in einer Transaktion
|
||||
with_connection(&state.db, |conn| {
|
||||
let tx = conn.transaction().map_err(DatabaseError::from)?;
|
||||
@ -236,31 +317,59 @@ impl ExtensionManager {
|
||||
reason: "Failed to lock HLC service".to_string(),
|
||||
})?;
|
||||
|
||||
// Lösche alle Permissions
|
||||
PermissionManager::delete_permissions_in_transaction(&tx, &hlc_service, extension_id)?;
|
||||
// Lösche alle Permissions mit full_extension_id
|
||||
PermissionManager::delete_permissions_in_transaction(
|
||||
&tx,
|
||||
&hlc_service,
|
||||
&full_extension_id,
|
||||
)?;
|
||||
|
||||
// Lösche Extension-Eintrag
|
||||
// Lösche Extension-Eintrag mit full_extension_id
|
||||
let sql = format!("DELETE FROM {} WHERE id = ?", TABLE_EXTENSIONS);
|
||||
SqlExecutor::execute_internal_typed(
|
||||
&tx,
|
||||
&hlc_service,
|
||||
&sql,
|
||||
rusqlite::params![extension_id],
|
||||
rusqlite::params![full_extension_id],
|
||||
)?;
|
||||
|
||||
tx.commit().map_err(DatabaseError::from)
|
||||
})?;
|
||||
|
||||
// Entferne aus dem In-Memory-Manager
|
||||
self.remove_extension(&extension_id)?;
|
||||
// Entferne aus dem In-Memory-Manager mit full_extension_id
|
||||
self.remove_extension(&full_extension_id)?;
|
||||
|
||||
// Lösche Dateien vom Dateisystem
|
||||
// Lösche nur den spezifischen Versions-Ordner: key_hash/name/version
|
||||
let extension_dir =
|
||||
self.get_extension_dir(app_handle, key_hash, extension_id, extension_version)?;
|
||||
self.get_extension_dir(app_handle, key_hash, extension_name, extension_version)?;
|
||||
|
||||
if extension_dir.exists() {
|
||||
std::fs::remove_dir_all(&extension_dir)
|
||||
.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
std::fs::remove_dir_all(&extension_dir).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(extension_dir.display().to_string(), e)
|
||||
})?;
|
||||
|
||||
// Versuche, leere Parent-Ordner zu löschen
|
||||
// 1. Extension-Name-Ordner (key_hash/name)
|
||||
if let Some(name_dir) = extension_dir.parent() {
|
||||
if name_dir.exists() {
|
||||
if let Ok(entries) = std::fs::read_dir(name_dir) {
|
||||
if entries.count() == 0 {
|
||||
let _ = std::fs::remove_dir(name_dir);
|
||||
|
||||
// 2. Key-Hash-Ordner (key_hash) - nur wenn auch leer
|
||||
if let Some(key_hash_dir) = name_dir.parent() {
|
||||
if key_hash_dir.exists() {
|
||||
if let Ok(entries) = std::fs::read_dir(key_hash_dir) {
|
||||
if entries.count() == 0 {
|
||||
let _ = std::fs::remove_dir(key_hash_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -268,9 +377,9 @@ impl ExtensionManager {
|
||||
|
||||
pub async fn preview_extension_internal(
|
||||
&self,
|
||||
source_path: String,
|
||||
file_bytes: Vec<u8>,
|
||||
) -> Result<ExtensionPreview, ExtensionError> {
|
||||
let extracted = Self::extract_and_validate_extension(&source_path, "haexhub_preview")?;
|
||||
let extracted = Self::extract_and_validate_extension(file_bytes, "haexhub_preview")?;
|
||||
|
||||
let is_valid_signature = ExtensionCrypto::verify_signature(
|
||||
&extracted.manifest.public_key,
|
||||
@ -293,11 +402,11 @@ impl ExtensionManager {
|
||||
pub async fn install_extension_with_permissions_internal(
|
||||
&self,
|
||||
app_handle: AppHandle,
|
||||
source_path: String,
|
||||
file_bytes: Vec<u8>,
|
||||
custom_permissions: EditablePermissions,
|
||||
state: &State<'_, AppState>,
|
||||
) -> Result<String, ExtensionError> {
|
||||
let extracted = Self::extract_and_validate_extension(&source_path, "haexhub_ext")?;
|
||||
let extracted = Self::extract_and_validate_extension(file_bytes, "haexhub_ext")?;
|
||||
|
||||
// Signatur verifizieren (bei Installation wird ein Fehler geworfen, nicht nur geprüft)
|
||||
ExtensionCrypto::verify_signature(
|
||||
@ -316,17 +425,95 @@ impl ExtensionManager {
|
||||
&extracted.manifest.version,
|
||||
)?;
|
||||
|
||||
std::fs::create_dir_all(&extensions_dir)
|
||||
.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
std::fs::create_dir_all(&extensions_dir).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(extensions_dir.display().to_string(), e)
|
||||
})?;
|
||||
|
||||
copy_directory(
|
||||
extracted.temp_dir.to_string_lossy().to_string(),
|
||||
extensions_dir.to_string_lossy().to_string(),
|
||||
)?;
|
||||
// Copy contents of extracted.temp_dir to extensions_dir
|
||||
// Note: extracted.temp_dir already points to the correct directory with manifest.json
|
||||
for entry in fs::read_dir(&extracted.temp_dir).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(extracted.temp_dir.display().to_string(), e)
|
||||
})? {
|
||||
let entry = entry.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
let path = entry.path();
|
||||
let file_name = entry.file_name();
|
||||
let dest_path = extensions_dir.join(&file_name);
|
||||
|
||||
if path.is_dir() {
|
||||
copy_directory(
|
||||
path.to_string_lossy().to_string(),
|
||||
dest_path.to_string_lossy().to_string(),
|
||||
)?;
|
||||
} else {
|
||||
fs::copy(&path, &dest_path).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(path.display().to_string(), e)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
let permissions = custom_permissions.to_internal_permissions(&full_extension_id);
|
||||
|
||||
PermissionManager::save_permissions(state, &permissions).await?;
|
||||
// Extension-Eintrag und Permissions in einer Transaktion speichern
|
||||
with_connection(&state.db, |conn| {
|
||||
let tx = conn.transaction().map_err(DatabaseError::from)?;
|
||||
|
||||
let hlc_service = state.hlc.lock().map_err(|_| DatabaseError::MutexPoisoned {
|
||||
reason: "Failed to lock HLC service".to_string(),
|
||||
})?;
|
||||
|
||||
// 1. Extension-Eintrag erstellen (oder aktualisieren falls schon vorhanden)
|
||||
let insert_ext_sql = format!(
|
||||
"INSERT OR REPLACE INTO {} (id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
TABLE_EXTENSIONS
|
||||
);
|
||||
|
||||
SqlExecutor::execute_internal_typed(
|
||||
&tx,
|
||||
&hlc_service,
|
||||
&insert_ext_sql,
|
||||
rusqlite::params![
|
||||
full_extension_id,
|
||||
extracted.manifest.name,
|
||||
extracted.manifest.version,
|
||||
extracted.manifest.author,
|
||||
extracted.manifest.entry,
|
||||
extracted.manifest.icon,
|
||||
extracted.manifest.public_key,
|
||||
extracted.manifest.signature,
|
||||
extracted.manifest.homepage,
|
||||
extracted.manifest.description,
|
||||
true, // enabled
|
||||
],
|
||||
)?;
|
||||
|
||||
// 2. Permissions speichern (oder aktualisieren falls schon vorhanden)
|
||||
let insert_perm_sql = format!(
|
||||
"INSERT OR REPLACE INTO {} (id, extension_id, resource_type, action, target, constraints, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
TABLE_EXTENSION_PERMISSIONS
|
||||
);
|
||||
|
||||
for perm in &permissions {
|
||||
use crate::database::generated::HaexExtensionPermissions;
|
||||
let db_perm: HaexExtensionPermissions = perm.into();
|
||||
|
||||
SqlExecutor::execute_internal_typed(
|
||||
&tx,
|
||||
&hlc_service,
|
||||
&insert_perm_sql,
|
||||
rusqlite::params![
|
||||
db_perm.id,
|
||||
db_perm.extension_id,
|
||||
db_perm.resource_type,
|
||||
db_perm.action,
|
||||
db_perm.target,
|
||||
db_perm.constraints,
|
||||
db_perm.status,
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
tx.commit().map_err(DatabaseError::from)
|
||||
})?;
|
||||
|
||||
let extension = Extension {
|
||||
id: full_extension_id.clone(),
|
||||
@ -372,16 +559,28 @@ impl ExtensionManager {
|
||||
|
||||
// Schritt 1: Alle Daten aus der Datenbank in einem Rutsch laden.
|
||||
let extensions = with_connection(&state.db, |conn| {
|
||||
let sql = "SELECT id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled FROM haexExtensions";
|
||||
let results = SqlExecutor::select_internal(conn, sql, &[])?;
|
||||
let sql = format!(
|
||||
"SELECT id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled FROM {}",
|
||||
TABLE_EXTENSIONS
|
||||
);
|
||||
eprintln!("DEBUG: SQL Query before transformation: {}", sql);
|
||||
let results = SqlExecutor::select_internal(conn, &sql, &[])?;
|
||||
eprintln!("DEBUG: Query returned {} results", results.len());
|
||||
|
||||
let mut data = Vec::new();
|
||||
for result in results {
|
||||
let full_extension_id = result["id"]
|
||||
.as_str()
|
||||
.ok_or_else(|| DatabaseError::SerializationError {
|
||||
reason: "Missing id field".to_string(),
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
let manifest = ExtensionManifest {
|
||||
id: result["id"]
|
||||
id: result["name"]
|
||||
.as_str()
|
||||
.ok_or_else(|| DatabaseError::SerializationError {
|
||||
reason: "Missing id field".to_string(),
|
||||
reason: "Missing name field".to_string(),
|
||||
})?
|
||||
.to_string(),
|
||||
name: result["name"]
|
||||
@ -411,7 +610,11 @@ impl ExtensionManager {
|
||||
.or_else(|| result["enabled"].as_i64().map(|v| v != 0))
|
||||
.unwrap_or(false);
|
||||
|
||||
data.push(ExtensionDataFromDb { manifest, enabled });
|
||||
data.push(ExtensionDataFromDb {
|
||||
full_extension_id,
|
||||
manifest,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
Ok(data)
|
||||
})?;
|
||||
@ -419,12 +622,19 @@ impl ExtensionManager {
|
||||
// Schritt 2: Die gesammelten Daten verarbeiten (Dateisystem, State-Mutationen).
|
||||
let mut loaded_extension_ids = Vec::new();
|
||||
|
||||
for extension in extensions {
|
||||
let full_extension_id = extension.manifest.full_extension_id()?;
|
||||
eprintln!("DEBUG: Found {} extensions in database", extensions.len());
|
||||
|
||||
for extension_data in extensions {
|
||||
let full_extension_id = extension_data.full_extension_id;
|
||||
eprintln!("DEBUG: Processing extension: {}", full_extension_id);
|
||||
let extension_path =
|
||||
self.get_extension_path_by_full_extension_id(app_handle, &full_extension_id)?;
|
||||
|
||||
if !extension_path.exists() || !extension_path.join("manifest.json").exists() {
|
||||
eprintln!(
|
||||
"DEBUG: Extension files missing for: {} at {:?}",
|
||||
full_extension_id, extension_path
|
||||
);
|
||||
self.missing_extensions
|
||||
.lock()
|
||||
.map_err(|e| ExtensionError::MutexPoisoned {
|
||||
@ -432,26 +642,31 @@ impl ExtensionManager {
|
||||
})?
|
||||
.push(MissingExtension {
|
||||
full_extension_id: full_extension_id.clone(),
|
||||
name: extension.manifest.name.clone(),
|
||||
version: extension.manifest.version.clone(),
|
||||
name: extension_data.manifest.name.clone(),
|
||||
version: extension_data.manifest.version.clone(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"DEBUG: Extension loaded successfully: {}",
|
||||
full_extension_id
|
||||
);
|
||||
|
||||
let extension = Extension {
|
||||
id: full_extension_id.clone(),
|
||||
name: extension.manifest.name.clone(),
|
||||
name: extension_data.manifest.name.clone(),
|
||||
source: ExtensionSource::Production {
|
||||
path: extension_path,
|
||||
version: extension.manifest.version.clone(),
|
||||
version: extension_data.manifest.version.clone(),
|
||||
},
|
||||
manifest: extension.manifest,
|
||||
enabled: extension.enabled,
|
||||
manifest: extension_data.manifest,
|
||||
enabled: extension_data.enabled,
|
||||
last_accessed: SystemTime::now(),
|
||||
};
|
||||
|
||||
loaded_extension_ids.push(full_extension_id.clone());
|
||||
self.add_production_extension(extension)?;
|
||||
loaded_extension_ids.push(full_extension_id);
|
||||
}
|
||||
|
||||
Ok(loaded_extension_ids)
|
||||
|
||||
Reference in New Issue
Block a user