Browse Source

Add pause and unpause support

master
Trevor Slocum 3 years ago
parent
commit
ca6648a139
  1. 7
      README.md
  2. 6
      app/build.gradle
  3. 4
      app/src/main/java/space/rocketnine/gophast/Download.java
  4. 145
      app/src/main/java/space/rocketnine/gophast/DownloadAdapter.java
  5. 46
      app/src/main/java/space/rocketnine/gophast/DownloadRunnable.java
  6. 90
      app/src/main/java/space/rocketnine/gophast/GoPhast.java
  7. 9
      app/src/main/res/anim/fade_out.xml
  8. 5
      app/src/main/res/layout/dialog_add_download.xml
  9. 19
      app/src/main/res/layout/list_downloads.xml
  10. 3
      app/src/main/res/values/strings.xml
  11. BIN
      assets/badge_amazon.png
  12. BIN
      assets/badge_amazon_56.png
  13. BIN
      assets/badge_fdroid.png
  14. BIN
      assets/badge_fdroid_56.png
  15. BIN
      assets/badge_google.png
  16. BIN
      assets/badge_google_56.png

7
README.md

@ -1,15 +1,16 @@
# GoPhast-Android
[![Donate](http://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space)
[![Donate](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space)
This application manages and accelerates downloads by using multiple connections.
## Download
- [Google Play](https://play.google.com/store/apps/details?id=space.rocketnine.gophast)
<!--<a href="https://f-droid.org/packages/space.rocketnine.gophast/"><img width="188" height="56" alt="F-Droid" src="https://gophast.rocketnine.space/assets/badge_fdroid_56.png"></a>-->
<a href="https://play.google.com/store/apps/details?id=space.rocketnine.gophast"><img width="188" height="56" alt="Google Play" src="https://gophast.rocketnine.space/assets/badge_google_56.png"></a>
<!--<a href="#"><img width="188" height="56" alt="Amazon Appstore" src="https://gophast.rocketnine.space/assets/badge_amazon_56.png"></a>-->
## Support
- Ensure you are running the latest version of GoPhast-Android.
- Review the [open issues](https://todo.sr.ht/~tslocum/gophast-android).
- Open a [new issue](https://todo.sr.ht/~tslocum/gophast-android).

6
app/build.gradle

@ -1,7 +1,7 @@
apply plugin: 'com.android.application'
project.ext {
libraryVersion = "0.1.3" // https://git.sr.ht/~tslocum/gophast/refs
libraryVersion = "0.1.4" // https://git.sr.ht/~tslocum/gophast/refs
}
android {
@ -13,8 +13,8 @@ android {
minSdkVersion 16
targetSdkVersion 28
versionCode 1011
versionName "0.1.1"
versionCode 1012
versionName "0.1.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

4
app/src/main/java/space/rocketnine/gophast/Download.java

@ -3,6 +3,8 @@ package space.rocketnine.gophast;
import android.net.Uri;
import android.webkit.MimeTypeMap;
import manager.Manager;
public class Download {
int Index;
long ID;
@ -13,7 +15,9 @@ public class Download {
long Size;
long Downloaded;
Uri Path;
boolean Paused;
boolean Complete;
boolean Failed;
String getMIME() {
String mime = "";

145
app/src/main/java/space/rocketnine/gophast/DownloadAdapter.java

@ -31,12 +31,25 @@ public class DownloadAdapter extends RecyclerView.Adapter<DownloadAdapter.Downlo
}
}
public static class DeltaProgress {
int progress;
boolean animate;
public DeltaProgress(int p) {
public DeltaProgress(int p, boolean a) {
progress = p;
animate = a;
}
}
public static class DeltaStatus {
boolean paused;
boolean complete;
boolean failed;
public DeltaStatus(boolean p, boolean c, boolean f) {
paused = p;
complete = c;
failed = f;
}
}
@ -44,16 +57,18 @@ public class DownloadAdapter extends RecyclerView.Adapter<DownloadAdapter.Downlo
public View v;
// each data item is just a string in this case
public TextView txtName;
public TextView txtSize;
public TextView txtStatus;
public TextView txtURL;
public TextView txtSize;
public ProgressBar progressBar;
public DownloadViewHolder(LinearLayout v) {
super(v);
this.v = v;
txtName = v.findViewById(R.id.downloadName);
txtSize = v.findViewById(R.id.downloadSize);
txtStatus = v.findViewById(R.id.downloadStatus);
txtURL = v.findViewById(R.id.downloadURL);
txtSize = v.findViewById(R.id.downloadSize);
progressBar = v.findViewById(R.id.downloadProgress);
progressBar.setMax(10000);
@ -82,37 +97,53 @@ public class DownloadAdapter extends RecyclerView.Adapter<DownloadAdapter.Downlo
public void onBindViewHolder(final DownloadViewHolder holder, int position) {
Download d = downloads.get(position);
holder.txtName.setText(d.Name);
if (d.Size > 0) {
holder.txtSize.setText(Formatter.formatShortFileSize(context, d.Size));
holder.txtSize.setVisibility(View.VISIBLE);
} else {
holder.txtSize.setVisibility(View.INVISIBLE);
}
setDownloadStatus(holder, new DeltaStatus(d.Paused, d.Complete, d.Failed), true);
holder.txtURL.setText(d.URL);
setDownloadSize(holder, new DeltaSize(d.Size));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
holder.progressBar.setProgress((int) (d.Downloaded * 10000.0 / d.Size), true);
holder.progressBar.setProgress((int) (d.Downloaded * 10000.0 / d.Size), false);
} else {
holder.progressBar.setProgress((int) (d.Downloaded * 10000.0 / d.Size));
}
holder.v.setOnClickListener(v -> {
Download d1 = downloads.get(holder.getAdapterPosition());
if (d1 == null) {
Download download = downloads.get(holder.getAdapterPosition());
if (download == null) {
return;
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(d1.Path, d1.getMIME());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
getApp().longToast("No application able to open this file is installed");
if (!download.Complete) {
if (!download.Paused) {
gophast.exec.execute(new Runnable() {
@Override
public void run() {
gophast.pauseDownload(download);
}
});
} else {
gophast.exec.execute(new Runnable() {
@Override
public void run() {
gophast.resumeDownload(download);
}
});
}
} else {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(download.Path, download.getMIME());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
getApp().longToast("No application able to open this file is installed");
}
}
});
holder.v.setOnLongClickListener(v -> {
@ -137,7 +168,12 @@ public class DownloadAdapter extends RecyclerView.Adapter<DownloadAdapter.Downlo
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(Intent.createChooser(intent, context.getString(R.string.openWith)));
} else if (i == 1) {
getApp().removeDownload(d121);
gophast.exec.execute(new Runnable() {
@Override
public void run() {
gophast.removeDownload(d121);
}
});
}
})
.create();
@ -154,27 +190,56 @@ public class DownloadAdapter extends RecyclerView.Adapter<DownloadAdapter.Downlo
} else {
for (Object payload : payloads) {
if (payload instanceof DeltaProgress) {
DeltaProgress d = (DeltaProgress) payload;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
holder.progressBar.setProgress(d.progress, true);
} else {
holder.progressBar.setProgress(d.progress);
}
setDownloadProgress(holder, (DeltaProgress) payload);
} else if (payload instanceof DeltaSize) {
DeltaSize d = (DeltaSize) payload;
if (d.size > 0) {
holder.txtSize.setText(Formatter.formatShortFileSize(context, d.size));
gophast.animateView(context, holder.txtSize, R.anim.fade_in);
} else {
holder.txtSize.setVisibility(View.INVISIBLE);
}
setDownloadSize(holder, (DeltaSize) payload);
} else if (payload instanceof DeltaStatus) {
setDownloadStatus(holder, (DeltaStatus) payload, false);
}
}
}
}
private void setDownloadProgress(final DownloadViewHolder holder, DeltaProgress d) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
holder.progressBar.setProgress(d.progress, d.animate);
} else {
holder.progressBar.setProgress(d.progress);
}
}
private void setDownloadSize(final DownloadViewHolder holder, DeltaSize d) {
if (d.size > 0) {
holder.txtSize.setText(Formatter.formatShortFileSize(context, d.size));
gophast.animateShowView(context, holder.txtSize, R.anim.fade_in);
} else {
holder.txtSize.setVisibility(View.INVISIBLE);
}
}
private void setDownloadStatus(final DownloadViewHolder holder, DeltaStatus d, boolean initialBind) {
if (d.complete) {
holder.txtStatus.setText(R.string.completed);
if (holder.txtStatus.getVisibility() != View.VISIBLE) {
gophast.animateShowView(context, holder.txtStatus, R.anim.fade_in);
}
} else if (d.failed) {
holder.txtStatus.setText(R.string.failed);
if (holder.txtStatus.getVisibility() != View.VISIBLE) {
gophast.animateShowView(context, holder.txtStatus, R.anim.fade_in);
}
} else if (d.paused) {
holder.txtStatus.setText(R.string.paused);
holder.txtStatus.setVisibility(View.VISIBLE);
} else {
if (initialBind) {
holder.txtStatus.setVisibility(View.INVISIBLE);
} else {
gophast.animateHideView(context, holder.txtStatus, R.anim.fade_out);
}
}
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {

46
app/src/main/java/space/rocketnine/gophast/DownloadRunnable.java

@ -13,6 +13,8 @@ class DownloadRunnable implements Runnable {
Download d;
GoPhast gophast;
RecyclerView recyclerView;
boolean wasPaused;
boolean animate;
private Runnable notifySizeRunnable = new Runnable() {
@Override
@ -23,7 +25,17 @@ class DownloadRunnable implements Runnable {
private Runnable notifyProgressRunnable = new Runnable() {
@Override
public void run() {
gophast.adapter.notifyItemChanged(d.Index, new DownloadAdapter.DeltaProgress((int) (d.Downloaded * 10000.0 / d.Size)));
gophast.adapter.notifyItemChanged(d.Index, new DownloadAdapter.DeltaProgress((int) (d.Downloaded * 10000.0 / d.Size), animate));
if (!animate && d.Downloaded > 0) {
animate = true;
}
}
};
private Runnable notifyStatusRunnable = new Runnable() {
@Override
public void run() {
gophast.adapter.notifyItemChanged(d.Index, new DownloadAdapter.DeltaStatus(d.Paused, d.Complete, d.Failed));
}
};
@ -36,9 +48,7 @@ class DownloadRunnable implements Runnable {
info.setURL(d.URL);
info.setPostData(new byte[]{});
d.ID = Manager.addDownload(info);
gophast.acquireWakeLock(d.ID);
gophast.startDownload(d, info);
d.Name = Manager.getName(d.ID);
d.Size = Manager.getSize(d.ID);
@ -57,6 +67,7 @@ class DownloadRunnable implements Runnable {
long id = d.ID;
long lastDownloaded = -99;
long newDownloaded;
int delay = 25;
boolean active;
for (; ; ) {
newDownloaded = Manager.getDownloaded(id);
@ -66,14 +77,35 @@ class DownloadRunnable implements Runnable {
lastDownloaded = newDownloaded;
recyclerView.post(notifyProgressRunnable);
if (newDownloaded < 0 || newDownloaded == size) {
d.Complete = true; // TODO: Cleanup when complete
if (newDownloaded == size) {
d.Paused = false;
d.Complete = true;
recyclerView.post(notifyStatusRunnable);
break;
} else if (wasPaused && !d.Paused) {
wasPaused = false;
recyclerView.post(notifyStatusRunnable);
}
} else if (d.Status == -7) {
d.Failed = true;
recyclerView.post(notifyStatusRunnable);
gophast.releaseWakeLock(d.ID);
return;
} else if (!wasPaused && d.Paused) {
wasPaused = true;
recyclerView.post(notifyStatusRunnable);
delay = 500;
} else if (wasPaused && !d.Paused) {
wasPaused = false;
recyclerView.post(notifyStatusRunnable);
delay = 25;
} else {
delay = 75;
}
try {
Thread.sleep(active ? 25 : 75);
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
break;

90
app/src/main/java/space/rocketnine/gophast/GoPhast.java

@ -31,7 +31,9 @@ import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import manager.DownloadInfo;
import manager.Manager;
import manager.NewDownloadInfo;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
@ -48,7 +50,7 @@ public class GoPhast extends Application {
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= 24) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
}
@ -75,7 +77,7 @@ public class GoPhast extends Application {
}
manager.Manager.setUserAgentExtra("gophast-android/" + appVersion + " (https://git.sr.ht/~tslocum/gophast-android)");
manager.Manager.setNoResume(true);
manager.Manager.setForce(true);
setDownloadDir("");
@ -104,7 +106,8 @@ public class GoPhast extends Application {
download.Name = (new File(url)).getName();
File downloadPath = (new File(getDownloadDir(), download.Name));
if (downloadPath.exists()) {
File downloadControlPath = (new File(getDownloadDir(), download.Name + ".gophast"));
if (downloadPath.exists() && !downloadControlPath.exists()) {
promtExistingFile(download, activity);
} else {
finishAddingDownload(download);
@ -134,7 +137,6 @@ public class GoPhast extends Application {
updateDownloadIndexes();
download.Path = Uri.fromFile(new File(getDownloadDir(), download.Name));
Log.d("gophast", "Add index " + download.Index);
adapter.notifyItemInserted(download.Index);
@ -157,10 +159,57 @@ public class GoPhast extends Application {
}
}
void startDownload(Download d, NewDownloadInfo info) throws Exception {
if (d.Paused) {
resumeDownload(d);
return;
} else if (d.Complete) {
return;
}
d.ID = Manager.addDownload(info);
acquireWakeLock(d.ID);
}
void pauseDownload(Download d) {
if (d.ID <= 0 || d.Paused || d.Complete) {
return;
}
d.Paused = true;
Log.d("gophast", "Pausing #" + d.ID);
try {
Manager.pauseDownload(d.ID);
} catch (Exception e) {
e.printStackTrace();
}
releaseWakeLock(d.ID);
}
void resumeDownload(Download d) {
if (d.ID <= 0 || !d.Paused || d.Complete) {
return;
}
d.Paused = false;
try {
Manager.resumeDownload(d.ID);
} catch (Exception e) {
e.printStackTrace();
return;
}
acquireWakeLock(d.ID);
}
void removeDownload(Download delete) {
if (delete == null) {
return;
}
Log.d("gophast", "Removing #" + delete.ID);
long id = delete.ID;
if (delete.ID > 0) {
@ -201,7 +250,15 @@ public class GoPhast extends Application {
deleted = true;
}
} catch (Exception e) {
// Failed to delete
// Failed to delete download
}
try {
File controlFile = new File(path + ".gophast");
if (controlFile.exists() && controlFile.isFile()) {
controlFile.delete();
}
} catch (Exception e) {
// Failed to delete control file
}
if (!deleted) {
longToast(String.format(getString(R.string.failedToDelete), name));
@ -260,7 +317,7 @@ public class GoPhast extends Application {
manager.Manager.setDownloadDir(downloadDir);
}
public void animateView(Context ctx, View v, int animation) {
public void animateShowView(Context ctx, View v, int animation) {
Animation fadeInAnimation = AnimationUtils.loadAnimation(
ctx, animation);
v.startAnimation(fadeInAnimation);
@ -281,6 +338,27 @@ public class GoPhast extends Application {
});
}
public void animateHideView(Context ctx, View v, int animation) {
Animation fadeInAnimation = AnimationUtils.loadAnimation(
ctx, animation);
v.startAnimation(fadeInAnimation);
fadeInAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
v.setVisibility(View.INVISIBLE);
}
});
}
/**
* This method converts dp unit to equivalent pixels, depending on device density.
*

9
app/src/main/res/anim/fade_out.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:duration="@android:integer/config_shortAnimTime"
android:repeatCount="0" />
</set>

5
app/src/main/res/layout/dialog_add_download.xml

@ -8,8 +8,9 @@
android:id="@+id/addDownloadURL"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp"
android:ellipsize="none"
android:hint="@string/url"
android:inputType="textUri|textMultiLine"

19
app/src/main/res/layout/list_downloads.xml

@ -19,9 +19,10 @@
<TextView
android:id="@+id/downloadName"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:clickable="false"
android:focusable="false"
android:gravity="center_vertical"
@ -31,6 +32,22 @@
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="18sp" />
<TextView
android:id="@+id/downloadStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="7dp"
android:clickable="false"
android:focusable="false"
android:visibility="invisible"
android:gravity="center_vertical|end"
android:includeFontPadding="false"
android:singleLine="true"
android:ellipsize="middle"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textSize="18sp" />
</LinearLayout>
<ProgressBar

3
app/src/main/res/values/strings.xml

@ -21,4 +21,7 @@
<string name="overwrite">Overwrite</string>
<string name="promptFileExists">File already exists. Overwrite?</string>
<string name="fetchingMetadata">Fetching metadata&#8230;</string>
<string name="paused">Paused</string>
<string name="completed">Completed</string>
<string name="failed">Failed</string>
</resources>

BIN
assets/badge_amazon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/badge_amazon_56.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets/badge_fdroid.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
assets/badge_fdroid_56.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/badge_google.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/badge_google_56.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Loading…
Cancel
Save