Create core/ directory with platform-agnostic sandbox components: - Timer manager, JSON API, Crypto API, Virtual FS - Lua sandbox, Permission gate, Audit log, Rate limiter - Platform abstraction interfaces (IAssetInterface, IFilesystemInterface) - Platform-agnostic logger with Android/Desktop implementations Update designer to link against mosis-core library instead of including sandbox sources directly. This is the foundation for unifying the Android service and desktop designer to share the same codebase. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
698 lines
22 KiB
C++
698 lines
22 KiB
C++
// 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 <fstream>
|
|
#include <sstream>
|
|
#include <filesystem>
|
|
#include <random>
|
|
#include <iomanip>
|
|
#include <ctime>
|
|
|
|
// For JSON parsing
|
|
#include <nlohmann/json.hpp>
|
|
|
|
// For ZIP extraction
|
|
#include <minizip/unzip.h>
|
|
|
|
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<int64_t>(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<std::mutex> 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<std::mutex> 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<InstalledApp> AppManager::GetInstalledApps() const {
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
std::vector<InstalledApp> apps;
|
|
apps.reserve(m_installed_apps.size());
|
|
for (const auto& [id, app] : m_installed_apps) {
|
|
apps.push_back(app);
|
|
}
|
|
return apps;
|
|
}
|
|
|
|
std::optional<InstalledApp> AppManager::GetApp(const std::string& package_id) const {
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<AppManifest> 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<std::string>());
|
|
}
|
|
}
|
|
|
|
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<void(float)> 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<std::string>());
|
|
}
|
|
}
|
|
|
|
// Parse timestamps
|
|
if (app_json.contains("installed_at")) {
|
|
auto ts = app_json["installed_at"].get<int64_t>();
|
|
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<int64_t>();
|
|
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<std::chrono::seconds>(
|
|
app.installed_at.time_since_epoch()).count();
|
|
app_json["updated_at"] = std::chrono::duration_cast<std::chrono::seconds>(
|
|
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<int64_t>(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
|