// app_manager.cpp - App installation and management implementation // Milestone 10: Device-Side App Management #include "app_manager.h" #include "../logger.h" #include "../sandbox/sandbox_manager.h" #include #include #include #include #include #include // For JSON parsing #include // For ZIP extraction #include namespace fs = std::filesystem; using json = nlohmann::json; namespace mosis { AppManager::AppManager(const std::string& data_root) : m_data_root(data_root) { // Create directory structure fs::create_directories(m_data_root + "/apps"); fs::create_directories(m_data_root + "/downloads"); fs::create_directories(m_data_root + "/backups"); fs::create_directories(m_data_root + "/config"); // Load installed apps registry LoadInstalledApps(); LOG_INFO("AppManager initialized at: %s", m_data_root.c_str()); } AppManager::~AppManager() { SaveInstalledApps(); } bool AppManager::Install(const std::string& package_url, const std::string& signature, ProgressCallback callback) { callback({InstallProgress::Stage::Downloading, 0.0f, ""}); // Generate download path std::string download_path = m_data_root + "/downloads/" + GenerateUUID() + ".mosis"; // Download package if (!DownloadFile(package_url, download_path, [&](float p) { callback({InstallProgress::Stage::Downloading, p, ""}); })) { callback({InstallProgress::Stage::Failed, 0.0f, "Download failed"}); return false; } callback({InstallProgress::Stage::Verifying, 0.0f, ""}); // Verify signature if (!signature.empty() && !VerifySignature(download_path, signature)) { fs::remove(download_path); callback({InstallProgress::Stage::Failed, 0.0f, "Signature verification failed"}); return false; } // Verify package integrity if (!VerifyPackage(download_path)) { fs::remove(download_path); callback({InstallProgress::Stage::Failed, 0.0f, "Package verification failed"}); return false; } // Continue with installation bool result = InstallFromFile(download_path, callback); // Clean up download fs::remove(download_path); return result; } bool AppManager::InstallFromFile(const std::string& package_path, ProgressCallback callback) { callback({InstallProgress::Stage::Verifying, 0.0f, ""}); // Extract manifest to get package_id auto manifest = ExtractManifest(package_path); if (!manifest) { callback({InstallProgress::Stage::Failed, 0.0f, "Invalid manifest"}); return false; } LOG_INFO("Installing app: %s v%s", manifest->id.c_str(), manifest->version.c_str()); callback({InstallProgress::Stage::Extracting, 0.0f, ""}); // Determine installation path std::string install_path = m_data_root + "/apps/" + manifest->id; // Check if already installed (update path) if (fs::exists(install_path + "/package")) { LOG_INFO("App already installed, updating: %s", manifest->id.c_str()); // Backup existing data BackupAppData(manifest->id); // Remove old package fs::remove_all(install_path + "/package"); } // Create directories fs::create_directories(install_path + "/package"); fs::create_directories(install_path + "/data"); fs::create_directories(install_path + "/cache"); fs::create_directories(install_path + "/db"); // Extract package if (!ExtractPackage(package_path, install_path + "/package")) { callback({InstallProgress::Stage::Failed, 0.0f, "Extraction failed"}); return false; } callback({InstallProgress::Stage::Registering, 0.0f, ""}); // Get package file size int64_t package_size = 0; try { package_size = static_cast(fs::file_size(package_path)); } catch (...) { package_size = 0; } // Register app InstalledApp app; app.package_id = manifest->id; app.name = manifest->name; app.version_name = manifest->version; app.version_code = manifest->version_code; app.install_path = install_path; app.permissions = manifest->permissions; app.installed_at = std::chrono::system_clock::now(); app.updated_at = std::chrono::system_clock::now(); app.package_size = package_size; app.data_size = 0; app.is_system_app = false; app.entry_point = manifest->entry; app.icon_path = manifest->icon; app.developer_name = manifest->developer_name; { std::lock_guard lock(m_mutex); m_installed_apps[manifest->id] = app; SaveInstalledApps(); } LOG_INFO("App installed successfully: %s", manifest->id.c_str()); callback({InstallProgress::Stage::Complete, 1.0f, ""}); return true; } bool AppManager::Uninstall(const std::string& package_id, bool keep_data) { std::lock_guard lock(m_mutex); auto it = m_installed_apps.find(package_id); if (it == m_installed_apps.end()) { LOG_WARN("Cannot uninstall: app not found: %s", package_id.c_str()); return false; } // Cannot uninstall system apps if (it->second.is_system_app) { LOG_WARN("Cannot uninstall system app: %s", package_id.c_str()); return false; } LOG_INFO("Uninstalling app: %s (keep_data=%d)", package_id.c_str(), keep_data); // Stop app if running if (m_sandbox_manager && m_sandbox_manager->IsAppRunning(package_id)) { LOG_INFO("Stopping running app before uninstall: %s", package_id.c_str()); m_sandbox_manager->StopApp(package_id); } // Remove files std::string install_path = it->second.install_path; try { fs::remove_all(install_path + "/package"); fs::remove_all(install_path + "/cache"); if (!keep_data) { fs::remove_all(install_path + "/data"); fs::remove_all(install_path + "/db"); fs::remove_all(install_path); } } catch (const std::exception& e) { LOG_ERROR("Error removing app files: %s", e.what()); return false; } // Unregister m_installed_apps.erase(it); SaveInstalledApps(); LOG_INFO("App uninstalled: %s", package_id.c_str()); return true; } bool AppManager::Update(const std::string& package_id, const std::string& package_url, const std::string& signature, ProgressCallback callback) { // Updates use the same flow as Install, which handles existing installations return Install(package_url, signature, callback); } std::vector AppManager::GetInstalledApps() const { std::lock_guard lock(m_mutex); std::vector apps; apps.reserve(m_installed_apps.size()); for (const auto& [id, app] : m_installed_apps) { apps.push_back(app); } return apps; } std::optional AppManager::GetApp(const std::string& package_id) const { std::lock_guard lock(m_mutex); auto it = m_installed_apps.find(package_id); if (it != m_installed_apps.end()) { return it->second; } return std::nullopt; } bool AppManager::IsInstalled(const std::string& package_id) const { std::lock_guard lock(m_mutex); return m_installed_apps.find(package_id) != m_installed_apps.end(); } int64_t AppManager::GetAppDataSize(const std::string& package_id) const { std::string data_path = GetAppDataPath(package_id); return CalculateDirectorySize(data_path); } bool AppManager::ClearAppData(const std::string& package_id) { std::string data_path = GetAppDataPath(package_id); std::string db_path = m_data_root + "/apps/" + package_id + "/db"; try { fs::remove_all(data_path); fs::remove_all(db_path); fs::create_directories(data_path); fs::create_directories(db_path); LOG_INFO("Cleared app data: %s", package_id.c_str()); return true; } catch (const std::exception& e) { LOG_ERROR("Error clearing app data: %s", e.what()); return false; } } bool AppManager::ClearAppCache(const std::string& package_id) { std::string cache_path = GetAppCachePath(package_id); try { fs::remove_all(cache_path); fs::create_directories(cache_path); LOG_INFO("Cleared app cache: %s", package_id.c_str()); return true; } catch (const std::exception& e) { LOG_ERROR("Error clearing app cache: %s", e.what()); return false; } } bool AppManager::BackupAppData(const std::string& package_id) { std::string data_path = GetAppDataPath(package_id); std::string backup_path = m_data_root + "/backups/" + package_id; try { if (fs::exists(data_path)) { fs::remove_all(backup_path); fs::copy(data_path, backup_path, fs::copy_options::recursive); LOG_INFO("Backed up app data: %s", package_id.c_str()); } return true; } catch (const std::exception& e) { LOG_ERROR("Error backing up app data: %s", e.what()); return false; } } bool AppManager::RestoreAppData(const std::string& package_id) { std::string data_path = GetAppDataPath(package_id); std::string backup_path = m_data_root + "/backups/" + package_id; try { if (fs::exists(backup_path)) { fs::remove_all(data_path); fs::copy(backup_path, data_path, fs::copy_options::recursive); LOG_INFO("Restored app data: %s", package_id.c_str()); } return true; } catch (const std::exception& e) { LOG_ERROR("Error restoring app data: %s", e.what()); return false; } } bool AppManager::LaunchApp(const std::string& package_id) { auto app = GetApp(package_id); if (!app) { LOG_ERROR("Cannot launch app: not installed: %s", package_id.c_str()); return false; } if (!m_sandbox_manager) { LOG_ERROR("Cannot launch app: sandbox manager not set"); return false; } if (m_sandbox_manager->IsAppRunning(package_id)) { LOG_WARN("App already running: %s", package_id.c_str()); return true; } std::string app_path = app->install_path + "/package"; LOG_INFO("Launching app: %s from %s", package_id.c_str(), app_path.c_str()); return m_sandbox_manager->StartApp(package_id, app_path, app->permissions, app->is_system_app); } bool AppManager::StopApp(const std::string& package_id) { if (!m_sandbox_manager) { LOG_ERROR("Cannot stop app: sandbox manager not set"); return false; } if (!m_sandbox_manager->IsAppRunning(package_id)) { LOG_WARN("App not running: %s", package_id.c_str()); return true; } LOG_INFO("Stopping app: %s", package_id.c_str()); return m_sandbox_manager->StopApp(package_id); } bool AppManager::IsAppRunning(const std::string& package_id) const { if (!m_sandbox_manager) { return false; } return m_sandbox_manager->IsAppRunning(package_id); } void AppManager::SetSandboxManager(LuaSandboxManager* manager) { m_sandbox_manager = manager; } std::string AppManager::GetAppPath(const std::string& package_id) const { return m_data_root + "/apps/" + package_id + "/package"; } std::string AppManager::GetAppDataPath(const std::string& package_id) const { return m_data_root + "/apps/" + package_id + "/data"; } std::string AppManager::GetAppCachePath(const std::string& package_id) const { return m_data_root + "/apps/" + package_id + "/cache"; } void AppManager::RegisterSystemApp(const InstalledApp& app) { std::lock_guard lock(m_mutex); InstalledApp system_app = app; system_app.is_system_app = true; m_installed_apps[app.package_id] = system_app; LOG_INFO("Registered system app: %s", app.package_id.c_str()); } bool AppManager::VerifyPackage(const std::string& path) { // Verify ZIP structure and manifest presence unzFile zip = unzOpen(path.c_str()); if (!zip) { LOG_ERROR("Cannot open package: %s", path.c_str()); return false; } bool has_manifest = false; if (unzGoToFirstFile(zip) == UNZ_OK) { do { char filename[256]; unz_file_info file_info; if (unzGetCurrentFileInfo(zip, &file_info, filename, sizeof(filename), nullptr, 0, nullptr, 0) == UNZ_OK) { if (std::string(filename) == "manifest.json") { has_manifest = true; break; } } } while (unzGoToNextFile(zip) == UNZ_OK); } unzClose(zip); if (!has_manifest) { LOG_ERROR("Package missing manifest.json: %s", path.c_str()); return false; } return true; } bool AppManager::VerifySignature(const std::string& path, const std::string& signature) { // TODO: Implement Ed25519 signature verification // For now, accept packages without strict verification LOG_WARN("Signature verification not yet implemented"); return true; } std::optional AppManager::ExtractManifest(const std::string& package_path) { unzFile zip = unzOpen(package_path.c_str()); if (!zip) { return std::nullopt; } std::string manifest_content; // Find and read manifest.json if (unzLocateFile(zip, "manifest.json", 0) != UNZ_OK) { unzClose(zip); return std::nullopt; } if (unzOpenCurrentFile(zip) != UNZ_OK) { unzClose(zip); return std::nullopt; } char buffer[4096]; int bytes_read; while ((bytes_read = unzReadCurrentFile(zip, buffer, sizeof(buffer))) > 0) { manifest_content.append(buffer, bytes_read); } unzCloseCurrentFile(zip); unzClose(zip); // Parse JSON try { json j = json::parse(manifest_content); AppManifest manifest; manifest.id = j.value("id", ""); manifest.name = j.value("name", ""); manifest.version = j.value("version", "1.0.0"); manifest.version_code = j.value("version_code", 1); manifest.entry = j.value("entry", "main.rml"); manifest.icon = j.value("icon", ""); manifest.description = j.value("description", ""); manifest.min_api_version = j.value("min_api_version", 1); if (j.contains("developer")) { manifest.developer_name = j["developer"].value("name", ""); manifest.developer_email = j["developer"].value("email", ""); } if (j.contains("permissions") && j["permissions"].is_array()) { for (const auto& perm : j["permissions"]) { manifest.permissions.push_back(perm.get()); } } if (manifest.id.empty()) { LOG_ERROR("Manifest missing required 'id' field"); return std::nullopt; } return manifest; } catch (const json::exception& e) { LOG_ERROR("Failed to parse manifest: %s", e.what()); return std::nullopt; } } bool AppManager::ExtractPackage(const std::string& package_path, const std::string& dest_path) { unzFile zip = unzOpen(package_path.c_str()); if (!zip) { LOG_ERROR("Cannot open package for extraction: %s", package_path.c_str()); return false; } bool success = true; if (unzGoToFirstFile(zip) == UNZ_OK) { do { char filename[512]; unz_file_info file_info; if (unzGetCurrentFileInfo(zip, &file_info, filename, sizeof(filename), nullptr, 0, nullptr, 0) != UNZ_OK) { continue; } std::string full_path = dest_path + "/" + filename; // Skip META-INF directory (signatures) if (std::string(filename).rfind("META-INF/", 0) == 0) { continue; } // Create directories size_t len = strlen(filename); if (len > 0 && filename[len - 1] == '/') { fs::create_directories(full_path); continue; } // Ensure parent directory exists fs::create_directories(fs::path(full_path).parent_path()); // Extract file if (unzOpenCurrentFile(zip) != UNZ_OK) { LOG_ERROR("Cannot open file in archive: %s", filename); success = false; break; } std::ofstream out(full_path, std::ios::binary); if (!out) { LOG_ERROR("Cannot create file: %s", full_path.c_str()); unzCloseCurrentFile(zip); success = false; break; } char buffer[8192]; int bytes_read; while ((bytes_read = unzReadCurrentFile(zip, buffer, sizeof(buffer))) > 0) { out.write(buffer, bytes_read); } out.close(); unzCloseCurrentFile(zip); } while (unzGoToNextFile(zip) == UNZ_OK); } unzClose(zip); return success; } bool AppManager::DownloadFile(const std::string& url, const std::string& dest_path, std::function progress_callback) { // TODO: Implement HTTP download using platform-specific APIs // For now, return false as this is a placeholder LOG_ERROR("HTTP download not yet implemented for: %s", url.c_str()); return false; } void AppManager::LoadInstalledApps() { std::string registry_path = m_data_root + "/config/apps.json"; std::ifstream file(registry_path); if (!file) { LOG_INFO("No existing app registry found"); return; } try { json j; file >> j; if (j.contains("apps") && j["apps"].is_array()) { for (const auto& app_json : j["apps"]) { InstalledApp app; app.package_id = app_json.value("package_id", ""); app.name = app_json.value("name", ""); app.version_name = app_json.value("version_name", ""); app.version_code = app_json.value("version_code", 0); app.install_path = app_json.value("install_path", ""); app.package_size = app_json.value("package_size", 0); app.data_size = app_json.value("data_size", 0); app.is_system_app = app_json.value("is_system_app", false); app.entry_point = app_json.value("entry_point", "main.rml"); app.icon_path = app_json.value("icon_path", ""); app.developer_name = app_json.value("developer_name", ""); if (app_json.contains("permissions") && app_json["permissions"].is_array()) { for (const auto& perm : app_json["permissions"]) { app.permissions.push_back(perm.get()); } } // Parse timestamps if (app_json.contains("installed_at")) { auto ts = app_json["installed_at"].get(); app.installed_at = std::chrono::system_clock::time_point( std::chrono::seconds(ts)); } if (app_json.contains("updated_at")) { auto ts = app_json["updated_at"].get(); app.updated_at = std::chrono::system_clock::time_point( std::chrono::seconds(ts)); } if (!app.package_id.empty()) { m_installed_apps[app.package_id] = app; } } } LOG_INFO("Loaded %zu installed apps", m_installed_apps.size()); } catch (const std::exception& e) { LOG_ERROR("Error loading app registry: %s", e.what()); } } void AppManager::SaveInstalledApps() { std::string registry_path = m_data_root + "/config/apps.json"; json j; j["version"] = 1; j["apps"] = json::array(); for (const auto& [id, app] : m_installed_apps) { json app_json; app_json["package_id"] = app.package_id; app_json["name"] = app.name; app_json["version_name"] = app.version_name; app_json["version_code"] = app.version_code; app_json["install_path"] = app.install_path; app_json["permissions"] = app.permissions; app_json["package_size"] = app.package_size; app_json["data_size"] = app.data_size; app_json["is_system_app"] = app.is_system_app; app_json["entry_point"] = app.entry_point; app_json["icon_path"] = app.icon_path; app_json["developer_name"] = app.developer_name; // Store timestamps as Unix seconds app_json["installed_at"] = std::chrono::duration_cast( app.installed_at.time_since_epoch()).count(); app_json["updated_at"] = std::chrono::duration_cast( app.updated_at.time_since_epoch()).count(); j["apps"].push_back(app_json); } std::ofstream file(registry_path); if (file) { file << j.dump(2); LOG_DEBUG("Saved app registry with %zu apps", m_installed_apps.size()); } else { LOG_ERROR("Failed to save app registry"); } } int64_t AppManager::CalculateDirectorySize(const std::string& path) const { int64_t size = 0; try { for (const auto& entry : fs::recursive_directory_iterator(path)) { if (entry.is_regular_file()) { size += static_cast(entry.file_size()); } } } catch (...) { // Directory may not exist } return size; } std::string AppManager::GenerateUUID() const { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, 15); std::stringstream ss; for (int i = 0; i < 32; ++i) { if (i == 8 || i == 12 || i == 16 || i == 20) { ss << '-'; } ss << std::hex << dis(gen); } return ss.str(); } } // namespace mosis