mirror of
https://github.com/mitb-archive/filebot
synced 2024-08-13 17:03:45 -04:00
* [Windows] allow renaming of files where just the upper/lower case is different
This commit is contained in:
parent
c75b376140
commit
3ed58bda08
@ -303,7 +303,7 @@ class RenameAction extends AbstractAction {
|
|||||||
// rename file, throw exception on failure
|
// rename file, throw exception on failure
|
||||||
File source = mapping.getKey();
|
File source = mapping.getKey();
|
||||||
File destination = resolveDestination(mapping.getKey(), mapping.getValue(), false);
|
File destination = resolveDestination(mapping.getKey(), mapping.getValue(), false);
|
||||||
if (!source.equals(destination)) {
|
if (!source.getAbsolutePath().equals(destination.getAbsolutePath())) {
|
||||||
action.rename(source, destination);
|
action.rename(source, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
|
|
||||||
package net.sourceforge.filebot.ui.rename;
|
package net.sourceforge.filebot.ui.rename;
|
||||||
|
|
||||||
|
|
||||||
import static net.sourceforge.filebot.similarity.EpisodeMetrics.*;
|
import static net.sourceforge.filebot.similarity.EpisodeMetrics.*;
|
||||||
import static net.sourceforge.tuned.FileUtilities.*;
|
import static net.sourceforge.tuned.FileUtilities.*;
|
||||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||||
@ -34,44 +32,41 @@ import net.sourceforge.tuned.FileUtilities;
|
|||||||
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
|
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
|
||||||
import net.sourceforge.tuned.ui.GradientStyle;
|
import net.sourceforge.tuned.ui.GradientStyle;
|
||||||
|
|
||||||
|
|
||||||
class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
||||||
|
|
||||||
private RenameModel renameModel;
|
private RenameModel renameModel;
|
||||||
|
|
||||||
private TypeRenderer typeRenderer = new TypeRenderer();
|
private TypeRenderer typeRenderer = new TypeRenderer();
|
||||||
|
|
||||||
private Color noMatchGradientBeginColor = new Color(0xB7B7B7);
|
private Color noMatchGradientBeginColor = new Color(0xB7B7B7);
|
||||||
private Color noMatchGradientEndColor = new Color(0x9A9A9A);
|
private Color noMatchGradientEndColor = new Color(0x9A9A9A);
|
||||||
|
|
||||||
private Color warningGradientBeginColor = Color.RED;
|
private Color warningGradientBeginColor = Color.RED;
|
||||||
private Color warningGradientEndColor = new Color(0xDC143C);
|
private Color warningGradientEndColor = new Color(0xDC143C);
|
||||||
|
|
||||||
private Color pathRainbowBeginColor = new Color(0xCC3300);
|
private Color pathRainbowBeginColor = new Color(0xCC3300);
|
||||||
private Color pathRainbowEndColor = new Color(0x008080);
|
private Color pathRainbowEndColor = new Color(0x008080);
|
||||||
|
|
||||||
|
|
||||||
public RenameListCellRenderer(RenameModel renameModel) {
|
public RenameListCellRenderer(RenameModel renameModel) {
|
||||||
super(new Insets(4, 7, 4, 7));
|
super(new Insets(4, 7, 4, 7));
|
||||||
|
|
||||||
this.renameModel = renameModel;
|
this.renameModel = renameModel;
|
||||||
setHighlightingEnabled(false);
|
setHighlightingEnabled(false);
|
||||||
|
|
||||||
setLayout(new MigLayout("insets 0, fill", "align left", "align center"));
|
setLayout(new MigLayout("insets 0, fill", "align left", "align center"));
|
||||||
this.add(typeRenderer, "gap rel:push, hidemode 3");
|
this.add(typeRenderer, "gap rel:push, hidemode 3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
public void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||||
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||||
|
|
||||||
// reset decoration / highlighting
|
// reset decoration / highlighting
|
||||||
setOpaque(false);
|
setOpaque(false);
|
||||||
setIcon(null);
|
setIcon(null);
|
||||||
typeRenderer.setVisible(false);
|
typeRenderer.setVisible(false);
|
||||||
typeRenderer.setAlpha(1.0f);
|
typeRenderer.setAlpha(1.0f);
|
||||||
|
|
||||||
// render unmatched values differently
|
// render unmatched values differently
|
||||||
if (!renameModel.hasComplement(index)) {
|
if (!renameModel.hasComplement(index)) {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
@ -81,16 +76,16 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||||||
typeRenderer.setAlpha(0.5f);
|
typeRenderer.setAlpha(0.5f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renameModel.preserveExtension() && index < renameModel.size() && renameModel.getMatch(index).getCandidate() != null) {
|
if (renameModel.preserveExtension() && index < renameModel.size() && renameModel.getMatch(index).getCandidate() != null) {
|
||||||
typeRenderer.setText(getType(renameModel.getMatch(index).getCandidate()));
|
typeRenderer.setText(getType(renameModel.getMatch(index).getCandidate()));
|
||||||
typeRenderer.setVisible(true);
|
typeRenderer.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value instanceof File) {
|
if (value instanceof File) {
|
||||||
// display file extension
|
// display file extension
|
||||||
File file = (File) value;
|
File file = (File) value;
|
||||||
|
|
||||||
if (renameModel.preserveExtension()) {
|
if (renameModel.preserveExtension()) {
|
||||||
setText(FileUtilities.getName(file));
|
setText(FileUtilities.getName(file));
|
||||||
} else {
|
} else {
|
||||||
@ -100,14 +95,14 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||||||
// display progress icon
|
// display progress icon
|
||||||
FormattedFuture formattedFuture = (FormattedFuture) value;
|
FormattedFuture formattedFuture = (FormattedFuture) value;
|
||||||
float matchProbablity = renameModel.hasComplement(index) ? getMatchProbablity(formattedFuture.getMatch()) : 1;
|
float matchProbablity = renameModel.hasComplement(index) ? getMatchProbablity(formattedFuture.getMatch()) : 1;
|
||||||
|
|
||||||
if (formattedFuture.isDone() && !formattedFuture.isCancelled()) {
|
if (formattedFuture.isDone() && !formattedFuture.isCancelled()) {
|
||||||
if (!renameModel.preserveExtension() && renameModel.hasComplement(index)) {
|
if (!renameModel.preserveExtension() && renameModel.hasComplement(index)) {
|
||||||
// absolute path mode
|
// absolute path mode
|
||||||
File file = renameModel.getMatch(index).getCandidate();
|
File file = renameModel.getMatch(index).getCandidate();
|
||||||
File path = resolveAbsolutePath(file.getParentFile(), formattedFuture.toString(), null);
|
File path = resolveAbsolutePath(file.getParentFile(), formattedFuture.toString(), null);
|
||||||
setText(isSelected || matchProbablity < 1 ? formatPath(path) : colorizePath(path, true));
|
setText(isSelected || matchProbablity < 1 ? formatPath(path) : colorizePath(path, true));
|
||||||
|
|
||||||
String ext = getExtension(path);
|
String ext = getExtension(path);
|
||||||
typeRenderer.setText(ext != null ? ext.toLowerCase() : "MISSING EXTENSION");
|
typeRenderer.setText(ext != null ? ext.toLowerCase() : "MISSING EXTENSION");
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
@ -122,25 +117,25 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||||||
} else {
|
} else {
|
||||||
setText(formattedFuture.preview()); // default text
|
setText(formattedFuture.preview()); // default text
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (formattedFuture.getState()) {
|
switch (formattedFuture.getState()) {
|
||||||
case PENDING:
|
case PENDING:
|
||||||
setIcon(ResourceManager.getIcon("worker.pending"));
|
setIcon(ResourceManager.getIcon("worker.pending"));
|
||||||
break;
|
break;
|
||||||
case STARTED:
|
case STARTED:
|
||||||
setIcon(ResourceManager.getIcon("worker.started"));
|
setIcon(ResourceManager.getIcon("worker.started"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renameModel.hasComplement(index)) {
|
if (renameModel.hasComplement(index)) {
|
||||||
setOpaque(true); // enable paint background
|
setOpaque(true); // enable paint background
|
||||||
setBackground(derive(warningGradientBeginColor, (1 - matchProbablity) * 0.5f)); // alpha indicates match probability
|
setBackground(derive(warningGradientBeginColor, (1 - matchProbablity) * 0.5f)); // alpha indicates match probability
|
||||||
|
|
||||||
if (matchProbablity < 1) {
|
if (matchProbablity < 1) {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
setGradientColors(warningGradientBeginColor, warningGradientEndColor);
|
setGradientColors(warningGradientBeginColor, warningGradientEndColor);
|
||||||
setIcon(ResourceManager.getIcon("status.warning"));
|
setIcon(ResourceManager.getIcon("status.warning"));
|
||||||
|
|
||||||
if (formattedFuture.isComplexFormat()) {
|
if (formattedFuture.isComplexFormat()) {
|
||||||
typeRenderer.setVisible(true);
|
typeRenderer.setVisible(true);
|
||||||
typeRenderer.setAlpha(1.0f);
|
typeRenderer.setAlpha(1.0f);
|
||||||
@ -148,40 +143,38 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if files already exist
|
// check if files already exist
|
||||||
FormattedFuture pathFuture = (FormattedFuture) value;
|
FormattedFuture pathFuture = (FormattedFuture) value;
|
||||||
if (pathFuture.isDone() && !pathFuture.isCancelled()) {
|
if (pathFuture.isDone() && !pathFuture.isCancelled()) {
|
||||||
File from = renameModel.getMatch(index).getCandidate();
|
File from = renameModel.getMatch(index).getCandidate();
|
||||||
File to = resolveAbsolutePath(from.getParentFile(), pathFuture.toString(), renameModel.preserveExtension() ? getExtension(from) : null);
|
File to = resolveAbsolutePath(from.getParentFile(), pathFuture.toString(), renameModel.preserveExtension() ? getExtension(from) : null);
|
||||||
if (from.equals(to)) {
|
if (from.getAbsolutePath().equals(to.getAbsolutePath())) {
|
||||||
setIcon(ResourceManager.getIcon("dialog.continue"));
|
setIcon(ResourceManager.getIcon("dialog.continue"));
|
||||||
} else if (to.exists()) {
|
} else if (to.exists() && !to.equals(from)) {
|
||||||
setIcon(ResourceManager.getIcon("dialog.cancel"));
|
setIcon(ResourceManager.getIcon("dialog.cancel")); // take into account that on Windows equals/exists is case-insensitive which we have to work around
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String formatPath(File file) {
|
protected String formatPath(File file) {
|
||||||
return normalizePathSeparators(file.getPath());
|
return normalizePathSeparators(file.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String colorizePath(File file, boolean hasExtension) {
|
protected String colorizePath(File file, boolean hasExtension) {
|
||||||
List<File> path = listPath(file);
|
List<File> path = listPath(file);
|
||||||
StringBuilder html = new StringBuilder(512);
|
StringBuilder html = new StringBuilder(512);
|
||||||
html.append("<html><nobr>");
|
html.append("<html><nobr>");
|
||||||
|
|
||||||
// colorize parent path
|
// colorize parent path
|
||||||
for (int i = 0; i < path.size() - 1; i++) {
|
for (int i = 0; i < path.size() - 1; i++) {
|
||||||
float f = (path.size() <= 2) ? 1 : (float) i / (path.size() - 2);
|
float f = (path.size() <= 2) ? 1 : (float) i / (path.size() - 2);
|
||||||
Color c = interpolateHSB(pathRainbowBeginColor, pathRainbowEndColor, f);
|
Color c = interpolateHSB(pathRainbowBeginColor, pathRainbowEndColor, f);
|
||||||
html.append(String.format("<span style='color:rgb(%1$d, %2$d, %3$d)'>%4$s</span><span style='color:rgb(%1$d, %2$d, %3$d)'>/</span>", c.getRed(), c.getGreen(), c.getBlue(), escapeHTML(FileUtilities.getName(path.get(i)))));
|
html.append(String.format("<span style='color:rgb(%1$d, %2$d, %3$d)'>%4$s</span><span style='color:rgb(%1$d, %2$d, %3$d)'>/</span>", c.getRed(), c.getGreen(), c.getBlue(), escapeHTML(FileUtilities.getName(path.get(i)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// only colorize extension
|
// only colorize extension
|
||||||
if (hasExtension) {
|
if (hasExtension) {
|
||||||
html.append(escapeHTML(FileUtilities.getName(file)));
|
html.append(escapeHTML(FileUtilities.getName(file)));
|
||||||
@ -192,100 +185,89 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
|||||||
} else {
|
} else {
|
||||||
html.append(file.getName());
|
html.append(file.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
return html.append("</nobr></html>").toString();
|
return html.append("</nobr></html>").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected File resolveAbsolutePath(File targetDir, String path, String extension) {
|
protected File resolveAbsolutePath(File targetDir, String path, String extension) {
|
||||||
File f = new File(extension == null || extension.isEmpty() ? path : String.format("%s.%s", path, extension));
|
File f = new File(extension == null || extension.isEmpty() ? path : String.format("%s.%s", path, extension));
|
||||||
if (!f.isAbsolute()) {
|
if (!f.isAbsolute()) {
|
||||||
f = new File(targetDir, f.getPath()); // resolve path against target folder
|
f = new File(targetDir, f.getPath()); // resolve path against target folder
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return f.getAbsoluteFile();
|
||||||
return f.getCanonicalFile();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return f.getAbsoluteFile();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected float getMatchProbablity(Match<Object, File> match) {
|
protected float getMatchProbablity(Match<Object, File> match) {
|
||||||
if (match.getValue() instanceof Episode) {
|
if (match.getValue() instanceof Episode) {
|
||||||
float f = verificationMetric().getSimilarity(match.getValue(), match.getCandidate());
|
float f = verificationMetric().getSimilarity(match.getValue(), match.getCandidate());
|
||||||
return (f + 1) / 2; // normalize -1..1 to 0..1
|
return (f + 1) / 2; // normalize -1..1 to 0..1
|
||||||
}
|
}
|
||||||
|
|
||||||
SimilarityMetric fsm = new MetricCascade(new MetricMin(FileSize, 0), FileName, EpisodeIdentifier);
|
SimilarityMetric fsm = new MetricCascade(new MetricMin(FileSize, 0), FileName, EpisodeIdentifier);
|
||||||
float f = fsm.getSimilarity(match.getValue(), match.getCandidate());
|
float f = fsm.getSimilarity(match.getValue(), match.getCandidate());
|
||||||
if (f != 0) {
|
if (f != 0) {
|
||||||
return (Math.max(f, 0)); // normalize -1..1 and boost by 0.25 (because file <-> file matches are not necessarily about Episodes)
|
return (Math.max(f, 0)); // normalize -1..1 and boost by 0.25 (because file <-> file matches are not necessarily about Episodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1; // assume match is OK
|
return 1; // assume match is OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String getType(File file) {
|
protected String getType(File file) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
return "Folder";
|
return "Folder";
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = FileUtilities.getExtension(file);
|
String extension = FileUtilities.getExtension(file);
|
||||||
if (extension != null) {
|
if (extension != null) {
|
||||||
return extension.toLowerCase();
|
return extension.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
// some file with no extension
|
// some file with no extension
|
||||||
return "File";
|
return "File";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class TypeRenderer extends DefaultListCellRenderer {
|
private static class TypeRenderer extends DefaultListCellRenderer {
|
||||||
|
|
||||||
private final Insets margin = new Insets(0, 10, 0, 0);
|
private final Insets margin = new Insets(0, 10, 0, 0);
|
||||||
private final Insets padding = new Insets(0, 6, 0, 5);
|
private final Insets padding = new Insets(0, 6, 0, 5);
|
||||||
private final int arc = 10;
|
private final int arc = 10;
|
||||||
|
|
||||||
private Color gradientBeginColor = new Color(0xFFCC00);
|
private Color gradientBeginColor = new Color(0xFFCC00);
|
||||||
private Color gradientEndColor = new Color(0xFF9900);
|
private Color gradientEndColor = new Color(0xFF9900);
|
||||||
|
|
||||||
private float alpha = 1.0f;
|
private float alpha = 1.0f;
|
||||||
|
|
||||||
|
|
||||||
public TypeRenderer() {
|
public TypeRenderer() {
|
||||||
setOpaque(false);
|
setOpaque(false);
|
||||||
setForeground(new Color(0x141414));
|
setForeground(new Color(0x141414));
|
||||||
|
|
||||||
setBorder(new CompoundBorder(new EmptyBorder(margin), new EmptyBorder(padding)));
|
setBorder(new CompoundBorder(new EmptyBorder(margin), new EmptyBorder(padding)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
Graphics2D g2d = (Graphics2D) g;
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
RoundRectangle2D shape = new RoundRectangle2D.Float(margin.left, margin.top, getWidth() - (margin.left + margin.right), getHeight(), arc, arc);
|
RoundRectangle2D shape = new RoundRectangle2D.Float(margin.left, margin.top, getWidth() - (margin.left + margin.right), getHeight(), arc, arc);
|
||||||
|
|
||||||
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
|
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
|
||||||
|
|
||||||
g2d.setPaint(GradientStyle.TOP_TO_BOTTOM.getGradientPaint(shape, gradientBeginColor, gradientEndColor));
|
g2d.setPaint(GradientStyle.TOP_TO_BOTTOM.getGradientPaint(shape, gradientBeginColor, gradientEndColor));
|
||||||
g2d.fill(shape);
|
g2d.fill(shape);
|
||||||
|
|
||||||
g2d.setFont(getFont());
|
g2d.setFont(getFont());
|
||||||
g2d.setPaint(getForeground());
|
g2d.setPaint(getForeground());
|
||||||
|
|
||||||
Rectangle2D textBounds = g2d.getFontMetrics().getStringBounds(getText(), g2d);
|
Rectangle2D textBounds = g2d.getFontMetrics().getStringBounds(getText(), g2d);
|
||||||
g2d.drawString(getText(), (float) (shape.getCenterX() - textBounds.getX() - (textBounds.getWidth() / 2f)), (float) (shape.getCenterY() - textBounds.getY() - (textBounds.getHeight() / 2)));
|
g2d.drawString(getText(), (float) (shape.getCenterX() - textBounds.getX() - (textBounds.getWidth() / 2f)), (float) (shape.getCenterY() - textBounds.getY() - (textBounds.getHeight() / 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setAlpha(float alpha) {
|
public void setAlpha(float alpha) {
|
||||||
this.alpha = alpha;
|
this.alpha = alpha;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,8 @@ public final class FileUtilities {
|
|||||||
} else {
|
} else {
|
||||||
// move file
|
// move file
|
||||||
try {
|
try {
|
||||||
java.nio.file.Files.move(source.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
// * On Windows ATOMIC_MOVE allows us to rename files even if only lower/upper-case changes (without ATOMIC_MOVE the operation would be ignored)
|
||||||
|
java.nio.file.Files.move(source.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||||
} catch (LinkageError e) {
|
} catch (LinkageError e) {
|
||||||
org.apache.commons.io.FileUtils.moveFile(source, destination); // use "copy and delete" as fallback if standard rename fails
|
org.apache.commons.io.FileUtils.moveFile(source, destination); // use "copy and delete" as fallback if standard rename fails
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user