Import miniabs 1.0 source
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
4fe9eed870
78 files changed
+5800
No files matched your search
@@ -0,0 +1,230 @@
|
||||
package com.miniabs;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.PowerManager;
|
||||
|
||||
/** Downloads episodes one at a time to the SD card, resuming partial files. */
|
||||
public class DownloadService extends IntentService {
|
||||
private static final int NOTIF_ID = 2;
|
||||
|
||||
private static final List<Episode> queue = new ArrayList<Episode>();
|
||||
static volatile Episode current;
|
||||
static volatile int percent;
|
||||
static volatile String lastError;
|
||||
private static volatile String cancelId;
|
||||
|
||||
public DownloadService() {
|
||||
super("MiniABS downloads");
|
||||
}
|
||||
|
||||
static void enqueue(Context c, Episode e) {
|
||||
synchronized (queue) {
|
||||
if (current != null && current.id.equals(e.id)) return;
|
||||
for (Episode q : queue) if (q.id.equals(e.id)) return;
|
||||
queue.add(e);
|
||||
}
|
||||
lastError = null;
|
||||
c.startService(new Intent(c, DownloadService.class).putExtra("ep", e.toJson().toString()));
|
||||
}
|
||||
|
||||
static void cancel(String epId) {
|
||||
synchronized (queue) {
|
||||
for (int i = queue.size() - 1; i >= 0; i--) if (queue.get(i).id.equals(epId)) queue.remove(i);
|
||||
}
|
||||
Episode cur = current;
|
||||
if (cur != null && cur.id.equals(epId)) cancelId = epId;
|
||||
}
|
||||
|
||||
static List<Episode> queued() {
|
||||
synchronized (queue) {
|
||||
return new ArrayList<Episode>(queue);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean busyWith(String epId) {
|
||||
Episode cur = current;
|
||||
if (cur != null && cur.id.equals(epId)) return true;
|
||||
synchronized (queue) {
|
||||
for (Episode q : queue) if (q.id.equals(epId)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
Episode e;
|
||||
try {
|
||||
e = Episode.fromStored(new JSONObject(intent.getStringExtra("ep")));
|
||||
} catch (Exception ex) {
|
||||
return;
|
||||
}
|
||||
synchronized (queue) {
|
||||
boolean found = false;
|
||||
for (int i = 0; i < queue.size(); i++) {
|
||||
if (queue.get(i).id.equals(e.id)) {
|
||||
queue.remove(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return; // cancelled while waiting
|
||||
current = e;
|
||||
}
|
||||
percent = 0;
|
||||
cancelId = null;
|
||||
PowerManager.WakeLock wake = ((PowerManager) getSystemService(POWER_SERVICE))
|
||||
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MiniABS download");
|
||||
WifiManager.WifiLock wifi = ((WifiManager) getSystemService(WIFI_SERVICE))
|
||||
.createWifiLock(WifiManager.WIFI_MODE_FULL, "MiniABS download");
|
||||
wake.acquire();
|
||||
wifi.acquire();
|
||||
try {
|
||||
download(e);
|
||||
if (e.id.equals(cancelId)) {
|
||||
Downloads.delete(this, e);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (!e.id.equals(cancelId)) {
|
||||
lastError = "Download failed: " + Util.describe(ex);
|
||||
notifyDone(e.title, lastError);
|
||||
}
|
||||
} finally {
|
||||
current = null;
|
||||
cancelId = null;
|
||||
stopForeground(true);
|
||||
wifi.release();
|
||||
wake.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void download(Episode e) throws IOException {
|
||||
if (Prefs.get(this).wifiOnly() && !Util.onWifi(this)) {
|
||||
throw new IOException("not on Wi-Fi (Settings: Download on Wi-Fi only)");
|
||||
}
|
||||
File dir = Downloads.dir(this);
|
||||
if (Downloads.audio(this, e) != null) {
|
||||
Downloads.saved(this, e);
|
||||
return;
|
||||
}
|
||||
if (e.ino.length() == 0) throw new IOException("this episode has no audio file");
|
||||
File part = new File(dir, e.id + ".part");
|
||||
Notification n = progressNotification(e, 0);
|
||||
startForeground(NOTIF_ID, n);
|
||||
|
||||
Api.ensureFresh(this, 600);
|
||||
HttpURLConnection conn = null;
|
||||
int code = 0;
|
||||
long have = part.exists() ? part.length() : 0;
|
||||
boolean refreshed = false;
|
||||
String[] urls = Api.downloadUrls(this, e);
|
||||
for (int i = 0; i < urls.length; i++) {
|
||||
conn = Api.open(this, urls[i], "GET");
|
||||
conn.setReadTimeout(60000);
|
||||
if (have > 0) conn.setRequestProperty("Range", "bytes=" + have + "-");
|
||||
code = Api.responseCode(conn);
|
||||
if (code == 401 && !refreshed && Prefs.get(this).canRefresh()) {
|
||||
conn.disconnect();
|
||||
Api.refresh(this, true);
|
||||
refreshed = true;
|
||||
urls = Api.downloadUrls(this, e);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
if (code == 200 || code == 206) break;
|
||||
conn.disconnect();
|
||||
if (code == 416 && have > 0) { // stale partial file: start over
|
||||
part.delete();
|
||||
have = 0;
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
if (code != 404 && code != 403) break;
|
||||
}
|
||||
if (code != 200 && code != 206) {
|
||||
if (code == 401) throw new Api.AuthException("sign-in expired; log in again");
|
||||
if (code == 403) throw new IOException("your account isn't allowed to download");
|
||||
throw new Api.ApiException(code, "");
|
||||
}
|
||||
try {
|
||||
boolean append = code == 206 && have > 0;
|
||||
if (!append) have = 0;
|
||||
long len = conn.getContentLength();
|
||||
long total = len > 0 ? have + len : (e.size > 0 ? e.size : -1);
|
||||
InputStream in = conn.getInputStream();
|
||||
OutputStream out = new FileOutputStream(part, append);
|
||||
try {
|
||||
byte[] buf = new byte[16384];
|
||||
long done = have, lastUi = 0;
|
||||
int r;
|
||||
while ((r = in.read(buf)) > 0) {
|
||||
if (e.id.equals(cancelId)) return;
|
||||
out.write(buf, 0, r);
|
||||
done += r;
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastUi > 1500) {
|
||||
lastUi = now;
|
||||
percent = total > 0 ? (int) (done * 100 / total) : 0;
|
||||
n = progressNotification(e, percent);
|
||||
nm().notify(NOTIF_ID, n);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
in.close();
|
||||
}
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
if (e.id.equals(cancelId)) return;
|
||||
File dest = new File(dir, e.id + e.ext);
|
||||
if (!part.renameTo(dest)) throw new IOException("couldn't save the file");
|
||||
Downloads.saved(this, e);
|
||||
percent = 100;
|
||||
}
|
||||
|
||||
private NotificationManager nm() {
|
||||
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
private PendingIntent openApp() {
|
||||
Intent i = new Intent(this, HomeActivity.class).putExtra("tab", HomeActivity.TAB_SAVED)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
return PendingIntent.getActivity(this, 1, i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
private Notification progressNotification(Episode e, int pct) {
|
||||
Notification n = new Notification(android.R.drawable.stat_sys_download, null, System.currentTimeMillis());
|
||||
n.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_ONLY_ALERT_ONCE;
|
||||
int left;
|
||||
synchronized (queue) {
|
||||
left = queue.size();
|
||||
}
|
||||
String text = "Downloading " + pct + "%" + (left > 0 ? " · " + left + " more queued" : "");
|
||||
n.setLatestEventInfo(this, e.title, text, openApp());
|
||||
return n;
|
||||
}
|
||||
|
||||
private void notifyDone(String title, String text) {
|
||||
Notification n = new Notification(android.R.drawable.stat_notify_error, text, System.currentTimeMillis());
|
||||
n.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||
n.setLatestEventInfo(this, title, text, openApp());
|
||||
nm().notify(3, n);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user