/* * MediathekView * Copyright (C) 2014 W. Xaver * W.Xaver[at]googlemail.com * http://zdfmediathk.sourceforge.net/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package mediathek.controller.starter; import mSearch.tool.Listener; import mSearch.tool.Log; import mSearch.tool.SysMsg; import mediathek.config.Daten; import mediathek.config.MVConfig; import mediathek.controller.MVBandwidthTokenBucket; import mediathek.controller.MVInputStream; import mediathek.daten.DatenDownload; import mediathek.gui.dialog.DialogContinueDownload; import mediathek.gui.dialog.MeldungDownloadfehler; import mediathek.tool.MVInfoFile; import mediathek.tool.MVSubtitle; import javax.swing.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import static mediathek.controller.starter.StarterClass.*; public class DirectHttpDownload extends Thread { private final Daten daten; private final DatenDownload datenDownload; private final Start start; private HttpURLConnection conn = null; private HttpDownloadState state = HttpDownloadState.DOWNLOAD; private long downloaded = 0; private File file = null; private String responseCode; private String exMessage; private FileOutputStream fos = null; private final java.util.Timer bandwidthCalculationTimer; private boolean retAbbrechen; private boolean dialogAbbrechenIsVis; enum HttpDownloadState { CANCEL, ERROR, DOWNLOAD } public DirectHttpDownload(Daten daten, DatenDownload d, java.util.Timer bandwidthCalculationTimer) { super(); this.daten = daten; this.bandwidthCalculationTimer = bandwidthCalculationTimer; datenDownload = d; start = datenDownload.start; setName("DIRECT DL THREAD_" + d.arr[DatenDownload.DOWNLOAD_TITEL]); start.status = Start.STATUS_RUN; notifyStartEvent(datenDownload); } /** * Return the content length of the requested Url. * * @param url {@link java.net.URL} to the specified content. * @return Length in bytes or -1 on error. */ private long getContentLength(final URL url) { final int TIMEOUT_LENGTH = 5000; //ms, beim Start eines Downloads long ret = -1; HttpURLConnection connection = null; try { connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("User-Agent", Daten.getUserAgent()); connection.setReadTimeout(TIMEOUT_LENGTH); connection.setConnectTimeout(TIMEOUT_LENGTH); if (connection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) { ret = connection.getContentLengthLong(); } // alles unter 300k sind Playlisten, ... if (ret < 300 * 1000) { ret = -1; } } catch (Exception ex) { ret = -1; Log.errorLog(643298301, ex); } finally { if (connection != null) { connection.disconnect(); } } return ret; } /** * Setup the HTTP connection common settings * * @param conn The active connection. */ private void setupHttpConnection(HttpURLConnection conn) { conn.setRequestProperty("Range", "bytes=" + downloaded + '-'); conn.setRequestProperty("User-Agent", Daten.getUserAgent()); conn.setDoInput(true); conn.setDoOutput(true); } /** * Start the actual download process here. * * @throws Exception */ private void downloadContent() throws Exception { if (Boolean.parseBoolean(datenDownload.arr[DatenDownload.DOWNLOAD_INFODATEI])) { MVInfoFile.writeInfoFile(datenDownload); } if (Boolean.parseBoolean(datenDownload.arr[DatenDownload.DOWNLOAD_SUBTITLE])) { MVSubtitle.writeSubtitle(datenDownload); } datenDownload.interruptRestart(); start.mVInputStream = new MVInputStream(conn.getInputStream(), bandwidthCalculationTimer); fos = new FileOutputStream(file, (downloaded != 0)); datenDownload.mVFilmSize.addAktSize(downloaded); final byte[] buffer = new byte[MVBandwidthTokenBucket.DEFAULT_BUFFER_SIZE]; long p, pp = 0, startProzent = -1; int len; long aktBandwidth, aktSize = 0; boolean melden = false; while ((len = start.mVInputStream.read(buffer)) != -1 && (!start.stoppen)) { downloaded += len; fos.write(buffer, 0, len); datenDownload.mVFilmSize.addAktSize(len); //für die Anzeige prüfen ob sich was geändert hat if (aktSize != datenDownload.mVFilmSize.getAktSize()) { aktSize = datenDownload.mVFilmSize.getAktSize(); melden = true; } if (datenDownload.mVFilmSize.getSize() > 0) { p = (aktSize * (long) 1000) / datenDownload.mVFilmSize.getSize(); if (startProzent == -1) { startProzent = p; } // p muss zwischen 1 und 999 liegen if (p == 0) { p = Start.PROGRESS_GESTARTET; } else if (p >= 1000) { p = 999; } start.percent = (int) p; if (p != pp) { pp = p; // Restzeit ermitteln if (p > 2 && p > startProzent) { // sonst macht es noch keinen Sinn int diffZeit = start.startZeit.diffInSekunden(); int restProzent = 1000 - (int) p; start.restSekunden = (diffZeit * restProzent / (p - startProzent)); // anfangen zum Schauen kann man, wenn die Restzeit kürzer ist // als die bereits geladene Speilzeit des Films bereitsAnschauen(datenDownload); } melden = true; } } aktBandwidth = start.mVInputStream.getBandwidth(); // bytes per second if (aktBandwidth != start.bandbreite) { start.bandbreite = aktBandwidth; melden = true; } if (melden) { Listener.notify(Listener.EREIGNIS_ART_DOWNLOAD_PROZENT, StarterClass.class.getName()); melden = false; } } start.bandbreite = start.mVInputStream.getSumBandwidth(); if (!start.stoppen) { if (datenDownload.quelle == DatenDownload.QUELLE_BUTTON) { // direkter Start mit dem Button start.status = Start.STATUS_FERTIG; } else if (pruefen(daten, datenDownload, start)) { //Anzeige ändern - fertig start.status = Start.STATUS_FERTIG; } else { //Anzeige ändern - bei Fehler fehlt der Eintrag start.status = Start.STATUS_ERR; } } } @Override public synchronized void run() { startmeldung(datenDownload, start); try { Files.createDirectories(Paths.get(datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD])); } catch (IOException ignored) { } int restartCount = 0; boolean restart = true; while (restart) { restart = false; try { final URL url = new URL(datenDownload.arr[DatenDownload.DOWNLOAD_URL]); file = new File(datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD_DATEINAME]); if (!cancelDownload()) { datenDownload.mVFilmSize.setSize(getContentLength(url)); datenDownload.mVFilmSize.setAktSize(0); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(1000 * MVConfig.getInt(MVConfig.Configs.SYSTEM_PARAMETER_DOWNLOAD_TIMEOUT_SEKUNDEN)); conn.setReadTimeout(1000 * MVConfig.getInt(MVConfig.Configs.SYSTEM_PARAMETER_DOWNLOAD_TIMEOUT_SEKUNDEN)); setupHttpConnection(conn); conn.connect(); final int httpResponseCode = conn.getResponseCode(); if (httpResponseCode >= HttpURLConnection.HTTP_BAD_REQUEST) { //Range passt nicht, also neue Verbindung versuchen... if (httpResponseCode == 416) { conn.disconnect(); //Get a new connection and reset download param... conn = (HttpURLConnection) url.openConnection(); downloaded = 0; setupHttpConnection(conn); conn.connect(); //hier war es dann nun wirklich... state = conn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST ? HttpDownloadState.ERROR : HttpDownloadState.DOWNLOAD; } else { // ================================== // dann wars das responseCode = "Responsecode: " + conn.getResponseCode() + '\n' + conn.getResponseMessage(); Log.errorLog(915236798, "HTTP-Fehler: " + conn.getResponseCode() + ' ' + conn.getResponseMessage()); SwingUtilities.invokeLater(() -> { if (!Daten.isAuto()) { new MeldungDownloadfehler(Daten.getInstance().getMediathekGui(), "URL des Films:\n" + datenDownload.arr[DatenDownload.DOWNLOAD_URL] + "\n\n" + responseCode + '\n', datenDownload).setVisible(true); } }); state = HttpDownloadState.ERROR; } } } switch (state) { case DOWNLOAD: downloadContent(); break; case CANCEL: // start.status = Start.STATUS_RUN; // bei "init" wird er sonst nochmal gestartet break; case ERROR: start.status = Start.STATUS_ERR; break; } } catch (Exception ex) { if ((ex instanceof java.io.IOException || ex instanceof java.net.SocketTimeoutException) && restartCount < MVConfig.getInt(MVConfig.Configs.SYSTEM_PARAMETER_DOWNLOAD_MAX_RESTART_HTTP)) { if (ex instanceof java.net.SocketTimeoutException) { //Timeout Fehlermeldung für zxd :) ArrayList<String> text = new ArrayList<>(); text.add("Timeout, Download Restarts: " + restartCount); text.add("Ziel: " + datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD_DATEINAME]); text.add("URL: " + datenDownload.arr[DatenDownload.DOWNLOAD_URL]); SysMsg.sysMsg(text.toArray(new String[text.size()])); } restartCount++; restart = true; } else { // dann weiß der Geier! exMessage = ex.getLocalizedMessage(); Log.errorLog(316598941, ex, "Fehler"); start.status = Start.STATUS_ERR; SwingUtilities.invokeLater(() -> { if (!Daten.isAuto()) { new MeldungDownloadfehler(Daten.getInstance().getMediathekGui(), exMessage, datenDownload).setVisible(true); } }); } } } try { if (start.mVInputStream != null) { start.mVInputStream.close(); } if (fos != null) { fos.close(); } if (conn != null) { conn.disconnect(); } } catch (Exception ignored) { } StarterClass.finalizeDownload(datenDownload, start, state); } private boolean cancelDownload() { if (!file.exists()) { // dann ist alles OK return false; } if (Daten.isAuto()) { return false; // immer überschreiben, keine GUI!!! } dialogAbbrechenIsVis = true; retAbbrechen = true; if (SwingUtilities.isEventDispatchThread()) { retAbbrechen = abbrechen_(); } else { SwingUtilities.invokeLater(() -> { retAbbrechen = abbrechen_(); dialogAbbrechenIsVis = false; }); } while (dialogAbbrechenIsVis) { try { wait(100); } catch (Exception ignored) { } } return retAbbrechen; } private boolean abbrechen_() { boolean result = false; if (file.exists()) { DialogContinueDownload dialogContinueDownload = new DialogContinueDownload(Daten.getInstance().getMediathekGui(), datenDownload, true /*weiterführen*/); dialogContinueDownload.setVisible(true); switch (dialogContinueDownload.getResult()) { case CANCELLED: // dann wars das state = HttpDownloadState.CANCEL; result = true; break; case CONTINUE: downloaded = file.length(); break; case RESTART_WITH_NEW_NAME: if (dialogContinueDownload.isNewName()) { Listener.notify(Listener.EREIGNIS_LISTE_DOWNLOADS, this.getClass().getSimpleName()); try { Files.createDirectories(Paths.get(datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD])); } catch (IOException ignored) { } file = new File(datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD_DATEINAME]); } break; } } return result; } private void bereitsAnschauen(DatenDownload datenDownload) { if (datenDownload.film != null && datenDownload.start != null) { if (datenDownload.film.dauerL > 0 && datenDownload.start.restSekunden > 0 && datenDownload.mVFilmSize.getAktSize() > 0 && datenDownload.mVFilmSize.getSize() > 0) { // macht nur dann Sinn final long zeitGeladen = datenDownload.film.dauerL * datenDownload.mVFilmSize.getAktSize() / datenDownload.mVFilmSize.getSize(); if (zeitGeladen > (datenDownload.start.restSekunden * 1.1 /* plus 10% zur Sicherheit*/)) { datenDownload.start.beginnAnschauen = true; } } } } }