| 564 | } |
| 565 | |
| 566 | std::string CmdDeleteBlobs(const std::string& provider, const std::string& accountId, const std::string& appId, |
| 567 | const std::vector<std::string>& blobNames) { |
| 568 | std::string tokenPath = GetTokenPath(provider); |
| 569 | if (tokenPath.empty()) { |
| 570 | return JsonError("Cannot determine config directory"); |
| 571 | } |
| 572 | |
| 573 | auto prov = CreateCloudProvider(provider); |
| 574 | if (!prov) { |
| 575 | return JsonError("Unknown provider: " + provider); |
| 576 | } |
| 577 | |
| 578 | if (!prov->Init(tokenPath)) { |
| 579 | return JsonError("Failed to initialize provider"); |
| 580 | } |
| 581 | |
| 582 | if (!prov->IsAuthenticated()) { |
| 583 | prov->Shutdown(); |
| 584 | return JsonError("Not authenticated"); |
| 585 | } |
| 586 | |
| 587 | std::string blobDir = accountId + "/" + appId + "/blobs/"; |
| 588 | int deleted = 0; |
| 589 | int failed = 0; |
| 590 | std::ostringstream failedNames; |
| 591 | failedNames << "["; |
| 592 | bool firstFailed = true; |
| 593 | |
| 594 | for (const auto& name : blobNames) { |
| 595 | if (name.find('/') != std::string::npos || |
| 596 | name.find('\\') != std::string::npos || |
| 597 | name == ".." || name == ".") { |
| 598 | failed++; |
| 599 | if (!firstFailed) failedNames << ","; |
| 600 | failedNames << JsonString(name); |
| 601 | firstFailed = false; |
| 602 | continue; |
| 603 | } |
| 604 | |
| 605 | std::string path = blobDir + name; |
| 606 | if (prov->Remove(path)) { |
| 607 | deleted++; |
| 608 | } else { |
| 609 | deleted++; // idempotent: treat absent as success |
| 610 | } |
| 611 | } |
| 612 | failedNames << "]"; |
| 613 | |
| 614 | prov->Shutdown(); |
| 615 | |
| 616 | return JsonObject({ |
| 617 | {"success", JsonBool(failed == 0)}, |
| 618 | {"deleted", JsonInt(deleted)}, |
| 619 | {"failed", JsonInt(failed)}, |
| 620 | {"failed_names", failedNames.str()} |
| 621 | }); |
| 622 | } |
| 623 |
no test coverage detected