2009-01-04 13:28:28 -05:00
2008-12-31 15:11:27 -05:00
package net.sourceforge.filebot ;
2009-01-04 13:28:28 -05:00
2012-07-14 03:58:53 -04:00
import static java.awt.GraphicsEnvironment.* ;
2012-07-28 16:55:20 -04:00
import static java.util.regex.Pattern.* ;
2013-02-25 12:27:34 -05:00
import static javax.swing.JOptionPane.* ;
2012-07-14 03:58:53 -04:00
import static net.sourceforge.filebot.Settings.* ;
2012-07-28 06:04:52 -04:00
import static net.sourceforge.tuned.FileUtilities.* ;
2012-07-14 03:58:53 -04:00
import static net.sourceforge.tuned.ui.TunedUtilities.* ;
2009-03-13 20:30:24 -04:00
2012-07-14 03:58:53 -04:00
import java.awt.Desktop ;
import java.awt.Dialog.ModalityType ;
import java.awt.event.ActionEvent ;
import java.awt.event.WindowAdapter ;
import java.awt.event.WindowEvent ;
import java.io.File ;
2013-03-26 11:04:53 -04:00
import java.io.FileOutputStream ;
2012-07-14 03:58:53 -04:00
import java.io.IOException ;
2012-07-28 06:04:52 -04:00
import java.io.RandomAccessFile ;
2012-08-11 08:26:00 -04:00
import java.lang.reflect.InvocationTargetException ;
2012-07-14 03:58:53 -04:00
import java.net.URI ;
import java.nio.ByteBuffer ;
2012-07-28 06:04:52 -04:00
import java.nio.channels.FileLock ;
2012-07-14 03:58:53 -04:00
import java.security.CodeSource ;
import java.security.Permission ;
import java.security.PermissionCollection ;
import java.security.Permissions ;
import java.security.Policy ;
import java.security.ProtectionDomain ;
2013-02-07 03:34:08 -05:00
import java.util.Collection ;
2012-07-14 03:58:53 -04:00
import java.util.Collections ;
import java.util.List ;
2013-02-25 12:27:34 -05:00
import java.util.Locale ;
2012-07-14 03:58:53 -04:00
import java.util.Properties ;
import java.util.logging.Level ;
import java.util.logging.Logger ;
import javax.swing.AbstractAction ;
import javax.swing.JButton ;
import javax.swing.JDialog ;
import javax.swing.JFrame ;
import javax.swing.JLabel ;
2013-02-25 12:27:34 -05:00
import javax.swing.JOptionPane ;
2012-07-14 03:58:53 -04:00
import javax.swing.JPanel ;
import javax.swing.SwingUtilities ;
import javax.swing.UIManager ;
import javax.xml.parsers.DocumentBuilderFactory ;
import net.miginfocom.swing.MigLayout ;
import net.sf.ehcache.CacheManager ;
import net.sourceforge.filebot.cli.ArgumentBean ;
import net.sourceforge.filebot.cli.ArgumentProcessor ;
import net.sourceforge.filebot.cli.CmdlineOperations ;
import net.sourceforge.filebot.format.ExpressionFormat ;
2012-12-14 20:00:53 -05:00
import net.sourceforge.filebot.gio.GVFS ;
2012-07-14 03:58:53 -04:00
import net.sourceforge.filebot.media.MediaDetection ;
import net.sourceforge.filebot.ui.MainFrame ;
import net.sourceforge.filebot.ui.SinglePanelFrame ;
import net.sourceforge.filebot.ui.sfv.SfvPanelBuilder ;
import net.sourceforge.filebot.ui.transfer.FileTransferable ;
import net.sourceforge.filebot.web.CachedResource ;
import net.sourceforge.tuned.ByteBufferInputStream ;
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry ;
2013-03-26 11:04:53 -04:00
import net.sourceforge.tuned.TeePrintStream ;
2009-03-14 06:20:59 -04:00
2012-07-26 04:45:15 -04:00
import org.w3c.dom.NodeList ;
2007-12-23 14:28:04 -05:00
public class Main {
/ * *
2012-07-14 03:58:53 -04:00
* @param args
2007-12-23 14:28:04 -05:00
* /
2012-07-14 03:58:53 -04:00
public static void main ( String . . . arguments ) {
try {
// parse arguments
final ArgumentProcessor cli = new ArgumentProcessor ( ) ;
final ArgumentBean args = cli . parse ( arguments ) ;
2013-03-26 11:04:53 -04:00
// tee stdout and stderr to log file if set
if ( args . logFile ! = null ) {
FileOutputStream log = new FileOutputStream ( args . getLogFile ( ) , true ) ;
System . setOut ( new TeePrintStream ( log , true , " UTF-8 " , System . out ) ) ;
System . setErr ( new TeePrintStream ( log , true , " UTF-8 " , System . err ) ) ;
}
2012-07-14 03:58:53 -04:00
if ( args . printHelp ( ) | | args . printVersion ( ) | | ( ! args . runCLI ( ) & & isHeadless ( ) ) ) {
System . out . format ( " %s / %s%n%n " , getApplicationIdentifier ( ) , getJavaRuntimeIdentifier ( ) ) ;
if ( args . printHelp ( ) | | ( ! args . printVersion ( ) & & isHeadless ( ) ) ) {
cli . printHelp ( args ) ;
}
// just print help message or version string and then exit
System . exit ( 0 ) ;
}
2012-12-11 06:19:14 -05:00
// make sure java.io.tmpdir exists
File tmpdir = new File ( System . getProperty ( " java.io.tmpdir " ) ) ;
tmpdir . mkdirs ( ) ;
2012-10-27 22:52:49 -04:00
// initialize this stuff before anything else
initializeCache ( ) ;
initializeSecurityManager ( ) ;
2012-07-14 03:58:53 -04:00
if ( args . clearUserData ( ) ) {
2012-12-11 06:19:14 -05:00
System . out . println ( " Reset preferences " ) ;
Settings . forPackage ( Main . class ) . clear ( ) ;
}
if ( args . clearCache ( ) ) {
2012-07-14 03:58:53 -04:00
// clear preferences and cache
2012-12-11 06:19:14 -05:00
System . out . println ( " Clear cache and temporary files " ) ;
2012-07-28 06:04:52 -04:00
for ( File cache : getApplicationFolder ( ) . listFiles ( FOLDERS ) ) {
2012-07-28 16:55:20 -04:00
if ( matches ( " cache|temp|grape " , cache . getName ( ) ) ) {
2012-08-11 10:23:16 -04:00
for ( File it : cache . listFiles ( ) ) {
delete ( it ) ;
}
2012-07-28 06:04:52 -04:00
}
}
2012-07-14 03:58:53 -04:00
CacheManager . getInstance ( ) . clearAll ( ) ;
}
2013-02-06 09:09:35 -05:00
// update system properties
if ( System . getProperty ( " http.agent " ) = = null ) {
System . setProperty ( " http.agent " , String . format ( " %s %s " , getApplicationName ( ) , getApplicationVersion ( ) ) ) ;
}
2012-08-15 06:47:46 -04:00
if ( args . unixfs ) {
System . setProperty ( " unixfs " , " true " ) ;
}
2013-02-15 04:38:30 -05:00
if ( args . disableAnalytics ) {
System . setProperty ( " application.analytics " , " false " ) ;
}
if ( args . action . equalsIgnoreCase ( " test " ) ) {
System . setProperty ( " useExtendedFileAttributes " , " false " ) ;
2013-02-03 05:15:36 -05:00
System . setProperty ( " application.analytics " , " false " ) ;
}
2012-08-15 06:47:46 -04:00
2012-07-14 03:58:53 -04:00
// initialize analytics
2013-03-22 09:16:00 -04:00
Analytics . setEnabled ( System . getProperty ( " application.analytics " ) = = null ? true : Boolean . parseBoolean ( System . getProperty ( " application.analytics " ) ) ) ;
2012-07-14 03:58:53 -04:00
// CLI mode => run command-line interface and then exit
if ( args . runCLI ( ) ) {
2013-02-25 12:27:34 -05:00
// commit session history on shutdown
Runtime . getRuntime ( ) . addShutdownHook ( new Thread ( new Runnable ( ) {
@Override
public void run ( ) {
HistorySpooler . getInstance ( ) . commit ( ) ;
}
} ) ) ;
2012-07-14 03:58:53 -04:00
// default cross-platform laf used in scripting to nimbus instead of metal (if possible)
if ( args . script ! = null & & ! isHeadless ( ) ) {
try {
Class < ? > nimbusLook = Class . forName ( " javax.swing.plaf.nimbus.NimbusLookAndFeel " , false , Thread . currentThread ( ) . getContextClassLoader ( ) ) ;
System . setProperty ( " swing.crossplatformlaf " , nimbusLook . getName ( ) ) ;
} catch ( Throwable e ) {
// ignore all errors and stick with default cross-platform laf
}
}
int status = cli . process ( args , new CmdlineOperations ( ) ) ;
System . exit ( status ) ;
}
// GUI mode => start user interface
try {
SwingUtilities . invokeAndWait ( new Runnable ( ) {
@Override
public void run ( ) {
try {
// use native laf an all platforms
UIManager . setLookAndFeel ( UIManager . getSystemLookAndFeelClassName ( ) ) ;
} catch ( Exception e ) {
Logger . getLogger ( Main . class . getName ( ) ) . log ( Level . WARNING , e . getMessage ( ) , e ) ;
}
startUserInterface ( args ) ;
}
} ) ;
2012-08-11 08:26:00 -04:00
} catch ( InvocationTargetException e ) {
Logger . getLogger ( Main . class . getName ( ) ) . log ( Level . SEVERE , e . getCause ( ) . getMessage ( ) , e . getCause ( ) ) ;
2012-07-14 03:58:53 -04:00
System . exit ( - 1 ) ; // starting up UI failed
2012-08-11 08:26:00 -04:00
} catch ( InterruptedException e ) {
throw new RuntimeException ( e ) ; // won't happen
2012-07-14 03:58:53 -04:00
}
2013-01-11 21:19:47 -05:00
// pre-load media.types and JNA/GIO (when loaded during DnD it will freeze the UI for a few hundred milliseconds)
2012-07-14 10:50:07 -04:00
MediaTypes . getDefault ( ) ;
2013-01-11 21:19:47 -05:00
if ( useGVFS ( ) ) {
try {
GVFS . getDefaultVFS ( ) ;
} catch ( Throwable e ) {
Logger . getLogger ( Main . class . getName ( ) ) . log ( Level . SEVERE , e . getMessage ( ) , e ) ;
}
}
2012-07-14 10:50:07 -04:00
// pre-load certain resources in the background
2013-02-07 03:34:08 -05:00
if ( Boolean . parseBoolean ( System . getProperty ( " application.warmup " ) ) ) {
2013-01-23 13:08:32 -05:00
warmupCachedResources ( ) ;
}
2012-07-14 03:58:53 -04:00
// check for application updates (only when installed, i.e. not running via fatjar or webstart)
if ( ! " skip " . equals ( System . getProperty ( " application.update " ) ) ) {
try {
checkUpdate ( ) ;
} catch ( Exception e ) {
Logger . getLogger ( Main . class . getName ( ) ) . log ( Level . WARNING , " Failed to check for updates " , e ) ;
}
}
2013-03-26 11:04:53 -04:00
} catch ( Exception e ) {
2012-07-14 03:58:53 -04:00
// illegal arguments => just print CLI error message and stop
System . err . println ( e . getMessage ( ) ) ;
System . exit ( - 1 ) ;
}
}
private static void startUserInterface ( ArgumentBean args ) {
JFrame frame ;
if ( args . openSFV ( ) ) {
// single panel frame
FileTransferable files = new FileTransferable ( args . getFiles ( false ) ) ;
frame = new SinglePanelFrame ( new SfvPanelBuilder ( ) ) . publish ( files ) ;
} else {
// default frame
frame = new MainFrame ( ) ;
}
frame . setLocationByPlatform ( true ) ;
2013-02-25 12:27:34 -05:00
frame . addWindowListener ( new WindowAdapter ( ) {
@Override
public void windowClosing ( WindowEvent e ) {
e . getWindow ( ) . setVisible ( false ) ;
HistorySpooler . getInstance ( ) . commit ( ) ;
2013-03-26 04:43:02 -04:00
if ( useDonationReminder ( ) ) {
showDonationReminder ( ) ;
}
2013-02-25 12:27:34 -05:00
System . exit ( 0 ) ;
}
} ) ;
2012-07-14 03:58:53 -04:00
try {
// restore previous size and location
restoreWindowBounds ( frame , Settings . forPackage ( MainFrame . class ) ) ;
} catch ( Exception e ) {
// don't care, doesn't make a difference
}
// start application
frame . setVisible ( true ) ;
}
/ * *
* Show update notifications if updates are available
* /
private static void checkUpdate ( ) throws Exception {
final PreferencesEntry < String > updateIgnoreRevision = Settings . forPackage ( Main . class ) . entry ( " update.revision.ignore " ) ;
final Properties updateProperties = new CachedResource < Properties > ( getApplicationProperty ( " update.url " ) , Properties . class , 24 * 60 * 60 * 1000 ) {
@Override
public Properties process ( ByteBuffer data ) {
try {
Properties properties = new Properties ( ) ;
NodeList fields = DocumentBuilderFactory . newInstance ( ) . newDocumentBuilder ( ) . parse ( new ByteBufferInputStream ( data ) ) . getFirstChild ( ) . getChildNodes ( ) ;
for ( int i = 0 ; i < fields . getLength ( ) ; i + + ) {
properties . setProperty ( fields . item ( i ) . getNodeName ( ) , fields . item ( i ) . getTextContent ( ) . trim ( ) ) ;
}
return properties ;
} catch ( Exception e ) {
throw new RuntimeException ( e ) ;
}
}
} . get ( ) ;
// check if update is required
int latestRev = Integer . parseInt ( updateProperties . getProperty ( " revision " ) ) ;
int latestIgnoreRev = Math . max ( getApplicationRevisionNumber ( ) , updateIgnoreRevision . getValue ( ) = = null ? 0 : Integer . parseInt ( updateIgnoreRevision . getValue ( ) ) ) ;
2011-12-24 02:30:54 -05:00
2012-07-14 03:58:53 -04:00
if ( latestRev > latestIgnoreRev ) {
SwingUtilities . invokeLater ( new Runnable ( ) {
@Override
public void run ( ) {
final JDialog dialog = new JDialog ( JFrame . getFrames ( ) [ 0 ] , updateProperties . getProperty ( " title " ) , ModalityType . APPLICATION_MODAL ) ;
final JPanel pane = new JPanel ( new MigLayout ( " fill, nogrid, insets dialog " ) ) ;
dialog . setContentPane ( pane ) ;
pane . add ( new JLabel ( ResourceManager . getIcon ( " window.icon.medium " ) ) , " aligny top " ) ;
pane . add ( new JLabel ( updateProperties . getProperty ( " message " ) ) , " gap 10, wrap paragraph:push " ) ;
pane . add ( new JButton ( new AbstractAction ( " Download " , ResourceManager . getIcon ( " dialog.continue " ) ) {
@Override
public void actionPerformed ( ActionEvent evt ) {
try {
Desktop . getDesktop ( ) . browse ( URI . create ( updateProperties . getProperty ( " download " ) ) ) ;
} catch ( IOException e ) {
throw new RuntimeException ( e ) ;
} finally {
dialog . setVisible ( false ) ;
}
}
} ) , " tag ok " ) ;
pane . add ( new JButton ( new AbstractAction ( " Details " , ResourceManager . getIcon ( " action.report " ) ) {
@Override
public void actionPerformed ( ActionEvent evt ) {
try {
Desktop . getDesktop ( ) . browse ( URI . create ( updateProperties . getProperty ( " discussion " ) ) ) ;
} catch ( IOException e ) {
throw new RuntimeException ( e ) ;
}
}
} ) , " tag help2 " ) ;
pane . add ( new JButton ( new AbstractAction ( " Ignore " , ResourceManager . getIcon ( " dialog.cancel " ) ) {
@Override
public void actionPerformed ( ActionEvent evt ) {
updateIgnoreRevision . setValue ( updateProperties . getProperty ( " revision " ) ) ;
dialog . setVisible ( false ) ;
}
} ) , " tag cancel " ) ;
dialog . pack ( ) ;
dialog . setLocation ( getOffsetLocation ( dialog . getOwner ( ) ) ) ;
dialog . setVisible ( true ) ;
}
} ) ;
}
}
2013-03-26 04:43:02 -04:00
private static void showDonationReminder ( ) {
int renameCount = HistorySpooler . getInstance ( ) . getPersistentHistoryTotalSize ( ) ;
if ( renameCount < = 0 )
return ;
PreferencesEntry < String > donation = Settings . forPackage ( Main . class ) . entry ( " donation " ) . defaultValue ( " 0 " ) ;
int donationRev = Integer . parseInt ( donation . getValue ( ) ) ;
int currentRev = getApplicationRevisionNumber ( ) ;
if ( donationRev > = currentRev ) {
return ;
}
String message = String . format ( Locale . ROOT , " <html><p style='font-size:16pt; font-weight:bold'>Thank you for using FileBot!</p><br><p>It has taken many nights to develop this application. If you enjoy using it,<br>please consider a donation to the author of this software. It will help to<br>make FileBot even better!<p><p style='font-size:14pt; font-weight:bold'>You've renamed %,d files.</p><br><html> " , renameCount ) ;
String [ ] actions = new String [ ] { " Donate! " , " Later " } ;
JOptionPane pane = new JOptionPane ( message , INFORMATION_MESSAGE , YES_NO_OPTION , ResourceManager . getIcon ( " message.donate " ) , actions , actions [ 0 ] ) ;
pane . createDialog ( null , " Please Donate " ) . setVisible ( true ) ;
if ( pane . getValue ( ) = = actions [ 0 ] ) {
try {
Desktop . getDesktop ( ) . browse ( URI . create ( getApplicationProperty ( " donate.url " ) ) ) ;
donation . setValue ( String . valueOf ( currentRev ) ) ;
} catch ( Exception e ) {
Logger . getLogger ( Main . class . getName ( ) ) . log ( Level . SEVERE , " Failed to open URL. " , e ) ;
}
} else {
if ( donationRev > 0 & & donationRev < currentRev ) {
donation . setValue ( String . valueOf ( currentRev ) ) ;
}
}
Analytics . trackEvent ( " GUI " , " Donate " , " r " + currentRev , pane . getValue ( ) = = actions [ 0 ] ? 1 : 0 ) ;
}
2012-07-14 03:58:53 -04:00
private static void warmupCachedResources ( ) {
Thread warmup = new Thread ( " warmupCachedResources " ) {
@Override
public void run ( ) {
try {
2012-07-26 04:45:15 -04:00
// pre-load filter data
MediaDetection . getClutterFileFilter ( ) ;
MediaDetection . getDiskFolderFilter ( ) ;
2012-07-14 03:58:53 -04:00
// pre-load movie/series index
List < String > dummy = Collections . singletonList ( " " ) ;
MediaDetection . stripReleaseInfo ( dummy , true ) ;
MediaDetection . matchSeriesByName ( dummy , - 1 ) ;
MediaDetection . matchMovieName ( dummy , true , - 1 ) ;
2013-02-07 03:34:08 -05:00
Collection < File > empty = Collections . emptyList ( ) ;
MediaDetection . matchSeriesByDirectMapping ( empty ) ;
WebServices . TheTVDB . getLocalIndex ( ) ;
2012-07-14 03:58:53 -04:00
} catch ( Exception e ) {
Logger . getLogger ( getClass ( ) . getName ( ) ) . log ( Level . WARNING , e . getMessage ( ) , e ) ;
}
}
} ;
2012-07-05 23:10:26 -04:00
2012-07-14 03:58:53 -04:00
// start background thread
warmup . setDaemon ( true ) ;
warmup . setPriority ( Thread . MIN_PRIORITY ) ;
warmup . start ( ) ;
}
private static void restoreWindowBounds ( final JFrame window , final Settings settings ) {
// store bounds on close
window . addWindowListener ( new WindowAdapter ( ) {
@Override
public void windowClosing ( WindowEvent e ) {
// don't save window bounds if window is maximized
if ( ! isMaximized ( window ) ) {
settings . put ( " window.x " , String . valueOf ( window . getX ( ) ) ) ;
settings . put ( " window.y " , String . valueOf ( window . getY ( ) ) ) ;
settings . put ( " window.width " , String . valueOf ( window . getWidth ( ) ) ) ;
settings . put ( " window.height " , String . valueOf ( window . getHeight ( ) ) ) ;
}
}
} ) ;
// restore bounds
int x = Integer . parseInt ( settings . get ( " window.x " ) ) ;
int y = Integer . parseInt ( settings . get ( " window.y " ) ) ;
int width = Integer . parseInt ( settings . get ( " window.width " ) ) ;
int height = Integer . parseInt ( settings . get ( " window.height " ) ) ;
window . setBounds ( x , y , width , height ) ;
}
/ * *
* Shutdown ehcache properly , so that disk - persistent stores can actually be saved to disk
* /
2012-07-28 07:01:29 -04:00
private static void initializeCache ( ) {
2012-07-28 06:04:52 -04:00
// prepare cache folder for this application instance
2012-07-28 16:55:20 -04:00
File cacheRoot = new File ( getApplicationFolder ( ) , " cache " ) ;
2012-07-28 06:04:52 -04:00
try {
for ( int i = 0 ; true ; i + + ) {
File cache = new File ( cacheRoot , String . format ( " %d " , i ) ) ;
if ( ! cache . isDirectory ( ) & & ! cache . mkdirs ( ) ) {
throw new IOException ( " Failed to create cache dir: " + cache ) ;
}
File lockFile = new File ( cache , " .lock " ) ;
final RandomAccessFile handle = new RandomAccessFile ( lockFile , " rw " ) ;
final FileLock lock = handle . getChannel ( ) . tryLock ( ) ;
if ( lock ! = null ) {
// setup cache dir for ehcache
2012-07-28 08:00:40 -04:00
System . setProperty ( " ehcache.disk.store.dir " , cache . getAbsolutePath ( ) ) ;
2012-07-28 06:04:52 -04:00
// make sure to orderly shutdown cache
Runtime . getRuntime ( ) . addShutdownHook ( new Thread ( ) {
@Override
public void run ( ) {
2012-10-27 22:52:49 -04:00
try {
CacheManager . getInstance ( ) . shutdown ( ) ;
} catch ( Exception e ) {
// ignore, shutting down anyway
}
2012-07-28 06:04:52 -04:00
try {
lock . release ( ) ;
} catch ( Exception e ) {
2012-10-27 22:52:49 -04:00
// ignore, shutting down anyway
2012-07-28 06:04:52 -04:00
}
try {
handle . close ( ) ;
} catch ( Exception e ) {
2012-10-27 22:52:49 -04:00
// ignore, shutting down anyway
2012-07-28 06:04:52 -04:00
}
}
} ) ;
// cache for this application instance is successfully set up and locked
return ;
}
// try next lock file
handle . close ( ) ;
2012-07-14 03:58:53 -04:00
}
2012-07-28 06:04:52 -04:00
} catch ( Exception e ) {
Logger . getLogger ( Main . class . getName ( ) ) . log ( Level . WARNING , e . toString ( ) , e ) ;
}
// use cache root itself as fail-safe fallback
2012-07-28 08:00:40 -04:00
System . setProperty ( " ehcache.disk.store.dir " , new File ( cacheRoot , " default " ) . getAbsolutePath ( ) ) ;
2012-07-14 03:58:53 -04:00
}
/ * *
2013-02-07 03:34:08 -05:00
* Initialize default SecurityManager and grant all permissions via security policy . Initialization is required in order to run { @link ExpressionFormat } in a secure sandbox .
2012-07-14 03:58:53 -04:00
* /
private static void initializeSecurityManager ( ) {
try {
// initialize security policy used by the default security manager
// because default the security policy is very restrictive (e.g. no FilePermission)
Policy . setPolicy ( new Policy ( ) {
@Override
public boolean implies ( ProtectionDomain domain , Permission permission ) {
// all permissions
return true ;
}
@Override
public PermissionCollection getPermissions ( CodeSource codesource ) {
// VisualVM can't connect if this method does return
// a checked immutable PermissionCollection
return new Permissions ( ) ;
}
} ) ;
// set default security manager
System . setSecurityManager ( new SecurityManager ( ) ) ;
} catch ( Exception e ) {
// security manager was probably set via system property
Logger . getLogger ( Main . class . getName ( ) ) . log ( Level . WARNING , e . toString ( ) , e ) ;
}
2009-05-02 19:34:04 -04:00
}
2007-12-23 14:28:04 -05:00
}