Merge pull request #1046 from thatsIch/b-976-version-checker

Fixes #976 Now uses GitHub to retrieve most current version
This commit is contained in:
thatsIch 2015-03-17 20:23:14 +01:00
commit 34597a4e21
33 changed files with 2023 additions and 169 deletions

View file

@ -79,6 +79,7 @@ jar {
minecraft {
version = config.minecraft_version + "-" + config.forge_version
replaceIn "AEConfig.java"
replace "@version@", project.version
replace "@aechannel@", config.aechannel

View file

@ -55,9 +55,6 @@ public class AEConfig extends Configuration implements IConfigurableObject, ICon
public static final double TUNNEL_POWER_LOSS = 0.05;
public String latestVersion = VERSION;
public long latestTimeStamp = 0;
public static final String VERSION = "@version@";
public static final String CHANNEL = "@aechannel@";
@ -149,7 +146,7 @@ public class AEConfig extends Configuration implements IConfigurableObject, ICon
public boolean disableColoredCableRecipesInNEI = true;
public boolean updatable = false;
final private File myPath;
final private File configFile;
public double meteoriteClusterChance = 0.1;
public double meteoriteSpawnChance = 0.3;
@ -224,22 +221,20 @@ public class AEConfig extends Configuration implements IConfigurableObject, ICon
public String getFilePath()
{
return this.myPath.toString();
return this.configFile.toString();
}
public AEConfig(String path) {
super( new File( path + "AppliedEnergistics2.cfg" ) );
this.myPath = new File( path + "AppliedEnergistics2.cfg" );
public AEConfig( File configFile ) {
super( configFile );
this.configFile = configFile;
FMLCommonHandler.instance().bus().register( this );
final double DEFAULT_BC_EXCHANGE = 5.0;
final double DEFAULT_IC2_EXCHANGE = 2.0;
final double DEFAULT_RTC_EXCHANGE = 1.0 / 11256.0;
final double DEFAULT_RF_EXCHANGE = 0.5;
final double DEFAULT_MEKANISM_EXCHANGE = 0.2;
PowerUnits.MJ.conversionRatio = this.get( "PowerRatios", "BuildCraft", DEFAULT_BC_EXCHANGE ).getDouble( DEFAULT_BC_EXCHANGE );
PowerUnits.MK.conversionRatio = this.get( "PowerRatios", "Mekanism", DEFAULT_MEKANISM_EXCHANGE ).getDouble( DEFAULT_MEKANISM_EXCHANGE );
PowerUnits.EU.conversionRatio = this.get( "PowerRatios", "IC2", DEFAULT_IC2_EXCHANGE ).getDouble( DEFAULT_IC2_EXCHANGE );
PowerUnits.WA.conversionRatio = this.get( "PowerRatios", "RotaryCraft", DEFAULT_RTC_EXCHANGE ).getDouble( DEFAULT_RTC_EXCHANGE );
@ -340,19 +335,6 @@ public class AEConfig extends Configuration implements IConfigurableObject, ICon
this.craftingCalculationTimePerTick );
}
if ( this.isFeatureEnabled( AEFeature.VersionChecker ) )
{
try
{
this.latestVersion = this.get( "VersionChecker", "LatestVersion", "" ).getString();
this.latestTimeStamp = Long.parseLong( this.get( "VersionChecker", "LatestTimeStamp", "" ).getString() );
}
catch (NumberFormatException err)
{
this.latestTimeStamp = 0;
}
}
this.updatable = true;
}
@ -411,12 +393,6 @@ public class AEConfig extends Configuration implements IConfigurableObject, ICon
@Override
public void save()
{
if ( this.isFeatureEnabled( AEFeature.VersionChecker ) )
{
this.get( "VersionChecker", "LatestVersion", this.latestVersion ).set( this.latestVersion );
this.get( "VersionChecker", "LatestTimeStamp", "" ).set( Long.toString( this.latestTimeStamp ) );
}
if ( this.isFeatureEnabled( AEFeature.SpatialIO ) )
{
this.get( "spatialio", "storageBiomeID", this.storageBiomeID ).set( this.storageBiomeID );

View file

@ -22,8 +22,6 @@ package appeng.core;
import java.io.File;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Stopwatch;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.Mod;
@ -37,6 +35,8 @@ import cpw.mods.fml.common.event.FMLServerStartingEvent;
import cpw.mods.fml.common.event.FMLServerStoppingEvent;
import cpw.mods.fml.common.network.NetworkRegistry;
import com.google.common.base.Stopwatch;
import appeng.core.crash.CrashEnhancement;
import appeng.core.crash.CrashInfo;
import appeng.core.features.AEFeature;
@ -47,6 +47,7 @@ import appeng.integration.IntegrationRegistry;
import appeng.integration.IntegrationType;
import appeng.server.AECommand;
import appeng.services.VersionChecker;
import appeng.services.version.VersionCheckerConfig;
import appeng.util.Platform;
@ -70,7 +71,7 @@ public class AppEng
private final IMCHandler imcHandler;
private String configPath;
private File configDirectory;
public AppEng()
{
@ -81,9 +82,9 @@ public class AppEng
FMLCommonHandler.instance().registerCrashCallable( new CrashEnhancement( CrashInfo.MOD_VERSION ) );
}
public String getConfigPath()
public final File getConfigDirectory()
{
return this.configPath;
return this.configDirectory;
}
public boolean isIntegrationEnabled( IntegrationType integrationName )
@ -104,11 +105,16 @@ public class AppEng
CommonHelper.proxy.missingCoreMod();
}
Stopwatch star = Stopwatch.createStarted();
this.configPath = event.getModConfigurationDirectory().getPath() + File.separator + "AppliedEnergistics2" + File.separator;
Stopwatch watch = Stopwatch.createStarted();
this.configDirectory = new File(event.getModConfigurationDirectory().getPath(), "AppliedEnergistics2");
AEConfig.instance = new AEConfig( this.configPath );
FacadeConfig.instance = new FacadeConfig( this.configPath );
final File configFile = new File( this.configDirectory, "AppliedEnergistics2.cfg");
final File facadeFile = new File( this.configDirectory, "Facades.cfg" );
final File versionFile = new File( this.configDirectory, "VersionChecker.cfg" );
AEConfig.instance = new AEConfig( configFile );
FacadeConfig.instance = new FacadeConfig( facadeFile );
final VersionCheckerConfig versionCheckerConfig = new VersionCheckerConfig( versionFile );
AELog.info( "Pre Initialization ( started )" );
@ -121,19 +127,23 @@ public class AppEng
Registration.INSTANCE.preInitialize( event );
if ( AEConfig.instance.isFeatureEnabled( AEFeature.VersionChecker ) )
if ( versionCheckerConfig.isEnabled() )
{
AELog.info( "Starting VersionChecker" );
this.startService( "AE2 VersionChecker", new Thread( new VersionChecker() ) );
final VersionChecker versionChecker = new VersionChecker( versionCheckerConfig );
final Thread versionCheckerThread = new Thread( versionChecker );
this.startService( "AE2 VersionChecker", versionCheckerThread );
}
AELog.info( "Pre Initialization ( ended after " + star.elapsed( TimeUnit.MILLISECONDS ) + "ms )" );
AELog.info( "Pre Initialization ( ended after " + watch.elapsed( TimeUnit.MILLISECONDS ) + "ms )" );
}
private void startService( String serviceName, Thread thread )
{
thread.setName( serviceName );
thread.setPriority( Thread.MIN_PRIORITY );
AELog.info( "Starting " + serviceName );
thread.start();
}

View file

@ -35,8 +35,8 @@ public class FacadeConfig extends Configuration
public static FacadeConfig instance;
final Pattern replacementPattern;
public FacadeConfig(String path) {
super( new File( path + "Facades.cfg" ) );
public FacadeConfig( File facadeFile ) {
super( facadeFile );
this.replacementPattern = Pattern.compile( "[^a-zA-Z0-9]" );
}

View file

@ -21,10 +21,6 @@ package appeng.core;
import java.lang.reflect.Field;
import com.google.common.base.Optional;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import net.minecraft.item.crafting.CraftingManager;
import net.minecraft.util.WeightedRandomChestContent;
import net.minecraft.world.biome.BiomeGenBase;
@ -41,6 +37,10 @@ import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.common.registry.VillagerRegistry;
import com.google.common.base.Optional;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import appeng.api.AEApi;
import appeng.api.IAppEngApi;
import appeng.api.config.Upgrades;
@ -546,7 +546,7 @@ public final class Registration
ItemMultiMaterial.instance.makeUnique();
if ( AEConfig.instance.isFeatureEnabled( AEFeature.CustomRecipes ) )
this.recipeHandler.parseRecipes( new ConfigLoader( AppEng.instance.getConfigPath() ), "index.recipe" );
this.recipeHandler.parseRecipes( new ConfigLoader( AppEng.instance.getConfigDirectory() ), "index.recipe" );
else
this.recipeHandler.parseRecipes( new JarLoader( "/assets/appliedenergistics2/recipes/" ), "index.recipe" );

View file

@ -62,7 +62,7 @@ public enum AEFeature
MassCannonBlockDamage("BlockFeatures"), TinyTNTBlockDamage("BlockFeatures"), Facades("Facades"),
VersionChecker("Services"), UnsupportedDeveloperTools("Misc", false), Creative("Misc"),
UnsupportedDeveloperTools("Misc", false), Creative("Misc"),
GrinderLogging("Misc", false), Logging("Misc"), IntegrationLogging("Misc", false), CustomRecipes("Crafting", false), WebsiteRecipes("Misc", false),

View file

@ -29,7 +29,7 @@ import appeng.core.WorldSettings;
import appeng.core.sync.AppEngPacket;
import appeng.core.sync.network.INetworkInfo;
import appeng.core.sync.network.NetworkHandler;
import appeng.services.helpers.ICompassCallback;
import appeng.services.compass.ICompassCallback;
public class PacketCompassRequest extends AppEngPacket implements ICompassCallback
{

View file

@ -33,7 +33,7 @@ import appeng.core.AEConfig;
import appeng.core.WorldSettings;
import appeng.core.features.registries.WorldGenRegistry;
import appeng.helpers.MeteoritePlacer;
import appeng.services.helpers.ICompassCallback;
import appeng.services.compass.ICompassCallback;
import appeng.util.Platform;
final public class MeteoriteWorldGen implements IWorldGenerator

View file

@ -18,6 +18,7 @@
package appeng.recipes.loader;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@ -25,19 +26,21 @@ import java.io.InputStreamReader;
import appeng.api.recipes.IRecipeLoader;
public class ConfigLoader implements IRecipeLoader
public final class ConfigLoader implements IRecipeLoader
{
private final File rootDirectory;
private final String rootPath;
public ConfigLoader(String s) {
this.rootPath = s;
public ConfigLoader( File rootDirectory )
{
this.rootDirectory = rootDirectory;
}
@Override
public BufferedReader getFile(String s) throws Exception
public BufferedReader getFile( String s ) throws Exception
{
File f = new File( this.rootPath + s );
final File f = new File( this.rootDirectory, s );
return new BufferedReader( new InputStreamReader( new FileInputStream( f ), "UTF-8" ) );
}
}

View file

@ -32,8 +32,8 @@ import net.minecraft.world.chunk.Chunk;
import appeng.api.AEApi;
import appeng.api.util.DimensionalCoord;
import appeng.services.helpers.CompassReader;
import appeng.services.helpers.ICompassCallback;
import appeng.services.compass.CompassReader;
import appeng.services.compass.ICompassCallback;
public class CompassService implements ThreadFactory
{

View file

@ -19,133 +19,167 @@
package appeng.services;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.minecraft.nbt.NBTTagCompound;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.event.FMLInterModComms;
import appeng.core.AEConfig;
import appeng.core.AELog;
import appeng.core.AppEng;
import appeng.services.version.github.FormattedRelease;
import appeng.services.version.github.ReleaseFetcher;
import appeng.services.version.ModVersionFetcher;
import appeng.services.version.Version;
import appeng.services.version.VersionCheckerConfig;
import appeng.services.version.VersionFetcher;
import appeng.services.version.VersionParser;
public class VersionChecker implements Runnable
/**
* Tries to connect to GitHub to retrieve the most current build.
* After comparison with the local version, several path can be chosen.
*
* If the local version is invalid, somebody might have build that version themselves
* or it is run in a developer environment, then nothing needs to be done.
*
* If GitHub can not be reached, then either is GitHub down
* or the connection to GitHub disturbed, then nothing needs to be done,
* since no comparison can be reached
*
* If the version was just recently checked, then no need to poll again.
* Nobody wants to bother to update several times a day.
*
* Config enables to fine-tune when a version is considered newer
*
* If the local version is newer or equal to the GitHub version,
* then no update needs to be posted
*
* Only after all that cases, if the external version is higher than the local,
* use Version Checker Mod and post several information needed for it to update the mod.
*/
public final class VersionChecker implements Runnable
{
private static final int FOUR_HOURS = 1000 * 3600 * 4;
private static final int SEC_TO_HOUR = 3600;
private static final int MS_TO_SEC = 1000;
private final VersionCheckerConfig config;
private final long delay;
public VersionChecker()
public VersionChecker( VersionCheckerConfig config )
{
final long now = new Date().getTime();
final long timeDiff = now - AEConfig.instance.latestTimeStamp;
this.delay = Math.max( 1, FOUR_HOURS - timeDiff);
this.config = config;
}
@Override
public void run()
{
try
Thread.yield();
final String rawLastCheck = this.config.lastCheck();
final long lastCheck = Long.parseLong( rawLastCheck );
final Date now = new Date();
final long nowInMs = now.getTime();
final long intervalInMs = this.config.interval() * SEC_TO_HOUR * MS_TO_SEC;
final long lastAfterInterval = lastCheck + intervalInMs;
this.processInterval( nowInMs, lastAfterInterval );
AELog.info( "Stopping AE2 VersionChecker" );
}
/**
* checks if enough time since last check has expired
*
* @param nowInMs now in milli seconds
* @param lastAfterInterval last version check including the interval defined in the config
*/
private void processInterval( long nowInMs, long lastAfterInterval )
{
if ( nowInMs > lastAfterInterval )
{
this.sleep( this.delay );
final String rawModVersion = AEConfig.VERSION;
final VersionParser parser = new VersionParser();
final VersionFetcher modFetcher = new ModVersionFetcher( rawModVersion, parser );
final ReleaseFetcher githubFetcher = new ReleaseFetcher( this.config, parser );
final Version modVersion = modFetcher.get();
final FormattedRelease githubRelease = githubFetcher.get();
this.processVersions( modVersion, githubRelease );
}
catch (InterruptedException e)
else
{
// :(
}
while (true)
{
Thread.yield();
try
{
String MCVersion = cpw.mods.fml.common.Loader.instance().getMCVersionString().replace( "Minecraft ", "" );
URL url = new URL( "http://feeds.ae-mod.info/latest.json?VersionMC=" + MCVersion + "&Channel=" + AEConfig.CHANNEL + "&CurrentVersion="
+ AEConfig.VERSION );
URLConnection yc = url.openConnection();
yc.setRequestProperty( "User-Agent", "AE2/" + AEConfig.VERSION + " (Channel:" + AEConfig.CHANNEL + ',' + MCVersion.replace( " ", ":" ) + ')' );
BufferedReader in = new BufferedReader( new InputStreamReader( yc.getInputStream() ) );
StringBuilder Version = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null)
Version.append( inputLine );
in.close();
if ( Version.length() > 2 )
{
JsonElement element = (new JsonParser()).parse( Version.toString() );
int version = element.getAsJsonObject().get( "FormatVersion" ).getAsInt();
if ( version == 1 )
{
JsonObject Meta = element.getAsJsonObject().get( "Meta" ).getAsJsonObject();
JsonArray Versions = element.getAsJsonObject().get( "Versions" ).getAsJsonArray();
if ( Versions.size() > 0 )
{
JsonObject Latest = Versions.get( 0 ).getAsJsonObject();
AEConfig.instance.latestVersion = Latest.get( "Version" ).getAsString();
AEConfig.instance.latestTimeStamp = (new Date()).getTime();
AEConfig.instance.save();
if ( !AEConfig.VERSION.equals( AEConfig.instance.latestVersion ) )
{
NBTTagCompound versionInf = new NBTTagCompound();
versionInf.setString( "modDisplayName", "Applied Energistics 2" );
versionInf.setString( "oldVersion", AEConfig.VERSION );
versionInf.setString( "newVersion", AEConfig.instance.latestVersion );
versionInf.setString( "updateUrl", Latest.get( "UserBuild" ).getAsString() );
versionInf.setBoolean( "isDirectLink", true );
JsonElement changeLog = Latest.get( "ChangeLog" );
if ( changeLog == null )
versionInf.setString( "changeLog", "For full change log please see: " + Meta.get( "DownloadLink" ).getAsString() );
else
versionInf.setString( "changeLog", changeLog.getAsString() );
versionInf.setString( "newFileName", "appliedenergistics2-" + AEConfig.instance.latestVersion + ".jar" );
FMLInterModComms.sendRuntimeMessage( AppEng.instance, "VersionChecker", "addUpdate", versionInf );
AELog.info( "Stopping VersionChecker" );
return;
}
}
}
}
this.sleep( FOUR_HOURS );
}
catch (Exception e)
{
try
{
this.sleep( FOUR_HOURS );
}
catch (InterruptedException e1)
{
AELog.error( e );
}
}
AELog.info( "Last check was just recently." );
}
}
private void sleep(long i) throws InterruptedException
/**
* Checks if the retrieved version is newer as the current mod version.
* Will notify player if config is enabled.
*
* @param modVersion version of mod
* @param githubRelease release retrieved through github
*/
private void processVersions( Version modVersion, FormattedRelease githubRelease )
{
Thread.sleep( i );
final Version githubVersion = githubRelease.version();
final String modFormatted = modVersion.formatted();
final String ghFormatted = githubVersion.formatted();
if ( githubVersion.isNewerAs( modVersion ) )
{
final String changelog = githubRelease.changelog();
if ( this.config.shouldNotifyPlayer() )
{
AELog.info( "Newer version is available: " + ghFormatted + " (found) > " + modFormatted + " (current)" );
if ( this.config.shouldPostChangelog() )
{
AELog.info( "Changelog: " + changelog );
}
}
this.interactWithVersionCheckerMod( modFormatted, ghFormatted, changelog );
}
else
{
AELog.info( "No newer version is available: " + ghFormatted + "(found) < " + modFormatted + " (current)");
}
}
/**
* Checks if the version checker mod is installed and handles it depending on that information
*
* @param modFormatted mod version formatted as rv2-beta-8
* @param ghFormatted retrieved github version formatted as rv2-beta-8
* @param changelog retrieved github changelog
*/
private void interactWithVersionCheckerMod( String modFormatted, String ghFormatted, String changelog )
{
if ( Loader.isModLoaded( "VersionChecker" ) )
{
final NBTTagCompound versionInf = new NBTTagCompound();
versionInf.setString( "modDisplayName", AppEng.MOD_NAME );
versionInf.setString( "oldVersion", modFormatted );
versionInf.setString( "newVersion", ghFormatted );
versionInf.setString( "updateUrl", "http://ae-mod.info/builds/appliedenergistics2-" + ghFormatted + ".jar" );
versionInf.setBoolean( "isDirectLink", true );
if ( !changelog.isEmpty() )
{
versionInf.setString( "changeLog", changelog );
}
versionInf.setString( "newFileName", "appliedenergistics2-" + ghFormatted + ".jar" );
FMLInterModComms.sendRuntimeMessage( AppEng.instance, "VersionChecker", "addUpdate", versionInf );
AELog.info( "Reported new version to VersionChecker mod." );
}
else
{
AELog.info( "VersionChecker mod is not installed; Proceeding." );
}
}
}

View file

@ -16,7 +16,7 @@
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.services.helpers;
package appeng.services.compass;
public class CompassException extends RuntimeException
{

View file

@ -16,7 +16,7 @@
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.services.helpers;
package appeng.services.compass;
import java.io.File;
import java.util.HashMap;

View file

@ -16,7 +16,7 @@
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.services.helpers;
package appeng.services.compass;
import java.io.File;

View file

@ -16,7 +16,7 @@
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.services.helpers;
package appeng.services.compass;
public interface ICompassCallback
{

View file

@ -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 +
'}';
}
}

View file

@ -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
}

View file

@ -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();
}
}

View file

@ -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";
}
}

View file

@ -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";
}
}

View file

@ -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;
}
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -0,0 +1,10 @@
package appeng.services.version;
/**
* Processes base information to retrieve a {@link Version}
*/
public interface VersionFetcher
{
Version get();
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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>>
{
}
}

View file

@ -0,0 +1,80 @@
package appeng.services.version;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link VersionParser}
*/
public final class VersionParserTest
{
private static final String GITHUB_VERSION = "rv2.beta.8";
private static final String GITHUB_INVALID_REVISION = "2.beta.8";
private static final String GITHUB_INVALID_CHANNEL = "rv2.gamma.8";
private static final String GITHUB_INVALID_BUILD = "rv2.beta.b8";
private static final String MOD_VERSION = "rv2-beta-8";
private static final String MOD_INVALID_REVISION = "2-beta-8";
private static final String MOD_INVALID_CHANNEL = "rv2-gamma-8";
private static final String MOD_INVALID_BUILD = "rv2-beta-b8";
private static final DefaultVersion VERSION = new DefaultVersion( 2, Channel.Beta, 8 );
private final VersionParser parser;
public VersionParserTest()
{
this.parser = new VersionParser();
}
@Test
public void testParseGitHub_shouldPass()
{
assertTrue( this.parser.parse( GITHUB_VERSION ).equals( VERSION ) );
}
@Test( expected = AssertionError.class )
public void parseGH_InvalidRevision_NotPass()
{
assertFalse( this.parser.parse( GITHUB_INVALID_REVISION ).equals( VERSION ) );
}
@Test( expected = AssertionError.class )
public void parseGH_InvalidChannel_NotPass()
{
assertFalse( this.parser.parse( GITHUB_INVALID_CHANNEL ).equals( VERSION ) );
}
@Test( expected = AssertionError.class )
public void parseGH_InvalidBuild_NotPass()
{
assertFalse( this.parser.parse( GITHUB_INVALID_BUILD ).equals( VERSION ) );
}
@Test
public void testParseMod_shouldPass()
{
assertTrue( this.parser.parse( MOD_VERSION ).equals( VERSION ) );
}
@Test( expected = AssertionError.class )
public void parseMod_InvalidRevision_NotPass()
{
assertFalse( this.parser.parse( MOD_INVALID_REVISION ).equals( VERSION ) );
}
@Test( expected = AssertionError.class )
public void parseMod_InvalidChannel_NotPass()
{
assertFalse( this.parser.parse( MOD_INVALID_CHANNEL ).equals( VERSION ) );
}
@Test( expected = AssertionError.class )
public void parseMod_InvalidBuild_NotPass()
{
assertFalse( this.parser.parse( MOD_INVALID_BUILD ).equals( VERSION ) );
}
}

View file

@ -28,7 +28,7 @@ import static org.junit.Assert.assertTrue;
/**
* Tests for {@link UUIDMatcher}
*/
public class UUIDMatcherTest
public final class UUIDMatcherTest
{
private static final String IS_UUID = "03ba29a1-d6bd-32ba-90b2-375e4d65abc9";
private static final String NO_UUID = "no";

File diff suppressed because it is too large Load diff