* integrate native MOVE/COPY on Windows
This commit is contained in:
parent
df91452cc0
commit
0d2314eab0
|
@ -9,6 +9,9 @@
|
||||||
# -Dhttp.proxyHost=localhost
|
# -Dhttp.proxyHost=localhost
|
||||||
# -Dhttp.proxyPort=8888
|
# -Dhttp.proxyPort=8888
|
||||||
|
|
||||||
|
# do not use native shell for move/copy operations
|
||||||
|
-DuseNativeShell=true
|
||||||
|
|
||||||
# http connection timeouts
|
# http connection timeouts
|
||||||
-Dsun.net.client.defaultConnectTimeout=5000
|
-Dsun.net.client.defaultConnectTimeout=5000
|
||||||
-Dsun.net.client.defaultReadTimeout=25000
|
-Dsun.net.client.defaultReadTimeout=25000
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
-Xms64m
|
-Xms64m
|
||||||
-Xmx512m
|
-Xmx512m
|
||||||
|
|
||||||
|
# do not use native shell for move/copy operations
|
||||||
|
-DuseNativeShell=false
|
||||||
|
|
||||||
# proxy settings
|
# proxy settings
|
||||||
# -Dhttp.proxyHost=localhost
|
# -Dhttp.proxyHost=localhost
|
||||||
# -Dhttp.proxyPort=8888
|
# -Dhttp.proxyPort=8888
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
# -Dhttp.proxyHost=localhost
|
# -Dhttp.proxyHost=localhost
|
||||||
# -Dhttp.proxyPort=8888
|
# -Dhttp.proxyPort=8888
|
||||||
|
|
||||||
|
# do not use native shell for move/copy operations
|
||||||
|
-DuseNativeShell=true
|
||||||
|
|
||||||
# http connection timeouts
|
# http connection timeouts
|
||||||
-Dsun.net.client.defaultConnectTimeout=5000
|
-Dsun.net.client.defaultConnectTimeout=5000
|
||||||
-Dsun.net.client.defaultReadTimeout=25000
|
-Dsun.net.client.defaultReadTimeout=25000
|
||||||
|
|
|
@ -2,10 +2,14 @@
|
||||||
package net.sourceforge.filebot;
|
package net.sourceforge.filebot;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.util.Collections.*;
|
||||||
import static net.sourceforge.tuned.FileUtilities.*;
|
import static net.sourceforge.tuned.FileUtilities.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
|
||||||
import com.sun.jna.Platform;
|
import com.sun.jna.Platform;
|
||||||
import com.sun.jna.WString;
|
import com.sun.jna.WString;
|
||||||
|
@ -21,43 +25,43 @@ public enum NativeRenameAction implements RenameAction {
|
||||||
COPY;
|
COPY;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File rename(File src, File dst) throws Exception {
|
public File rename(File src, File dst) throws IOException {
|
||||||
return rename(new File[] { src }, new File[] { dst })[0];
|
dst = resolveDestination(src, dst).getCanonicalFile();
|
||||||
|
rename(singletonMap(src, dst));
|
||||||
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public File[] rename(File[] src, File[] dst) throws Exception {
|
public void rename(Map<File, File> map) throws IOException {
|
||||||
String[] pFrom = new String[src.length];
|
String[] src = new String[map.size()];
|
||||||
String[] pTo = new String[dst.length];
|
String[] dst = new String[map.size()];
|
||||||
File[] result = new File[dst.length];
|
|
||||||
|
|
||||||
// resolve paths
|
// resolve paths
|
||||||
for (int i = 0; i < pFrom.length; i++) {
|
int i = 0;
|
||||||
result[i] = resolveDestination(src[i], dst[i]).getCanonicalFile(); // resolve dst
|
for (Entry<File, File> it : map.entrySet()) {
|
||||||
pTo[i] = result[i].getPath();
|
src[i] = it.getKey().getCanonicalPath();
|
||||||
pFrom[i] = src[i].getCanonicalPath(); // resolve src
|
dst[i] = it.getValue().getCanonicalPath();
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure parameter structure
|
// configure parameter structure
|
||||||
SHFILEOPSTRUCT op = new SHFILEOPSTRUCT();
|
SHFILEOPSTRUCT op = new SHFILEOPSTRUCT();
|
||||||
|
op.wFunc = (this == MOVE) ? ShellAPI.FO_MOVE : ShellAPI.FO_COPY;
|
||||||
op.wFunc = ShellAPI.class.getField("FO_" + name()).getInt(null); // ShellAPI.FO_MOVE | ShellAPI.FO_COPY
|
|
||||||
op.fFlags = Shell32.FOF_MULTIDESTFILES | Shell32.FOF_NOCONFIRMMKDIR;
|
op.fFlags = Shell32.FOF_MULTIDESTFILES | Shell32.FOF_NOCONFIRMMKDIR;
|
||||||
|
|
||||||
op.pFrom = new WString(op.encodePaths(pFrom));
|
op.pFrom = new WString(op.encodePaths(src));
|
||||||
op.pTo = new WString(op.encodePaths(pTo));
|
op.pTo = new WString(op.encodePaths(dst));
|
||||||
|
System.out.println("NativeRenameAction.rename()");
|
||||||
Shell32.INSTANCE.SHFileOperation(op);
|
Shell32.INSTANCE.SHFileOperation(op);
|
||||||
|
|
||||||
if (op.fAnyOperationsAborted) {
|
if (op.fAnyOperationsAborted) {
|
||||||
throw new IOException("Operation Aborted");
|
throw new CancellationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean isSupported(NativeRenameAction action) {
|
public static boolean isSupported() {
|
||||||
return Platform.isWindows();
|
return Platform.isWindows();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,12 @@ public final class Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean useNativeShell() {
|
||||||
|
//TODO disable by default for final release
|
||||||
|
return System.getProperty("useNativeShell") == null ? true : Boolean.parseBoolean(System.getProperty("useNativeShell"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static int getPreferredThreadPoolSize() {
|
public static int getPreferredThreadPoolSize() {
|
||||||
try {
|
try {
|
||||||
return Integer.parseInt(System.getProperty("threadPool"));
|
return Integer.parseInt(System.getProperty("threadPool"));
|
||||||
|
|
|
@ -3,6 +3,7 @@ package net.sourceforge.filebot.ui.rename;
|
||||||
|
|
||||||
|
|
||||||
import static java.util.Collections.*;
|
import static java.util.Collections.*;
|
||||||
|
import static net.sourceforge.filebot.Settings.*;
|
||||||
import static net.sourceforge.filebot.ui.NotificationLogging.*;
|
import static net.sourceforge.filebot.ui.NotificationLogging.*;
|
||||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||||
|
|
||||||
|
@ -21,9 +22,12 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
@ -31,6 +35,7 @@ import javax.swing.SwingWorker;
|
||||||
|
|
||||||
import net.sourceforge.filebot.Analytics;
|
import net.sourceforge.filebot.Analytics;
|
||||||
import net.sourceforge.filebot.HistorySpooler;
|
import net.sourceforge.filebot.HistorySpooler;
|
||||||
|
import net.sourceforge.filebot.NativeRenameAction;
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
import net.sourceforge.filebot.StandardRenameAction;
|
import net.sourceforge.filebot.StandardRenameAction;
|
||||||
import net.sourceforge.tuned.ui.ProgressDialog;
|
import net.sourceforge.tuned.ui.ProgressDialog;
|
||||||
|
@ -66,23 +71,36 @@ class RenameAction extends AbstractAction {
|
||||||
Window window = getWindow(evt.getSource());
|
Window window = getWindow(evt.getSource());
|
||||||
try {
|
try {
|
||||||
Map<File, File> renameMap = checkRenamePlan(validate(model.getRenameMap(), window));
|
Map<File, File> renameMap = checkRenamePlan(validate(model.getRenameMap(), window));
|
||||||
|
StandardRenameAction action = (StandardRenameAction) getValue(RENAME_ACTION);
|
||||||
|
|
||||||
|
// start processing
|
||||||
window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
RenameJob renameJob = new RenameJob(renameMap, (StandardRenameAction) getValue(RENAME_ACTION));
|
|
||||||
renameJob.execute();
|
|
||||||
|
|
||||||
try {
|
if (useNativeShell() && isNativeActionSupported(action)) {
|
||||||
// wait a for little while (renaming might finish in less than a second)
|
RenameJob renameJob = new NativeRenameJob(renameMap, NativeRenameAction.valueOf(action.name()));
|
||||||
renameJob.get(2, TimeUnit.SECONDS);
|
renameJob.execute();
|
||||||
} catch (TimeoutException ex) {
|
try {
|
||||||
// move/renaming will probably take a while
|
renameJob.get(); // wait for native operation to finish or be cancelled
|
||||||
ProgressDialog dialog = createProgressDialog(window, renameJob);
|
} catch (CancellationException e) {
|
||||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
// ignore
|
||||||
dialog.setIndeterminate(true);
|
}
|
||||||
|
} else {
|
||||||
|
RenameJob renameJob = new RenameJob(renameMap, action);
|
||||||
|
renameJob.execute();
|
||||||
|
|
||||||
// display progress dialog and stop blocking EDT
|
try {
|
||||||
window.setCursor(Cursor.getDefaultCursor());
|
// wait a for little while (renaming might finish in less than a second)
|
||||||
dialog.setVisible(true);
|
renameJob.get(2, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException ex) {
|
||||||
|
// move/renaming will probably take a while
|
||||||
|
ProgressDialog dialog = createProgressDialog(window, renameJob);
|
||||||
|
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||||
|
dialog.setIndeterminate(true);
|
||||||
|
|
||||||
|
// display progress dialog and stop blocking EDT
|
||||||
|
window.setCursor(Cursor.getDefaultCursor());
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// could not rename one of the files, revert all changes
|
// could not rename one of the files, revert all changes
|
||||||
|
@ -93,6 +111,15 @@ class RenameAction extends AbstractAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isNativeActionSupported(StandardRenameAction action) {
|
||||||
|
try {
|
||||||
|
return NativeRenameAction.isSupported() && NativeRenameAction.valueOf(action.name()) != null;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private Map<File, File> checkRenamePlan(List<Entry<File, File>> renamePlan) {
|
private Map<File, File> checkRenamePlan(List<Entry<File, File>> renamePlan) {
|
||||||
// build rename map and perform some sanity checks
|
// build rename map and perform some sanity checks
|
||||||
Map<File, File> renameMap = new HashMap<File, File>();
|
Map<File, File> renameMap = new HashMap<File, File>();
|
||||||
|
@ -198,13 +225,15 @@ class RenameAction extends AbstractAction {
|
||||||
|
|
||||||
protected class RenameJob extends SwingWorker<Map<File, File>, Void> implements Cancellable {
|
protected class RenameJob extends SwingWorker<Map<File, File>, Void> implements Cancellable {
|
||||||
|
|
||||||
private final StandardRenameAction action;
|
protected final net.sourceforge.filebot.RenameAction action;
|
||||||
|
|
||||||
private final Map<File, File> renameMap;
|
protected final Map<File, File> renameMap;
|
||||||
private final Map<File, File> renameLog;
|
protected final Map<File, File> renameLog;
|
||||||
|
|
||||||
|
protected final Semaphore postprocess = new Semaphore(0);
|
||||||
|
|
||||||
|
|
||||||
public RenameJob(Map<File, File> renameMap, StandardRenameAction action) {
|
public RenameJob(Map<File, File> renameMap, net.sourceforge.filebot.RenameAction action) {
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.renameMap = synchronizedMap(renameMap);
|
this.renameMap = synchronizedMap(renameMap);
|
||||||
this.renameLog = synchronizedMap(new LinkedHashMap<File, File>());
|
this.renameLog = synchronizedMap(new LinkedHashMap<File, File>());
|
||||||
|
@ -213,19 +242,22 @@ class RenameAction extends AbstractAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<File, File> doInBackground() throws Exception {
|
protected Map<File, File> doInBackground() throws Exception {
|
||||||
for (Entry<File, File> mapping : renameMap.entrySet()) {
|
try {
|
||||||
|
for (Entry<File, File> mapping : renameMap.entrySet()) {
|
||||||
if (isCancelled())
|
if (isCancelled())
|
||||||
return renameLog;
|
return renameLog;
|
||||||
|
|
||||||
// update progress dialog
|
// update progress dialog
|
||||||
firePropertyChange("currentFile", mapping.getKey(), mapping.getValue());
|
firePropertyChange("currentFile", mapping.getKey(), mapping.getValue());
|
||||||
|
|
||||||
// rename file, throw exception on failure
|
// rename file, throw exception on failure
|
||||||
action.rename(mapping.getKey(), mapping.getValue());
|
action.rename(mapping.getKey(), mapping.getValue());
|
||||||
|
|
||||||
// remember successfully renamed matches for history entry and possible revert
|
// remember successfully renamed matches for history entry and possible revert
|
||||||
renameLog.put(mapping.getKey(), mapping.getValue());
|
renameLog.put(mapping.getKey(), mapping.getValue());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
postprocess.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
return renameLog;
|
return renameLog;
|
||||||
|
@ -235,13 +267,13 @@ class RenameAction extends AbstractAction {
|
||||||
@Override
|
@Override
|
||||||
protected void done() {
|
protected void done() {
|
||||||
try {
|
try {
|
||||||
get(); // check exceptions
|
postprocess.acquire();
|
||||||
} catch (Exception e) {
|
} catch (InterruptedException e) {
|
||||||
// ignore
|
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect renamed types
|
// collect renamed types
|
||||||
List<Class> types = new ArrayList<Class>();
|
final List<Class> types = new ArrayList<Class>();
|
||||||
|
|
||||||
// remove renamed matches
|
// remove renamed matches
|
||||||
for (File source : renameLog.keySet()) {
|
for (File source : renameLog.keySet()) {
|
||||||
|
@ -269,7 +301,45 @@ class RenameAction extends AbstractAction {
|
||||||
public boolean cancel() {
|
public boolean cancel() {
|
||||||
return cancel(true);
|
return cancel(true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class NativeRenameJob extends RenameJob implements Cancellable {
|
||||||
|
|
||||||
|
public NativeRenameJob(Map<File, File> renameMap, NativeRenameAction action) {
|
||||||
|
super(renameMap, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<File, File> doInBackground() throws Exception {
|
||||||
|
NativeRenameAction shell = (NativeRenameAction) action;
|
||||||
|
|
||||||
|
// call native shell move/copy
|
||||||
|
try {
|
||||||
|
shell.rename(renameMap);
|
||||||
|
} catch (CancellationException e) {
|
||||||
|
// set as cancelled and propagate the exception
|
||||||
|
super.cancel(false);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
// check status of renamed files
|
||||||
|
for (Entry<File, File> it : renameMap.entrySet()) {
|
||||||
|
if (it.getValue().exists()) {
|
||||||
|
renameLog.put(it.getKey(), it.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
postprocess.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return renameLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean cancel() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue