--- /dev/null
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.net;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+
+import com.threerings.getdown.data.Resource;
+import com.threerings.getdown.util.ConnectionUtil;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Implements downloading files over HTTP
+ */
+public class HTTPDownloader extends Downloader
+{
+ public HTTPDownloader (Proxy proxy)
+ {
+ _proxy = proxy;
+ }
+
+ @Override protected long checkSize (Resource rsrc) throws IOException
+ {
+ URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
+ try {
+ // if we're accessing our data via HTTP, we only need a HEAD request
+ if (conn instanceof HttpURLConnection) {
+ HttpURLConnection hcon = (HttpURLConnection)conn;
+ hcon.setRequestMethod("HEAD");
+ hcon.connect();
+ // make sure we got a satisfactory response code
+ if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ throw new IOException("Unable to check up-to-date for " +
+ rsrc.getRemote() + ": " + hcon.getResponseCode());
+ }
+ }
+ return conn.getContentLength();
+
+ } finally {
+ // let it be known that we're done with this connection
+ conn.getInputStream().close();
+ }
+ }
+
+ @Override protected void download (Resource rsrc) throws IOException
+ {
+ // TODO: make FileChannel download impl (below) robust and allow apps to opt-into it via a
+ // system property
+ if (true) {
+ // download the resource from the specified URL
+ URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
+ conn.connect();
+
+ // make sure we got a satisfactory response code
+ if (conn instanceof HttpURLConnection) {
+ HttpURLConnection hcon = (HttpURLConnection)conn;
+ if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ throw new IOException("Unable to download resource " + rsrc.getRemote() + ": " +
+ hcon.getResponseCode());
+ }
+ }
+ long actualSize = conn.getContentLength();
+ log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize);
+ long currentSize = 0L;
+ byte[] buffer = new byte[4*4096];
+ try (InputStream in = conn.getInputStream();
+ FileOutputStream out = new FileOutputStream(rsrc.getLocalNew())) {
+
+ // TODO: look to see if we have a download info file
+ // containing info on potentially partially downloaded data;
+ // if so, use a "Range: bytes=HAVE-" header.
+
+ // read in the file data
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ // abort the download if the downloader is aborted
+ if (_state == State.ABORTED) {
+ break;
+ }
+ // write it out to our local copy
+ out.write(buffer, 0, read);
+ // note that we've downloaded some data
+ currentSize += read;
+ reportProgress(rsrc, currentSize, actualSize);
+ }
+ }
+
+ } else {
+ log.info("Downloading resource", "url", rsrc.getRemote(), "size", "unknown");
+ File localNew = rsrc.getLocalNew();
+ try (ReadableByteChannel rbc = Channels.newChannel(rsrc.getRemote().openStream());
+ FileOutputStream fos = new FileOutputStream(localNew)) {
+ // TODO: more work is needed here, transferFrom can fail to transfer the entire
+ // file, in which case it's not clear what we're supposed to do.. call it again?
+ // will it repeatedly fail?
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ reportProgress(rsrc, localNew.length(), localNew.length());
+ }
+ }
+ }
+
+ protected final Proxy _proxy;
+}