filebot/source/net/filebot/Main.java

475 lines
15 KiB
Java
Raw Normal View History

2014-04-19 02:30:29 -04:00
package net.filebot;
import static java.awt.GraphicsEnvironment.*;
import static java.util.stream.Collectors.*;
2019-02-21 09:10:24 -05:00
import static net.filebot.ExitCode.*;
2016-03-02 10:55:06 -05:00
import static net.filebot.Logging.*;
2018-06-14 06:11:00 -04:00
import static net.filebot.MediaTypes.*;
2014-04-19 02:30:29 -04:00
import static net.filebot.Settings.*;
2018-11-18 09:28:14 -05:00
import static net.filebot.ui.GettingStartedUtil.*;
import static net.filebot.ui.ThemeSupport.*;
2014-04-19 02:30:29 -04:00
import static net.filebot.util.FileUtilities.*;
import static net.filebot.util.FileUtilities.getChildren;
import static net.filebot.util.XPathUtilities.*;
2014-07-29 02:45:15 -04:00
import static net.filebot.util.ui.SwingUI.*;
import java.awt.Dialog.ModalityType;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
2016-03-09 14:26:00 -05:00
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.prefs.Preferences;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
2016-03-10 14:43:21 -05:00
import org.kohsuke.args4j.CmdLineException;
import org.w3c.dom.Document;
2014-04-19 02:30:29 -04:00
import net.filebot.cli.ArgumentBean;
import net.filebot.cli.ArgumentProcessor;
import net.filebot.format.ExpressionFormat;
import net.filebot.platform.mac.MacAppUtilities;
import net.filebot.platform.windows.WinAppUtilities;
2015-05-09 04:07:38 -04:00
import net.filebot.ui.FileBotMenuBar;
2014-04-19 02:30:29 -04:00
import net.filebot.ui.MainFrame;
2016-03-09 11:18:20 -05:00
import net.filebot.ui.NotificationHandler;
2014-04-19 02:30:29 -04:00
import net.filebot.ui.PanelBuilder;
import net.filebot.ui.SinglePanelFrame;
2016-04-23 13:05:33 -04:00
import net.filebot.ui.SupportDialog;
import net.filebot.ui.transfer.FileTransferable;
2014-04-19 02:30:29 -04:00
import net.filebot.util.PreferencesMap.PreferencesEntry;
import net.filebot.util.ui.SwingEventBus;
import net.miginfocom.swing.MigLayout;
public class Main {
2016-10-18 13:02:51 -04:00
public static void main(String[] argv) {
try {
// parse arguments
ArgumentBean args = ArgumentBean.parse(argv);
2016-10-18 14:30:07 -04:00
// just print help message or version string and then exit
if (args.printHelp()) {
log.info(String.format("%s%n%n%s", getApplicationIdentifier(), args.usage()));
2019-02-21 04:52:22 -05:00
System.exit(SUCCESS);
2016-10-18 14:30:07 -04:00
}
2016-10-18 14:30:07 -04:00
if (args.printVersion()) {
log.info(String.join(" / ", getApplicationIdentifier(), getJavaRuntimeIdentifier(), getSystemIdentifier()));
2019-02-21 04:52:22 -05:00
System.exit(SUCCESS);
}
if (args.clearCache() || args.clearUserData() || args.clearHistory()) {
// clear persistent history
if (args.clearHistory) {
log.info("Reset history");
HistorySpooler.getInstance().clear();
}
// clear persistent user preferences
2013-11-07 02:43:58 -05:00
if (args.clearUserData()) {
2016-10-18 13:02:51 -04:00
log.info("Reset preferences");
2013-11-07 02:43:58 -05:00
Settings.forPackage(Main.class).clear();
getPreferencesBackupFile().delete();
2013-11-07 02:43:58 -05:00
}
// clear caches
2013-11-07 02:43:58 -05:00
if (args.clearCache()) {
2017-03-24 09:57:00 -04:00
// clear cache must be called manually
if (System.console() == null) {
2018-06-29 06:23:31 -04:00
log.severe("`filebot -clear-cache` must be called from an interactive console.");
2019-02-21 04:52:22 -05:00
System.exit(ERROR);
2017-03-24 09:57:00 -04:00
}
2016-10-18 13:02:51 -04:00
log.info("Clear cache");
2016-11-25 12:37:09 -05:00
for (File folder : getChildren(ApplicationFolder.Cache.get(), FOLDERS)) {
2016-10-18 13:02:51 -04:00
log.fine("* Delete " + folder);
2016-05-15 15:09:30 -04:00
delete(folder);
2013-11-07 02:43:58 -05:00
}
}
// just clear cache and/or settings and then exit
2019-02-21 04:52:22 -05:00
System.exit(SUCCESS);
2013-11-07 02:43:58 -05:00
}
2016-03-11 03:16:57 -05:00
// make sure we can access application arguments at any time
2016-10-18 13:02:51 -04:00
setApplicationArguments(args);
2016-03-11 03:16:57 -05:00
2016-03-08 11:21:10 -05:00
// update system properties
initializeSystemProperties(args);
2016-03-10 09:05:56 -05:00
initializeLogging(args);
// initialize this stuff before anything else
2016-03-06 13:11:30 -05:00
CacheManager.getInstance();
initializeSecurityManager();
2014-11-10 03:18:32 -05:00
// initialize history spooler
2016-03-05 16:06:26 -05:00
HistorySpooler.getInstance().setPersistentHistoryEnabled(useRenameHistory());
// CLI mode => run command-line interface and then exit
if (args.runCLI()) {
2018-06-10 11:45:24 -04:00
// just import and print license when running with --license option
if (LICENSE.isFile()) {
2018-12-05 07:24:18 -05:00
String psm = args.getLicenseKey();
if (psm != null) {
configureLicense(psm);
2019-02-21 04:52:22 -05:00
System.exit(SUCCESS);
2018-12-05 07:24:18 -05:00
}
}
2018-06-10 11:45:24 -04:00
int status = new ArgumentProcessor().run(args);
System.exit(status);
}
// just print help page if we can't run any command and also can't start the GUI
if (isHeadless()) {
log.info(String.format("%s / %s (headless)%n%n%s", getApplicationIdentifier(), getJavaRuntimeIdentifier(), args.usage()));
2019-02-21 04:52:22 -05:00
System.exit(ERROR);
2016-10-18 14:30:07 -04:00
}
// GUI mode => start user interface
2016-10-18 13:02:51 -04:00
SwingUtilities.invokeLater(() -> {
startUserInterface(args);
2016-10-18 13:02:51 -04:00
// run background tasks
2016-10-30 13:38:12 -04:00
newSwingWorker(() -> onStart(args)).execute();
2016-10-18 13:02:51 -04:00
});
2015-10-11 12:56:01 -04:00
} catch (CmdLineException e) {
// illegal arguments => print CLI error message
2016-10-18 13:02:51 -04:00
log.severe(e::getMessage);
2019-02-21 04:52:22 -05:00
System.exit(ERROR);
2015-10-11 12:56:01 -04:00
} catch (Throwable e) {
// unexpected error => dump stack
debug.log(Level.SEVERE, "Error during startup", e);
2019-02-21 04:52:22 -05:00
System.exit(ERROR);
}
}
private static void onStart(ArgumentBean args) throws Exception {
2016-10-18 13:02:51 -04:00
// publish file arguments
List<File> files = args.getFiles(false);
if (files.size() > 0) {
SwingEventBus.getInstance().post(new FileTransferable(files));
}
2018-12-05 07:24:18 -05:00
// import license if launched with license file
2018-06-11 05:32:23 -04:00
if (LICENSE.isFile()) {
2018-12-05 07:24:18 -05:00
try {
String psm = args.getLicenseKey();
if (psm != null) {
configureLicense(psm);
}
} catch (Throwable e) {
debug.log(Level.WARNING, e, e::getMessage);
}
2018-06-10 15:13:53 -04:00
}
// restore preferences from backup if necessary
try {
if (Preferences.userNodeForPackage(Main.class).keys().length == 0) {
File f = getPreferencesBackupFile();
if (f.exists()) {
log.fine("Restore user preferences: " + f);
Settings.restore(f);
} else {
log.fine("No user preferences found: " + f);
}
}
} catch (Exception e) {
debug.log(Level.WARNING, "Failed to restore preferences", e);
}
2016-10-16 05:34:10 -04:00
// JavaFX is used for ProgressMonitor and GettingStartedDialog
try {
initJavaFX();
} catch (Throwable e) {
2018-06-10 05:16:51 -04:00
log.log(Level.SEVERE, "Failed to initialize JavaFX", e);
2016-10-16 05:34:10 -04:00
}
2016-10-16 03:46:42 -04:00
// check if application help should be shown
if (!"skip".equals(System.getProperty("application.help"))) {
try {
checkGettingStarted();
} catch (Throwable e) {
debug.log(Level.WARNING, "Failed to show Getting Started help", e);
}
}
// check for application updates
if (!"skip".equals(System.getProperty("application.update"))) {
try {
checkUpdate();
} catch (Throwable e) {
debug.log(Level.WARNING, "Failed to check for updates", e);
}
}
}
private static void startUserInterface(ArgumentBean args) {
2018-07-01 12:36:32 -04:00
// use native LaF an all platforms
setTheme();
2016-03-09 11:02:36 -05:00
2018-08-09 02:58:04 -04:00
// start standard frame or single panel frame
List<PanelBuilder> panels = args.getPanelBuilders();
2016-11-25 07:47:08 -05:00
JFrame frame = panels.size() > 1 ? new MainFrame(panels) : new SinglePanelFrame(panels.get(0));
2013-04-09 14:20:52 -04:00
try {
2016-11-25 07:47:08 -05:00
restoreWindowBounds(frame, Settings.forPackage(MainFrame.class)); // restore previous size and location
2013-04-09 14:20:52 -04:00
} catch (Exception e) {
2016-11-25 07:47:08 -05:00
frame.setLocation(120, 80); // make sure the main window is not displayed out of screen bounds
2013-04-09 14:20:52 -04:00
}
2016-11-23 15:51:40 -05:00
frame.addWindowListener(windowClosed(evt -> {
evt.getWindow().setVisible(false);
// make sure any long running operations are done now and not later on the
// shutdown hook thread
2016-11-23 15:51:40 -05:00
HistorySpooler.getInstance().commit();
2018-06-14 11:26:39 -04:00
if (isAppStore()) {
SupportDialog.AppStoreReview.maybeShow();
}
// restore preferences on start if empty (TODO: remove after a few releases)
try {
File f = getPreferencesBackupFile();
2019-04-19 04:26:51 -04:00
if (!f.exists() || !lastModifiedWithin(f, Duration.ofDays(30))) {
log.fine("Store user preferences: " + f);
Settings.store(f);
}
} catch (Exception e) {
debug.log(Level.WARNING, "Failed to store preferences", e);
}
2016-11-23 15:51:40 -05:00
System.exit(0);
}));
// configure main window
if (isMacApp()) {
2018-07-01 12:36:32 -04:00
// macOS-specific configuration
MacAppUtilities.initializeApplication(FileBotMenuBar.createHelp(), files -> {
2018-06-14 06:11:00 -04:00
if (LICENSE.isFile() && files.size() == 1 && containsOnly(files, LICENSE_FILES)) {
configureLicense(files.get(0));
} else {
SwingEventBus.getInstance().post(new FileTransferable(files));
}
});
} else if (isWindowsApp()) {
2018-07-01 12:36:32 -04:00
// Windows-specific configuration
2018-07-23 10:14:07 -04:00
WinAppUtilities.initializeApplication(isUWP() ? null : getApplicationName());
2018-03-05 06:44:24 -05:00
frame.setIconImages(ResourceManager.getApplicationIconImages());
} else {
2018-07-01 12:36:32 -04:00
// generic Linux / FreeBSD / Solaris configuration
2018-03-05 06:44:24 -05:00
frame.setIconImages(ResourceManager.getApplicationIconImages());
}
// start application
2015-05-09 04:07:38 -04:00
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
private static File getPreferencesBackupFile() {
return ApplicationFolder.AppData.resolve("preferences.backup.xml");
}
/**
* Show update notifications if updates are available
*/
private static void checkUpdate() throws Exception {
Cache cache = Cache.getCache(getApplicationName(), CacheType.Persistent);
Document dom = cache.xml(getApplicationProperty("update.url"), URL::new).expire(Cache.ONE_WEEK).retry(0).get();
// flush to disk
cache.flush();
// parse update xml
2016-11-23 15:51:40 -05:00
Map<String, String> update = streamElements(dom.getFirstChild()).collect(toMap(n -> n.getNodeName(), n -> n.getTextContent().trim()));
// check if update is required
int latestRev = Integer.parseInt(update.get("revision"));
int currentRev = getApplicationRevisionNumber();
if (latestRev > currentRev && currentRev > 0) {
SwingUtilities.invokeLater(() -> {
2016-11-23 15:51:40 -05:00
JDialog dialog = new JDialog(JFrame.getFrames()[0], update.get("title"), ModalityType.APPLICATION_MODAL);
JPanel pane = new JPanel(new MigLayout("fill, nogrid, insets dialog"));
dialog.setContentPane(pane);
pane.add(new JLabel(ResourceManager.getIcon("window.icon.medium")), "aligny top");
2016-11-19 13:52:01 -05:00
pane.add(new JLabel(update.get("message")), "aligny top, gap 10, wrap paragraph:push");
pane.add(newButton("Download", ResourceManager.getIcon("dialog.continue"), evt -> {
openURI(update.get("download"));
dialog.setVisible(false);
}), "tag ok");
pane.add(newButton("Details", ResourceManager.getIcon("action.report"), evt -> {
openURI(update.get("discussion"));
}), "tag help2");
pane.add(newButton("Ignore", ResourceManager.getIcon("dialog.cancel"), evt -> {
dialog.setVisible(false);
}), "tag cancel");
dialog.pack();
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
dialog.setVisible(true);
});
}
}
/**
* Show Getting Started to new users
*/
private static void checkGettingStarted() throws Exception {
PreferencesEntry<String> started = Settings.forPackage(Main.class).entry("getting.started").defaultValue("0");
if ("0".equals(started.getValue())) {
started.setValue("1");
started.flush();
// open Getting Started
2018-11-18 09:28:14 -05:00
SwingUtilities.invokeLater(() -> openGettingStarted("show".equals(System.getProperty("application.help"))));
}
}
2016-11-23 15:51:40 -05:00
private static void restoreWindowBounds(JFrame window, Settings settings) {
// store bounds on close
2016-11-23 15:51:40 -05:00
window.addWindowListener(windowClosed(evt -> {
// 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()));
}
2016-11-23 15:51:40 -05:00
}));
// 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);
}
/**
2018-08-09 02:58:04 -04:00
* Initialize default SecurityManager and grant all permissions via security policy. Initialization is required in order to run {@link ExpressionFormat} in a secure sandbox.
*/
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
debug.log(Level.WARNING, e, e::getMessage);
}
}
2016-03-08 11:21:10 -05:00
public static void initializeSystemProperties(ArgumentBean args) {
2018-06-29 06:23:31 -04:00
System.setProperty("http.agent", String.format("%s/%s", getApplicationName(), getApplicationVersion()));
2016-03-08 11:21:10 -05:00
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "60000");
System.setProperty("swing.crossplatformlaf", "javax.swing.plaf.nimbus.NimbusLookAndFeel");
2016-11-25 10:59:26 -05:00
System.setProperty("grape.root", ApplicationFolder.AppData.resolve("grape").getPath());
2016-03-08 11:21:10 -05:00
System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
// enable dark mode by default if dark mode is set system-wide
if (Boolean.parseBoolean(System.getProperty("DarkMode"))) {
System.setProperty("net.filebot.theme", "Darcula");
}
// set additional user-defined default system properties
File userDefinedSystemProperties = ApplicationFolder.AppData.resolve("system.properties");
if (userDefinedSystemProperties.isFile()) {
try (FileInputStream in = new FileInputStream(userDefinedSystemProperties)) {
Properties p = new Properties();
p.load(in);
p.forEach((k, v) -> System.setProperty(k.toString(), v.toString()));
} catch (Exception e) {
log.log(Level.WARNING, e, e::getMessage);
}
}
if (args.unixfs) {
System.setProperty("unixfs", "true");
}
2016-11-25 07:47:08 -05:00
if (args.disableExtendedAttributes) {
System.setProperty("useExtendedFileAttributes", "false");
System.setProperty("useCreationDate", "false");
}
2019-05-19 01:47:17 -04:00
if (args.disableHistory) {
System.setProperty("application.rename.history", "false");
}
2016-03-08 11:21:10 -05:00
}
2016-03-10 09:05:56 -05:00
public static void initializeLogging(ArgumentBean args) throws IOException {
2016-11-25 12:23:56 -05:00
// make sure that these folders exist
2016-11-25 12:37:09 -05:00
ApplicationFolder.TemporaryFiles.get().mkdirs();
ApplicationFolder.AppData.get().mkdirs();
2016-11-25 12:23:56 -05:00
2016-03-15 10:57:21 -04:00
if (args.runCLI()) {
// CLI logging settings
log.setLevel(args.getLogLevel());
} else {
// GUI logging settings
log.setLevel(Level.INFO);
log.addHandler(new NotificationHandler(getApplicationName()));
// log errors to file
try {
Handler errorLogHandler = createSimpleFileHandler(ApplicationFolder.AppData.resolve("error.log"), Level.WARNING);
log.addHandler(errorLogHandler);
debug.addHandler(errorLogHandler);
2016-03-15 10:57:21 -04:00
} catch (Exception e) {
2016-11-25 12:23:56 -05:00
log.log(Level.WARNING, "Failed to initialize error log", e);
2016-03-15 10:57:21 -04:00
}
}
// tee stdout and stderr to log file if --log-file is set
2016-03-10 09:05:56 -05:00
if (args.logFile != null) {
Handler logFileHandler = createLogFileHandler(args.getLogFile(), args.logLock, Level.ALL);
log.addHandler(logFileHandler);
debug.addHandler(logFileHandler);
2016-03-10 09:05:56 -05:00
}
}
}