Automatically remap repeatedly remapped channels

If you replay a log multiple times and are remapping a channel
/foo on each run, this will now cascade the remaps so that on
the first replay you got
/foo -> /original/foo
and then on the next replay you get
/original/foo -> /original/original/foo
/foo -> /original/foo
and so on.

This is needed to handle logs where timestamp channels are not
NOT_LOGGED.

Change-Id: I158735709504ee1d3ebb7a9678c03bbb0a53ba5c
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/aos/events/logging/log_reader.cc b/aos/events/logging/log_reader.cc
index 87ad72c..f80f28d 100644
--- a/aos/events/logging/log_reader.cc
+++ b/aos/events/logging/log_reader.cc
@@ -288,6 +288,10 @@
               RemoteMessage::GetFullyQualifiedName(), "", node, true);
 
           if (timestamp_channel != nullptr) {
+            // If for some reason a timestamp channel is not NOT_LOGGED (which
+            // is unusual), then remap the channel so that the replayed channel
+            // doesn't overlap with the special separate replay we do for
+            // timestamps.
             if (timestamp_channel->logger() != LoggerConfig::NOT_LOGGED) {
               RemapLoggedChannel<RemoteMessage>(
                   timestamp_channel->name()->string_view(), node);
@@ -1278,9 +1282,40 @@
   event_loop_factory_ = nullptr;
 }
 
+namespace {
+// Checks if the specified channel name/type exists in the config and, depending
+// on the value of conflict_handling, calls conflict_handler or just dies.
+template <typename F>
+void CheckAndHandleRemapConflict(std::string_view new_name,
+                                 std::string_view new_type,
+                                 const Configuration *config,
+                                 LogReader::RemapConflict conflict_handling,
+                                 F conflict_handler) {
+  const Channel *existing_channel =
+      configuration::GetChannel(config, new_name, new_type, "", nullptr, true);
+  if (existing_channel != nullptr) {
+    switch (conflict_handling) {
+      case LogReader::RemapConflict::kDisallow:
+        LOG(FATAL)
+            << "Channel "
+            << configuration::StrippedChannelToString(existing_channel)
+            << " is already used--you can't remap a logged channel to it.";
+        break;
+      case LogReader::RemapConflict::kCascade:
+        LOG(INFO) << "Automatically remapping "
+                  << configuration::StrippedChannelToString(existing_channel)
+                  << " to avoid conflicts.";
+        conflict_handler();
+        break;
+    }
+  }
+}
+}  // namespace
+
 void LogReader::RemapLoggedChannel(std::string_view name, std::string_view type,
                                    std::string_view add_prefix,
-                                   std::string_view new_type) {
+                                   std::string_view new_type,
+                                   RemapConflict conflict_handling) {
   for (size_t ii = 0; ii < logged_configuration()->channels()->size(); ++ii) {
     const Channel *const channel = logged_configuration()->channels()->Get(ii);
     if (channel->name()->str() == name &&
@@ -1292,6 +1327,16 @@
       remapped_channel.remapped_name =
           std::string(add_prefix) + std::string(name);
       remapped_channel.new_type = new_type;
+      const std::string_view remapped_type =
+          remapped_channel.new_type.empty() ? type : remapped_channel.new_type;
+      CheckAndHandleRemapConflict(
+          remapped_channel.remapped_name, remapped_type,
+          remapped_configuration_, conflict_handling,
+          [this, &remapped_channel, remapped_type, add_prefix,
+           conflict_handling]() {
+            RemapLoggedChannel(remapped_channel.remapped_name, remapped_type,
+                               add_prefix, "", conflict_handling);
+          });
       remapped_channels_[ii] = std::move(remapped_channel);
       VLOG(1) << "Remapping channel "
               << configuration::CleanedChannelToString(channel)
@@ -1307,7 +1352,8 @@
 void LogReader::RemapLoggedChannel(std::string_view name, std::string_view type,
                                    const Node *node,
                                    std::string_view add_prefix,
-                                   std::string_view new_type) {
+                                   std::string_view new_type,
+                                   RemapConflict conflict_handling) {
   VLOG(1) << "Node is " << aos::FlatbufferToJson(node);
   const Channel *remapped_channel =
       configuration::GetChannel(logged_configuration(), name, type, "", node);
@@ -1350,6 +1396,15 @@
       std::string(add_prefix) +
       std::string(remapped_channel->name()->string_view());
   remapped_channel_struct.new_type = new_type;
+  const std::string_view remapped_type = new_type.empty() ? type : new_type;
+  CheckAndHandleRemapConflict(
+      remapped_channel_struct.remapped_name, remapped_type,
+      remapped_configuration_, conflict_handling,
+      [this, &remapped_channel_struct, remapped_type, node, add_prefix,
+       conflict_handling]() {
+        RemapLoggedChannel(remapped_channel_struct.remapped_name, remapped_type,
+                           node, add_prefix, "", conflict_handling);
+      });
   remapped_channels_[channel_index] = std::move(remapped_channel_struct);
   MakeRemappedConfig();
 }