class DownloadVideoThread extends Thread {

    private static final Logger logger = Logger.getLogger(DownloadVideoThread.class);

    private URL url;
    private File saveAs;
    private String prettySaveAs;
    private AbstractRipper observer;
    private int retries;

    public DownloadVideoThread(URL url, File saveAs, AbstractRipper observer) {
        super();
        this.url = url;
        this.saveAs = saveAs;
        this.prettySaveAs = Utils.removeCWD(saveAs);
        this.observer = observer;
        this.retries = Utils.getConfigInteger("download.retries", 1);
    }

    /**
     * Attempts to download the file. Retries as needed.
     * Notifies observers upon completion/error/warn.
     */
    public void run() {<FILL_FUNCTION_BODY>}

    /**
     * @param url
     *      Target URL
     * @return 
     *      Returns connection length
     */
    private int getTotalBytes(URL url) throws IOException {
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("HEAD");
        conn.setRequestProperty("accept",  "*/*");
        conn.setRequestProperty("Referer", this.url.toExternalForm()); // Sic
        conn.setRequestProperty("User-agent", AbstractRipper.USER_AGENT);
        return conn.getContentLength();
    }

}

        try {
            observer.stopCheck();
        } catch (IOException e) {
            observer.downloadErrored(url, "Download interrupted");
            return;
        }
        if (saveAs.exists()) {
            if (Utils.getConfigBoolean("file.overwrite", false)) {
                logger.info("[!] Deleting existing file" + prettySaveAs);
                saveAs.delete();
            } else {
                logger.info("[!] Skipping " + url + " -- file already exists: " + prettySaveAs);
                observer.downloadExists(url, saveAs);
                return;
            }
        }

        int bytesTotal, bytesDownloaded = 0;
        try {
            bytesTotal = getTotalBytes(this.url);
        } catch (IOException e) {
            logger.error("Failed to get file size at " + this.url, e);
            observer.downloadErrored(this.url, "Failed to get file size of " + this.url);
            return;
        }
        observer.setBytesTotal(bytesTotal);
        observer.sendUpdate(STATUS.TOTAL_BYTES, bytesTotal);
        logger.debug("Size of file at " + this.url + " = " + bytesTotal + "b");

        int tries = 0; // Number of attempts to download
        do {
            InputStream bis = null; OutputStream fos = null;
            byte[] data = new byte[1024 * 256];
            int bytesRead;
            try {
                logger.info("    Downloading file: " + url + (tries > 0 ? " Retry #" + tries : ""));
                observer.sendUpdate(STATUS.DOWNLOAD_STARTED, url.toExternalForm());

                // Setup HTTP request
                HttpURLConnection huc;
                if (this.url.toString().startsWith("https")) {
                    huc = (HttpsURLConnection) this.url.openConnection();
                }
                else {
                    huc = (HttpURLConnection) this.url.openConnection();
                }
                huc.setInstanceFollowRedirects(true);
                huc.setConnectTimeout(0); // Never timeout
                huc.setRequestProperty("accept",  "*/*");
                huc.setRequestProperty("Referer", this.url.toExternalForm()); // Sic
                huc.setRequestProperty("User-agent", AbstractRipper.USER_AGENT);
                tries += 1;
                logger.debug("Request properties: " + huc.getRequestProperties().toString());
                huc.connect();
                // Check status code
                bis = new BufferedInputStream(huc.getInputStream());
                fos = new FileOutputStream(saveAs);
                while ( (bytesRead = bis.read(data)) != -1) {
                    try {
                        observer.stopCheck();
                    } catch (IOException e) {
                        observer.downloadErrored(url, "Download interrupted");
                        return;
                    }
                    fos.write(data, 0, bytesRead);
                    bytesDownloaded += bytesRead;
                    observer.setBytesCompleted(bytesDownloaded);
                    observer.sendUpdate(STATUS.COMPLETED_BYTES, bytesDownloaded);
                }
                bis.close();
                fos.close();
                break; // Download successful: break out of infinite loop
            } catch (IOException e) {
                logger.error("[!] Exception while downloading file: " + url + " - " + e.getMessage(), e);
            } finally {
                // Close any open streams
                try {
                    if (bis != null) { bis.close(); }
                } catch (IOException e) { }
                try {
                    if (fos != null) { fos.close(); }
                } catch (IOException e) { }
            }
            if (tries > this.retries) {
                logger.error("[!] Exceeded maximum retries (" + this.retries + ") for URL " + url);
                observer.downloadErrored(url, "Failed to download " + url.toExternalForm());
                return;
            }
        } while (true);
        observer.downloadCompleted(url, saveAs);
        logger.info("[+] Saved " + url + " as " + this.prettySaveAs);
    
<methods>public void <init>() ,public void <init>(java.lang.Runnable) ,public void <init>(java.lang.String) ,public void <init>(java.lang.ThreadGroup, java.lang.Runnable) ,public void <init>(java.lang.ThreadGroup, java.lang.String) ,public void <init>(java.lang.Runnable, java.lang.String) ,public void <init>(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String) ,public void <init>(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long) ,public void <init>(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, boolean) ,public static int activeCount() ,public final void checkAccess() ,public int countStackFrames() ,public static native java.lang.Thread currentThread() ,public static void dumpStack() ,public static int enumerate(java.lang.Thread[]) ,public static Map<java.lang.Thread,java.lang.StackTraceElement[]> getAllStackTraces() ,public java.lang.ClassLoader getContextClassLoader() ,public static java.lang.Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() ,public long getId() ,public final java.lang.String getName() ,public final int getPriority() ,public java.lang.StackTraceElement[] getStackTrace() ,public java.lang.Thread.State getState() ,public final java.lang.ThreadGroup getThreadGroup() ,public java.lang.Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() ,public static native boolean holdsLock(java.lang.Object) ,public void interrupt() ,public static boolean interrupted() ,public final boolean isAlive() ,public final boolean isDaemon() ,public boolean isInterrupted() ,public final void join() throws java.lang.InterruptedException,public final synchronized void join(long) throws java.lang.InterruptedException,public final synchronized void join(long, int) throws java.lang.InterruptedException,public static void onSpinWait() ,public final void resume() ,public void run() ,public void setContextClassLoader(java.lang.ClassLoader) ,public final void setDaemon(boolean) ,public static void setDefaultUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler) ,public final synchronized void setName(java.lang.String) ,public final void setPriority(int) ,public void setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler) ,public static native void sleep(long) throws java.lang.InterruptedException,public static void sleep(long, int) throws java.lang.InterruptedException,public synchronized void start() ,public final void stop() ,public final void suspend() ,public java.lang.String toString() ,public static native void yield() <variables>private static final java.lang.StackTraceElement[] EMPTY_STACK_TRACE,public static final int MAX_PRIORITY,public static final int MIN_PRIORITY,public static final int NORM_PRIORITY,private volatile sun.nio.ch.Interruptible blocker,private final java.lang.Object blockerLock,private java.lang.ClassLoader contextClassLoader,private boolean daemon,private static volatile java.lang.Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler,private volatile long eetop,private java.lang.ThreadGroup group,java.lang.ThreadLocal.ThreadLocalMap inheritableThreadLocals,private java.security.AccessControlContext inheritedAccessControlContext,private volatile boolean interrupted,private volatile java.lang.String name,volatile java.lang.Object parkBlocker,private int priority,private final long stackSize,private boolean stillborn,private java.lang.Runnable target,private static int threadInitNumber,int threadLocalRandomProbe,int threadLocalRandomSecondarySeed,long threadLocalRandomSeed,java.lang.ThreadLocal.ThreadLocalMap threadLocals,private static long threadSeqNumber,private volatile int threadStatus,private final long tid,private volatile java.lang.Thread.UncaughtExceptionHandler uncaughtExceptionHandler