/* * mygram-droid: Android TV client for Foldergram. * Copyright (C) 2026 Danilo M. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ package mygram.droid; import android.content.Context; import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.LinkProperties; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** Finds Foldergram instances on the LAN. */ // ponytail: probes only the TV's /24 on the default port; manual URL entry covers proxies and other ports. final class Discovery { static final int PORT = 4141; private Discovery() { } /** True for a Foldergram /api/health reply: {"ok":true,...,"storage":{...}}. */ static boolean isFoldergram(String body) { try { JSONObject o = new JSONObject(body); return o.optBoolean("ok") && o.optJSONObject("storage") != null; } catch (JSONException e) { return false; } } /** The TV's own IPv4 address, or null when offline. */ static String localIpv4(Context c) { ConnectivityManager cm = c.getSystemService(ConnectivityManager.class); LinkProperties lp = cm.getLinkProperties(cm.getActiveNetwork()); if (lp != null) for (LinkAddress a : lp.getLinkAddresses()) if (a.getAddress() instanceof Inet4Address) return a.getAddress().getHostAddress(); return null; } /** Base URLs of instances answering on the /24 around self. Blocks a few seconds. */ static List scan(String self) throws InterruptedException { String prefix = self.substring(0, self.lastIndexOf('.') + 1); ExecutorService pool = Executors.newFixedThreadPool(64); List found = Collections.synchronizedList(new ArrayList<>()); for (int i = 1; i < 255; i++) { String base = "http://" + prefix + i + ":" + PORT; pool.execute(() -> { try { if (isFoldergram(Api.request(base, "GET", "/health", null, 800))) found.add(base); } catch (IOException e) { // nothing listening there } }); } pool.shutdown(); pool.awaitTermination(20, TimeUnit.SECONDS); List out = new ArrayList<>(found); Collections.sort(out); return out; } /** Reverse-DNS name plus IP when there is a name, else the IP. */ static String label(String base) { String ip = URI.create(base).getHost(); try { String name = InetAddress.getByName(ip).getCanonicalHostName(); return name.equals(ip) ? ip : name + " " + ip; } catch (UnknownHostException e) { return ip; } } }