1 package org.simantics.acorn;
3 import java.io.ByteArrayInputStream;
4 import java.io.IOException;
5 import java.io.OutputStream;
6 import java.io.Serializable;
7 import java.nio.file.Files;
8 import java.nio.file.Path;
10 import java.util.function.Consumer;
11 import java.util.stream.Collectors;
12 import java.util.stream.Stream;
14 import org.simantics.acorn.exception.InvalidHeadStateException;
15 import org.simantics.databoard.Bindings;
16 import org.simantics.databoard.binding.mutable.MutableVariant;
17 import org.simantics.databoard.file.RuntimeIOException;
18 import org.simantics.databoard.serialization.Serializer;
19 import org.simantics.databoard.util.binary.BinaryMemory;
20 import org.simantics.utils.FileUtils;
22 public class MainState implements Serializable {
24 private static final long serialVersionUID = 6237383147637270225L;
26 public static final String MAIN_STATE = "main.state";
28 public int headDir = 0;
33 private MainState(int headDir) {
34 this.headDir = headDir;
37 public static MainState load(Path directory, Consumer<Exception> callback) throws IOException {
38 Files.createDirectories(directory);
39 Path mainState = directory.resolve(MAIN_STATE);
41 byte[] bytes = Files.readAllBytes(mainState);
42 MainState state = null;
43 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
44 state = (MainState) org.simantics.databoard.Files.readFile(bais, Bindings.getBindingUnchecked(MainState.class));
48 Path latest = directory.resolve(Integer.toString(state.headDir - 1));
50 Path headState = latest.resolve(HeadState.HEAD_STATE);
51 HeadState.validateHeadStateIntegrity(headState);
53 } catch (InvalidHeadStateException e) {
58 cleanBaseDirectory(directory, latest, callback);
62 } catch(Exception i) {
65 Path latest = findNewHeadStateDir(directory, callback);
67 largest = safeParseInt(-1, latest.getFileName().toString());
68 // +1 because we want to return the next head version to use,
69 // not the latest existing version.
71 MainState state = new MainState( largest );
72 cleanBaseDirectory(directory, latest, callback);
75 if (Files.exists(mainState)) {
76 Files.delete(mainState);
81 public void save(Path directory) throws IOException {
82 Path f = directory.resolve(MAIN_STATE);
83 BinaryMemory rf = new BinaryMemory(4096);
85 MutableVariant v = new MutableVariant(Bindings.getBindingUnchecked(MainState.class), this);
86 Serializer s = Bindings.getSerializerUnchecked( Bindings.VARIANT );
91 byte[] bytes = rf.toByteBuffer().array();
92 try (OutputStream out = Files.newOutputStream(f)) {
98 private static boolean isInteger(Path p) {
100 Integer.parseInt(p.getFileName().toString());
102 } catch (NumberFormatException e) {
112 * @throws IOException
114 private static Path findNewHeadStateDir(Path directory, Consumer<Exception> callback) throws IOException {
115 try (Stream<Path> s = Files.walk(directory, 1)) {
116 List<Path> reverseSortedPaths = s
117 .filter(p -> !p.equals(directory) && isInteger(p) && Files.isDirectory(p))
118 .sorted((p1, p2) -> {
119 int p1Name = Integer.parseInt(p1.getFileName().toString());
120 int p2Name = Integer.parseInt(p2.getFileName().toString());
121 return Integer.compare(p2Name, p1Name);
122 }).collect(Collectors.toList());
125 for (Path last : reverseSortedPaths) {
126 Path headState = last.resolve(HeadState.HEAD_STATE);
128 HeadState.validateHeadStateIntegrity(headState);
131 } catch (IOException | InvalidHeadStateException e) {
132 // Cleanup is done in {@link cleanBaseDirectory} method
140 private static int safeParseInt(int defaultValue, String s) {
142 return Integer.parseInt(s);
143 } catch (NumberFormatException e) {
148 private static void cleanBaseDirectory(Path directory, Path latest, Consumer<Exception> callback) throws IOException {
149 try (Stream<Path> s = Files.walk(directory, 1)) {
150 List<Path> reverseSortedPaths = s
151 .filter(p -> !p.equals(directory) && isInteger(p) && Files.isDirectory(p))
152 .sorted((p1, p2) -> {
153 int p1Name = Integer.parseInt(p1.getFileName().toString());
154 int p2Name = Integer.parseInt(p2.getFileName().toString());
155 return Integer.compare(p2Name, p1Name);
156 }).collect(Collectors.toList());
158 for (Path p : reverseSortedPaths) {
159 if (!p.equals(latest)) {
160 if (Files.exists(p.resolve(HeadState.HEAD_STATE))) {
161 // this indicates that there is a possibility that index and vg's are out of sync
162 // if we are able to find folders with higher number than the current head.state
163 callback.accept(null);
165 uncheckedDeleteAll(p);
173 private static void uncheckedDeleteAll(Path path) {
175 FileUtils.deleteAll(path.toFile());
176 } catch (IOException e) {
177 throw new RuntimeIOException(e);