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.check; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.security.DigestOutputStream; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.swt.widgets.Shell; import org.jooq.Cursor; import org.jooq.Record; import org.jooq.impl.Factory; import de.blizzy.backup.BackupPlugin; import de.blizzy.backup.Compression; import de.blizzy.backup.IStorageInterceptor; import de.blizzy.backup.LengthOutputStream; import de.blizzy.backup.Messages; import de.blizzy.backup.StorageInterceptorDescriptor; import de.blizzy.backup.Utils; import de.blizzy.backup.database.Database; import de.blizzy.backup.database.schema.Tables; import de.blizzy.backup.settings.Settings; public class CheckRun implements IRunnableWithProgress { private static final class FileCheckResult { static final FileCheckResult BROKEN = new FileCheckResult(false, null); boolean ok; String checksumSHA256; FileCheckResult(boolean ok, String checksumSHA256) { this.ok = ok; this.checksumSHA256 = checksumSHA256; } } private static final int SHA256_LENGTH = DigestUtils.sha256Hex(StringUtils.EMPTY).length(); private Settings settings; private String outputFolder; private Shell parentShell; private Database database; private boolean backupOk = true; private List<IStorageInterceptor> storageInterceptors = new ArrayList<>(); public CheckRun(Settings settings, Shell parentShell) { this.settings = settings; this.parentShell = parentShell; outputFolder = settings.getOutputFolder(); } public void runCheck() { boolean canceled = false; boolean errors = false; try { ProgressMonitorDialog dlg = new ProgressMonitorDialog(parentShell); dlg.run(true, true, this); } catch (InvocationTargetException e) { BackupPlugin.getDefault().logError("error while checking backup integrity", e.getCause()); //$NON-NLS-1$ errors = true; } catch (RuntimeException e) { BackupPlugin.getDefault().logError("error while checking backup integrity", e); //$NON-NLS-1$ errors = true; } catch (InterruptedException e) { canceled = true; } if (!canceled) { if (errors) { MessageDialog.openError(parentShell, Messages.Title_BackupIntegrityCheck, Messages.ErrorsWhileCheckingBackup); } else { if (backupOk) { MessageDialog.openInformation(parentShell, Messages.Title_BackupIntegrityCheck, Messages.BackupIntegrityIntact); } else { MessageDialog.openError(parentShell, Messages.Title_BackupIntegrityCheck, Messages.BackupIntegrityNotIntact); } } } } @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { database = new Database(settings, false); final boolean[] ok = { true }; List<StorageInterceptorDescriptor> descs = BackupPlugin.getDefault().getStorageInterceptors(); for (final StorageInterceptorDescriptor desc : descs) { final IStorageInterceptor interceptor = desc.getStorageInterceptor(); SafeRunner.run(new ISafeRunnable() { @Override public void run() { IDialogSettings settings = Utils.getChildSection(Utils.getSection("storageInterceptors"), //$NON-NLS-1$ desc.getId()); if (!interceptor.initialize(parentShell, settings)) { ok[0] = false; } } @Override public void handleException(Throwable t) { ok[0] = false; interceptor.showErrorMessage(t, parentShell); BackupPlugin.getDefault() .logError("error while initializing storage interceptor '" + desc.getName() + "'", t); //$NON-NLS-1$ //$NON-NLS-2$ } }); storageInterceptors.add(interceptor); } if (!ok[0]) { monitor.done(); throw new InterruptedException(); } try { database.open(storageInterceptors); database.initialize(); int numFiles = database.factory().select(Factory.count()).from(Tables.FILES).fetchOne(Factory.count()) .intValue(); monitor.beginTask(Messages.Title_CheckBackupIntegrity, numFiles); Cursor<Record> cursor = null; try { cursor = database.factory().select(Tables.FILES.ID, Tables.FILES.BACKUP_PATH, Tables.FILES.CHECKSUM, Tables.FILES.LENGTH, Tables.FILES.COMPRESSION).from(Tables.FILES).fetchLazy(); while (cursor.hasNext()) { if (monitor.isCanceled()) { throw new InterruptedException(); } Record record = cursor.fetchOne(); String backupPath = record.getValue(Tables.FILES.BACKUP_PATH); String checksum = record.getValue(Tables.FILES.CHECKSUM); long length = record.getValue(Tables.FILES.LENGTH).longValue(); Compression compression = Compression .fromValue(record.getValue(Tables.FILES.COMPRESSION).intValue()); FileCheckResult checkResult = checkFile(backupPath, checksum, length, compression); if (!checkResult.ok) { backupOk = false; break; } if (checksum.length() != SHA256_LENGTH) { Integer id = record.getValue(Tables.FILES.ID); database.factory().update(Tables.FILES) .set(Tables.FILES.CHECKSUM, checkResult.checksumSHA256) .where(Tables.FILES.ID.equal(id)).execute(); } monitor.worked(1); } } finally { database.closeQuietly(cursor); } } catch (SQLException | IOException e) { boolean handled = false; for (IStorageInterceptor interceptor : storageInterceptors) { if (interceptor.showErrorMessage(e, parentShell)) { handled = true; } } if (handled) { throw new InterruptedException(); } throw new InvocationTargetException(e); } finally { database.close(); for (final IStorageInterceptor interceptor : storageInterceptors) { SafeRunner.run(new ISafeRunnable() { @Override public void run() { interceptor.destroy(); } @Override public void handleException(Throwable t) { BackupPlugin.getDefault().logError("error while destroying storage interceptor", t); //$NON-NLS-1$ } }); } System.gc(); monitor.done(); } } private FileCheckResult checkFile(String backupPath, String checksum, long length, Compression compression) throws IOException { File backupFile = Utils.toBackupFile(backupPath, outputFolder); if (backupFile.isFile()) { InputStream in = null; OutputStream out = null; try { InputStream fileIn = new BufferedInputStream(new FileInputStream(backupFile)); InputStream interceptIn = fileIn; for (IStorageInterceptor interceptor : storageInterceptors) { interceptIn = interceptor.interceptInputStream(interceptIn, length); } InputStream compressIn = compression.getInputStream(interceptIn); LengthOutputStream lengthOut = new LengthOutputStream(new NullOutputStream()); MessageDigest digest = MessageDigest.getInstance("SHA-256"); //$NON-NLS-1$ out = new DigestOutputStream(lengthOut, digest); MessageDigest md5Digest = null; if (checksum.length() != SHA256_LENGTH) { md5Digest = MessageDigest.getInstance("MD5"); //$NON-NLS-1$ out = new DigestOutputStream(out, md5Digest); } in = compressIn; IOUtils.copy(in, out); out.flush(); String fileChecksum = Hex.encodeHexString(digest.digest()); String fileChecksumMD5 = (md5Digest != null) ? Hex.encodeHexString(md5Digest.digest()) : null; long fileLength = lengthOut.getLength(); boolean ok = (fileLength == length) && checksum.equals((checksum.length() == SHA256_LENGTH) ? fileChecksum : fileChecksumMD5); return new FileCheckResult(ok, fileChecksum); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); } } return FileCheckResult.BROKEN; } }