package org.simantics; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.simantics.utils.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Tuukka Lehtonen * @since 1.34.0 */ public class DatabaseBaselines { private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseBaselines.class); private static final boolean REQUIRE_INDEX_IN_BASELINE = false; private static final String DB_DIRECTORY = "db"; //$NON-NLS-1$ private static final String INDEX_DIRECTORY = ".metadata/.plugins/org.simantics.db.indexing"; //$NON-NLS-1$ private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("d. MMM yyyy HH:mm:ss"); public static Path packageBaseline(Path fromWorkspace, Path packageFile) throws IOException { return compressZip(fromWorkspace, collectBaselinePaths(fromWorkspace), packageFile); } private static List collectBaselinePaths(Path workspace) throws IOException { Path dbPath = workspace.resolve(DB_DIRECTORY); Path indexPath = workspace.resolve(INDEX_DIRECTORY); if (!Files.isDirectory(dbPath)) throw new IllegalArgumentException("workspace database directory " + dbPath + " does not exist"); List paths = Files.walk(dbPath).collect(Collectors.toList()); if (Files.isDirectory(indexPath)) { List indexPaths = Files.walk(indexPath).collect(Collectors.toList()); paths.addAll(indexPaths); } else { if (REQUIRE_INDEX_IN_BASELINE) throw new IllegalArgumentException("workspace database index directory " + indexPath + " does not exist"); } return paths; } private static Path compressZip(Path relativeRoot, List paths, Path zipFile) throws IOException { if (LOGGER.isDebugEnabled()) LOGGER.debug("Compressing " + paths.size() + " path entries into ZIP file " + zipFile); try (ZipOutputStream zout = new ZipOutputStream(Files.newOutputStream(zipFile))) { compressZip(relativeRoot, zout, paths); return zipFile; } finally { if (LOGGER.isDebugEnabled()) LOGGER.debug("Compressed " + paths.size() + " entries into " + zipFile); } } private static void compressZip(Path relativeRoot, ZipOutputStream zout, List paths) throws IOException { for (Path p : paths) { Path rp = relativeRoot.relativize(p); String name = rp.toString(); if (Files.isDirectory(p)) { name = name.endsWith("/") ? name : name + "/"; zout.putNextEntry(new ZipEntry(name)); } else { zout.putNextEntry(new ZipEntry(name)); FileUtils.copy(p.toFile(), zout); zout.closeEntry(); } } } public static byte[] baselineIndicatorContents(Path path) throws IOException { return String.format("%s%n%s%n", path.toString(), Instant.now().atZone(ZoneId.systemDefault()).format(TIMESTAMP_FORMAT)) .getBytes("UTF-8"); } public static void validateWorkspaceForBaselineInitialization(Path workspaceLocation) throws PlatformException { try { Path db = workspaceLocation.resolve(DB_DIRECTORY); if (Files.exists(db)) throw new PlatformException("Database location " + db + " already exists. Cannot re-initialize workspace from baseline."); if (REQUIRE_INDEX_IN_BASELINE) { Path index = workspaceLocation.resolve(INDEX_DIRECTORY); if (!Files.exists(index) || !isEmptyDirectory(index)) throw new PlatformException("Index location " + index + " already exists. Cannot re-initialize workspace from baseline."); } } catch (IOException e) { throw new PlatformException("Failed to validate workspace for baseline initialization", e); } } private static boolean isEmptyDirectory(Path dir) throws IOException { return Files.walk(dir).count() == 1; } public static void validateBaselineFile(Path baseline) throws PlatformException { try (ZipFile zip = new ZipFile(baseline.toFile())) { ZipEntry db = zip.getEntry(DB_DIRECTORY); if (db == null) throw new PlatformException("Baseline archive does not contain database directory '" + DB_DIRECTORY + "'"); if (REQUIRE_INDEX_IN_BASELINE) { ZipEntry index = zip.getEntry(INDEX_DIRECTORY); if (index == null) throw new PlatformException("Baseline archive does not contain database index directory '" + INDEX_DIRECTORY + "'"); } } catch (IOException e) { throw new PlatformException("Failed to validate baseline archive " + baseline, e); } } public static void initializeWorkspaceWithBaseline(Path baseline, Path workspaceLocation, Path indicatorPath) throws PlatformException { try { Files.createDirectories(workspaceLocation); FileUtils.extractZip(baseline.toFile(), workspaceLocation.toFile()); if (indicatorPath != null) Files.write(indicatorPath, DatabaseBaselines.baselineIndicatorContents(indicatorPath)); } catch (IOException e) { throw new PlatformException(e); } } public static void main(String[] args) throws IOException { packageBaseline(Paths.get("D:/temp/desktop/workspace"), Paths.get("d:/temp/desktop/workspace/baseline.zip")); } }