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 s = Files.walk(directory, 1)) { List 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); } } }