Create persistent task files safely

Use a temporary directory to create the files in first to avoid
partially written files being exposed in the waiting directory.

Change-Id: I82222e16b52926ced304cb24eadd1c8d56cf9a7a
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
index 5dc8f73..e9a60e4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -141,7 +141,7 @@
     for (Destination cfg : destinations.get().getAll(FilterType.ALL)) {
       if (cfg.wouldPushProject(project) && cfg.wouldPushRef(refName)) {
         for (URIish uri : cfg.getURIs(project, urlMatch)) {
-          replicationTasksStorage.persist(
+          replicationTasksStorage.create(
               new ReplicateRefUpdate(project.get(), refName, uri, cfg.getRemoteConfigName()));
           cfg.schedule(project, refName, uri, state, now);
         }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationTasksStorage.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationTasksStorage.java
index 950ef26..b069508 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationTasksStorage.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationTasksStorage.java
@@ -46,8 +46,9 @@
  * task:
  *
  * <p><code>
- *   .../running/<sha1>    running replication tasks
- *   .../waiting/<sha1>    outstanding replication tasks
+ *   .../building/<tmp_name>  new replication tasks under construction
+ *   .../running/<sha1>       running replication tasks
+ *   .../waiting/<sha1>       outstanding replication tasks
  * </code>
  *
  * <p>Tasks are moved atomically via a rename between those directories to indicate the current
@@ -84,18 +85,20 @@
 
   private static Gson GSON = new Gson();
 
+  private final Path buildingUpdates;
   private final Path runningUpdates;
   private final Path waitingUpdates;
 
   @Inject
   ReplicationTasksStorage(ReplicationConfig config) {
     Path refUpdates = config.getEventsDirectory().resolve("ref-updates");
+    buildingUpdates = refUpdates.resolve("building");
     runningUpdates = refUpdates.resolve("running");
     waitingUpdates = refUpdates.resolve("waiting");
   }
 
-  public String persist(ReplicateRefUpdate r) {
-    return new Task(r).persist();
+  public String create(ReplicateRefUpdate r) {
+    return new Task(r).create();
   }
 
   @VisibleForTesting
@@ -175,16 +178,19 @@
       waiting = createDir(waitingUpdates).resolve(taskKey);
     }
 
-    public String persist() {
+    public String create() {
       if (Files.exists(waiting)) {
         return taskKey;
       }
 
       try {
-        logger.atFine().log("CREATE %s %s", waiting, updateLog());
-        Files.write(waiting, json.getBytes(UTF_8));
+        Path tmp = Files.createTempFile(createDir(buildingUpdates), taskKey, null);
+        logger.atFine().log("CREATE %s %s", tmp, updateLog());
+        Files.write(tmp, json.getBytes(UTF_8));
+        logger.atFine().log("RENAME %s %s %s", tmp, waiting, updateLog());
+        rename(tmp, waiting);
       } catch (IOException e) {
-        logger.atWarning().withCause(e).log("Couldn't persist task %s", json);
+        logger.atWarning().withCause(e).log("Couldn't create task %s", json);
       }
       return taskKey;
     }