Import minamp 1.0 source

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xockoandClaude Opus 5.5 committed 2026-10-14 19:38:37 +02:00
commit 0df8124629
194 files changed
+7319

No files matched your search

+167
View File
@@ -0,0 +1,167 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.minamp.net.CertVerifier;
import com.minamp.net.Http;
import com.minamp.net.StreamProxy;
import com.minamp.plex.Item;
import com.minamp.plex.Library;
import com.minamp.plex.Plex;
import com.minamp.plex.Server;
import com.minamp.util.Json;
/** End-to-end test of sign-in, discovery, connection picking, browsing and the stream proxy. */
public class PlexTest {
static int failures = 0;
static void check(boolean ok, String what) {
System.out.println((ok ? "PASS " : "FAIL ") + what);
if (!ok) failures++;
}
static RootDir roots(String dir) { return new RootDir(new File(dir)); }
static final class RootDir implements CertVerifier.RootStore {
final File dir;
RootDir(File d) { dir = d; }
public List<byte[]> find(byte[] subjectDer) {
String h = CertVerifier.sha1Hex(subjectDer);
List<byte[]> out = new ArrayList<byte[]>();
for (int i = 0; ; i++) {
File f = new File(dir, h + "-" + i + ".der");
if (!f.exists()) break;
try { out.add(read(new FileInputStream(f))); } catch (Exception e) { break; }
}
return out.isEmpty() ? null : out;
}
}
public static void main(String[] args) throws Exception {
CertVerifier.setRootStore(roots(args[0]));
Plex.clientId = "test-client";
// --- sign-in via PIN
Plex.Pin pin = Plex.createPin();
check("WXYZ".equals(pin.code), "PIN created: " + pin.code);
String account = null;
for (int i = 0; i < 10 && account == null; i++) { account = Plex.checkPin(pin.id); if (account == null) Thread.sleep(300); }
check("account-token-123".equals(account), "PIN linked -> account token");
check("tester".equals(Plex.username(account)), "username");
// --- discovery
List<Server> servers = Plex.servers(account);
check(servers.size() == 1, "one server (players filtered out)");
Server s = servers.get(0);
StringBuilder order = new StringBuilder();
for (Server.Conn c : s.connections) order.append(c.kind).append(' ');
check(s.connections.get(0).kind == Server.LOCAL && s.connections.get(s.connections.size() - 1).kind == Server.RELAY,
"connections ordered local..relay: " + order);
// persistence round trip
Server s2 = Server.fromJson(Json.parseObj(s.toJson().toString()));
check(s2.connections.size() == s.connections.size() && s2.token.equals(s.token), "server JSON round trip");
long t0 = System.currentTimeMillis();
Server.Conn picked = s2.pick();
check(picked.uri.startsWith("https://127-0-0-1.abc123.plex.direct:32443"), "picked local TLS connection " + picked.uri
+ " in " + (System.currentTimeMillis() - t0) + "ms");
// --- browsing
List<Server.Section> secs = s2.musicSections();
check(secs.size() == 1 && "Music".equals(secs.get(0).title), "music sections only");
Library lib = new Library(s2, secs.get(0).key);
List<Item> artists = s2.items(lib.artistsPath(100, 100));
check(artists.size() == 100 && "Artist 100".equals(artists.get(0).title), "artists paging");
Item album = s2.items(lib.recentlyAddedPath(10)).get(0);
check(album.type == Item.ALBUM && "The Testers · 1999".equals(album.subtitle()), "album item + subtitle");
List<Item> tracks = lib.tracksOf(album);
check(tracks.size() == 2 && tracks.get(0).partKey.endsWith("file.mp3") && tracks.get(0).loved(), "album tracks with media");
check(Library.canDirectPlay(tracks.get(0), 0) && !Library.canDirectPlay(tracks.get(1), 0), "direct play decisions");
check(!Library.canDirectPlay(tracks.get(0), 128), "192k mp3 transcoded when capped at 128k");
Item rt = Item.deserialize(tracks.get(0).serialize());
check(rt.title.equals("Song One") && rt.partKey.equals(tracks.get(0).partKey) && rt.duration == 200000, "item JSON round trip");
List<Item> res = lib.search("test");
check(res.size() == 4 && res.get(0).type == Item.HEADER && res.get(1).type == Item.ARTIST && res.get(3).type == Item.TRACK,
"search hubs grouped (" + res.size() + ")");
check(lib.libraryRadio().size() == 2, "library radio");
String img = s2.imageUrl("/library/metadata/10/thumb/1", 64);
Http.Response ir = Http.get(img, null, 10000);
byte[] ib = ir.readAll();
check(ir.code == 200 && ib.length > 100, "artwork via photo transcoder");
lib.timeline(tracks.get(0), "playing", 1000);
lib.rate(tracks.get(0), 10);
check(true, "timeline + rate calls");
// --- stream proxy
StreamProxy proxy = new StreamProxy();
proxy.start();
String id = proxy.newId();
String local = proxy.register(id, lib.directUrl(tracks.get(0)), "mp3");
HttpURLConnection hc = (HttpURLConnection) new URL(local).openConnection(Proxy.NO_PROXY);
hc.setRequestProperty("Range", "bytes=1000-");
byte[] body = read(hc.getInputStream());
check(hc.getResponseCode() == 206 && body.length == 299000 && (body[0] & 255) == (1000 * 7) % 256,
"proxy direct stream with Range over TLS (" + hc.getResponseCode() + ", " + body.length + "B)");
String id2 = proxy.newId();
String local2 = proxy.register(id2, lib.transcodeUrl(tracks.get(1), 192, 30500, "sess1"), "mp3");
hc = (HttpURLConnection) new URL(local2).openConnection(Proxy.NO_PROXY);
byte[] tbody = read(hc.getInputStream());
String ts = new String(tbody, "ISO-8859-1");
check(hc.getResponseCode() == 200 && ts.startsWith("TRANSCODE offset=30 bitrate=192|") && hc.getHeaderField("Content-Length") == null,
"proxy de-chunks transcode stream (" + tbody.length + "B)");
// abort mid-stream
String id3 = proxy.newId();
String local3 = proxy.register(id3, lib.directUrl(tracks.get(0)), "mp3");
hc = (HttpURLConnection) new URL(local3).openConnection(Proxy.NO_PROXY);
InputStream in = hc.getInputStream();
in.read(new byte[100]);
proxy.unregister(id3);
check(true, "unregister mid-stream doesn't hang");
String gone = proxy.register("nope", "http://127.0.0.1:1/x", "mp3");
proxy.unregister("nope");
hc = (HttpURLConnection) new URL(gone).openConnection(Proxy.NO_PROXY);
check(hc.getResponseCode() == 404, "unknown route 404");
// --- failover: break the preferred connection's identity, expect fallback to plain http LAN
Server s3 = new Server();
s3.machineId = "machine-abc123"; s3.token = s.token; s3.name = "x";
s3.add("https://localhost:32443", Server.LOCAL); // cert won't match "localhost"
s3.add("http://127.0.0.1:32400", Server.LOCAL_HTTP);
s3.add("https://10-255-255-1.abc123.plex.direct:9", Server.REMOTE);
s3.sortConnections();
Server.Conn c3 = s3.pick();
check(c3.kind == Server.LOCAL_HTTP, "falls back when TLS name check fails -> " + c3.uri);
// --- trust checks
CertVerifier.setRootStore(roots(args[1])); // real Mozilla roots: test CA must be rejected
Http.evictAll();
try {
Http.get("https://127-0-0-1.abc123.plex.direct:32443/identity", null, 5000).readAll();
check(false, "untrusted root rejected");
} catch (Exception e) {
check(e.getMessage() != null && e.getMessage().indexOf("Untrusted") >= 0, "untrusted root rejected: " + e.getMessage());
}
check(Server.normalizeManual("192.168.1.5").equals("http://192.168.1.5:32400"), "manual URL normalise");
check(Server.normalizeManual("https://plex.example.com/").equals("https://plex.example.com"), "manual https URL kept");
System.out.println(failures == 0 ? "ALL PASSED" : failures + " FAILED");
System.exit(failures == 0 ? 0 : 1);
}
static byte[] read(InputStream in) throws Exception {
ByteArrayOutputStream b = new ByteArrayOutputStream();
byte[] buf = new byte[8192]; int n;
while ((n = in.read(buf)) > 0) b.write(buf, 0, n);
in.close();
return b.toByteArray();
}
}