* integrate native MOVE/COPY on Windows

This commit is contained in:
Reinhard Pointner 2012-07-17 20:52:03 +00:00
parent df91452cc0
commit 0d2314eab0
6 changed files with 142 additions and 53 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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