--- /dev/null
+package org.simantics.acorn;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.simantics.databoard.file.RuntimeIOException;
+import org.simantics.utils.FileUtils;
+
+public class MainState implements Serializable {
+
+ private static final long serialVersionUID = 6237383147637270225L;
+
+ public int headDir = 0;
+
+ public MainState() {
+ }
+
+ public MainState(int headDir) {
+ this.headDir = headDir;
+ }
+
+ public static MainState load(Path directory) throws IOException {
+ Files.createDirectories(directory);
+ Path f = directory.resolve("main.state");
+ try {
+ MainState state = null;
+ try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(Files.newInputStream(f)))) {
+ state = (MainState) in.readObject();
+ }
+ while (true) {
+ Path last = directory.resolve(Integer.toString(state.headDir - 1));
+ try {
+ Path headState = last.resolve("head.state");
+ HeadState.validateHeadStateIntegrity(headState);
+ break;
+ } catch (InvalidHeadStateException e) {
+ e.printStackTrace();
+ state.headDir--;
+ uncheckedDeleteAll(last);
+ }
+ }
+ return state;
+ } catch(IOException i) {
+ return new MainState( findNewHeadState(directory) );
+ } catch(ClassNotFoundException c) {
+ throw new Error("MainState class not found", c);
+ } finally {
+ if (Files.exists(f)) {
+ Files.delete(f);
+ }
+ }
+ }
+
+ public void save(Path directory) throws IOException {
+ Path f = directory.resolve("main.state");
+ try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(Files.newOutputStream(f)))) {
+ out.writeObject(this);
+ }
+ FileIO.syncPath(f);
+ }
+
+ private static boolean isInteger(Path p) {
+ try {
+ Integer.parseInt(p.getFileName().toString());
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * TODO> shouldn't do two things in the same function, this does both head.state search and directory cleanup
+ *
+ * @param directory
+ * @return
+ * @throws IOException
+ */
+ private static int findNewHeadState(Path directory) throws IOException {
+ try (Stream<Path> s = Files.walk(directory, 1)) {
+ List<Path> reverseSortedPaths = s
+ .filter(p -> !p.equals(directory) && isInteger(p) && Files.isDirectory(p))
+ .sorted((p1, p2) -> {
+ int p1Name = Integer.parseInt(p1.getFileName().toString());
+ int p2Name = Integer.parseInt(p2.getFileName().toString());
+ return Integer.compare(p2Name, p1Name);
+ }).collect(Collectors.toList());
+
+ int largest = -1;
+ for (Path last : reverseSortedPaths) {
+ Path headState = last.resolve("head.state");
+ if (Files.exists(headState)) {
+ try {
+ HeadState.validateHeadStateIntegrity(headState);
+ largest = safeParseInt(-1, last.getFileName().toString());
+ break;
+ } catch (IOException | InvalidHeadStateException e) {
+ e.printStackTrace();
+ uncheckedDeleteAll(last);
+ }
+ } else {
+ uncheckedDeleteAll(last);
+ }
+ }
+ // +1 because we want to return the next head version to use,
+ // not the latest existing version.
+ return largest + 1;
+ }
+ }
+
+ private static int safeParseInt(int defaultValue, String s) {
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ private static void uncheckedDeleteAll(Path path) {
+ try {
+ FileUtils.deleteAll(path.toFile());
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+}