Java tutorial
/* * Copyright 2012 Brendan McCarthy (brendan@oddsoftware.net) * * This file is part of Feedscribe. * * Feedscribe is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 * as published by the Free Software Foundation. * * Feedscribe 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 Feedscribe. If not, see <http://www.gnu.org/licenses/>. */ package net.oddsoftware.android.feedscribe.data; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.ProxySelector; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.ProxySelectorRoutePlanner; import android.content.Context; import android.media.MediaPlayer; import android.net.Uri; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import net.oddsoftware.android.feedscribe.Globals; import net.oddsoftware.android.utils.MediaScan; public class Downloader extends Object { private Context mContext; ExecutorService mThreadPool; Thread mAwakeChecker; long mAwakeInterval = 5000; int mDownloadCount; FeedManager mFeedManager; WakeLock mWakeLock; // TODO - should check connection time private static Downloader mInstance = null; public static synchronized Downloader getInstance(Context ctx) { if (mInstance == null) { mInstance = new Downloader(ctx); } return mInstance; } private Downloader(Context ctx) { mContext = ctx; mThreadPool = Executors.newFixedThreadPool(4); mFeedManager = FeedManager.getInstance(mContext); mDownloadCount = 0; mWakeLock = ((PowerManager) ctx.getSystemService(Context.POWER_SERVICE)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "FeedScribe.Downloader"); mWakeLock.setReferenceCounted(false); mAwakeChecker = new Thread() { @Override public void run() { try { while (true) { Thread.sleep(mAwakeInterval); checkAwake(); } } catch (InterruptedException exc) { return; } } }; mAwakeChecker.start(); } private void checkAwake() { processDownloads(); Globals.LOG.v("Downloader.checkAwake number of downloads: " + mDownloadCount); if (mDownloadCount > 0) { mWakeLock.acquire(mAwakeInterval * 5); // timed here to release it if something goes horribly wrong Globals.LOG.v("Downloader.checkAwake aquiring wake lock: " + mWakeLock.isHeld()); } else { mWakeLock.release(); Globals.LOG.v("Downloader.checkAwake releasing wake lock: " + mWakeLock.isHeld()); } } public void processDownloads() { ArrayList<Download> downloads = mFeedManager.getDownloads(); int count = 0; for (Download download : downloads) { if (!download.mEnqueued) { download.mEnqueued = true; Enclosure enclosure = mFeedManager.getEnclosure(download.mEnclosureId); if (enclosure != null) { mThreadPool.execute(new DownloadThread(download, enclosure)); } else { Globals.LOG.w("Downloader.processDownloads - enclosure " + download.mEnclosureId + " is null"); download.mCancelled = true; } } if (!(download.mPaused || download.mCancelled)) { count++; } } mDownloadCount = count; } private class DownloadThread implements Runnable { Download mDownload; Enclosure mEnclosure; public DownloadThread(Download download, Enclosure enclosure) { mDownload = download; mEnclosure = enclosure; } public void run() { if (mDownload.mPaused) { return; } // do download in background performDownload(); // only do one of these at a time synchronized (mFeedManager) { if (mDownload.mCancelled) { boolean deleted = mFeedManager.deleteDownload(mDownload, mEnclosure); Globals.LOG.i("Downloader - deleting cancelled download " + mEnclosure.mDownloadPath + " success " + deleted); } else if (mDownload.mSuccess) { MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mEnclosure.mDownloadPath))); if (mp != null) { mEnclosure.mDuration = mp.getDuration(); mp.release(); } else { Globals.LOG.w("unable to determine duration for '" + mEnclosure.mDownloadPath + "'"); } mFeedManager.downloadComplete(mDownload); } else { // download stopped for some reason .... } mFeedManager.updateEnclosure(mEnclosure); } } private void performDownload() { mDownload.mSuccess = false; if (mEnclosure.mDownloadPath.length() == 0) { // this should be safe in the background thread if (!mFeedManager.createFile(mEnclosure)) { mDownload.mSuccess = false; return; } else { mFeedManager.updateEnclosure(mEnclosure); } } // enclosure file is set, start the download try { mDownload.mInProgress = true; File outputFile = new File(mEnclosure.mDownloadPath); long existingFileSize = outputFile.length(); HttpClient client = createHttpClient(); HttpUriRequest request = createHttpRequest(mEnclosure.mURL, existingFileSize, null, null); HttpResponse response = client.execute(request); StatusLine status = response.getStatusLine(); HttpEntity entity = response.getEntity(); Globals.LOG.d("http request: " + mEnclosure.mURL); Globals.LOG.d("http response code: " + status); if (entity == null) { return; } InputStream is = entity.getContent(); OutputStream os = null; long contentLength = 0; boolean fileComplete = false; // partial content - append file if (status.getStatusCode() == 206) { os = new FileOutputStream(outputFile, true); contentLength = existingFileSize + entity.getContentLength(); } else if (status.getStatusCode() == 200) { os = new FileOutputStream(outputFile); contentLength = entity.getContentLength(); existingFileSize = 0; // reset } else if (status.getStatusCode() == 416) // requested range not satisfiable = file complete { contentLength = 0; fileComplete = true; } else { // abort Globals.LOG.e("Error downloading url: " + mEnclosure.mURL + "response " + status); return; } mDownload.mSize = contentLength; mDownload.mDownloaded = existingFileSize; long size = existingFileSize; byte[] buffer = new byte[4096]; int bytes; boolean stopped = false; try { while ((bytes = is.read(buffer)) != -1 && !stopped && !fileComplete) { size += bytes; mDownload.mDownloaded = size; if (size > mDownload.mSize) { mDownload.mSize = size; } os.write(buffer, 0, bytes); stopped = mDownload.mPaused || mDownload.mCancelled; } } finally { // close streams even if there was an exception is.close(); if (os != null) { os.close(); } } if (!stopped) { mEnclosure.mLength = size; mEnclosure.mDownloadTime = new Date().getTime(); MediaScan.ScanFile(mContext, mEnclosure.mDownloadPath, mEnclosure.mContentType); mDownload.mSuccess = true; } mDownload.mInProgress = false; return; } catch (MalformedURLException exc) { Globals.LOG.e("error parsing download url", exc); mDownload.mSuccess = false; } catch (IOException exc) { Globals.LOG.e("error downloading enclosure", exc); // if this was the only error, reschedule for downloading mDownload.mSuccess = false; mDownload.mEnqueued = false; } } } private HttpClient createHttpClient() { // use apache http client lib to set parameters from feedStatus DefaultHttpClient client = new DefaultHttpClient(); // set up proxy handler ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner( client.getConnectionManager().getSchemeRegistry(), ProxySelector.getDefault()); client.setRoutePlanner(routePlanner); return client; } private HttpUriRequest createHttpRequest(String url, long resumeFrom, String eTag, Date lastModified) { HttpGet request = new HttpGet(url); request.setHeader("User-Agent", FeedManager.USER_AGENT); if (resumeFrom > 0) { request.setHeader("Range", "bytes=" + resumeFrom + "-"); } // send etag if we have it if (eTag != null && eTag.length() > 0) { request.setHeader("If-None-Match", eTag); } // send If-Modified-Since if we have it if (lastModified != null && lastModified.getTime() > 0) { SimpleDateFormat dateFormat = new SimpleDateFormat("EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' GMT'", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); String formattedTime = dateFormat.format(lastModified); // If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT request.setHeader("If-Modified-Since", formattedTime); } return request; } }