package org.simantics.acorn; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import org.simantics.acorn.exception.InvalidHeadStateException; import org.simantics.databoard.Bindings; import org.simantics.databoard.binding.mutable.MutableVariant; import org.simantics.databoard.file.RuntimeIOException; import org.simantics.databoard.serialization.Serializer; import org.simantics.databoard.util.binary.BinaryMemory; import org.simantics.utils.FileUtils; public class MainState implements Serializable { private static final long serialVersionUID = 6237383147637270225L; public static final String MAIN_STATE = "main.state"; public int headDir = 0; public MainState() { } private MainState(int headDir) { this.headDir = headDir; } public static MainState load(Path directory, Consumer callback) throws IOException { Files.createDirectories(directory); Path mainState = directory.resolve(MAIN_STATE); try { byte[] bytes = Files.readAllBytes(mainState); MainState state = null; try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { state = (MainState) org.simantics.databoard.Files.readFile(bais, Bindings.getBindingUnchecked(MainState.class)); } while (true) { Path latest = directory.resolve(Integer.toString(state.headDir - 1)); try { Path headState = latest.resolve(HeadState.HEAD_STATE); HeadState.validateHeadStateIntegrity(headState); break; } catch (InvalidHeadStateException e) { e.printStackTrace(); state.headDir--; callback.accept(e); } finally { cleanBaseDirectory(directory, latest, callback); } } return state; } catch(Exception i) { callback.accept(i); int largest = -1; Path latest = findNewHeadStateDir(directory, callback); if (latest != null) largest = safeParseInt(-1, latest.getFileName().toString()); // +1 because we want to return the next head version to use, // not the latest existing version. largest++; MainState state = new MainState( largest ); cleanBaseDirectory(directory, latest, callback); return state; } finally { if (Files.exists(mainState)) { Files.delete(mainState); } } } public void save(Path directory) throws IOException { Path f = directory.resolve(MAIN_STATE); BinaryMemory rf = new BinaryMemory(4096); try { MutableVariant v = new MutableVariant(Bindings.getBindingUnchecked(MainState.class), this); Serializer s = Bindings.getSerializerUnchecked( Bindings.VARIANT ); s.serialize(rf, v); } finally { rf.close(); } byte[] bytes = rf.toByteBuffer().array(); try (OutputStream out = Files.newOutputStream(f)) { out.write(bytes); } FileIO.syncPath(f); } private static boolean isInteger(Path p) { try { Integer.parseInt(p.getFileName().toString()); return true; } catch (NumberFormatException e) { return false; } } /** * * @param directory * @param callback * @return * @throws IOException */ private static Path findNewHeadStateDir(Path directory, Consumer callback) 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()); Path latest = null; for (Path last : reverseSortedPaths) { Path headState = last.resolve(HeadState.HEAD_STATE); try { HeadState.validateHeadStateIntegrity(headState); latest = last; break; } catch (IOException | InvalidHeadStateException e) { // Cleanup is done in {@link cleanBaseDirectory} method callback.accept(e); } } return latest; } } private static int safeParseInt(int defaultValue, String s) { try { return Integer.parseInt(s); } catch (NumberFormatException e) { return defaultValue; } } private static void cleanBaseDirectory(Path directory, Path latest, Consumer callback) 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()); for (Path p : reverseSortedPaths) { if (!p.equals(latest)) { if (Files.exists(p.resolve(HeadState.HEAD_STATE))) { // this indicates that there is a possibility that index and vg's are out of sync // if we are able to find folders with higher number than the current head.state callback.accept(null); } uncheckedDeleteAll(p); } else { break; } } } } private static void uncheckedDeleteAll(Path path) { try { FileUtils.deleteAll(path.toFile()); } catch (IOException e) { throw new RuntimeIOException(e); } } }