Improve UI by streaming MoQT chat messages to a file.

If output_file is an argument, it allows a somewhat friendlier interface where the tail command in a different window allows view of all the messages, while not interfering with typing in the main CLI window.

I will integrate the interactive tool for a third mode.

PiperOrigin-RevId: 608722885
diff --git a/quiche/quic/moqt/tools/chat_client_bin.cc b/quiche/quic/moqt/tools/chat_client_bin.cc
index 282d2d8..8e639d6 100644
--- a/quiche/quic/moqt/tools/chat_client_bin.cc
+++ b/quiche/quic/moqt/tools/chat_client_bin.cc
@@ -6,6 +6,7 @@
 #include <unistd.h>
 
 #include <cstdint>
+#include <fstream>
 #include <iostream>
 #include <memory>
 #include <optional>
@@ -39,6 +40,10 @@
     bool, disable_certificate_verification, false,
     "If true, don't verify the server certificate.");
 
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, output_file, "",
+    "chat messages will stream to a file instead of stdout");
+
 class ChatClient {
  public:
   ChatClient(quic::QuicServerId& server_id, std::string path,
@@ -56,6 +61,11 @@
     std::unique_ptr<quic::ProofVerifier> verifier;
     const bool ignore_certificate = quiche::GetQuicheCommandLineFlag(
         FLAGS_disable_certificate_verification);
+    output_filename_ = quiche::GetQuicheCommandLineFlag(FLAGS_output_file);
+    if (!output_filename_.empty()) {
+      output_file_.open(output_filename_);
+      output_file_ << "Chat transcript:\n";
+    }
     if (ignore_certificate) {
       verifier = std::make_unique<quic::FakeProofVerifier>();
     } else {
@@ -94,6 +104,13 @@
 
   moqt::FullSequence& next_sequence() { return next_sequence_; }
 
+  bool has_output_file() { return !output_filename_.empty(); }
+
+  void WriteToFile(absl::string_view user, absl::string_view message) {
+    output_file_ << user << ": " << message << "\n\n";
+    output_file_.flush();
+  }
+
   class QUICHE_EXPORT RemoteTrackVisitor : public moqt::RemoteTrack::Visitor {
    public:
     RemoteTrackVisitor(ChatClient* client) : client_(client) {}
@@ -138,6 +155,10 @@
         std::cout << "Username " << username << "doesn't exist\n";
         return;
       }
+      if (client_->has_output_file()) {
+        client_->WriteToFile(username, object);
+        return;
+      }
       std::cout << username << ": " << object << "\n";
     }
 
@@ -201,7 +222,6 @@
     }
     while (std::getline(f, line)) {
       if (!got_version) {
-        // Chat server currently does not send version
         if (line != "version=1") {
           session_->Error(moqt::MoqtError::kProtocolViolation,
                           "Catalog does not begin with version");
@@ -305,6 +325,9 @@
 
   // Handling incoming and outgoing messages
   moqt::FullSequence next_sequence_ = {0, 0};
+
+  std::ofstream output_file_;
+  std::string output_filename_;
 };
 
 // A client for MoQT over chat, used for interop testing. See
@@ -335,8 +358,13 @@
     client.RunEventLoop();
   }
   if (client.session_is_open()) {
-    std::cout << "Fully connected. Press ENTER to begin input of message, "
-              << "ENTER when done. Exit session if the message is ':exit'\n";
+    if (client.has_output_file()) {
+      std::cout << "Fully connected. Messages are in the output file. Exit the "
+                << "session by entering :exit\n";
+    } else {
+      std::cout << "Fully connected. Press ENTER to begin input of message, "
+                << "ENTER when done. Exit session if the message is ':exit'\n";
+    }
   }
   bool session_is_open = client.session_is_open();
   struct pollfd poll_settings = {
@@ -349,8 +377,10 @@
     while (poll(&poll_settings, 1, 0) <= 0) {
       client.RunEventLoop();
     }
-    std::cin.ignore(10, '\n');
-    std::cout << username << ": ";
+    if (!client.has_output_file()) {
+      std::cin.ignore(10, '\n');
+      std::cout << username << ": ";
+    }
     std::getline(std::cin, message_to_send);
     if (message_to_send.empty()) {
       continue;
@@ -361,6 +391,9 @@
       session_is_open = false;
       break;
     }
+    if (client.has_output_file()) {
+      client.WriteToFile(username, message_to_send);
+    }
     client.session()->PublishObject(
         client.my_track_name(), client.next_sequence().group++,
         client.next_sequence().object, /*object_send_order=*/0,