| 174 | } |
| 175 | |
| 176 | std::string CmdListRemoteApps(const std::string& provider, const std::string& accountId) { |
| 177 | std::string tokenPath = GetTokenPath(provider); |
| 178 | if (tokenPath.empty()) { |
| 179 | return JsonError("Cannot determine config directory"); |
| 180 | } |
| 181 | |
| 182 | auto prov = CreateCloudProvider(provider); |
| 183 | if (!prov) { |
| 184 | return JsonError("Unknown provider: " + provider); |
| 185 | } |
| 186 | |
| 187 | if (!prov->Init(tokenPath)) { |
| 188 | return JsonError("Failed to initialize provider"); |
| 189 | } |
| 190 | |
| 191 | if (!prov->IsAuthenticated()) { |
| 192 | prov->Shutdown(); |
| 193 | return JsonError("Not authenticated"); |
| 194 | } |
| 195 | |
| 196 | // List app folders first, then per-app stats (avoids heavy recursive listing). |
| 197 | std::string prefix = accountId + "/"; |
| 198 | auto appIds = prov->ListSubfolders(prefix); |
| 199 | std::map<std::string, std::pair<int, uint64_t>> appStats; // appId -> (count, totalSize) |
| 200 | for (const auto& appId : appIds) { |
| 201 | if (appId.empty()) continue; |
| 202 | |
| 203 | std::vector<ICloudProvider::FileInfo> files; |
| 204 | if (!prov->ListChecked(prefix + appId + "/", files)) { |
| 205 | prov->Shutdown(); |
| 206 | return JsonError("Failed to list remote app " + appId); |
| 207 | } |
| 208 | |
| 209 | auto& stats = appStats[appId]; |
| 210 | for (const auto& f : files) { |
| 211 | stats.first++; |
| 212 | stats.second += f.size; |
| 213 | } |
| 214 | } |
| 215 | prov->Shutdown(); |
| 216 | |
| 217 | // Build JSON array |
| 218 | std::ostringstream apps; |
| 219 | apps << "["; |
| 220 | bool first = true; |
| 221 | for (const auto& [appId, stats] : appStats) { |
| 222 | if (!first) apps << ","; |
| 223 | apps << JsonObject({ |
| 224 | {"app_id", JsonString(appId)}, |
| 225 | {"file_count", JsonInt(stats.first)}, |
| 226 | {"total_size", JsonInt(static_cast<int64_t>(stats.second))} |
| 227 | }); |
| 228 | first = false; |
| 229 | } |
| 230 | apps << "]"; |
| 231 | |
| 232 | return JsonObject({ |
| 233 | {"success", JsonBool(true)}, |
no test coverage detected