text: // Copyright 2019 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "src/developer/forensics/feedback_data/attachments/inspect_ptr.h"
  
  #include <lib/async/cpp/task.h>
  #include <lib/fit/result.h>
  #include <lib/fostr/fidl/fuchsia/diagnostics/formatting.h>
  #include <lib/syslog/cpp/macros.h>
  #include <zircon/errors.h>
  #include <zircon/types.h>
  
  #include <memory>
  #include <string>
  #include <vector>
  
  #include "src/developer/forensics/utils/errors.h"
  #include "src/developer/forensics/utils/fit/promise.h"
  #include "src/developer/forensics/utils/fit/timeout.h"
  #include "src/lib/fsl/vmo/strings.h"
  #include "src/lib/fxl/strings/join_strings.h"
  
  namespace forensics {
  namespace feedback_data {
  
  ::fit::promise<AttachmentValue> CollectInspectData(async_dispatcher_t* dispatcher,
                                                     std::shared_ptr<sys::ServiceDirectory> services,
                                                     fit::Timeout timeout) {
    std::unique_ptr<Inspect> inspect = std::make_unique<Inspect>(dispatcher, services);
  
    // We must store the promise in a variable due to the fact that the order of evaluation of
    // function parameters is undefined.
    auto inspect_data = inspect->Collect(std::move(timeout));
    return fit::ExtendArgsLifetimeBeyondPromise(/*promise=*/std::move(inspect_data),
                                                /*args=*/std::move(inspect));
  }
  
  Inspect::Inspect(async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services)
      : archive_(dispatcher, services, kArchiveAccessorName) {}
  
  ::fit::promise<AttachmentValue> Inspect::Collect(fit::Timeout timeout) {
    // We set up the connection and all the error handlers.
    SetUp();
  
    // We start the Inspect data collection.
    StreamInspectSnapshot();
  
    // We wait on one way to finish the flow, joining whichever data has been collected.
    return archive_.WaitForDone(std::move(timeout))
        .then([this](::fit::result<void, Error>& result) -> ::fit::result<AttachmentValue> {
          if (inspect_data_.empty()) {
            FX_LOGS(WARNING) << "Empty Inspect data";
            AttachmentValue value = (result.is_ok()) ? AttachmentValue(Error::kMissingValue)
                                                     : AttachmentValue(result.error());
            return ::fit::ok(std::move(value));
          }
  
          std::string joined_data = "[\n";
          joined_data += fxl::JoinStrings(inspect_data_, ",\n");
          joined_data += "\n]";
  
          AttachmentValue value = (result.is_ok())
                                      ? AttachmentValue(std::move(joined_data))
                                      : AttachmentValue(std::move(joined_data), result.error());
  
          return ::fit::ok(std::move(value));
        });
  }
  
  void Inspect::SetUp() {
    snapshot_iterator_.set_error_handler([this](zx_status_t status) {
      if (archive_.IsAlreadyDone()) {
        return;
      }
  
      FX_PLOGS(ERROR, status) << "Lost connection to fuchsia.diagnostics.BatchIterator";
      archive_.CompleteError(Error::kConnectionError);
    });
  }
  
  void Inspect::StreamInspectSnapshot() {
    fuchsia::diagnostics::StreamParameters stream_parameters;
    stream_parameters.set_data_type(fuchsia::diagnostics::DataType::INSPECT);
    stream_parameters.set_stream_mode(fuchsia::diagnostics::StreamMode::SNAPSHOT);
    stream_parameters.set_format(fuchsia::diagnostics::Format::JSON);
    stream_parameters.set_client_selector_configuration(
        fuchsia::diagnostics::ClientSelectorConfiguration::WithSelectAll(true));
    archive_->StreamDiagnostics(std::move(stream_parameters), snapshot_iterator_.NewRequest());
    AppendNextInspectBatch();
  }
  
  void Inspect::AppendNextInspectBatch() {
    snapshot_iterator_->GetNext([this](auto result) {
      if (archive_.IsAlreadyDone()) {
        return;
      }
  
      if (result.is_err()) {
        FX_LOGS(ERROR) << "Failed to retrieve next Inspect batch: " << result.err();
        // TODO(fxbug.dev/51658): don't complete the flow on an error. The API says we should continue
        // making calls instead.
        archive_.CompleteError(Error::kBadValue);
        return;
      }
  
      std::vector<fuchsia::diagnostics::FormattedContent> batch = std::move(result.response().batch);
      if (batch.empty()) {  // We have gotten all the Inspect data.
        archive_.CompleteOk();
        return;
      }
  
      for (const auto& chunk : batch) {
        if (!chunk.is_json()) {
          FX_LOGS(WARNING) << "Missing JSON Inspect chunk, skipping";
          continue;
        }
  
        std::string json;
        if (!fsl::StringFromVmo(chunk.json(), &json)) {
          FX_LOGS(WARNING) << "Failed to convert Inspect data chunk to string, skipping";
          continue;
        }
        inspect_data_.push_back(json);
      }
      AppendNextInspectBatch();
    });
  }
  
  }  // namespace feedback_data
  }  // namespace forensics
  