JavaFX HTTP Download task im Hintergrund innerhalb GUI

Bitte aktiviere JavaScript!
Hallo zusammen,

ich versuche mich gerade an einem HTTP Download Manager. Den Code ohne JavaFX habe ich am laufen und nun versuche ich das Ganze in eine kleine JavaFX basierte GUI zu "gießen". Ich versuche hierbei das MVC Pattern zu benutzen, so wie ich es hier im Forum gelernt habe. Als Basis hatte ich dazu mal mit Hilfe von dzim eine kleine Muster Applikation geschrieben. Siehe Github MVC-Example

Ich habe verschieden Eingabefelder in meinem MainController:

12193

Wenn alle erfoderlichen Felder ausgefüllt sind wird der Start Button aktiv. Beim click auf den Start Button wird dann folgende Methode im MainController ausgeführt:
Java:
public void startDownloadButtonTapped() {
        // Clear messages TextArea/Labels before starting ...
        messages.setText("");
        message1Label.setText("");
        message2Label.setText("");

        toggleStartStopDownloadButton.setFadeTransitionShowButtonOne(false);
        pBar.progressProperty().bind(serviceWorkerTask1.progressProperty());
        downloadFolderTextField.setDisable(true);
        sourceUrlTextField.setDisable(true);
        proxyServerTextField.setDisable(true);
        proxyServerTextField.setOpacity(0.25);
        proxyPortTextField.setDisable(true);
        proxyPortTextField.setOpacity(0.25);
        model.setDownloadFinished(false);
        if (!serviceWorkerTask1.isRunning()) {
            LOGGER.info("Start downloading service task ...");
            serviceWorkerTask1.reset();
            serviceWorkerTask1.start();
        }
    }
Die Arbeit wird dann im ServiceWorkerTask1 gemacht - so meine Idee. Hier habe ich bis jetzt folgendes ausgeführt, was auch so funktioniert:

Java:
Service serviceWorkerTask1 = new Service() {
        int indexValue;

        @Override
        protected Task createTask() {
            return new Task() {
                @Override
                protected Void call() throws Exception {
                    // Start entering execution code here ...

                    // Check, and if necessary create DownloadFolder ...
                    dao.checkAndCreateDownloadFolder(downloadFolderTextField.getText());

                    // Get file size
                    model.setFileSize(dao.getFileSize(sourceUrlTextField.getText()));
                    if (model.getFileSize() != -1) {
                        Platform.runLater(() -> message2Label.setText("File size: " + model.getFileSize() + " Bytes"));
                    } else {
                        serviceWorkerTask1.onFailedProperty();
                    }
                  

//                    final int max = 10;
//                    int counter;
//                    for (counter = 1; counter <= max; counter++) {
//                        indexValue = model.getArrayIndex(counter, max);
//                        updateMessage("Value at index " + counter + ": " + indexValue);
//                        updateProgress(counter, max);
//                        Thread.sleep(1000);
//                    }

                    // End entering execution code here ...
                    return null;
                }

                @Override
                protected void succeeded() {
                    super.succeeded();
                    messages.appendText("Download Manager Task Succeeded!");
                    LOGGER.info("Download Manager Task Succeeded!");
                    updateProgress(1.0, 1.0);
                    toggleStartStopDownloadButton.setFadeTransitionShowButtonOne(true);
                    downloadFolderTextField.setDisable(false);
                    sourceUrlTextField.setDisable(false);
                    proxyServerTextField.setDisable(false);
                    proxyServerTextField.setOpacity(1.0);
                    proxyPortTextField.setDisable(false);
                    proxyPortTextField.setOpacity(1.0);
                }

                @Override
                protected void cancelled() {
                    super.cancelled();
                    messages.appendText("Download Manager Task Cancelled!");
                    LOGGER.info("Download Manager Task Cancelled!");
                    toggleStartStopDownloadButton.setFadeTransitionShowButtonOne(true);
                    downloadFolderTextField.setDisable(false);
                    sourceUrlTextField.setDisable(false);
                    proxyServerTextField.setDisable(false);
                    proxyServerTextField.setOpacity(1.0);
                    proxyPortTextField.setDisable(false);
                    proxyPortTextField.setOpacity(1.0);
                    updateProgress(0, 0);
                }

                @Override
                protected void failed() {
                    super.failed();
                    messages.appendText("Download Manager Task Failed!");
                    LOGGER.info("Download Manager Task Failed!");
                    toggleStartStopDownloadButton.setFadeTransitionShowButtonOne(true);
                    downloadFolderTextField.setDisable(false);
                    sourceUrlTextField.setDisable(false);
                    proxyServerTextField.setDisable(false);
                    proxyServerTextField.setOpacity(1.0);
                    proxyPortTextField.setDisable(false);
                    proxyPortTextField.setOpacity(1.0);
                    updateProgress(0, 0);
                }
            };
        }
1. Ich erstelle den DownloadeFolder falls dieser nicht existiert. Das ganze mache ich in einem DAOService:

Java:
public void checkAndCreateDownloadFolder(String downloadFolder) {
        try {
            downloadFolderFile = new File(downloadFolder);
            if (!downloadFolderFile.exists()) {
                downloadFolderFile.mkdirs();
            }
        } catch (Exception ex) {
            fireException(ex);
        }
    }
2. Dann ermiietel ich die Größe in Byte des zu ladenden Files:

Java:
public int getFileSize(String link) {
        URLConnection conn = null;
        try {
            URL url = new URL(link);
            Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxyserver", 8080));
            conn = url.openConnection(proxy);
            if (conn instanceof HttpURLConnection) {
                ((HttpURLConnection) conn).setRequestMethod("HEAD");
            }
            conn.getInputStream();
            return conn.getContentLength();
        } catch (Exception ex) {
            fireException(ex);
            return -1;
        } finally {
            if (conn instanceof HttpURLConnection) {
                ((HttpURLConnection) conn).disconnect();
            }
        }

    }
Auch das funktioniert soweit.

Info: Der Code für den Proxy Server (CheckBox) ist noch nicht implementiert, aktuell wird immer proxy verwendet.

Nun fehlt mir noch folgendes:
- Start Download des Files
- In der ProgressBar soll angezeigt werden wieviel Bytes von Gesamt Bytes geladen sind.
- Am Ende soll noch noch der Durchsatz angezeigt werden, z.B.: 2 MByte/sec.

Der Code aus dem Programm *OHNE* JavaFX sieht dann so aus:

Java:
public void run() {
        try {
            URL url = new URL(link);
            HttpURLConnection hConnection = (HttpURLConnection) url.openConnection();

            // Get size of file (int Bytes) to be downloaded ...
            int fileSize = getFileSize(url);

            // Inputstream arbeitet immer mit Byte
            BufferedInputStream bufferedInputStream = new BufferedInputStream(hConnection.getInputStream());

            // Datei schreiben / erstellen
            outputFile = new File(defaultDownloadFolder, "DownloadFile.data");
            OutputStream outputStream = new FileOutputStream(outputFile);
            BufferedOutputStream bOutputStream = new BufferedOutputStream(outputStream, 1024);

            byte[] buffer = new byte[1024];
            int downloaded = 0;
            int readByte = 0;

            while ((readByte = bufferedInputStream.read(buffer, 0, 1024)) >= 0) {
                bOutputStream.write(buffer, 0, readByte);
                downloaded += readByte;

                System.out.println("Bereits " + downloaded + " Byte " + " von " + fileSize + " Bytes geladen");
            }

            bOutputStream.close();
            bufferedInputStream.close();
            System.out.println("Download erfolgreich");

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
An dieser Stelle fehlt mir nun die Idee wie ich diesen Download und die Anzeige bzw. Update der ProgressBar in mein Programm, sprich in den ServiceTask am Besten integrieren kann. Folgendes ist mir im Detail nicht klar:
1. Wie kann ich den Download im Hintergrund laufen lassen. Meine Idee wäre hier den Download Code in den DAOService zu integrieren.
2. Wenn der Download im DAOService läuft habe ich dort keinen Zugriff auf das Model um z.B. die geladenen Bytes zu speichern um diese dann im MainController anzuzeigen und damit auch die ProgressBar zu bedienen.
3. Ich müsste dann ja auch noch einen Merker, bzw. Trigger (BooleanProperty im Model !!??) haben der mir zeigt das der Download nun fertig ist.

Ich hoffe ich habe mein Problem anschaulich erklärt und Ihr habe eine Idee wie ich das programmieren könnte.

Gruß

Ralf
 
Zuletzt bearbeitet:
Hallo mihe7,

Danke für den Tipp ;) werde ich beim nächsten Mal beherzigen ... hoffe das hierzu dennoch jemand etwas sagen kann ...
 
Der Code aus dem Programm *OHNE* JavaFX sieht dann so aus:
Dort würde ich anfangen: ohne JavaFX.

Nehmen wir mal an, wir hätten eine Klasse Download. Die API könnte in etwa so aussehen:
Java:
class Download implements Runnable {
    public Download(URL url) { ... }
    public void addDownloadListener(DownloadListener l) { ... }
    public void removeDownloadListener(DownloadListener l) { ... }
    public void run() { ... }
}
Die run-Methode macht das, was Du oben gezeigt hast - mit Ausnahme der Ausgaben. Die ersetzt Du durch Benachrichtigungen der Listener.

Damit hast Du eine Download-Klasse, die unabhängig vom Framework funktioniert und über das Observer-Pattern entkoppelt ist.

Wie kann ich den Download im Hintergrund laufen lassen. Meine Idee wäre hier den Download Code in den DAOService zu integrieren.
Du würdest die Download-Klasse in einem Task verwenden.

Wenn der Download im DAOService läuft habe ich dort keinen Zugriff auf das Model um z.B. die geladenen Bytes zu speichern um diese dann im MainController anzuzeigen und damit auch die ProgressBar zu bedienen.
Hier sehe ich im Wesentlichen vier Möglichkeiten:
1. Du gibst das Model dem Task mit
2. Du gibst einen DownloadListener dem Task mit
3. Dein MainController registriert sich als Listener der Download-Klasse
4. Dein Task registriert sich als Listener der Download-Klasse und der MainController lauscht auf Änderungen von Properties, die der Service anbietet. Du könntest z. B. als Value-Typ die DownloadEvent-Klasse verwenden und dann die value-Property beim Eintreffen eines DownloadEvents aktualisieren.

Ich müsste dann ja auch noch einen Merker, bzw. Trigger (BooleanProperty im Model !!??) haben der mir zeigt das der Download nun fertig ist.
Wäre damit erschlagen, oder?
 
Hallo mihe7,

erst einmal vielen Dank für Deine Ideen und Ausführungen.

Das mit dem DAOService habe ich aus meinen Projekten mit MVC und Datenbanken entnommen, mit dem Hintergrund das der Controller nicht wissen muss/sollte wie er an die Daten kommt - spricht einen Entkoppelung um dann gf. relativ einfach eine andere Download Art, wie. z.B. von HTTP auf FTP, umstellen zu können.

Nach Deinen Ausführungen und bei genauerer Betrachtung würde ich diese "Logik" in die Download Klasse implementieren, welches dann ja quasi die Entkoppelung von Controller zu DAO ist.

Soweit habe ich das verstanden und ich würde jetzt mal probieren eine Download Klasse zu kodieren. An Deiner Ausführung ist mir die wichtige Sache mit

public void addDownloadListener(DownloadListener l) { ... } public void removeDownloadListener(DownloadListener l) { ... }
absolut nicht klar. Wenn Du dazu noch etwas im Detail schreiben könntest wäre das sehr hilfreich.

Eine weitere Problematik ist mir auch noch nicht ganz klar.
Ich starte innerhalb des Service Task den Download, in etwa stelle ich mir das so vor ...

Java:
new Thread(new Download(downloadLink)).start();
An der Stelle muss der Service Task ja auf die Beendigung des Download warten und z.B. die ProgressBar aktualisieren und z.B. die bereits geladenen Bytes anzeigen/aktualisieren.

Wie würde ich das denn realisieren? Ich stelle mir vor das ich z.B. so lange in einer while-loop bleibe bis ein Property (im Model??) gesetzt ist.
Oder kommt hier der Listener in's Spiel den ich wie oben geschrieben noch nicht verstanden habe o_O

Zu dem Punkt mit dem Austausch der Daten würde ich der Download Klasse das Model mitgeben - da wüsste ich wie das funktioniert ;) Deine Vorschläge 2-4 sind vermutlich eleganter, aber da haben wir wieder die Listener ...

Gruß

Ralf
 
Habs jetzt nur überflogen, aber bringt die Task-Klasse den ganzen Spaß nicht schon mit sich?
Java:
public class HttpDownloadTask extends Task<File> {

    private String link;

    public HttpDownloadTask( String link ) {
        this.link = link;
    }

    @Override
    protected File call() throws Exception {
        File outputFile = null;
        try {
            URL url = new URL(link);
            HttpURLConnection hConnection = ( HttpURLConnection ) url.openConnection();

            updateMessage("Ermittle Dateigröße...");
            int fileSize = getFileSize();

            updateProgress(0, fileSize);
            Platform.runLater(() -> updateMessage(String.format("Download: %d / %d Byte ", 0, fileSize)));
            BufferedInputStream bufferedInputStream = new BufferedInputStream(hConnection.getInputStream());


            outputFile = new File("DownloadFile.data");
            OutputStream outputStream = new FileOutputStream(outputFile);
            BufferedOutputStream bOutputStream = new BufferedOutputStream(outputStream, 1024);

            byte[] buffer = new byte[ 1024 ];
            int downloaded = 0;
            int readByte = 0;

            while ( ( readByte = bufferedInputStream.read(buffer, 0, 1024) ) >= 0 ) {
                bOutputStream.write(buffer, 0, readByte);
                downloaded += readByte;

                final int progress = downloaded;
                updateProgress(downloaded, fileSize);
                Platform.runLater(() -> updateMessage(String.format("Download: %d / %d Byte ", progress, fileSize)));
            }

            bOutputStream.close();
            bufferedInputStream.close();
        } catch ( Exception e ) {
            Platform.runLater(() -> updateMessage("Beim Download ist ein Fehler aufgetreten"));
            failed();
        }
        return outputFile;
    }

    private int getFileSize() {
        URLConnection conn = null;
        try {
            URL url = new URL(link);
            conn = url.openConnection();
            if ( conn instanceof HttpURLConnection ) {
                ( ( HttpURLConnection ) conn ).setRequestMethod("HEAD");
            }
            conn.getInputStream();
            return conn.getContentLength();
        } catch ( Exception e ) {
            Platform.runLater(() -> updateMessage("Fehler bei der Ermittlung der Dateigröße!"));
            failed();
            return -1;
        } finally {
            if ( conn instanceof HttpURLConnection ) {
                ( ( HttpURLConnection ) conn ).disconnect();
            }
        }
    }
}
Java:
public class DownloadController implements Initializable {

    @FXML
    private ProgressIndicator downloadIndicator;
    @FXML
    private Button downloadStartButton;
    @FXML
    private Label downloadStatusLabel;

    private Service<File> downloadService;

    @FXML
    public void onDownloadStartPressed() {
        downloadStartButton.setDisable(true);
        downloadService.start();
    }

    @Override
    public void initialize( URL location, ResourceBundle resources ) {
        downloadService = new Service<>() {
            @Override
            protected Task<File> createTask() {
                HttpDownloadTask downloadTask = new HttpDownloadTask("https://speed.hetzner.de/100MB.bin");

                downloadStatusLabel.textProperty().bind(downloadTask.messageProperty());
                downloadIndicator.progressProperty().bind(downloadTask.progressProperty());

                downloadTask.exceptionProperty().addListener((obs, oldVal, newVal) -> downloadStatusLabel.setText(newVal.getMessage()));

                downloadTask.setOnSucceeded(event -> downloadStartButton.setDisable(false));
                downloadTask.setOnCancelled(event -> downloadStartButton.setDisable(false));
                downloadTask.setOnFailed(event -> downloadStartButton.setDisable(false));

                return downloadTask;
            }
        };
    }
}
 
Hallo Robat,

:) ich habe das jetzt auch nur überflogen, wollte Dir aber schon mal DANKE sagen, das sieht in der Tat so aus wie das was ich vor habe - nur wesentlich übersichtlicher. Ich schaue mir das Morgen in Ruhe an, versuche es zu verstehen und bei mir zu implementieren.

Ich melde mich dann hier im Forum mit dem Ergebnis.

Was ich auch noch nicht kannte ist https://speed.hetzner.de :p da kann ich mir meine Test Files auf meinem Webspace ja sparen - Super - vielen Dank.

Gruß

Ralf
 
Habs jetzt nur überflogen, aber bringt die Task-Klasse den ganzen Spaß nicht schon mit sich?
Natürlich bringt die das mit, ist aber eine JavaFX-Klasse, womit die Logik nun fix an FX gebunden ist. Die Trennung davon wäre dann Punkt 4 :)

absolut nicht klar. Wenn Du dazu noch etwas im Detail schreiben könntest wäre das sehr hilfreich.
Naja, ich habe einen etwas anderen Ansatz als @Robat gewählt, um den Kern des Downloads unabhängig von JavaFX zu halten. Stell Dir vor, Du baust eine Download-Bibliothek. Die kannst Du dann für FX, Swing, ggf. Android, ... verwenden.

Im Wesentlichen besteht der Download aus der Methode, die Du geschrieben hast. Lediglich die Statusmeldungen werden nicht am Bildschirm ausgegeben sondern an Beobachter (das wäre dann die registrierten DownloadListener) verteilt.

Diese Entkopplung erkaufst Du Dir mit erhöhter Komplexität gegenüber der Lösung, den Code direkt in den FX-Task einzubauen.

Wenn Du dazu Code sehen willst, kann ich Dir kurzfristig was zusammenschustern.

Ich starte innerhalb des Service Task den Download, in etwa stelle ich mir das so vor ...
Fast. Du brauchst keinen neuen Thread erzeugen, da der FX-Task bereits in einem separaten Thread ausgeführt wird. Sprich: einfach direkt die run()-Methode aufrufen.
 
Hallo mihe7,

Wenn Du dazu Code sehen willst, kann ich Dir kurzfristig was zusammenschustern.
da ich das Ganze als Übung für mich sehe um den Umgang mit Files, JavaFX und grundsätzliche Java Themen mache, wäre es echt super wenn Du dazu etwas schreiben könntest was mir auf die Sprünge hilft :) um diesen Weg zu kodieren.

Ich werde auch den reinen JavaFX Weg von @Robat probieren, dann habe ich mehrere Wege und Beispiele ;). Wie von Dir geschrieben hatte ich, bevor ich @Robat Kommentar gelesen habe, die Download Klasse erstellt. Hier mal der Code als Referenz, wie gesagt noch vollkommen ohne Listener, etc.

Java:
package de.ralfb_web.services;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;

import de.ralfb_web.model.Model;
import de.ralfb_web.utils.ExceptionListener;

public class DownloadWorker implements Runnable {

    private String link;
    private File downloadFolderFile;
    private File outputFile;
    private String saveFileName;
    private Model model;
    private Boolean useProxy;
    private String proxyServer;
    private int proxyPort;
    private ExceptionListener exceptionListener;
    private Proxy proxy;
    private HttpURLConnection hConnection;

    public DownloadWorker(String link, File downloadFolderFile, Model model, Boolean useProxy, String proxyServer,
            int proxyPort) {
        super();
        this.link = link;
        this.downloadFolderFile = downloadFolderFile;
        this.model = model;
        this.useProxy = useProxy;
        this.proxyServer = proxyServer;
        this.proxyPort = proxyPort;
        this.saveFileName = link.substring(link.lastIndexOf('/') + 1);

        if (!downloadFolderFile.exists()) {
            downloadFolderFile.mkdirs();
        }

        this.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyServer, proxyPort));

    }

    public DownloadWorker(String link, File downloadFolderFile, String saveFileName, Model model, Boolean useProxy) {
        super();
        this.link = link;
        this.downloadFolderFile = downloadFolderFile;
        this.saveFileName = saveFileName;
        this.model = model;
        this.useProxy = useProxy;
        this.saveFileName = link.substring(link.lastIndexOf('/') + 1);

        if (!downloadFolderFile.exists()) {
            downloadFolderFile.mkdirs();
        }
    }

    /**
     * Method to register an ExceptionListener
     * 
     * @param l ExceptionListener
     */
    public void registerExceptionListener(ExceptionListener l) {
        exceptionListener = l;
    }

    /**
     * Method to remove (set to null) an exception listener
     */
    public void removeExceptionListener() {
        exceptionListener = null;
    }

    /**
     * Method to fire an exception using exceptionOccured() method
     * 
     * @param th Throwable
     */
    private void fireException(Throwable th) {
        if (exceptionListener != null) {
            exceptionListener.exceptionOccurred(th);
        }
    }

    @Override
    public void run() {
        try {
            URL url = new URL(link);
            if (useProxy) {
                hConnection = (HttpURLConnection) url.openConnection(proxy);
            } else {
                hConnection = (HttpURLConnection) url.openConnection();
            }

            // Get size of file (int Bytes) to be downloaded ...
            int fileSize = getFileSize(url);

            // Inputstream always works with Byte
            BufferedInputStream bufferedInputStream = new BufferedInputStream(hConnection.getInputStream());

            // Write File
            outputFile = new File(downloadFolderFile, saveFileName);
            OutputStream outputStream = new FileOutputStream(outputFile);
            BufferedOutputStream bOutputStream = new BufferedOutputStream(outputStream, 1024);

            byte[] buffer = new byte[1024];
            // How many bytes are already downloaded
            int downloaded = 0;
            // Actual read bytes
            int readByte = 0;

            while ((readByte = bufferedInputStream.read(buffer, 0, 1024)) >= 0) {
                bOutputStream.write(buffer, 0, readByte);
                downloaded += readByte;

                System.out.println("Bereits " + downloaded + " Byte " + " von " + fileSize + " Bytes geladen");
            }

            bOutputStream.close();
            bufferedInputStream.close();
            System.out.println("Download erfolgreich");

        } catch (Exception ex) {
            fireException(ex);
        }
    }

    public int getFileSize(URL url) {
        URLConnection conn = null;
        try {
            if (useProxy) {
                conn = url.openConnection(proxy);
            } else {
                conn = url.openConnection();
            }

            if (conn instanceof HttpURLConnection) {
                ((HttpURLConnection) conn).setRequestMethod("HEAD");
            }
            conn.getInputStream();
            return conn.getContentLength();
        } catch (Exception ex) {
            fireException(ex);
            return -1;
        } finally {
            if (conn instanceof HttpURLConnection) {
                ((HttpURLConnection) conn).disconnect();
            }
        }

    }

}
Aufgerufen habe ich das dann testweise mal in dem ServiceWorkerTask:

Java:
DownloadWorker dw = new DownloadWorker(sourceUrlTextField.getText(), downloadFolderFile, model, httpProxyCheckBox.isSelected(), proxyServerTextField.getText(), Integer.valueOf(proxyPortTextField.getText()));
dw.run();
Info: Die Ausgaben habe ich in der DownloadWorker Klasse zur Kontrolle auf sysout belassen.

Gruß,

Ralf
 
wie gesagt noch vollkommen ohne Listener, etc.
Du hast ja schon einen ExceptionListener :) wenn Du das noch als Liste implementierst, dann kannst Du mehrere registrieren (ist aber nicht unbedingt notwendig).

Java:
public interface DownloadListener {
    void stateChanged(DownloadEvent e);
}
Java:
import java.net.URL;
import java.util.Optional;

public class DownloadEvent {
    public enum Type {START,RECEIVED,FINISHED,FAILED};

    private final Type type;
    private final URL url;
    private final long total;
    private final long received;
    private final long duration;
    private final Exception cause;

    public DownloadEvent(Type type, URL url, long total, long received, long duration,
                         Exception cause) {
        this.type = type;
        this.url = url;
        this.total = total;
        this.received = received;
        this.duration = duration;
        this.cause = cause;
    }

    public DownloadEvent(URL url, Exception cause) {
        this(Type.FAILED, url, -1L, -1L, -1L, cause);
    }

  
    public DownloadEvent(Type type, URL url, long total, long received, long duration) {
        this(type, url, total, received, duration, null);
    }

    public DownloadEvent(URL url, long total, long received, long duration) {
        this(Type.RECEIVED, url, total, received, duration, null);
    }

    public Type getType() { return type; }
    public URL getURL() { return url; }
    public long getTotal() { return total; }
    public long getReceived() { return received; }
    public long getDuration() { return duration; }
    public Optional<Exception> getCause() { return Optional.ofNullable(cause); }
}

Java:
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Download implements Runnable {
    private final List<DownloadListener> listeners = new ArrayList<>();
    private final URL url;

    public Download(URL url) {
        this.url = url;
    }

    public void addDownloadListener(DownloadListener l) {
        listeners.add(l);
    }

    public void removeDownloadListener(DownloadListener l) {
        listeners.remove(l);
    }

    private void fire(DownloadEvent e) {
        listeners.forEach(l -> l.stateChanged(e));
    }

    @Override
    public void run() {
        Random rand = new Random();
        long total = rand.nextInt(10_000_000 - 1_000_000) + 1_000_000;
        long recvd = 0L;
        long start = System.currentTimeMillis();
        long duration = 0L;
        try {
            while (recvd < total) {
                Thread.sleep(500);
                long bytes = rand.nextInt(1_000_000);
                duration = System.currentTimeMillis() - start;
                recvd = Math.min(recvd + bytes, total);
                fire(new DownloadEvent(url, total, recvd, duration));
            }
            fire(new DownloadEvent(DownloadEvent.Type.FINISHED, url, total, recvd, duration));
        } catch (Exception ex) {
            fire(new DownloadEvent(url, ex));
        }
    }
}

Anbindung an die "Konsole":
Java:
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class TestDownload {
     public static void main(String[] args) throws Exception {
         Download download = new Download(new java.net.URL("http://www.google.de"));
         download.addDownloadListener(e -> {
             long total = e.getTotal();
             long received = e.getReceived();
             long duration = e.getDuration();

             switch(e.getType()) {
                 case FINISHED:
                     System.out.printf("Durchsatz: %d KB/s\n", total / duration);
                     break;
                 case RECEIVED:
                     System.out.printf("%d %% erledigt, %d von %d Bytes empfangen\n",
                                       received*100/total, received, total);
                     break;
                 case FAILED:
                     System.out.printf("Fehlgeschlagen");
                     e.getCause().ifPresent(Exception::printStackTrace);
                     break;
                 default:
                     System.out.println("Ereignis ignoriert");
                     break;
             }
        });

        // Start in eigenem Thread
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.submit(download);
        service.shutdown();

        // Alternativ: start in laufendem Thread
        // download.run();
     }
}
Das wäre eine Möglichkeit. Schöner (weil man ohne switch auskommt und trotzdem ein funktionales Interfaces hat) wäre es, wenn man die Registrierung abhängig vom Event-Type macht... Dazu könnte man eine Map<DownloadEvent.Type, List<DownloadListener>> statt einer popligen Liste verwenden.

Der Einbau in Deinen FX-Task funktioniert genauso.
Java:
    @Override
    protected File call() throws Exception {
        Download download = new Download(link);
        download.addDownloadListener(this::handleDownloadEvent);
        download.run(); // läuft im FX-Hintergrundthread
    }

    private void handleDownloadEvent(DownloadEvent e) {
         long total = e.getTotal();
         long received = e.getReceived();
         long duration = e.getDuration();

         switch(e.getType()) {
             case FINISHED:
                 Platform.runLater(() -> updateMessage(String.format("Download beendet. Durchsatz %d KB/s", total / duration)));
                 break;
             case RECEIVED:
                 Platform.runLater(() -> updateMessage(String.format("Download: %d / %d Byte ", received, total)));
                 break;
             case FAILED:
                 Platform.runLater(() -> updateMessage("Beim Download ist ein Fehler aufgetreten"));
                 e.getCause().ifPresent(Exception::printStackTrace);
                 break;
             default:
                 break;
         }
    }
 
Jetzt hast du meine Lösung angesprochen .. da muss ich sie ja doch wieder posten. Menno! :( :p
Java:
public interface Download {
    void addDownloadListener(DownloadListener listener);
    void removeDownloadListener(DownloadListener listener);
    File execute();
}
public interface DownloadListener {
    void onUpdateProgress(int progress, int total);
    void onUpdateMessage(String message);
    void onSucceed();
    void doCancel();
    void onFail(Exception e);
}
Java:
public class HttpDownload implements Download {

    private List<DownloadListener> listeners;
    private String link;

    public HttpDownload( String link ) {
        this.listeners = new ArrayList<>();
        this.link = link;
    }

    public File execute() {
        File outputFile = null;
        try {
            URL url = new URL(link);
            HttpURLConnection hConnection = ( HttpURLConnection ) url.openConnection();

            listeners.forEach(l -> l.onUpdateMessage("Ermittle Dateigröße..."));
            int fileSize = getFileSize();

            listeners.forEach(l -> {
                l.onUpdateProgress(0, fileSize);
                l.onUpdateMessage(String.format("Download: %d / %d Byte ", 0, fileSize));
            });
            BufferedInputStream bufferedInputStream = new BufferedInputStream(hConnection.getInputStream());


            outputFile = new File("DownloadFile.data");
            OutputStream outputStream = new FileOutputStream(outputFile);
            BufferedOutputStream bOutputStream = new BufferedOutputStream(outputStream, 1024);

            byte[] buffer = new byte[ 1024 ];
            int downloaded = 0;
            int readByte = 0;

            while ( ( readByte = bufferedInputStream.read(buffer, 0, 1024) ) >= 0 ) {
                bOutputStream.write(buffer, 0, readByte);
                downloaded += readByte;

                final int progress = downloaded;
                listeners.forEach(l -> {
                    l.onUpdateProgress( progress, fileSize);
                    l.onUpdateMessage(String.format("Download: %d / %d Byte ", progress, fileSize));
                });
            }

            bOutputStream.close();
            bufferedInputStream.close();
        } catch ( Exception e ) {
            listeners.forEach(l -> {
                l.onUpdateMessage("Beim Download ist ein Fehler aufgetreten");
                l.onFail(e);
            });
        } return outputFile;
    }


    private int getFileSize() {
        URLConnection conn = null;
        try {
            URL url = new URL(link);
            conn = url.openConnection();

            if ( conn instanceof HttpURLConnection ) {
                ( ( HttpURLConnection ) conn ).setRequestMethod("HEAD");
            }

            conn.getInputStream();

            return conn.getContentLength();
        } catch ( Exception e ) {
            listeners.forEach(l -> {
                l.onUpdateMessage("Fehler bei der Ermittlung der Dateigröße!");
                l.onFail(e);
            });
            return -1;
        } finally {
            if ( conn instanceof HttpURLConnection ) {
                ( ( HttpURLConnection ) conn ).disconnect();
            }
        }
    }

    @Override
    public void addDownloadListener( DownloadListener listener ) {
        this.listeners.add(listener);
    }

    @Override
    public void removeDownloadListener( DownloadListener listener ) {
        this.listeners.remove(listener);
    }
}
Java:
public class HttpDownloadTask extends Task<File> implements DownloadListener {

    private Download downloadImpl;

    public HttpDownloadTask( Download downloadImpl)  {
        this.downloadImpl = downloadImpl;
    }

    @Override
    protected File call() throws Exception {
        downloadImpl.addDownloadListener(this);
        return downloadImpl.execute();
    }

    @Override
    public void onUpdateProgress( int progress, int total ) {
        updateProgress(progress, total);
    }

    @Override
    public void onUpdateMessage( String message ) {
        updateMessage(message);
    }

    @Override
    public void onSucceed() {
        succeeded();
    }

    @Override
    public void doCancel() {
        cancel();
    }

    @Override
    public void onFail( Exception e ) {
        updateMessage(e.getMessage());
        failed();
    }
}
Und benutzen dann eben so
Java:
HttpDownloadTask downloadTask = new HttpDownloadTask(new HttpDownload("https://speed.hetzner.de/100MB.bin"));
 
Quatsch das war doch nur hingeklatscht .. da geht bestimmt noch mehr! :)
Aber immerhin bin ich dann heute mal nicht Schuld! *_*
 
Ich muss zugeben ich bin wie immer "platt" oder besser "überwältigt" was den o.g. Code angeht o_O allerdings im absolut positiven Sinn, auch wenn ich jetzt Tage benötige um mir das im detail anzusehen, zu verstehen(!!) und dann bei mir zu implemntieren. Doch diese Zeit investiere ich sehr gerne denn es steckt so viel Know-How dort drin und das versuche ich gerne aufzunehmen - Vielen Dank Euch beiden für die Ausführungen.

Etwas Off-Topic, Ihr verwendet oben diese Spoiler um "lange" Code Passage einzuklappen, was mir sehr gut gefällt. Geht das über:
1. Einfügen Spoiler
2. Innerhalb der Spoiler Tags einfügen Code ?

Gruß

Ralf
 
Passende Stellenanzeigen aus deiner Region:

Neue Themen

Oben