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