chiark / gitweb /
wip
[tripe-android] / peers.scala
CommitLineData
8eabb4ff
MW
1package uk.org.distorted.tripe; package object peers {
2
3import java.io.{BufferedReader, File, FileReader, Reader};
4import java.net.{InetAddress, Inet4Address, Inet6Address,
5 UnknownHostException};
6
7import scala.collection.mutable.{HashMap, HashSet};
8import scala.concurrent.Channel;
9import scala.util.control.Breaks;
10import scala.util.matching.Regex;
11
12val RX_COMMENT = """(?x) ^ \s* (?: [;\#] .* )? $""".r;
13val RX_GRPHDR = """(?x) ^ \s* \[ (.*) \] \s* $""".r;
14val RX_ASSGN = """(?x) ^
15 ([^\s:=] (?: [^:=]* [^\s:=])?)
16 \s* [:=] \s*
17 (| [^\s\#;]\S* (?: \s+ [^\s\#;]\S*)*)
18 (?: \s+ (?: [;\#].*)? )? $""".r;
19val RX_CONT = """(?x) ^ \s+
20 (| [^\s\#;]\S* (?: \s+ [^\s\#;]\S*)*)
21 (?: \s+ (?: [;\#].*)? )? $""".r;
22val RX_REF = """(?x) \$ \( ([^)]+) \)""".r;
23val RX_RESOLVE = """(?x) \$ ([46*]*) \[ ([^\]]+) \]""".r;
24val RX_PARENT = """(?x) [^\s,]+""".r
25
26def with_cleaner[T](body: Cleaner => T): T = {
27 val cleaner = new Cleaner;
28 try { body(cleaner) }
29 finally { cleaner.cleanup(); }
30}
31
32class Cleaner {
33 var cleanups: List[() => Unit] = Nil;
34 def apply(cleanup: => Unit) { cleanups +:= { () => cleanup; } }
35 def cleanup() { cleanups foreach { _() } }
36}
37
38def lines(r: Reader) = new Traversable[String] {
39 val in: BufferedReader = new BufferedReader(r);
40 override def foreach[T](f: String => T) {
41 while (true) in.readLine match {
42 case null => return;
43 case line => f(line);
44 }
45 }
46}
47
48def thread(name: String, run: Boolean = true, daemon: Boolean = true)
49 (body: => Unit): Thread = {
50 val t = new Thread(new Runnable { override def run() { body } }, name);
51 t.setDaemon(daemon);
52 if (run) t.start();
53 t
54}
55
56object BulkResolver {
57 val BREAK = new Breaks;
58}
59
60class BulkResolver(val nthreads: Int = 8) {
61 import BulkResolver.BREAK._;
62 class Host(val name: String) {
63 var a4, a6: Seq[InetAddress] = Seq.empty;
64
65 def addaddr(a: InetAddress) { a match {
66 case _: Inet4Address => a4 +:= a;
67 case _: Inet6Address => a6 +:= a;
68 case _ => ();
69 } }
70
71 def get(flags: String): Seq[InetAddress] = {
72 var wanta4, wanta6, any, all = false;
73 var b = Seq.newBuilder[InetAddress];
74 for (ch <- flags) ch match {
75 case '*' => all = true;
76 case '4' => wanta4 = true; any = true;
77 case '6' => wanta6 = true; any = true;
78 case _ => ???
79 }
80 if (!any) { wanta4 = true; wanta6 = true; }
81 if (wanta4) b ++= a4;
82 if (wanta6) b ++= a6;
83 (all, b.result) match {
84 case (true, aa) => aa
85 case (false, aa@(Nil | Seq(_))) => aa
86 case (false, Seq(a, _*)) => Seq(a)
87 }
88 }
89 }
90 val ch = new Channel[Host];
91 val map = HashMap[String, Host]();
92 var preparing = true;
93
94 val workers = Array.tabulate(nthreads) { i =>
95 thread(s"resolver worker #$i") {
96 breakable {
97 while (true) {
98 val host = ch.read; if (host == null) break;
99println(s";; ${Thread.currentThread.getName} resolving `${host.name}'");
100 try {
101 for (a <- InetAddress.getAllByName(host.name)) host.addaddr(a);
102 } catch { case e: UnknownHostException => () }
103 }
104 }
105println(s";; ${Thread.currentThread.getName} done'");
106 ch.write(null);
107 }
108 }
109
110 def prepare(name: String) {
111println(s";; prepare host `$name'");
112 assert(preparing);
113 if (!(map contains name)) {
114 val host = new Host(name);
115 map(name) = host;
116 ch.write(host);
117 }
118 }
119
120 def finish() {
121 assert(preparing);
122 preparing = false;
123 ch.write(null);
124 for (t <- workers) t.join();
125 }
126
127 def resolve(name: String, flags: String): Seq[InetAddress] =
128 map(name).get(flags);
129}
130
131def fmtpath(path: Seq[String]) =
132 path.reverse map { i => s"`$i'" } mkString " -> ";
133
134class ConfigSyntaxError(val file: File, val lno: Int, val msg: String)
135 extends Exception {
136 override def getMessage(): String = s"$file:$lno: $msg";
137}
138class MissingConfigSection(val sect: String) extends Exception {
139 override def getMessage(): String =
140 s"missing configuration section `$sect'";
141}
142class MissingConfigItem(val sect: String, val key: String,
143 val path: Seq[(String)]) extends Exception {
144 override def getMessage(): String = {
145 val msg = s"missing configuration item `$key' in section `$sect'";
146 if (path == Nil) msg
147 else msg + s" (wanted while expanding ${fmtpath(path)})"
148 }
149}
150class AmbiguousConfig(val key: String,
151 val v0: String, val p0: Seq[String],
152 val v1: String, val p1: Seq[String])
153 extends Exception {
154 override def getMessage(): String =
155 s"ambiguous answer resolving key `$key': " +
156 s"path ${fmtpath(p0)} yields `$v0', but ${fmtpath(p1)} yields `$v1'";
157}
158
159class ConfigCycle(val key: String, path: Seq[String]) extends Exception {
160 override def getMessage(): String =
161 s"found a cycle ${fmtpath(path)} looking up key `$key'";
162}
163class NoHostAddresses(val sect: String, val key: String, val host: String)
164 extends Exception {
165 override def getMessage(): String =
166 s"no addresses found for `$host' (key `$key' in section `$sect')";
167}
168
169object Config {
170 sealed abstract class ConfigCacheEntry;
171 case object StillLooking extends ConfigCacheEntry;
172 case object NotFound extends ConfigCacheEntry;
173 case class Found(value: String, path: Seq[String])
174 extends ConfigCacheEntry;
175}
176
177class Config { conf =>
178 import Config._;
179 class Section(val name: String) {
180 val itemmap = HashMap[String, String]();
181 val cache = HashMap[String, ConfigCacheEntry]();
182 override def toString: String = s"${getClass.getName}($name)";
183 def parents: Seq[Section] =
184 (itemmap.get("@inherit")
185 map { pp => (RX_PARENT.findAllIn(pp) map { conf.section _ }).toList }
186 getOrElse Nil);
187
188 def get_internal(key: String, path: Seq[String] = Nil):
189 Option[(String, Seq[String])] = {
190 val incpath = name +: path;
191
192 for (r <- cache.get(key)) r match {
193 case StillLooking => throw new ConfigCycle(key, incpath)
194 case NotFound => return None
195 case Found(v, p) => return Some((v, p ++ path));
196 }
197
198 for (v <- itemmap.get(key)) {
199 cache(key) = Found(v, Seq(name));
200 return Some((v, incpath));
201 }
202
203 cache(key) = StillLooking;
204
205 ((None: Option[(String, Seq[String])]) /: parents) { (st, parent) =>
206 parent.get_internal(key, incpath) match {
207 case None => st;
208 case newst@Some((v, p)) => st match {
209 case None => newst
210 case Some((vv, _)) if v == vv => st
211 case Some((vv, pp)) =>
212 throw new AmbiguousConfig(key, v, p, vv, pp)
213 }
214 }
215 } match {
216 case None => cache(key) = NotFound; None
217 case Some((v, p)) =>
218 cache(key) = Found(v, p dropRight path.length);
219 Some((v, p))
220 }
221 }
222
223 def get(key: String, resolve: Boolean = true,
224 path: Seq[String] = Nil): String = {
225 val v0 = key match {
226 case "name" => itemmap.getOrElse("name", name)
227 case _ => get_internal(key).
228 getOrElse(throw new MissingConfigItem(name, key, path))._1
229 }
230 expand(key, v0, resolve, path)
231 }
232
233 def expand(key: String, value: String, resolve: Boolean,
234 path: Seq[String]): String = {
235 val v1 = RX_REF.replaceAllIn(value, { m =>
236 Regex.quoteReplacement(get(m.group(1), resolve, path))
237 });
238 val v2 = if (!resolve) v1
239 else RX_RESOLVE.replaceAllIn(v1, { m =>
240 resolver.resolve(m.group(2), m.group(1)) match {
241 case Nil =>
242 throw new NoHostAddresses(name, key, m.group(2));
243 case addrs =>
244 Regex.quoteReplacement((addrs map { _.getHostAddress }).
245 mkString(" "));
246 }
247 })
248 v2
249 }
250
251 def items: Seq[String] = {
252 val b = Seq.newBuilder[String];
253 val seen = HashSet[String]();
254 val visiting = HashSet[String](name);
255 var stack = List(this);
256
257 while (stack != Nil) {
258 val sect = stack.head; stack = stack.tail;
259 for (k <- sect.itemmap.keys)
260 if (!(seen contains k)) { b += k; seen += k; }
261 for (p <- sect.parents)
262 if (!(visiting contains p.name))
263 { stack ::= p; visiting += p.name; }
264 }
265 b.result
266 }
267 }
268 val sectmap = new HashMap[String, Section];
269 def sections: Iterator[Section] = sectmap.values.iterator;
270 def section(name: String): Section =
271 sectmap.getOrElse(name, throw new MissingConfigSection(name));
272
273 val resolver = new BulkResolver;
274
275 def parseFile(path: File): this.type = {
276println(s";; parse ${path.getPath}");
277 with_cleaner { clean =>
278 val in = new FileReader(path); clean { in.close(); }
279
280 val lno = 1;
281 val b = new StringBuilder;
282 var key: String = null;
283 var sect: Section = null;
284 def flush() {
285 if (key != null) {
286 sect.itemmap(key) = b.result;
287println(s";; in `${sect.name}', set `$key' to `${b.result}'");
288 b.clear();
289 key = null;
290 }
291 }
292 for (line <- lines(in)) line match {
293 case RX_COMMENT() =>
294 ();
295 case RX_GRPHDR(grp) =>
296 flush();
297 sect = sectmap.getOrElseUpdate(grp, new Section(grp));
298 case RX_CONT(v) =>
299 if (key == null) {
300 throw new ConfigSyntaxError(
301 path, lno, "no config value to continue");
302 }
303 b += '\n'; b ++= v;
304 case RX_ASSGN(k, v) =>
305 if (sect == null) {
306 throw new ConfigSyntaxError(
307 path, lno, "no active section to update");
308 }
309 flush();
310 key = k; b ++= v;
311 case _ =>
312 throw new ConfigSyntaxError(path, lno, "incomprehensible line");
313 }
314 flush();
315 }
316 this
317 }
318 def parse(path: File): this.type = {
319 if (!path.isDirectory) parseFile(path);
320 else for {
321 f <- path.listFiles sortBy { _.getName };
322 name = f.getName;
323 if name.length > 0;
324 tail = name(name.length - 1);
325 if tail != '#' && tail != '~'
326 } parseFile(f);
327 this
328 }
329 def parse(path: String): this.type = parse(new File(path));
330
331 def analyse() {
332println(";; resolving all...");
333 for ((_, sect) <- sectmap) {
334println(s";; resolving in section `${sect.name}'...");
335 for (key <- sect.items) {
336println(s";; resolving in key `$key'...");
337 val mm = RX_RESOLVE.findAllIn(sect.get(key, false));
338 for (host <- mm) { resolver.prepare(mm.group(2)); }
339 }
340 }
341 resolver.finish();
342
343 def dumpsect(sect: Section) {
344 for (k <- sect.items.filterNot(_.startsWith("@")).sorted)
345 println(s";; `$k' -> `${sect.get(k)}'")
346 }
347 for (sect <- sectmap.values.toSeq sortBy { _.name })
348 if (sect.name.startsWith("@")) ();
349 else if (sect.name.startsWith("$")) {
350 println(s";; special section `${sect.name}'");
351 dumpsect(sect);
352 } else {
353 println(s";; peer section `${sect.name}'");
354 dumpsect(sect);
355 }
356 }
357}
358
359}