Author Topic: [Java/PHP] Auto-updater!  (Read 2150 times)

0 Members and 1 Guest are viewing this topic.

Offline Camel

  • Hero Member
  • *****
  • Posts: 1703
    • View Profile
    • BNU Bot
[Java/PHP] Auto-updater!
« on: October 01, 2007, 05:24:05 pm »
You can find everything here:
http://bnubot.googlecode.com/svn/trunk/BNUBot/src/net/bnubot/vercheck/

This is mutli-part, so I'll start with the basics and work my way up.

ReleaseType:
Code: [Select]
/**
 * This file is distributed under the GPL
 * $Id: ReleaseType.java 652 2007-09-06 21:00:33Z scotta $
 */

package net.bnubot.vercheck;

public enum ReleaseType {
Stable(0),
ReleaseCandidate(1),
Beta(2),
Alpha(3),
Development(4);

private int t;
private ReleaseType(int t) {
this.t = t;
}

public boolean isDevelopment() {
return (t >= Development.t);
}

public boolean isAlpha() {
return (t >= Alpha.t);
}

public boolean isBeta() {
return (t >= Beta.t);
}

public boolean isReleaseCandidate() {
return (t >= ReleaseCandidate.t);
}
}

VersionNumber:
Code: [Select]
/**
 * This file is distributed under the GPL
 * $Id: VersionNumber.java 705 2007-09-25 00:23:23Z scotta $
 */

package net.bnubot.vercheck;

public class VersionNumber {
private ReleaseType RELEASE_TYPE = null;
private Integer VER_MAJOR = null;
private Integer VER_MINOR = null;
private Integer VER_REVISION = null;
private Integer VER_ALPHA = null;
private Integer VER_BETA = null;
private Integer VER_RELEASE_CANDIDATE = null;
private Integer VER_SVN_REVISION = null;
private String VER_STRING = null;
private String BUILD_DATE = null;

public VersionNumber(ReleaseType rt, Integer major, Integer minor, Integer revision, Integer alpha, Integer beta, Integer rc) {
RELEASE_TYPE = rt;
VER_MAJOR = major;
VER_MINOR = minor;
VER_REVISION = revision;
VER_ALPHA = alpha;
VER_BETA = beta;
VER_RELEASE_CANDIDATE = rc;
}

public VersionNumber(ReleaseType rt, Integer major, Integer minor, Integer revision, Integer alpha, Integer beta, Integer rc, Integer svn, String builddate) {
this(rt, major, minor, revision, alpha, beta, rc);
VER_SVN_REVISION = svn;
BUILD_DATE = builddate;
}

public String toString() {
if(VER_STRING != null)
return VER_STRING;

VER_STRING = VER_MAJOR.toString() + '.' + VER_MINOR.toString() + '.' + VER_REVISION.toString();
if(ReleaseType.Development.equals(RELEASE_TYPE))
VER_STRING += " Development";
else if(VER_ALPHA != null)
VER_STRING += " Alpha " + VER_ALPHA.toString();
else if(VER_BETA != null)
VER_STRING += " Beta " + VER_BETA.toString();
else if(VER_RELEASE_CANDIDATE != null)
VER_STRING += " RC " + VER_RELEASE_CANDIDATE.toString();

if(VER_SVN_REVISION != null)
VER_STRING += " (r" + VER_SVN_REVISION.toString() + ")";

return VER_STRING;
}

public String getBuildDate() {
return BUILD_DATE;
}

public boolean isNewerThan(VersionNumber vn) {
if(VER_MAJOR > vn.VER_MAJOR) return true;
if(VER_MAJOR < vn.VER_MAJOR) return false;
if(VER_MINOR > vn.VER_MINOR) return true;
if(VER_MINOR < vn.VER_MINOR) return false;
if(VER_REVISION > vn.VER_REVISION) return true;
if(VER_REVISION < vn.VER_REVISION) return false;

if(VER_RELEASE_CANDIDATE != vn.VER_RELEASE_CANDIDATE) {
if(VER_RELEASE_CANDIDATE == null)
return false;
if(vn.VER_RELEASE_CANDIDATE == null)
return true;
if(VER_RELEASE_CANDIDATE > vn.VER_RELEASE_CANDIDATE) return true;
if(VER_RELEASE_CANDIDATE < vn.VER_RELEASE_CANDIDATE) return false;
}

if(VER_BETA != vn.VER_BETA) {
if(VER_BETA == null)
return false;
if(vn.VER_BETA == null)
return true;
if(VER_BETA > vn.VER_BETA) return true;
if(VER_BETA < vn.VER_BETA) return false;
}

if(VER_ALPHA != vn.VER_ALPHA) {
if(VER_ALPHA == null)
return false;
if(vn.VER_ALPHA == null)
return true;
if(VER_ALPHA > vn.VER_ALPHA) return true;
if(VER_ALPHA < vn.VER_ALPHA) return false;
}

if((VER_SVN_REVISION != null) && (vn.VER_SVN_REVISION != null)) {
if(VER_SVN_REVISION > vn.VER_SVN_REVISION) return true;
}

return false;
}

public ReleaseType getReleaseType() {
return RELEASE_TYPE;
}

public Integer revision() {
return VER_SVN_REVISION;
}
}

The next thing is my CurrentVersion class - it's super hacky, but it works well. If it finds "net/bnubot/version.properties" inside of the jar that's running, it loads info out of there. Otherwise, it looks for "src/net/bnubot/version.properties" and compares it to all of the java source files in "src" looking for the SVN revision provided by the $Id$ tag - if the SVN revision in version.properties doesn't match the latest svn revision from the files, it changes the release type to development, updates the svn revision, and saves the file.

Dependencies include my SortedProperties class, and my Out class, both of which you can find in this forum or in my SVN repository.

Code: [Select]
/**
 * This file is distributed under the GPL
 * $Id: CurrentVersion.java 705 2007-09-25 00:23:23Z scotta $
 */

package net.bnubot.vercheck;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.Properties;

import net.bnubot.util.Out;
import net.bnubot.util.SortedProperties;

public final class CurrentVersion {
protected static ReleaseType RELEASE_TYPE = null;
protected static Integer VER_MAJOR = null;
protected static Integer VER_MINOR = null;
protected static Integer VER_REVISION = null;
protected static Integer VER_RELEASE_CANDIDATE = null;
protected static Integer VER_ALPHA = null;
protected static Integer VER_BETA = null;
private static boolean VER_SVN_SET = false;
protected static Integer VER_SVN_REVISION = null;
private static VersionNumber VER = null;
private static String BUILD_DATE = null;
private static boolean fromJar = false;

private static final Integer revision(File f) {
if(VER_SVN_SET)
return VER_SVN_REVISION;

if(!f.exists())
return null;

Integer r = null;
for(File sf : f.listFiles()) {
if(sf.isDirectory()) {
Integer r2 = revision(sf);
if((r2 != null) && ((r == null) || (r2 > r)))
r = r2;
continue;
}

int ext = sf.getName().indexOf(".java");
if(ext == -1)
continue;
if(ext != sf.getName().length() - 5)
continue;

try {
FileReader fr = new FileReader(sf);
do {
int i = fr.read();
if(i == -1) { // <EOF>
Out.error(CurrentVersion.class, "Couldn't find Id: tag in " + sf.getPath());
break;
}

// Search for "$Id: "
if(i != '$')
continue;
if(fr.read() != 'I')
continue;
if(fr.read() != 'd')
continue;
if(fr.read() != ':')
continue;
if(fr.read() != ' ')
continue;

// Skip over the filename
String fileName = new String();
do {
int c = fr.read();
if(c == ' ')
break;
fileName += (char)c;
} while(true);

if(!sf.getName().equals(fileName)) {
Out.error(CurrentVersion.class, "File name in Id: tag doesn't match actual file name: " + sf.getPath());
break;
}

// Read in the revision as a String
String rev = "";
do {
int c = fr.read();
if(c < '0')
break;
if(c > '9')
break;

rev += (char) c;
} while(true);

if(rev.length() == 0)
continue;

// Parse the long
int r2 = Integer.parseInt(rev);
if((r == null) || (r2 > r))
r = r2;

break;
} while(true);
} catch(Exception e) {
Out.exception(e);
}
}
return r;
}

private static final Integer revision() {
if(!VER_SVN_SET) {
VER_SVN_REVISION = revision(new File("src"));
VER_SVN_SET = true;
}

return VER_SVN_REVISION;
}

public static boolean fromJar() {
version();
return fromJar;
}

public static final VersionNumber version() {
if(VER != null)
return VER;

try {
String vpPath = "/net/bnubot/version.properties";
// Eclipse likes to copy version.properties to bin; if it's there, delete it
File f = new File("bin" + vpPath);
if(f.exists())
f.delete();
f = null;
// Try to load the file out of the JAR
URL vp = VersionNumber.class.getResource(vpPath);
InputStream is = null;
try {
is = vp.openStream();
fromJar = true;
} catch(NullPointerException e) {
// Either the JAR is messed up, or we're running in the ide - look for the file in the working directory
f = new File("src" + vpPath);
if(f.exists())
is = new FileInputStream(f);
}
if(is == null) {
// Failed to determine the bot version
Out.fatalException(new FileNotFoundException(vpPath));
}

Properties versionprops = new SortedProperties();
versionprops.load(is);
is.close();

Integer VER_SVN_REVISION_FILE = null;
if(versionprops.containsKey("RELEASE_TYPE"))
RELEASE_TYPE = Enum.valueOf(ReleaseType.class, (String)versionprops.get("RELEASE_TYPE"));
if(versionprops.containsKey("VER_MAJOR"))
VER_MAJOR = Integer.parseInt((String)versionprops.get("VER_MAJOR"));
if(versionprops.containsKey("VER_MINOR"))
VER_MINOR = Integer.parseInt((String)versionprops.get("VER_MINOR"));
if(versionprops.containsKey("VER_REVISION"))
VER_REVISION = Integer.parseInt((String)versionprops.get("VER_REVISION"));
if(versionprops.containsKey("VER_RELEASE_CANDIDATE"))
VER_RELEASE_CANDIDATE = Integer.parseInt((String)versionprops.get("VER_RELEASE_CANDIDATE"));
if(versionprops.containsKey("VER_ALPHA"))
VER_ALPHA = Integer.parseInt((String)versionprops.get("VER_ALPHA"));
if(versionprops.containsKey("VER_BETA"))
VER_BETA = Integer.parseInt((String)versionprops.get("VER_BETA"));
if(versionprops.containsKey("VER_SVN_REVISION"))
VER_SVN_REVISION_FILE = Integer.parseInt((String)versionprops.get("VER_SVN_REVISION"));

if(f == null)
BUILD_DATE = versionprops.getProperty("BUILD_DATE");
else {
BUILD_DATE = new Date().toString();
versionprops.setProperty("BUILD_DATE", BUILD_DATE);
}

if(revision() == null) {
VER_SVN_REVISION = VER_SVN_REVISION_FILE;
} else {
if((VER_SVN_REVISION_FILE == null) || (VER_SVN_REVISION > VER_SVN_REVISION_FILE)) {
Out.info(CurrentVersion.class, "File version is " + VER_SVN_REVISION_FILE);
Out.info(CurrentVersion.class, "Calculated version is " + VER_SVN_REVISION);

if((f != null) && (f.exists())) {
versionprops.setProperty("RELEASE_TYPE", ReleaseType.Development.name());
versionprops.setProperty("VER_SVN_REVISION", Integer.toString(VER_SVN_REVISION));
versionprops.store(new FileOutputStream(f), null);
}
}
}

VER = new VersionNumber(RELEASE_TYPE, VER_MAJOR, VER_MINOR, VER_REVISION, VER_ALPHA, VER_BETA, VER_RELEASE_CANDIDATE, revision(), BUILD_DATE);
return VER;
} catch(Exception e) {
Out.exception(e);
}

throw new NullPointerException();
}
}

I've already posted my XML Decorator class, so I'll skip it here.

The meat and potatoes: VersionCheck! Depends on URLDownloader, which I'll post separately since it's in a different package. Also depends on an array from JBLS, which essentially determines which products my bot supports - this is so I can update the hardcoded verbytes in the JBLS Constants class without re-publishing the bot. Obviously, if you intend to use this to implement your own auto-update, you'll have to rewrite the first half of checkVersion(boolean, ReleaseType). You'll also notice that I added a way for me to push files on to the client's computer - I know it's sketchy, but it's the most user-friendly solution I could think of.
Code: [Select]
/**
 * This file is distributed under the GPL
 * $Id: VersionCheck.java 720 2007-09-27 20:06:46Z scotta $
 */

package net.bnubot.vercheck;

import java.io.File;
import java.net.URL;

import javax.swing.JOptionPane;

import org.jbls.util.Constants;

import net.bnubot.core.ConnectionSettings;
import net.bnubot.util.Out;
import net.bnubot.util.URLDownloader;

public class VersionCheck {
protected static XMLElementDecorator elem = null;
protected static VersionNumber vnLatest = null;

public static boolean checkVersion() throws Exception {
return checkVersion(false, ConnectionSettings.releaseType);
}

public static boolean checkVersion(boolean forceDownload, ReleaseType rt) throws Exception {
{
String url = "http://www.clanbnu.ws/bnubot/version.php?";
if(!forceDownload && (CurrentVersion.version().revision() != null))
url += "svn=" + CurrentVersion.version().revision() + "&";
url += "release=" + rt.toString();
elem = XMLElementDecorator.parse(url);
}

XMLElementDecorator error = elem.getChild("error");
if(error != null) {
Out.error(VersionCheck.class, error.getString());
return false;
}

XMLElementDecorator motd = elem.getPath("bnubot/motd");
if((motd != null) && (motd.getString() != null))
Out.info(VersionCheck.class, motd.getString());

XMLElementDecorator downloads = elem.getPath("bnubot/downloads");
if(downloads != null) {
for(XMLElementDecorator file : downloads.getChildren("file"))
URLDownloader.downloadURL(
new URL(file.getChild("from").getString()),
new File(file.getChild("to").getString()),
false);
}

XMLElementDecorator gamesElem = elem.getPath("bnubot/games");
if(gamesElem != null)
for(int i = 0; i < Constants.prods.length; i++) {
String game = Constants.prods[i];
int verByte = Constants.IX86verbytes[i];

XMLElementDecorator gameElem = gamesElem.getPath(game);
if(gameElem == null)
continue;

int vb = gameElem.getPath("verbyte").getInt();

if(verByte != vb) {
Out.error(VersionCheck.class, "Verbyte for game " + game + " is updating from 0x" + Integer.toHexString(verByte) + " to 0x" + Integer.toHexString(vb));
Constants.IX86verbytes[i] = vb;
}
}

XMLElementDecorator verLatest = elem.getPath("bnubot/latestVersion");
if(verLatest == null)
return false;

vnLatest = new VersionNumber(
Enum.valueOf(ReleaseType.class, verLatest.getChild("type").getString()),
verLatest.getChild("major").getInt(),
verLatest.getChild("minor").getInt(),
verLatest.getChild("revision").getInt(),
verLatest.getChild("alpha").getInt(),
verLatest.getChild("beta").getInt(),
verLatest.getChild("rc").getInt(),
verLatest.getChild("svn").getInt(),
verLatest.getChild("built").getString());

String url = verLatest.getChild("url").getString();
if(forceDownload) {
if(url == null)
return false;
URLDownloader.downloadURL(new URL(url), new File("BNUBot.jar"), true);
return true;
}

if(!vnLatest.isNewerThan(CurrentVersion.version()))
return false;

Out.error(VersionCheck.class, "Latest version: " + vnLatest.toString());

if(url != null) {
try {
File thisJar = new File("BNUBot.jar");
if(thisJar.exists()) {
String msg = "There is an update to BNU-Bot avalable.\nWould you like to update to version " + vnLatest.toString() + "?";
if(JOptionPane.showConfirmDialog(null, msg, "Update?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
URLDownloader.downloadURL(new URL(url), new File("BNUBot.jar"), true);
JOptionPane.showMessageDialog(null, "Update complete. Please restart BNU-Bot.");
System.exit(0);
}
return true;
}
} catch(Exception e) {
Out.exception(e);
}

Out.error(VersionCheck.class, "Update: " + url);
}
return true;
}
}

Now, the PHP end (version.php). I won't post the full thing for security reasons, but this should be enough to get anyone going:
Code: [Select]
header('Content-Type: text/xml');

function printTag($name, $value) {
    echo '<' . $name;
    if($value === null)
        echo '/>';
    else
        echo '>' . $value . '</' . $name . '>';
}

function printVersion($type, $major, $minor, $revision, $alpha, $beta, $rc, $svn, $built, $url) {
    printTag('type', $type);
    printTag('major', $major);
    printTag('minor', $minor);
    printTag('revision', $revision);
    printTag('alpha', $alpha);
    printTag('beta', $beta);
    printTag('rc', $rc);
    printTag('svn', $svn);
    printTag('built', $built);
    printTag('url', $url);
}

?>
<bnubot>
    <motd/>
    <downloads>
        <file>
            <from>http://bnubot.googlecode.com/svn/trunk/BNUBot/war3_icons.bni</from>
            <to>war3_icons.bni</to>
        </file>
        <file>
            <from>http://bnubot.googlecode.com/svn/trunk/BNUBot/w3xp_icons.bni</from>
            <to>w3xp_icons.bni</to>
        </file>
        <file>
            <from>http://bnubot.googlecode.com/files/derby.jar</from>
            <to>lib/derby.jar</to>
        </file>
        <file>
            <from>http://bnubot.googlecode.com/svn/trunk/BNUBot/schema.derby</from>
            <to>schema.derby</to>
        </file>
        <file>
            <from>http://bnubot.googlecode.com/svn/trunk/BNUBot/tray.gif</from>
            <to>tray.gif</to>
        </file>
    </downloads>
    <latestVersion>
       <?PHP
            switch($_GET['release']) {
            case 'Development':
                printVersion('Development', 2, 0, 0, null, 5, null, $svnrev, null, null);
                break;
            default:
                $url = 'http://bnubot.googlecode.com/files/BNUBot-2-0-0-b5.jar';
                printVersion('Beta', 2, 0, 0, null, 5, null, 681, null, $url);
                break;
            }
        ?>
    </latestVersion>
    <?PHP
        if(isset($verbytes)) {
            echo "<games>\n";
            foreach($verbytes as $game => $vb)
                echo "\t\t<$game><verbyte>$vb</verbyte></$game>\n";

            echo "\t</games>";
        }
    ?>
</bnubot>

Comments/suggestions are welcome :)
« Last Edit: October 01, 2007, 05:35:07 pm by Camel »

<Camel> i said what what
<Blaze> in the butt
<Camel> you want to do it in my butt?
<Blaze> in my butt
<Camel> let's do it in the butt
<Blaze> Okay!