Fixes #976 Now uses GitHub to retrieve most current version
Reworked whole Version Checker with an extensible interface to add any other service later on easier. The version checker now has its own config file, to collect the different options and extract them from the main config file. In that you can specify how fine the versions should be checked.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
/**
|
||||
* Base version of {@link Version}.
|
||||
*
|
||||
* Provides a unified way to test for equality and print a formatted string
|
||||
*/
|
||||
public abstract class BaseVersion implements Version
|
||||
{
|
||||
private final int revision;
|
||||
private final Channel channel;
|
||||
private final int build;
|
||||
|
||||
/**
|
||||
* @param revision revision in natural number
|
||||
* @param channel channel
|
||||
* @param build build in natural number
|
||||
*
|
||||
* @throws AssertionError if assertion are enabled and revision or build are not natural numbers
|
||||
*/
|
||||
public BaseVersion( int revision, Channel channel, int build )
|
||||
{
|
||||
assert revision >= 0;
|
||||
assert build >= 0;
|
||||
|
||||
this.revision = revision;
|
||||
this.channel = channel;
|
||||
this.build = build;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int revision()
|
||||
{
|
||||
return this.revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Channel channel()
|
||||
{
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int build()
|
||||
{
|
||||
return this.build;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatted()
|
||||
{
|
||||
return "rv" + this.revision + '-' + this.channel.name().toLowerCase() + '-' + this.build;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode()
|
||||
{
|
||||
int result = this.revision;
|
||||
result = 31 * result + ( this.channel != null ? this.channel.hashCode() : 0 );
|
||||
result = 31 * result + this.build;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals( Object o )
|
||||
{
|
||||
if ( this == o )
|
||||
return true;
|
||||
if ( !( o instanceof Version ) )
|
||||
return false;
|
||||
|
||||
Version that = (Version) o;
|
||||
|
||||
if ( this.revision != that.revision() )
|
||||
return false;
|
||||
if ( this.build != that.build() )
|
||||
return false;
|
||||
return this.channel == that.channel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString()
|
||||
{
|
||||
return "Version{" +
|
||||
"revision=" + this.revision +
|
||||
", channel=" + this.channel +
|
||||
", build=" + this.build +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
/**
|
||||
* Represents the release channel of Applied Energistics. The mod is either in Alpha, Beta or Release channel.
|
||||
* Any more might be confusing to the end-user
|
||||
*/
|
||||
public enum Channel
|
||||
{
|
||||
Alpha,
|
||||
Beta,
|
||||
Release
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
/**
|
||||
* AE prints version like rv2-beta-8
|
||||
* GitHub prints version like rv2.beta.8
|
||||
*/
|
||||
public final class DefaultVersion extends BaseVersion
|
||||
{
|
||||
/**
|
||||
* @param revision natural number
|
||||
* @param channel either alpha, beta or release
|
||||
* @param build natural number
|
||||
*/
|
||||
public DefaultVersion( int revision, Channel channel, int build )
|
||||
{
|
||||
super( revision, channel, build );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNewerAs( Version maybeOlder )
|
||||
{
|
||||
if ( this.revision() > maybeOlder.revision() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( this.channel().compareTo( maybeOlder.channel() ) > 0 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.build() > maybeOlder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
/**
|
||||
* Exceptional template for {@link Version}, when the mod does not want a check
|
||||
*/
|
||||
public final class DoNotCheckVersion extends BaseVersion
|
||||
{
|
||||
public DoNotCheckVersion()
|
||||
{
|
||||
super( Integer.MAX_VALUE, Channel.Release, Integer.MAX_VALUE );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNewerAs( Version maybeOlder )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatted()
|
||||
{
|
||||
return "dev build";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
/**
|
||||
* Exceptional template when the {@link Version} could not be retrieved
|
||||
*/
|
||||
public final class MissingVersion extends BaseVersion
|
||||
{
|
||||
public MissingVersion()
|
||||
{
|
||||
super( 0, Channel.Alpha, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maybeOlder ignored
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
@Override
|
||||
public boolean isNewerAs( Version maybeOlder )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatted()
|
||||
{
|
||||
return "missing";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for {@link VersionParser} to check if the check is happening in developer environment or in a pull request.
|
||||
*
|
||||
* In that case ignore the check.
|
||||
*/
|
||||
public final class ModVersionFetcher implements VersionFetcher
|
||||
{
|
||||
private final String rawModVersion;
|
||||
private final VersionParser parser;
|
||||
|
||||
public ModVersionFetcher( String rawModVersion, VersionParser parser )
|
||||
{
|
||||
this.rawModVersion = rawModVersion;
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses only, if not checked in developer environment or in a pull request
|
||||
*
|
||||
* @return {@link DoNotCheckVersion} if in developer environment or pull request, else the parsed {@link Version}
|
||||
*/
|
||||
@Override
|
||||
public Version get()
|
||||
{
|
||||
if ( this.rawModVersion.equals( "@version@" ) || this.rawModVersion.contains( "pr" ) )
|
||||
{
|
||||
return new DoNotCheckVersion();
|
||||
}
|
||||
else
|
||||
{
|
||||
final Version version = this.parser.parse( this.rawModVersion );
|
||||
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
/**
|
||||
* Stores version information, which are easily compared
|
||||
*/
|
||||
public interface Version
|
||||
{
|
||||
/**
|
||||
* @return revision of this version
|
||||
*/
|
||||
int revision();
|
||||
|
||||
/**
|
||||
* @return channel of this version
|
||||
*/
|
||||
Channel channel();
|
||||
|
||||
/**
|
||||
* @return build of this version
|
||||
*/
|
||||
int build();
|
||||
|
||||
/**
|
||||
* A version is never if these criteria are met:
|
||||
* if the current revision is higher than the compared revision OR
|
||||
* if revision are equal and the current channel is higher than the compared channel (Release > Beta > Alpha) OR
|
||||
* if revision, channel are equal and the build is higher than the compared build
|
||||
*
|
||||
* @return true if criteria are met
|
||||
*/
|
||||
boolean isNewerAs( Version maybeOlder );
|
||||
|
||||
/**
|
||||
* Prints the revision, channel and build into a common displayed way
|
||||
*
|
||||
* rv2-beta-8
|
||||
*
|
||||
* @return formatted version
|
||||
*/
|
||||
String formatted();
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
|
||||
|
||||
/**
|
||||
* Seperate config file to handle the version checker
|
||||
*/
|
||||
public final class VersionCheckerConfig
|
||||
{
|
||||
private static final int DEFAULT_INTERVAL_HOURS = 24;
|
||||
private static final int MIN_INTERVAL_HOURS = 0;
|
||||
private static final int MAX_INTERVAL_HOURS = 7 * 24;
|
||||
|
||||
private final Configuration config;
|
||||
|
||||
private final boolean isEnabled;
|
||||
|
||||
private final String lastCheck;
|
||||
private final int interval;
|
||||
|
||||
private final String level;
|
||||
|
||||
private final boolean shouldNotifyPlayer;
|
||||
private final boolean shouldPostChangelog;
|
||||
|
||||
/**
|
||||
* @param file requires fully qualified file in which the config is saved
|
||||
*/
|
||||
public VersionCheckerConfig( File file )
|
||||
{
|
||||
this.config = new Configuration( file );
|
||||
|
||||
// initializes default values by caching
|
||||
this.isEnabled = this.config.getBoolean( "enabled", "general", true, "If true, the version checker is enabled. Acts as a master switch." );
|
||||
|
||||
this.lastCheck = this.config.getString( "lastCheck", "cache", "0", "The number of milliseconds since January 1, 1970, 00:00:00 GMT of the last successful check." );
|
||||
this.interval = this.config.getInt( "interval", "cache", DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS, MAX_INTERVAL_HOURS, "Waits as many hours, until it checks again." );
|
||||
|
||||
this.level = this.config.getString( "level", "channel", "Beta", "Determines the channel level which should be checked for updates. Can be either Release, Beta or Alpha." );
|
||||
|
||||
this.shouldNotifyPlayer = this.config.getBoolean( "notify", "client", true, "If true, the player is getting a notification, that a new version is available." );
|
||||
this.shouldPostChangelog = this.config.getBoolean( "changelog", "client", true, "If true, the player is getting a notification including changelog. Only happens if notification are enabled." );
|
||||
}
|
||||
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
public String lastCheck()
|
||||
{
|
||||
return this.lastCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the current date in milli seconds into the "lastCheck" field of the config
|
||||
* and makes it persistent.
|
||||
*/
|
||||
public void updateLastCheck()
|
||||
{
|
||||
final Date now = new Date();
|
||||
final long nowInMs = now.getTime();
|
||||
final String nowAsString = Long.toString( nowInMs );
|
||||
|
||||
this.config.get( "cache", "lastCheck", "0" ).set( nowAsString );
|
||||
|
||||
this.config.save();
|
||||
}
|
||||
|
||||
public int interval()
|
||||
{
|
||||
return this.interval;
|
||||
}
|
||||
|
||||
public String level()
|
||||
{
|
||||
return this.level;
|
||||
}
|
||||
|
||||
public boolean shouldNotifyPlayer()
|
||||
{
|
||||
return this.shouldNotifyPlayer;
|
||||
}
|
||||
|
||||
public boolean shouldPostChangelog()
|
||||
{
|
||||
return this.shouldPostChangelog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
/**
|
||||
* Processes base information to retrieve a {@link Version}
|
||||
*/
|
||||
public interface VersionFetcher
|
||||
{
|
||||
Version get();
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package appeng.services.version;
|
||||
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* can parse a version in form of rv2-beta-8 or rv2.beta.8
|
||||
*/
|
||||
public final class VersionParser
|
||||
{
|
||||
private static final Pattern PATTERN_DOT = Pattern.compile( "\\." );
|
||||
private static final Pattern PATTERN_DASH = Pattern.compile( "-" );
|
||||
private static final Pattern PATTERN_REVISION = Pattern.compile( "[^0-9]+" );
|
||||
private static final Pattern PATTERN_BUILD = Pattern.compile( "[^0-9]+" );
|
||||
private static final Pattern PATTERN_NATURAL = Pattern.compile( "[0-9]+" );
|
||||
private static final Pattern PATTERN_VALID_REVISION = Pattern.compile( "^rv\\d+$" );
|
||||
|
||||
/**
|
||||
* Parses the {@link Version} out of a String
|
||||
*
|
||||
* @param raw String in form of rv2-beta-8 or rv2.beta.8
|
||||
*
|
||||
* @return {@link Version} encoded in the raw String
|
||||
*
|
||||
* @throws AssertionError if raw String does not match pattern of a {@link Version}
|
||||
*/
|
||||
public Version parse( String raw )
|
||||
{
|
||||
final String transformed = this.transformDelimiter( raw );
|
||||
final String[] split = transformed.split( "_" );
|
||||
|
||||
return this.parseVersion( split );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all "." and "-" into "_" to make them uniform
|
||||
*
|
||||
* @param raw raw version string containing "." or "-"
|
||||
*
|
||||
* @return transformed raw, where "." and "-" are replaced by "_"
|
||||
*/
|
||||
private String transformDelimiter( String raw )
|
||||
{
|
||||
assert raw.contains( "." ) || raw.contains( "-" );
|
||||
|
||||
final String withoutDot = PATTERN_DOT.matcher( raw ).replaceAll( "_" );
|
||||
final String withoutDash = PATTERN_DASH.matcher( withoutDot ).replaceAll( "_" );
|
||||
|
||||
return withoutDash;
|
||||
}
|
||||
|
||||
/**
|
||||
* parses the {@link Version} out of the split.
|
||||
* The split must have a length of 3,
|
||||
* representing revision, channel and build.
|
||||
*
|
||||
* @param splitRaw raw version split with length of 3
|
||||
*
|
||||
* @return {@link Version} represented by the splitRaw
|
||||
*/
|
||||
private Version parseVersion( String[] splitRaw )
|
||||
{
|
||||
assert splitRaw.length == 3;
|
||||
|
||||
final String rawRevision = splitRaw[0];
|
||||
final String rawChannel = splitRaw[1];
|
||||
final String rawBuild = splitRaw[2];
|
||||
|
||||
final int revision = this.parseRevision( rawRevision );
|
||||
final Channel channel = this.parseChannel( rawChannel );
|
||||
final int build = this.parseBuild( rawBuild );
|
||||
|
||||
return new DefaultVersion( revision, channel, build );
|
||||
}
|
||||
|
||||
/**
|
||||
* A revision starts with the keyword "rv", followed by a natural number
|
||||
*
|
||||
* @param rawRevision String containing the revision number
|
||||
*
|
||||
* @return revision number
|
||||
*/
|
||||
private int parseRevision( String rawRevision )
|
||||
{
|
||||
assert PATTERN_VALID_REVISION.matcher( rawRevision ).matches();
|
||||
|
||||
final int revision = new Scanner( rawRevision ).useDelimiter( PATTERN_REVISION ).nextInt();
|
||||
|
||||
return revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* A channel is atm either one of {@link Channel#Alpha}, {@link Channel#Beta} or {@link Channel#Release}
|
||||
*
|
||||
* @param rawChannel String containing the channel
|
||||
*
|
||||
* @return matching {@link Channel} to the String
|
||||
*/
|
||||
private Channel parseChannel( String rawChannel )
|
||||
{
|
||||
assert rawChannel.equalsIgnoreCase( Channel.Alpha.name() ) || rawChannel.equalsIgnoreCase( Channel.Beta.name() ) || rawChannel.equalsIgnoreCase( Channel.Release.name() );
|
||||
|
||||
for ( Channel channel : Channel.values() )
|
||||
{
|
||||
if ( channel.name().equalsIgnoreCase( rawChannel ) )
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidParameterException( "Raw channel did not contain any of the pre-programmed types." );
|
||||
}
|
||||
|
||||
/**
|
||||
* A build is just a natural number
|
||||
*
|
||||
* @param rawBuild String containing the build number
|
||||
*
|
||||
* @return build number
|
||||
*/
|
||||
private int parseBuild( String rawBuild )
|
||||
{
|
||||
assert PATTERN_NATURAL.matcher( rawBuild ).matches();
|
||||
|
||||
final int build = new Scanner( rawBuild ).useDelimiter( PATTERN_BUILD ).nextInt();
|
||||
|
||||
return build;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package appeng.services.version.github;
|
||||
|
||||
|
||||
import appeng.services.version.Version;
|
||||
|
||||
|
||||
/**
|
||||
* Default template when a {@link FormattedRelease} is needed.
|
||||
*/
|
||||
public final class DefaultFormattedRelease implements FormattedRelease
|
||||
{
|
||||
private final Version version;
|
||||
private final String changelog;
|
||||
|
||||
public DefaultFormattedRelease( Version version, String changelog )
|
||||
{
|
||||
this.version = version;
|
||||
this.changelog = changelog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String changelog()
|
||||
{
|
||||
return this.changelog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version version()
|
||||
{
|
||||
return this.version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package appeng.services.version.github;
|
||||
|
||||
|
||||
import appeng.services.version.Version;
|
||||
|
||||
|
||||
/**
|
||||
* Represents the acquired, processed information through github about a release of Applied Energistics 2
|
||||
*/
|
||||
public interface FormattedRelease
|
||||
{
|
||||
/**
|
||||
* @return changelog
|
||||
*/
|
||||
String changelog();
|
||||
|
||||
/**
|
||||
* @return processed version
|
||||
*/
|
||||
Version version();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package appeng.services.version.github;
|
||||
|
||||
|
||||
import appeng.services.version.MissingVersion;
|
||||
import appeng.services.version.Version;
|
||||
|
||||
|
||||
/**
|
||||
* Exceptional template, when no meaningful {@link FormattedRelease} could be obtained
|
||||
*/
|
||||
public final class MissingFormattedRelease implements FormattedRelease
|
||||
{
|
||||
private final Version version;
|
||||
|
||||
public MissingFormattedRelease()
|
||||
{
|
||||
this.version = new MissingVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return empty string
|
||||
*/
|
||||
@Override
|
||||
public String changelog()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link MissingVersion}
|
||||
*/
|
||||
@Override
|
||||
public Version version()
|
||||
{
|
||||
return this.version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package appeng.services.version.github;
|
||||
|
||||
|
||||
/**
|
||||
* Template class for Gson to write values from the Json Object into an actual class
|
||||
*/
|
||||
@SuppressWarnings( "ALL" )
|
||||
public class Release
|
||||
{
|
||||
/**
|
||||
* name of the tag it is saved
|
||||
*/
|
||||
public String tag_name;
|
||||
|
||||
/**
|
||||
* Contains the changelog
|
||||
*/
|
||||
public String body;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package appeng.services.version.github;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import appeng.core.AELog;
|
||||
import appeng.services.version.Channel;
|
||||
import appeng.services.version.Version;
|
||||
import appeng.services.version.VersionCheckerConfig;
|
||||
import appeng.services.version.VersionParser;
|
||||
|
||||
|
||||
public final class ReleaseFetcher
|
||||
{
|
||||
private static final String GITHUB_RELEASES_URL = "https://api.github.com/repos/AppliedEnergistics/Applied-Energistics-2/releases";
|
||||
private static final FormattedRelease EXCEPTIONAL_RELEASE = new MissingFormattedRelease();
|
||||
|
||||
private final VersionCheckerConfig config;
|
||||
private final VersionParser parser;
|
||||
|
||||
public ReleaseFetcher( VersionCheckerConfig config, VersionParser parser )
|
||||
{
|
||||
this.config = config;
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
public FormattedRelease get()
|
||||
{
|
||||
final Gson gson = new Gson();
|
||||
final Type type = new ReleasesTypeToken().getType();
|
||||
|
||||
try
|
||||
{
|
||||
final URL releasesURL = new URL( GITHUB_RELEASES_URL );
|
||||
final String rawReleases = this.getRawReleases( releasesURL );
|
||||
|
||||
this.config.updateLastCheck();
|
||||
|
||||
final List<Release> releases = gson.fromJson( rawReleases, type );
|
||||
final FormattedRelease latestFitRelease = this.getLatestFitRelease( releases );
|
||||
|
||||
return latestFitRelease;
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
AELog.error( e );
|
||||
|
||||
return EXCEPTIONAL_RELEASE;
|
||||
}
|
||||
}
|
||||
|
||||
private String getRawReleases( URL url ) throws IOException
|
||||
{
|
||||
return IOUtils.toString( url );
|
||||
}
|
||||
|
||||
private FormattedRelease getLatestFitRelease( Iterable<Release> releases )
|
||||
{
|
||||
final String levelInConfig = this.config.level();
|
||||
final Channel level = Channel.valueOf( levelInConfig );
|
||||
final int levelOrdinal = level.ordinal();
|
||||
|
||||
for ( Release release : releases )
|
||||
{
|
||||
final String rawVersion = release.tag_name;
|
||||
final String changelog = release.body;
|
||||
|
||||
final Version version = this.parser.parse( rawVersion );
|
||||
|
||||
if ( version.channel().ordinal() >= levelOrdinal )
|
||||
{
|
||||
return new DefaultFormattedRelease( version, changelog );
|
||||
}
|
||||
}
|
||||
|
||||
return EXCEPTIONAL_RELEASE;
|
||||
}
|
||||
|
||||
private static final class ReleasesTypeToken extends TypeToken<List<Release>>
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user