- Translate the interface to Galician (tools/translate_gl.py), including Galician month abbreviations for dates, which Android 2.3 lacks - Resize for the Galaxy Mini's 240x320 screen: larger text, rows, tabs and buttons; all sizes now live in res/values/dimens.xml - Redraw play/pause/refresh icons for every density; slightly brighter secondary text colours Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
253 lines
7.7 KiB
Java
253 lines
7.7 KiB
Java
package com.miniabs;
|
|
|
|
import java.io.IOException;
|
|
import java.io.Reader;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
|
|
/**
|
|
* Small streaming JSON parser that builds org.json objects straight from the
|
|
* socket. Very long strings (episode show notes) are cut short and bulky keys
|
|
* are dropped while reading, so a podcast with hundreds of episodes still fits
|
|
* in the Galaxy Mini's small heap.
|
|
*/
|
|
final class Json {
|
|
/** Keys never kept, at any depth. */
|
|
private static final String[] ALWAYS_SKIP = {"chapters", "libraryFiles", "metaTags", "rssFeed"};
|
|
|
|
private final Reader in;
|
|
private final char[] buf = new char[8192];
|
|
private int pos, len;
|
|
private final int maxString;
|
|
private final String[] deepSkip;
|
|
private int depth;
|
|
|
|
private Json(Reader in, int maxString, String[] deepSkip) {
|
|
this.in = in;
|
|
this.maxString = maxString;
|
|
this.deepSkip = deepSkip;
|
|
}
|
|
|
|
static Object parse(Reader r) throws IOException {
|
|
return parse(r, 2000, null);
|
|
}
|
|
|
|
/**
|
|
* @param maxString longest string kept (longer ones end with an ellipsis)
|
|
* @param deepSkip keys dropped inside objects nested three or more levels deep
|
|
*/
|
|
static Object parse(Reader r, int maxString, String[] deepSkip) throws IOException {
|
|
Json j = new Json(r, maxString, deepSkip);
|
|
int c = j.skipWs();
|
|
if (c == -1) return null;
|
|
return j.value(c);
|
|
}
|
|
|
|
private int read() throws IOException {
|
|
if (pos >= len) {
|
|
len = in.read(buf, 0, buf.length);
|
|
pos = 0;
|
|
if (len <= 0) {
|
|
len = 0;
|
|
return -1;
|
|
}
|
|
}
|
|
return buf[pos++];
|
|
}
|
|
|
|
private void unread() {
|
|
pos--;
|
|
}
|
|
|
|
private int skipWs() throws IOException {
|
|
int c;
|
|
do {
|
|
c = read();
|
|
} while (c == ' ' || c == '\n' || c == '\r' || c == '\t');
|
|
return c;
|
|
}
|
|
|
|
private Object value(int c) throws IOException {
|
|
switch (c) {
|
|
case '{':
|
|
return object();
|
|
case '[':
|
|
return array();
|
|
case '"':
|
|
return string(maxString);
|
|
case 't':
|
|
expect("rue");
|
|
return Boolean.TRUE;
|
|
case 'f':
|
|
expect("alse");
|
|
return Boolean.FALSE;
|
|
case 'n':
|
|
expect("ull");
|
|
return JSONObject.NULL;
|
|
default:
|
|
if (c == '-' || (c >= '0' && c <= '9')) return number(c);
|
|
throw new IOException("Resposta inesperada do servidor");
|
|
}
|
|
}
|
|
|
|
private void expect(String rest) throws IOException {
|
|
for (int i = 0; i < rest.length(); i++) {
|
|
if (read() != rest.charAt(i)) throw new IOException("Resposta inesperada do servidor");
|
|
}
|
|
}
|
|
|
|
private Object number(int first) throws IOException {
|
|
StringBuilder sb = new StringBuilder(16);
|
|
sb.append((char) first);
|
|
boolean dbl = false;
|
|
int c;
|
|
while (true) {
|
|
c = read();
|
|
if ((c >= '0' && c <= '9') || c == '-' || c == '+') {
|
|
sb.append((char) c);
|
|
} else if (c == '.' || c == 'e' || c == 'E') {
|
|
dbl = true;
|
|
sb.append((char) c);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (c != -1) unread();
|
|
String s = sb.toString();
|
|
try {
|
|
if (!dbl) {
|
|
long l = Long.parseLong(s);
|
|
if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) return Integer.valueOf((int) l);
|
|
return Long.valueOf(l);
|
|
}
|
|
return Double.valueOf(s);
|
|
} catch (NumberFormatException e) {
|
|
try {
|
|
return Double.valueOf(s);
|
|
} catch (NumberFormatException e2) {
|
|
return Integer.valueOf(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean skipKey(String key) {
|
|
for (String k : ALWAYS_SKIP) if (k.equals(key)) return true;
|
|
if (deepSkip != null && depth >= 3) {
|
|
for (String k : deepSkip) if (k.equals(key)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private JSONObject object() throws IOException {
|
|
depth++;
|
|
JSONObject o = new JSONObject();
|
|
int c = skipWs();
|
|
if (c == '}') {
|
|
depth--;
|
|
return o;
|
|
}
|
|
while (true) {
|
|
if (c != '"') throw new IOException("Resposta inesperada do servidor");
|
|
String key = string(512);
|
|
c = skipWs();
|
|
if (c != ':') throw new IOException("Resposta inesperada do servidor");
|
|
c = skipWs();
|
|
if (skipKey(key)) {
|
|
skipValue(c);
|
|
} else {
|
|
Object v = value(c);
|
|
try {
|
|
o.put(key, v);
|
|
} catch (JSONException ignored) {
|
|
// NaN or similar; drop it
|
|
}
|
|
}
|
|
c = skipWs();
|
|
if (c == ',') {
|
|
c = skipWs();
|
|
continue;
|
|
}
|
|
if (c == '}') {
|
|
depth--;
|
|
return o;
|
|
}
|
|
throw new IOException("Resposta inesperada do servidor");
|
|
}
|
|
}
|
|
|
|
private JSONArray array() throws IOException {
|
|
JSONArray a = new JSONArray();
|
|
int c = skipWs();
|
|
if (c == ']') return a;
|
|
while (true) {
|
|
a.put(value(c));
|
|
c = skipWs();
|
|
if (c == ',') {
|
|
c = skipWs();
|
|
continue;
|
|
}
|
|
if (c == ']') return a;
|
|
throw new IOException("Resposta inesperada do servidor");
|
|
}
|
|
}
|
|
|
|
private void skipValue(int c) throws IOException {
|
|
if (c == '"') {
|
|
string(0);
|
|
return;
|
|
}
|
|
if (c == '{' || c == '[') {
|
|
int d = 1;
|
|
while (d > 0) {
|
|
int ch = read();
|
|
if (ch == -1) throw new IOException("A resposta chegou incompleta");
|
|
if (ch == '"') string(0);
|
|
else if (ch == '{' || ch == '[') d++;
|
|
else if (ch == '}' || ch == ']') d--;
|
|
}
|
|
return;
|
|
}
|
|
value(c);
|
|
}
|
|
|
|
private String string(int max) throws IOException {
|
|
StringBuilder sb = new StringBuilder(max > 0 ? Math.min(max, 32) : 0);
|
|
boolean cut = false;
|
|
while (true) {
|
|
int c = read();
|
|
if (c == -1) throw new IOException("A resposta chegou incompleta");
|
|
if (c == '"') break;
|
|
if (c == '\\') {
|
|
c = read();
|
|
switch (c) {
|
|
case 'n': c = '\n'; break;
|
|
case 't': c = '\t'; break;
|
|
case 'r': c = '\r'; break;
|
|
case 'b': c = '\b'; break;
|
|
case 'f': c = '\f'; break;
|
|
case 'u': {
|
|
int v = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
int d = Character.digit(read(), 16);
|
|
if (d < 0) throw new IOException("Resposta inesperada do servidor");
|
|
v = v * 16 + d;
|
|
}
|
|
c = v;
|
|
break;
|
|
}
|
|
case -1:
|
|
throw new IOException("A resposta chegou incompleta");
|
|
default:
|
|
break; // \" \\ \/
|
|
}
|
|
}
|
|
if (sb.length() < max) sb.append((char) c);
|
|
else cut = true;
|
|
}
|
|
if (cut && max > 0) sb.append('…');
|
|
return sb.toString();
|
|
}
|
|
}
|