]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.acorn/src/org/simantics/acorn/MainState.java
Merge commit 'ffdf837'
[simantics/platform.git] / bundles / org.simantics.acorn / src / org / simantics / acorn / MainState.java
1 package org.simantics.acorn;
2
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;
9 import java.util.List;
10 import java.util.function.Consumer;
11 import java.util.stream.Collectors;
12 import java.util.stream.Stream;
13
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;
21
22 public class MainState implements Serializable {
23
24     private static final long serialVersionUID = 6237383147637270225L;
25
26     public static final String MAIN_STATE = "main.state";
27     
28     public int headDir = 0;
29
30     public MainState() {
31     }
32     
33     private MainState(int headDir) {
34         this.headDir = headDir;
35     }
36
37     public static MainState load(Path directory, Consumer<Exception> callback) throws IOException {
38         Files.createDirectories(directory);
39         Path mainState = directory.resolve(MAIN_STATE);
40         try {
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));
45             }
46             
47             while (true) {
48                 Path latest = directory.resolve(Integer.toString(state.headDir - 1));
49                 try {
50                     Path headState = latest.resolve(HeadState.HEAD_STATE);
51                     HeadState.validateHeadStateIntegrity(headState);
52                     break;
53                 } catch (InvalidHeadStateException e) {
54                     e.printStackTrace();
55                     state.headDir--;
56                     callback.accept(e);
57                 } finally {
58                     cleanBaseDirectory(directory, latest, callback);
59                 }
60             }
61             return state;
62         } catch(Exception i) {
63             callback.accept(i);
64             int largest = -1;
65             Path latest = findNewHeadStateDir(directory, callback);
66             if (latest != null)
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.
70             largest++;
71             MainState state = new MainState( largest );
72             cleanBaseDirectory(directory, latest, callback);
73             return state;
74         } finally {
75             if (Files.exists(mainState)) {
76                 Files.delete(mainState);
77             }
78         }
79     }
80
81     public void save(Path directory) throws IOException {
82         Path f = directory.resolve(MAIN_STATE);
83         BinaryMemory rf = new BinaryMemory(4096);
84         try {
85             MutableVariant v = new MutableVariant(Bindings.getBindingUnchecked(MainState.class), this);
86             Serializer s = Bindings.getSerializerUnchecked( Bindings.VARIANT );
87             s.serialize(rf, v);
88         } finally {
89             rf.close();
90         }
91         byte[] bytes = rf.toByteBuffer().array();
92         try (OutputStream out = Files.newOutputStream(f)) {
93             out.write(bytes);
94         }
95         FileIO.syncPath(f);
96     }
97
98     private static boolean isInteger(Path p) {
99         try {
100             Integer.parseInt(p.getFileName().toString());
101             return true;
102         } catch (NumberFormatException e) {
103             return false;
104         }
105     }
106
107     /**
108      *  
109      * @param directory
110      * @param callback 
111      * @return
112      * @throws IOException
113      */
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());
123
124             Path latest = null;
125             for (Path last : reverseSortedPaths) {
126                 Path headState = last.resolve(HeadState.HEAD_STATE);
127                 try {
128                     HeadState.validateHeadStateIntegrity(headState);
129                     latest = last;
130                     break;
131                 } catch (IOException | InvalidHeadStateException e) {
132                     // Cleanup is done in {@link cleanBaseDirectory} method
133                     callback.accept(e);
134                 }
135             }
136             return latest;
137         }
138     }
139
140     private static int safeParseInt(int defaultValue, String s) {
141         try {
142             return Integer.parseInt(s);
143         } catch (NumberFormatException e) {
144             return defaultValue;
145         }
146     }
147
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());
157             
158             for (Path p : reverseSortedPaths) {
159                 if (!p.equals(latest)) {
160                     // this indicates that there is a possibility that index and vg's are out of sync
161                     // if we are able to find folders with higher number than the current head.state
162                     callback.accept(null);
163                     uncheckedDeleteAll(p);
164                 } else {
165                     break;
166                 }
167             }
168             
169         }
170     }
171
172     private static void uncheckedDeleteAll(Path path) {
173         try {
174             FileUtils.deleteAll(path.toFile());
175         } catch (IOException e) {
176             throw new RuntimeIOException(e);
177         }
178     }
179
180 }