mirror of
https://github.com/mitb-archive/filebot
synced 2024-12-23 16:28:51 -05:00
Refactor Presets
This commit is contained in:
parent
e921e50c3c
commit
c2fc4c2913
@ -102,7 +102,7 @@ public final class WebServices {
|
||||
return getService(name, getMusicIdentificationServices());
|
||||
}
|
||||
|
||||
private static <T extends Datasource> T getService(String name, T[] services) {
|
||||
public static <T extends Datasource> T getService(String name, T[] services) {
|
||||
return StreamEx.of(services).findFirst(it -> it.getIdentifier().equalsIgnoreCase(name) || it.getName().equalsIgnoreCase(name)).orElse(null);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import java.util.Map;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import net.filebot.ResourceManager;
|
||||
import net.filebot.web.Datasource;
|
||||
|
||||
public class XattrMetaInfoProvider implements Datasource {
|
||||
@ -18,7 +19,7 @@ public class XattrMetaInfoProvider implements Datasource {
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return null;
|
||||
return ResourceManager.getIcon("search.xattr");
|
||||
}
|
||||
|
||||
public Map<File, Object> match(Collection<File> files, boolean strict) {
|
||||
|
BIN
source/net/filebot/resources/search.xattr.png
Normal file
BIN
source/net/filebot/resources/search.xattr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 528 B |
BIN
source/net/filebot/resources/search.xattr@2x.png
Normal file
BIN
source/net/filebot/resources/search.xattr@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -100,33 +100,29 @@ public class MainFrame extends JFrame {
|
||||
}));
|
||||
|
||||
installAction(getRootPane(), getKeyStroke(VK_F5, 0), newAction("Run", evt -> {
|
||||
try {
|
||||
withWaitCursor(getRootPane(), () -> {
|
||||
GroovyPad pad = new GroovyPad();
|
||||
withWaitCursor(getRootPane(), () -> {
|
||||
GroovyPad pad = new GroovyPad();
|
||||
|
||||
pad.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowOpened(WindowEvent e) {
|
||||
setVisible(false);
|
||||
pad.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowOpened(WindowEvent e) {
|
||||
setVisible(false);
|
||||
|
||||
// run default script on startup
|
||||
pad.runScript(GroovyPad.DEFAULT_SCRIPT);
|
||||
};
|
||||
// run default script on startup
|
||||
pad.runScript(GroovyPad.DEFAULT_SCRIPT);
|
||||
};
|
||||
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
setVisible(true);
|
||||
};
|
||||
});
|
||||
|
||||
pad.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
pad.setModalExclusionType(ModalExclusionType.TOOLKIT_EXCLUDE);
|
||||
pad.setLocationByPlatform(true);
|
||||
pad.setVisible(true);
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
setVisible(true);
|
||||
};
|
||||
});
|
||||
} catch (Exception e) {
|
||||
debug.log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
|
||||
pad.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
pad.setModalExclusionType(ModalExclusionType.TOOLKIT_EXCLUDE);
|
||||
pad.setLocationByPlatform(true);
|
||||
pad.setVisible(true);
|
||||
});
|
||||
}));
|
||||
|
||||
installAction(this.getRootPane(), getKeyStroke(VK_F1, 0), newAction("Help", evt -> GettingStartedStage.start()));
|
||||
|
@ -25,14 +25,26 @@ class ExpressionFormatter implements MatchFormatter {
|
||||
private Class<?> target;
|
||||
|
||||
public ExpressionFormatter(String expression, Format preview, Class<?> target) {
|
||||
if (expression == null || expression.isEmpty())
|
||||
if (expression == null || expression.isEmpty()) {
|
||||
throw new IllegalArgumentException("Expression must not be null or empty");
|
||||
}
|
||||
|
||||
this.expression = expression;
|
||||
this.preview = preview;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public ExpressionFormatter(ExpressionFormat format, Format preview, Class<?> target) {
|
||||
this(format.getExpression(), preview, target);
|
||||
|
||||
// use compiled format expression right away
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public Class<?> getTargetClass() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canFormat(Match<?, ?> match) {
|
||||
// target object is required, file is optional
|
||||
|
@ -162,12 +162,13 @@ public class FormatDialog extends JDialog {
|
||||
}
|
||||
|
||||
public static Mode getMode(Datasource datasource) {
|
||||
if (datasource instanceof EpisodeListProvider)
|
||||
return Mode.Episode;
|
||||
if (datasource instanceof MovieIdentificationService)
|
||||
return Mode.Movie;
|
||||
if (datasource instanceof EpisodeListProvider)
|
||||
return Mode.Episode;
|
||||
if (datasource instanceof MusicIdentificationService)
|
||||
return Mode.Music;
|
||||
|
||||
return Mode.File;
|
||||
}
|
||||
}
|
||||
@ -376,8 +377,6 @@ public class FormatDialog extends JDialog {
|
||||
return new ExpressionFormat(format).format(sample);
|
||||
}, s -> {
|
||||
formatExample.setText(s);
|
||||
}, e -> {
|
||||
debug.log(Level.SEVERE, e.getMessage(), e);
|
||||
}).execute();
|
||||
});
|
||||
|
||||
|
@ -39,8 +39,8 @@ class MatchAction extends AbstractAction {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
withWaitCursor(evt.getSource(), () -> {
|
||||
withWaitCursor(evt.getSource(), () -> {
|
||||
try {
|
||||
Matcher<Object, File> matcher = new Matcher<Object, File>(model.values(), model.candidates(), false, EpisodeMetrics.defaultSequence(true));
|
||||
List<Match<Object, File>> matches = ProgressMonitor.runTask("Match", "Finding optimal alignment. This may take a while.", (message, progress, cancelled) -> {
|
||||
message.accept(String.format("Checking %d combinations...", matcher.remainingCandidates().size() * matcher.remainingValues().size()));
|
||||
@ -53,12 +53,12 @@ class MatchAction extends AbstractAction {
|
||||
|
||||
// insert objects that could not be matched at the end of the model
|
||||
model.addAll(matcher.remainingValues(), matcher.remainingCandidates());
|
||||
});
|
||||
} catch (CancellationException e) {
|
||||
debug.finest(e::toString);
|
||||
} catch (Throwable e) {
|
||||
log.log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
} catch (CancellationException e) {
|
||||
debug.finest(e::toString);
|
||||
} catch (Throwable e) {
|
||||
log.log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,13 +17,14 @@ import net.filebot.web.SortOrder;
|
||||
|
||||
public class PlainFileMatcher implements Datasource, AutoCompleteMatcher {
|
||||
|
||||
public static PlainFileMatcher getInstance() {
|
||||
return new PlainFileMatcher();
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "file";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Generic File";
|
||||
return "Plain File";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,21 +1,23 @@
|
||||
package net.filebot.ui.rename;
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static net.filebot.Logging.*;
|
||||
import static net.filebot.WebServices.*;
|
||||
import static net.filebot.util.FileUtilities.*;
|
||||
import static net.filebot.util.ui.SwingUI.*;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.filebot.CachedResource.Transform;
|
||||
import net.filebot.Language;
|
||||
import net.filebot.Settings;
|
||||
import net.filebot.StandardRenameAction;
|
||||
import net.filebot.WebServices;
|
||||
import net.filebot.format.ExpressionFileFilter;
|
||||
import net.filebot.format.ExpressionFilter;
|
||||
import net.filebot.format.ExpressionFormat;
|
||||
import net.filebot.mac.MacAppUtilities;
|
||||
import net.filebot.media.XattrMetaInfoProvider;
|
||||
import net.filebot.web.Datasource;
|
||||
import net.filebot.web.EpisodeListProvider;
|
||||
import net.filebot.web.MovieIdentificationService;
|
||||
@ -51,114 +53,77 @@ public class Preset {
|
||||
}
|
||||
|
||||
public File getInputFolder() {
|
||||
return path == null || path.isEmpty() ? null : new File(path);
|
||||
return getValue(path, File::new);
|
||||
}
|
||||
|
||||
public ExpressionFileFilter getIncludeFilter() {
|
||||
try {
|
||||
return path == null || path.isEmpty() || includes == null || includes.isEmpty() ? null : new ExpressionFileFilter(new ExpressionFilter(includes), false);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return getInputFolder() == null ? null : getValue(includes, expression -> new ExpressionFileFilter(new ExpressionFilter(expression), false));
|
||||
}
|
||||
|
||||
public ExpressionFormat getFormat() {
|
||||
try {
|
||||
return format == null || format.isEmpty() ? null : new ExpressionFormat(format);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<File> selectInputFiles(ActionEvent evt) {
|
||||
File folder = getInputFolder();
|
||||
ExpressionFileFilter filter = getIncludeFilter();
|
||||
|
||||
if (folder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Settings.isMacSandbox()) {
|
||||
if (!MacAppUtilities.askUnlockFolders(getWindow(evt.getSource()), singleton(getInputFolder()))) {
|
||||
throw new IllegalStateException("Unable to access folder: " + folder);
|
||||
}
|
||||
}
|
||||
|
||||
return listFiles(getInputFolder(), filter == null ? FILES : filter, HUMAN_NAME_ORDER);
|
||||
}
|
||||
|
||||
public AutoCompleteMatcher getAutoCompleteMatcher() {
|
||||
MovieIdentificationService mdb = WebServices.getMovieIdentificationService(database);
|
||||
if (mdb != null) {
|
||||
return new MovieMatcher(mdb);
|
||||
}
|
||||
|
||||
EpisodeListProvider sdb = WebServices.getEpisodeListProvider(database);
|
||||
if (sdb != null) {
|
||||
return new EpisodeListMatcher(sdb, sdb == WebServices.AniDB);
|
||||
}
|
||||
|
||||
MusicIdentificationService adb = WebServices.getMusicIdentificationService(database);
|
||||
if (adb != null) {
|
||||
return new MusicMatcher(adb);
|
||||
}
|
||||
|
||||
if (PlainFileMatcher.getInstance().getIdentifier().equals(database)) {
|
||||
return PlainFileMatcher.getInstance();
|
||||
}
|
||||
|
||||
throw new IllegalStateException(database);
|
||||
}
|
||||
|
||||
public Datasource getDatasource() {
|
||||
if (database == null || database.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MovieIdentificationService mdb = WebServices.getMovieIdentificationService(database);
|
||||
if (mdb != null) {
|
||||
return mdb;
|
||||
}
|
||||
|
||||
EpisodeListProvider sdb = WebServices.getEpisodeListProvider(database);
|
||||
if (sdb != null) {
|
||||
return sdb;
|
||||
}
|
||||
|
||||
MusicIdentificationService adb = WebServices.getMusicIdentificationService(database);
|
||||
if (adb != null) {
|
||||
return adb;
|
||||
}
|
||||
|
||||
if (PlainFileMatcher.getInstance().getIdentifier().equals(database)) {
|
||||
return PlainFileMatcher.getInstance();
|
||||
}
|
||||
|
||||
throw new IllegalStateException(database);
|
||||
return getValue(format, ExpressionFormat::new);
|
||||
}
|
||||
|
||||
public String getMatchMode() {
|
||||
return matchMode == null || matchMode.isEmpty() ? null : matchMode;
|
||||
return getValue(matchMode, mode -> mode);
|
||||
}
|
||||
|
||||
public SortOrder getSortOrder() {
|
||||
try {
|
||||
return SortOrder.forName(sortOrder);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return getValue(sortOrder, SortOrder::forName);
|
||||
}
|
||||
|
||||
public Language getLanguage() {
|
||||
return language == null || language.isEmpty() ? null : Language.getLanguage(language);
|
||||
return getValue(language, Language::getLanguage);
|
||||
}
|
||||
|
||||
public StandardRenameAction getRenameAction() {
|
||||
return getValue(action, StandardRenameAction::forName);
|
||||
}
|
||||
|
||||
public Datasource getDatasource() {
|
||||
return getValue(database, id -> getService(id, getSupportedServices()));
|
||||
}
|
||||
|
||||
private <T> T getValue(String s, Transform<String, T> t) {
|
||||
try {
|
||||
return StandardRenameAction.forName(action);
|
||||
return s == null || s.isEmpty() ? null : t.transform(s);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
debug.log(Level.WARNING, e, e::toString);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<File> selectFiles() {
|
||||
File folder = getInputFolder();
|
||||
if (folder == null || !folder.isDirectory()) {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
FileFilter filter = getIncludeFilter();
|
||||
|
||||
return listFiles(folder, filter == null ? FILES : f -> FILES.accept(f) && filter.accept(f), HUMAN_NAME_ORDER);
|
||||
}
|
||||
|
||||
public AutoCompleteMatcher getAutoCompleteMatcher() {
|
||||
Datasource db = getDatasource();
|
||||
|
||||
if (db instanceof MovieIdentificationService) {
|
||||
return new MovieMatcher((MovieIdentificationService) db);
|
||||
}
|
||||
|
||||
if (db instanceof EpisodeListProvider) {
|
||||
return new EpisodeListMatcher((EpisodeListProvider) db, db == AniDB);
|
||||
}
|
||||
|
||||
if (db instanceof MusicIdentificationService) {
|
||||
return new MusicMatcher((MusicIdentificationService) db);
|
||||
}
|
||||
|
||||
if (db instanceof XattrMetaInfoProvider) {
|
||||
return XATTR_FILE_MATCHER;
|
||||
}
|
||||
|
||||
return PLAIN_FILE_MATCHER; // default to plain file matcher
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -166,4 +131,25 @@ public class Preset {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static final XattrFileMatcher XATTR_FILE_MATCHER = new XattrFileMatcher();
|
||||
public static final PlainFileMatcher PLAIN_FILE_MATCHER = new PlainFileMatcher();
|
||||
|
||||
public static Datasource[] getSupportedServices() {
|
||||
Stream<Datasource> services = Stream.of(getMovieIdentificationServices(), getEpisodeListProviders(), getMusicIdentificationServices()).flatMap(Stream::of);
|
||||
services = Stream.concat(services, Stream.of(XATTR_FILE_MATCHER, PLAIN_FILE_MATCHER));
|
||||
return services.toArray(Datasource[]::new);
|
||||
}
|
||||
|
||||
public static StandardRenameAction[] getSupportedActions() {
|
||||
return new StandardRenameAction[] { StandardRenameAction.MOVE, StandardRenameAction.COPY, StandardRenameAction.KEEPLINK, StandardRenameAction.SYMLINK, StandardRenameAction.HARDLINK };
|
||||
}
|
||||
|
||||
public static Language[] getSupportedLanguages() {
|
||||
return Stream.of(Language.preferredLanguages(), Language.availableLanguages()).flatMap(List::stream).toArray(Language[]::new);
|
||||
}
|
||||
|
||||
public static String[] getSupportedMatchModes() {
|
||||
return new String[] { RenamePanel.MATCH_MODE_OPPORTUNISTIC, RenamePanel.MATCH_MODE_STRICT };
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,30 +1,25 @@
|
||||
package net.filebot.ui.rename;
|
||||
|
||||
import static java.awt.Font.*;
|
||||
import static java.util.Collections.*;
|
||||
import static javax.swing.BorderFactory.*;
|
||||
import static net.filebot.Logging.*;
|
||||
import static net.filebot.Settings.*;
|
||||
import static net.filebot.util.ui.SwingUI.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Font;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.ButtonGroup;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JRadioButton;
|
||||
@ -45,6 +40,7 @@ import net.filebot.WebServices;
|
||||
import net.filebot.format.ExpressionFilter;
|
||||
import net.filebot.format.ExpressionFormat;
|
||||
import net.filebot.format.MediaBindingBean;
|
||||
import net.filebot.mac.MacAppUtilities;
|
||||
import net.filebot.ui.HeaderPanel;
|
||||
import net.filebot.util.FileUtilities.ExtensionFileFilter;
|
||||
import net.filebot.web.Datasource;
|
||||
@ -91,7 +87,7 @@ public class PresetEditor extends JDialog {
|
||||
actionCombo = createRenameActionCombo();
|
||||
providerCombo = createDataProviderCombo();
|
||||
sortOrderCombo = new JComboBox<SortOrder>(SortOrder.values());
|
||||
matchModeCombo = createMatchModeCombo();
|
||||
matchModeCombo = new JComboBox<String>(Preset.getSupportedMatchModes());
|
||||
languageCombo = createLanguageCombo();
|
||||
|
||||
inputPanel = new JPanel(new MigLayout("insets 0, fill"));
|
||||
@ -141,7 +137,7 @@ public class PresetEditor extends JDialog {
|
||||
providerCombo.addItemListener((evt) -> updateComponentStates());
|
||||
updateComponentStates();
|
||||
|
||||
setSize(650, 570);
|
||||
setSize(730, 570);
|
||||
|
||||
// add helpful tooltips
|
||||
filterEditor.setToolTipText(FILE_FILTER_TOOLTIP);
|
||||
@ -237,98 +233,57 @@ public class PresetEditor extends JDialog {
|
||||
}
|
||||
|
||||
private JComboBox<Datasource> createDataProviderCombo() {
|
||||
DefaultComboBoxModel<Datasource> providers = new DefaultComboBoxModel<>();
|
||||
for (Datasource[] seq : new Datasource[][] { WebServices.getEpisodeListProviders(), WebServices.getMovieIdentificationServices(), WebServices.getMusicIdentificationServices() }) {
|
||||
for (Datasource it : seq) {
|
||||
providers.addElement(it);
|
||||
JComboBox<Datasource> combo = new JComboBox<Datasource>(Preset.getSupportedServices());
|
||||
|
||||
ListCellRenderer<? super Datasource> renderer = combo.getRenderer();
|
||||
combo.setRenderer((list, value, index, isSelected, cellHasFocus) -> {
|
||||
JLabel label = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
if (value instanceof Datasource) {
|
||||
Datasource provider = (Datasource) value;
|
||||
label.setText(provider.getName());
|
||||
label.setIcon(provider.getIcon());
|
||||
}
|
||||
}
|
||||
providers.addElement(PlainFileMatcher.getInstance());
|
||||
|
||||
JComboBox<Datasource> combo = new JComboBox<Datasource>(providers);
|
||||
combo.setRenderer(new ListCellRenderer<Object>() {
|
||||
|
||||
private final ListCellRenderer<Object> parent = (ListCellRenderer) combo.getRenderer();
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) parent.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
if (value instanceof Datasource) {
|
||||
Datasource provider = (Datasource) value;
|
||||
label.setText(provider.getName());
|
||||
label.setIcon(provider.getIcon());
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
return label;
|
||||
});
|
||||
|
||||
return combo;
|
||||
}
|
||||
|
||||
private JComboBox<String> createMatchModeCombo() {
|
||||
String[] modes = new String[] { RenamePanel.MATCH_MODE_OPPORTUNISTIC, RenamePanel.MATCH_MODE_STRICT };
|
||||
JComboBox<String> combo = new JComboBox<>(modes);
|
||||
return combo;
|
||||
}
|
||||
|
||||
private JComboBox<Language> createLanguageCombo() {
|
||||
DefaultComboBoxModel<Language> languages = new DefaultComboBoxModel<>();
|
||||
for (Language it : Language.preferredLanguages()) {
|
||||
languages.addElement(it);
|
||||
}
|
||||
for (Language it : Language.availableLanguages()) {
|
||||
languages.addElement(it);
|
||||
}
|
||||
JComboBox<Language> combo = new JComboBox<Language>(Preset.getSupportedLanguages());
|
||||
|
||||
JComboBox<Language> combo = new JComboBox<Language>(languages);
|
||||
combo.setRenderer(new ListCellRenderer<Language>() {
|
||||
ListCellRenderer<? super Language> renderer = combo.getRenderer();
|
||||
combo.setRenderer((list, value, index, isSelected, cellHasFocus) -> {
|
||||
JLabel label = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
private final ListCellRenderer<Language> parent = (ListCellRenderer) combo.getRenderer();
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends Language> list, Language value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) parent.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
if (value instanceof Language) {
|
||||
Language it = value;
|
||||
label.setText(it.getName());
|
||||
label.setIcon(ResourceManager.getFlagIcon(it.getCode()));
|
||||
}
|
||||
|
||||
return label;
|
||||
if (value instanceof Language) {
|
||||
Language it = value;
|
||||
label.setText(it.getName());
|
||||
label.setIcon(ResourceManager.getFlagIcon(it.getCode()));
|
||||
}
|
||||
|
||||
return label;
|
||||
});
|
||||
|
||||
return combo;
|
||||
}
|
||||
|
||||
private JComboBox<RenameAction> createRenameActionCombo() {
|
||||
DefaultComboBoxModel<RenameAction> actions = new DefaultComboBoxModel<>();
|
||||
for (StandardRenameAction it : EnumSet.of(StandardRenameAction.MOVE, StandardRenameAction.COPY, StandardRenameAction.KEEPLINK, StandardRenameAction.SYMLINK, StandardRenameAction.HARDLINK)) {
|
||||
actions.addElement(it);
|
||||
}
|
||||
JComboBox<RenameAction> combo = new JComboBox<RenameAction>(Preset.getSupportedActions());
|
||||
|
||||
JComboBox<RenameAction> combo = new JComboBox<RenameAction>(actions);
|
||||
combo.setRenderer(new ListCellRenderer<RenameAction>() {
|
||||
ListCellRenderer<? super RenameAction> renderer = combo.getRenderer();
|
||||
combo.setRenderer((list, value, index, isSelected, cellHasFocus) -> {
|
||||
JLabel label = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
private final ListCellRenderer<RenameAction> parent = (ListCellRenderer) combo.getRenderer();
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends RenameAction> list, RenameAction value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) parent.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
|
||||
if (value instanceof StandardRenameAction) {
|
||||
StandardRenameAction it = (StandardRenameAction) value;
|
||||
label.setText(it.getDisplayName());
|
||||
label.setIcon(ResourceManager.getIcon("rename.action." + it.toString().toLowerCase()));
|
||||
}
|
||||
|
||||
return label;
|
||||
if (value instanceof StandardRenameAction) {
|
||||
StandardRenameAction it = (StandardRenameAction) value;
|
||||
label.setText(it.getDisplayName());
|
||||
label.setIcon(ResourceManager.getIcon("rename.action." + it.toString().toLowerCase()));
|
||||
}
|
||||
|
||||
return label;
|
||||
});
|
||||
|
||||
return combo;
|
||||
@ -338,104 +293,90 @@ public class PresetEditor extends JDialog {
|
||||
return result;
|
||||
}
|
||||
|
||||
private final Action selectInputFolder = new AbstractAction("Select Input Folder", ResourceManager.getIcon("action.load")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
File f = UserFiles.showOpenDialogSelectFolder(null, "Select Input Folder", evt);
|
||||
if (f != null) {
|
||||
pathInput.setText(f.getAbsolutePath());
|
||||
}
|
||||
private final Action selectInputFolder = newAction("Select Input Folder", ResourceManager.getIcon("action.load"), evt -> {
|
||||
File f = UserFiles.showOpenDialogSelectFolder(null, "Select Input Folder", evt);
|
||||
if (f != null) {
|
||||
pathInput.setText(f.getAbsolutePath());
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
private final Action editFormatExpression = new AbstractAction("Open Format Editor", ResourceManager.getIcon("action.format")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
FormatDialog.Mode mode = FormatDialog.Mode.getMode((Datasource) providerCombo.getSelectedItem());
|
||||
MediaBindingBean lockOnBinding = null;
|
||||
if (mode == FormatDialog.Mode.File) {
|
||||
List<File> files = UserFiles.showLoadDialogSelectFiles(false, false, null, new ExtensionFileFilter(ExtensionFileFilter.WILDCARD), "Select Sample File", evt);
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
lockOnBinding = new MediaBindingBean(files.get(0), files.get(0));
|
||||
private final Action editFormatExpression = newAction("Open Format Editor", ResourceManager.getIcon("action.format"), evt -> {
|
||||
FormatDialog.Mode mode = FormatDialog.Mode.getMode((Datasource) providerCombo.getSelectedItem());
|
||||
MediaBindingBean lockOnBinding = null;
|
||||
if (mode == FormatDialog.Mode.File) {
|
||||
List<File> files = UserFiles.showLoadDialogSelectFiles(false, false, null, new ExtensionFileFilter(ExtensionFileFilter.WILDCARD), "Select Sample File", evt);
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FormatDialog dialog = new FormatDialog(getWindow(evt.getSource()), mode, lockOnBinding);
|
||||
dialog.setFormatCode(formatEditor.getText());
|
||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.submit()) {
|
||||
formatEditor.setText(dialog.getFormat().getExpression());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final Action listFiles = new AbstractAction("List Files", ResourceManager.getIcon("action.search")) {
|
||||
|
||||
private JMenuItem createListItem(ActionEvent evt, File f) {
|
||||
JMenuItem m = new JMenuItem(f.getPath());
|
||||
m.addActionListener((e) -> {
|
||||
BindingDialog dialog = new BindingDialog(getWindow(evt.getSource()), "File Bindings", FormatDialog.Mode.File.getFormat(), false);
|
||||
dialog.setLocation(getOffsetLocation(getWindow(evt.getSource())));
|
||||
dialog.setInfoObject(f);
|
||||
dialog.setMediaFile(f);
|
||||
dialog.setVisible(true);
|
||||
});
|
||||
return m;
|
||||
lockOnBinding = new MediaBindingBean(files.get(0), files.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
try {
|
||||
withWaitCursor(evt.getSource(), () -> {
|
||||
List<File> selectInputFiles = getPreset().selectInputFiles(evt);
|
||||
FormatDialog dialog = new FormatDialog(getWindow(evt.getSource()), mode, lockOnBinding);
|
||||
dialog.setFormatCode(formatEditor.getText());
|
||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||
dialog.setVisible(true);
|
||||
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
if (selectInputFiles == null || selectInputFiles.isEmpty()) {
|
||||
popup.add("No files selected").setEnabled(false);
|
||||
} else {
|
||||
for (File file : selectInputFiles) {
|
||||
popup.add(createListItem(evt, file));
|
||||
}
|
||||
}
|
||||
|
||||
JComponent source = (JComponent) evt.getSource();
|
||||
popup.show(source, -3, source.getHeight() + 4);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Invalid preset settings: " + e.getMessage(), e);
|
||||
}
|
||||
if (dialog.submit()) {
|
||||
formatEditor.setText(dialog.getFormat().getExpression());
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
private final Action ok = new AbstractAction("Save Preset", ResourceManager.getIcon("dialog.continue")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
private final Action listFiles = newAction("List Files", ResourceManager.getIcon("action.search"), evt -> {
|
||||
withWaitCursor(evt.getSource(), () -> {
|
||||
try {
|
||||
Preset preset = getPreset();
|
||||
if (preset != null) {
|
||||
result = Result.SET;
|
||||
setVisible(false);
|
||||
if (preset.getInputFolder() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMacSandbox()) {
|
||||
if (!MacAppUtilities.askUnlockFolders(getWindow(evt.getSource()), singleton(preset.getInputFolder()))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<File> files = preset.selectFiles();
|
||||
|
||||
// display selected files as popup with easy access to more binding info
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
if (files.size() > 0) {
|
||||
for (File f : files) {
|
||||
popup.add(newAction(f.getPath(), e -> {
|
||||
BindingDialog dialog = new BindingDialog(getWindow(evt.getSource()), "File Bindings", FormatDialog.Mode.File.getFormat(), false);
|
||||
dialog.setLocation(getOffsetLocation(getWindow(evt.getSource())));
|
||||
dialog.setInfoObject(f);
|
||||
dialog.setMediaFile(f);
|
||||
dialog.setVisible(true);
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
popup.add("No files selected").setEnabled(false);
|
||||
}
|
||||
|
||||
JComponent source = (JComponent) evt.getSource();
|
||||
popup.show(source, -3, source.getHeight() + 4);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Invalid preset settings: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
private final Action delete = new AbstractAction("Delete Preset", ResourceManager.getIcon("dialog.cancel")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
result = Result.DELETE;
|
||||
setVisible(false);
|
||||
private final Action ok = newAction("Save Preset", ResourceManager.getIcon("dialog.continue"), evt -> {
|
||||
try {
|
||||
Preset preset = getPreset();
|
||||
if (preset != null) {
|
||||
result = Result.SET;
|
||||
setVisible(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Invalid preset settings: " + e.getMessage(), e);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
private final Action delete = newAction("Delete Preset", ResourceManager.getIcon("dialog.cancel"), evt -> {
|
||||
result = Result.DELETE;
|
||||
setVisible(false);
|
||||
});
|
||||
|
||||
private static final String FILE_FILTER_TOOLTIP = "<html>File Selector Expression<br><hr noshade>e.g.<br>• fn =~ /alias/<br>• ext =~ /mp4/<br>• minutes > 100<br>• age < 7<br>• file.isEpisode()<br>• …<br></html>";
|
||||
|
||||
|
@ -65,63 +65,59 @@ class RenameAction extends AbstractAction {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Window window = getWindow(evt.getSource());
|
||||
withWaitCursor(window, () -> {
|
||||
Map<File, File> renameMap = validate(model.getRenameMap(), window);
|
||||
Window window = getWindow(evt.getSource());
|
||||
withWaitCursor(window, () -> {
|
||||
Map<File, File> renameMap = validate(model.getRenameMap(), window);
|
||||
|
||||
if (renameMap.isEmpty()) {
|
||||
return;
|
||||
if (renameMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Match<Object, File>> matches = new ArrayList<Match<Object, File>>(model.matches());
|
||||
StandardRenameAction action = (StandardRenameAction) getValue(RENAME_ACTION);
|
||||
|
||||
// start processing
|
||||
Map<File, File> renameLog = new LinkedHashMap<File, File>();
|
||||
|
||||
try {
|
||||
if (useNativeShell() && NativeRenameAction.isSupported(action)) {
|
||||
// call on EDT
|
||||
NativeRenameWorker worker = new NativeRenameWorker(renameMap, renameLog, NativeRenameAction.valueOf(action.name()));
|
||||
worker.call(null, null, null);
|
||||
} else {
|
||||
// call and wait
|
||||
StandardRenameWorker worker = new StandardRenameWorker(renameMap, renameLog, action);
|
||||
String message = String.format("%sing %d %s. This may take a while.", action.getDisplayName(), renameMap.size(), renameMap.size() == 1 ? "file" : "files");
|
||||
ProgressMonitor.runTask(action.getDisplayName(), message, worker).get();
|
||||
}
|
||||
} catch (CancellationException e) {
|
||||
debug.finest(e::toString);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, String.format("%s: %s", getRootCause(e).getClass().getSimpleName(), getRootCauseMessage(e)), e);
|
||||
}
|
||||
|
||||
List<Match<Object, File>> matches = new ArrayList<Match<Object, File>>(model.matches());
|
||||
StandardRenameAction action = (StandardRenameAction) getValue(RENAME_ACTION);
|
||||
// abort if nothing happened
|
||||
if (renameLog.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start processing
|
||||
Map<File, File> renameLog = new LinkedHashMap<File, File>();
|
||||
log.info(String.format("%d files renamed.", renameLog.size()));
|
||||
|
||||
try {
|
||||
if (useNativeShell() && NativeRenameAction.isSupported(action)) {
|
||||
// call on EDT
|
||||
NativeRenameWorker worker = new NativeRenameWorker(renameMap, renameLog, NativeRenameAction.valueOf(action.name()));
|
||||
worker.call(null, null, null);
|
||||
} else {
|
||||
// call and wait
|
||||
StandardRenameWorker worker = new StandardRenameWorker(renameMap, renameLog, action);
|
||||
String message = String.format("%sing %d %s. This may take a while.", action.getDisplayName(), renameMap.size(), renameMap.size() == 1 ? "file" : "files");
|
||||
ProgressMonitor.runTask(action.getDisplayName(), message, worker).get();
|
||||
}
|
||||
} catch (CancellationException e) {
|
||||
debug.finest(e::toString);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, String.format("%s: %s", getRootCause(e).getClass().getSimpleName(), getRootCauseMessage(e)), e);
|
||||
}
|
||||
|
||||
// abort if nothing happened
|
||||
if (renameLog.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(String.format("%d files renamed.", renameLog.size()));
|
||||
|
||||
// remove renamed matches
|
||||
renameLog.forEach((from, to) -> {
|
||||
model.matches().remove(model.files().indexOf(from));
|
||||
});
|
||||
|
||||
HistorySpooler.getInstance().append(renameLog.entrySet());
|
||||
|
||||
// store xattr
|
||||
storeMetaInfo(renameMap, matches);
|
||||
|
||||
// delete empty folders
|
||||
if (action == StandardRenameAction.MOVE) {
|
||||
deleteEmptyFolders(renameLog);
|
||||
}
|
||||
// remove renamed matches
|
||||
renameLog.forEach((from, to) -> {
|
||||
model.matches().remove(model.files().indexOf(from));
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
log.log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
|
||||
HistorySpooler.getInstance().append(renameLog.entrySet());
|
||||
|
||||
// store xattr
|
||||
storeMetaInfo(renameMap, matches);
|
||||
|
||||
// delete empty folders
|
||||
if (action == StandardRenameAction.MOVE) {
|
||||
deleteEmptyFolders(renameLog);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeMetaInfo(Map<File, File> renameMap, List<Match<Object, File>> matches) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.filebot.ui.rename;
|
||||
|
||||
import static java.awt.event.KeyEvent.*;
|
||||
import static java.util.Collections.*;
|
||||
import static javax.swing.JOptionPane.*;
|
||||
import static javax.swing.KeyStroke.*;
|
||||
import static javax.swing.SwingUtilities.*;
|
||||
@ -15,7 +16,6 @@ import static net.filebot.util.ui.SwingUI.*;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Window;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
@ -63,6 +63,7 @@ import net.filebot.Settings;
|
||||
import net.filebot.StandardRenameAction;
|
||||
import net.filebot.UserFiles;
|
||||
import net.filebot.WebServices;
|
||||
import net.filebot.format.ExpressionFormat;
|
||||
import net.filebot.format.MediaBindingBean;
|
||||
import net.filebot.mac.MacAppUtilities;
|
||||
import net.filebot.media.MetaAttributes;
|
||||
@ -314,15 +315,13 @@ public class RenamePanel extends JComponent {
|
||||
private void installKeyStrokeActions() {
|
||||
// manual force name via F2
|
||||
installAction(this, WHEN_IN_FOCUSED_WINDOW, getKeyStroke(VK_F2, 0), newAction("Force Name", evt -> {
|
||||
try {
|
||||
withWaitCursor(evt.getSource(), () -> {
|
||||
if (namesList.getModel().isEmpty()) {
|
||||
withWaitCursor(evt.getSource(), () -> {
|
||||
// match to xattr metadata object or the file itself
|
||||
Map<File, Object> xattr = WebServices.XattrMetaData.match(renameModel.files(), false);
|
||||
// match to xattr metadata object or the file itself
|
||||
Map<File, Object> xattr = WebServices.XattrMetaData.match(renameModel.files(), false);
|
||||
|
||||
renameModel.clear();
|
||||
renameModel.addAll(xattr.values(), xattr.keySet());
|
||||
});
|
||||
renameModel.clear();
|
||||
renameModel.addAll(xattr.values(), xattr.keySet());
|
||||
} else {
|
||||
int index = namesList.getListComponent().getSelectedIndex();
|
||||
if (index >= 0) {
|
||||
@ -335,9 +334,7 @@ public class RenamePanel extends JComponent {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
debug.log(Level.WARNING, e::toString);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
// map 1..9 number keys to presets
|
||||
@ -359,19 +356,15 @@ public class RenamePanel extends JComponent {
|
||||
|
||||
// copy debug information (paths and objects)
|
||||
installAction(this, WHEN_IN_FOCUSED_WINDOW, getKeyStroke(VK_F7, 0), newAction("Copy Debug Information", evt -> {
|
||||
try {
|
||||
withWaitCursor(evt.getSource(), () -> {
|
||||
String text = getDebugInfo();
|
||||
if (text.length() > 0) {
|
||||
copyToClipboard(text);
|
||||
log.info("Match model has been copied to clipboard");
|
||||
} else {
|
||||
log.warning("Match model is empty");
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
debug.log(Level.WARNING, e, e::getMessage);
|
||||
}
|
||||
withWaitCursor(evt.getSource(), () -> {
|
||||
String text = getDebugInfo();
|
||||
if (text.length() > 0) {
|
||||
copyToClipboard(text);
|
||||
log.info("Match model has been copied to clipboard");
|
||||
} else {
|
||||
log.warning("Match model is empty");
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
@ -581,40 +574,36 @@ public class RenamePanel extends JComponent {
|
||||
}
|
||||
|
||||
private void showFormatEditor(MediaBindingBean binding) {
|
||||
try {
|
||||
withWaitCursor(this, () -> {
|
||||
FormatDialog dialog = new FormatDialog(getWindowAncestor(RenamePanel.this), getFormatEditorMode(binding), binding);
|
||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||
dialog.setVisible(true);
|
||||
withWaitCursor(this, () -> {
|
||||
FormatDialog dialog = new FormatDialog(getWindowAncestor(RenamePanel.this), getFormatEditorMode(binding), binding);
|
||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.submit()) {
|
||||
switch (dialog.getMode()) {
|
||||
case Episode:
|
||||
renameModel.useFormatter(Episode.class, new ExpressionFormatter(dialog.getFormat().getExpression(), EpisodeFormat.SeasonEpisode, Episode.class));
|
||||
persistentEpisodeFormat.setValue(dialog.getFormat().getExpression());
|
||||
break;
|
||||
case Movie:
|
||||
renameModel.useFormatter(Movie.class, new ExpressionFormatter(dialog.getFormat().getExpression(), MovieFormat.NameYear, Movie.class));
|
||||
persistentMovieFormat.setValue(dialog.getFormat().getExpression());
|
||||
break;
|
||||
case Music:
|
||||
renameModel.useFormatter(AudioTrack.class, new ExpressionFormatter(dialog.getFormat().getExpression(), new AudioTrackFormat(), AudioTrack.class));
|
||||
persistentMusicFormat.setValue(dialog.getFormat().getExpression());
|
||||
break;
|
||||
case File:
|
||||
renameModel.useFormatter(File.class, new ExpressionFormatter(dialog.getFormat().getExpression(), new FileNameFormat(), File.class));
|
||||
persistentFileFormat.setValue(dialog.getFormat().getExpression());
|
||||
break;
|
||||
}
|
||||
|
||||
if (binding == null) {
|
||||
persistentLastFormatState.setValue(dialog.getMode().name());
|
||||
}
|
||||
if (dialog.submit()) {
|
||||
switch (dialog.getMode()) {
|
||||
case Episode:
|
||||
renameModel.useFormatter(Episode.class, new ExpressionFormatter(dialog.getFormat().getExpression(), EpisodeFormat.SeasonEpisode, Episode.class));
|
||||
persistentEpisodeFormat.setValue(dialog.getFormat().getExpression());
|
||||
break;
|
||||
case Movie:
|
||||
renameModel.useFormatter(Movie.class, new ExpressionFormatter(dialog.getFormat().getExpression(), MovieFormat.NameYear, Movie.class));
|
||||
persistentMovieFormat.setValue(dialog.getFormat().getExpression());
|
||||
break;
|
||||
case Music:
|
||||
renameModel.useFormatter(AudioTrack.class, new ExpressionFormatter(dialog.getFormat().getExpression(), new AudioTrackFormat(), AudioTrack.class));
|
||||
persistentMusicFormat.setValue(dialog.getFormat().getExpression());
|
||||
break;
|
||||
case File:
|
||||
renameModel.useFormatter(File.class, new ExpressionFormatter(dialog.getFormat().getExpression(), new FileNameFormat(), File.class));
|
||||
persistentFileFormat.setValue(dialog.getFormat().getExpression());
|
||||
break;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
debug.log(Level.WARNING, e::getMessage);
|
||||
}
|
||||
|
||||
if (binding == null) {
|
||||
persistentLastFormatState.setValue(dialog.getMode().name());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getDebugInfo() throws Exception {
|
||||
@ -705,20 +694,33 @@ public class RenamePanel extends JComponent {
|
||||
|
||||
@Override
|
||||
public List<File> getFiles(ActionEvent evt) {
|
||||
List<File> selection = preset.selectInputFiles(evt);
|
||||
File inputFolder = preset.getInputFolder();
|
||||
|
||||
if (selection != null) {
|
||||
renameModel.clear();
|
||||
renameModel.files().addAll(selection);
|
||||
} else {
|
||||
selection = new ArrayList<File>(super.getFiles(evt));
|
||||
if (inputFolder == null) {
|
||||
return super.getFiles(evt); // default behaviour
|
||||
}
|
||||
|
||||
if (selection.isEmpty()) {
|
||||
throw new IllegalStateException("No files selected.");
|
||||
if (isMacSandbox()) {
|
||||
if (!MacAppUtilities.askUnlockFolders(getWindow(RenamePanel.this), singleton(inputFolder))) {
|
||||
return emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
return selection;
|
||||
try {
|
||||
List<File> selection = onSecondaryLoop(preset::selectFiles); // run potentially long-running operations on secondary EDT
|
||||
|
||||
if (selection.size() > 0) {
|
||||
renameModel.clear();
|
||||
renameModel.files().addAll(selection);
|
||||
return selection;
|
||||
}
|
||||
|
||||
log.info("No files have been selected.");
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, e, e::toString);
|
||||
}
|
||||
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -738,40 +740,38 @@ public class RenamePanel extends JComponent {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
Window window = getWindow(RenamePanel.this);
|
||||
window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
SwingWorker<ExpressionFormatter, Void> worker = newSwingWorker(() -> {
|
||||
ExpressionFormat format = preset.getFormat();
|
||||
|
||||
// Swing Bug Workaround: heavy-weight popup window blocks parent window from being updated (i.e. set wait cursor) unless we wait a little bit until the popup window is destroyed
|
||||
invokeLater(200, () -> {
|
||||
try {
|
||||
if (preset.getFormat() != null) {
|
||||
switch (FormatDialog.Mode.getMode(preset.getDatasource())) {
|
||||
case Episode:
|
||||
renameModel.useFormatter(Episode.class, new ExpressionFormatter(preset.getFormat().getExpression(), EpisodeFormat.SeasonEpisode, Episode.class));
|
||||
break;
|
||||
case Movie:
|
||||
renameModel.useFormatter(Movie.class, new ExpressionFormatter(preset.getFormat().getExpression(), MovieFormat.NameYear, Movie.class));
|
||||
break;
|
||||
case Music:
|
||||
renameModel.useFormatter(AudioTrack.class, new ExpressionFormatter(preset.getFormat().getExpression(), new AudioTrackFormat(), AudioTrack.class));
|
||||
break;
|
||||
case File:
|
||||
renameModel.useFormatter(File.class, new ExpressionFormatter(preset.getFormat().getExpression(), new FileNameFormat(), File.class));
|
||||
break;
|
||||
}
|
||||
if (format != null && preset.getDatasource() != null) {
|
||||
switch (Mode.getMode(preset.getDatasource())) {
|
||||
case Episode:
|
||||
return new ExpressionFormatter(format, EpisodeFormat.SeasonEpisode, Episode.class);
|
||||
case Movie:
|
||||
return new ExpressionFormatter(format, MovieFormat.NameYear, Movie.class);
|
||||
case Music:
|
||||
return new ExpressionFormatter(format, new AudioTrackFormat(), AudioTrack.class);
|
||||
case File:
|
||||
return new ExpressionFormatter(format, new FileNameFormat(), File.class);
|
||||
}
|
||||
|
||||
if (preset.getRenameAction() != null) {
|
||||
new SetRenameAction(preset.getRenameAction()).actionPerformed(evt);
|
||||
}
|
||||
|
||||
super.actionPerformed(evt);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.INFO, e, e::getMessage);
|
||||
} finally {
|
||||
window.setCursor(Cursor.getDefaultCursor());
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}, formatter -> {
|
||||
if (formatter != null) {
|
||||
renameModel.useFormatter(formatter.getTargetClass(), formatter);
|
||||
}
|
||||
|
||||
if (preset.getRenameAction() != null) {
|
||||
new SetRenameAction(preset.getRenameAction()).actionPerformed(evt);
|
||||
}
|
||||
|
||||
super.actionPerformed(evt);
|
||||
}, () -> namesList.firePropertyChange(LOADING_PROPERTY, true, false));
|
||||
|
||||
// auto-match in progress
|
||||
namesList.firePropertyChange(LOADING_PROPERTY, false, true);
|
||||
worker.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@ -859,11 +859,15 @@ public class RenamePanel extends JComponent {
|
||||
// clear names list
|
||||
renameModel.values().clear();
|
||||
|
||||
final List<File> remainingFiles = new LinkedList<File>(getFiles(evt));
|
||||
final boolean strict = isStrict(evt);
|
||||
final SortOrder order = getSortOrder(evt);
|
||||
final Locale locale = getLocale(evt);
|
||||
final boolean autodetection = isAutoDetectionEnabled(evt);
|
||||
List<File> remainingFiles = new LinkedList<File>(getFiles(evt));
|
||||
if (remainingFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean strict = isStrict(evt);
|
||||
SortOrder order = getSortOrder(evt);
|
||||
Locale locale = getLocale(evt);
|
||||
boolean autodetection = isAutoDetectionEnabled(evt);
|
||||
|
||||
if (isMacSandbox()) {
|
||||
if (!MacAppUtilities.askUnlockFolders(getWindow(RenamePanel.this), remainingFiles)) {
|
||||
|
38
source/net/filebot/ui/rename/XattrFileMatcher.java
Normal file
38
source/net/filebot/ui/rename/XattrFileMatcher.java
Normal file
@ -0,0 +1,38 @@
|
||||
package net.filebot.ui.rename;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.filebot.media.XattrMetaInfoProvider;
|
||||
import net.filebot.similarity.Match;
|
||||
import net.filebot.web.SortOrder;
|
||||
|
||||
public class XattrFileMatcher extends XattrMetaInfoProvider implements AutoCompleteMatcher {
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "xattr";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Extended Attributes";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Match<File, ?>> match(Collection<File> files, boolean strict, SortOrder order, Locale locale, boolean autodetection, Component parent) throws Exception {
|
||||
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||
|
||||
// use strict mode to exclude files that are not xattr tagged
|
||||
match(files, true).forEach((k, v) -> {
|
||||
matches.add(new Match<File, Object>(k, v));
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
}
|
@ -99,10 +99,10 @@ class MovieEditor implements TableCellEditor {
|
||||
|
||||
newSwingWorker(() -> {
|
||||
return runSearch(mapping, table);
|
||||
}, (options) -> {
|
||||
}, options -> {
|
||||
runSelect(options, mapping, table);
|
||||
reset(null, table);
|
||||
}, (error) -> {
|
||||
}, error -> {
|
||||
reset(error, table);
|
||||
}).execute();
|
||||
|
||||
|
@ -13,6 +13,7 @@ import java.awt.Frame;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.SecondaryLoop;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
@ -27,6 +28,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
@ -311,19 +313,17 @@ public final class SwingUI {
|
||||
return timer;
|
||||
}
|
||||
|
||||
public static void withWaitCursor(Object source, BackgroundRunnable runnable) throws Exception {
|
||||
Window window = getWindow(source);
|
||||
|
||||
if (window == null) {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
public static void withWaitCursor(Object source, BackgroundRunnable runnable) {
|
||||
// window ancestor may be null
|
||||
Optional<Window> window = Optional.ofNullable(getWindow(source));
|
||||
|
||||
try {
|
||||
window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
window.ifPresent(w -> w.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)));
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
debug.log(Level.SEVERE, e, e::toString);
|
||||
} finally {
|
||||
window.setCursor(Cursor.getDefaultCursor());
|
||||
window.ifPresent(w -> w.setCursor(Cursor.getDefaultCursor()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,12 +368,37 @@ public final class SwingUI {
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T onSecondaryLoop(BackgroundSupplier<T> supplier) throws ExecutionException, InterruptedException {
|
||||
// run spawn new EDT and block current EDT
|
||||
SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
|
||||
|
||||
SwingWorker<T, Void> worker = newSwingWorker(supplier, null, null, () -> secondaryLoop.exit());
|
||||
worker.execute();
|
||||
|
||||
// wait for worker to finish without blocking the EDT
|
||||
secondaryLoop.enter();
|
||||
|
||||
return worker.get();
|
||||
}
|
||||
|
||||
public static SwingWorker<Void, Void> newSwingWorker(BackgroundRunnable doInBackground) {
|
||||
return new SwingRunnable(doInBackground);
|
||||
}
|
||||
|
||||
public static <T> SwingWorker<T, Void> newSwingWorker(BackgroundSupplier<T> doInBackground, Consumer<T> done) {
|
||||
return new SwingLambda<T, Void>(doInBackground, done, null, null);
|
||||
}
|
||||
|
||||
public static <T> SwingWorker<T, Void> newSwingWorker(BackgroundSupplier<T> doInBackground, Consumer<T> done, Consumer<Exception> error) {
|
||||
return new SwingLambda<T, Void>(doInBackground, done, error);
|
||||
return new SwingLambda<T, Void>(doInBackground, done, error, null);
|
||||
}
|
||||
|
||||
public static <T> SwingWorker<T, Void> newSwingWorker(BackgroundSupplier<T> doInBackground, Consumer<T> done, Runnable close) {
|
||||
return new SwingLambda<T, Void>(doInBackground, done, null, close);
|
||||
}
|
||||
|
||||
public static <T> SwingWorker<T, Void> newSwingWorker(BackgroundSupplier<T> doInBackground, Consumer<T> done, Consumer<Exception> error, Runnable close) {
|
||||
return new SwingLambda<T, Void>(doInBackground, done, error, close);
|
||||
}
|
||||
|
||||
private static class SwingRunnable extends SwingWorker<Void, Void> {
|
||||
@ -404,13 +429,17 @@ public final class SwingUI {
|
||||
private static class SwingLambda<T, V> extends SwingWorker<T, V> {
|
||||
|
||||
private BackgroundSupplier<T> doInBackground;
|
||||
private Consumer<T> done;
|
||||
private Consumer<Exception> error;
|
||||
private Optional<Consumer<T>> done;
|
||||
private Optional<Consumer<Exception>> error;
|
||||
|
||||
public SwingLambda(BackgroundSupplier<T> doInBackground, Consumer<T> done, Consumer<Exception> error) {
|
||||
private Optional<Runnable> close;
|
||||
|
||||
public SwingLambda(BackgroundSupplier<T> doInBackground, Consumer<T> done, Consumer<Exception> error, Runnable close) {
|
||||
this.doInBackground = doInBackground;
|
||||
this.done = done;
|
||||
this.error = error;
|
||||
|
||||
this.done = Optional.ofNullable(done);
|
||||
this.error = Optional.ofNullable(error);
|
||||
this.close = Optional.ofNullable(close);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -421,11 +450,18 @@ public final class SwingUI {
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
done.accept(get());
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
error.accept(e);
|
||||
T value = get();
|
||||
done.ifPresent(c -> c.accept(value));
|
||||
} catch (Exception e) {
|
||||
error.orElse(this::printException).accept(e); // print stacktrace by default
|
||||
} finally {
|
||||
close.ifPresent(Runnable::run);
|
||||
}
|
||||
}
|
||||
|
||||
private void printException(Exception e) {
|
||||
debug.log(Level.SEVERE, e, e::toString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user