blob: 56777f37cae2bfb92182f86e1bc71cae3b7c6782 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
/*
* mygram-droid: Android TV client for Foldergram.
* Copyright (C) 2026 Danilo M. <danix@danix.xyz>
*
* 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<String> scan(String self) throws InterruptedException {
String prefix = self.substring(0, self.lastIndexOf('.') + 1);
ExecutorService pool = Executors.newFixedThreadPool(64);
List<String> 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<String> 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;
}
}
}
|