1 package org.simantics.db.server.internal;
\r
3 import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
\r
4 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
\r
6 import java.io.EOFException;
\r
8 import java.io.FilenameFilter;
\r
9 import java.io.IOException;
\r
10 import java.io.OutputStream;
\r
11 import java.io.RandomAccessFile;
\r
12 import java.nio.file.DirectoryStream;
\r
13 import java.nio.file.FileSystems;
\r
14 import java.nio.file.FileVisitOption;
\r
15 import java.nio.file.FileVisitResult;
\r
16 import java.nio.file.Files;
\r
17 import java.nio.file.Path;
\r
18 import java.nio.file.PathMatcher;
\r
19 import java.nio.file.Paths;
\r
20 import java.nio.file.SimpleFileVisitor;
\r
21 import java.nio.file.attribute.BasicFileAttributes;
\r
22 import java.util.ArrayList;
\r
23 import java.util.EnumSet;
\r
24 import java.util.HashMap;
\r
25 import java.util.List;
\r
26 import java.util.Properties;
\r
27 import java.util.TreeMap;
\r
28 import java.util.UUID;
\r
30 import org.simantics.databoard.Bindings;
\r
31 import org.simantics.databoard.binding.impl.TreeMapBinding;
\r
32 import org.simantics.databoard.serialization.Serializer;
\r
33 import org.simantics.db.Database;
\r
34 import org.simantics.db.DatabaseUserAgent;
\r
35 import org.simantics.db.ServiceLocator;
\r
36 import org.simantics.db.common.CommentMetadata;
\r
37 import org.simantics.db.common.CommitMetadata;
\r
38 import org.simantics.db.common.utils.Logger;
\r
39 import org.simantics.db.common.utils.MetadataUtil;
\r
40 import org.simantics.db.exception.SDBException;
\r
41 import org.simantics.db.server.DatabaseLastExitException;
\r
42 import org.simantics.db.server.ProCoreException;
\r
43 import org.simantics.db.server.internal.ProCoreServer.TailFile;
\r
44 import org.simantics.db.server.protocol.MessageNumber;
\r
45 import org.simantics.db.server.protocol.MessageText;
\r
47 public class DatabaseI implements Database {
\r
48 private static String NL = System.getProperty("line.separator");
\r
49 private static String TEMP_PREFIX = "db.temp.";
\r
50 public static DatabaseI newDatabaseI(File dbFolder) {
\r
51 return new DatabaseI(dbFolder);
\r
53 private final SessionManager sessionManager = new SessionManager();
\r
54 private final Server server;
\r
55 private final JournalI journal;
\r
56 private DatabaseUserAgent dbUserAgent = null;
\r
57 private DatabaseI(File dbFolder) {
\r
58 server = new Server(dbFolder);
\r
59 journal = new JournalI(dbFolder);
\r
61 public boolean isSubFolder(Path base, Path sub)
\r
62 throws IOException {
\r
63 if (null == base || null == sub)
\r
65 return isSubFolder(base.toFile(), sub.toFile());
\r
67 public boolean isSubFolder(File base, File sub)
\r
68 throws IOException {
\r
69 if (null == base || null == sub)
\r
71 Path basePath = base.getCanonicalFile().toPath();
\r
72 Path subPath = sub.getCanonicalFile().toPath();
\r
73 if (subPath.startsWith(basePath))
\r
78 public Path saveDatabase()
\r
79 throws ProCoreException {
\r
80 Path dbFolder = getFolder().toPath();
\r
81 Path parent = getFolder().getParentFile().toPath();
\r
82 Path temp = createTempFolder(parent, "Could not create temporary directory for saving database.");
\r
83 copyTree(dbFolder, temp, FileOption.IF_NEW);
\r
86 public Path saveDatabase(Path parent)
\r
87 throws ProCoreException {
\r
88 Path dbFolder = getFolder().toPath();
\r
90 boolean yes = isSubFolder(dbFolder, parent);
\r
92 throw new ProCoreException("Parent must not be subdirectory of database directory."
\r
93 + NL + "database=" + dbFolder
\r
94 + NL + "parent=" + parent);
\r
95 } catch (IOException e) {
\r
96 throw new ProCoreException("Failed to save database to parent."
\r
97 + NL + "database=" + dbFolder
\r
98 + NL + "parent=" + parent);
\r
100 Path temp = createTempFolder(parent, "Could not create temporary directory for saving database.");
\r
101 copyTree(dbFolder, temp, FileOption.IF_NEW);
\r
104 public void deleteDatabaseFiles() throws ProCoreException {
\r
105 if (server.isAlive()) {
\r
106 server.tryToStop();
\r
107 if (server.isAlive())
\r
108 throw new ProCoreException("Server must be dead to delete database.");
\r
110 Path db = getFolder().toPath();
\r
111 deleteDatabaseFiles(db); // Redundant, but tests the method.
\r
114 Path copyDatabaseFiles(Path from, Path to) throws ProCoreException {
\r
115 copyTree(from, to, ProCoreServer.BRANCH_DIR, FileOption.IF_EXIST);
\r
116 if (Files.exists(from.resolve(ProCoreServer.TAIL_DIR)))
\r
117 copyTree(from, to, ProCoreServer.TAIL_DIR, FileOption.IF_EXIST);
\r
118 copyPath(from, to, ProCoreServer.CONFIG_FILE, FileOption.IF_EXIST);
\r
119 copyPath(from, to, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);
\r
120 copyPath(from, to, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);
\r
121 copyPath(from, to, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);
\r
122 copyFiles(from, to, ProCoreServer.PAGE_FILE_PATTERN, FileOption.IF_EXIST);
\r
125 public static void deleteDatabaseFiles(Path from) throws ProCoreException {
\r
126 deleteFile(from, ProCoreServer.DCS_FILE, FileOption.IF_EXIST);
\r
127 deleteFile(from, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_EXIST);
\r
128 deleteFile(from, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_EXIST);
\r
129 deleteFile(from, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_EXIST);
\r
130 deleteFile(from, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);
\r
131 deleteFile(from, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);
\r
132 deleteFile(from, ProCoreServer.CONFIG_FILE, FileOption.IF_EXIST);
\r
133 deleteFiles(from, ProCoreServer.PAGE_FILE_PATTERN);
\r
134 deleteTree(from.resolve(ProCoreServer.BRANCH_DIR));
\r
135 deleteFile(from, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);
\r
136 deleteTree(from.resolve(ProCoreServer.TAIL_DIR));
\r
138 Path moveDatabaseFiles(Path from, Path to) throws ProCoreException {
\r
139 moveFolder(from, to, ProCoreServer.BRANCH_DIR, FileOption.IF_NEW);
\r
140 if (Files.exists(from.resolve(ProCoreServer.TAIL_DIR)))
\r
141 moveFolder(from, to, ProCoreServer.TAIL_DIR, FileOption.IF_NEW);
\r
142 movePath(from, to, ProCoreServer.CONFIG_FILE, FileOption.IF_NEW);
\r
143 movePath(from, to, ProCoreServer.GUARD_FILE, FileOption.IF_NEW);
\r
144 movePath(from, to, ProCoreServer.JOURNAL_FILE, FileOption.IF_NEW);
\r
145 movePath(from, to, ProCoreServer.LOG_FILE, FileOption.IF_NEW);
\r
146 moveFiles(from, to, ProCoreServer.PAGE_FILE_PATTERN, FileOption.IF_NEW);
\r
147 movePath(from, to, ProCoreServer.DCS_FILE, FileOption.IF_NEW);
\r
148 moveFile(from, to, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_NEW);
\r
149 moveFile(from, to, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_NEW);
\r
150 moveFile(from, to, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_NEW);
\r
153 private void moveFile(Path from, Path to, String file, FileOption fileOption) throws ProCoreException {
\r
154 if (Files.exists(from.resolve(file)))
\r
155 movePath(from, to, file, fileOption);
\r
157 public void serverCreateConfiguration() throws ProCoreException {
\r
158 server.createConfiguration();
\r
160 public boolean ignoreExit() throws ProCoreException {
\r
162 throw new ProCoreException("Folder must be valid database folder to ignore exit." );
\r
163 server.deleteGuard();
\r
164 File folder = server.getFolder();
\r
165 Path db = Paths.get(folder.getAbsolutePath());
\r
166 Path ri = db.resolve(ProCoreServer.RECOVERY_IGNORED_FILE);
\r
167 if (!Files.exists(ri))
\r
168 try (OutputStream os = Files.newOutputStream(ri)) {
\r
169 } catch (IOException e) {
\r
170 throw new ProCoreException("Could not create file: " + ri, e);
\r
172 boolean ok = false;
\r
173 DatabaseUserAgent dbu = dbUserAgent; // Save current user agent.
\r
175 dbUserAgent = null; // Recursion not supported. Disable user agent.
\r
177 ok = server.isActive();
\r
180 dbUserAgent = dbu; // Restore used user agent.
\r
184 Files.deleteIfExists(ri);
\r
185 } catch (IOException e) {
\r
186 Logger.defaultLogError("Could not delete file: " + ri, e);
\r
188 Path rn = db.resolve(ProCoreServer.RECOVERY_NEEDED_FILE);
\r
190 Files.deleteIfExists(rn);
\r
191 } catch (IOException e) {
\r
192 Logger.defaultLogError("Could not delete file: " + rn, e);
\r
198 public boolean ignoreProtocol() throws ProCoreException {
\r
200 throw new ProCoreException("Folder must be valid database folder to ignore exit." );
\r
201 File folder = server.getFolder();
\r
202 Path db = Paths.get(folder.getAbsolutePath());
\r
203 Path ri = db.resolve(ProCoreServer.PROTOCOL_IGNORED_FILE);
\r
204 if (!Files.exists(ri))
\r
205 try (OutputStream os = Files.newOutputStream(ri)) {
\r
206 } catch (IOException e) {
\r
207 throw new ProCoreException("Could not create file: " + ri, e);
\r
209 boolean ok = false;
\r
212 ok = server.isActive();
\r
218 private long preJournalCheck() throws ProCoreException {
\r
219 File folder = server.getFolder();
\r
220 if (!folder.isDirectory())
\r
221 throw new ProCoreException("Database folder does not exist." + NL + "folder=" + folder);
\r
222 File file = new File(folder, ProCoreServer.JOURNAL_FILE);
\r
223 if (!file.isFile())
\r
224 throw new ProCoreException("Journal file does not exist." + NL + "file=" + file);
\r
225 else if (!file.canRead())
\r
226 throw new ProCoreException("Journal file must be readale to create database from journal." + NL + "file=" + file);
\r
227 else if (server.isAlive())
\r
228 throw new ProCoreException("Server must be dead to create database from journal file." + NL + "file=" + file);
\r
229 return getNextClusterId(folder.toPath());
\r
231 private void postJournalFix(long nextFreeId) throws SDBException {
\r
232 Session s = newSession(null);
\r
233 long current = s.reserveIds(0);
\r
234 if (current < nextFreeId)
\r
235 s.reserveIds((int)(nextFreeId - current));
\r
238 private Path createTempFolder(Path parent, String fail) throws ProCoreException {
\r
240 return Files.createTempDirectory(parent, TEMP_PREFIX);
\r
241 } catch (IOException e) {
\r
242 throw new ProCoreException(fail, e);
\r
245 public void replaceFromJournal() throws SDBException {
\r
246 long nextFreeId = preJournalCheck();
\r
247 Path db = Paths.get(server.getFolder().getAbsolutePath());
\r
248 Path temp = createTempFolder(db, "Could not create temporary directory for database to be created from journal.");
\r
249 movePath(db, temp, ProCoreServer.CONFIG_FILE, FileOption.IF_NEW);
\r
250 movePath(db, temp, ProCoreServer.JOURNAL_FILE, FileOption.IF_NEW);
\r
251 deleteFile(db, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);
\r
252 deleteFiles(db, ProCoreServer.PAGE_FILE_PATTERN);
\r
253 Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);
\r
254 deleteFiles(dbHead, ProCoreServer.VALUE_PATTERN);
\r
255 deleteFiles(dbHead, ProCoreServer.DATA_PATTERN);
\r
256 deleteFiles(dbHead, ProCoreServer.INDEX_PATTERN);
\r
257 Path dbHeadTailFile = dbHead.resolve(ProCoreServer.TAIL_FILE);
\r
258 boolean headTailFileExisted = Files.exists(dbHeadTailFile);
\r
259 final long NEXT_REVISION = 1;
\r
260 if (!headTailFileExisted) // Number of change sets and database id not known at this point. Fortunately they are not used by recovery.
\r
261 TailFile.createTailFile(dbHeadTailFile.toFile(), NEXT_REVISION, nextFreeId, UUID.randomUUID().toString());
\r
262 movePath(dbHead, temp, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);
\r
263 Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);
\r
264 boolean tailExisted = Files.isDirectory(dbTail);
\r
266 copyPath(dbTail, dbHead, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);
\r
269 Files.createDirectory(dbTail);
\r
270 } catch (IOException e) {
\r
271 throw new ProCoreException("Failed to create directory: " + dbTail, e);
\r
273 copyPath(temp, dbTail, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);
\r
275 server.createConfiguration();
\r
278 String t = server.execute("journalRead " + temp.getFileName());
\r
279 if (t.length() > 0)
\r
280 throw new ProCoreException("Could not read journal. reply=" + t);
\r
281 postJournalFix(nextFreeId); // Not the right way for implementing this, but better than incorrect recovery.
\r
283 server.tryToStop();
\r
285 movePath(temp, db, ProCoreServer.CONFIG_FILE, FileOption.OVERWRITE);
\r
288 deleteTree(dbTail);
\r
290 public boolean canPurge() throws ProCoreException {
\r
291 File folder = server.getFolder();
\r
292 Path db = Paths.get(folder.getAbsolutePath());
\r
293 Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);
\r
294 if (!Files.exists(dbHead))
\r
295 return false; // Already clean.
\r
296 boolean empty = isFolderEmpty(dbHead);
\r
298 return false; // Already clean.
\r
299 final boolean[] found = new boolean[1];
\r
301 dbHead.toFile().listFiles(new FilenameFilter() {
\r
303 public boolean accept(File dir, String name) {
\r
306 else if (!name.equals(ProCoreServer.TAIL_FILE)) {
\r
316 * Removes old history i.e. change sets.
\r
317 * Note that also cleans last exit status and removes guard file.
\r
318 * This means that all information about last exit status is also lost
\r
320 * @throws ProCoreException
\r
322 public void purge() throws SDBException {
\r
323 synchronized (server.proCoreServer.getProCoreClient()) {
\r
324 boolean wasAlive = server.isAlive();
\r
327 Path db = Paths.get(server.getFolder().getAbsolutePath());
\r
328 Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);
\r
329 Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);
\r
330 if (!Files.isDirectory(dbTail)) {
\r
332 Files.createDirectory(dbTail);
\r
333 } catch (IOException e) {
\r
334 throw new ProCoreException("Failed to create directory: " + dbTail, e);
\r
337 long nextFreeId = getNextClusterId(db);
\r
338 boolean cleanHead = Files.isDirectory(dbHead) && !isFolderEmpty(dbHead);
\r
339 Logger.defaultLog("Purging old history and exit information. folder=" + db);
\r
341 deleteClusters(dbHead, dbTail);
\r
342 movePath(dbHead, dbTail, ProCoreServer.TAIL_FILE, FileOption.OVERWRITE);
\r
343 moveFiles(dbHead, dbTail, ProCoreServer.VALUE_PATTERN, FileOption.IF_NEW);
\r
344 moveFiles(dbHead, dbTail, ProCoreServer.DATA_PATTERN, FileOption.OVERWRITE);
\r
345 deleteFiles(dbHead, ProCoreServer.INDEX_PATTERN);
\r
347 deleteFiles(db, ProCoreServer.PAGE_FILE_PATTERN);
\r
348 deleteFile(db, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);
\r
349 deleteFile(db, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);
\r
350 deleteFile(db, ProCoreServer.DCS_FILE, FileOption.IF_EXIST);
\r
351 deleteFile(db, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_EXIST);
\r
352 deleteFile(db, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_EXIST);
\r
353 deleteFile(db, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_EXIST);
\r
354 deleteFile(db, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);
\r
355 purgeValues(dbTail);
\r
357 Session s = newSession(null);
\r
358 long current = s.reserveIds(0);
\r
359 if (current < nextFreeId)
\r
360 s.reserveIds((int)(nextFreeId - current));
\r
363 server.tryToStop();
\r
366 private void copyPath(Path fromFolder, Path toFolder, String file, FileOption fileOption) throws ProCoreException {
\r
367 Path from = fromFolder.resolve(file);
\r
368 Path to = toFolder.resolve(file);
\r
370 if (FileOption.IF_EXIST.equals(fileOption)) {
\r
371 if (!Files.exists(from))
\r
374 if (FileOption.OVERWRITE.equals(fileOption))
\r
375 Files.copy(from, to, COPY_ATTRIBUTES, REPLACE_EXISTING);
\r
377 Files.copy(from, to, COPY_ATTRIBUTES);
\r
378 } catch (IOException e) {
\r
379 throw new ProCoreException("Could not copy " + from + " to " + to, e);
\r
382 private static void deleteFile(Path from, String file, FileOption fileOption) throws ProCoreException {
\r
383 Path path = from.resolve(file);
\r
384 deletePath(path, fileOption);
\r
386 private static void deletePath(Path path, FileOption fileOption) throws ProCoreException {
\r
388 if (FileOption.IF_EXIST.equals(fileOption))
\r
389 Files.deleteIfExists(path);
\r
391 Files.delete(path);
\r
392 } catch (IOException e) {
\r
393 throw new ProCoreException("Could not delete " + path, e);
\r
396 private boolean isFolderEmpty(final Path folder) { // True if folder exists and is empty.
\r
397 if (!Files.isDirectory(folder))
\r
399 try(DirectoryStream<Path> folderStream = Files.newDirectoryStream(folder)) {
\r
400 return !folderStream.iterator().hasNext();
\r
401 } catch (IOException e) {
\r
402 Logger.defaultLogError("Failed to open folder stream. folder=" + folder, e);
\r
406 private void movePath(Path fromPath, Path toPath, String file, FileOption fileOption) throws ProCoreException {
\r
407 Path from = fromPath.resolve(file);
\r
408 Path to = toPath.resolve(file);
\r
410 if (FileOption.OVERWRITE.equals(fileOption))
\r
411 Files.move(from, to, REPLACE_EXISTING);
\r
413 Files.move(from, to);
\r
414 } catch (IOException e) {
\r
415 throw new ProCoreException("Could not move " + from + " to " + to, e);
\r
418 private static void copyTree(Path from, Path to, String path, final FileOption fileOption) throws ProCoreException {
\r
419 copyTree(from.resolve(path), to.resolve(path), fileOption);
\r
421 private static void copyTree(Path from, Path to, final FileOption fileOption) throws ProCoreException {
\r
422 class Visitor extends SimpleFileVisitor<Path> {
\r
423 private Path fromPath;
\r
424 private Path toPath;
\r
425 Visitor(Path from, Path to) {
\r
430 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
\r
431 Path targetPath = toPath.resolve(fromPath.relativize(dir));
\r
432 if (!Files.exists(targetPath)) {
\r
433 Files.createDirectory(targetPath);
\r
435 return FileVisitResult.CONTINUE;
\r
438 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
\r
439 if (FileOption.OVERWRITE.equals(fileOption))
\r
440 Files.copy(file, toPath.resolve(fromPath.relativize(file)), COPY_ATTRIBUTES, REPLACE_EXISTING);
\r
442 Files.copy(file, toPath.resolve(fromPath.relativize(file)), COPY_ATTRIBUTES);
\r
443 return FileVisitResult.CONTINUE;
\r
447 Visitor v = new Visitor(from, to);
\r
448 EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);
\r
449 Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);
\r
450 } catch (IOException e) {
\r
451 throw new ProCoreException("Could not copy " + from + " to " + to, e);
\r
454 private static void deleteTree(Path path) throws ProCoreException {
\r
455 if (!Files.exists(path))
\r
457 class Visitor extends SimpleFileVisitor<Path> {
\r
459 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
\r
461 Files.delete(file);
\r
462 } catch (IOException ioe) {
\r
463 ioe.printStackTrace();
\r
466 return FileVisitResult.CONTINUE;
\r
469 public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
\r
473 } catch (IOException ioe) {
\r
474 ioe.printStackTrace();
\r
477 return FileVisitResult.CONTINUE;
\r
483 Visitor v = new Visitor();
\r
484 EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);
\r
485 Files.walkFileTree(path, opts, Integer.MAX_VALUE, v);
\r
486 } catch (IOException e) {
\r
487 throw new ProCoreException("Could not delete " + path, e);
\r
490 private static void moveFolder(Path fromPath, Path toPath, String folder, FileOption fileOption) throws ProCoreException {
\r
491 Path from = fromPath.resolve(folder);
\r
492 Path to = toPath.resolve(folder);
\r
493 copyTree(from, to, fileOption);
\r
501 private void copyFiles(final Path from, final Path to, String pattern, final FileOption fileOption) throws ProCoreException {
\r
502 class Visitor extends SimpleFileVisitor<Path> {
\r
503 private final PathMatcher matcher;
\r
504 Visitor(String pattern) {
\r
505 matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
\r
508 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
\r
509 Path name = file.getFileName();
\r
511 return FileVisitResult.CONTINUE;
\r
512 else if (!matcher.matches(name))
\r
513 return FileVisitResult.CONTINUE;
\r
514 if (FileOption.OVERWRITE.equals(fileOption))
\r
515 Files.copy(file, to.resolve(name), REPLACE_EXISTING);
\r
517 Files.copy(file, to.resolve(name));
\r
518 return FileVisitResult.CONTINUE;
\r
521 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
\r
522 if (dir.equals(from))
\r
523 return FileVisitResult.CONTINUE;
\r
525 return FileVisitResult.SKIP_SUBTREE;
\r
529 Visitor v = new Visitor(pattern);
\r
530 EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);
\r
531 Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);
\r
532 } catch (IOException e) {
\r
533 throw new ProCoreException("Could not copy " + from + " to " + to, e);
\r
536 private void moveFiles(final Path from, final Path to, String pattern, final FileOption fileOption) throws ProCoreException {
\r
537 class Visitor extends SimpleFileVisitor<Path> {
\r
538 private final PathMatcher matcher;
\r
539 Visitor(String pattern) {
\r
540 matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
\r
543 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
\r
544 Path name = file.getFileName();
\r
546 return FileVisitResult.CONTINUE;
\r
547 else if (!matcher.matches(name))
\r
548 return FileVisitResult.CONTINUE;
\r
549 if (FileOption.OVERWRITE.equals(fileOption))
\r
550 Files.move(file, to.resolve(name), REPLACE_EXISTING);
\r
552 Files.move(file, to.resolve(name));
\r
553 return FileVisitResult.CONTINUE;
\r
556 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
\r
557 if (dir.equals(from))
\r
558 return FileVisitResult.CONTINUE;
\r
560 return FileVisitResult.SKIP_SUBTREE;
\r
564 Visitor v = new Visitor(pattern);
\r
565 EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);
\r
566 Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);
\r
567 } catch (IOException e) {
\r
568 throw new ProCoreException("Could not move " + from + " to " + to, e);
\r
571 private void deleteClusters(final Path head, final Path tail) throws ProCoreException {
\r
572 class Visitor extends SimpleFileVisitor<Path> {
\r
573 private final PathMatcher matcher;
\r
574 Visitor(String pattern) {
\r
575 matcher = FileSystems.getDefault().getPathMatcher("glob:" + ProCoreServer.DELETED_PATTERN);
\r
578 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
\r
579 Path name = file.getFileName();
\r
581 return FileVisitResult.CONTINUE;
\r
582 else if (!matcher.matches(name))
\r
583 return FileVisitResult.CONTINUE;
\r
584 String deletedStr = name.toString();
\r
585 String indexName = deletedStr.replaceFirst(ProCoreServer.DELETED_PREFIX, ProCoreServer.INDEX_PREFIX);
\r
586 String dataName = deletedStr.replaceFirst(ProCoreServer.DELETED_PREFIX, ProCoreServer.DATA_PREFIX);
\r
587 String[] ss = deletedStr.split("\\.");
\r
588 String valuePattern = ProCoreServer.VALUE_PREFIX + ss[1] + "." + ss[2] + "*";
\r
589 Files.delete(file);
\r
590 Files.delete(head.resolve(indexName));
\r
591 Files.delete(head.resolve(dataName));
\r
593 deleteFiles(head, valuePattern);
\r
594 if (Files.isDirectory(tail)) {
\r
595 Files.deleteIfExists(tail.resolve(dataName));
\r
596 deleteFiles(tail, valuePattern);
\r
598 } catch (ProCoreException e) {
\r
599 throw new IOException("Delete values failed.", e);
\r
601 return FileVisitResult.CONTINUE;
\r
604 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
\r
605 if (dir.equals(head))
\r
606 return FileVisitResult.CONTINUE;
\r
608 return FileVisitResult.SKIP_SUBTREE;
\r
612 Visitor v = new Visitor(ProCoreServer.DELETED_PREFIX);
\r
613 EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);
\r
614 Files.walkFileTree(head, opts, Integer.MAX_VALUE, v);
\r
615 } catch (IOException e) {
\r
616 throw new ProCoreException("Could not delete cluster(s).\nhead=" + head + "\ntail=" + tail);
\r
619 private static long convertHexStringToLong(String hex) {
\r
620 if (hex.length() < 16)
\r
621 return Long.parseLong(hex, 16);
\r
622 long f = Long.parseLong(hex.substring(0,1), 16) << 60;
\r
623 long l = Long.parseLong(hex.substring(1,16), 16);
\r
626 private void purgeValues(final Path folder) throws ProCoreException {
\r
627 final class Value {
\r
628 long first; // First part of cluster id.
\r
629 long second; // Secon part of cluster id.
\r
630 int index; // Resource index of the value.
\r
631 transient long cs; // Change set of the value.
\r
632 public Value(long first, long second, int index, long cs) {
\r
633 this.first = first;
\r
634 this.second = second;
\r
635 this.index = index;
\r
639 return ProCoreServer.VALUE_PREFIX + toString() + ProCoreServer.VALUE_SUFFIX;
\r
642 public String toString() {
\r
643 return String.format("%x.%x.%d.%d", first, second, index, cs);
\r
646 public boolean equals(Object o) {
\r
649 else if (!(o instanceof Value))
\r
651 Value x = (Value)o;
\r
652 return first == x.first && second == x.second && index == x.index;
\r
655 public int hashCode() {
\r
657 int f = (int)(first ^ (first >>> 32));
\r
658 result = 31 * result + f;
\r
659 int s = (int)(second ^ (second >>> 32));
\r
660 result = 31 * result + s;
\r
661 return result + index;
\r
664 File[] files = folder.toFile().listFiles(new FilenameFilter() {
\r
666 public boolean accept(File dir, String name) {
\r
667 return name.startsWith(ProCoreServer.VALUE_PREFIX);
\r
670 HashMap<Value, Value> values = new HashMap<Value, Value>();
\r
671 for (int i = 0; i < files.length; ++i) {
\r
672 String s = files[i].getName();
\r
673 String[] ss = s.split("\\.");
\r
674 if (ss.length != 6) {
\r
675 Logger.defaultLogError("Illegal external value file name. name=" + s);
\r
678 long first = convertHexStringToLong(ss[1]);
\r
679 long second = convertHexStringToLong(ss[2]);
\r
680 int ri = Integer.parseInt(ss[3]);
\r
681 long cs = Long.parseLong(ss[4]);
\r
682 Value nv = new Value(first, second, ri, cs);
\r
683 Value ov = values.get(nv);
\r
685 values.put(nv, nv);
\r
686 else if (ov.cs < nv.cs) {
\r
687 deleteFile(folder, ov.getName(), FileOption.IF_EXIST);
\r
690 deleteFile(folder, nv.getName(), FileOption.IF_EXIST);
\r
693 private long getNextClusterId(final Path db) throws ProCoreException {
\r
694 long clusterId = 0;
\r
695 Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);
\r
696 File[] files = dbHead.toFile().listFiles(new FilenameFilter() {
\r
698 public boolean accept(File dir, String name) {
\r
699 return name.startsWith(ProCoreServer.DATA_PREFIX);
\r
702 for (int i = 0; i < files.length; ++i) {
\r
703 String s = files[i].getName();
\r
704 String[] ss = s.split("\\.", 4);
\r
705 if (ss.length != 4) {
\r
706 Logger.defaultLogError("Illegal cluster file name. name=" + s);
\r
709 long id = convertHexStringToLong(ss[2]);
\r
710 if (id > clusterId)
\r
713 final Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);
\r
714 if (!Files.exists(dbTail))
\r
715 return clusterId + 1;
\r
716 class Visitor extends SimpleFileVisitor<Path> {
\r
717 private final PathMatcher matcher;
\r
719 Visitor(String pattern, long clusterId) {
\r
720 this.clusterId = clusterId;
\r
721 matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
\r
724 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
\r
725 Path name = file.getFileName();
\r
727 return FileVisitResult.CONTINUE;
\r
728 else if (!matcher.matches(name))
\r
729 return FileVisitResult.CONTINUE;
\r
730 String s = name.toString();
\r
731 String[] ss = s.split("\\.", 4);
\r
732 if (ss.length != 4) {
\r
733 Logger.defaultLogError("Illegal cluster file name. name=" + s);
\r
734 return FileVisitResult.CONTINUE;
\r
736 long id = convertHexStringToLong(ss[2]);
\r
737 if (id > clusterId)
\r
739 return FileVisitResult.CONTINUE;
\r
742 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
\r
743 if (dir.equals(dbTail))
\r
744 return FileVisitResult.CONTINUE;
\r
746 return FileVisitResult.SKIP_SUBTREE;
\r
750 Visitor v = new Visitor(ProCoreServer.DATA_PATTERN, clusterId);
\r
751 EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);
\r
752 Files.walkFileTree(dbTail, opts, Integer.MAX_VALUE, v);
\r
753 return v.clusterId + 1;
\r
754 } catch (IOException e) {
\r
755 throw new ProCoreException("Could not get next free cluster id for " + db, e);
\r
758 private static void deleteFiles(final Path folder, String pattern) throws ProCoreException {
\r
759 if (!Files.exists(folder))
\r
761 class Visitor extends SimpleFileVisitor<Path> {
\r
762 private final PathMatcher matcher;
\r
763 Visitor(String pattern) {
\r
764 matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
\r
767 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
\r
768 Path name = file.getFileName();
\r
770 return FileVisitResult.CONTINUE;
\r
771 else if (!matcher.matches(name))
\r
772 return FileVisitResult.CONTINUE;
\r
773 Files.delete(file);
\r
774 return FileVisitResult.CONTINUE;
\r
777 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
\r
778 if (dir.equals(folder))
\r
779 return FileVisitResult.CONTINUE;
\r
781 return FileVisitResult.SKIP_SUBTREE;
\r
785 Visitor v = new Visitor(pattern);
\r
786 EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);
\r
787 Files.walkFileTree(folder, opts, Integer.MAX_VALUE, v);
\r
788 } catch (IOException e) {
\r
789 throw new ProCoreException("Could not delete " + folder + " with " + pattern, e);
\r
792 public void copy(File to) throws ProCoreException {
\r
793 copyDatabaseFiles(server.getFolder().toPath(), to.toPath());
\r
795 public boolean isServerAlive() {
\r
796 return server.isAlive();
\r
798 Client newClient() throws ProCoreException {
\r
799 return server.newClient();
\r
801 public void createFolder() throws ProCoreException {
\r
802 if (server.folder.exists())
\r
804 boolean created = server.folder.mkdirs();
\r
806 throw new ProCoreException("Could not create folder=" + server.folder);
\r
809 * @throws ProCoreException if could not stop server.
\r
811 public boolean serverTryToStop() throws ProCoreException {
\r
812 return server.tryToStop();
\r
816 void Do(ProCoreServer proCoreServer) {
\r
821 private final File folder; // Always non null.
\r
822 private final ProCoreServer proCoreServer;
\r
823 void start() throws ProCoreException {
\r
825 proCoreServer.start();
\r
826 } catch (DatabaseLastExitException e) {
\r
827 if (null == dbUserAgent)
\r
829 if (!dbUserAgent.handleStart(e))
\r
830 throw new ProCoreException(folder, "Failed to handle start exception.", e);
\r
831 proCoreServer.start();
\r
834 void stop() throws ProCoreException {
\r
836 proCoreServer.stop();
\r
837 } catch (InterruptedException e) {
\r
838 if (proCoreServer.isAlive())
\r
839 throw new ProCoreException("ProCoreServer stop was interrupted.", e);
\r
842 boolean isAlive() {
\r
844 return proCoreServer.isAlive();
\r
845 } catch (ProCoreException e) {
\r
846 Logger.defaultLogError(e);
\r
850 Server(File aFolder) {
\r
851 if (null == aFolder)
\r
852 throw new RuntimeException("Database folder can not be null.");
\r
854 folder = aFolder.getCanonicalFile();
\r
855 } catch (IOException e) {
\r
856 String t = "Could not get canonical path. file=" + aFolder;
\r
857 Logger.defaultLogError(t);
\r
858 throw new RuntimeException(t);
\r
861 File serverFolder = org.simantics.db.server.internal.Activator.getServerFolder();
\r
862 if (!folder.isDirectory())
\r
864 proCoreServer = ProCoreServer.getProCoreServer(serverFolder, folder);
\r
866 } catch (Throwable t) {
\r
867 Logger.defaultLogError(t);
\r
868 throw new RuntimeException(t);
\r
871 void createConfiguration() throws ProCoreException {
\r
872 if (!folder.isDirectory())
\r
873 throw new ProCoreException("Can't create configuration because folder is not ok. folder=" + folder.getAbsolutePath());
\r
874 File file = new File(folder, ProCoreServer.CONFIG_FILE);
\r
876 file.createNewFile();
\r
877 } catch (IOException e) {
\r
878 throw new ProCoreException("Can't create configuration file. file=" + file.getAbsolutePath());
\r
881 void deleteGuard() throws ProCoreException {
\r
882 if (server.isActive())
\r
883 throw new ProCoreException("Will not delete guard file when server is alive.");
\r
884 if (!server.isFolderOk(server.getFolder()))
\r
885 throw new ProCoreException("Will not delete guard file when server folder is not ok. folder=" + server.getFolder());
\r
886 File file = new File(folder, ProCoreServer.GUARD_FILE);
\r
887 if (!file.exists())
\r
888 throw new ProCoreException("Guard file does not exist. file=" + file.getAbsolutePath());
\r
889 boolean deleted = file.delete();
\r
891 throw new ProCoreException("Failed to delete file=" + file.getAbsolutePath());
\r
893 String execute(String aCommand) throws ProCoreException {
\r
895 return proCoreServer.execute(aCommand);
\r
896 } catch (InterruptedException e) {
\r
897 throw new ProCoreException("Execute call was interrupted.", e);
\r
900 Status getStatus() {
\r
901 Status status = Status.NoDatabase;
\r
904 path = folder.getCanonicalPath();
\r
905 } catch (IOException e) {
\r
906 Util.logError("Could not get canonical path for folder. folder=" + folder.getAbsolutePath(), e);
\r
907 path = "<no path>";
\r
909 if (!isFolderOk(folder))
\r
910 status = Status.NoDatabase;
\r
911 else if (!isAlive())
\r
912 status = Status.NotRunning;
\r
913 else if (isConnected()) {
\r
915 status = Status.Local;
\r
917 status = Status.Remote;
\r
919 status = Status.Standalone;
\r
920 status.message = status.getString() + "@" + path;
\r
926 boolean isActive() {
\r
928 return proCoreServer.isActive();
\r
929 } catch (ProCoreException e) {
\r
930 Logger.defaultLogError("IsActive failed.", e);
\r
934 boolean isConnected() {
\r
936 return proCoreServer.isConnected();
\r
937 } catch (ProCoreException e) {
\r
938 Logger.defaultLogError("IsConnected failed.", e);
\r
942 boolean isLocal() {
\r
944 return proCoreServer.isLocal();
\r
945 } catch (ProCoreException e) {
\r
946 Logger.defaultLogError("IsLocal faailed.", e);
\r
950 void connect() throws ProCoreException, InterruptedException {
\r
951 proCoreServer.connect();
\r
953 void disconnect() {
\r
955 proCoreServer.disconnect();
\r
956 } catch (ProCoreException e) {
\r
957 Logger.defaultLogError("Could not disconnect.", e);
\r
962 * @return true if given folder contains database journal file or
\r
963 * configuration file.
\r
965 boolean isFolderOk(File aFolder) {
\r
966 if (!aFolder.isDirectory())
\r
968 File config = new File(aFolder, ProCoreServer.CONFIG_FILE);
\r
969 if (config.exists())
\r
971 File journal = new File(aFolder, ProCoreServer.JOURNAL_FILE);
\r
972 if (journal.exists())
\r
977 * @throws ProCoreException if could not stop server.
\r
979 boolean tryToStop() throws ProCoreException {
\r
980 return proCoreServer.tryToStop();
\r
982 Client newClient() throws ProCoreException {
\r
983 return proCoreServer.newClient();
\r
986 // From interface Database
\r
988 public void initFolder(Properties properties) throws ProCoreException {
\r
990 serverCreateConfiguration();
\r
993 public void deleteFiles() throws ProCoreException {
\r
994 deleteDatabaseFiles();
\r
997 public void start() throws ProCoreException {
\r
1001 public boolean isRunning() throws ProCoreException {
\r
1002 return isServerAlive();
\r
1005 public boolean tryToStop() throws ProCoreException {
\r
1006 return serverTryToStop();
\r
1009 public String execute(String aCommand) throws ProCoreException {
\r
1010 return server.execute(aCommand);
\r
1013 public void purgeDatabase() throws SDBException {
\r
1014 synchronized (server.proCoreServer.getProCoreClient()) {
\r
1015 if (!server.isLocal())
\r
1016 throw new ProCoreException("Purge is allowed only for local server.");
\r
1017 List<Client> clients = sessionManager.disconnect(this);
\r
1020 sessionManager.connect(this, clients);
\r
1021 } catch (InterruptedException e) {
\r
1022 throw new ProCoreException("Failed to connect after purge.", e);
\r
1027 public Session newSession(ServiceLocator locator) throws ProCoreException {
\r
1028 return sessionManager.newSession(this);
\r
1031 public Path createFromChangeSets(int revision) throws ProCoreException {
\r
1032 if (!isFolderOk())
\r
1033 throw new ProCoreException("Folder must be valid database folder to create database from journal." );
\r
1034 File folder = server.getFolder();
\r
1035 File file = new File(folder, ProCoreServer.DCS_FILE);
\r
1036 if (!file.isFile() && !file.canRead())
\r
1037 throw new ProCoreException("Dump file must be readable. file=" + file.getAbsolutePath());
\r
1038 Path db = Paths.get(folder.getAbsolutePath());
\r
1039 Path temp = createTempFolder(db, "Could not create temporary directory for database to be created from change sets.");
\r
1040 Server s = new Server(temp.toFile());
\r
1041 s.createConfiguration();
\r
1043 String t = s.execute("loadChangeSets .. " + revision);
\r
1044 if (t.length() < 1)
\r
1045 throw new ProCoreException("Could not read journal. reply=" + t);
\r
1048 int cs = Integer.parseInt(t);
\r
1049 if (cs == revision)
\r
1051 throw new ProCoreException("Could not load change sets. reply=" + t);
\r
1052 } catch (NumberFormatException e) {
\r
1053 throw new ProCoreException("Could not load change sets. reply=" + t);
\r
1057 public void clone(File to, int revision, boolean saveHistory) {
\r
1058 String history = saveHistory ? "with history." : "without history.";
\r
1059 String message = "Clone to " + to.getAbsolutePath() + "@" + revision + " " + history;
\r
1060 Util.trace(message);
\r
1063 public DatabaseUserAgent getUserAgent() {
\r
1064 return dbUserAgent;
\r
1067 public void setUserAgent(DatabaseUserAgent dbUserAgent) {
\r
1068 this.dbUserAgent = dbUserAgent;
\r
1071 public Status getStatus() {
\r
1072 return server.getStatus();
\r
1075 public File getFolder() {
\r
1076 return server.getFolder();
\r
1079 public boolean isFolderOk() {
\r
1080 return server.isFolderOk(server.getFolder());
\r
1083 public boolean isFolderOk(File folder) {
\r
1084 return server.isFolderOk(folder);
\r
1086 public boolean isFolderEmpty() {
\r
1087 return isFolderEmpty(server.getFolder());
\r
1090 public boolean isFolderEmpty(File folder) {
\r
1091 return isFolderEmpty(folder.toPath());
\r
1094 public void deleteGuard() throws ProCoreException {
\r
1095 server.deleteGuard();
\r
1098 public boolean isConnected() throws ProCoreException {
\r
1099 return server.isConnected();
\r
1102 public void connect() throws ProCoreException {
\r
1103 if (!isFolderOk())
\r
1104 throw new ProCoreException("Could not connect to " + getFolder());
\r
1105 if (!server.isAlive())
\r
1107 if (isConnected())
\r
1111 } catch (InterruptedException e) {
\r
1112 Util.logError("Server connect was interrupted.", e);
\r
1114 if (server.isActive())
\r
1116 throw new ProCoreException("Could not connect to " + getFolder());
\r
1119 public void disconnect() throws ProCoreException {
\r
1120 server.disconnect();
\r
1123 public Path dumpChangeSets() throws ProCoreException {
\r
1124 if (!isFolderOk())
\r
1125 throw new ProCoreException("Folder must be set to dump change sets.");
\r
1126 if (!server.isActive())
\r
1127 throw new ProCoreException("Server must be responsive to dump change sets.");
\r
1128 String t = server.execute("dumpChangeSets").replaceAll("\n", "");
\r
1130 int ncs = Integer.parseInt(t);
\r
1133 File file = new File(getFolder(), ProCoreServer.DCS_FILE);
\r
1134 return file.toPath();
\r
1135 } catch (NumberFormatException e) {
\r
1136 throw new ProCoreException("Could not dump change sets.", e);
\r
1140 public long serverGetTailChangeSetId() throws ProCoreException {
\r
1142 return server.proCoreServer.getTailData().nextChangeSetId;
\r
1143 } catch (TailReadException e) {
\r
1148 public JournalI getJournal() throws ProCoreException {
\r
1151 class JournalI implements Database.Journal {
\r
1153 private final File journalFile;
\r
1154 private long lastModified;
\r
1155 private RandomAccessFile file; // Journal file.
\r
1156 private ArrayList<Long> offsets = new ArrayList<Long>(); // Offsets to commands in journal file.
\r
1157 private Line lines[] = new Line[256];
\r
1158 int firstLine = 0; // Index of the first element in lines table.
\r
1159 Analyzed(File dbFolder) {
\r
1160 journalFile = new File(dbFolder, ProCoreServer.JOURNAL_FILE);
\r
1161 for (int i=0; i<lines.length; ++i)
\r
1162 lines[i] = new Line();
\r
1164 private boolean canRead() {
\r
1165 return journalFile.isFile() && journalFile.canRead();
\r
1167 private int count() {
\r
1169 return readOffsets().size();
\r
1170 } catch (ProCoreException e) {
\r
1171 Util.logError("Failed to read or analyze journal. file=" + journalFile, e);
\r
1176 private void clear() {
\r
1180 private void close() {
\r
1181 if (file != null) {
\r
1184 } catch (Throwable e) {
\r
1185 Logger.defaultLogError("Close file threw an exception.", e);
\r
1190 private void open() throws ProCoreException {
\r
1192 file = new RandomAccessFile(journalFile, "r");
\r
1193 } catch (Throwable e) {
\r
1195 throw new ProCoreException("Failed to open journal file.", e);
\r
1198 private String getComment(byte[] bytes) throws ProCoreException {
\r
1199 Serializer METADATA_SERIALIZER = Bindings.getSerializerUnchecked(new TreeMapBinding(Bindings.STRING, Bindings.BYTE_ARRAY));
\r
1201 @SuppressWarnings("unchecked")
\r
1202 TreeMap<String, byte[]> metadata = (TreeMap<String, byte[]>) METADATA_SERIALIZER.deserialize(bytes);
\r
1203 CommitMetadata commit = MetadataUtil.getMetadata(metadata, CommitMetadata.class);
\r
1204 String date = "<metadata does not contain date>";
\r
1205 if (null != commit && null != commit.date)
\r
1206 date = commit.date.toString();
\r
1207 CommentMetadata comment = MetadataUtil.getMetadata(metadata, CommentMetadata.class);
\r
1208 String comments = "<metadata does not contain comment>";
\r
1209 if (null != comment)
\r
1210 comments = comment.toString();
\r
1211 return date + " " + comments;
\r
1212 } catch (IOException e) {
\r
1213 throw new ProCoreException("Failed to interpret metadata.", e);
\r
1216 private Line getRow(int index, Line line) throws ProCoreException {
\r
1217 if (index < 0 || index >= offsets.size())
\r
1218 return null; // Index out of range.
\r
1219 if (index < firstLine || index >= firstLine + lines.length) {
\r
1221 if (index < firstLine)
\r
1222 return null; // Index out of range.
\r
1224 int offset = index - firstLine;
\r
1225 line.status = lines[offset].status;
\r
1226 line.request = lines[offset].request;
\r
1227 line.comment = lines[offset].comment;
\r
1230 private void readLines(int first) throws ProCoreException {
\r
1233 for (int i=0, index = first; i<lines.length; ++i, ++index)
\r
1234 readLine(index, lines[i]);
\r
1235 firstLine = first;
\r
1240 private void readLine(int index, Line line) throws ProCoreException {
\r
1241 if (index >= offsets.size()) {
\r
1242 line.status = false;
\r
1243 line.request = "<Illegal request.>";
\r
1244 line.comment = "<Illegal request.>";
\r
1247 long offset = offsets.get(index);
\r
1249 file.seek(offset);
\r
1250 int i = file.readInt();
\r
1251 int length = Integer.reverseBytes(i);
\r
1252 byte b = file.readByte();
\r
1253 boolean ok = b != 0;
\r
1254 b = file.readByte();
\r
1255 // boolean littleEndian = b != 0;
\r
1256 i = file.readInt();
\r
1257 int type = Integer.reverseBytes(i);
\r
1258 String comment = "";
\r
1260 throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");
\r
1261 else if (length > 6) {
\r
1263 file.skipBytes(length - 6);
\r
1264 else if (length > 22){
\r
1265 file.skipBytes(16);
\r
1266 i = file.readInt();
\r
1267 int size = Integer.reverseBytes(i);
\r
1268 if (size != length - 26)
\r
1269 throw new ProCoreException("Metadata corrupted at" + file.getFilePointer() + ".");
\r
1270 byte[] bytes = new byte[size];
\r
1271 file.readFully(bytes);
\r
1272 comment = getComment(bytes);
\r
1273 if (null == comment)
\r
1274 comment = "<metadata does not contain comment>";
\r
1277 i = file.readInt();
\r
1278 int length2 = Integer.reverseBytes(i);
\r
1279 if (length != length2)
\r
1280 throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");
\r
1281 String command = MessageText.get(type);
\r
1283 line.request = command;
\r
1284 line.comment = comment;
\r
1285 } catch (IOException e) {
\r
1286 throw new ProCoreException("Journal file corrupted.");
\r
1289 private ArrayList<Long> readOffsets() throws ProCoreException {
\r
1296 long modified = journalFile.lastModified();
\r
1297 if (lastModified != 0 && lastModified == modified)
\r
1298 return offsets; // Offsets already up to date.
\r
1305 int i = file.readInt();
\r
1306 int version = Integer.reverseBytes(i);
\r
1308 throw new ProCoreException("Unsupported journal file version. expected=1 got=" + version);
\r
1309 i = file.readInt();
\r
1310 int major = Integer.reverseBytes(i);
\r
1311 if (major != MessageNumber.ProtocolVersionMajor)
\r
1312 throw new ProCoreException("Unsupported journal request major version. expected=" + MessageNumber.ProtocolVersionMajor + " got=" + major);
\r
1313 i = file.readInt();
\r
1314 int minor = Integer.reverseBytes(i);
\r
1315 if (minor > MessageNumber.ProtocolVersionMinor)
\r
1316 throw new ProCoreException("Unsupported journal request minor version. expected=" + MessageNumber.ProtocolVersionMinor + " got=" + minor);
\r
1317 i = file.readInt();
\r
1318 int length = Integer.reverseBytes(i);
\r
1319 while (length > 0) { // Not supporting unsigned integers.
\r
1321 throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");
\r
1322 file.skipBytes(length);
\r
1323 i = file.readInt();
\r
1324 int length2 = Integer.reverseBytes(i);
\r
1325 if (length != length2)
\r
1326 throw new ProCoreException("Journal file corrupted at " + file.getFilePointer() + ".");
\r
1327 long offset = file.getFilePointer() - length - 8;
\r
1328 offsets.add(offset);
\r
1329 i = file.readInt();
\r
1330 length = Integer.reverseBytes(i);
\r
1332 } catch (EOFException e) {
\r
1333 } catch (IOException e) {
\r
1334 throw new ProCoreException("Failed to get command count.", e);
\r
1338 lastModified = modified;
\r
1343 private final Analyzed analyzed;
\r
1344 JournalI(File dbFolder) {
\r
1345 this.analyzed = new Analyzed(dbFolder);
\r
1348 public boolean canRead() {
\r
1349 return analyzed.canRead() && !isServerAlive();
\r
1352 public int count() {
\r
1353 return analyzed.count();
\r
1356 public int read(int index, Line line) throws ProCoreException {
\r
1357 int count = analyzed.count();
\r
1358 analyzed.getRow(index, line);
\r
1363 public String getCompression() {
\r