!759 sclass Post extends ELPost { Dialog dialog; *() {} *(ELPost p, Dialog *dialog) { copyFields(p, this); } } sclass Dialog { File file; long modified, fileSize; S dialog, progID; L log; } static L dialogs = synchroList(); static MultiMap lowerCaseIndex = new MultiMap(true); p { loadAll(); print("Updating every 10 seconds, full scan every 5 minutes."); thread "Big Collector Updater" { int counter = 0; while licensed { pcall { if (counter++ >= 5*60/10) { print("Full scan."); counter = 0; loadAll(); } else { int n = loadChangedLogs(); /*if (n != 0) print(n(n, "log") + " updated");*/ } } sleepSeconds(10); } } makeBot("Big Collector."); } svoid loadAll { long time = now(); for (File f : allEventLogs()) loadLog(f); done("Loaded " + n(l(dialogs), "dialog") + ", " + n(numberOfLines(), "line"), time); } ssvoid loadLog(File f) { pcall { File dialogDir = f.getParentFile(); S dialog = dialogDir.getName(); S progID = dialogDir.getParentFile().getName(); if (!isSnippetID(progID)) { if (!isSnippetID(dialog)) ret; progID = dialog; dialog = "_"; } progID = formatSnippetID(progID); print("Loading dialog: " + progID + " / " + dialog); final new Dialog d; d.file = f; d.modified = f.lastModified(); d.fileSize = f.length(); d.dialog = dialog; d.progID = progID; pcall { d.log = map(func(ELPost p) { new Post(p, d) }, scanEventLogForPosts(f)); } addDialog(d); } } static void unindexDialog(Dialog d) { for (Post post : d.log) lowerCaseIndex.remove(toLower(post.text), post); } static void indexDialog(Dialog d) { for (Post post : d.log) lowerCaseIndex.put(toLower(post.text), post); } synchronized svoid addDialog(Dialog d) { for (Dialog old : getWhere(dialogs, "dialog", d.dialog, "progID", d.progID)) { unindexDialog(old); dialogs.remove(old); } indexDialog(d); dialogs.add(d); } static int numberOfLines() { int n = 0; for (Dialog d : dialogs) { n += l(d.log); } ret n; } static int loadChangedLogs() { int n = 0; for (Dialog d : cloneList(dialogs)) if (d.file.length() != d.fileSize) { loadLog(d.file); ++n; } ret n; } synchronized answer { if "load all dialogs" { loadAll(); ret "ok"; } if "load dialog * in *" { File f = getProgramFile(m.unq(1), m.unq(0)); if (!f.exists()) ret format("not found: *", f); loadLog(f); ret "OK"; } if "* posts that swic *" { final S pat = m.unq(1); L l = concatValues(keysStartingWith(pat, (TreeMap) lowerCaseIndex.data)); if (eq(m.unq(0), "find")) ret structure(map("formatPost", l)); else if (eq(m.unq(0), "count")) ret lstr(l); } if "* posts that * *" { final S pat = m.unq(1); final O pred = makePred(m.unq(1), m.unq(2)); if (pred != null) if (eq(m.unq(0), "count")) ret str(countPosts(pred)); else if (eq(m.unq(0), "find")) ret structure(findPosts(pred)); } } static O makePred(S a, final S pat) { if (eq(a, "swic")) ret func(ELPost p) { swic(p.text, pat) }; if (eq(a, "containsIgnoreCase")) ret func(ELPost p) { containsIgnoreCase(p.text, pat) }; if (eq(a, "match")) ret func(ELPost p) { match(pat, p.text) }; if (eq(a, "jmatch")) ret func(ELPost p) { jmatch(pat, p.text) }; null; } static L findPosts(O pred) { new L posts; for (final Dialog d : cloneList(dialogs)) { L filtered = filter(pred, d.log); for (Post p : filtered) posts.add(formatPost(p)); } ret posts; } static Map formatPost(Post p) { ret litorderedmap("text", p.text, "prog", p.dialog.progID, "dialog", p.dialog.dialog, "time", p.chatTime); } static int countPosts(O pred) { int n = 0; for (final Dialog d : cloneList(dialogs)) n += nfilter(pred, d.log); ret n; }