* 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.proxyPort=8888
# do not use native shell for move/copy operations
-DuseNativeShell=true
# http connection timeouts
-Dsun.net.client.defaultConnectTimeout=5000
-Dsun.net.client.defaultReadTimeout=25000

View File

@ -10,6 +10,9 @@
-Xms64m
-Xmx512m
# do not use native shell for move/copy operations
-DuseNativeShell=false
# proxy settings
# -Dhttp.proxyHost=localhost
# -Dhttp.proxyPort=8888

View File

@ -10,6 +10,9 @@
# -Dhttp.proxyHost=localhost
# -Dhttp.proxyPort=8888
# do not use native shell for move/copy operations
-DuseNativeShell=true
# http connection timeouts
-Dsun.net.client.defaultConnectTimeout=5000
-Dsun.net.client.defaultReadTimeout=25000

View File

@ -2,10 +2,14 @@
package net.sourceforge.filebot;
import static java.util.Collections.*;
import static net.sourceforge.tuned.FileUtilities.*;
import java.io.File;
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.WString;
@ -21,43 +25,43 @@ public enum NativeRenameAction implements RenameAction {
COPY;
@Override
public File rename(File src, File dst) throws Exception {
return rename(new File[] { src }, new File[] { dst })[0];
public File rename(File src, File dst) throws IOException {
dst = resolveDestination(src, dst).getCanonicalFile();
rename(singletonMap(src, dst));
return dst;
}
public File[] rename(File[] src, File[] dst) throws Exception {
String[] pFrom = new String[src.length];
String[] pTo = new String[dst.length];
File[] result = new File[dst.length];
public void rename(Map<File, File> map) throws IOException {
String[] src = new String[map.size()];
String[] dst = new String[map.size()];
// resolve paths
for (int i = 0; i < pFrom.length; i++) {
result[i] = resolveDestination(src[i], dst[i]).getCanonicalFile(); // resolve dst
pTo[i] = result[i].getPath();
pFrom[i] = src[i].getCanonicalPath(); // resolve src
int i = 0;
for (Entry<File, File> it : map.entrySet()) {
src[i] = it.getKey().getCanonicalPath();
dst[i] = it.getValue().getCanonicalPath();
i++;
}
// configure parameter structure
SHFILEOPSTRUCT op = new SHFILEOPSTRUCT();
op.wFunc = ShellAPI.class.getField("FO_" + name()).getInt(null); // ShellAPI.FO_MOVE | ShellAPI.FO_COPY
op.wFunc = (this == MOVE) ? ShellAPI.FO_MOVE : ShellAPI.FO_COPY;
op.fFlags = Shell32.FOF_MULTIDESTFILES | Shell32.FOF_NOCONFIRMMKDIR;
op.pFrom = new WString(op.encodePaths(pFrom));
op.pTo = new WString(op.encodePaths(pTo));
op.pFrom = new WString(op.encodePaths(src));
op.pTo = new WString(op.encodePaths(dst));
System.out.println("NativeRenameAction.rename()");
Shell32.INSTANCE.SHFileOperation(op);
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();
}
}

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() {
try {
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 net.sourceforge.filebot.Settings.*;
import static net.sourceforge.filebot.ui.NotificationLogging.*;
import static net.sourceforge.tuned.ui.TunedUtilities.*;
@ -21,9 +22,12 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Icon;
@ -31,6 +35,7 @@ import javax.swing.SwingWorker;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.HistorySpooler;
import net.sourceforge.filebot.NativeRenameAction;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.StandardRenameAction;
import net.sourceforge.tuned.ui.ProgressDialog;
@ -66,23 +71,36 @@ class RenameAction extends AbstractAction {
Window window = getWindow(evt.getSource());
try {
Map<File, File> renameMap = checkRenamePlan(validate(model.getRenameMap(), window));
StandardRenameAction action = (StandardRenameAction) getValue(RENAME_ACTION);
// start processing
window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
RenameJob renameJob = new RenameJob(renameMap, (StandardRenameAction) getValue(RENAME_ACTION));
renameJob.execute();
try {
// wait a for little while (renaming might finish in less than a second)
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);
if (useNativeShell() && isNativeActionSupported(action)) {
RenameJob renameJob = new NativeRenameJob(renameMap, NativeRenameAction.valueOf(action.name()));
renameJob.execute();
try {
renameJob.get(); // wait for native operation to finish or be cancelled
} catch (CancellationException e) {
// ignore
}
} else {
RenameJob renameJob = new RenameJob(renameMap, action);
renameJob.execute();
// display progress dialog and stop blocking EDT
window.setCursor(Cursor.getDefaultCursor());
dialog.setVisible(true);
try {
// wait a for little while (renaming might finish in less than a second)
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) {
// 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) {
// build rename map and perform some sanity checks
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 {
private final StandardRenameAction action;
protected final net.sourceforge.filebot.RenameAction action;
private final Map<File, File> renameMap;
private final Map<File, File> renameLog;
protected final Map<File, File> renameMap;
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.renameMap = synchronizedMap(renameMap);
this.renameLog = synchronizedMap(new LinkedHashMap<File, File>());
@ -213,19 +242,22 @@ class RenameAction extends AbstractAction {
@Override
protected Map<File, File> doInBackground() throws Exception {
for (Entry<File, File> mapping : renameMap.entrySet()) {
if (isCancelled())
return renameLog;
// update progress dialog
firePropertyChange("currentFile", mapping.getKey(), mapping.getValue());
// rename file, throw exception on failure
action.rename(mapping.getKey(), mapping.getValue());
// remember successfully renamed matches for history entry and possible revert
renameLog.put(mapping.getKey(), mapping.getValue());
try {
for (Entry<File, File> mapping : renameMap.entrySet()) {
if (isCancelled())
return renameLog;
// update progress dialog
firePropertyChange("currentFile", mapping.getKey(), mapping.getValue());
// rename file, throw exception on failure
action.rename(mapping.getKey(), mapping.getValue());
// remember successfully renamed matches for history entry and possible revert
renameLog.put(mapping.getKey(), mapping.getValue());
}
} finally {
postprocess.release();
}
return renameLog;
@ -235,13 +267,13 @@ class RenameAction extends AbstractAction {
@Override
protected void done() {
try {
get(); // check exceptions
} catch (Exception e) {
// ignore
postprocess.acquire();
} catch (InterruptedException e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
}
// collect renamed types
List<Class> types = new ArrayList<Class>();
final List<Class> types = new ArrayList<Class>();
// remove renamed matches
for (File source : renameLog.keySet()) {
@ -269,7 +301,45 @@ class RenameAction extends AbstractAction {
public boolean cancel() {
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();
}
}
}