Added checking for new releases
This commit is contained in:
parent
40c522ec8f
commit
ff6f5b70bc
6 changed files with 290 additions and 28 deletions
|
@ -48,6 +48,7 @@ public class FTLModManager {
|
|||
config.setProperty( "use_default_ui", "false" );
|
||||
config.setProperty( "remember_geometry", "true" );
|
||||
// "update_catalog" doesn't have a default.
|
||||
// "update_app" doesn't have a default.
|
||||
// "manager_geometry" doesn't have a default.
|
||||
|
||||
// Read the config file.
|
||||
|
@ -128,19 +129,30 @@ public class FTLModManager {
|
|||
}
|
||||
|
||||
// Prompt if update_catalog is invalid or hasn't been set.
|
||||
boolean askAboutUpdates = false;
|
||||
String catalogUpdateInterval = config.getProperty( "update_catalog" );
|
||||
String appUpdateInterval = config.getProperty( "update_app" );
|
||||
|
||||
String updateCatalog = config.getProperty( "update_catalog" );
|
||||
if ( updateCatalog == null || !updateCatalog.matches("^true|false$") ) {
|
||||
if ( catalogUpdateInterval == null || !catalogUpdateInterval.matches("^\\d+$") )
|
||||
askAboutUpdates = true;
|
||||
if ( appUpdateInterval == null || !appUpdateInterval.matches("^\\d+$") )
|
||||
askAboutUpdates = true;
|
||||
|
||||
if ( askAboutUpdates ) {
|
||||
String message = "";
|
||||
message += "Would you like Slipstream to periodically\n";
|
||||
message += "download descriptions for the latest mods?\n\n";
|
||||
message += "check for updates and download descriptions\n";
|
||||
message += "for the latest mods?\n\n";
|
||||
message += "You can change this later in modman.cfg.";
|
||||
|
||||
int response = JOptionPane.showConfirmDialog(null, message, "Catalog Updates", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
|
||||
if ( response == JOptionPane.YES_OPTION )
|
||||
config.setProperty( "update_catalog", "true" );
|
||||
else
|
||||
config.setProperty( "update_catalog", "false" );
|
||||
int response = JOptionPane.showConfirmDialog(null, message, "Updates", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
|
||||
if ( response == JOptionPane.YES_OPTION ) {
|
||||
config.setProperty( "update_catalog", "7" );
|
||||
config.setProperty( "update_app", "4" );
|
||||
} else {
|
||||
config.setProperty( "update_catalog", "0" );
|
||||
config.setProperty( "update_app", "0" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
45
src/main/java/net/vhati/modmanager/core/AutoUpdateInfo.java
Normal file
45
src/main/java/net/vhati/modmanager/core/AutoUpdateInfo.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
package net.vhati.modmanager.core;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.vhati.modmanager.core.ComparableVersion;
|
||||
|
||||
|
||||
/**
|
||||
* Holds info about available updates.
|
||||
*/
|
||||
public class AutoUpdateInfo {
|
||||
private ComparableVersion latestVersion = null;
|
||||
private Map<String,String> latestURLs = new TreeMap<String,String>();
|
||||
private Map<ComparableVersion,List<String>> changelog = new TreeMap<ComparableVersion,List<String>>( Collections.reverseOrder() );
|
||||
|
||||
|
||||
public void setLatestVersion( ComparableVersion version ) {
|
||||
latestVersion = version;
|
||||
}
|
||||
|
||||
public ComparableVersion getLatestVersion() {
|
||||
return latestVersion;
|
||||
}
|
||||
|
||||
|
||||
public void putLatestURL( String os, String url ) {
|
||||
latestURLs.put( os, url );
|
||||
}
|
||||
|
||||
public void putChanges( ComparableVersion version, List<String> changeList ) {
|
||||
changelog.put( version, changeList );
|
||||
}
|
||||
|
||||
|
||||
public Map<String,String> getLatestURLs() {
|
||||
return latestURLs;
|
||||
}
|
||||
|
||||
public Map<ComparableVersion,List<String>> getChangelog() {
|
||||
return changelog;
|
||||
}
|
||||
}
|
|
@ -29,6 +29,14 @@ public class SlipstreamConfig {
|
|||
return config.setProperty( key, value );
|
||||
}
|
||||
|
||||
public int getPropertyAsInt( String key, int defaultValue ) {
|
||||
String s = config.getProperty( key );
|
||||
if ( s != null && s.matches("^\\d+$") )
|
||||
return Integer.parseInt( s );
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public String getProperty( String key, String defaultValue ) {
|
||||
return config.getProperty( key, defaultValue );
|
||||
}
|
||||
|
@ -48,7 +56,8 @@ public class SlipstreamConfig {
|
|||
configComments += " allow_zip - Sets whether to treat .zip files as .ftl files. Default: false.\n";
|
||||
configComments += " ftl_dats_path - The path to FTL's resources folder. If invalid, you'll be prompted.\n";
|
||||
configComments += " never_run_ftl - If true, there will be no offer to run FTL after patching. Default: false.\n";
|
||||
configComments += " update_catalog - If true, periodically download descriptions for the latest mods.\n";
|
||||
configComments += " update_catalog - If a number greater than 0, check for new mod descriptions every N days.\n";
|
||||
configComments += " update_app - If a number greater than 0, check for new app version every N days.\n";
|
||||
configComments += " use_default_ui - If true, no attempt will be made to resemble a native GUI. Default: false.\n";
|
||||
configComments += " remember_geometry - If true, window geometry will be saved on exit and restored on startup.\n";
|
||||
configComments += "\n";
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package net.vhati.modmanager.json;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.vhati.modmanager.core.AutoUpdateInfo;
|
||||
import net.vhati.modmanager.core.ComparableVersion;
|
||||
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
public class JacksonAutoUpdateReader {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(JacksonAutoUpdateReader.class);
|
||||
|
||||
|
||||
public static AutoUpdateInfo parse( File jsonFile ) {
|
||||
AutoUpdateInfo aui = new AutoUpdateInfo();
|
||||
|
||||
Exception exception = null;
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure( JsonParser.Feature.ALLOW_SINGLE_QUOTES, true );
|
||||
mapper.setVisibility( PropertyAccessor.FIELD, Visibility.ANY );
|
||||
|
||||
JsonNode rootNode = mapper.readTree( jsonFile );
|
||||
JsonNode historiesNode = rootNode.get( "history_versions" );
|
||||
JsonNode historyNode = historiesNode.get( "1" );
|
||||
|
||||
JsonNode latestNode = historyNode.get( "latest" );
|
||||
aui.setLatestVersion( new ComparableVersion( latestNode.get( "version" ).textValue() ) );
|
||||
|
||||
Iterator<Map.Entry<String,JsonNode>> fieldIt = latestNode.get( "urls" ).fields();
|
||||
while ( fieldIt.hasNext() ) {
|
||||
Map.Entry<String,JsonNode> entry = fieldIt.next();
|
||||
aui.putLatestURL( entry.getKey(), entry.getValue().textValue() );
|
||||
}
|
||||
|
||||
JsonNode changelogNode = historyNode.get( "changelog" );
|
||||
|
||||
for ( JsonNode releaseNode : changelogNode ) {
|
||||
String releaseVersion = releaseNode.get( "version" ).textValue();
|
||||
|
||||
List<String> changeList = new ArrayList<String>( releaseNode.get( "changes" ).size() );
|
||||
for ( JsonNode changeNode : releaseNode.get( "changes" ) ) {
|
||||
changeList.add( changeNode.textValue() );
|
||||
}
|
||||
aui.putChanges( new ComparableVersion( releaseVersion ), changeList );
|
||||
}
|
||||
}
|
||||
catch ( JsonProcessingException e ) {
|
||||
exception = e;
|
||||
}
|
||||
catch ( IOException e ) {
|
||||
exception = e;
|
||||
}
|
||||
if ( exception != null ) {
|
||||
log.error( exception );
|
||||
return null;
|
||||
}
|
||||
|
||||
return aui;
|
||||
}
|
||||
}
|
|
@ -76,7 +76,7 @@ public class URLFetcher {
|
|||
int responseCode = httpConn.getResponseCode();
|
||||
|
||||
if ( responseCode == HttpURLConnection.HTTP_NOT_MODIFIED ) {
|
||||
log.debug( String.format( "The server's \"%s\" has not been modified since the previous check.", httpConn.getURL().getFile() ) );
|
||||
log.debug( String.format( "No need to update \"%s\", the server's copy has not been modified since the previous check.", localFile.getName() ) );
|
||||
|
||||
// Update the local file's timestamp as if it had downloaded.
|
||||
localFile.setLastModified( new Date().getTime() );
|
||||
|
@ -98,7 +98,7 @@ public class URLFetcher {
|
|||
}
|
||||
}
|
||||
else {
|
||||
log.error( String.format( "Download request failed: HTTP Code %d (%s).", responseCode, httpConn.getResponseMessage() ) );
|
||||
log.error( String.format( "Download request failed for \"%s\": HTTP Code %d (%s).", httpConn.getURL(), responseCode, httpConn.getResponseMessage() ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import java.util.HashMap;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.swing.BorderFactory;
|
||||
|
@ -59,6 +60,7 @@ import javax.swing.event.ListSelectionListener;
|
|||
import javax.swing.table.DefaultTableModel;
|
||||
|
||||
import net.vhati.ftldat.FTLDat;
|
||||
import net.vhati.modmanager.core.AutoUpdateInfo;
|
||||
import net.vhati.modmanager.core.ComparableVersion;
|
||||
import net.vhati.modmanager.core.FTLUtilities;
|
||||
import net.vhati.modmanager.core.HashObserver;
|
||||
|
@ -72,6 +74,7 @@ import net.vhati.modmanager.core.ModUtilities;
|
|||
import net.vhati.modmanager.core.Report;
|
||||
import net.vhati.modmanager.core.Report.ReportFormatter;
|
||||
import net.vhati.modmanager.core.SlipstreamConfig;
|
||||
import net.vhati.modmanager.json.JacksonAutoUpdateReader;
|
||||
import net.vhati.modmanager.json.JacksonGrognakCatalogReader;
|
||||
import net.vhati.modmanager.json.URLFetcher;
|
||||
import net.vhati.modmanager.ui.ChecklistTableModel;
|
||||
|
@ -92,26 +95,30 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver
|
|||
private static final Logger log = LogManager.getLogger(ManagerFrame.class);
|
||||
|
||||
public static final String CATALOG_URL = "https://raw.github.com/Vhati/Slipstream-Mod-Manager/master/skel_common/backup/current_catalog.json";
|
||||
public static final String AUTOUPDATE_URL = "https://raw.github.com/Vhati/Slipstream-Mod-Manager/master/auto-update.json";
|
||||
public static final String APP_UPDATE_URL = "https://raw.github.com/Vhati/Slipstream-Mod-Manager/master/auto_update.json";
|
||||
|
||||
private File backupDir = new File( "./backup/" );
|
||||
private File modsDir = new File( "./mods/" );
|
||||
|
||||
private int catalogFetchInterval = 7; // Days.
|
||||
private File catalogFile = new File( backupDir, "current_catalog.json" );
|
||||
private File catalogETagFile = new File( backupDir, "current_catalog_etag.txt" );
|
||||
|
||||
private File appUpdateFile = new File( backupDir, "auto_update.json" );
|
||||
private File appUpdateETagFile = new File( backupDir, "auto_update_etag.txt" );
|
||||
|
||||
private SlipstreamConfig appConfig;
|
||||
private String appName;
|
||||
private ComparableVersion appVersion;
|
||||
private String appURL;
|
||||
private String appAuthor;
|
||||
|
||||
private NerfListener nerfListener = new NerfListener( this );
|
||||
|
||||
private HashMap<File,String> modFileHashes = new HashMap<File,String>();
|
||||
private ModDB modDB = new ModDB();
|
||||
|
||||
private AutoUpdateInfo appUpdateInfo = null;
|
||||
|
||||
private NerfListener nerfListener = new NerfListener( this );
|
||||
|
||||
private ChecklistTableModel<ModFileInfo> localModsTableModel;
|
||||
private JTable localModsTable;
|
||||
|
||||
|
@ -128,6 +135,7 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver
|
|||
private JButton toggleAllBtn;
|
||||
private JButton validateBtn;
|
||||
private JButton modsFolderBtn;
|
||||
private JButton updateBtn;
|
||||
private JSplitPane splitPane;
|
||||
private ModInfoArea infoArea;
|
||||
|
||||
|
@ -198,9 +206,16 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver
|
|||
modsFolderBtn.setEnabled( Desktop.isDesktopSupported() );
|
||||
modActionsPanel.add( modsFolderBtn );
|
||||
|
||||
updateBtn = new JButton("Update");
|
||||
updateBtn.setMargin( actionInsets );
|
||||
updateBtn.addMouseListener( new StatusbarMouseListener( this, String.format( "Show info about the latest version of %s.", appName ) ) );
|
||||
updateBtn.addActionListener(this);
|
||||
updateBtn.setEnabled( false );
|
||||
modActionsPanel.add( updateBtn );
|
||||
|
||||
topPanel.add( modActionsPanel, BorderLayout.EAST );
|
||||
|
||||
JButton[] actionBtns = new JButton[] {patchBtn, toggleAllBtn, validateBtn, modsFolderBtn };
|
||||
JButton[] actionBtns = new JButton[] {patchBtn, toggleAllBtn, validateBtn, modsFolderBtn, updateBtn };
|
||||
int actionBtnWidth = Integer.MIN_VALUE;
|
||||
int actionBtnHeight = Integer.MIN_VALUE;
|
||||
for ( JButton btn : actionBtns ) {
|
||||
|
@ -402,6 +417,7 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver
|
|||
List<String> preferredOrder = loadModOrder();
|
||||
rescanMods( preferredOrder );
|
||||
|
||||
int catalogUpdateInterval = appConfig.getPropertyAsInt( "update_catalog", 0 );
|
||||
boolean needNewCatalog = false;
|
||||
|
||||
if ( catalogFile.exists() ) {
|
||||
|
@ -409,25 +425,26 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver
|
|||
ModDB currentDB = JacksonGrognakCatalogReader.parse( catalogFile );
|
||||
if ( currentDB != null ) modDB = currentDB;
|
||||
|
||||
if ( catalogUpdateInterval > 0 ) {
|
||||
// Check if the downloaded catalog is stale.
|
||||
Date catalogDate = new Date( catalogFile.lastModified() );
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add( Calendar.DATE, catalogFetchInterval * -1 );
|
||||
cal.add( Calendar.DATE, catalogUpdateInterval * -1 );
|
||||
if ( catalogDate.before( cal.getTime() ) ) {
|
||||
log.debug( String.format( "Catalog is older than %d days.", catalogFetchInterval ) );
|
||||
log.debug( String.format( "Catalog is older than %d days.", catalogUpdateInterval ) );
|
||||
needNewCatalog = true;
|
||||
} else {
|
||||
log.debug( "Catalog isn't stale yet." );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Catalog file doesn't exist.
|
||||
needNewCatalog = true;
|
||||
}
|
||||
|
||||
// Don't update if the user doesn't want to.
|
||||
String updatesAllowed = appConfig.getProperty( "update_catalog", "false" );
|
||||
if ( !updatesAllowed.equals("true") ) needNewCatalog = false;
|
||||
if ( catalogUpdateInterval <= 0 ) needNewCatalog = false;
|
||||
|
||||
if ( needNewCatalog ) {
|
||||
Runnable fetchTask = new Runnable() {
|
||||
|
@ -441,11 +458,56 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver
|
|||
Thread fetchThread = new Thread( fetchTask );
|
||||
fetchThread.start();
|
||||
}
|
||||
|
||||
int appUpdateInterval = appConfig.getPropertyAsInt( "update_app", 0 );
|
||||
boolean needAppUpdate = false;
|
||||
|
||||
if ( appUpdateFile.exists() ) {
|
||||
// Load the info first, before downloading.
|
||||
AutoUpdateInfo aui = JacksonAutoUpdateReader.parse( appUpdateFile );
|
||||
if ( aui != null ) {
|
||||
appUpdateInfo = aui;
|
||||
updateBtn.setEnabled( appVersion.compareTo(appUpdateInfo.getLatestVersion()) < 0 );
|
||||
}
|
||||
|
||||
if ( appUpdateInterval > 0 ) {
|
||||
// Check if the app update info is stale.
|
||||
Date catalogDate = new Date( appUpdateFile.lastModified() );
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add( Calendar.DATE, catalogUpdateInterval * -1 );
|
||||
if ( catalogDate.before( cal.getTime() ) ) {
|
||||
log.debug( String.format( "App update info is older than %d days.", appUpdateInterval ) );
|
||||
needAppUpdate = true;
|
||||
} else {
|
||||
log.debug( "App update info isn't stale yet." );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// App update file doesn't exist.
|
||||
needAppUpdate = true;
|
||||
}
|
||||
|
||||
// Don't update if the user doesn't want to.
|
||||
if ( appUpdateInterval <= 0 ) needAppUpdate = false;
|
||||
|
||||
if ( needAppUpdate ) {
|
||||
Runnable fetchTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean fetched = URLFetcher.refetchURL( APP_UPDATE_URL, appUpdateFile, appUpdateETagFile );
|
||||
|
||||
if ( fetched ) reloadAppUpdateInfo();
|
||||
}
|
||||
};
|
||||
Thread fetchThread = new Thread( fetchTask );
|
||||
fetchThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reparses and replace the downloaded ModDB catalog. (thread-safe)
|
||||
* Reparses and replaces the downloaded ModDB catalog. (thread-safe)
|
||||
*/
|
||||
public void reloadCatalog() {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
|
@ -459,6 +521,24 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reparses info about available app updates. (thread-safe)
|
||||
*/
|
||||
public void reloadAppUpdateInfo() {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if ( appUpdateFile.exists() ) {
|
||||
AutoUpdateInfo aui = JacksonAutoUpdateReader.parse( appUpdateFile );
|
||||
if ( aui != null ) {
|
||||
appUpdateInfo = aui;
|
||||
updateBtn.setEnabled( appVersion.compareTo(appUpdateInfo.getLatestVersion()) < 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a mod list with names sorted in a preferred order.
|
||||
|
@ -579,6 +659,42 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver
|
|||
infoArea.setDescription( appName, appAuthor, appVersion.toString(), appURL, body );
|
||||
}
|
||||
|
||||
public void showAppUpdateInfo() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
for ( Map.Entry<ComparableVersion,List<String>> entry : appUpdateInfo.getChangelog().entrySet() ) {
|
||||
if ( appVersion.compareTo( entry.getKey() ) >= 0 ) break;
|
||||
|
||||
if ( buf.length() > 0 ) buf.append( "\n" );
|
||||
buf.append( entry.getKey() ).append( ":\n" );
|
||||
|
||||
for ( String change : entry.getValue() ) {
|
||||
buf.append( " - " ).append( change ).append( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
infoArea.clear();
|
||||
infoArea.appendTitleText( "What's New\n" );
|
||||
infoArea.appendRegularText( String.format( "Version %s: ", appUpdateInfo.getLatestVersion().toString() ) );
|
||||
boolean first = true;
|
||||
for ( Map.Entry<String,String> entry : appUpdateInfo.getLatestURLs().entrySet() ) {
|
||||
if ( !first ) infoArea.appendRegularText( " " );
|
||||
infoArea.appendRegularText( "[" );
|
||||
infoArea.appendLinkText( entry.getValue(), entry.getKey() );
|
||||
infoArea.appendRegularText( "]" );
|
||||
first = false;
|
||||
}
|
||||
infoArea.appendRegularText( "\n" );
|
||||
infoArea.appendRegularText( "\n" );
|
||||
infoArea.appendRegularText( buf.toString() );
|
||||
infoArea.setCaretPosition( 0 );
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
log.error( "Error filling info text area.", e );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows info about a local mod in the text area.
|
||||
*/
|
||||
|
@ -720,6 +836,9 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver
|
|||
log.error( "Error opening mods/ folder.", f );
|
||||
}
|
||||
}
|
||||
else if ( source == updateBtn ) {
|
||||
showAppUpdateInfo();
|
||||
}
|
||||
else if ( source == rescanMenuItem ) {
|
||||
setStatusText( "" );
|
||||
if ( rescanMenuItem.isEnabled() == false ) return;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue