package net.vhati.modmanager.ui;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.Lock;
import javax.swing.SwingUtilities;
import net.vhati.modmanager.core.AutoUpdateInfo;
import net.vhati.modmanager.core.ModDB;
import net.vhati.modmanager.core.ModFileInfo;
import net.vhati.modmanager.core.SlipstreamConfig;
import net.vhati.modmanager.json.JacksonAutoUpdateReader;
import net.vhati.modmanager.json.JacksonCatalogReader;
import net.vhati.modmanager.json.URLFetcher;
import net.vhati.modmanager.ui.ManagerFrame;
import net.vhati.modmanager.ui.table.ListState;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Performs I/O-related setup for ManagerFrame in the background.
*
* Reads cached local metadata.
* Rescans the "mods/" folder.
* Reads saved catalog, and redownloads if stale.
* Reads saved info about app updates, and redownloads if stale.
*/
public class ManagerInitThread extends Thread {
private static final Logger log = LogManager.getLogger(ManagerInitThread.class);
private final ManagerFrame frame;
private final SlipstreamConfig appConfig;
private final File modsDir;
private final File modsTableStateFile;
private final File metadataFile;
private final File catalogFile;
private final File catalogETagFile;
private final File appUpdateFile;
private final File appUpdateETagFile;
public ManagerInitThread( ManagerFrame frame, SlipstreamConfig appConfig, File modsDir, File modsTableStateFile, File metadataFile, File catalogFile, File catalogETagFile, File appUpdateFile, File appUpdateETagFile ) {
this.frame = frame;
this.appConfig = appConfig;
this.modsDir = modsDir;
this.modsTableStateFile = modsTableStateFile;
this.metadataFile = metadataFile;
this.catalogFile = catalogFile;
this.catalogETagFile = catalogETagFile;
this.appUpdateFile = appUpdateFile;
this.appUpdateETagFile = appUpdateETagFile;
}
@Override
public void run() {
try {
init();
}
catch ( Exception e ) {
log.error( "Error during ManagerFrame init.", e );
}
}
private void init() throws InterruptedException {
if ( metadataFile.exists() ) {
// Load cached metadata first, before scanning for new info.
ModDB cachedDB = JacksonCatalogReader.parse( metadataFile );
if ( cachedDB != null ) frame.setLocalModDB( cachedDB );
}
final ListState<ModFileInfo> tableState = loadModsTableState();
Lock managerLock = frame.getLock();
managerLock.lock();
try {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() { frame.rescanMods( tableState ); }
});
// Wait until notified that "mods/" has been scanned.
while ( frame.isScanning() ) {
frame.getScanEndedCondition().await();
}
}
finally {
managerLock.unlock();
}
int catalogUpdateInterval = appConfig.getPropertyAsInt( "update_catalog", 0 );
boolean needNewCatalog = false;
if ( catalogFile.exists() ) {
// Load the catalog first, before updating.
reloadCatalog();
if ( catalogUpdateInterval > 0 ) {
// Check if the downloaded catalog is stale.
if ( isFileStale( catalogFile, catalogUpdateInterval ) ) {
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.
if ( catalogUpdateInterval <= 0 ) needNewCatalog = false;
if ( needNewCatalog ) {
boolean fetched = URLFetcher.refetchURL( ManagerFrame.CATALOG_URL, catalogFile, catalogETagFile );
if ( fetched && catalogFile.exists() ) {
reloadCatalog();
}
}
int appUpdateInterval = appConfig.getPropertyAsInt( "update_app", 0 );
boolean needAppUpdate = false;
if ( appUpdateFile.exists() ) {
// Load the info first, before downloading.
reloadAppUpdateInfo();
if ( appUpdateInterval > 0 ) {
// Check if the app update info is stale.
if ( isFileStale( appUpdateFile, appUpdateInterval ) ) {
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 ) {
boolean fetched = URLFetcher.refetchURL( ManagerFrame.APP_UPDATE_URL, appUpdateFile, appUpdateETagFile );
if ( fetched && appUpdateFile.exists() ) {
reloadAppUpdateInfo();
}
}
}
/**
* Reads modorder.txt and returns a list of mod names in preferred order.
*/
private ListState<ModFileInfo> loadModsTableState() {
List<String> fileNames = new ArrayList<String>();
BufferedReader br = null;
try {
FileInputStream is = new FileInputStream( modsTableStateFile );
br = new BufferedReader(new InputStreamReader( is, Charset.forName("UTF-8") ));
String line;
while ( (line = br.readLine()) != null ) {
fileNames.add( line );
}
}
catch ( FileNotFoundException e ) {
}
catch ( IOException e ) {
log.error( String.format( "Error reading \"%s\".", modsTableStateFile.getName() ), e );
fileNames.clear();
}
finally {
try {if ( br != null ) br.close();}
catch ( Exception e ) {}
}
ListState<ModFileInfo> result = new ListState<ModFileInfo>();
for ( String fileName : fileNames ) {
File modFile = new File( modsDir, fileName );
ModFileInfo modFileInfo = new ModFileInfo( modFile );
result.addItem( modFileInfo );
}
return result;
}
private void reloadCatalog() {
ModDB currentDB = JacksonCatalogReader.parse( catalogFile );
if ( currentDB != null ) frame.setCatalogModDB( currentDB );
}
private void reloadAppUpdateInfo() {
AutoUpdateInfo aui = JacksonAutoUpdateReader.parse( appUpdateFile );
if ( aui != null ) frame.setAppUpdateInfo( aui );
}
/**
* Returns true if a file is older than N days.
*/
private boolean isFileStale( File f, int maxDays ) {
Date modifiedDate = new Date( f.lastModified() );
Calendar cal = Calendar.getInstance();
cal.add( Calendar.DATE, maxDays * -1 );
return modifiedDate.before( cal.getTime() );
}
}