/******************************************************************************
* Copyright © 2013-2016 The Nxt Core Developers. *
* *
* See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Nxt software, including this file, may be copied, modified, propagated, *
* or distributed except according to the terms contained in the LICENSE.txt *
* file. *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
package nxt.tools;
import nxt.Constants;
import nxt.Nxt;
import nxt.util.Logger;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
* Compact and reorganize the NRS database. The NRS application must not be
* running.
*
* To run the database compact tool on Linux or Mac:
*
* java -cp "classes:lib/*:conf" nxt.tools.CompactDatabase
*
* To run the database compact toon on Windows:
*
* java -cp "classes;lib/*;conf" -Dnxt.runtime.mode=desktop nxt.tools.CompactDatabase
*/
public class CompactDatabase {
/**
* Compact the NRS database
*
* @param args Command line arguments
*/
public static void main(String[] args) {
//
// Initialize Nxt properties and logging
//
Logger.init();
//
// Compact the database
//
int exitCode = compactDatabase();
//
// Shutdown the logger and exit
//
Logger.shutdown();
System.exit(exitCode);
}
/**
* Compact the database
*/
private static int compactDatabase() {
int exitCode = 0;
//
// Get the database URL
//
String dbPrefix = Constants.isTestnet ? "nxt.testDb" : "nxt.db";
String dbType = Nxt.getStringProperty(dbPrefix + "Type");
if (!"h2".equals(dbType)) {
Logger.logErrorMessage("Database type must be 'h2'");
return 1;
}
String dbUrl = Nxt.getStringProperty(dbPrefix + "Url");
if (dbUrl == null) {
String dbPath = Nxt.getDbDir(Nxt.getStringProperty(dbPrefix + "Dir"));
dbUrl = String.format("jdbc:%s:%s", dbType, dbPath);
}
String dbUsername = Nxt.getStringProperty(dbPrefix + "Username", "sa");
String dbPassword = Nxt.getStringProperty(dbPrefix + "Password", "sa", true);
//
// Get the database path. This is the third colon-separated operand and is
// terminated by a semi-colon or by the end of the string.
//
int pos = dbUrl.indexOf(':');
if (pos >= 0) {
pos = dbUrl.indexOf(':', pos+1);
}
if (pos < 0) {
Logger.logErrorMessage("Malformed database URL: " + dbUrl);
return 1;
}
String dbDir;
int startPos = pos + 1;
int endPos = dbUrl.indexOf(';', startPos);
if (endPos < 0) {
dbDir = dbUrl.substring(startPos);
} else {
dbDir = dbUrl.substring(startPos, endPos);
}
//
// Remove the optional 'file' operand
//
if (dbDir.startsWith("file:"))
dbDir = dbDir.substring(5);
//
// Remove the database prefix from the end of the database path. The path
// separator can be either '/' or '\' (Windows will accept either separator
// so we can't rely on the system property).
//
endPos = dbDir.lastIndexOf('\\');
pos = dbDir.lastIndexOf('/');
if (endPos >= 0) {
if (pos >= 0) {
endPos = Math.max(endPos, pos);
}
} else {
endPos = pos;
}
if (endPos < 0) {
Logger.logErrorMessage("Malformed database URL: " + dbUrl);
return 1;
}
dbDir = dbDir.substring(0, endPos);
Logger.logInfoMessage("Database directory is '" + dbDir + '"');
//
// Create our files
//
int phase = 0;
File sqlFile = new File(dbDir, "backup.sql.gz");
File dbFile = new File(dbDir, "nxt.h2.db");
if (!dbFile.exists()) {
dbFile = new File(dbDir, "nxt.mv.db");
if (!dbFile.exists()) {
Logger.logErrorMessage("NRS database not found");
return 1;
}
}
File oldFile = new File(dbFile.getPath() + ".bak");
try {
//
// Create the SQL script
//
Logger.logInfoMessage("Creating the SQL script");
if (sqlFile.exists()) {
if (!sqlFile.delete()) {
throw new IOException(String.format("Unable to delete '%s'", sqlFile.getPath()));
}
}
try (Connection conn = DriverManager.getConnection(dbUrl, dbUsername, dbPassword);
Statement s = conn.createStatement()) {
s.execute("SCRIPT TO '" + sqlFile.getPath() + "' COMPRESSION GZIP CHARSET 'UTF-8'");
}
//
// Create the new database
//
Logger.logInfoMessage("Creating the new database");
if (!dbFile.renameTo(oldFile)) {
throw new IOException(String.format("Unable to rename '%s' to '%s'",
dbFile.getPath(), oldFile.getPath()));
}
phase = 1;
try (Connection conn = DriverManager.getConnection(dbUrl, dbUsername, dbPassword);
Statement s = conn.createStatement()) {
s.execute("RUNSCRIPT FROM '" + sqlFile.getPath() + "' COMPRESSION GZIP CHARSET 'UTF-8'");
s.execute("ANALYZE");
}
//
// New database has been created
//
phase = 2;
Logger.logInfoMessage("Database successfully compacted");
} catch (Throwable exc) {
Logger.logErrorMessage("Unable to compact the database", exc);
exitCode = 1;
} finally {
switch (phase) {
case 0:
//
// We failed while creating the SQL file
//
if (sqlFile.exists()) {
if (!sqlFile.delete()) {
Logger.logErrorMessage(String.format("Unable to delete '%s'", sqlFile.getPath()));
}
}
break;
case 1:
//
// We failed while creating the new database
//
File newFile = new File(dbDir, "nxt.h2.db");
if (newFile.exists()) {
if (!newFile.delete()) {
Logger.logErrorMessage(String.format("Unable to delete '%s'", newFile.getPath()));
}
} else {
newFile = new File(dbDir, "nxt.mv.db");
if (newFile.exists()) {
if (!newFile.delete()) {
Logger.logErrorMessage(String.format("Unable to delete '%s'", newFile.getPath()));
}
}
}
if (!oldFile.renameTo(dbFile)) {
Logger.logErrorMessage(String.format("Unable to rename '%s' to '%s'",
oldFile.getPath(), dbFile.getPath()));
}
break;
case 2:
//
// New database created
//
if (!sqlFile.delete()) {
Logger.logErrorMessage(String.format("Unable to delete '%s'", sqlFile.getPath()));
}
if (!oldFile.delete()) {
Logger.logErrorMessage(String.format("Unable to delete '%s'", oldFile.getPath()));
}
break;
}
}
return exitCode;
}
}