// Path: src/main/java/mirror/tasks/TaskFactory.java
// public interface TaskFactory {
// 
//   default TaskHandle runTask(TaskLogic logic) {
//     return runTask(logic, null);
//   }
// 
//   TaskHandle runTask(TaskLogic logic, Runnable onFailure);
// 
//   void stopTask(TaskLogic logic);
// 
//   default TaskPool newTaskPool() {
//     return new TaskPool(this);
//   }
// 
// }
// 
// Path: src/main/java/mirror/tasks/TaskLogic.java
// public interface TaskLogic {
// 
//   /** Run one interaction, and return how long the task wants to sleep. */
//   Duration runOneLoop() throws InterruptedException;
// 
//   /** Called on the task thread, before we start calling {@link #runOneLoop()} in a loop. */
//   default void onStart() throws InterruptedException {
//   }
// 
//   default void onFailure() throws InterruptedException {
//   }
// 
//   /** Called on the task thread, after we've interrupted/stopped calling {@link #runOneLoop()}. */
//   default void onStop() throws InterruptedException {
//   }
// 
//   /**
//    * Called off the task thread, when we're trying to interrupt the task.
//    *
//    * Most threads shouldn't need this because they'll shut down
//    * when we call thread.interrupt().
//    */
//   default void onInterrupt() {
//   }
// 
//   default String getName() {
//     String name = getClass().getSimpleName();
//     // lambdas don't have simple names
//     if (name.equals("")) {
//       name = StringUtils.substringAfterLast(getClass().getName(), ".");
//     }
//     return name;
//   }
// }
// 
// Path: src/main/java/mirror/tasks/TaskPool.java
// public class TaskPool {
// 
//   private static final Logger log = LoggerFactory.getLogger(TaskPool.class);
//   private final TaskFactory factory;
//   private final List<TaskLogic> tasks = new CopyOnWriteArrayList<>();
//   private final List<Runnable> callbacks = new CopyOnWriteArrayList<>();
//   private final AtomicBoolean shutdown = new AtomicBoolean(false);
// 
//   public TaskPool(TaskFactory factory) {
//     this.factory = factory;
//   }
// 
//   public TaskHandle runTask(TaskLogic logic) {
//     if (shutdown.get()) {
//       throw new IllegalStateException("Pool is shutdown");
//     }
//     TaskHandle h = factory.runTask(logic, this::stopAllTasks);
//     tasks.add(logic);
//     return h;
//   }
// 
//   public void stopTask(TaskLogic logic) {
//     factory.stopTask(logic);
//     tasks.remove(logic);
//   }
// 
//   public void stopAllTasks() {
//     // Several places call MirrorSession.stop when shutting down, so don't
//     // error out if this is already shut down
//     if (shutdown.compareAndSet(false, true)) {
//       factory.runTask(new StopTasksInPool());
//     }
//   }
// 
//   public void addShutdownCallback(Runnable callback) {
//     callbacks.add(callback);
//   }
// 
//   private class StopTasksInPool implements TaskLogic {
//     @Override
//     public Duration runOneLoop() throws InterruptedException {
//       new ArrayList<>(tasks).forEach(t -> {
//         try {
//           stopTask(t);
//         } catch (Exception e) {
//           log.error("Error calling callback", e);
//         }
//       });
//       callbacks.forEach(r -> {
//         try {
//           r.run();
//         } catch (Exception e) {
//           log.error("Error calling callback", e);
//         }
//       });
//       log.debug("All callbacks complete");
//       return Duration.ofMillis(-1);
//     }
//   }
// 
// }


import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.grpc.StatusRuntimeException;
import mirror.tasks.TaskFactory;
import mirror.tasks.TaskLogic;
import mirror.tasks.TaskPool;
    });
    return seedRemote;
  }

  public void addInitialRemoteUpdates(List<Update> remoteInitialUpdates) {
    remoteInitialUpdates.forEach(u -> {
      // if a file, mark it has an initial sync, so we know not to save it
      // it until we get the real update with the data filled in
      if (UpdateTree.isFile(u)) {
        u = u.toBuilder().setData(UpdateTree.initialSyncMarker).build();
      }
      tree.addRemote(u);
    });
  }

  public void diffAndStartPolling(OutgoingConnection outgoingChanges) {
    this.outgoingChanges = outgoingChanges;

    start(syncLogic);

    saveToRemote = new SaveToRemote(queues, fileAccess, outgoingChanges);
    start(saveToRemote);
  }

  public void stop() {
    log.info("Stopping session");
    // this won't block; could potentially add a CountDownLatch
    taskPool.stopAllTasks();
  }

// Path: src/main/java/mirror/tasks/TaskFactory.java
// public interface TaskFactory {
// 
//   default TaskHandle runTask(TaskLogic logic) {
//     return runTask(logic, null);
//   }
// 
//   TaskHandle runTask(TaskLogic logic, Runnable onFailure);
// 
//   void stopTask(TaskLogic logic);
// 
//   default TaskPool newTaskPool() {
//     return new TaskPool(this);
//   }
// 
// }
// 
// Path: src/main/java/mirror/tasks/TaskLogic.java
// public interface TaskLogic {
// 
//   /** Run one interaction, and return how long the task wants to sleep. */
//   Duration runOneLoop() throws InterruptedException;
// 
//   /** Called on the task thread, before we start calling {@link #runOneLoop()} in a loop. */
//   default void onStart() throws InterruptedException {
//   }
// 
//   default void onFailure() throws InterruptedException {
//   }
// 
//   /** Called on the task thread, after we've interrupted/stopped calling {@link #runOneLoop()}. */
//   default void onStop() throws InterruptedException {
//   }
// 
//   /**
//    * Called off the task thread, when we're trying to interrupt the task.
//    *
//    * Most threads shouldn't need this because they'll shut down
//    * when we call thread.interrupt().
//    */
//   default void onInterrupt() {
//   }
// 
//   default String getName() {
//     String name = getClass().getSimpleName();
//     // lambdas don't have simple names
//     if (name.equals("")) {
//       name = StringUtils.substringAfterLast(getClass().getName(), ".");
//     }
//     return name;
//   }
// }
// 
// Path: src/main/java/mirror/tasks/TaskPool.java
// public class TaskPool {
// 
//   private static final Logger log = LoggerFactory.getLogger(TaskPool.class);
//   private final TaskFactory factory;
//   private final List<TaskLogic> tasks = new CopyOnWriteArrayList<>();
//   private final List<Runnable> callbacks = new CopyOnWriteArrayList<>();
//   private final AtomicBoolean shutdown = new AtomicBoolean(false);
// 
//   public TaskPool(TaskFactory factory) {
//     this.factory = factory;
//   }
// 
//   public TaskHandle runTask(TaskLogic logic) {
//     if (shutdown.get()) {
//       throw new IllegalStateException("Pool is shutdown");
//     }
//     TaskHandle h = factory.runTask(logic, this::stopAllTasks);
//     tasks.add(logic);
//     return h;
//   }
// 
//   public void stopTask(TaskLogic logic) {
//     factory.stopTask(logic);
//     tasks.remove(logic);
//   }
// 
//   public void stopAllTasks() {
//     // Several places call MirrorSession.stop when shutting down, so don't
//     // error out if this is already shut down
//     if (shutdown.compareAndSet(false, true)) {
//       factory.runTask(new StopTasksInPool());
//     }
//   }
// 
//   public void addShutdownCallback(Runnable callback) {
//     callbacks.add(callback);
//   }
// 
//   private class StopTasksInPool implements TaskLogic {
//     @Override
//     public Duration runOneLoop() throws InterruptedException {
//       new ArrayList<>(tasks).forEach(t -> {
//         try {
//           stopTask(t);
//         } catch (Exception e) {
//           log.error("Error calling callback", e);
//         }
//       });
//       callbacks.forEach(r -> {
//         try {
//           r.run();
//         } catch (Exception e) {
//           log.error("Error calling callback", e);
//         }
//       });
//       log.debug("All callbacks complete");
//       return Duration.ofMillis(-1);
//     }
//   }
// 
// }


// Path: src/main/java/mirror/MirrorSession.java
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.grpc.StatusRuntimeException;
import mirror.tasks.TaskFactory;
import mirror.tasks.TaskLogic;
import mirror.tasks.TaskPool;

    });
    return seedRemote;
  }

  public void addInitialRemoteUpdates(List<Update> remoteInitialUpdates) {
    remoteInitialUpdates.forEach(u -> {
      // if a file, mark it has an initial sync, so we know not to save it
      // it until we get the real update with the data filled in
      if (UpdateTree.isFile(u)) {
        u = u.toBuilder().setData(UpdateTree.initialSyncMarker).build();
      }
      tree.addRemote(u);
    });
  }

  public void diffAndStartPolling(OutgoingConnection outgoingChanges) {
    this.outgoingChanges = outgoingChanges;

    start(syncLogic);

    saveToRemote = new SaveToRemote(queues, fileAccess, outgoingChanges);
    start(saveToRemote);
  }

  public void stop() {
    log.info("Stopping session");
    // this won't block; could potentially add a CountDownLatch
    taskPool.stopAllTasks();
  }

  private void start(TaskLogic logic) {