/** * Copyright (c) 2005-2017, KoLmafia development team * http://kolmafia.sourceforge.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "KoLmafia" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.kolmafia.utilities; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import javax.swing.ImageIcon; import net.java.dev.spellcast.utilities.DataUtilities; import net.java.dev.spellcast.utilities.JComponentUtilities; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.StaticEntity; import net.sourceforge.kolmafia.request.GenericRequest; import net.sourceforge.kolmafia.preferences.Preferences; public class FileUtilities { private static final Pattern FILEID_PATTERN = Pattern.compile( "(\\d+)\\." ); public static final BufferedReader getReader( final String filename, final boolean allowOverride ) { return FileUtilities.getReader( DataUtilities.getReader( KoLConstants.DATA_DIRECTORY, filename, allowOverride ) ); } public static final BufferedReader getReader( final String filename ) { return FileUtilities.getReader( DataUtilities.getReader( KoLConstants.DATA_DIRECTORY, filename ) ); } public static final BufferedReader getReader( final File file ) { return FileUtilities.getReader( DataUtilities.getReader( file ) ); } public static final BufferedReader getReader( final InputStream istream ) { return FileUtilities.getReader( DataUtilities.getReader( istream ) ); } private static final BufferedReader getReader( final BufferedReader reader ) { String lastMessage = DataUtilities.getLastMessage(); if ( lastMessage != null ) { RequestLogger.printLine( lastMessage ); } return reader; } public static final BufferedReader getVersionedReader( final String filename, final int version ) { BufferedReader reader = FileUtilities.getReader( DataUtilities.getReader( KoLConstants.DATA_DIRECTORY, filename, true ) ); // If no file, no reader if ( reader == null ) { return null; } // Read the version number String line = FileUtilities.readLine( reader ); // Parse the version number and validate try { int fileVersion = StringUtilities.parseInt( line ); if ( version == fileVersion ) { return reader; } RequestLogger.printLine( "Incorrect version of \"" + filename + "\". Found " + fileVersion + " require " + version ); } catch ( Exception e ) { // Incompatible data file, use KoLmafia's internal // files instead. } // We don't understand this file format try { reader.close(); } catch ( Exception e ) { StaticEntity.printStackTrace( e ); } // Override file is wrong version. Get built-in file reader = DataUtilities.getReader( KoLConstants.DATA_DIRECTORY, filename, false ); // Don't forget to skip past its version number: FileUtilities.readLine( reader ); return reader; } public static final String readLine( final BufferedReader reader ) { if ( reader == null ) { return null; } try { String line; // Read in all of the comment lines, or until // the end of file, whichever comes first. while ( ( line = reader.readLine() ) != null && ( line.startsWith( "#" ) || line.length() == 0 ) ) { ; } // If you've reached the end of file, then // return null. Otherwise, return the line // that's been split on tabs. return line; } catch ( IOException e ) { StaticEntity.printStackTrace( e ); return null; } } public static final String[] readData( final BufferedReader reader ) { if ( reader == null ) { return null; } String line = readLine( reader ); return line == null ? null : line.split( "\t", -1 ); } public static final boolean loadLibrary( final File parent, final String directory, final String filename ) { // Next, load the icon which will be used by KoLmafia // in the system tray. For now, this will be the old // icon used by KoLmelion. File library = new File( parent, filename ); if ( library.exists() ) { if ( parent == KoLConstants.RELAY_LOCATION && !Preferences.getString( "lastRelayUpdate" ).equals( StaticEntity.getVersion() ) ) { library.delete(); } else { return true; } } InputStream istream = DataUtilities.getInputStream( directory, filename ); byte[] data = ByteBufferUtilities.read( istream ); OutputStream output = DataUtilities.getOutputStream( library ); try { output.write( data ); } catch ( IOException e ) { StaticEntity.printStackTrace( e ); } try { output.close(); } catch ( IOException e ) { StaticEntity.printStackTrace( e ); } return true; } public static final void downloadFile( final String remote, final File local ) { // Assume that any file with content is good FileUtilities.downloadFile( remote, local, false ); } public static final void downloadFile( final String remote, final File local, boolean probeLastModified ) { if ( !local.exists() || local.length() == 0 ) { // If we don't have it cached, don't probe probeLastModified = false; } else if ( !probeLastModified ) { // If we are not probing, assume that a file with content is good return; } HttpURLConnection connection; try { connection = (HttpURLConnection) new URL( null, remote ).openConnection(); } catch ( IOException e ) { return; } if ( probeLastModified ) { //This isn't perfect, because the user could've modified the file themselves, but it's better than nothing. connection.setIfModifiedSince( local.lastModified() ); } if ( remote.startsWith( "http://pics.communityofloathing.com" ) ) { Matcher idMatcher = FileUtilities.FILEID_PATTERN.matcher( local.getPath() ); if ( idMatcher.find() ) { connection.setRequestProperty( "Referer", "http://www.kingdomofloathing.com/showplayer.php?who=" + idMatcher.group( 1 ) ); } } if ( RequestLogger.isDebugging() ) { GenericRequest.printRequestProperties( remote, (HttpURLConnection)connection ); } if ( RequestLogger.isTracing() ) { RequestLogger.trace( "Requesting: " + remote ); } InputStream istream = null; try { int responseCode = connection.getResponseCode(); String responseMessage = connection.getResponseMessage(); switch ( responseCode ) { case 200: istream = connection.getInputStream(); if ( "gzip".equals( connection.getContentEncoding() ) ) { istream = new GZIPInputStream( istream ); } break; case 304: //Requested variant not modified, fall through. if ( RequestLogger.isDebugging() ) { RequestLogger.updateDebugLog( "Not modified: " + remote ); } if ( RequestLogger.isTracing() ) { RequestLogger.trace( "Not modified: " + remote ); } default: if ( RequestLogger.isDebugging() ) { RequestLogger.updateDebugLog( "Server returned response code " + responseCode + " (" + responseMessage + ")" ); } return; } } catch ( IOException e ) { return; } if ( RequestLogger.isDebugging() ) { GenericRequest.printHeaderFields( remote, (HttpURLConnection)connection ); } if ( RequestLogger.isTracing() ) { RequestLogger.trace( "Retrieved: " + remote ); } OutputStream ostream = DataUtilities.getOutputStream( local ); try { // If it's Javascript, then modify it so that // all the variables point to KoLmafia. if ( remote.endsWith( ".js" ) ) { byte[] bytes = ByteBufferUtilities.read( istream ); String text = new String( bytes ); text = StringUtilities.globalStringReplace( text, "location.hostname", "location.host" ); ostream.write( text.getBytes() ); } else if ( remote.endsWith( ".gif" ) ) { byte[] bytes = ByteBufferUtilities.read( istream ); String signature = new String( bytes, 0, 3 ); // Certain firewalls return garbage if they // prevent you from getting to the image // server. Don't cache that. // Additionally, don't cache KoL's blank image that seems // to be a standin for a 404 these days.. // Removing this check for now, since only a legitimate // blank.gif currently returns a blank image // && bytes.length != 61 if ( signature.equals( "GIF" ) ) { ostream.write( bytes, 0, bytes.length ); } } else { ByteBufferUtilities.read( istream, ostream ); } } catch ( IOException e ) { StaticEntity.printStackTrace( e ); } try { ostream.close(); } catch ( IOException e ) { StaticEntity.printStackTrace( e ); } // Don't keep a 0-length file if ( local.exists() && local.length() == 0 ) { local.delete(); } else { String lastModifiedString = ((HttpURLConnection)connection).getHeaderField( "Last-Modified" ); long lastModified = StringUtilities.parseDate( lastModifiedString ); if ( lastModified > 0 ) { local.setLastModified( lastModified ); } } } /** * Downloads the given file from the KoL images server and stores it locally. */ private static final String localImageName( final String filename ) { if ( filename == null || filename.equals( "" ) ) { return null; } String images = ".com"; int index = filename.lastIndexOf( images ); int offset = index == -1 ? 0 : ( index + images.length() + 1 ); String localname = offset > 0 ? filename.substring( offset ) : filename; if ( localname.startsWith( "albums/" ) ) { localname = localname.substring( 7 ); } return localname; } public static final File imageFile( final String filename ) { return new File( KoLConstants.IMAGE_LOCATION, FileUtilities.localImageName( filename ) ); } public static final File downloadImage( final String filename ) { String localname = FileUtilities.localImageName( filename ); File localfile = new File( KoLConstants.IMAGE_LOCATION, localname ); try { if ( !localfile.exists() || localfile.length() == 0 ) { if ( JComponentUtilities.getImage( localname ) != null ) { loadLibrary( KoLConstants.IMAGE_LOCATION, KoLConstants.IMAGE_DIRECTORY, localname ); } else { downloadFile( filename, localfile ); } } return localfile; } catch ( Exception e ) { // This can happen whenever there is bad internet // or whenever the familiar is brand-new. return null; } } /** * Downloads an image from the KoL image server and returns it as an icon */ public static final ImageIcon downloadIcon( final String image, final String container, final String defaultImage ) { if ( image == null || image.equals( "" ) ) { return JComponentUtilities.getImage( defaultImage ); } String path = container == null || container.equals( "" ) ? image : container + "/" + image; File file = FileUtilities.downloadImage( KoLmafia.imageServerPath() + path ); if ( file == null ) { return JComponentUtilities.getImage( defaultImage ); } ImageIcon icon = JComponentUtilities.getImage( path ); return icon != null ? icon : JComponentUtilities.getImage( defaultImage ); } /** * Copies a file. */ public static void copyFile( File source, File destination ) { InputStream sourceStream = DataUtilities.getInputStream( source ); OutputStream destinationStream = DataUtilities.getOutputStream( destination ); if ( !( sourceStream instanceof FileInputStream ) || !( destinationStream instanceof FileOutputStream ) ) { try { sourceStream.close(); } catch ( IOException e ) { StaticEntity.printStackTrace( e ); } try { destinationStream.close(); } catch ( IOException e ) { StaticEntity.printStackTrace( e ); } return; } FileChannel sourceChannel = ( (FileInputStream) sourceStream ).getChannel(); FileChannel destinationChannel = ( (FileOutputStream) destinationStream ).getChannel(); try { sourceChannel.transferTo( 0, sourceChannel.size(), destinationChannel ); } catch ( IOException e ) { StaticEntity.printStackTrace( e ); } try { sourceStream.close(); } catch ( IOException e ) { StaticEntity.printStackTrace( e ); } try { destinationStream.close(); } catch ( IOException e ) { StaticEntity.printStackTrace( e ); } } private static List<Object> getPathList( File f ) { List<Object> l = new ArrayList<Object>(); File r; try { r = f.getCanonicalFile(); while ( r != null ) { l.add( r.getName() ); r = r.getParentFile(); } } catch ( IOException e ) { e.printStackTrace(); l = null; } return l; } /** * figure out a string representing the relative path of 'f' with respect to 'r' * * @param r home path * @param f path of file */ private static String matchPathLists( List<Object> r, List<Object> f ) { int i; int j; String s; // start at the beginning of the lists // iterate while both lists are equal s = ""; i = r.size() - 1; j = f.size() - 1; // first eliminate common root while ( ( i >= 0 ) && ( j >= 0 ) && ( r.get( i ).equals( f.get( j ) ) ) ) { i-- ; j-- ; } // for each remaining level in the home path, add a .. for ( ; i >= 0; i-- ) { s += ".." + File.separator; } // for each level in the file path, add the path for ( ; j >= 1; j-- ) { s += f.get( j ) + File.separator; } // file name s += f.get( j ); return s; } /** * get relative path of File 'f' with respect to 'home' directory example : home = /a/b/c f = /a/d/e/x.txt s = * getRelativePath(home,f) = ../../d/e/x.txt * * @param home base path, should be a directory, not a file, or it doesn't make sense * @param f file to generate path for * @return path from home to f as a string */ public static String getRelativePath( File home, File f ) { List<Object> homelist; List<Object> filelist; String s; homelist = getPathList( home ); filelist = getPathList( f ); s = matchPathLists( homelist, filelist ); return s; } }