package com.dgex.offspring.update;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
public class UpdateManager {
static Logger logger = Logger.getLogger(UpdateManager.class);
private final Map<File, Long> map = null;
private final File dir = null;
private final IProgressMonitor monitor;
private final FolderBackup backup;
private final UpdateLog updateLog;
private final FolderCompare compare;
private X509Certificate certificate;
private List<File> changedFiles = null;
private final X509CertificateVerify jarVerifier;
public UpdateManager(IProgressMonitor monitor) {
this.monitor = monitor;
this.updateLog = new UpdateLog();
this.backup = new FolderBackup(monitor, updateLog);
this.compare = new FolderCompare(monitor, updateLog);
this.jarVerifier = new X509CertificateVerify(monitor, updateLog);
}
/* Step 0 */
public boolean initialize() {
certificate = getCertificate();
if (certificate == null) {
updateLog.logError(getClass().getName(),
"Could not get X509Certificate certificate");
return false;
}
return true;
}
/*
* (1)
*
* Create a full backup of all installation files and place these files in a
* temporary backup folder outside the installation directory.
*/
public boolean createFullBackup() {
return backup.backup(backup.getInstallDir(), backup.getBackupDir());
}
/*
* (2)
*
* Compare all files in the installation directory to those in the backup
* folder. We are interested in all added and all updated files. Removed files
* are not of interest.
*
* After this operation in changedFiles we have a list of all files that
* either changed or where added during the update.
*/
public boolean analyzeChangedFiles() {
try {
logger.info("==================================================");
logger.info("BEFORE analyzeChangedFiles");
for (File file : changedFiles) {
logger.info(file.getAbsoluteFile());
}
changedFiles = compare.compare(backup.getInstallDir(),
backup.getBackupDir());
logger.info("==================================================");
logger.info("AFTER analyzeChangedFiles");
for (File file : changedFiles) {
logger.info(file.getAbsoluteFile());
}
}
catch (IOException e) {
updateLog.logError(getClass().getName(),
"IOError while doing file compare");
return false;
}
return true;
}
/*
* (3)
*
* All jar files in Offspring are signed with the offspring certificate. We
* visit all files in the changedFiles list and verify if that file could be
* verified as signed by us.
*
* All verified files are removed from the list of changed files, what remains
* in the changedFiles are files that still need verification.
*/
public boolean verifyJarCertificates() {
logger.info("==================================================");
logger.info("BEFORE verifyJarCertificates");
for (File file : changedFiles) {
logger.info(file.getAbsoluteFile());
}
jarVerifier.verify(changedFiles, certificate);
logger.info("==================================================");
logger.info("AFTER verifyJarCertificates");
for (File file : changedFiles) {
logger.info(file.getAbsoluteFile());
}
return true;
}
/*
* (4)
*
* Not only jar files are distributed through updates also log files and
* configuration files these files are a lot less sensitive than the
* executable jar files.
*
* Executables and dynamic libraries on the other hand are not allowed to
* change between updates. Also ini files that control
*/
public boolean verifyUnsignedUpdates() {
logger.info("These files are left over and are not verified");
for (File file : changedFiles) {
logger.info(file.getAbsoluteFile());
}
return true;
}
public boolean isVerified() {
return true;
}
public void createErrorLog() {}
public void rolebackUpdate() {}
public void printDifference() throws IOException {
Map<File, Long> map2 = generateChecksumMap(dir);
List<File> all = combinedKeys(map, map2);
Collections.sort(all);
List<File> removed = new ArrayList<File>();
List<File> added = new ArrayList<File>();
List<File> changed = new ArrayList<File>();
for (File file : all) {
if (!map2.containsKey(file)) {
removed.add(file);
}
else if (!map.containsKey(file)) {
added.add(file);
}
else {
long checksum = FileUtils.checksumCRC32(file);
if (!map.get(file).equals(checksum)) {
changed.add(file);
}
}
}
for (File file : changed)
logger.info("CHANGED - " + file.getAbsolutePath());
for (File file : removed)
logger.info("REMOVED - " + file.getAbsolutePath());
for (File file : added)
logger.info("ADDED - " + file.getAbsolutePath());
}
/* Returns the combined list of keys in two hasmaps */
private List<File> combinedKeys(Map<File, Long> map1, Map<File, Long> map2) {
Map<File, Long> combined = new HashMap<File, Long>();
for (File key : map1.keySet())
combined.put(key, 1l);
for (File key : map2.keySet())
combined.put(key, 1l);
return new ArrayList<File>(combined.keySet());
}
private Map<File, Long> generateChecksumMap(File directory)
throws IOException {
Map<File, Long> map = new HashMap<File, Long>();
Iterator<File> files = FileUtils.iterateFiles(directory, null, true);
while (files != null && files.hasNext()) {
File file = files.next();
long checksum = FileUtils.checksumCRC32(file);
map.put(file, checksum);
}
return map;
}
private X509Certificate getCertificate() {
try {
File dir = new File(Platform.getInstallLocation().getURL().toURI());
File file = new File(dir.getAbsolutePath() + File.separator
+ "offspring.crt");
return X509CertificateFile.getCertificate(file);
}
catch (URISyntaxException e) {
logger.error("Could not read certificate", e);
}
return null;
}
}