Java tutorial
/* blizzy's Backup - Easy to use personal file backup application Copyright (C) 2011-2012 Maik Schreiber This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.blizzy.backup.database; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.jooq.Cursor; import org.jooq.Record; import org.jooq.SQLDialect; import org.jooq.exception.DataAccessException; import org.jooq.impl.Factory; import de.blizzy.backup.BackupPlugin; import de.blizzy.backup.Compression; import de.blizzy.backup.IDatabaseModifyingStorageInterceptor; import de.blizzy.backup.IStorageInterceptor; import de.blizzy.backup.Utils; import de.blizzy.backup.database.schema.Tables; import de.blizzy.backup.settings.Settings; public class Database { private static final String DB_FOLDER_NAME = "$blizzysbackup"; //$NON-NLS-1$ private String outputFolder; private File realFolder; private boolean heavyDuty; private File folder; private Connection conn; private Factory factory; public Database(Settings settings, boolean heavyDuty) { this.outputFolder = settings.getOutputFolder(); this.realFolder = new File(new File(outputFolder), DB_FOLDER_NAME); folder = realFolder; this.heavyDuty = heavyDuty; } public void open(Collection<IStorageInterceptor> storageInterceptors) throws SQLException, IOException { if (heavyDuty) { folder = Files.createTempDirectory("blizzysbackup-").toFile(); //$NON-NLS-1$ if (realFolder.isDirectory()) { copyFolder(realFolder, folder, false); } } open(folder, storageInterceptors); } private void open(File folder, Collection<IStorageInterceptor> storageInterceptors) throws SQLException { if (conn == null) { try { if (!folder.exists()) { FileUtils.forceMkdir(folder); } try { Class.forName("org.h2.Driver"); //$NON-NLS-1$ } catch (ClassNotFoundException e) { throw new RuntimeException(e); } StringBuilder paramsBuf = new StringBuilder("CACHE_SIZE=65536"); //$NON-NLS-1$ String password = StringUtils.EMPTY; for (IStorageInterceptor interceptor : storageInterceptors) { if (interceptor instanceof IDatabaseModifyingStorageInterceptor) { IDatabaseModifyingStorageInterceptor i = (IDatabaseModifyingStorageInterceptor) interceptor; paramsBuf.append(";").append(i.getDatabaseParameters()); //$NON-NLS-1$ password = i.getDatabasePassword() + " " + password; //$NON-NLS-1$ } } conn = DriverManager.getConnection("jdbc:h2:" + folder.getAbsolutePath() + "/backup" + //$NON-NLS-1$ //$NON-NLS-2$ ";" + paramsBuf.toString(), //$NON-NLS-1$ "sa", password); //$NON-NLS-1$ conn.setAutoCommit(true); factory = new Factory(conn, SQLDialect.MYSQL); } catch (IOException e) { throw new RuntimeException(e); } } } public void close() { if (conn != null) { try { conn.close(); } catch (SQLException e) { // ignore } finally { conn = null; factory = null; } if (heavyDuty) { try { copyFolder(folder, realFolder, false); FileUtils.forceDelete(folder); } catch (IOException e) { BackupPlugin.getDefault().logError("error while closing database", e); //$NON-NLS-1$ } finally { folder = realFolder; } } } } public Factory factory() { return factory; } public static boolean containsDatabaseFolder(File folder) { return new File(folder, DB_FOLDER_NAME).isDirectory(); } public void backupDatabase(File targetFolder) throws IOException { copyFolder(folder, targetFolder, true); } private void copyFolder(File folder, File targetFolder, boolean zipFiles) throws IOException { FileUtils.forceMkdir(targetFolder); FileUtils.cleanDirectory(targetFolder); for (File file : folder.listFiles()) { if (file.isDirectory()) { copyFolder(file, new File(targetFolder, file.getName()), zipFiles); } else { if (zipFiles) { Utils.zipFile(file, new File(targetFolder, file.getName() + ".zip")); //$NON-NLS-1$ } else { FileUtils.copyFile(file, new File(targetFolder, file.getName())); } } } } public void initialize() { try { int sha256Length = DigestUtils.sha256Hex(StringUtils.EMPTY).length(); factory.query("CREATE TABLE IF NOT EXISTS backups (" + //$NON-NLS-1$ "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, " + //$NON-NLS-1$ "run_time DATETIME NOT NULL, " + //$NON-NLS-1$ "num_entries INT NULL" + //$NON-NLS-1$ ")") //$NON-NLS-1$ .execute(); int sampleBackupPathLength = Utils.createSampleBackupFilePath().length(); factory.query("CREATE TABLE IF NOT EXISTS files (" + //$NON-NLS-1$ "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, " + //$NON-NLS-1$ "backup_path VARCHAR(" + sampleBackupPathLength + ") NOT NULL, " + //$NON-NLS-1$ //$NON-NLS-2$ "checksum VARCHAR(" + sha256Length + ") NOT NULL, " + //$NON-NLS-1$ //$NON-NLS-2$ "length BIGINT NOT NULL, " + //$NON-NLS-1$ "compression TINYINT NOT NULL" + //$NON-NLS-1$ ")") //$NON-NLS-1$ .execute(); factory.query("CREATE INDEX IF NOT EXISTS idx_old_files ON files " + //$NON-NLS-1$ "(checksum, length)") //$NON-NLS-1$ .execute(); factory.query("CREATE TABLE IF NOT EXISTS entries (" + //$NON-NLS-1$ "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, " + //$NON-NLS-1$ "parent_id INT NULL, " + //$NON-NLS-1$ "backup_id INT NOT NULL, " + //$NON-NLS-1$ "type TINYINT NOT NULL, " + //$NON-NLS-1$ "creation_time DATETIME NULL, " + //$NON-NLS-1$ "modification_time DATETIME NULL, " + //$NON-NLS-1$ "hidden BOOLEAN NOT NULL, " + //$NON-NLS-1$ "name VARCHAR(1024) NOT NULL, " + //$NON-NLS-1$ "name_lower VARCHAR(1024) NOT NULL, " + //$NON-NLS-1$ "file_id INT NULL" + //$NON-NLS-1$ ")") //$NON-NLS-1$ .execute(); factory.query("CREATE INDEX IF NOT EXISTS idx_entries_files ON entries " + //$NON-NLS-1$ "(file_id)") //$NON-NLS-1$ .execute(); factory.query("CREATE INDEX IF NOT EXISTS idx_folder_entries ON entries " + //$NON-NLS-1$ "(backup_id, parent_id)") //$NON-NLS-1$ .execute(); factory.query("DROP INDEX IF EXISTS idx_entries_names") //$NON-NLS-1$ .execute(); factory.query("CREATE INDEX IF NOT EXISTS idx_entries_names2 ON entries " + //$NON-NLS-1$ "(name, backup_id, parent_id)") //$NON-NLS-1$ .execute(); if (!isTableColumnExistent("FILES", "COMPRESSION")) { //$NON-NLS-1$ //$NON-NLS-2$ factory.query( "ALTER TABLE files ADD compression TINYINT NULL DEFAULT " + Compression.GZIP.getValue()) //$NON-NLS-1$ .execute(); factory.update(Tables.FILES) .set(Tables.FILES.COMPRESSION, Byte.valueOf((byte) Compression.GZIP.getValue())).execute(); factory.query("ALTER TABLE files ALTER COLUMN compression TINYINT NOT NULL") //$NON-NLS-1$ .execute(); } if (getTableColumnSize("FILES", "CHECKSUM") != sha256Length) { //$NON-NLS-1$ //$NON-NLS-2$ factory.query("ALTER TABLE files ALTER COLUMN " + //$NON-NLS-1$ "checksum VARCHAR(" + sha256Length + ") NOT NULL") //$NON-NLS-1$ //$NON-NLS-2$ .execute(); } if (!isTableColumnExistent("ENTRIES", "NAME_LOWER")) { //$NON-NLS-1$ //$NON-NLS-2$ factory.query("ALTER TABLE entries ADD name_lower VARCHAR(1024) NULL") //$NON-NLS-1$ .execute(); factory.update(Tables.ENTRIES).set(Tables.ENTRIES.NAME_LOWER, Tables.ENTRIES.NAME.lower()) .execute(); factory.query("ALTER TABLE entries ALTER COLUMN name_lower VARCHAR(1024) NOT NULL") //$NON-NLS-1$ .execute(); } factory.query("CREATE INDEX IF NOT EXISTS idx_entries_search ON entries " + //$NON-NLS-1$ "(backup_id, name_lower)") //$NON-NLS-1$ .execute(); if (getTableColumnSize("FILES", "BACKUP_PATH") != sampleBackupPathLength) { //$NON-NLS-1$ //$NON-NLS-2$ Cursor<Record> cursor = null; try { cursor = factory.select(Tables.FILES.ID, Tables.FILES.BACKUP_PATH).from(Tables.FILES) .fetchLazy(); while (cursor.hasNext()) { Record record = cursor.fetchOne(); String backupPath = record.getValue(Tables.FILES.BACKUP_PATH); String backupFileName = StringUtils.substringAfterLast(backupPath, "/"); //$NON-NLS-1$ if (backupFileName.indexOf('-') > 0) { Integer id = record.getValue(Tables.FILES.ID); File backupFile = Utils.toBackupFile(backupPath, outputFolder); File folder = backupFile.getParentFile(); int maxIdx = Utils.getMaxBackupFileIndex(folder); int newIdx = maxIdx + 1; String newBackupFileName = Utils.toBackupFileName(newIdx); File newBackupFile = new File(folder, newBackupFileName); FileUtils.moveFile(backupFile, newBackupFile); String newBackupPath = StringUtils.substringBeforeLast(backupPath, "/") + "/" //$NON-NLS-1$//$NON-NLS-2$ + newBackupFileName; factory.update(Tables.FILES).set(Tables.FILES.BACKUP_PATH, newBackupPath) .where(Tables.FILES.ID.equal(id)).execute(); } } factory.query("ALTER TABLE files ALTER COLUMN backup_path VARCHAR(" + sampleBackupPathLength //$NON-NLS-1$ + ") NOT NULL") //$NON-NLS-1$ .execute(); } finally { closeQuietly(cursor); } } factory.query("ANALYZE") //$NON-NLS-1$ .execute(); } catch (SQLException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } private boolean isTableColumnExistent(String tableName, String columnName) throws SQLException { ResultSet rs = null; try { rs = conn.getMetaData().getColumns(null, null, tableName, columnName); return rs.next(); } finally { closeQuietly(rs); } } private int getTableColumnSize(String tableName, String columnName) throws SQLException { ResultSet rs = null; try { rs = conn.getMetaData().getColumns(null, null, tableName, columnName); rs.next(); return rs.getInt(7); } finally { closeQuietly(rs); } } public void closeQuietly(Cursor<?> cursor) { if (cursor != null) { try { cursor.close(); } catch (DataAccessException e) { // ignore } } } private void closeQuietly(ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { // ignore } } } }