]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.db.server/src/org/simantics/db/server/internal/DatabaseI.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.db.server / src / org / simantics / db / server / internal / DatabaseI.java
diff --git a/bundles/org.simantics.db.server/src/org/simantics/db/server/internal/DatabaseI.java b/bundles/org.simantics.db.server/src/org/simantics/db/server/internal/DatabaseI.java
new file mode 100644 (file)
index 0000000..9d03ee4
--- /dev/null
@@ -0,0 +1,1366 @@
+package org.simantics.db.server.internal;\r
+\r
+import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;\r
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;\r
+\r
+import java.io.EOFException;\r
+import java.io.File;\r
+import java.io.FilenameFilter;\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+import java.io.RandomAccessFile;\r
+import java.nio.file.DirectoryStream;\r
+import java.nio.file.FileSystems;\r
+import java.nio.file.FileVisitOption;\r
+import java.nio.file.FileVisitResult;\r
+import java.nio.file.Files;\r
+import java.nio.file.Path;\r
+import java.nio.file.PathMatcher;\r
+import java.nio.file.Paths;\r
+import java.nio.file.SimpleFileVisitor;\r
+import java.nio.file.attribute.BasicFileAttributes;\r
+import java.util.ArrayList;\r
+import java.util.EnumSet;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Properties;\r
+import java.util.TreeMap;\r
+import java.util.UUID;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.binding.impl.TreeMapBinding;\r
+import org.simantics.databoard.serialization.Serializer;\r
+import org.simantics.db.Database;\r
+import org.simantics.db.DatabaseUserAgent;\r
+import org.simantics.db.ServiceLocator;\r
+import org.simantics.db.common.CommentMetadata;\r
+import org.simantics.db.common.CommitMetadata;\r
+import org.simantics.db.common.utils.Logger;\r
+import org.simantics.db.common.utils.MetadataUtil;\r
+import org.simantics.db.exception.SDBException;\r
+import org.simantics.db.server.DatabaseLastExitException;\r
+import org.simantics.db.server.ProCoreException;\r
+import org.simantics.db.server.internal.ProCoreServer.TailFile;\r
+import org.simantics.db.server.protocol.MessageNumber;\r
+import org.simantics.db.server.protocol.MessageText;\r
+\r
+public class DatabaseI implements Database {\r
+    private static String NL = System.getProperty("line.separator");\r
+    private static String TEMP_PREFIX = "db.temp.";\r
+    public static DatabaseI newDatabaseI(File dbFolder) {\r
+        return new DatabaseI(dbFolder);\r
+    }\r
+    private final SessionManager sessionManager = new SessionManager();\r
+    private final Server server;\r
+    private final JournalI journal;\r
+    private DatabaseUserAgent dbUserAgent = null;\r
+    private DatabaseI(File dbFolder) {\r
+        server = new Server(dbFolder);\r
+        journal = new JournalI(dbFolder);\r
+    }\r
+    public boolean isSubFolder(Path base, Path sub)\r
+    throws IOException {\r
+        if (null == base || null == sub)\r
+            return false;\r
+        return isSubFolder(base.toFile(), sub.toFile());\r
+    }\r
+    public boolean isSubFolder(File base, File sub)\r
+    throws IOException {\r
+        if (null == base || null == sub)\r
+            return false;\r
+        Path basePath = base.getCanonicalFile().toPath();\r
+        Path subPath = sub.getCanonicalFile().toPath();\r
+        if (subPath.startsWith(basePath))\r
+            return true;\r
+        else\r
+            return false;\r
+    }\r
+    public Path saveDatabase()\r
+    throws ProCoreException {\r
+        Path dbFolder = getFolder().toPath();\r
+        Path parent = getFolder().getParentFile().toPath();\r
+        Path temp = createTempFolder(parent, "Could not create temporary directory for saving database.");\r
+        copyTree(dbFolder, temp, FileOption.IF_NEW);\r
+        return temp;\r
+    }\r
+    public Path saveDatabase(Path parent)\r
+    throws ProCoreException {\r
+        Path dbFolder = getFolder().toPath();\r
+        try {\r
+            boolean yes = isSubFolder(dbFolder, parent);\r
+            if (yes)\r
+                throw new ProCoreException("Parent must not be subdirectory of database directory."\r
+                        + NL + "database=" + dbFolder\r
+                        + NL + "parent=" + parent);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Failed to save database to parent."\r
+                    + NL + "database=" + dbFolder\r
+                    + NL + "parent=" + parent);\r
+        }\r
+        Path temp = createTempFolder(parent, "Could not create temporary directory for saving database.");\r
+        copyTree(dbFolder, temp, FileOption.IF_NEW);\r
+        return temp;\r
+    }\r
+    public void deleteDatabaseFiles() throws ProCoreException {\r
+        if (server.isAlive()) {\r
+            server.tryToStop();\r
+            if (server.isAlive())\r
+                throw new ProCoreException("Server must be dead to delete database.");\r
+        }\r
+        Path db = getFolder().toPath();\r
+        deleteDatabaseFiles(db); // Redundant, but tests the method.\r
+        deleteTree(db);\r
+    }\r
+    Path copyDatabaseFiles(Path from, Path to) throws ProCoreException {\r
+        copyTree(from, to, ProCoreServer.BRANCH_DIR, FileOption.IF_EXIST);\r
+        if (Files.exists(from.resolve(ProCoreServer.TAIL_DIR)))\r
+            copyTree(from, to, ProCoreServer.TAIL_DIR, FileOption.IF_EXIST);\r
+        copyPath(from, to, ProCoreServer.CONFIG_FILE, FileOption.IF_EXIST);\r
+        copyPath(from, to, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
+        copyPath(from, to, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);\r
+        copyPath(from, to, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);\r
+        copyFiles(from, to, ProCoreServer.PAGE_FILE_PATTERN, FileOption.IF_EXIST);\r
+        return to;\r
+    }\r
+    public static void deleteDatabaseFiles(Path from) throws ProCoreException {\r
+        deleteFile(from, ProCoreServer.DCS_FILE, FileOption.IF_EXIST);\r
+        deleteFile(from, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_EXIST);\r
+        deleteFile(from, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_EXIST);\r
+        deleteFile(from, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_EXIST);\r
+        deleteFile(from, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);\r
+        deleteFile(from, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
+        deleteFile(from, ProCoreServer.CONFIG_FILE, FileOption.IF_EXIST);\r
+        deleteFiles(from, ProCoreServer.PAGE_FILE_PATTERN);\r
+        deleteTree(from.resolve(ProCoreServer.BRANCH_DIR));\r
+        deleteFile(from, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);\r
+        deleteTree(from.resolve(ProCoreServer.TAIL_DIR));\r
+    }\r
+    Path moveDatabaseFiles(Path from, Path to) throws ProCoreException {\r
+        moveFolder(from, to, ProCoreServer.BRANCH_DIR, FileOption.IF_NEW);\r
+        if (Files.exists(from.resolve(ProCoreServer.TAIL_DIR)))\r
+            moveFolder(from, to, ProCoreServer.TAIL_DIR, FileOption.IF_NEW);\r
+        movePath(from, to, ProCoreServer.CONFIG_FILE, FileOption.IF_NEW);\r
+        movePath(from, to, ProCoreServer.GUARD_FILE, FileOption.IF_NEW);\r
+        movePath(from, to, ProCoreServer.JOURNAL_FILE, FileOption.IF_NEW);\r
+        movePath(from, to, ProCoreServer.LOG_FILE, FileOption.IF_NEW);\r
+        moveFiles(from, to, ProCoreServer.PAGE_FILE_PATTERN, FileOption.IF_NEW);\r
+        movePath(from, to, ProCoreServer.DCS_FILE, FileOption.IF_NEW);\r
+        moveFile(from, to, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_NEW);\r
+        moveFile(from, to, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_NEW);\r
+        moveFile(from, to, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_NEW);\r
+        return to;\r
+    }\r
+    private void moveFile(Path from, Path to, String file, FileOption fileOption) throws ProCoreException {\r
+        if (Files.exists(from.resolve(file)))\r
+            movePath(from, to, file, fileOption);\r
+    }\r
+    public void serverCreateConfiguration()  throws ProCoreException {\r
+        server.createConfiguration();\r
+    }\r
+    public boolean ignoreExit() throws ProCoreException {\r
+        if (!isFolderOk())\r
+            throw new ProCoreException("Folder must be valid database folder to ignore exit." );\r
+        server.deleteGuard();\r
+        File folder = server.getFolder();\r
+        Path db = Paths.get(folder.getAbsolutePath());\r
+        Path ri = db.resolve(ProCoreServer.RECOVERY_IGNORED_FILE);\r
+        if (!Files.exists(ri))\r
+            try (OutputStream os = Files.newOutputStream(ri)) {\r
+            } catch (IOException e) {\r
+                throw new ProCoreException("Could not create file: " + ri, e);\r
+            }\r
+        boolean ok = false;\r
+        DatabaseUserAgent dbu = dbUserAgent; // Save current user agent.\r
+        try {\r
+            dbUserAgent = null; // Recursion not supported. Disable user agent.\r
+            server.start();\r
+            ok = server.isActive();\r
+        } finally {\r
+            try {\r
+                dbUserAgent = dbu; // Restore used user agent.\r
+                server.stop();\r
+            } finally {\r
+                try {\r
+                    Files.deleteIfExists(ri);\r
+                } catch (IOException e) {\r
+                    Logger.defaultLogError("Could not delete file: " + ri, e);\r
+                }\r
+                Path rn = db.resolve(ProCoreServer.RECOVERY_NEEDED_FILE);\r
+                try {\r
+                    Files.deleteIfExists(rn);\r
+                } catch (IOException e) {\r
+                    Logger.defaultLogError("Could not delete file: " + rn, e);\r
+                }\r
+            }\r
+        }\r
+        return ok;\r
+    }\r
+    public boolean ignoreProtocol() throws ProCoreException {\r
+        if (!isFolderOk())\r
+            throw new ProCoreException("Folder must be valid database folder to ignore exit." );\r
+        File folder = server.getFolder();\r
+        Path db = Paths.get(folder.getAbsolutePath());\r
+        Path ri = db.resolve(ProCoreServer.PROTOCOL_IGNORED_FILE);\r
+        if (!Files.exists(ri))\r
+            try (OutputStream os = Files.newOutputStream(ri)) {\r
+            } catch (IOException e) {\r
+                throw new ProCoreException("Could not create file: " + ri, e);\r
+            }\r
+        boolean ok = false;\r
+        try {\r
+            server.start();\r
+            ok = server.isActive();\r
+        } finally {\r
+            server.stop();\r
+        }\r
+        return ok;\r
+    }\r
+    private long preJournalCheck() throws ProCoreException {\r
+        File folder = server.getFolder();\r
+        if (!folder.isDirectory())\r
+            throw new ProCoreException("Database folder does not exist." + NL + "folder=" + folder);\r
+        File file = new File(folder, ProCoreServer.JOURNAL_FILE);\r
+        if (!file.isFile())\r
+            throw new ProCoreException("Journal file does not exist." + NL + "file=" + file);\r
+        else if (!file.canRead())\r
+            throw new ProCoreException("Journal file must be readale to create database from journal." + NL + "file=" + file);\r
+        else if (server.isAlive())\r
+            throw new ProCoreException("Server must be dead to create database from journal file." + NL + "file=" + file);\r
+        return getNextClusterId(folder.toPath());\r
+    }\r
+    private void postJournalFix(long nextFreeId)  throws SDBException {\r
+        Session s = newSession(null);\r
+        long current =  s.reserveIds(0);\r
+        if (current < nextFreeId)\r
+            s.reserveIds((int)(nextFreeId - current));\r
+        s.isClosed();\r
+    }\r
+    private Path createTempFolder(Path parent, String fail) throws ProCoreException {\r
+        try {\r
+            return Files.createTempDirectory(parent, TEMP_PREFIX);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException(fail, e);\r
+        }\r
+    }\r
+    public void replaceFromJournal() throws SDBException {\r
+        long nextFreeId = preJournalCheck();\r
+        Path db = Paths.get(server.getFolder().getAbsolutePath());\r
+        Path temp = createTempFolder(db, "Could not create temporary directory for database to be created from journal.");\r
+        movePath(db, temp, ProCoreServer.CONFIG_FILE, FileOption.IF_NEW);\r
+        movePath(db, temp, ProCoreServer.JOURNAL_FILE, FileOption.IF_NEW);\r
+        deleteFile(db, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
+        deleteFiles(db, ProCoreServer.PAGE_FILE_PATTERN);\r
+        Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
+        deleteFiles(dbHead, ProCoreServer.VALUE_PATTERN);\r
+        deleteFiles(dbHead, ProCoreServer.DATA_PATTERN);\r
+        deleteFiles(dbHead, ProCoreServer.INDEX_PATTERN);\r
+        Path dbHeadTailFile = dbHead.resolve(ProCoreServer.TAIL_FILE);\r
+        boolean headTailFileExisted = Files.exists(dbHeadTailFile);\r
+        final long NEXT_REVISION = 1;\r
+        if (!headTailFileExisted) // Number of change sets and database id not known at this point. Fortunately they are not used by recovery.\r
+            TailFile.createTailFile(dbHeadTailFile.toFile(), NEXT_REVISION, nextFreeId, UUID.randomUUID().toString());\r
+        movePath(dbHead, temp, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);\r
+        Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);\r
+        boolean tailExisted = Files.isDirectory(dbTail);\r
+        if (tailExisted)\r
+            copyPath(dbTail, dbHead, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);\r
+        else {\r
+            try {\r
+                Files.createDirectory(dbTail);\r
+            } catch (IOException e) {\r
+                throw new ProCoreException("Failed to create directory: " + dbTail, e);\r
+            }\r
+            copyPath(temp, dbTail, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);\r
+        }\r
+        server.createConfiguration();\r
+        server.start();\r
+        try {\r
+            String t = server.execute("journalRead " + temp.getFileName());\r
+            if (t.length() > 0)\r
+                throw new ProCoreException("Could not read journal. reply=" + t);\r
+            postJournalFix(nextFreeId); // Not the right way for implementing this, but better than incorrect recovery.\r
+        } finally {\r
+            server.tryToStop();\r
+        }\r
+        movePath(temp, db, ProCoreServer.CONFIG_FILE, FileOption.OVERWRITE);\r
+        deleteTree(temp);\r
+        if (!tailExisted)\r
+            deleteTree(dbTail);\r
+    }\r
+    public boolean canPurge() throws ProCoreException {\r
+        File folder = server.getFolder();\r
+        Path db = Paths.get(folder.getAbsolutePath());\r
+        Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
+        if (!Files.exists(dbHead))\r
+            return false; // Already clean.\r
+        boolean empty = isFolderEmpty(dbHead);\r
+        if (empty)\r
+            return false; // Already clean.\r
+        final boolean[] found = new boolean[1];\r
+        found[0] = false;\r
+        dbHead.toFile().listFiles(new FilenameFilter() {\r
+            @Override\r
+            public boolean accept(File dir, String name) {\r
+                if (found[0])\r
+                    return false;\r
+                else if (!name.equals(ProCoreServer.TAIL_FILE)) {\r
+                    found[0] = true;\r
+                    return false;\r
+                }\r
+                return true;\r
+            }\r
+        });\r
+        return found[0];\r
+    }\r
+    /**\r
+     * Removes old history i.e. change sets.\r
+     * Note that also cleans last exit status and removes guard file.\r
+     * This means that all information about last exit status is also lost\r
+     * .\r
+     * @throws ProCoreException\r
+     */\r
+    public void purge() throws SDBException {\r
+        synchronized (server.proCoreServer.getProCoreClient()) {\r
+        boolean wasAlive = server.isAlive();\r
+        if (wasAlive)\r
+            server.stop();\r
+        Path db = Paths.get(server.getFolder().getAbsolutePath());\r
+        Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
+        Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);\r
+        if (!Files.isDirectory(dbTail)) {\r
+            try {\r
+                Files.createDirectory(dbTail);\r
+            } catch (IOException e) {\r
+                throw new ProCoreException("Failed to create directory: " + dbTail, e);\r
+            }\r
+        }\r
+        long nextFreeId = getNextClusterId(db);\r
+        boolean cleanHead = Files.isDirectory(dbHead) && !isFolderEmpty(dbHead);\r
+        Logger.defaultLog("Purging old history and exit information. folder=" + db);\r
+        if (cleanHead) {\r
+            deleteClusters(dbHead, dbTail);\r
+            movePath(dbHead, dbTail, ProCoreServer.TAIL_FILE, FileOption.OVERWRITE);\r
+            moveFiles(dbHead, dbTail, ProCoreServer.VALUE_PATTERN, FileOption.IF_NEW);\r
+            moveFiles(dbHead, dbTail, ProCoreServer.DATA_PATTERN, FileOption.OVERWRITE);\r
+            deleteFiles(dbHead, ProCoreServer.INDEX_PATTERN);\r
+        }\r
+        deleteFiles(db, ProCoreServer.PAGE_FILE_PATTERN);\r
+        deleteFile(db, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);\r
+        deleteFile(db, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);\r
+        deleteFile(db, ProCoreServer.DCS_FILE, FileOption.IF_EXIST);\r
+        deleteFile(db, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_EXIST);\r
+        deleteFile(db, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_EXIST);\r
+        deleteFile(db, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_EXIST);\r
+        deleteFile(db, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
+        purgeValues(dbTail);\r
+        server.start();\r
+        Session s = newSession(null);\r
+        long current =  s.reserveIds(0);\r
+        if (current < nextFreeId)\r
+            s.reserveIds((int)(nextFreeId - current));\r
+        s.isClosed();\r
+        if (!wasAlive)\r
+            server.tryToStop();\r
+        }\r
+    }\r
+    private void copyPath(Path fromFolder, Path toFolder, String file, FileOption fileOption) throws ProCoreException {\r
+        Path from = fromFolder.resolve(file);\r
+        Path to = toFolder.resolve(file);\r
+        try {\r
+            if (FileOption.IF_EXIST.equals(fileOption)) {\r
+                if (!Files.exists(from))\r
+                    return;\r
+            }\r
+            if (FileOption.OVERWRITE.equals(fileOption))\r
+                Files.copy(from, to, COPY_ATTRIBUTES, REPLACE_EXISTING);\r
+            else\r
+                Files.copy(from, to, COPY_ATTRIBUTES);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not copy " + from + " to " + to, e);\r
+        }\r
+    }\r
+    private static void deleteFile(Path from, String file, FileOption fileOption) throws ProCoreException {\r
+        Path path = from.resolve(file);\r
+        deletePath(path, fileOption);\r
+    }\r
+    private static void deletePath(Path path, FileOption fileOption) throws ProCoreException {\r
+        try {\r
+            if (FileOption.IF_EXIST.equals(fileOption))\r
+                Files.deleteIfExists(path);\r
+            else\r
+                Files.delete(path);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not delete " + path, e);\r
+        }\r
+    }\r
+    private boolean isFolderEmpty(final Path folder) { // True if folder exists and is empty.\r
+        if (!Files.isDirectory(folder))\r
+            return false;\r
+        try(DirectoryStream<Path> folderStream = Files.newDirectoryStream(folder)) {\r
+            return !folderStream.iterator().hasNext();\r
+        } catch (IOException e) {\r
+            Logger.defaultLogError("Failed to open folder stream. folder=" + folder, e);\r
+            return false;\r
+        }\r
+    }\r
+    private void movePath(Path fromPath, Path toPath, String file, FileOption fileOption) throws ProCoreException {\r
+        Path from = fromPath.resolve(file);\r
+        Path to = toPath.resolve(file);\r
+        try {\r
+            if (FileOption.OVERWRITE.equals(fileOption))\r
+                Files.move(from, to, REPLACE_EXISTING);\r
+            else\r
+                Files.move(from, to);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not move " + from + " to " + to, e);\r
+        }\r
+    }\r
+    private static void copyTree(Path from, Path to, String path, final FileOption fileOption) throws ProCoreException {\r
+        copyTree(from.resolve(path), to.resolve(path), fileOption);\r
+    }\r
+    private static void copyTree(Path from, Path to, final FileOption fileOption) throws ProCoreException {\r
+        class Visitor extends SimpleFileVisitor<Path> {\r
+            private Path fromPath;\r
+            private Path toPath;\r
+            Visitor(Path from, Path to) {\r
+                fromPath = from;\r
+                toPath = to;\r
+            }\r
+            @Override\r
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+                Path targetPath = toPath.resolve(fromPath.relativize(dir));\r
+                if (!Files.exists(targetPath)) {\r
+                    Files.createDirectory(targetPath);\r
+                }\r
+                return FileVisitResult.CONTINUE;\r
+            }\r
+            @Override\r
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+                if (FileOption.OVERWRITE.equals(fileOption))\r
+                    Files.copy(file, toPath.resolve(fromPath.relativize(file)), COPY_ATTRIBUTES, REPLACE_EXISTING);\r
+                else\r
+                    Files.copy(file, toPath.resolve(fromPath.relativize(file)), COPY_ATTRIBUTES);\r
+                return FileVisitResult.CONTINUE;\r
+            }\r
+        }\r
+        try {\r
+            Visitor v = new Visitor(from, to);\r
+            EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+            Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not copy " + from + " to " + to, e);\r
+        }\r
+    }\r
+    private static void deleteTree(Path path) throws ProCoreException {\r
+        if (!Files.exists(path))\r
+            return;\r
+        class Visitor extends SimpleFileVisitor<Path> {\r
+            @Override\r
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+                try {\r
+                    Files.delete(file);\r
+                } catch (IOException ioe) {\r
+                    ioe.printStackTrace();\r
+                    throw ioe;\r
+                }\r
+                return FileVisitResult.CONTINUE;\r
+            }\r
+            @Override\r
+            public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {\r
+                if (e == null) {\r
+                    try {\r
+                        Files.delete(dir);\r
+                    } catch (IOException ioe) {\r
+                        ioe.printStackTrace();\r
+                        throw ioe;\r
+                    }\r
+                    return FileVisitResult.CONTINUE;\r
+                }\r
+                throw e;\r
+            }\r
+        }\r
+        try {\r
+            Visitor v = new Visitor();\r
+            EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+            Files.walkFileTree(path, opts, Integer.MAX_VALUE, v);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not delete " + path, e);\r
+        }\r
+    }\r
+    private static void moveFolder(Path fromPath, Path toPath, String folder, FileOption fileOption) throws ProCoreException {\r
+        Path from = fromPath.resolve(folder);\r
+        Path to = toPath.resolve(folder);\r
+        copyTree(from, to, fileOption);\r
+        deleteTree(from);\r
+    }\r
+    enum FileOption {\r
+        IF_EXIST,\r
+        IF_NEW,\r
+        OVERWRITE\r
+    }\r
+    private void copyFiles(final Path from, final Path to, String pattern, final FileOption fileOption) throws ProCoreException {\r
+        class Visitor extends SimpleFileVisitor<Path> {\r
+            private final PathMatcher matcher;\r
+            Visitor(String pattern) {\r
+                matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
+            }\r
+            @Override\r
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+                Path name = file.getFileName();\r
+                if (null == name)\r
+                    return FileVisitResult.CONTINUE;\r
+                else if (!matcher.matches(name))\r
+                    return FileVisitResult.CONTINUE;\r
+                if (FileOption.OVERWRITE.equals(fileOption))\r
+                    Files.copy(file, to.resolve(name), REPLACE_EXISTING);\r
+                else\r
+                    Files.copy(file, to.resolve(name));\r
+                return FileVisitResult.CONTINUE;\r
+            }\r
+            @Override\r
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+                if (dir.equals(from))\r
+                    return FileVisitResult.CONTINUE;\r
+                else\r
+                    return FileVisitResult.SKIP_SUBTREE;\r
+            }\r
+        }\r
+        try {\r
+            Visitor v = new Visitor(pattern);\r
+            EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+            Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not copy " + from + " to " + to, e);\r
+        }\r
+    }\r
+    private void moveFiles(final Path from, final Path to, String pattern, final FileOption fileOption) throws ProCoreException {\r
+        class Visitor extends SimpleFileVisitor<Path> {\r
+            private final PathMatcher matcher;\r
+            Visitor(String pattern) {\r
+                matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
+            }\r
+            @Override\r
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+                Path name = file.getFileName();\r
+                if (null == name)\r
+                    return FileVisitResult.CONTINUE;\r
+                else if (!matcher.matches(name))\r
+                    return FileVisitResult.CONTINUE;\r
+                if (FileOption.OVERWRITE.equals(fileOption))\r
+                    Files.move(file, to.resolve(name), REPLACE_EXISTING);\r
+                else\r
+                    Files.move(file, to.resolve(name));\r
+                return FileVisitResult.CONTINUE;\r
+            }\r
+            @Override\r
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+                if (dir.equals(from))\r
+                    return FileVisitResult.CONTINUE;\r
+                else\r
+                    return FileVisitResult.SKIP_SUBTREE;\r
+            }\r
+        }\r
+        try {\r
+            Visitor v = new Visitor(pattern);\r
+            EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+            Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not move " + from + " to " + to, e);\r
+        }\r
+    }\r
+    private void deleteClusters(final Path head, final Path tail) throws ProCoreException {\r
+        class Visitor extends SimpleFileVisitor<Path> {\r
+            private final PathMatcher matcher;\r
+            Visitor(String pattern) {\r
+                matcher = FileSystems.getDefault().getPathMatcher("glob:" + ProCoreServer.DELETED_PATTERN);\r
+            }\r
+            @Override\r
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+                Path name = file.getFileName();\r
+                if (null == name)\r
+                    return FileVisitResult.CONTINUE;\r
+                else if (!matcher.matches(name))\r
+                    return FileVisitResult.CONTINUE;\r
+                String deletedStr = name.toString();\r
+                String indexName = deletedStr.replaceFirst(ProCoreServer.DELETED_PREFIX, ProCoreServer.INDEX_PREFIX);\r
+                String dataName = deletedStr.replaceFirst(ProCoreServer.DELETED_PREFIX, ProCoreServer.DATA_PREFIX);\r
+                String[] ss = deletedStr.split("\\.");\r
+                String valuePattern = ProCoreServer.VALUE_PREFIX + ss[1] + "." + ss[2] + "*";\r
+                Files.delete(file);\r
+                Files.delete(head.resolve(indexName));\r
+                Files.delete(head.resolve(dataName));\r
+                try {\r
+                    deleteFiles(head, valuePattern);\r
+                    if (Files.isDirectory(tail)) {\r
+                        Files.deleteIfExists(tail.resolve(dataName));\r
+                        deleteFiles(tail, valuePattern);\r
+                    }\r
+                } catch (ProCoreException e) {\r
+                    throw new IOException("Delete values failed.", e);\r
+                }\r
+                return FileVisitResult.CONTINUE;\r
+            }\r
+            @Override\r
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+                if (dir.equals(head))\r
+                    return FileVisitResult.CONTINUE;\r
+                else\r
+                    return FileVisitResult.SKIP_SUBTREE;\r
+            }\r
+        }\r
+        try {\r
+            Visitor v = new Visitor(ProCoreServer.DELETED_PREFIX);\r
+            EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+            Files.walkFileTree(head, opts, Integer.MAX_VALUE, v);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not delete cluster(s).\nhead=" + head + "\ntail=" + tail);\r
+        }\r
+    }\r
+    private static long convertHexStringToLong(String hex) {\r
+        if (hex.length() < 16)\r
+            return Long.parseLong(hex, 16);\r
+        long f = Long.parseLong(hex.substring(0,1), 16) << 60;\r
+        long l = Long.parseLong(hex.substring(1,16), 16);\r
+        return f | l;\r
+    }\r
+    private void purgeValues(final Path folder) throws ProCoreException {\r
+        final class Value {\r
+            long first; // First part of cluster id.\r
+            long second; // Secon part of cluster id.\r
+            int index; // Resource index of the value.\r
+            transient long cs; // Change set of the value.\r
+            public Value(long first, long second, int index, long cs) {\r
+                this.first = first;\r
+                this.second = second;\r
+                this.index = index;\r
+                this.cs = cs;\r
+            }\r
+            String getName() {\r
+                return ProCoreServer.VALUE_PREFIX + toString() + ProCoreServer.VALUE_SUFFIX;\r
+            }\r
+            @Override\r
+            public String toString() {\r
+                return String.format("%x.%x.%d.%d", first, second, index, cs);\r
+            }\r
+            @Override\r
+            public boolean equals(Object o) {\r
+                if (this == o)\r
+                    return true;\r
+                else if (!(o instanceof Value))\r
+                    return false;\r
+                Value x = (Value)o;\r
+                return first == x.first && second == x.second && index == x.index;\r
+            }\r
+            @Override\r
+            public int hashCode() {\r
+                int result = 17;\r
+                int f = (int)(first ^ (first >>> 32));\r
+                result = 31 * result + f;\r
+                int s = (int)(second ^ (second >>> 32));\r
+                result = 31 * result + s;\r
+                return result + index;\r
+            }\r
+        }\r
+        File[] files = folder.toFile().listFiles(new FilenameFilter() {\r
+            @Override\r
+            public boolean accept(File dir, String name) {\r
+                return name.startsWith(ProCoreServer.VALUE_PREFIX);\r
+            }\r
+        });\r
+        HashMap<Value, Value> values = new HashMap<Value, Value>();\r
+        for (int i = 0; i < files.length; ++i) {\r
+            String s = files[i].getName();\r
+            String[] ss = s.split("\\.");\r
+            if (ss.length != 6) {\r
+                Logger.defaultLogError("Illegal external value file name. name=" + s);\r
+                continue;\r
+            }\r
+            long first = convertHexStringToLong(ss[1]);\r
+            long second = convertHexStringToLong(ss[2]);\r
+            int ri = Integer.parseInt(ss[3]);\r
+            long cs = Long.parseLong(ss[4]);\r
+            Value nv = new Value(first, second, ri, cs);\r
+            Value ov = values.get(nv);\r
+            if (null == ov)\r
+                values.put(nv, nv);\r
+            else if (ov.cs < nv.cs) {\r
+                deleteFile(folder, ov.getName(), FileOption.IF_EXIST);\r
+                ov.cs = nv.cs;;\r
+            } else\r
+                deleteFile(folder, nv.getName(), FileOption.IF_EXIST);\r
+        }\r
+    }\r
+    private long getNextClusterId(final Path db) throws ProCoreException {\r
+        long clusterId = 0;\r
+        Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
+        File[] files = dbHead.toFile().listFiles(new FilenameFilter() {\r
+            @Override\r
+            public boolean accept(File dir, String name) {\r
+                return name.startsWith(ProCoreServer.DATA_PREFIX);\r
+            }\r
+        });\r
+        for (int i = 0; i < files.length; ++i) {\r
+            String s = files[i].getName();\r
+            String[] ss = s.split("\\.", 4);\r
+            if (ss.length != 4) {\r
+                Logger.defaultLogError("Illegal cluster file name. name=" + s);\r
+                continue;\r
+            }\r
+            long id = convertHexStringToLong(ss[2]);\r
+            if (id > clusterId)\r
+                clusterId = id;\r
+        }\r
+        final Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);\r
+        if (!Files.exists(dbTail))\r
+            return clusterId + 1;\r
+        class Visitor extends SimpleFileVisitor<Path> {\r
+            private final PathMatcher matcher;\r
+            long clusterId;\r
+            Visitor(String pattern, long clusterId) {\r
+                this.clusterId = clusterId;\r
+                matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
+            }\r
+            @Override\r
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+                Path name = file.getFileName();\r
+                if (null == name)\r
+                    return FileVisitResult.CONTINUE;\r
+                else if (!matcher.matches(name))\r
+                    return FileVisitResult.CONTINUE;\r
+                String s = name.toString();\r
+                String[] ss = s.split("\\.", 4);\r
+                if (ss.length != 4) {\r
+                    Logger.defaultLogError("Illegal cluster file name. name=" + s);\r
+                    return FileVisitResult.CONTINUE;\r
+                }\r
+                long id = convertHexStringToLong(ss[2]);\r
+                if (id > clusterId)\r
+                    clusterId = id;\r
+                return FileVisitResult.CONTINUE;\r
+            }\r
+            @Override\r
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+                if (dir.equals(dbTail))\r
+                    return FileVisitResult.CONTINUE;\r
+                else\r
+                    return FileVisitResult.SKIP_SUBTREE;\r
+            }\r
+        }\r
+        try {\r
+            Visitor v = new Visitor(ProCoreServer.DATA_PATTERN, clusterId);\r
+            EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+            Files.walkFileTree(dbTail, opts, Integer.MAX_VALUE, v);\r
+            return v.clusterId + 1;\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not get next free cluster id for " + db, e);\r
+        }\r
+    }\r
+    private static void deleteFiles(final Path folder, String pattern) throws ProCoreException {\r
+        if (!Files.exists(folder))\r
+            return;\r
+        class Visitor extends SimpleFileVisitor<Path> {\r
+            private final PathMatcher matcher;\r
+            Visitor(String pattern) {\r
+                matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
+            }\r
+            @Override\r
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+                Path name = file.getFileName();\r
+                if (null == name)\r
+                    return FileVisitResult.CONTINUE;\r
+                else if (!matcher.matches(name))\r
+                    return FileVisitResult.CONTINUE;\r
+                Files.delete(file);\r
+                return FileVisitResult.CONTINUE;\r
+            }\r
+            @Override\r
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+                if (dir.equals(folder))\r
+                    return FileVisitResult.CONTINUE;\r
+                else\r
+                    return FileVisitResult.SKIP_SUBTREE;\r
+            }\r
+        }\r
+        try {\r
+            Visitor v = new Visitor(pattern);\r
+            EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+            Files.walkFileTree(folder, opts, Integer.MAX_VALUE, v);\r
+        } catch (IOException e) {\r
+            throw new ProCoreException("Could not delete " + folder + " with " + pattern, e);\r
+        }\r
+    }\r
+    public void copy(File to) throws ProCoreException {\r
+        copyDatabaseFiles(server.getFolder().toPath(), to.toPath());\r
+    }\r
+    public boolean isServerAlive() {\r
+        return server.isAlive();\r
+    }\r
+    Client newClient() throws ProCoreException {\r
+        return server.newClient();\r
+    }\r
+    public void createFolder() throws ProCoreException {\r
+        if (server.folder.exists())\r
+            return;\r
+        boolean created = server.folder.mkdirs();\r
+        if (!created)\r
+            throw new ProCoreException("Could not create folder=" + server.folder);\r
+    }\r
+    /**\r
+     * @throws ProCoreException if could not stop server.\r
+     */\r
+    public boolean serverTryToStop() throws ProCoreException {\r
+        return server.tryToStop();\r
+    }\r
+    class Server {\r
+        class Command {\r
+            void Do(ProCoreServer proCoreServer) {\r
+            }\r
+        }\r
+        class Event {\r
+        }\r
+        private final File folder; // Always non null.\r
+        private final ProCoreServer proCoreServer;\r
+        void start()  throws ProCoreException {\r
+            try {\r
+                proCoreServer.start();\r
+            } catch (DatabaseLastExitException e) {\r
+                if (null == dbUserAgent)\r
+                    return;\r
+                if (!dbUserAgent.handleStart(e))\r
+                    throw new ProCoreException(folder, "Failed to handle start exception.", e);\r
+                proCoreServer.start();\r
+            }\r
+        }\r
+        void stop() throws ProCoreException {\r
+            try {\r
+                proCoreServer.stop();\r
+            } catch (InterruptedException e) {\r
+                if (proCoreServer.isAlive())\r
+                    throw new ProCoreException("ProCoreServer stop was interrupted.", e);\r
+            }\r
+        }\r
+        boolean isAlive() {\r
+            try {\r
+                return proCoreServer.isAlive();\r
+            } catch (ProCoreException e) {\r
+                Logger.defaultLogError(e);\r
+                return false;\r
+            }\r
+        }\r
+        Server(File aFolder) {\r
+            if (null == aFolder)\r
+                throw new RuntimeException("Database folder can not be null.");\r
+            try {\r
+                folder = aFolder.getCanonicalFile();\r
+            } catch (IOException e) {\r
+                String t = "Could not get canonical path. file=" + aFolder;\r
+                Logger.defaultLogError(t);\r
+                throw new RuntimeException(t);\r
+            }\r
+            try {\r
+                File serverFolder = org.simantics.db.server.internal.Activator.getServerFolder();\r
+                if (!folder.isDirectory())\r
+                    folder.mkdirs();\r
+                proCoreServer = ProCoreServer.getProCoreServer(serverFolder, folder);\r
+                return;\r
+            } catch (Throwable t) {\r
+                Logger.defaultLogError(t);\r
+                throw new RuntimeException(t);\r
+            }\r
+        }\r
+        void createConfiguration() throws ProCoreException {\r
+            if (!folder.isDirectory())\r
+                throw new ProCoreException("Can't create configuration because folder is not ok. folder=" + folder.getAbsolutePath());\r
+            File file = new File(folder, ProCoreServer.CONFIG_FILE);\r
+            try {\r
+                file.createNewFile();\r
+            } catch (IOException e) {\r
+                throw new ProCoreException("Can't create configuration file. file=" + file.getAbsolutePath());\r
+            }\r
+        }\r
+        void deleteGuard() throws ProCoreException {\r
+            if (server.isActive())\r
+                throw new ProCoreException("Will not delete guard file when server is alive.");\r
+            if (!server.isFolderOk(server.getFolder()))\r
+                throw new ProCoreException("Will not delete guard file when server folder is not ok. folder=" + server.getFolder());\r
+            File file = new File(folder, ProCoreServer.GUARD_FILE);\r
+            if (!file.exists())\r
+                throw new ProCoreException("Guard file does not exist. file=" + file.getAbsolutePath());\r
+            boolean deleted = file.delete();\r
+            if (!deleted)\r
+                throw new ProCoreException("Failed to delete file=" + file.getAbsolutePath());\r
+        }\r
+        String execute(String aCommand) throws ProCoreException {\r
+            try {\r
+                return proCoreServer.execute(aCommand);\r
+            } catch (InterruptedException e) {\r
+                throw new ProCoreException("Execute call was interrupted.", e);\r
+            }\r
+        }\r
+        Status getStatus() {\r
+            Status status = Status.NoDatabase;\r
+            String path;\r
+            try {\r
+                path = folder.getCanonicalPath();\r
+            } catch (IOException e) {\r
+                Util.logError("Could not get canonical path for folder. folder=" + folder.getAbsolutePath(), e);\r
+                path = "<no path>";\r
+            }\r
+            if (!isFolderOk(folder))\r
+                status = Status.NoDatabase;\r
+            else if (!isAlive())\r
+                status = Status.NotRunning;\r
+            else if (isConnected()) {\r
+                if (isLocal())\r
+                    status = Status.Local;\r
+                else\r
+                    status = Status.Remote;\r
+            } else\r
+                status = Status.Standalone;\r
+            status.message = status.getString() + "@" + path;\r
+            return status;\r
+        }\r
+        File getFolder() {\r
+            return folder;\r
+        }\r
+        boolean isActive() {\r
+            try {\r
+                return proCoreServer.isActive();\r
+            } catch (ProCoreException e) {\r
+                Logger.defaultLogError("IsActive failed.", e);\r
+                return false;\r
+            }\r
+        }\r
+        boolean isConnected() {\r
+            try {\r
+                return proCoreServer.isConnected();\r
+            } catch (ProCoreException e) {\r
+                Logger.defaultLogError("IsConnected failed.", e);\r
+                return false;\r
+            }\r
+        }\r
+        boolean isLocal() {\r
+            try {\r
+                return proCoreServer.isLocal();\r
+            } catch (ProCoreException e) {\r
+                Logger.defaultLogError("IsLocal faailed.", e);\r
+                return false;\r
+            }\r
+        }\r
+        void connect() throws ProCoreException, InterruptedException {\r
+            proCoreServer.connect();\r
+        }\r
+        void disconnect() {\r
+            try {\r
+                proCoreServer.disconnect();\r
+            } catch (ProCoreException e) {\r
+                Logger.defaultLogError("Could not disconnect.", e);\r
+            }\r
+        }\r
+        /**\r
+         * @param aFolder\r
+         * @return true if given folder contains database journal file or\r
+         *         configuration file.\r
+         */\r
+        boolean isFolderOk(File aFolder) {\r
+            if (!aFolder.isDirectory())\r
+                return false;\r
+            File config = new File(aFolder, ProCoreServer.CONFIG_FILE);\r
+            if (config.exists())\r
+                return true;\r
+            File journal = new File(aFolder, ProCoreServer.JOURNAL_FILE);\r
+            if (journal.exists())\r
+                return true;\r
+            return false;\r
+        }\r
+        /**\r
+         * @throws ProCoreException if could not stop server.\r
+         */\r
+        boolean tryToStop() throws ProCoreException {\r
+            return proCoreServer.tryToStop();\r
+        }\r
+        Client newClient() throws ProCoreException {\r
+            return proCoreServer.newClient();\r
+        }\r
+    }\r
+// From interface Database\r
+    @Override\r
+    public void initFolder(Properties properties) throws ProCoreException {\r
+        createFolder();\r
+        serverCreateConfiguration();\r
+    }\r
+    @Override\r
+    public void deleteFiles() throws ProCoreException {\r
+        deleteDatabaseFiles();\r
+    }\r
+    @Override\r
+    public void start() throws ProCoreException {\r
+        connect();\r
+    }\r
+    @Override\r
+    public boolean isRunning() throws ProCoreException {\r
+        return isServerAlive();\r
+    }\r
+    @Override\r
+    public boolean tryToStop() throws ProCoreException {\r
+        return serverTryToStop();\r
+    }\r
+    @Override\r
+    public String execute(String aCommand) throws ProCoreException {\r
+        return server.execute(aCommand);\r
+    }\r
+    @Override\r
+    public void purgeDatabase() throws SDBException {\r
+        synchronized (server.proCoreServer.getProCoreClient()) {\r
+            if (!server.isLocal())\r
+                throw new ProCoreException("Purge is allowed only for local server.");\r
+            List<Client> clients = sessionManager.disconnect(this);\r
+            purge();\r
+            try {\r
+                sessionManager.connect(this, clients);\r
+            } catch (InterruptedException e) {\r
+                throw new ProCoreException("Failed to connect after purge.", e);\r
+            }\r
+        }\r
+    }\r
+    @Override\r
+    public Session newSession(ServiceLocator locator) throws ProCoreException {\r
+        return sessionManager.newSession(this);\r
+    }\r
+    @Override\r
+    public Path createFromChangeSets(int revision) throws ProCoreException {\r
+        if (!isFolderOk())\r
+            throw new ProCoreException("Folder must be valid database folder to create database from journal." );\r
+        File folder = server.getFolder();\r
+        File file = new File(folder, ProCoreServer.DCS_FILE);\r
+        if (!file.isFile() && !file.canRead())\r
+            throw new ProCoreException("Dump file must be readable. file=" + file.getAbsolutePath());\r
+        Path db = Paths.get(folder.getAbsolutePath());\r
+        Path temp = createTempFolder(db, "Could not create temporary directory for database to be created from change sets.");\r
+        Server s = new Server(temp.toFile());\r
+        s.createConfiguration();\r
+        s.start();\r
+        String t = s.execute("loadChangeSets .. " + revision);\r
+        if (t.length() < 1)\r
+            throw new ProCoreException("Could not read journal. reply=" + t);\r
+        s.tryToStop();\r
+        try {\r
+            int cs = Integer.parseInt(t);\r
+            if (cs == revision)\r
+                return temp;\r
+            throw new ProCoreException("Could not load change sets. reply=" + t);\r
+        } catch (NumberFormatException e) {\r
+            throw new ProCoreException("Could not load change sets. reply=" + t);\r
+        }\r
+    }\r
+    @Override\r
+    public void clone(File to, int revision, boolean saveHistory) {\r
+        String history = saveHistory ? "with history." : "without history.";\r
+        String message = "Clone to " + to.getAbsolutePath() + "@" + revision + " " + history;\r
+        Util.trace(message);\r
+    }\r
+    @Override\r
+    public DatabaseUserAgent getUserAgent() {\r
+        return dbUserAgent;\r
+    }\r
+    @Override\r
+    public void setUserAgent(DatabaseUserAgent dbUserAgent) {\r
+        this.dbUserAgent = dbUserAgent;\r
+    }\r
+    @Override\r
+    public Status getStatus() {\r
+        return server.getStatus();\r
+    }\r
+    @Override\r
+    public File getFolder() {\r
+        return server.getFolder();\r
+    }\r
+    @Override\r
+    public boolean isFolderOk() {\r
+        return server.isFolderOk(server.getFolder());\r
+    }\r
+    @Override\r
+    public boolean isFolderOk(File folder) {\r
+        return server.isFolderOk(folder);\r
+    }\r
+    public boolean isFolderEmpty() {\r
+        return isFolderEmpty(server.getFolder());\r
+    }\r
+    @Override\r
+    public boolean isFolderEmpty(File folder) {\r
+        return isFolderEmpty(folder.toPath());\r
+    }\r
+    @Override\r
+    public void deleteGuard() throws ProCoreException {\r
+        server.deleteGuard();\r
+    }\r
+    @Override\r
+    public boolean isConnected() throws ProCoreException {\r
+        return server.isConnected();\r
+    }\r
+    @Override\r
+    public void connect() throws ProCoreException {\r
+        if (!isFolderOk())\r
+            throw new ProCoreException("Could not connect to " + getFolder());\r
+        if (!server.isAlive())\r
+            server.start();\r
+        if (isConnected())\r
+            return;\r
+        try {\r
+            server.connect();\r
+        } catch (InterruptedException e) {\r
+            Util.logError("Server connect was interrupted.", e);\r
+        }\r
+        if (server.isActive())\r
+            return;\r
+        throw new ProCoreException("Could not connect to " + getFolder());\r
+    }\r
+    @Override\r
+    public void disconnect() throws ProCoreException {\r
+        server.disconnect();\r
+    }\r
+    @Override\r
+    public Path dumpChangeSets() throws ProCoreException {\r
+        if (!isFolderOk())\r
+            throw new ProCoreException("Folder must be set to dump change sets.");\r
+        if (!server.isActive())\r
+            throw new  ProCoreException("Server must be responsive to dump change sets.");\r
+        String t = server.execute("dumpChangeSets").replaceAll("\n", "");\r
+        try {\r
+            int ncs = Integer.parseInt(t);\r
+            if (ncs < 1)\r
+                return null;\r
+            File file = new File(getFolder(), ProCoreServer.DCS_FILE);\r
+            return file.toPath();\r
+        } catch (NumberFormatException e) {\r
+            throw new ProCoreException("Could not dump change sets.", e);\r
+        }\r
+    }\r
+    @Override\r
+    public long serverGetTailChangeSetId() throws ProCoreException {\r
+        try {\r
+            return server.proCoreServer.getTailData().nextChangeSetId;\r
+        } catch (TailReadException e) {\r
+            return 1;\r
+        }\r
+    }\r
+    @Override\r
+    public JournalI getJournal() throws ProCoreException {\r
+        return journal;\r
+    }\r
+    class JournalI implements Database.Journal {\r
+        class Analyzed {\r
+            private final File journalFile;\r
+            private long lastModified;\r
+            private RandomAccessFile file; // Journal file.\r
+            private ArrayList<Long> offsets = new ArrayList<Long>(); // Offsets to commands in journal file.\r
+            private Line lines[] = new Line[256];\r
+            int firstLine = 0; // Index of the first element in lines table.\r
+            Analyzed(File dbFolder) {\r
+                journalFile = new File(dbFolder, ProCoreServer.JOURNAL_FILE);\r
+                for (int i=0; i<lines.length; ++i)\r
+                    lines[i] = new Line();\r
+            }\r
+            private boolean canRead() {\r
+                return journalFile.isFile() && journalFile.canRead();\r
+            }\r
+            private int count() {\r
+                try {\r
+                    return readOffsets().size();\r
+                } catch (ProCoreException e) {\r
+                    Util.logError("Failed to read or analyze journal. file=" + journalFile, e);\r
+                    clear();\r
+                    return 0;\r
+                }\r
+            }\r
+            private void clear() {\r
+                close();\r
+                offsets.clear();\r
+            }\r
+            private void close() {\r
+                if (file != null) {\r
+                    try {\r
+                        file.close();\r
+                    } catch (Throwable e) {\r
+                        Logger.defaultLogError("Close file threw an exception.", e);\r
+                    }\r
+                    file = null;\r
+                }\r
+            }\r
+            private void open() throws ProCoreException {\r
+                try {\r
+                    file = new RandomAccessFile(journalFile, "r");\r
+                } catch (Throwable e) {\r
+                    file = null;\r
+                    throw new ProCoreException("Failed to open journal file.", e);\r
+                }\r
+            }\r
+            private String getComment(byte[] bytes) throws ProCoreException {\r
+                Serializer METADATA_SERIALIZER = Bindings.getSerializerUnchecked(new TreeMapBinding(Bindings.STRING, Bindings.BYTE_ARRAY));\r
+                try {\r
+                    @SuppressWarnings("unchecked")\r
+                    TreeMap<String, byte[]> metadata = (TreeMap<String, byte[]>) METADATA_SERIALIZER.deserialize(bytes);\r
+                    CommitMetadata commit = MetadataUtil.getMetadata(metadata, CommitMetadata.class);\r
+                    String date = "<metadata does not contain date>";\r
+                    if (null != commit  && null != commit.date)\r
+                        date = commit.date.toString();\r
+                    CommentMetadata comment = MetadataUtil.getMetadata(metadata, CommentMetadata.class);\r
+                    String comments = "<metadata does not contain comment>";\r
+                    if (null != comment)\r
+                        comments = comment.toString();\r
+                    return date + " " + comments;\r
+                } catch (IOException e) {\r
+                    throw new ProCoreException("Failed to interpret metadata.", e);\r
+                }\r
+            }\r
+            private Line getRow(int index, Line line) throws ProCoreException {\r
+                if (index < 0 || index >= offsets.size())\r
+                    return null; // Index out of range.\r
+                if (index < firstLine || index >= firstLine + lines.length) {\r
+                    readLines(index);\r
+                    if (index < firstLine)\r
+                        return null; // Index out of range.\r
+                }\r
+                int offset = index - firstLine;\r
+                line.status = lines[offset].status;\r
+                line.request = lines[offset].request;\r
+                line.comment = lines[offset].comment;\r
+                return line;\r
+            }\r
+            private void readLines(int first) throws ProCoreException {\r
+                open();\r
+                try {\r
+                    for (int i=0, index = first; i<lines.length; ++i, ++index)\r
+                        readLine(index, lines[i]);\r
+                    firstLine = first;\r
+                } finally {\r
+                    close();\r
+                }\r
+            }\r
+            private void readLine(int index, Line line) throws ProCoreException {\r
+                if (index >= offsets.size()) {\r
+                    line.status = false;\r
+                    line.request = "<Illegal request.>";\r
+                    line.comment = "<Illegal request.>";\r
+                    return;\r
+                }\r
+                long offset = offsets.get(index);\r
+                try {\r
+                    file.seek(offset);\r
+                    int i = file.readInt();\r
+                    int length = Integer.reverseBytes(i);\r
+                    byte b = file.readByte();\r
+                    boolean ok = b != 0;\r
+                    b = file.readByte();\r
+                    // boolean littleEndian = b != 0;\r
+                    i = file.readInt();\r
+                    int type = Integer.reverseBytes(i);\r
+                    String comment = "";\r
+                    if (length < 6)\r
+                        throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");\r
+                    else if (length > 6) {\r
+                        if (type != 3)\r
+                            file.skipBytes(length - 6);\r
+                        else if (length > 22){\r
+                            file.skipBytes(16);\r
+                            i = file.readInt();\r
+                            int size = Integer.reverseBytes(i);\r
+                            if (size != length - 26)\r
+                                throw new ProCoreException("Metadata corrupted at" + file.getFilePointer() + ".");\r
+                            byte[] bytes = new byte[size];\r
+                            file.readFully(bytes);\r
+                            comment = getComment(bytes);\r
+                            if (null == comment)\r
+                                comment = "<metadata does not contain comment>";\r
+                        }\r
+                    }\r
+                    i = file.readInt();\r
+                    int length2 = Integer.reverseBytes(i);\r
+                    if (length != length2)\r
+                        throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");\r
+                    String command = MessageText.get(type);\r
+                    line.status = ok;\r
+                    line.request = command;\r
+                    line.comment = comment;\r
+                } catch (IOException e) {\r
+                    throw new ProCoreException("Journal file corrupted.");\r
+                }\r
+            }\r
+            private ArrayList<Long> readOffsets() throws ProCoreException {\r
+                if (!canRead()) {\r
+                    lastModified = 0;\r
+                    offsets.clear();\r
+                    firstLine = 0;\r
+                    return offsets;\r
+                }\r
+                long modified = journalFile.lastModified();\r
+                if (lastModified != 0 && lastModified == modified)\r
+                    return offsets; // Offsets already up to date.\r
+                lastModified = 0;\r
+                offsets.clear();\r
+                firstLine = 0;\r
+                open();\r
+                try {\r
+                    file.seek(0);\r
+                    int i = file.readInt();\r
+                    int version = Integer.reverseBytes(i);\r
+                    if (version != 1)\r
+                        throw new ProCoreException("Unsupported journal file version. expected=1 got=" + version);\r
+                    i = file.readInt();\r
+                    int major = Integer.reverseBytes(i);\r
+                    if (major != MessageNumber.ProtocolVersionMajor)\r
+                        throw new ProCoreException("Unsupported journal request major version. expected=" + MessageNumber.ProtocolVersionMajor + " got=" + major);\r
+                    i = file.readInt();\r
+                    int minor = Integer.reverseBytes(i);\r
+                    if (minor > MessageNumber.ProtocolVersionMinor)\r
+                        throw new ProCoreException("Unsupported journal request minor version. expected=" + MessageNumber.ProtocolVersionMinor + " got=" + minor);\r
+                    i = file.readInt();\r
+                    int length = Integer.reverseBytes(i);\r
+                    while (length > 0) { // Not supporting unsigned integers.\r
+                        if (length < 6)\r
+                            throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");\r
+                        file.skipBytes(length);\r
+                        i = file.readInt();\r
+                        int length2 = Integer.reverseBytes(i);\r
+                        if (length != length2)\r
+                            throw new ProCoreException("Journal file corrupted at " + file.getFilePointer() + ".");\r
+                        long offset = file.getFilePointer() - length - 8;\r
+                        offsets.add(offset);\r
+                        i = file.readInt();\r
+                        length = Integer.reverseBytes(i);\r
+                    }\r
+                } catch (EOFException e) {\r
+                } catch (IOException e) {\r
+                    throw new ProCoreException("Failed to get command count.", e);\r
+                } finally {\r
+                    close();\r
+                }\r
+                lastModified = modified;\r
+                readLines(0);\r
+                return offsets;\r
+            }\r
+        }\r
+        private final Analyzed analyzed;\r
+        JournalI(File dbFolder) {\r
+            this.analyzed = new Analyzed(dbFolder);\r
+        }\r
+        @Override\r
+        public boolean canRead() {\r
+            return analyzed.canRead() && !isServerAlive();\r
+        }\r
+        @Override\r
+        public int count() {\r
+            return analyzed.count();\r
+        }\r
+        @Override\r
+        public int read(int index, Line line) throws ProCoreException {\r
+            int count = analyzed.count();\r
+            analyzed.getRow(index, line);\r
+            return count;\r
+        }\r
+    }\r
+       @Override\r
+       public String getCompression() {\r
+               return "FLZ";\r
+       }\r
+}\r