files - java io move file to another directory
Forzar detener Java Files.copy() ejecutándose en un hilo externo (3)
La respuesta aquí parecía ser una solución válida antes de Java 8: ¿Cómo cancelar Files.copy () en Java?
Pero ahora no funciona, porque ExtendedCopyOption.INTERRUPTIBLE
es privado.
Básicamente, necesito descargar un archivo de una URL
dada y guardarlo en mi sistema de archivos local usando Files.copy()
. Actualmente, estoy usando un servicio JavaFX porque necesito mostrar el progreso en una ProgressBar
.
Sin embargo, no sé cómo bloquear el hilo que ejecuta Files.copy()
si la operación lleva demasiado tiempo. El uso de Thread.stop()
es al menos no deseado. Incluso Thread.interrupt()
falla.
También quiero que la operación finalice correctamente si la conexión a Internet deja de estar disponible.
Para probar el caso cuando no hay conexión a Internet disponible, retiro mi cable de ethernet y lo vuelvo a colocar después de 3 segundos. Desafortunadamente, Files.copy()
solo regresa cuando devuelvo el cable de ethernet, aunque me gustaría que fallara inmediatamente.
Como puedo ver, internamente Files.copy()
está ejecutando un bucle, lo que impide que el hilo salga.
Tester
(descargando OBS Studio exe):
/**
* @author GOXR3PLUS
*
*/
public class TestDownloader extends Application {
/**
* @param args
*/
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
// Block From exiting
Platform.setImplicitExit(false);
// Try to download the File from URL
new DownloadService().startDownload(
"https://github.com/jp9000/obs-studio/releases/download/17.0.2/OBS-Studio-17.0.2-Small-Installer.exe",
System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "OBS-Studio-17.0.2-Small-Installer.exe");
}
}
DownloadService
:
El uso de @sillyfly comment con FileChannel
y la eliminación de File.copy
parece funcionar solo con la llamada a Thread.interrupt()
pero no sale cuando Internet no está disponible.
import java.io.File;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
/**
* JavaFX Service which is Capable of Downloading Files from the Internet to the
* LocalHost
*
* @author GOXR3PLUS
*
*/
public class DownloadService extends Service<Boolean> {
// -----
private long totalBytes;
private boolean succeeded = false;
private volatile boolean stopThread;
// CopyThread
private Thread copyThread = null;
// ----
private String urlString;
private String destination;
/**
* The logger of the class
*/
private static final Logger LOGGER = Logger.getLogger(DownloadService.class.getName());
/**
* Constructor
*/
public DownloadService() {
setOnFailed(f -> System.out.println("Failed with value: " + super.getValue()+" , Copy Thread is Alive? "+copyThread.isAlive()));
setOnSucceeded(s -> System.out.println("Succeeded with value: " + super.getValue()+" , Copy Thread is Alive? "+copyThread.isAlive()));
setOnCancelled(c -> System.out.println("Succeeded with value: " + super.getValue()+" , Copy Thread is Alive? "+copyThread.isAlive()));
}
/**
* Start the Download Service
*
* @param urlString
* The source File URL
* @param destination
* The destination File
*/
public void startDownload(String urlString, String destination) {
if (!super.isRunning()) {
this.urlString = urlString;
this.destination = destination;
totalBytes = 0;
restart();
}
}
@Override
protected Task<Boolean> createTask() {
return new Task<Boolean>() {
@Override
protected Boolean call() throws Exception {
// Succeeded boolean
succeeded = true;
// URL and LocalFile
URL urlFile = new URL(java.net.URLDecoder.decode(urlString, "UTF-8"));
File destinationFile = new File(destination);
try {
// Open the connection and get totalBytes
URLConnection connection = urlFile.openConnection();
totalBytes = Long.parseLong(connection.getHeaderField("Content-Length"));
// --------------------- Copy the File to External Thread-----------
copyThread = new Thread(() -> {
// Start File Copy
try (FileChannel zip = FileChannel.open(destinationFile.toPath(), StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
zip.transferFrom(Channels.newChannel(connection.getInputStream()), 0, Long.MAX_VALUE);
// Files.copy(dl.openStream(), fl.toPath(),StandardCopyOption.REPLACE_EXISTING)
} catch (Exception ex) {
stopThread = true;
LOGGER.log(Level.WARNING, "DownloadService failed", ex);
}
System.out.println("Copy Thread exited...");
});
// Set to Daemon
copyThread.setDaemon(true);
// Start the Thread
copyThread.start();
// -------------------- End of Copy the File to External Thread-------
// ---------------------------Check the %100 Progress--------------------
long outPutFileLength;
long previousLength = 0;
int failCounter = 0;
// While Loop
while ((outPutFileLength = destinationFile.length()) < totalBytes && !stopThread) {
// Check the previous length
if (previousLength != outPutFileLength) {
previousLength = outPutFileLength;
failCounter = 0;
} else
++failCounter;
// 2 Seconds passed without response
if (failCounter == 40 || stopThread)
break;
// Update Progress
super.updateProgress((outPutFileLength * 100) / totalBytes, 100);
System.out.println("Current Bytes:" + outPutFileLength + " ,|, TotalBytes:" + totalBytes
+ " ,|, Current Progress: " + (outPutFileLength * 100) / totalBytes + " %");
// Sleep
try {
Thread.sleep(50);
} catch (InterruptedException ex) {
LOGGER.log(Level.WARNING, "", ex);
}
}
// 2 Seconds passed without response
if (failCounter == 40)
succeeded = false;
// --------------------------End of Check the %100 Progress--------------------
} catch (Exception ex) {
succeeded = false;
// Stop the External Thread which is updating the %100
// progress
stopThread = true;
LOGGER.log(Level.WARNING, "DownloadService failed", ex);
}
//----------------------Finally------------------------------
System.out.println("Trying to interrupt[shoot with an assault rifle] the copy Thread");
// ---FORCE STOP COPY FILES
if (copyThread != null && copyThread.isAlive()) {
copyThread.interrupt();
System.out.println("Done an interrupt to the copy Thread");
// Run a Looping checking if the copyThread has stopped...
while (copyThread.isAlive()) {
System.out.println("Copy Thread is still Alive,refusing to die.");
Thread.sleep(50);
}
}
System.out.println("Download Service exited:[Value=" + succeeded + "] Copy Thread is Alive? "
+ (copyThread == null ? "" : copyThread.isAlive()));
//---------------------- End of Finally------------------------------
return succeeded;
}
};
}
}
Preguntas interesantes:
Hay un aspecto importante no cubierto por las otras respuestas / comentarios; y esa es una suposición equivocada de los suyos:
Lo que quiero es que falle inmediatamente cuando no haya conexión a Internet.
No es tan fácil. La pila TCP / máquina de estado es realmente una cosa bastante complicada; y dependiendo de su contexto (tipo de SO, implementación de la pila TCP, parámetros del núcleo, ...), puede haber situaciones en las que se produce una partición de red y un remitente no se da cuenta durante 15 o más minutos . Escuche here para más detalles sobre eso.
En otras palabras: "simplemente desconectar el enchufe" no es igual a "romper inmediatamente" su conexión TCP existente. Y solo para el registro: no es necesario enchufar los cables manualmente para simular las interrupciones de la red. En una configuración de prueba razonable, las herramientas como los cortafuegos iptables aka pueden hacer eso por usted.
Le recomiendo encarecidamente que use un FileChannel
. Tiene el método transferFrom()
que devuelve inmediatamente cuando se interrumpe el hilo que lo ejecuta. (El Javadoc aquí dice que debería generar una ClosedByInterruptException
, pero no es así).
try (FileChannel channel = FileChannel.open(Paths.get(...), StandardOpenOption.CREATE,
StandardOpenOption.WRITE)) {
channel.transferFrom(Channels.newChannel(new URL(...).openStream()), 0, Long.MAX_VALUE);
}
También tiene el potencial de funcionar mucho mejor que su alternativa de java.io
(Sin embargo, resulta que la implementación de Files.copy()
puede elegir delegar en este método en lugar de realizar la copia por sí mismo).
Aquí hay un ejemplo de un servicio JavaFX reutilizable que le permite buscar un recurso de Internet y guardarlo en su sistema de archivos local, con una finalización elegante y automática si la operación lleva demasiado tiempo.
- La tarea de servicio (generada por
createTask()
) es el usuario de la API de canal de archivos. - Se usa un
ScheduledExecutorService
separado para manejar la restricción de tiempo. - Siga siempre las buenas prácticas para extender el
Service
. - Si opta por utilizar dicho método de alto nivel, no podrá rastrear el progreso de la tarea.
- Si la conexión no está disponible,
transferFrom()
debería regresar sin lanzar una excepción.
Para comenzar el servicio (puede hacerse desde cualquier hilo):
DownloadService downloadService = new DownloadService();
downloadService.setRemoteResourceLocation(new URL("http://speedtest.ftp.otenet.gr/files/test1Gb.db"));
downloadService.setPathToLocalResource(Paths.get("C:", "test1Gb.db"));
downloadService.start();
y luego cancelarlo (de lo contrario, se cancelará automáticamente después de que el tiempo expire):
downloadService.cancel();
Tenga en cuenta que el mismo servicio se puede reutilizar, solo asegúrese de restablecerlo antes de volver a comenzar:
downloadService.reset();
Aquí está la clase DownloadService
:
public class DownloadService extends Service<Void> {
private static final long TIME_BUDGET = 2; // In seconds
private final ScheduledExecutorService watchdogService =
Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
private final ThreadFactory delegate = Executors.defaultThreadFactory();
@Override
public Thread newThread(Runnable r) {
Thread thread = delegate.newThread(r);
thread.setDaemon(true);
return thread;
}
});
private Future<?> watchdogThread;
private final ObjectProperty<URL> remoteResourceLocation = new SimpleObjectProperty<>();
private final ObjectProperty<Path> pathToLocalResource = new SimpleObjectProperty<>();
public final URL getRemoteResourceLocation() {
return remoteResourceLocation.get();
}
public final void setRemoteResourceLocation(URL remoteResourceLocation) {
this.remoteResourceLocation.set(remoteResourceLocation);
}
public ObjectProperty<URL> remoteResourceLocationProperty() {
return remoteResourceLocation;
}
public final Path getPathToLocalResource() {
return pathToLocalResource.get();
}
public final void setPathToLocalResource(Path pathToLocalResource) {
this.pathToLocalResource.set(pathToLocalResource);
}
public ObjectProperty<Path> pathToLocalResourceProperty() {
return pathToLocalResource;
}
@Override
protected Task<Void> createTask() {
final Path pathToLocalResource = getPathToLocalResource();
final URL remoteResourceLocation = getRemoteResourceLocation();
if (pathToLocalResource == null) {
throw new IllegalStateException("pathToLocalResource property value is null");
}
if (remoteResourceLocation == null) {
throw new IllegalStateException("remoteResourceLocation property value is null");
}
return new Task<Void>() {
@Override
protected Void call() throws IOException {
try (FileChannel channel = FileChannel.open(pathToLocalResource, StandardOpenOption.CREATE,
StandardOpenOption.WRITE)) {
channel.transferFrom(Channels.newChannel(remoteResourceLocation.openStream()), 0, Long.MAX_VALUE);
}
return null;
}
};
}
@Override
protected void running() {
watchdogThread = watchdogService.schedule(() -> {
Platform.runLater(() -> cancel());
}, TIME_BUDGET, TimeUnit.SECONDS);
}
@Override
protected void succeeded() {
watchdogThread.cancel(false);
}
@Override
protected void cancelled() {
watchdogThread.cancel(false);
}
@Override
protected void failed() {
watchdogThread.cancel(false);
}
}
Parece que necesita un HTTP GET asíncrono / cancelable que puede ser difícil.
El problema es que si se detienen las paradas de lectura en espera de más datos (se tira del cable), no se cerrará hasta que el zócalo muera o entren nuevos datos.
Hay algunas rutas que puede seguir, jugando con fábricas de socket para establecer un buen tiempo de espera, utilizando el cliente http con tiempos de espera y otros.
Me gustaría echar un vistazo a Apache Http Components que tiene HTTP no bloqueado basado en java NIO Sockets.