Rough guess at what ApkDownloader refactor could look like.

This commit is contained in:
Peter Serwylo 2014-05-20 05:34:08 +09:30
parent c4b0eb9b51
commit a44ce0e4e7
5 changed files with 105 additions and 194 deletions

2
.gitignore vendored
View File

@ -6,6 +6,6 @@
/build.xml
*~
/.idea/
/*.iml
*.iml
out
/.settings/

View File

@ -20,37 +20,29 @@
package org.fdroid.fdroid;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.io.*;
import android.util.Log;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.net.HttpDownloader;
public class ApkDownloader extends Thread {
public static final int EVENT_APK_DOWNLOAD_COMPLETE = 100;
public static final int EVENT_ERROR_HASH_MISMATCH = 101;
public static final int EVENT_ERROR_DOWNLOAD_FAILED = 102;
public static final int EVENT_ERROR_UNKNOWN = 103;
private Apk curapk;
private String repoaddress;
private String filename;
private File destdir;
private File localfile;
public static enum Status {
STARTING, RUNNING, ERROR, DONE, CANCELLED
}
private ProgressListener listener;
public static enum Error {
CORRUPT, UNKNOWN
public void setProgressListener(ProgressListener listener) {
this.listener = listener;
}
private Status status = Status.STARTING;
private Error error;
private int progress;
private int max;
private String errorMessage;
// Constructor - creates a Downloader to download the given Apk,
// which must have its detail populated.
ApkDownloader(Apk apk, String repoaddress, File destdir) {
@ -59,50 +51,19 @@ public class ApkDownloader extends Thread {
this.destdir = destdir;
}
public synchronized Status getStatus() {
return status;
}
// Current progress and maximum value for progress dialog
public synchronized int getProgress() {
return progress;
}
public synchronized int getMax() {
return max;
}
// Error code and error message, only valid if status is ERROR
public synchronized Error getErrorType() {
return error;
}
public synchronized String getErrorMessage() {
return errorMessage;
}
// The URL being downloaded or path to a cached file
public synchronized String remoteFile() {
return filename;
}
// The downloaded APK. Valid only when getStatus() has returned STATUS.DONE.
public File localFile() {
return localfile;
}
// The APK being downloaded
public synchronized Apk getApk() {
return curapk;
public String remoteFile() {
return repoaddress + "/" + curapk.apkName.replace(" ", "%20");
}
@Override
public void run() {
localfile = new File(destdir, curapk.apkName);
InputStream input = null;
OutputStream output = null;
String apkname = curapk.apkName;
localfile = new File(destdir, apkname);
try {
// See if we already have this apk cached...
@ -111,12 +72,7 @@ public class ApkDownloader extends Thread {
Hasher hash = new Hasher(curapk.hashType, localfile);
if (hash.match(curapk.hash)) {
Log.d("FDroid", "Using cached apk at " + localfile);
synchronized (this) {
progress = 1;
max = 1;
status = Status.DONE;
return;
}
return;
} else {
Log.d("FDroid", "Not using cached apk at " + localfile);
localfile.delete();
@ -124,72 +80,45 @@ public class ApkDownloader extends Thread {
}
// If we haven't got the apk locally, we'll have to download it...
String remotefile;
remotefile = repoaddress + "/" + apkname.replace(" ", "%20");
Log.d("FDroid", "Downloading apk from " + remotefile);
synchronized (this) {
filename = remotefile;
progress = 0;
max = curapk.size;
status = Status.RUNNING;
}
input = new URL(remotefile).openStream();
output = new FileOutputStream(localfile);
byte data[] = new byte[Utils.BUFFER_SIZE];
while (true) {
if (isInterrupted()) {
Log.d("FDroid", "Download cancelled!");
break;
}
int count = input.read(data);
if (count == -1) {
break;
}
output.write(data, 0, count);
synchronized (this) {
progress += count;
}
}
HttpDownloader downloader = new HttpDownloader(remoteFile(), localfile);
downloader.setProgressListener(listener);
if (isInterrupted()) {
localfile.delete();
synchronized (this) {
status = Status.CANCELLED;
}
Log.d("FDroid", "Downloading apk from " + remoteFile());
int httpStatus = downloader.downloadHttpFile();
if (httpStatus != 200 || !localfile.exists()) {
sendProgress(EVENT_ERROR_DOWNLOAD_FAILED);
return;
}
Hasher hash = new Hasher(curapk.hashType, localfile);
if (!hash.match(curapk.hash)) {
synchronized (this) {
Log.d("FDroid", "Downloaded file hash of " + hash.getHash()
+ " did not match repo's " + curapk.hash);
// No point keeping a bad file, whether we're
// caching or not.
localfile.delete();
error = Error.CORRUPT;
errorMessage = null;
status = Status.ERROR;
return;
}
Log.d("FDroid", "Downloaded file hash of " + hash.getHash()
+ " did not match repo's " + curapk.hash);
// No point keeping a bad file, whether we're
// caching or not.
localfile.delete();
sendProgress(EVENT_ERROR_HASH_MISMATCH);
return;
}
} catch (Exception e) {
Log.e("FDroid", "Download failed:\n" + Log.getStackTraceString(e));
synchronized (this) {
if (localfile.exists()) {
localfile.delete();
error = Error.UNKNOWN;
errorMessage = e.toString();
status = Status.ERROR;
return;
}
} finally {
Utils.closeQuietly(output);
Utils.closeQuietly(input);
sendProgress(EVENT_ERROR_UNKNOWN);
return;
}
Log.d("FDroid", "Download finished: " + localfile);
synchronized (this) {
status = Status.DONE;
sendProgress(EVENT_APK_DOWNLOAD_COMPLETE);
}
private void sendProgress(int type) {
if (listener != null) {
listener.onProgress(new ProgressListener.Event(type));
}
}
}

View File

@ -26,6 +26,7 @@ import org.fdroid.fdroid.data.*;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.installer.Installer.AndroidNotCompatibleException;
import org.fdroid.fdroid.installer.Installer.InstallerCallback;
import org.fdroid.fdroid.net.Downloader;
import org.xml.sax.XMLReader;
import android.app.AlertDialog;
@ -394,10 +395,6 @@ public class AppDetails extends ListActivity {
updateViews();
MenuManager.create(this).invalidateOptionsMenu();
if (downloadHandler != null) {
downloadHandler.startUpdates();
}
}
@Override
@ -405,9 +402,6 @@ public class AppDetails extends ListActivity {
if (myAppObserver != null) {
getContentResolver().unregisterContentObserver(myAppObserver);
}
if (downloadHandler != null) {
downloadHandler.stopUpdates();
}
if (app != null && (app.ignoreAllUpdates != startingIgnoreAll
|| app.ignoreThisUpdate != startingIgnoreThis)) {
setIgnoreUpdates(app.id, app.ignoreAllUpdates, app.ignoreThisUpdate);
@ -1039,7 +1033,7 @@ public class AppDetails extends ListActivity {
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT, app.name);
shareIntent.putExtra(Intent.EXTRA_TEXT, app.name+" ("+app.summary+") - https://f-droid.org/app/"+app.id);
shareIntent.putExtra(Intent.EXTRA_TEXT, app.name + " (" + app.summary + ") - https://f-droid.org/app/" + app.id);
startActivity(Intent.createChooser(shareIntent, getString(R.string.menu_share)));
}
@ -1071,81 +1065,61 @@ public class AppDetails extends ListActivity {
}
// Handler used to update the progress dialog while downloading.
private class DownloadHandler extends Handler {
private class DownloadHandler extends Handler implements ProgressListener {
private ApkDownloader download;
private ProgressDialog pd;
private boolean updating;
private String id;
public DownloadHandler(Apk apk, String repoaddress, File destdir) {
id = apk.id;
download = new ApkDownloader(apk, repoaddress, destdir);
download.start();
startUpdates();
}
public DownloadHandler(DownloadHandler oldHandler) {
if (oldHandler != null) {
download = oldHandler.download;
}
startUpdates();
}
public boolean updateProgress() {
public void onProgress(ProgressListener.Event event) {
boolean finished = false;
switch (download.getStatus()) {
case RUNNING:
if (pd == null) {
pd = createProgressDialog(download.remoteFile(),
download.getProgress(), download.getMax());
} else {
pd.setProgress(download.getProgress());
}
break;
case ERROR:
if (pd != null)
pd.dismiss();
String text;
if (download.getErrorType() == ApkDownloader.Error.CORRUPT)
text = getString(R.string.corrupt_download);
else
text = download.getErrorMessage();
Toast.makeText(AppDetails.this, text, Toast.LENGTH_LONG).show();
finished = true;
break;
case DONE:
if (pd != null)
pd.dismiss();
installApk(download.localFile(), id);
finished = true;
break;
case CANCELLED:
Toast.makeText(AppDetails.this,
getString(R.string.download_cancelled),
Toast.LENGTH_SHORT).show();
finished = true;
break;
default:
break;
}
return finished;
}
switch (event.type) {
case Downloader.EVENT_PROGRESS:
if (pd == null) {
pd = createProgressDialog(download.remoteFile(),
event.progress, event.total);
} else {
pd.setProgress(event.progress);
}
break;
public void startUpdates() {
if (!updating) {
updating = true;
sendEmptyMessage(0);
}
}
case ApkDownloader.EVENT_ERROR_DOWNLOAD_FAILED:
case ApkDownloader.EVENT_ERROR_HASH_MISMATCH:
case ApkDownloader.EVENT_ERROR_UNKNOWN:
String text;
if (event.type == ApkDownloader.EVENT_ERROR_HASH_MISMATCH)
text = getString(R.string.corrupt_download);
else
text = getString(R.string.details_notinstalled);
Toast.makeText(AppDetails.this, text, Toast.LENGTH_LONG).show();
finished = true;
break;
public void stopUpdates() {
updating = false;
removeMessages(0);
case ApkDownloader.EVENT_APK_DOWNLOAD_COMPLETE:
installApk(download.localFile(), id);
finished = true;
break;
}
if (finished) {
destroy();
}
}
public void cancel() {
if (download != null)
download.interrupt();
// TODO: Re-implement...
}
public void destroy() {
@ -1155,24 +1129,10 @@ public class AppDetails extends ListActivity {
pd.dismiss();
pd = null;
}
// Cancel any scheduled updates so that we don't
// accidentally recreate the progress dialog.
stopUpdates();
}
// Repeatedly run updateProgress() until it's finished.
@Override
public void handleMessage(Message msg) {
if (download == null)
return;
boolean finished = updateProgress();
if (finished)
download = null;
else
sendMessageDelayed(obtainMessage(), 50);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// handle cases for install manager first

View File

@ -10,15 +10,28 @@ public abstract class Downloader {
private OutputStream outputStream;
private ProgressListener progressListener = null;
private ProgressListener.Event progressEvent = null;
private final File outputFile;
private File outputFile;
public static final int EVENT_PROGRESS = 1;
public abstract InputStream inputStream() throws IOException;
// The context is required for opening the file to write to.
public Downloader(String destFile, Context ctx)
throws FileNotFoundException, MalformedURLException {
outputStream = ctx.openFileOutput(destFile, Context.MODE_PRIVATE);
outputFile = new File(ctx.getFilesDir() + File.separator + destFile);
this(new File(ctx.getFilesDir() + File.separator + destFile));
}
// The context is required for opening the file to write to.
public Downloader(Context ctx) throws IOException {
this(File.createTempFile("dl-", "", ctx.getCacheDir()));
}
public Downloader(File destFile)
throws FileNotFoundException, MalformedURLException {
// http://developer.android.com/guide/topics/data/data-storage.html#InternalCache
outputFile = destFile;
outputStream = new FileOutputStream(outputFile);
}
/**
@ -26,10 +39,7 @@ public abstract class Downloader {
* you are done*.
* @see org.fdroid.fdroid.net.Downloader#getFile()
*/
public Downloader(Context ctx) throws IOException {
// http://developer.android.com/guide/topics/data/data-storage.html#InternalCache
outputFile = File.createTempFile("dl-", "", ctx.getCacheDir());
outputStream = new FileOutputStream(outputFile);
public Downloader(File destFile, Context ctx) throws IOException {
}
public Downloader(OutputStream output)
@ -38,6 +48,11 @@ public abstract class Downloader {
outputFile = null;
}
public void setProgressListener(ProgressListener listener) {
this.progressListener = listener;
this.progressEvent = new ProgressListener.Event(EVENT_PROGRESS, totalDownloadSize());
}
public void setProgressListener(ProgressListener progressListener,
ProgressListener.Event progressEvent) {
this.progressListener = progressListener;
@ -61,7 +76,7 @@ public abstract class Downloader {
protected abstract int totalDownloadSize();
public void download() throws IOException {
protected void download() throws IOException {
setupProgressListener();
InputStream input = null;
try {

View File

@ -24,6 +24,13 @@ public class HttpDownloader extends Downloader {
sourceUrl = new URL(source);
}
// The context is required for opening the file to write to.
public HttpDownloader(String source, File destFile)
throws FileNotFoundException, MalformedURLException {
super(destFile);
sourceUrl = new URL(source);
}
/**
* Downloads to a temporary file, which *you must delete yourself when
* you are done*.