diff --git a/source/net/filebot/ui/subtitle/upload/SubtitleUploadDialog.java b/source/net/filebot/ui/subtitle/upload/SubtitleUploadDialog.java index 840aaf9b..6e53ac67 100644 --- a/source/net/filebot/ui/subtitle/upload/SubtitleUploadDialog.java +++ b/source/net/filebot/ui/subtitle/upload/SubtitleUploadDialog.java @@ -1,6 +1,7 @@ package net.filebot.ui.subtitle.upload; import static net.filebot.media.MediaDetection.*; +import static net.filebot.util.ui.SwingUI.*; import java.awt.Color; import java.awt.Dimension; @@ -21,14 +22,10 @@ import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JScrollPane; import javax.swing.JTable; -import javax.swing.SwingWorker; import net.filebot.Language; import net.filebot.ResourceManager; @@ -64,8 +61,8 @@ public class SubtitleUploadDialog extends JDialog { content.add(new JScrollPane(subtitleMappingTable), "grow, wrap"); - content.add(new JButton(uploadAction), "tag ok"); - content.add(new JButton(finishAction), "tag cancel"); + content.add(newButton("Upload", ResourceManager.getIcon("dialog.continue"), this::doUpload), "tag ok"); + content.add(newButton("Close", ResourceManager.getIcon("dialog.cancel"), this::doClose), "tag cancel"); } protected JTable createTable() { @@ -108,10 +105,9 @@ public class SubtitleUploadDialog extends JDialog { } public void startChecking() { - SubtitleMapping[] data = ((SubtitleMappingTableModel) subtitleMappingTable.getModel()).getData(); - for (SubtitleMapping it : data) { - if (it.isCheckReady()) { - checkExecutorService.submit(new CheckTask(it)); + for (SubtitleMapping mapping : ((SubtitleMappingTableModel) subtitleMappingTable.getModel()).getData()) { + if (mapping.isCheckReady()) { + checkExecutorService.submit(() -> runCheck(mapping)); } } } @@ -135,153 +131,121 @@ public class SubtitleUploadDialog extends JDialog { }).toList(); } - private final Action uploadAction = new AbstractAction("Upload", ResourceManager.getIcon("dialog.continue")) { + private void runCheck(SubtitleMapping mapping) { + try { - @Override - public void actionPerformed(ActionEvent evt) { - // disable any active cell editor - if (subtitleMappingTable.getCellEditor() != null) { - subtitleMappingTable.getCellEditor().stopCellEditing(); - } + if (mapping.getIdentity() == null && mapping.getVideo() != null) { + mapping.setState(Status.Checking); - // don't allow restart of upload as long as there are still unfinished download tasks - if (uploadExecutorService != null && !uploadExecutorService.isTerminated()) { - return; - } + CheckResult checkResult = database.checkSubtitle(mapping.getVideo(), mapping.getSubtitle()); - uploadExecutorService = Executors.newSingleThreadExecutor(); - - SubtitleMapping[] table = ((SubtitleMappingTableModel) subtitleMappingTable.getModel()).getData(); - for (SubtitleGroup it : groupRunsByCD(table)) { - if (it.isUploadReady()) { - uploadExecutorService.submit(new UploadTask(it)); + // force upload all subtitles regardless of what TryUploadSubtitles returns (because other programs often submit crap) + if (checkResult.exists) { + mapping.setLanguage(Language.getLanguage(checkResult.language)); // trust language hint only if upload not required } } - // terminate after all uploads have been completed - uploadExecutorService.shutdown(); - } - }; - - private final Action finishAction = new AbstractAction("Close", ResourceManager.getIcon("dialog.cancel")) { - - @Override - public void actionPerformed(ActionEvent evt) { - if (checkExecutorService != null) { - checkExecutorService.shutdownNow(); - } - if (uploadExecutorService != null) { - uploadExecutorService.shutdownNow(); + if (mapping.getLanguage() == null) { + mapping.setState(Status.Identifying); + try { + Locale locale = database.detectLanguage(FileUtilities.readFile(mapping.getSubtitle())); + mapping.setLanguage(Language.getLanguage(locale)); + } catch (Exception e) { + Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to auto-detect language: " + e.getMessage()); + } } - setVisible(false); - dispose(); - } - }; - - private class CheckTask extends SwingWorker { - - private final SubtitleMapping mapping; - - public CheckTask(SubtitleMapping mapping) { - this.mapping = mapping; - } - - @Override - protected Object doInBackground() throws Exception { - try { - - if (mapping.getIdentity() == null && mapping.getVideo() != null) { - mapping.setState(Status.Checking); - - CheckResult checkResult = database.checkSubtitle(mapping.getVideo(), mapping.getSubtitle()); - - // force upload all subtitles regardless of what TryUploadSubtitles returns (because other programs often submit crap) - if (checkResult.exists) { - mapping.setLanguage(Language.getLanguage(checkResult.language)); // trust language hint only if upload not required - } - } - - if (mapping.getLanguage() == null) { - mapping.setState(Status.Identifying); - try { - Locale locale = database.detectLanguage(FileUtilities.readFile(mapping.getSubtitle())); - mapping.setLanguage(Language.getLanguage(locale)); - } catch (Exception e) { - Logger.getLogger(CheckTask.class.getClass().getName()).log(Level.WARNING, "Failed to auto-detect language: " + e.getMessage()); - } - } - - if (mapping.getIdentity() == null && mapping.getVideo() != null) { - mapping.setState(Status.Identifying); - try { - if (MediaDetection.isEpisode(mapping.getVideo().getPath(), true)) { - List seriesNames = MediaDetection.detectSeriesNames(Collections.singleton(mapping.getVideo()), true, false, Locale.ENGLISH); - NAMES: for (String name : seriesNames) { - List options = WebServices.TheTVDB.search(name, Locale.ENGLISH); - for (SearchResult entry : options) { - TheTVDBSeriesInfo seriesInfo = (TheTVDBSeriesInfo) WebServices.TheTVDB.getSeriesInfo(entry, Locale.ENGLISH); - if (seriesInfo.getImdbId() != null) { - int imdbId = grepImdbId(seriesInfo.getImdbId()).iterator().next(); - mapping.setIdentity(WebServices.OpenSubtitles.getMovieDescriptor(new Movie(null, 0, imdbId, -1), Locale.ENGLISH)); - break NAMES; - } - } - } - } else { - Collection identity = MediaDetection.detectMovie(mapping.getVideo(), database, Locale.ENGLISH, true); - for (Movie it : identity) { - if (it.getImdbId() <= 0 && it.getTmdbId() > 0) { - it = WebServices.TheMovieDB.getMovieDescriptor(it, Locale.ENGLISH); - } - if (it != null && it.getImdbId() > 0) { - mapping.setIdentity(it); - break; + if (mapping.getIdentity() == null && mapping.getVideo() != null) { + mapping.setState(Status.Identifying); + try { + if (MediaDetection.isEpisode(mapping.getVideo().getPath(), true)) { + List seriesNames = MediaDetection.detectSeriesNames(Collections.singleton(mapping.getVideo()), true, false, Locale.ENGLISH); + NAMES: for (String name : seriesNames) { + List options = WebServices.TheTVDB.search(name, Locale.ENGLISH); + for (SearchResult entry : options) { + TheTVDBSeriesInfo seriesInfo = (TheTVDBSeriesInfo) WebServices.TheTVDB.getSeriesInfo(entry, Locale.ENGLISH); + if (seriesInfo.getImdbId() != null) { + int imdbId = grepImdbId(seriesInfo.getImdbId()).iterator().next(); + mapping.setIdentity(WebServices.OpenSubtitles.getMovieDescriptor(new Movie(null, 0, imdbId, -1), Locale.ENGLISH)); + break NAMES; } } } - } catch (Exception e) { - Logger.getLogger(CheckTask.class.getClass().getName()).log(Level.WARNING, "Failed to auto-detect movie: " + e.getMessage()); + } else { + Collection identity = MediaDetection.detectMovie(mapping.getVideo(), database, Locale.ENGLISH, true); + for (Movie it : identity) { + if (it.getImdbId() <= 0 && it.getTmdbId() > 0) { + it = WebServices.TheMovieDB.getMovieDescriptor(it, Locale.ENGLISH); + } + if (it != null && it.getImdbId() > 0) { + mapping.setIdentity(it); + break; + } + } } + } catch (Exception e) { + Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to auto-detect movie: " + e.getMessage()); } - - if (mapping.getVideo() == null) { - mapping.setState(Status.IllegalInput); - } else if (mapping.getIdentity() == null || mapping.getLanguage() == null) { - mapping.setState(Status.IdentificationRequired); - } else { - mapping.setState(Status.UploadReady); - } - } catch (Exception e) { - Logger.getLogger(CheckTask.class.getClass().getName()).log(Level.SEVERE, e.getMessage(), e); - mapping.setState(Status.CheckFailed); } - return null; + + if (mapping.getVideo() == null) { + mapping.setState(Status.IllegalInput); + } else if (mapping.getIdentity() == null || mapping.getLanguage() == null) { + mapping.setState(Status.IdentificationRequired); + } else { + mapping.setState(Status.UploadReady); + } + } catch (Exception e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, e.getMessage(), e); + mapping.setState(Status.CheckFailed); } } - private class UploadTask extends SwingWorker { + private void runUpload(SubtitleGroup group) { + try { + group.setState(Status.Uploading); + database.uploadSubtitle(group.getIdentity(), group.getLanguage().getLocale(), group.getVideoFiles(), group.getSubtitleFiles()); + group.setState(Status.UploadComplete); + } catch (Exception e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, e.getMessage(), e); + group.setState(Status.UploadFailed); + } + } - private final SubtitleGroup uploadGroup; - - public UploadTask(SubtitleGroup uploadGroup) { - this.uploadGroup = uploadGroup; + public void doUpload(ActionEvent evt) { + // disable any active cell editor + if (subtitleMappingTable.getCellEditor() != null) { + subtitleMappingTable.getCellEditor().stopCellEditing(); } - @Override - protected Object doInBackground() { - try { - uploadGroup.setState(Status.Uploading); + // don't allow restart of upload as long as there are still unfinished download tasks + if (uploadExecutorService != null && !uploadExecutorService.isTerminated()) { + return; + } - database.uploadSubtitle(uploadGroup.getIdentity(), uploadGroup.getLanguage().getLocale(), uploadGroup.getVideoFiles(), uploadGroup.getSubtitleFiles()); + uploadExecutorService = Executors.newSingleThreadExecutor(); - uploadGroup.setState(Status.UploadComplete); - } catch (Exception e) { - Logger.getLogger(UploadTask.class.getClass().getName()).log(Level.SEVERE, e.getMessage(), e); - uploadGroup.setState(Status.UploadFailed); + SubtitleMapping[] table = ((SubtitleMappingTableModel) subtitleMappingTable.getModel()).getData(); + for (SubtitleGroup group : groupRunsByCD(table)) { + if (group.isUploadReady()) { + uploadExecutorService.submit(() -> runUpload(group)); } - return null; } + + // terminate after all uploads have been completed + uploadExecutorService.shutdown(); + } + + public void doClose(ActionEvent evt) { + if (checkExecutorService != null) { + checkExecutorService.shutdownNow(); + } + if (uploadExecutorService != null) { + uploadExecutorService.shutdownNow(); + } + + setVisible(false); + dispose(); } } diff --git a/source/net/filebot/util/ui/SwingUI.java b/source/net/filebot/util/ui/SwingUI.java index b699144e..45ba8a20 100644 --- a/source/net/filebot/util/ui/SwingUI.java +++ b/source/net/filebot/util/ui/SwingUI.java @@ -20,6 +20,8 @@ import java.awt.image.BufferedImage; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; import java.util.regex.Pattern; import javax.swing.AbstractAction; @@ -33,6 +35,7 @@ import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; import javax.swing.Timer; import javax.swing.event.MouseInputListener; import javax.swing.plaf.basic.BasicTableUI; @@ -266,6 +269,89 @@ public final class SwingUI { return timer; } + public static JButton newButton(String name, Icon icon, Consumer action) { + return new JButton(new LambdaAction(name, icon, action)); + } + + public static Action newAction(String name, Icon icon, Consumer action) { + return new LambdaAction(name, icon, action); + } + + private static class LambdaAction extends AbstractAction { + + private Consumer action; + + public LambdaAction(String name, Icon icon, Consumer action) { + super(name, icon); + this.action = action; + } + + @Override + public void actionPerformed(ActionEvent e) { + action.accept(e); + } + } + + public static SwingWorker newSwingWorker(BackgroundRunnable doInBackground) { + return new SwingRunnable(doInBackground); + } + + public static SwingWorker newSwingWorker(BackgroundSupplier doInBackground, Consumer done, Consumer error) { + return new SwingLambda(doInBackground, done, error); + } + + private static class SwingRunnable extends SwingWorker { + + private BackgroundRunnable doInBackground; + + public SwingRunnable(BackgroundRunnable doInBackground) { + this.doInBackground = doInBackground; + } + + @Override + protected Void doInBackground() throws Exception { + doInBackground.run(); + return null; + } + } + + @FunctionalInterface + public static interface BackgroundRunnable { + void run() throws Exception; + } + + @FunctionalInterface + public static interface BackgroundSupplier { + T get() throws Exception; + } + + private static class SwingLambda extends SwingWorker { + + private BackgroundSupplier doInBackground; + private Consumer done; + private Consumer error; + + public SwingLambda(BackgroundSupplier doInBackground, Consumer done, Consumer error) { + this.doInBackground = doInBackground; + this.done = done; + this.error = error; + } + + @Override + protected T doInBackground() throws Exception { + return doInBackground.get(); + } + + @Override + protected void done() { + try { + done.accept(get()); + } catch (InterruptedException | ExecutionException e) { + error.accept(e); + } + } + } + /** * When trying to drag a row of a multi-select JTable, it will start selecting rows instead of initiating a drag. This TableUI will give the JTable proper dnd behaviour. */