Refactor Presets

This commit is contained in:
Reinhard Pointner 2016-10-24 02:19:41 +08:00
parent e921e50c3c
commit c2fc4c2913
16 changed files with 476 additions and 466 deletions

View File

@ -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);
}

View File

@ -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) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -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()));

View File

@ -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

View File

@ -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();
});

View File

@ -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);
}
});
}
}

View File

@ -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

View File

@ -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 };
}
}

View File

@ -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 &gt; 100<br>• age &lt; 7<br>• file.isEpisode()<br>• …<br></html>";

View File

@ -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) {

View File

@ -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)) {

View 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;
}
}

View File

@ -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();

View File

@ -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);
}
}
/**