add local app discovery: scan apps folder, render on home screen
This commit is contained in:
184
docs/APP-DISCOVERY.md
Normal file
184
docs/APP-DISCOVERY.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# App Discovery System
|
||||||
|
|
||||||
|
Local app discovery without requiring a backend server.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
| Feature | Status |
|
||||||
|
|---------|--------|
|
||||||
|
| Directory scanning | ✅ Implemented |
|
||||||
|
| Manifest parsing | ✅ Implemented |
|
||||||
|
| Home screen rendering | ✅ Implemented |
|
||||||
|
| App launching | 🔄 In Progress |
|
||||||
|
| Package installation | ⏳ Planned |
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Home Screen │
|
||||||
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||||
|
│ │ Dialer │ │ Messages│ │ TestApp │ │ MyApp │ ← scanned │
|
||||||
|
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ from │
|
||||||
|
│ apps/ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ /data/data/com.omixlab.mosis/files/ │
|
||||||
|
│ ├── apps/ ← Installed third-party apps │
|
||||||
|
│ │ ├── com.mosis.testapp/ │
|
||||||
|
│ │ │ ├── manifest.json ← App metadata │
|
||||||
|
│ │ │ ├── main.rml ← Entry point │
|
||||||
|
│ │ │ ├── app.lua │
|
||||||
|
│ │ │ └── icon.tga │
|
||||||
|
│ │ └── com.example.myapp/ │
|
||||||
|
│ │ └── ... │
|
||||||
|
│ ├── downloads/ ← Pending .mosis packages │
|
||||||
|
│ └── config/ │
|
||||||
|
│ └── apps.json ← App registry (optional cache) │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discovery Flow
|
||||||
|
|
||||||
|
### Phase 1: Direct Folder Discovery (MVP) ✅
|
||||||
|
|
||||||
|
1. **On boot**: Home screen calls `mosis.apps.getInstalled()`
|
||||||
|
2. **AppManager** scans `/files/apps/` for folders containing `manifest.json`
|
||||||
|
3. **For each app**: Read manifest, extract name/icon/entry point
|
||||||
|
4. **Home screen**: Render app grid with icons
|
||||||
|
5. **On tap**: Launch app via `mosis.apps.launch(package_id)`
|
||||||
|
|
||||||
|
### Phase 2: Package Installation (Future)
|
||||||
|
|
||||||
|
1. User copies `.mosis` file to `/files/downloads/`
|
||||||
|
2. Store app or file manager shows pending packages
|
||||||
|
3. User taps "Install" → `mosis.apps.install(path)`
|
||||||
|
4. Package extracted to `/files/apps/{package_id}/`
|
||||||
|
5. Home screen refreshes to show new app
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### AppManager (C++)
|
||||||
|
|
||||||
|
The `AppManager::ScanAppsDirectory()` method scans for installed apps:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// app_manager.cpp
|
||||||
|
void AppManager::ScanAppsDirectory() {
|
||||||
|
std::string apps_dir = m_data_root + "/apps";
|
||||||
|
for (const auto& entry : fs::directory_iterator(apps_dir)) {
|
||||||
|
if (!entry.is_directory()) continue;
|
||||||
|
|
||||||
|
// Look for manifest.json
|
||||||
|
std::string manifest_path = entry.path().string() + "/manifest.json";
|
||||||
|
// Parse and register app...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lua API
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Get all installed apps (system + third-party)
|
||||||
|
local apps = mosis.apps.getInstalled()
|
||||||
|
-- Returns: [{package_id, name, icon_path, entry, version, is_system_app}, ...]
|
||||||
|
|
||||||
|
-- Launch an app
|
||||||
|
mosis.apps.launch("com.mosis.testapp")
|
||||||
|
|
||||||
|
-- Check if app is running
|
||||||
|
local running = mosis.apps.isRunning("com.mosis.testapp")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Home Screen (home.lua)
|
||||||
|
|
||||||
|
The home screen dynamically renders third-party apps:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function initHome(doc)
|
||||||
|
-- Get installed apps, filter to third-party only
|
||||||
|
local apps = mosis.apps.getInstalled()
|
||||||
|
for _, app in ipairs(apps) do
|
||||||
|
if not app.is_system_app then
|
||||||
|
-- Render app icon
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function launchThirdPartyApp(package_id)
|
||||||
|
mosis.apps.launch(package_id)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test App Deployment
|
||||||
|
|
||||||
|
To deploy a test app manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Push to temp location (avoid permission issues)
|
||||||
|
adb push test-apps/com.mosis.sandbox-test/ //data/local/tmp/com.mosis.sandbox-test/
|
||||||
|
|
||||||
|
# 2. Copy to app's private storage
|
||||||
|
adb shell "run-as com.omixlab.mosis cp -r /data/local/tmp/com.mosis.sandbox-test /data/data/com.omixlab.mosis/files/apps/"
|
||||||
|
|
||||||
|
# 3. Verify
|
||||||
|
adb shell "run-as com.omixlab.mosis ls /data/data/com.omixlab.mosis/files/apps/com.mosis.sandbox-test/"
|
||||||
|
```
|
||||||
|
|
||||||
|
For Desktop Designer:
|
||||||
|
```bash
|
||||||
|
# Copy to designer's sandbox_data folder
|
||||||
|
cp -r test-apps/com.mosis.sandbox-test/ designer/build/Debug/sandbox_data/apps/
|
||||||
|
```
|
||||||
|
|
||||||
|
## App Manifest Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "com.mosis.testapp",
|
||||||
|
"name": "Test App",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"version_code": 1,
|
||||||
|
"entry": "main.rml",
|
||||||
|
"icon": "icon.tga",
|
||||||
|
"description": "A test application",
|
||||||
|
"permissions": ["storage", "network"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
### System Apps (bundled in APK assets)
|
||||||
|
```
|
||||||
|
assets/apps/
|
||||||
|
├── home/ # Home screen (always loaded)
|
||||||
|
├── dialer/
|
||||||
|
├── messages/
|
||||||
|
├── contacts/
|
||||||
|
├── settings/
|
||||||
|
├── browser/
|
||||||
|
└── store/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Third-Party Apps (in private storage)
|
||||||
|
```
|
||||||
|
/data/data/com.omixlab.mosis/files/apps/
|
||||||
|
├── com.mosis.testapp/
|
||||||
|
│ ├── manifest.json
|
||||||
|
│ ├── main.rml
|
||||||
|
│ ├── app.lua
|
||||||
|
│ ├── styles.rcss
|
||||||
|
│ └── icon.tga
|
||||||
|
└── com.example.game/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
1. **Package signature verification** before installation
|
||||||
|
2. **Version tracking** in apps.json registry
|
||||||
|
3. **Update detection** by comparing version_code
|
||||||
|
4. **Uninstall** with data cleanup option
|
||||||
|
5. **Store integration** for HTTP-based discovery
|
||||||
151
src/main/assets/apps/home/home.lua
Normal file
151
src/main/assets/apps/home/home.lua
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
-- home.lua - Home screen dynamic app rendering
|
||||||
|
-- Handles system apps and discovered third-party apps
|
||||||
|
|
||||||
|
-- System apps with their navigation keys and colors
|
||||||
|
local system_apps = {
|
||||||
|
-- Row 1
|
||||||
|
{name = "Phone", icon = "phone", color = "#4CAF50", nav = "dialer"},
|
||||||
|
{name = "Messages", icon = "message", color = "#2196F3", nav = "messages"},
|
||||||
|
{name = "Contacts", icon = "contacts", color = "#FF9800", nav = "contacts"},
|
||||||
|
{name = "Browser", icon = "browser", color = "#F44336", nav = "browser"},
|
||||||
|
-- Row 2
|
||||||
|
{name = "Gallery", icon = "gallery", color = "#9C27B0", nav = nil},
|
||||||
|
{name = "Camera", icon = "camera", color = "#00BCD4", nav = "camera"},
|
||||||
|
{name = "Settings", icon = "settings", color = "#607D8B", nav = "settings"},
|
||||||
|
{name = "Music", icon = "music", color = "#E91E63", nav = "music"},
|
||||||
|
-- Row 3
|
||||||
|
{name = "Calendar", icon = "calendar", color = "#3F51B5", nav = nil},
|
||||||
|
{name = "Clock", icon = "clock", color = "#009688", nav = nil},
|
||||||
|
{name = "Notes", icon = "notes", color = "#795548", nav = nil},
|
||||||
|
{name = "Maps", icon = "maps", color = "#FF5722", nav = nil},
|
||||||
|
-- Row 4
|
||||||
|
{name = "Store", icon = "store", color = "#8BC34A", nav = "store"},
|
||||||
|
{name = "Files", icon = "files", color = "#CDDC39", nav = nil},
|
||||||
|
{name = "Calculator", icon = "calculator", color = "#FFC107", nav = nil},
|
||||||
|
{name = "Weather", icon = "weather", color = "#673AB7", nav = nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- State
|
||||||
|
local installed_apps = {}
|
||||||
|
local home_document = nil -- Store document reference
|
||||||
|
|
||||||
|
-- Initialize on load (receives document from onload event)
|
||||||
|
function initHome(doc)
|
||||||
|
print("[Home] Initializing home screen...")
|
||||||
|
home_document = doc
|
||||||
|
|
||||||
|
-- Get installed third-party apps
|
||||||
|
if mosis and mosis.apps then
|
||||||
|
installed_apps = mosis.apps.getInstalled() or {}
|
||||||
|
print("[Home] Found " .. #installed_apps .. " installed apps")
|
||||||
|
|
||||||
|
-- Filter to only third-party (non-system) apps
|
||||||
|
local third_party = {}
|
||||||
|
for _, app in ipairs(installed_apps) do
|
||||||
|
if not app.is_system_app then
|
||||||
|
table.insert(third_party, app)
|
||||||
|
print("[Home] Third-party app: " .. app.name .. " (" .. app.package_id .. ")")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
installed_apps = third_party
|
||||||
|
else
|
||||||
|
print("[Home] Warning: mosis.apps API not available")
|
||||||
|
installed_apps = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Render dynamic apps
|
||||||
|
renderThirdPartyApps()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Generate a color based on package_id
|
||||||
|
function getAppColor(package_id)
|
||||||
|
local colors = {
|
||||||
|
"#BB86FC", "#03DAC6", "#FF9800", "#2196F3",
|
||||||
|
"#4CAF50", "#F44336", "#E91E63", "#3F51B5",
|
||||||
|
"#009688", "#795548", "#FF5722", "#673AB7"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Simple hash of package_id to pick a color
|
||||||
|
local hash = 0
|
||||||
|
for i = 1, #package_id do
|
||||||
|
hash = hash + package_id:byte(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
return colors[(hash % #colors) + 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get first letter for placeholder icon
|
||||||
|
function getAppInitial(name)
|
||||||
|
return name:sub(1, 1):upper()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Render third-party apps into the grid
|
||||||
|
function renderThirdPartyApps()
|
||||||
|
-- Use stored document reference
|
||||||
|
if not home_document then
|
||||||
|
print("[Home] Could not get document reference")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local grid = home_document:GetElementById("third-party-apps")
|
||||||
|
if not grid then
|
||||||
|
print("[Home] third-party-apps container not found")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clear existing content
|
||||||
|
grid.inner_rml = ""
|
||||||
|
|
||||||
|
if #installed_apps == 0 then
|
||||||
|
print("[Home] No third-party apps to display")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build HTML for each app
|
||||||
|
local html = ""
|
||||||
|
for _, app in ipairs(installed_apps) do
|
||||||
|
local color = getAppColor(app.package_id)
|
||||||
|
local initial = getAppInitial(app.name)
|
||||||
|
local icon_html
|
||||||
|
|
||||||
|
-- Check if app has an icon
|
||||||
|
if app.icon and app.icon ~= "" then
|
||||||
|
-- Third-party app icon path would be in their install directory
|
||||||
|
-- For now, use initial as we need file:// protocol support
|
||||||
|
icon_html = '<span style="font-size: 28px; color: #000000;">' .. initial .. '</span>'
|
||||||
|
else
|
||||||
|
icon_html = '<span style="font-size: 28px; color: #000000;">' .. initial .. '</span>'
|
||||||
|
end
|
||||||
|
|
||||||
|
html = html .. [[
|
||||||
|
<div class="app-icon">
|
||||||
|
<div class="app-icon-image" style="background-color: ]] .. color .. [[;"
|
||||||
|
onclick="launchThirdPartyApp(']] .. app.package_id .. [[')">
|
||||||
|
]] .. icon_html .. [[
|
||||||
|
</div>
|
||||||
|
<span class="app-icon-label">]] .. app.name .. [[</span>
|
||||||
|
</div>
|
||||||
|
]]
|
||||||
|
end
|
||||||
|
|
||||||
|
grid.inner_rml = html
|
||||||
|
print("[Home] Rendered " .. #installed_apps .. " third-party apps")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Launch a third-party app
|
||||||
|
function launchThirdPartyApp(package_id)
|
||||||
|
print("[Home] Launching app: " .. package_id)
|
||||||
|
|
||||||
|
if mosis and mosis.apps then
|
||||||
|
local success = mosis.apps.launch(package_id)
|
||||||
|
if success then
|
||||||
|
print("[Home] App launched: " .. package_id)
|
||||||
|
else
|
||||||
|
print("[Home] Failed to launch app: " .. package_id)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print("[Home] Cannot launch app: mosis.apps not available")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- initHome() is called via onload in home.rml
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<link type="text/rcss" href="../../ui/theme.rcss"/>
|
<link type="text/rcss" href="../../ui/theme.rcss"/>
|
||||||
<link type="text/rcss" href="../../ui/components.rcss"/>
|
<link type="text/rcss" href="../../ui/components.rcss"/>
|
||||||
<script src="../../scripts/navigation.lua"></script>
|
<script src="../../scripts/navigation.lua"></script>
|
||||||
|
<script src="home.lua"></script>
|
||||||
<title>Virtual Smartphone - Home</title>
|
<title>Virtual Smartphone - Home</title>
|
||||||
<style>
|
<style>
|
||||||
.home-screen {
|
.home-screen {
|
||||||
@@ -34,9 +35,43 @@
|
|||||||
.status-bar-icons img {
|
.status-bar-icons img {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Third-party apps section */
|
||||||
|
.app-grid-section {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#third-party-apps .app-icon {
|
||||||
|
width: 25%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#third-party-apps .app-icon-image {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 8px auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#third-party-apps .app-icon-image:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#third-party-apps .app-icon-label {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="home-screen">
|
<body class="home-screen" onload="initHome(document)">
|
||||||
<!-- Status Bar -->
|
<!-- Status Bar -->
|
||||||
<div class="status-bar">
|
<div class="status-bar">
|
||||||
<span class="status-bar-time">12:30</span>
|
<span class="status-bar-time">12:30</span>
|
||||||
@@ -121,6 +156,11 @@
|
|||||||
<div class="app-icon-image" style="background-color: #673AB7;"><img src="../../icons/weather.tga"/></div>
|
<div class="app-icon-image" style="background-color: #673AB7;"><img src="../../icons/weather.tga"/></div>
|
||||||
<span class="app-icon-label">Weather</span>
|
<span class="app-icon-label">Weather</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Third-party apps (dynamically populated by home.lua) -->
|
||||||
|
<div id="third-party-apps" class="app-grid-section">
|
||||||
|
<!-- Apps will be rendered here by home.lua -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -566,12 +566,93 @@ bool AppManager::DownloadFile(const std::string& url, const std::string& dest_pa
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AppManager::ScanAppsDirectory() {
|
||||||
|
std::string apps_dir = m_data_root + "/apps";
|
||||||
|
|
||||||
|
if (!fs::exists(apps_dir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Scanning apps directory: %s", apps_dir.c_str());
|
||||||
|
|
||||||
|
for (const auto& entry : fs::directory_iterator(apps_dir)) {
|
||||||
|
if (!entry.is_directory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string package_id = entry.path().filename().string();
|
||||||
|
|
||||||
|
// Skip if already registered
|
||||||
|
if (m_installed_apps.find(package_id) != m_installed_apps.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for manifest.json in direct folder or package/ subfolder
|
||||||
|
std::string manifest_path = entry.path().string() + "/manifest.json";
|
||||||
|
std::string manifest_path_pkg = entry.path().string() + "/package/manifest.json";
|
||||||
|
|
||||||
|
std::string actual_manifest;
|
||||||
|
std::string app_base_path;
|
||||||
|
|
||||||
|
if (fs::exists(manifest_path)) {
|
||||||
|
actual_manifest = manifest_path;
|
||||||
|
app_base_path = entry.path().string();
|
||||||
|
} else if (fs::exists(manifest_path_pkg)) {
|
||||||
|
actual_manifest = manifest_path_pkg;
|
||||||
|
app_base_path = entry.path().string() + "/package";
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse manifest
|
||||||
|
std::ifstream mf(actual_manifest);
|
||||||
|
if (!mf) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
json j;
|
||||||
|
mf >> j;
|
||||||
|
|
||||||
|
InstalledApp app;
|
||||||
|
app.package_id = j.value("id", package_id);
|
||||||
|
app.name = j.value("name", package_id);
|
||||||
|
app.version_name = j.value("version", "1.0.0");
|
||||||
|
app.version_code = j.value("version_code", 1);
|
||||||
|
app.install_path = entry.path().string();
|
||||||
|
app.entry_point = j.value("entry", "main.rml");
|
||||||
|
app.icon_path = j.value("icon", "");
|
||||||
|
app.is_system_app = false;
|
||||||
|
|
||||||
|
if (j.contains("developer")) {
|
||||||
|
app.developer_name = j["developer"].value("name", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.contains("permissions") && j["permissions"].is_array()) {
|
||||||
|
for (const auto& perm : j["permissions"]) {
|
||||||
|
app.permissions.push_back(perm.get<std::string>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.installed_at = std::chrono::system_clock::now();
|
||||||
|
app.updated_at = std::chrono::system_clock::now();
|
||||||
|
|
||||||
|
m_installed_apps[app.package_id] = app;
|
||||||
|
LOG_INFO("Discovered app: %s (%s)", app.name.c_str(), app.package_id.c_str());
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LOG_WARN("Failed to parse manifest for %s: %s", package_id.c_str(), e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AppManager::LoadInstalledApps() {
|
void AppManager::LoadInstalledApps() {
|
||||||
std::string registry_path = m_data_root + "/config/apps.json";
|
std::string registry_path = m_data_root + "/config/apps.json";
|
||||||
|
|
||||||
std::ifstream file(registry_path);
|
std::ifstream file(registry_path);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
LOG_INFO("No existing app registry found");
|
LOG_INFO("No existing app registry found, scanning directory...");
|
||||||
|
ScanAppsDirectory();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,11 +699,15 @@ void AppManager::LoadInstalledApps() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("Loaded %zu installed apps", m_installed_apps.size());
|
LOG_INFO("Loaded %zu apps from registry", m_installed_apps.size());
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR("Error loading app registry: %s", e.what());
|
LOG_ERROR("Error loading app registry: %s", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always scan directory to discover newly copied apps
|
||||||
|
ScanAppsDirectory();
|
||||||
|
LOG_INFO("Total installed apps: %zu", m_installed_apps.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppManager::SaveInstalledApps() {
|
void AppManager::SaveInstalledApps() {
|
||||||
|
|||||||
@@ -152,6 +152,9 @@ private:
|
|||||||
void LoadInstalledApps();
|
void LoadInstalledApps();
|
||||||
void SaveInstalledApps();
|
void SaveInstalledApps();
|
||||||
|
|
||||||
|
// Directory scanning for app discovery
|
||||||
|
void ScanAppsDirectory();
|
||||||
|
|
||||||
// Directory size calculation
|
// Directory size calculation
|
||||||
int64_t CalculateDirectorySize(const std::string& path) const;
|
int64_t CalculateDirectorySize(const std::string& path) const;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user