package com.vitco.manager.updater;
import com.vitco.layout.content.console.ConsoleInterface;
import com.vitco.manager.action.ActionManager;
import com.vitco.manager.error.ErrorHandlerInterface;
import com.vitco.manager.lang.LangSelectorInterface;
import com.vitco.manager.thread.LifeTimeThread;
import com.vitco.manager.thread.ThreadManagerInterface;
import com.vitco.settings.VitcoSettings;
import com.vitco.util.file.FileTools;
import com.vitco.util.misc.UrlUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.*;
import java.net.URLDecoder;
/**
* Handles
*
* - update notification of the program
* - updating of the updater
*
* Note: The updated updater only takes effect for the next start.
*/
public class Updater {
// var & setter
protected LangSelectorInterface langSelector;
@Autowired(required=true)
public final void setLangSelector(LangSelectorInterface langSelector) {
this.langSelector = langSelector;
}
private ThreadManagerInterface threadManager;
// set the action handler
@Autowired
public final void setThreadManager(ThreadManagerInterface threadManager) {
this.threadManager = threadManager;
}
// var & setter
private ErrorHandlerInterface errorHandler;
@Autowired
public final void setErrorHandler(ErrorHandlerInterface errorHandler) {
this.errorHandler = errorHandler;
}
// var & setter
private ConsoleInterface console;
@Autowired
public final void setConsole(ConsoleInterface console) {
this.console = console;
}
// var & setter
protected ActionManager actionManager;
@Autowired(required=true)
public final void setActionManager(ActionManager actionManager) {
this.actionManager = actionManager;
}
private String digest = null;
private final LifeTimeThread updaterThread = new LifeTimeThread() {
private boolean notify = false;
@Override
public void loop() throws InterruptedException {
synchronized (updaterThread) {
updaterThread.wait(60000);
}
if (notify) { // wait an additional minute before notifying
console.addLine(langSelector.getString("update_available_please_restart"));
stopThread();
} else {
String updaterInfo = UrlUtil.readUrl(VitcoSettings.PROGRAM_UPDATER_URL, errorHandler);
if (updaterInfo != null && !updaterInfo.equals("")) {
//console.addLine("===" + updaterInfo + "===");
String newDigest = DigestUtils.md5Hex(updaterInfo);
if (digest != null) {
if (!digest.equals(newDigest) && !newDigest.equals("")) {
notify = true;
}
}
digest = newDigest;
}
}
}
};
@PostConstruct
public final void init() {
// todo check if this works on mac & linux
// load the local digest
String path = getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
try {
String appJarLocation = URLDecoder.decode(path, "UTF-8");
File appJar = new File(appJarLocation);
String absolutePath = appJar.getAbsolutePath();
String filePath = absolutePath.
substring(0, absolutePath.lastIndexOf(File.separator) + 1);
// ---------
// check if the updater has updated
upgradeGetdown(
new File(filePath + "getdown-client-old.jar"),
new File(filePath + "getdown-client.jar"),
new File(filePath + "getdown-client-new.jar")
);
// ---------
File digestFile = new File(filePath + "digest.txt");
if (digestFile.exists()) {
String localUpdaterInfo = FileTools.readFileAsString(digestFile, errorHandler);
digest = DigestUtils.md5Hex(localUpdaterInfo);
//console.addLine("===" + localUpdaterInfo + "===");
}
} catch (UnsupportedEncodingException e) {
errorHandler.handle(e);
}
actionManager.registerAction("force_update_check", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
synchronized (updaterThread) {
updaterThread.notify();
}
}
});
// initialize the update checker
threadManager.manage(updaterThread);
}
/**
* Copies the contents of the supplied input stream to the supplied output stream.
*/
public static <T extends OutputStream> T copy (InputStream in, T out) throws IOException {
byte[] buffer = new byte[4096];
for (int read; (read = in.read(buffer)) > 0; ) {
out.write(buffer, 0, read);
}
return out;
}
/**
* Upgrades Getdown by moving an installation managed copy of the Getdown jar file over the
* non-managed copy (which would be used to run Getdown itself).
*
* <p> If the upgrade fails for a variety of reasons, there's not much else one
* can do other than try again next time around.
*/
public void upgradeGetdown (File oldgd, File curgd, File newgd) {
// we assume getdown's jar file size changes with every upgrade, this is not guaranteed,
// but in reality it will, and it allows us to avoid pointlessly upgrading getdown every
// time the client is updated which is unnecessarily flirting with danger
if (!newgd.exists() || newgd.length() == curgd.length()) {
return;
}
// clear out any old getdown
if (oldgd.exists()) {
if (!oldgd.delete()) {
console.addLine("Error: Failed to remove old file version (1).");
}
}
// now try updating using renames
if (!curgd.exists() || curgd.renameTo(oldgd)) {
if (newgd.renameTo(curgd)) {
if (oldgd.exists()) {
if (!oldgd.delete()) {
console.addLine("Error: Failed to remove old file version (2).");
}
}
try {
// copy the moved file back to getdown-dop-new.jar so that we don't end up
// downloading another copy next time
copy(new FileInputStream(curgd), new FileOutputStream(newgd));
} catch (IOException e) {
errorHandler.handle(e);
}
return;
}
// try to unfuck ourselves
if (!oldgd.renameTo(curgd)) {
console.addLine("Error: Failed to revert renaming.");
}
}
// that didn't work, let's try copying it
try {
copy(new FileInputStream(newgd), new FileOutputStream(curgd));
} catch (IOException ioe) {
errorHandler.handle(ioe);
}
}
}