Download Jar. Uses 1658K of libraries. Click here for Pure Java version (26075L/154K).
1 | !7 |
2 | |
3 | // See #1023660 for the older 99 lines version |
4 | |
5 | !include once #1024472 // agi.blue core db |
6 | |
7 | // global constants |
8 | |
9 | static SS searchTypeToText = litmap( |
10 | leven := "Leven", |
11 | literal := "Literal", |
12 | scored := "Scored"); |
13 | |
14 | // global vars |
15 | |
16 | static int sourceCodeLines; |
17 | |
18 | static RealAGIBlue agiBlue; |
19 | static Renderer renderer = new Renderer1; |
20 | |
21 | set flag AllPublic. |
22 | |
23 | sS classesToShare = [[ |
24 | DynamicObject Concept Concepts Page Entry Slice LoadedSlice |
25 | IConceptIndex IFieldIndex IConceptCounter ConceptFieldIndexCI ConceptFieldIndexDesc |
26 | AbstractEntry Signer Session User GoogleUser MinimalUser CookieUser |
27 | BlobEntry |
28 | SliceInfo CentralIndexEntry AGIBlue RealAGIBlue TimedCache |
29 | GlobalID Renderer |
30 | IterableIterator CloseableIterableIterator |
31 | ]]; |
32 | |
33 | // end of global data |
34 | |
35 | p { |
36 | agiBlue = new RealAGIBlue; |
37 | } |
38 | |
39 | svoid _onLoad_sourceCodeLines { |
40 | sourceCodeLines = countLines(mySource()); |
41 | } |
42 | |
43 | svoid cleanMeUp { |
44 | cleanUp(agiBlue); |
45 | agiBlue = null; |
46 | } |
47 | |
48 | sinterface Renderer { |
49 | O html(S uri, SS params); |
50 | } |
51 | |
52 | set flag NoNanoHTTPD. html { |
53 | printWithTime("Starting request"); |
54 | O o = renderer.html(uri, params); |
55 | printWithTime("Done with request"); |
56 | ret o; |
57 | } |
58 | |
59 | sclass RealAGIBlue > AGIBlue { |
60 | volatile bool started, broken; |
61 | TimedCache<Long> sizeOnDisk = new(60.0, f guessClusteredSizeOfProgramDirWithoutBackups); |
62 | |
63 | *() { |
64 | |
65 | try { |
66 | if (bootstrapDataFrom(#1023558)) deleteMyBackups(); |
67 | thinMyBackups(); |
68 | fan = slicesLoadedOnDemand(loadedSlices); |
69 | mainConcepts = fan.get("", null); |
70 | |
71 | // index main DB |
72 | |
73 | indexConceptFields(Slice, 'globalID, Slice, 'caseID); |
74 | indexConceptField(CookieUser, 'cookie); |
75 | idx_slicesByName = new ConceptFieldIndexCI(Slice, 'name); |
76 | idx_slicesByModification = new ConceptFieldIndexDesc(Slice, '_modified); |
77 | idx_usersByName = new ConceptFieldIndexCI(GoogleUser, 'googleLastName); |
78 | idx_usersBySeen = new ConceptFieldIndexDesc(User, 'lastSeen); |
79 | |
80 | cset(uniq_returnIfNew Slice(caseID := ""), name := "main slice"); |
81 | loadedSlices.get("").initialSetup(toGlobalIDObj(mainSliceGlobalID())); |
82 | loadedSlices.get("").sliceConcept = sliceConceptForCaseID(""); |
83 | |
84 | backgroundFan = slicesLoadedOnDemand(backgroundSlices); |
85 | backgroundFan.lock = fan.lock; |
86 | |
87 | // legacy conversions! |
88 | //deleteConcepts(Slice, globalID := GlobalID('wftlawbagrwprywn)); |
89 | |
90 | // Approve this machine's key |
91 | PKIKeyPair machineKey = agiBot_trustedKeyForMachine(); |
92 | if (machineKey != null) { |
93 | print("Approving this machine's key: " + machineKey.publicKey); |
94 | cset(uniq_sync(Signer, publicKey := machineKey.publicKey), trusted := true, approvedBy := "local"); |
95 | } |
96 | |
97 | rst_index.trigger(); |
98 | } catch print e { |
99 | set broken; |
100 | } finally { |
101 | set started; |
102 | } |
103 | |
104 | // do stuff after we are officially started |
105 | |
106 | // calculate DB size regularly iff there were changes |
107 | sizeOnDisk.keepValueWhileCalculating = true; |
108 | doEveryAndNow(60.0, rWatcher(() -> db_mainConceptsChangeCount(), r { sizeOnDisk! })); |
109 | |
110 | } // end of main program |
111 | |
112 | L<Entry> entriesOnPage(Page p) { |
113 | ret p == null ? null : sortedByField count(findBackRefs(p, Entry)); |
114 | } |
115 | |
116 | L<Page> pagesSortedByLength(L<Page> l) { |
117 | ret sortedByCalculatedField(l, p -> l(p.q)); |
118 | } |
119 | |
120 | sbool agiBlue_isOriginal() { |
121 | ret amProgram(#1023558); |
122 | } |
123 | |
124 | S agiBlueURL(S subUri) { ret agiBlueURL() + prependSlash(subUri); } |
125 | |
126 | S agiBlueURL() { |
127 | ret agiBlue_isOriginal() ? "https://agi.blue" : "/" + psI(programID()) + "/raw"; |
128 | } |
129 | |
130 | S agiBlueName() { |
131 | ret agiBlue_isOriginal() |
132 | ? "agi.blue" |
133 | : isProgramID(#1024512) ? "agi.blue productive" |
134 | : "agi.blue clone " + programID(); |
135 | } |
136 | |
137 | void cleanMeUp { |
138 | cleanUp(fan); |
139 | } |
140 | |
141 | LoadedSlice loadSlice(Slice slice) { |
142 | ret loadSlice(slice.caseID, str(slice.globalID)); |
143 | } |
144 | |
145 | LoadedSlice loadSlice(S caseID, S globalID) { |
146 | caseID = unnull(caseID); |
147 | lock fan.lock; |
148 | |
149 | // move slice from background to foreground |
150 | |
151 | Concepts cc = backgroundFan.getIfLoaded(caseID); |
152 | if (cc != null) { |
153 | LoadedSlice slice = assertNotNull(backgroundSlices.get(caseID)); |
154 | backgroundFan.loaded.remove(caseID); |
155 | fan.loaded.put(caseID, cc); |
156 | backgroundSlices.remove(caseID); |
157 | loadedSlices.put(caseID, slice); |
158 | ret slice; |
159 | } |
160 | |
161 | fan.get(caseID, globalID); |
162 | ret loadedSlices.get(caseID); |
163 | } |
164 | |
165 | void unloadSlice(S caseID) { |
166 | fan.unloadCase(caseID); |
167 | backgroundFan.unloadCase(caseID); |
168 | } |
169 | |
170 | // load background slice |
171 | LoadedSlice loadBackgroundSlice(Slice slice) { |
172 | ret loadBackgroundSlice(slice.caseID, str(slice.globalID)); |
173 | } |
174 | |
175 | LoadedSlice loadBackgroundSlice(S caseID, S globalID) { |
176 | caseID = unnull(caseID); |
177 | lock fan.lock; |
178 | |
179 | // check if in foreground |
180 | |
181 | if (fan.getIfLoaded(caseID) != null) |
182 | ret loadedSlices.get(caseID); |
183 | |
184 | backgroundFan.get(caseID, globalID); |
185 | LoadedSlice slice = assertNotNull(backgroundSlices.get(caseID)); |
186 | slice.lastAccess = sysNow(); |
187 | while (l(backgroundSlices) > maxBackgroundSlices) |
188 | unloadLeastRecentlyAccessedBackgroundSlice(); |
189 | assertNotNull("slice.sliceConcept", slice.sliceConcept); |
190 | ret slice; |
191 | } |
192 | |
193 | // TODO: sync unloading slice with accessors |
194 | void unloadLeastRecentlyAccessedBackgroundSlice() { |
195 | lock fan.lock; |
196 | S caseID = lowestByField lastAccess(keys(backgroundSlices)); |
197 | if (caseID != null) |
198 | backgroundFan.unloadCase(caseID); |
199 | } |
200 | |
201 | Slice sliceConceptForGlobalID(S globalID) { |
202 | ret sliceConceptForGlobalID(toGlobalIDObj(globalID)); |
203 | } |
204 | |
205 | Slice sliceConceptForGlobalID(GlobalID globalID) { |
206 | ret conceptWhere Slice(+globalID); |
207 | } |
208 | |
209 | Slice sliceConceptForCaseID(S caseID) { |
210 | ret conceptWhere Slice(+caseID); |
211 | } |
212 | |
213 | Slice sliceForName(S name) { |
214 | ret conceptWhereCI Slice(+name); |
215 | } |
216 | |
217 | class Query extends WorksOnSlice { |
218 | SS vars = ciMap(); |
219 | |
220 | *(LoadedSlice slice) { super(slice); } |
221 | |
222 | O process(S query) { |
223 | try object processLines(agiBlue_parseQueryScript(query)); |
224 | ret serveJSON("No return statement"); |
225 | } |
226 | |
227 | O processLines(L<ALQLLine> lines) { |
228 | for (ALQLLine line : lines) { |
229 | if (line cast ALQLSlice) { |
230 | Slice slice = sliceForName(line.slice); |
231 | if (slice == null) ret serveJSON("Slice not found: " + line.slice); |
232 | LoadedSlice oldSlice = Query.this.slice; |
233 | try { |
234 | setSlice(loadSlice(slice)); |
235 | try object processLines(line.contents); |
236 | } finally { |
237 | setSlice(oldSlice); |
238 | } |
239 | } else if (line cast ALQLReturn) |
240 | ret serveJSON(ll(getOrKeep(vars, line.var))); |
241 | else if (line cast ALQLReturnTuple) |
242 | ret serveJSON(map(v -> getOrKeep(vars, v), line.vars)); |
243 | else if (line cast ALQLTriple) { |
244 | T3S t = line.triple; |
245 | t = tripleMap(t, s -> getOrKeep(vars, s)); |
246 | Cl<Page> pages; |
247 | S var; |
248 | if (isDollarVar(t.c)) { |
249 | var = t.c; |
250 | if (isDollarVar(t.a)) { |
251 | if (isDollarVar(t.b)) todo(t); |
252 | Entry e = random(conceptsWhereCI(cc, Entry, key := t.b)); |
253 | if (e == null) ret serveJSON("No results for " + var); |
254 | pages = pagesForKeyAndValue(t.b, t.c); |
255 | vars.put(t.a, e.page->q); |
256 | vars.put(t.c, e.value); |
257 | continue; |
258 | } else if (isDollarVar(t.b)) { |
259 | Page page = findPageFromQ(t.a); |
260 | Entry e = random(findBackRefs(page, Entry)); |
261 | if (e == null) ret serveJSON("No results for " + var); |
262 | vars.put(t.b, e.key); |
263 | vars.put(t.c, e.value); |
264 | continue; |
265 | } else { |
266 | S val = getValue(t.a, t.b); |
267 | if (val == null) ret serveJSON("No results for " + var); |
268 | pages = ll(pageFromQ(val)); |
269 | } |
270 | } else if (isDollarVar(t.b)) { |
271 | var = t.b; |
272 | if (isDollarVar(t.c)) todo(t); |
273 | if (isDollarVar(t.a)) { |
274 | L<Entry> entries = conceptsWhereCI(cc, Entry, value := t.c); |
275 | if (empty(entries)) ret serveJSON("No results for " + var); |
276 | Entry e = random(entries); |
277 | vars.put(t.a, e.page->q); |
278 | vars.put(t.b, e.key); |
279 | continue; |
280 | } else { |
281 | Cl<S> keys = keysForPageAndValue(t.a, t.c); |
282 | if (empty(keys)) ret serveJSON("No results for " + var); |
283 | pages = map(f<S, Page> pageFromQ, keys); |
284 | } |
285 | } else { |
286 | var = t.a; |
287 | if (!isDollarVar(t.a)) todo(t); |
288 | if (isDollarVar(t.b)) todo(t); |
289 | if (isDollarVar(t.c)) todo(t); |
290 | pages = pagesForKeyAndValue(t.b, t.c); |
291 | } |
292 | if (empty(pages)) ret serveJSON("No results for " + var); |
293 | vars.put(var, random(pages).q); |
294 | } else if (line cast ALQLPage) { |
295 | if (eq(line.matchMethod, 'eqic)) |
296 | findPageFromQ(line.page); |
297 | else if (eq(line.matchMethod, 'flexMatchDollarVarsIC_first)) { |
298 | new L<SS> l; |
299 | for (Page p : list(cc, Page)) |
300 | addIfNotNull(l, flexMatchDollarVarsIC_first(line.page, p.q)); |
301 | if (empty(l)) ret serveJSON("No results for " + curly(line.page)); |
302 | vars.putAll(random(l)); |
303 | } else |
304 | fail("Unknown match method: " + line.matchMethod); |
305 | } else |
306 | fail("Can't interpret: " + line); |
307 | } |
308 | |
309 | null; |
310 | } |
311 | } |
312 | |
313 | /*S globalIDFromCaseID(S caseID) { |
314 | ret assertGlobalID(takeLast(globalIDLength(), caseID)); |
315 | }*/ |
316 | |
317 | S agiBlue_pageURLWithSlice(Page p) { |
318 | ret p == null ? null : agiBlueURL() + hquery(slice := _get(sliceForPage(p), 'globalID), q := p.q); |
319 | } |
320 | |
321 | Slice sliceForPage(Page p) { |
322 | if (p == null) null; |
323 | SliceInfo info = conceptWhere(p._concepts, SliceInfo); |
324 | ret info == null ? null : sliceConceptForGlobalID(info.globalID); |
325 | } |
326 | |
327 | GlobalID mainSliceGlobalID() { |
328 | ret conceptWhere Slice(caseID := "").globalID; |
329 | } |
330 | |
331 | ConceptsLoadedOnDemand slicesLoadedOnDemand(Map<S, LoadedSlice> loadedSlices) { |
332 | new ConceptsLoadedOnDemand fan; |
333 | fan.onCaseLoaded(voidfunc(S caseID, O globalID, Concepts concepts) { |
334 | LoadedSlice ls = new(caseID, concepts); |
335 | concepts.miscMap = mapPutOrCreate_multi(concepts.miscMap, |
336 | agiBlue := RealAGIBlue.this, |
337 | loadedSlice := ls); |
338 | ls.sliceConcept = sliceConceptForCaseID(caseID); |
339 | |
340 | if (nempty(globalID)) |
341 | ls.initialSetup(toGlobalIDObj((S) globalID)); |
342 | loadedSlices.put(caseID, ls); |
343 | }); |
344 | fan.onUnloadingCase(voidfunc(S caseID, Concepts concepts) { |
345 | loadedSlices.remove(caseID); |
346 | }); |
347 | ret fan; |
348 | } |
349 | |
350 | // check if slices are loaded both in foreground and background (BAD) |
351 | S checkDoubleLoads() { |
352 | LS l = sharedKeys(loadedSlices, backgroundSlices); |
353 | ret empty(l) ? null : "ERROR: Slice loaded in background and foreground: " + first(l); |
354 | } |
355 | |
356 | // just use size of concepts.structure.gz |
357 | long estimatedSliceDataSize(Slice slice) { |
358 | ret estimatedSliceDataSize(slice.caseID); |
359 | } |
360 | |
361 | long estimatedSliceDataSize(S caseID) { |
362 | ret l(conceptsFile(fan.dbID(caseID))); |
363 | } |
364 | |
365 | // restart magic! |
366 | void softRestart { // TODO |
367 | print("Cloning..."); |
368 | cloningSince = sysNow(); |
369 | Class c = hotwireDependent(programID()); |
370 | |
371 | // New strategy: Share AGIBlue & just grab the new renderer :) |
372 | |
373 | copyFields(mc(), c, 'agiBlue, 'mainConcepts, 'creator_class); |
374 | renderer = (Renderer) get renderer(c); |
375 | print("Got new renderer."); |
376 | |
377 | // TODO: any clean up of this instance? |
378 | } |
379 | } // end of RealAGIBlue |
380 | |
381 | sclass WorksOnSlice { |
382 | delegate LoadedSlice to AGIBlue. |
383 | |
384 | Concepts cc; |
385 | LoadedSlice slice; |
386 | |
387 | *() {} |
388 | *(LoadedSlice *slice) { cc = slice.cc; } |
389 | |
390 | void setSlice(LoadedSlice slice) { |
391 | this.slice = slice; |
392 | cc = slice.cc; |
393 | } |
394 | |
395 | Page findPageFromParams(Map map) { |
396 | S q = getString q(map); |
397 | ret empty(q) ? null : findPageFromQ(q); |
398 | } |
399 | |
400 | Page findOrMakePageFromParams(Map map) { |
401 | ret pageFromQ(getString q(map)); |
402 | } |
403 | |
404 | Page findPageFromQ(S q) { |
405 | ret conceptWhereCI(cc, Page, +q); |
406 | } |
407 | |
408 | Page pageFromQ(S q) { |
409 | ret slice.pageFromQ(q); |
410 | } |
411 | |
412 | Set<Page> pagesForKeyAndValue(S key, S value) { |
413 | L<Entry> entries = conceptsWhereIC(cc, Entry, +value, +key); |
414 | ret asSet(ccollect page(entries)); |
415 | } |
416 | |
417 | Set<Page> pagesWithKey(S key) { |
418 | L<Entry> entries = conceptsWhereIC(cc, Entry, +key); |
419 | ret asSet(ccollect page(entries)); |
420 | } |
421 | |
422 | Cl<S> keysForPageAndValue(S q, S value) { |
423 | Page page = findPageFromQ(q); |
424 | if (page == null) null; |
425 | ret collect key(conceptsWhereIC(cc, Entry, +page, +value)); |
426 | } |
427 | |
428 | // DB functions |
429 | |
430 | bool hasPage(S q) { ret hasConceptWhereIC(Page, +q); } |
431 | S getValue(Page page, S key) { |
432 | ret page == null || empty(key) ? null : getString value(highestByField count(objectsWhereIC(findBackRefs(page, Entry), +key))); |
433 | } |
434 | S getValue(S page, S key) { |
435 | ret getValue(findPageFromQ(page), key); |
436 | } |
437 | S pageDisplayName(Page page) { |
438 | /*S name = getValue(page, "read as"); |
439 | bool unnaturalName = nempty(name) && !eq(makeAGIDomain(name), page.url); |
440 | ret unnaturalName ? name + " " + squareBracketed(page.url) : or2(name, unpackAGIDomainOpt(page.url));*/ |
441 | ret page.q; |
442 | } |
443 | |
444 | S renderThing(S s, bool forceURLDisplay) { |
445 | ret forceURLDisplay || isURL(s) || isAGIDomain(s) |
446 | ? ahref(fixAGILink(absoluteURL(s)), htmlencode2(shorten(agiBlue.displayLength, s))) |
447 | : ahref(agiBlue_linkForPhrase(s, |
448 | slice := slice.isMainSlice() ? null : slice.sliceConcept.globalID), |
449 | htmlencode2(shorten(agiBlue.displayLength, s))); |
450 | } |
451 | |
452 | GlobalID sliceID() { ret slice.globalID(); } |
453 | |
454 | SliceInfo sliceInfo() { ret slice.sliceInfo; } |
455 | |
456 | } // end of WorksOnSlice |
457 | |
458 | |
459 | sclass Renderer1 implements Renderer { |
460 | // Serve page |
461 | |
462 | O html(S uri, SS params) { |
463 | sleepWhile(() -> !agiBlue.started); |
464 | if (agiBlue.broken) ret subBot_serve500("Internal error"); |
465 | ret new Request().serve(uri, params); |
466 | } |
467 | |
468 | // forward things to AGIBlue object |
469 | delegate agiBlue_pageURLWithSlice to agiBlue. |
470 | delegate newCentralIndexGet to agiBlue. |
471 | delegate centralIndexMade to agiBlue. |
472 | delegate agiBlueURL to agiBlue. |
473 | delegate dumpSliceToFile to agiBlue. |
474 | delegate checkDoubleLoads to agiBlue. |
475 | delegate asyncSearch to agiBlue. |
476 | delegate centralIndexGetSlices to agiBlue. |
477 | delegate dbLog to agiBlue. |
478 | delegate entryToTriple to agiBlue. |
479 | delegate loadSlice to agiBlue. |
480 | delegate agiBlueName to agiBlue. |
481 | delegate idx_slicesByModification to agiBlue. |
482 | delegate idx_slicesByName to agiBlue. |
483 | delegate idx_usersBySeen to agiBlue. |
484 | delegate idx_usersByName to agiBlue. |
485 | delegate agiBlue_isOriginal to agiBlue. |
486 | delegate loadBackgroundSlice to agiBlue. |
487 | delegate maxSliceNameLength to agiBlue. |
488 | delegate pagesSortedByLength to agiBlue. |
489 | delegate entriesOnPage to agiBlue. |
490 | delegate centralIndex to agiBlue. |
491 | delegate showGoogleLogIn to agiBlue. |
492 | delegate rst_index to agiBlue. |
493 | delegate LoadedSlice to AGIBlue. |
494 | delegate centralIndexMadeIn to agiBlue. |
495 | delegate sideSearchType to agiBlue. |
496 | delegate sideDisplayLength to agiBlue. |
497 | delegate sliceConceptForGlobalID to agiBlue. |
498 | delegate mainSliceGlobalID to agiBlue. |
499 | delegate sliceForName to agiBlue. |
500 | |
501 | class Request extends WorksOnSlice { |
502 | S cookie; |
503 | Session session; |
504 | Slice sliceConcept; |
505 | S uri; |
506 | SS params; |
507 | long started = sysNow(); |
508 | S q, domain; |
509 | Set<S> get; |
510 | |
511 | // when serving a concept page, info that may be grabbed |
512 | // by thought bots |
513 | L<Entry> usesAsKey; |
514 | |
515 | // should also work for standalone |
516 | bool isHomeDomain() { |
517 | S domain = domain(); |
518 | ret eqic(domain, "www.agi.blue") || !ewic(domain, ".agi.blue"); |
519 | } |
520 | |
521 | O serve(S uri, SS params) { |
522 | this.uri = dropMultipleSlashes(uri); |
523 | this.params = params; |
524 | new Matches m; |
525 | |
526 | print(uri + " ? " + unnull(subBot_query())); |
527 | |
528 | // get cookie & session |
529 | |
530 | cookie = cookieFromUser(); |
531 | if (cookie != null) |
532 | { |
533 | session = uniq_sync(Session, +cookie); |
534 | if (session.user! == null) |
535 | cset(session, user := uniq_sync CookieUser(+cookie)); |
536 | } else |
537 | session = unlistedWithValues(Session); |
538 | |
539 | // check if user wants to change slices |
540 | |
541 | S selectSlice = params.get('slice); |
542 | if (isGlobalID(selectSlice)) |
543 | cset(session, selectedSlice := selectSlice); |
544 | if (session.selectedSlice == null) |
545 | cset(session, selectedSlice := str(mainSliceGlobalID())); |
546 | |
547 | // get slice's case ID |
548 | |
549 | S caseID = ""; |
550 | sliceConcept = sliceConceptForGlobalID(session.selectedSlice); |
551 | print("Selected slice: " + session.selectedSlice + ", obj? " + (sliceConcept != null)); |
552 | if (sliceConcept != null) caseID = sliceConcept.caseID; |
553 | print("caseID: " + caseID); |
554 | |
555 | // load slice |
556 | |
557 | slice = assertNotNull(loadSlice(caseID, session.selectedSlice)); |
558 | cc = slice.cc; |
559 | |
560 | // Check for special URIs |
561 | |
562 | if (swic(uri, "/bot/")) ret serveBot(); |
563 | |
564 | if (swic(uri, "/user/", m)) { |
565 | User user = conceptWhere User(globalID := toGlobalIDObj(assertGlobalID(m.rest()))); |
566 | if (user == null) ret subBot_serve404("User not found"); |
567 | S title = "User " + user; |
568 | ret hhtml_miniPage(title, h3(agiBlueNameHTML() + " | " + title) |
569 | + "User type: " + classShortName(user)); |
570 | } |
571 | |
572 | // eleu appends a slash to the URI if it's a single identifier, so we drop it again |
573 | S uri2 = dropTrailingSlash(uri); |
574 | |
575 | if (eqic(uri2, "/google-verify")) { |
576 | print("Google-verify started."); |
577 | Payload payload = printStruct(googleVerifyUserToken2(googleSignInID(), params.get("token"))); |
578 | if (payload == null) ret print("google-verify", "No"); |
579 | S email = payload.getEmail(); |
580 | if (empty(email)) ret print("google-verify", "No"); |
581 | |
582 | GoogleUser user = uniqCI_sync GoogleUser(googleEmail := email); |
583 | cset(user, |
584 | googleEmailVerified := payload.getEmailVerified(), |
585 | googleFirstName := strOrNull(payload.get("given_name")), |
586 | googleLastName := strOrNull(payload.get("family_name"))); |
587 | |
588 | cset(session, |
589 | +user); |
590 | |
591 | ret print("google-verify", payload.getEmail() + " " + (payload.getEmailVerified() ? "(verified)" : "(not verified)")); |
592 | } |
593 | |
594 | cset(session.user!, lastSeen := now()); |
595 | |
596 | if (nempty(params.get('searchAction))) ret serveSearchAction(); |
597 | |
598 | if (eqic(uri2, "/users")) ret serveUsersList(); |
599 | |
600 | if (eqic(uri2, "/slices")) ret serveSlicesList(); |
601 | |
602 | if (eqic(uri2, "/classes")) ret serveClasses(); |
603 | |
604 | if (eqic(uri2, "/search")) ret serveScoredSearch(); |
605 | if (eqic(uri2, "/literalSearch")) ret serveLiteralSearch(); |
606 | if (eqic(uri2, "/levenSearch")) ret serveLevenSearch(); |
607 | |
608 | if (eqic(uri2, "/query")) ret serveQueryPage(); |
609 | if (eqic(uri2, "/createSlice")) ret serveCreateSlicePage(); |
610 | if (eqic(uri2, "/deletePage")) ret serveDeletePage(); |
611 | if (eqic(uri2, "/deleteSlice")) ret serveDeleteSlice(); |
612 | |
613 | if (eqic(uri2, "/checkboxes")) ret serveCheckboxes(); |
614 | if (eqic(uri2, "/multiAdd")) ret serveMultiAdd(); |
615 | |
616 | if (eqic(uri2, "/version")) ret myTranspilationDate(); |
617 | |
618 | if (eqic(uri2, "/restart") && authed()) { |
619 | agiBlue.softRestart(); |
620 | ret "OK"; |
621 | } |
622 | |
623 | q = params.get('q); |
624 | get = asCISet(nempties(subBot_paramsAsMultiMap().get('get))); |
625 | |
626 | domain = or2(params.get('domain), domain()); |
627 | |
628 | S raw = firstKeyWithValue("", params); // agi.blue?something |
629 | if (nempty(raw) && empty(q)) q = raw; |
630 | /*if (nempty(q)) { |
631 | domain = makeAGIDomain(q); |
632 | if (l(domain) > maximumDomainPartLength()) // escape with "domain=" |
633 | ret hrefresh(agiBlueURL() + hquery(+domain, key := "read as", value := q)); |
634 | ret hrefresh("http://" + domain + (eq(q, domain) ? "" : "/" + hquery(key := "read as", value := q))); |
635 | //uri = "/"; replaceMapWithParams(params, key := "read as", value := q); |
636 | }*/ |
637 | S url = domain + dropTrailingSlash(uri); |
638 | |
639 | // domain to query |
640 | //if (empty(q)) q = url; |
641 | if (empty(q)) { |
642 | S qq = agiBlue_urlToQuery(url); |
643 | if (neqic(qq, "agi.blue")) q = qq; |
644 | } |
645 | |
646 | Page page, bool newPage = unpair uniqCI2_sync(cc, Page, +q); |
647 | if (newPage) dbLog("New page", +q); |
648 | //printStructs(+params, +raw, +q); |
649 | |
650 | if (empty(params.get('q)) && empty(raw) && isHomeDomain()) { |
651 | //L<Page> pages = sortedByFieldDesc _modified(list(cc, Page)); // TODO: use index |
652 | L<Page> pages = cloneList(slice.idx_latestChangedPages.objectIterator()); |
653 | int start = parseInt(params.get("start")), step = 100; |
654 | S nav = pageNav2("/", l(pages), start, step, "start"); |
655 | S content = hform(b("GIVE ME INPUT: ") + htextinput('q, autofocus := true) + " " + hsubmit("Search", name := 'searchAction) + " " + hsubmit("Add to slice")) |
656 | + h1(sliceAsHTML() + " has " + nPages(countConcepts(cc, Page)) + " and " + nConnections(countConcepts(cc, Entry))) |
657 | + p(nav) |
658 | + p_nemptyLines(map(pageToHTMLLink(), subList(pages, start, start+step))); |
659 | |
660 | ret hhtml_agiBlue(hhead_title("Slice " + sliceConcept().name + " | agi.blue") // SERVE SLICE HOME PAGE |
661 | + hbody(hfullcenterAndTopLeft(top() + content |
662 | + footer(), |
663 | sliceSelector() |
664 | ))); |
665 | } |
666 | |
667 | S key = trim(params.get('key)), value = trim(params.get('value)); |
668 | L<Entry> entries = agiBlue.new GetEntriesAndPost(cc).go(page, params).entries; |
669 | |
670 | //S get = params.get('get); |
671 | if (nempty(get)) |
672 | ret serveJSON(collect value(llNotNulls(firstThat(entries, e -> get.contains(e.key))))); |
673 | |
674 | S key2 = key, value2 = value; if (nempty(key) && nempty(value)) key2 = value2 = ""; // input reset |
675 | |
676 | bool withHidden = eq(params.get('withHidden), "1"); |
677 | new Set<Int> hide; |
678 | if (!withHidden) for (Entry e : entries) if (eqic(e.key, 'hide) && isSquareBracketedInt(e.value)) addAll(hide, e.count, parseInt(unSquareBracket(e.value))); |
679 | new MultiMap<Int, S> mmMeta; |
680 | for (Entry e : entries) if (isSquareBracketedInt(e.key)) mmMeta.put(parseInt(unSquareBracket(e.key)), e.value); |
681 | new MultiMap<S> mm; |
682 | for (Entry e : entries) mm.put(e.key, e.value); |
683 | |
684 | //S name = or2(/* ouch */ last(mm.get("read as")), /* end ouch */ unpackAGIDomain(page.url), page.url); |
685 | S name = page.q; |
686 | |
687 | // Find references |
688 | |
689 | L<Entry> refs = concatLists(conceptsWhereIC(cc, Entry, value := name), |
690 | conceptsWhereIC(cc, Entry, key := name)); |
691 | Set<Page> refPages = asSet(ccollect page(refs)); |
692 | refPages.remove(page); // don't list current page as reference |
693 | |
694 | // Search in page names (depending on default search type) |
695 | |
696 | L<Page> searchResults; |
697 | int totalResults = 0, searchResultsToShow = 50; |
698 | if (eq(sideSearchType, 'leven)) |
699 | searchResults = levenSearch(page.q, max := searchResultsToShow); |
700 | else if (eq(sideSearchType, 'literal)) |
701 | { |
702 | searchResults = literalSearch(page.q, max := maxInt()); |
703 | print("totalResults", totalResults = l(searchResults)); |
704 | searchResults = takeFirst(searchResultsToShow, searchResults); |
705 | } else |
706 | searchResults = (L<Page>) dm_call('agiBlueSearch, 'search, page.q, maxResult := clipIntPlus(searchResultsToShow, 1), agiBlueBotID := programID(), +cc); |
707 | searchResults.remove(page); |
708 | |
709 | // Find uses as key |
710 | usesAsKey = conceptsWhereIC(cc, Entry, key := page.q); |
711 | |
712 | // Find page in other slices |
713 | CentralIndexEntry cie = centralIndex.get(page.q); |
714 | L<Slice> pageInOtherSlices = cie == null ? null : listMinus(cie.slices, slice.sliceConcept); |
715 | |
716 | S pageName_html = htmlEncode2(shorten(agiBlue.displayLength, name)); |
717 | |
718 | S mainContents = |
719 | top() + h1(ahref_unstyled(agiBlue_pageURLWithSlice(page), pageName_html) |
720 | + (!isURL(page.q) ? "" : " " + targetBlank(page.q, hsnippetimg(#1101876))) |
721 | + (newPage ? " [huh????]" : "")) |
722 | + p_nemptyLines(map(entries, func(Entry e) -> S { |
723 | !withHidden && (hide.contains(e.count) || eqic(e.key, "read as") && eqic(e.value, name)) ? "" |
724 | : "[" + e.count + "] " + |
725 | renderThing(e.key, false) + ": " + |
726 | b(renderThing(e.value, cic(mmMeta.get(e.count), "is a URL"))) |
727 | })) |
728 | + hpostform(h3("Add an entry") |
729 | + "Key: " + hinputfield(key := key2) + " Value: " + hinputfield(value := value2) + "<br><br>" + hsubmit("Add") |
730 | ) |
731 | |
732 | + p(ahref(agiBlueURL() + "/literalSearch" + hquery(q := page.q), "[literal search]", title := "Search pages with a name containing this page's name literally") |
733 | + " " + |
734 | ahref(agiBlueURL() + "/levenSearch" + hquery(q := page.q), "[leven search 1]", title := "Search pages with a Levenshtein similarity of 1 containing this page's name literally") |
735 | + " " + |
736 | ahref(agiBlueURL() + "/search" + hquery(q := page.q), "[scored search]", title := "Search pages with ScoredSearch") |
737 | + " " + |
738 | ahref(agiBlueURL() + "/deletePage" + hquery(slice := sliceID(), q := page.q), "[delete page]") |
739 | ) |
740 | |
741 | // optional "uses as key" section |
742 | |
743 | + (empty(usesAsKey) ? "" : h3(quote(pageName_html) + " as key") |
744 | + p_nemptyLines_showFirst(50, map(usesAsKey, e -> |
745 | pageToHTMLLink().get(e.page!) + unicode_spacedRightPointingTriangle() + |
746 | pageName_html + unicode_spacedRightPointingTriangle() + |
747 | pageToHTMLLink().get(pageFromQ(e.value)) |
748 | ))) |
749 | |
750 | // optional "in other slices" section |
751 | |
752 | + (empty(pageInOtherSlices) ? "" : h3(quote(pageName_html) + " in other slices") |
753 | + p_nemptyLines_showFirst(50, map(slice -> |
754 | ahref(agiBlueURL() + hquery(slice := slice.globalID, q := page.q), htmlEncode2(slice.name)), pageInOtherSlices))) |
755 | ; |
756 | |
757 | // end of mainContents |
758 | |
759 | S sideContents = |
760 | hform(b("GIVE ME INPUT:") + " " |
761 | + htextinput('q) + " " |
762 | + hsubmit("Ask", onclick := "document.getElementById('newInputForm').target = '';") + " " |
763 | + hsubmit("+Tab", title := "Ask and show result in a new tab", onclick := "document.getElementById('newInputForm').target = '_blank';"), |
764 | id := 'newInputForm) |
765 | |
766 | + h3("References (" + l(refPages) + ")") |
767 | |
768 | + p_nemptyLines_showFirst(10, map(pageToHTMLLink(displayLength := sideDisplayLength), refPages)) |
769 | |
770 | + h3(searchTypeToText.get(sideSearchType) + " search results (" |
771 | + (l(searchResults) >= searchResultsToShow ? searchResultsToShow + "+" : str(l(searchResults))) |
772 | + (totalResults > l(searchResults) ? " of " + n2(totalResults) : "") + ")") |
773 | |
774 | + p_nemptyLines_showFirst(searchResultsToShow, map(pageToHTMLLink(displayLength := sideDisplayLength), searchResults)) |
775 | |
776 | + hdiv("", id := 'extraStuff); |
777 | |
778 | // TODO: sync search delivery with WebSocket creation |
779 | if (asyncSearch) |
780 | doLater(6.0, r { dm_call('agiBlueSearch, 'searchAndPost, page.q, agiBlueBotID := programID(), +cc) }); |
781 | |
782 | // notify local interested parties |
783 | vmBus_send('agiBlue_servingConceptPage, this, page); |
784 | |
785 | S iframe = params.get('render_iframe); |
786 | |
787 | // serve a concept page |
788 | ret hhtml_agiBlue(hhead_title(pageDisplayName(page)) + hbody( |
789 | tag('table, |
790 | tr( |
791 | td(sliceSelector(), valign := 'top) |
792 | + td(sideContents, align := 'right, valign := 'top, rowspan := 2)) |
793 | + tr(td(mainContents + |
794 | (empty(iframe) ? "" : iframe(iframe, width := 600, height := 400)) + |
795 | footer(), align := 'center, valign := 'top)), |
796 | width := "100%", height := "100%"))); |
797 | } |
798 | |
799 | O servePagesToBot(Iterable<Page> pages) { |
800 | ret serveListToBot(map(pageToMap(wrapMapAsParams(params)), pages)); |
801 | } |
802 | |
803 | O serveListToBot(Collection l) { |
804 | if (nempty(params.get('max))) |
805 | l = takeFirst(parseInt(params.get('max)), l); |
806 | ret serveJSON(l); |
807 | } |
808 | |
809 | // uri starts with "/bot/" |
810 | O serveBot() { |
811 | S q = params.get('q); |
812 | |
813 | if (eqic(uri, "/bot/hello")) ret serveJSON("hello"); |
814 | if (eqic(uri, "/bot/hasPage")) ret serveJSON(hasPage(q)); |
815 | if (eqic(uri, "/bot/randomPageContaining")) { |
816 | assertNempty(q); |
817 | ret servePageToBot(random(filter(list(cc, Page), p -> cic(p.q, q))), params); |
818 | } |
819 | if (eqic(uri, "/bot/allPages")) |
820 | ret servePagesToBot(list(cc, Page)); |
821 | if (eqic(uri, "/bot/allPagesStartingWith")) { |
822 | assertNempty(q); |
823 | ret servePagesToBot(filter(list(cc, Page), p -> swic(p.q, q))); |
824 | } |
825 | if (eqic(uri, "/bot/allPagesEndingWith")) { |
826 | assertNempty(q); |
827 | ret servePagesToBot(filter(list(cc, Page), p -> ewic(p.q, q))); |
828 | } |
829 | if (eqic(uri, "/bot/allPagesContaining")) { |
830 | assertNempty(q); |
831 | ret servePagesToBot(filter(list(cc, Page), p -> cic(p.q, q))); |
832 | } |
833 | if (eqic(uri, "/bot/allPagesContainingRegexp")) { |
834 | assertNempty(q); |
835 | Pattern pat = regexpIC(q); |
836 | ret servePagesToBot(filter(list(cc, Page), p -> regexpFindIC(pat, p.q))); |
837 | } |
838 | |
839 | if (eqicOneOf(uri, "/bot/postSigned", /*"/bot/makePhysicalSlice",*/ "/bot/approveTrustRequest")) { |
840 | S text = rtrim(params.get('text)); |
841 | S key = getSignerKey(text); |
842 | if (empty(key)) ret subBot_serve500("Please include your public key"); |
843 | if (!isSignedWithKey(text, key)) ret subBot_serve500("Signature didn't verify"); |
844 | text = dropLastTwoLines(text); // drop signer + sig line |
845 | |
846 | Signer signer = uniq_sync Signer(publicKey := key); |
847 | |
848 | /*if (eqic(uri, "/bot/makePhysicalSlice")) { |
849 | if (!signer.trusted) ret subBot_serve500("Untrusted signer"); |
850 | Page page = findPageFromParams(jsonDecodeMap(text)); |
851 | if (page == null) ret subBot_serve500("Page not found"); |
852 | ret serveJSON(uniq2_sync(PhysicalSlice, slicePage := page).b ? "Slice made" : "Slice exists"); |
853 | }*/ |
854 | |
855 | if (eqic(uri, "/bot/postSigned")) { |
856 | new L out; |
857 | for (S line : tlft(text)) { |
858 | SS map = jsonDecodeMap(line); |
859 | AGIBlue.GetEntriesAndPost x = agiBlue.new GetEntriesAndPost(cc); |
860 | x.signer = signer; |
861 | Page page = findOrMakePageFromParams(map); |
862 | if (page == null) continue with out.add("Invalid page reference"); |
863 | x.go(page, map); |
864 | out.add(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); |
865 | } |
866 | ret serveJSON(out); |
867 | } |
868 | |
869 | if (eqic(uri, "/bot/approveTrustRequest")) { |
870 | if (!signer.trusted) ret subBot_serve500("Untrusted signer"); |
871 | Signer toApprove = conceptWhere Signer(publicKey := trim(text)); |
872 | if (toApprove == null) ret subBot_serve500("Signer to approve not found"); |
873 | cset(toApprove, trusted := true, approvedBy := signer.globalID); |
874 | ret serveJSON("Approved: " + trim(text)); |
875 | } |
876 | |
877 | ret subBot_serve500("CONFUSION"); |
878 | } |
879 | |
880 | if (eqic(uri, "/bot/post")) { |
881 | AGIBlue.GetEntriesAndPost x = agiBlue.new GetEntriesAndPost(cc); |
882 | x.go(pageFromQ(q), params); |
883 | ret serveJSON(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); |
884 | } |
885 | |
886 | if (eqic(uri, "/bot/entriesOnPage")) |
887 | ret serveJSON(map(entriesOnPage(findPageFromParams(params)), entryToMap(false))); |
888 | |
889 | if (eqic(uri, "/bot/entriesForKey")) |
890 | ret serveJSON(map(conceptsWhereIC(cc, Entry, key := params.get('key)), entryToMap(true))); |
891 | |
892 | // look up value from page & key in current slice |
893 | if (eqic(uri, "/bot/lookup")) { |
894 | S key = params.get('key); |
895 | if (empty(key)) ret serveJSON("Need key"); |
896 | S value = getValue(findPageFromParams(params), key); |
897 | ret serveJSON(empty(value) ? "" : litmap(+value)); |
898 | } |
899 | |
900 | if (eqic(uri, "/bot/multiLookup")) { |
901 | S key = params.get('key); |
902 | if (empty(key)) ret serveJSON("Need key"); |
903 | ret serveJSON(collect value(objectsWhereIC(findBackRefs(findPageFromParams(params), Entry), +key))); |
904 | } |
905 | |
906 | if (eqic(uri, "/bot/lookupMultipleKeys")) { |
907 | Set<S> keys = asCISet(subBot_paramsAsMultiMap().get('key)); |
908 | L<Entry> entries = findBackRefs(findPageFromParams(params), Entry); |
909 | ret serveJSON(map(entryToMap(false), filter(entries, e -> contains(keys, e.key)))); |
910 | } |
911 | |
912 | // look up value from page & key in all slices |
913 | if (eqic(uri, "/bot/multiLookupInAllSlices")) { |
914 | S key = params.get('key); |
915 | if (empty(key)) ret serveJSON("Need key"); |
916 | Cl<Slice> slices = centralIndexGetSlices(key); |
917 | new L<Map> results; |
918 | fOr (Slice s : slices) { |
919 | WorksOnSlice wos = new(loadBackgroundSlice(s)); |
920 | Page page = wos.findPageFromParams(params); |
921 | if (page != null) |
922 | fOr (Entry e : objectsWhereIC(findBackRefs(page, Entry), +key)) |
923 | results.add(litorderedmap("c" := e.value, "slice" := str(s.globalID()))); |
924 | } |
925 | ret serveJSON(results); |
926 | } |
927 | |
928 | if (eqic(uri, "/bot/latestEntries")) |
929 | ret serveJSON(map(takeFirst(10, slice.idx_latestEntries.objectIterator()), entryToMap(true))); |
930 | if (eqic(uri, "/bot/latestPages")) |
931 | ret serveJSON(map(takeFirst(10, slice.idx_latestCreatedPages.objectIterator()), pageToMap())); |
932 | if (eqic(uri, "/bot/latestChangedPages")) |
933 | ret serveJSON(map(takeFirst(10, slice.idx_latestChangedPages.objectIterator()), pageToMap())); |
934 | |
935 | if (eqic(uri, "/bot/googleUsersCount")) |
936 | ret serveJSON(countConcepts(GoogleUser)); |
937 | |
938 | if (eqic(uri, "/bot/totalPageCount")) |
939 | ret serveJSON(countConcepts(Page)); |
940 | /*if (eqic(uri, "/bot/pageWithoutPhysicalSliceCount")) |
941 | ret serveJSON(countConceptsWhere(Page, slice := null)); |
942 | if (eqic(uri, "/bot/physicalSliceCount")) |
943 | ret serveJSON(countConcepts(PhysicalSlice));*/ |
944 | if (eqic(uri, "/bot/trustedSignersCount")) |
945 | ret serveJSON(countConcepts(Signer, trusted := true)); |
946 | |
947 | if (eqic(uri, "/bot/valueSearch")) { |
948 | S value = params.get('value); |
949 | L<Entry> entries = conceptsWhereIC(cc, Entry, +value); |
950 | ret serveJSON(map(takeFirst(100, entries), entryToMap(true))); |
951 | } |
952 | |
953 | if (eqic(uri, "/bot/keyAndValueSearch")) { |
954 | S key = params.get('key), value = params.get('value); |
955 | Cl<Page> pages = pagesForKeyAndValue(key, value); |
956 | ret servePagesToBot(pages); |
957 | } |
958 | |
959 | if (eqic(uri, "/bot/pagesWithKey")) { |
960 | S key = params.get('key); |
961 | ret servePagesToBot(pagesWithKey(key)); |
962 | } |
963 | |
964 | if (eqic(uri, "/bot/match")) { |
965 | PositionalTokenIndex2 index = slice.positionalIndex; |
966 | if (index == null) ret "No positional index"; |
967 | ret serveJSON(fastJoinAll(positionalTokenIndex2_matchAll_returnTokenizations(q, index))); |
968 | } |
969 | |
970 | if (eqic(uri, "/bot/aLookup")) { |
971 | S key = params.get('key), value = params.get('value); |
972 | Cl<Page> pages = pagesForKeyAndValue(key, value); |
973 | ret serveJSON(collect q(pages)); |
974 | } |
975 | |
976 | if (eqic(uri, "/bot/bLookup")) { |
977 | S value = params.get('value); |
978 | ret serveJSON(keysForPageAndValue(q, value)); |
979 | } |
980 | |
981 | if (eqic(uri, "/bot/keyValuePairsByPopularity")) { |
982 | L<PairS> pairs = map(list(Entry), e -> pair(e.key, e.value)); |
983 | LPair<S, Int> pairs2 = multiSetTopPairs(ciMultiSet(map pairToUglyStringForCIComparison(pairs))); |
984 | ret serveJSON(map(pairs2, p -> { |
985 | S key, value = unpair pairFromUglyString(p.a); |
986 | ret litorderedmap(n := p.b, +key, +value); |
987 | })); |
988 | } |
989 | |
990 | // technically a duplicate of entriesForKey |
991 | if (eqic(uri, "/bot/pagesAndValuesForKey")) { |
992 | S key = params.get('key); |
993 | L<Entry> entries = conceptsWhereIC(cc, Entry, +key); |
994 | L<PairS> pairs = map(entries, e -> pair(e.page->q, e.value)); |
995 | ret serveJSON(pairsToLists(pairs)); |
996 | } |
997 | |
998 | if (eqic(uri, "/bot/allKeys")) |
999 | ret serveListToBot(distinctCIFieldValuesOfConcepts(cc, Entry, 'key)); |
1000 | |
1001 | if (eqic(uri, "/bot/allKeysByPopularity")) |
1002 | ret serveListToBot(mapMultiSetByPopularity(distinctCIFieldValuesOfConcepts_multiSet(cc, Entry, 'key), (key, n) -> litorderedmap(+n, +key))); |
1003 | |
1004 | if (eqic(uri, "/bot/dbSize")) |
1005 | ret serveJSON(l(conceptsFile())); |
1006 | |
1007 | if (eqic(uri, "/bot/query")) |
1008 | ret serveBotQuery(); |
1009 | |
1010 | if (eqic(uri, "/bot/createSlice")) { |
1011 | Slice slice = createSlice(assertNempty(params.get('name))); |
1012 | ret serveJSON(litorderedmap(id := str(slice.globalID), name := slice.name)); |
1013 | } |
1014 | |
1015 | if (eqic(uri, "/bot/dumpAllSlices") && authed()) { |
1016 | new L<Map> out; |
1017 | for (Slice slice) try { |
1018 | out.add(litorderedmap(slice := slice.caseID, ms := returnTimeFor(() -> dumpSliceToFile(loadBackgroundSlice(slice))))); |
1019 | } catch print e { |
1020 | out.add(litorderedmap(slice.caseID, error := exceptionToStringShort(e))); |
1021 | } |
1022 | ret serveJSON(out); |
1023 | } |
1024 | |
1025 | if (eqic(uri, "/bot/dumpSlice")) { |
1026 | File f = dumpSliceToFile(slice); |
1027 | ret "OK, saved as: " + f2s(f); |
1028 | } |
1029 | |
1030 | if (eqic(uri, "/bot/diskStats")) |
1031 | ret serveJSON(litcimap( |
1032 | sizeOnDisk := agiBlue.sizeOnDisk!, |
1033 | freeDiskSpace := freeDiskSpace() |
1034 | )); |
1035 | |
1036 | if (eqic(uri, "/bot/memStats")) { |
1037 | Map<S, ?> fg = cloneMap(agiBlue.loadedSlices); |
1038 | Map<S, ?> bg = cloneMap(agiBlue.backgroundSlices); |
1039 | ret serveJSON(litcimap( |
1040 | error := checkDoubleLoads(), |
1041 | centralIndexEntries := l(centralIndex), |
1042 | centralIndexMade := renderHowLongAgo(centralIndexMade), |
1043 | centralIndexMadeInMS := centralIndexMadeIn, |
1044 | numLoadedForegroundSlices := l(fg), |
1045 | numLoadedBackgroundSlices := l(bg), |
1046 | loadedForegroundSlicesEstimatedSize := longSum(map(s -> agiBlue.estimatedSliceDataSize(s), keys(fg))), |
1047 | loadedBackgroundSlicesEstimatedSize := longSum(map(s -> agiBlue.estimatedSliceDataSize(s), keys(bg))), |
1048 | loadedForegroundSlices := sortedIC(keysList(fg)), |
1049 | loadedBackgroundSlices := sortedIC(keysList(bg)), |
1050 | sessionsWithGoogleUsers := countPred(list(Session), s -> s.user! instanceof GoogleUser), |
1051 | userObjects := countConcepts(User), |
1052 | processSize := getOpt(vmBus_query('processSize), 'processSize), |
1053 | slices := countConcepts(Slice) |
1054 | )); |
1055 | } |
1056 | |
1057 | if (eqic(uri, "/bot/centralIndexGet")) { |
1058 | CentralIndexEntry e = centralIndex.get(q); |
1059 | ret serveJSON(e == null ? ll() : map(e.slices, sliceToMap())); |
1060 | } |
1061 | |
1062 | // return primary triple from each slice |
1063 | if (eqic(uri, "/bot/centralIndexGrab")) { |
1064 | CentralIndexEntry ie = centralIndex.get(q); |
1065 | new L<Map> out; |
1066 | if (ie != null) for (Slice slice : ie.slices) { |
1067 | LoadedSlice ls = loadBackgroundSlice(slice); |
1068 | WorksOnSlice wos = new(ls); |
1069 | Page page = wos.findPageFromQ(q); |
1070 | Iterable<Entry> entries = entriesOnPage(page); |
1071 | print("Page for " + q + " in slice " + ls.caseID + ": " + yesNo(page != null) + " - " + l(entries)); |
1072 | Entry e = first(entries); |
1073 | if (e != null) |
1074 | out.add(litorderedmap(a := page.q, b := e.key, c := e.value, slice := slice.caseID)); |
1075 | } |
1076 | ret serveJSON(out); |
1077 | } |
1078 | |
1079 | // Search for literal substring in central index entry |
1080 | // Returns only the words found, not the slices |
1081 | if (eqic(uri, "/bot/centralIndexLiteralSearch")) { |
1082 | LS entries = containingIC(keys(centralIndex), q); |
1083 | ret serveJSON(entries); |
1084 | } |
1085 | |
1086 | if (eqic(uri, "/bot/updateCentralIndex") && authed()) { |
1087 | rst_index.trigger(); |
1088 | ret "OK"; |
1089 | } |
1090 | |
1091 | if (eqic(uri, "/bot/allGoogleEmails") && authed()) |
1092 | ret serveJSON(collect googleEmail(list(GoogleUser))); |
1093 | |
1094 | if (eqic(uri, "/bot/words2_spaces_all")) |
1095 | ret serveJSON(mapToValues_ciMap words2_spaces_cached(collect q(conceptsSortedByFieldCI(slice.cc, Page, 'q)))); |
1096 | |
1097 | if (eqic(uri, "/bot/words2_spaces_collapse_all")) |
1098 | ret serveJSON(mapToValues_ciMap words2_spaces_collapse_cached(collect q(conceptsSortedByFieldCI(slice.cc, Page, 'q)))); |
1099 | |
1100 | if (eqic(uri, "/bot/makeManyPages")) { |
1101 | //LS qs = nempties(subBot_paramsAsMultiMap().get('q)); |
1102 | LS qs = tlft(params.get("pages")); |
1103 | slice.haltSliceDumping = true; |
1104 | try { |
1105 | fOr ping (S _q : qs) |
1106 | pageFromQ(_q); |
1107 | } finally { |
1108 | slice.haltSliceDumping = true; |
1109 | slice.rstDumpSlice.trigger(); |
1110 | } |
1111 | ret "OK (" + l(qs) + ")"; |
1112 | } |
1113 | |
1114 | if (eqic(uri, "/bot/sliceNamesMap")) |
1115 | ret serveJSON(mapToMap(list(Slice), s -> pair(str(s.globalID), s.name))); |
1116 | |
1117 | if (eqic(uri, "/bot/sliceForName")) { |
1118 | Slice slice = sliceForName(q); |
1119 | ret serveJSON(slice == null ? null : str(slice.globalID)); |
1120 | } |
1121 | |
1122 | if (eqic(uri, "/bot/allowOpenPosting")) { |
1123 | try object errorIfNotOwnerOfSlice(); |
1124 | bool allow = eq("1", params.get('allow)); |
1125 | cset(sliceInfo(), openPosting := allow); |
1126 | ret "Set openPosting to " + allow; |
1127 | } |
1128 | |
1129 | if (eqic(uri, "/bot/createUnusedNumberedPage")) { |
1130 | int n = 1; |
1131 | // TODO: sync |
1132 | while (findPageFromQ(q + n) != null) ++n; |
1133 | ret serveJSON(litmap(q := pageFromQ(q + n).q)); |
1134 | } |
1135 | |
1136 | // end of bot methods |
1137 | |
1138 | ret subBot_serve404(); |
1139 | } |
1140 | |
1141 | O serveBotQuery() { |
1142 | S query = params.get('query); |
1143 | ret agiBlue.new Query(slice).process(query); |
1144 | } |
1145 | |
1146 | int searchResultsToShow() { ret agiBlue.searchResultsToShow; } |
1147 | |
1148 | O serveLiteralSearch() { |
1149 | S q = params.get('q); |
1150 | L<Page> searchResults = literalSearch(q); |
1151 | ret serveSearchResults("literal search" , q, searchResultsToShow(), searchResults); |
1152 | } |
1153 | |
1154 | L<Page> literalSearch(S q, O... _) { |
1155 | int searchResultsToShow = optPar max(_, 100); |
1156 | |
1157 | // quick search in random order |
1158 | //L<Page> searchResults = takeFirst(clipIntPlus(searchResultsToShow, 1), filterIterator(iterator(list(cc, Page)), p -> cic(p.q, q)); |
1159 | |
1160 | // full search, order by length |
1161 | ret takeFirst(clipIntPlus(searchResultsToShow, 1), pagesSortedByLength(filter(list(cc, Page), p -> cic(p.q, q)))); |
1162 | } |
1163 | |
1164 | O serveScoredSearch() { |
1165 | S q = params.get('q); |
1166 | L<Page> searchResults = (L<Page>) dm_call('agiBlueSearch, 'search, q, +cc); |
1167 | ret serveSearchResults("scored search" , q, searchResultsToShow(), searchResults); |
1168 | } |
1169 | |
1170 | O serveLevenSearch() { |
1171 | S q = params.get('q); |
1172 | L<Page> searchResults = levenSearch(q); |
1173 | ret serveSearchResults("leven search with distance 1" , q, searchResultsToShow(), searchResults); |
1174 | } |
1175 | |
1176 | L<Page> levenSearch(S q, O... _) { |
1177 | int searchResultsToShow = optPar max(_, 100); |
1178 | int maxEditDistance = 1; |
1179 | |
1180 | new Map<Page, Int> map; |
1181 | for (Page p : list(cc, Page)) { |
1182 | int distance = leven_limitedIC(q, p.q, maxEditDistance+1); |
1183 | if (distance <= maxEditDistance) map.put(p, distance); |
1184 | } |
1185 | ret takeFirst(clipIntPlus(searchResultsToShow, 1), keysSortedByValue(map)); |
1186 | } |
1187 | |
1188 | O serveSearchResults(S searchType, S q, int searchResultsToShow, Collection<Page> searchResults) { |
1189 | S title = "agi.blue " + searchType + " for " + htmlEncode2(quote(q)) + " (" + (l(searchResults) >= searchResultsToShow ? searchResultsToShow + "+ results" : nResults(l(searchResults))) + ")"; |
1190 | |
1191 | ret hhtml_agiBlue(hhead_title(htmldecode_dropAllTags(title)) |
1192 | + hbody(hfullcenter(//top() + |
1193 | h3(title) |
1194 | + p_nemptyLines_showFirst(searchResultsToShow, map(pageToHTMLLink(), searchResults))))); |
1195 | } |
1196 | |
1197 | O serveQueryPage() { |
1198 | S query = params.get('query); |
1199 | if (query == null) query = loadSnippet(#1024258); |
1200 | S title = agiBlueNameHTML() + " | Execute a query script (" + targetBlank("http://code.botcompany.de/1024274", "ALQL") + ")"; |
1201 | ret hhtml_miniPage(title, h3(title) |
1202 | + form( |
1203 | htextarea(query, name := 'query, cols := 80, rows := 10, autofocus := true) |
1204 | + "<br><br>" |
1205 | + hsubmit("Execute"), action := "/bot/query" |
1206 | )); |
1207 | } |
1208 | |
1209 | O hhtml_miniPage(S htmlTitle, O contents) { |
1210 | ret hhtml_agiBlue(hhead_title(htmldecode_dropAllTags(htmlTitle)) |
1211 | + hbody(hfullcenter(contents))); |
1212 | } |
1213 | |
1214 | S sliceHomeURL() { |
1215 | ret slice == null ? agiBlueURL() : sliceHomeURL(slice.sliceConcept.globalID); |
1216 | } |
1217 | |
1218 | S sliceHomeURL(Slice slice) { ret sliceHomeURL(slice.globalID); } |
1219 | |
1220 | S sliceHomeURL(GlobalID slice) { |
1221 | ret agiBlueURL() + hquery(+slice); |
1222 | } |
1223 | |
1224 | S sliceLinkHTML(Slice slice) { |
1225 | ret slice == null ? "-": ahref(sliceHomeURL(slice), htmlEncode2(slice.name)); |
1226 | } |
1227 | |
1228 | O serveCreateSlicePage() { |
1229 | S sliceName = trim(params.get('sliceName)); |
1230 | if (eq(params.get('doIt), "1") && nempty(sliceName)) { |
1231 | // TODO: check for existing name |
1232 | Slice slice = createSlice(sliceName); |
1233 | ret hrefresh(sliceHomeURL(slice.globalID)); |
1234 | } |
1235 | |
1236 | S title = agiBlueNameHTML() + " | Create slice"; |
1237 | ret hhtml_agiBlue(hhead_title(htmldecode_dropAllTags(title)) |
1238 | + hbody(hfullcenter( |
1239 | h3(title) |
1240 | + form( |
1241 | hhidden(doIt := 1) |
1242 | + "Slice name: " + htextinput(+sliceName, autofocus := true) |
1243 | + "<br><br>" |
1244 | + hsubmit("Create slice")) |
1245 | ))); |
1246 | } |
1247 | |
1248 | O serveDeletePage() { |
1249 | q = params.get('q); |
1250 | Page page = findPageFromQ(q); |
1251 | if (page == null) ret "Page " + quote(q) + " not found in slice " + htmlEncode(slice.name()); |
1252 | try object errorIfNotOwnerOfSlice(); |
1253 | deleteConcepts(findBackRefs(page, AbstractEntry)); |
1254 | cdelete(page); |
1255 | ret hrefresh(1.0, sliceHomeURL()) + "Page deleted"; |
1256 | } |
1257 | |
1258 | // TODO: delete without loading |
1259 | O serveDeleteSlice() { |
1260 | assertNempty("Need slice param", params.get('slice)); |
1261 | try object errorIfNotOwnerOfSlice(); |
1262 | S caseID = sliceConcept.caseID; |
1263 | agiBlue.unloadSlice(caseID); |
1264 | File dir = sliceDir(sliceConcept); |
1265 | if (!dirExists(dir)) ret "Dir not found: " + f2s(dir); |
1266 | moveDirectory(dir, programFile("deletedSlices/" + sliceConcept.caseID)); |
1267 | cset(session, selectedSlice := null); |
1268 | cdelete(sliceConcept); |
1269 | ret serveText("Slice " + caseID + " deleted (& backed up)"); |
1270 | } |
1271 | |
1272 | O errorIfNotOwnerOfSlice() { |
1273 | bool ownage = slice.sliceConcept.owner! == session.user!; |
1274 | if (!ownage) |
1275 | ret subBot_serve403("Sorry, you don't own slice " + htmlEncode(slice.name())); |
1276 | null; |
1277 | } |
1278 | |
1279 | F1<Page, S> pageToHTMLLink(O... _) { |
1280 | optPar int displayLength = agiBlue.displayLength; |
1281 | ret func(Page p) -> S { |
1282 | S name = pageDisplayName(p); |
1283 | ret ahref(agiBlue_pageURLWithSlice(p), |
1284 | htmlEncode2(shorten(displayLength, name)), |
1285 | title := name); |
1286 | }; |
1287 | } |
1288 | |
1289 | // and google log-in |
1290 | S sliceSelector() { |
1291 | ret htag table(tr( |
1292 | (!showGoogleLogIn ? "" : td( |
1293 | googleSignIn_signInButton(agiBlueURL() + "/google-verify", "console.log(data);", ""), style := "padding-right: 10px;") |
1294 | + td(nobr(ahref("javascript:signOut()", "Sign out")), style := "padding-right: 10px; padding-bottom: 0.15em")) |
1295 | |
1296 | + (!agiBlue.showSliceSelector ? "" : hform(td( |
1297 | "Select reality slice: " |
1298 | + hselect(availableSlices(), session.selectedSlice, name := 'slice, onchange := "this.form.submit()") |
1299 | /*+ " " + hsubmit("Go")*/ |
1300 | + " | " + |
1301 | ahref(agiBlueURL("/slices"), nSlices(countConcepts(Slice))) + " | " + |
1302 | ahref(agiBlueURL() + "/createSlice", "Create slice...") |
1303 | ))))); |
1304 | } |
1305 | |
1306 | S sliceAsHTML() { |
1307 | if (slice.isMainSlice()) ret htmlEncode2(agiBlueName() + "'s main slice"); |
1308 | if (slice.sliceConcept == null) ret "Slice ???"; |
1309 | ret htmlEncode2("Slice " + quote(slice.sliceConcept.name)); |
1310 | } |
1311 | |
1312 | Slice sliceConcept() { ret slice.sliceConcept; } |
1313 | |
1314 | S footer() { |
1315 | ret p(small(elapsedMS_sysNow(started) + " ms")); |
1316 | } |
1317 | |
1318 | S verifiedEmail() { |
1319 | if (session == null || !(session.user! instanceof GoogleUser)) null; |
1320 | ret ((GoogleUser) session.user!).googleEmail; |
1321 | } |
1322 | |
1323 | bool authed() { |
1324 | bool ver = eqic(verifiedEmail(), "stefan.reich.maker.of.eye@googlemail.com"); |
1325 | print("Auth check " + verifiedEmail() + " -> " + ver); |
1326 | ret ver; |
1327 | } |
1328 | |
1329 | S googleSignInID() { |
1330 | ret eqic(domain(), "botcompany.de") ? botCompanyGoogleSignInID() : agiBlueGoogleSignInID(); |
1331 | } |
1332 | |
1333 | S hhtml_agiBlue(S contents) { |
1334 | ret hhtml(hAddToHead_fast(contents, |
1335 | hIncludeGoogleFont("Source Sans Pro") |
1336 | //+ [[<meta name="google-site-verification" content="oRvyeTqgSuv-FE_c-2UKM1Vp0oDqx8h9WruHYWqA-NQ" />]] |
1337 | + loadJQuery() |
1338 | + hmobilefix() |
1339 | + googleSignIn_header("", googleSignInID()) |
1340 | + hstylesheet("body { font-family: Source Sans Pro }"))); |
1341 | } |
1342 | |
1343 | // list classes in current slice |
1344 | O serveClasses() { |
1345 | // find classes & counts |
1346 | |
1347 | L<Entry> entries = conceptsWhereCI(cc, Entry, key := "_class"); |
1348 | MultiSet<S> classesAndCount = ciMultiSet(); |
1349 | for (Entry e : entries) |
1350 | classesAndCount.add(e.value); |
1351 | |
1352 | S title = agiBlueNameHTML() + " | " + htmlEncode(sliceConcept().name) + " | Classes"; |
1353 | |
1354 | ret hhtml_miniPage(title, h3(title) + |
1355 | htmlTable2(map(keys(classesAndCount), c -> |
1356 | litorderedmap( |
1357 | "Class" := pageToHTMLLink().get(findPageFromQ(c)), |
1358 | "Elements" := classesAndCount.get(c), |
1359 | )), |
1360 | htmlEncode := false)); |
1361 | } |
1362 | |
1363 | O serveSlicesList() { |
1364 | S sort = getAny(params, 'sort, 'by); |
1365 | L<Slice> slices; |
1366 | S shown; |
1367 | if (eqic(sort, 'name)) { |
1368 | slices = asList(idx_slicesByName.objectIterator()); |
1369 | shown = "sorted by name"; |
1370 | } else { |
1371 | sort = 'date; |
1372 | slices = asList(idx_slicesByModification.objectIterator()); |
1373 | shown = "last modified first"; |
1374 | } |
1375 | |
1376 | S title = agiBlueNameHTML() + " has " + nSlices(slices) /*+ " (" + shown + ")"*/; |
1377 | new PreIncLongCounter counter; |
1378 | ret hhtml_agiBlue(hhead_title_decode(title) |
1379 | + hbody(hfullcenter( |
1380 | h1(title) |
1381 | + p(joinWithSpacedVBar( |
1382 | ahrefIf(neqic(sort, 'name), agiBlueURL() + "/slices" + hquery(sort := 'name), "List by name"), |
1383 | ahrefIf(neqic(sort, 'date), agiBlueURL() + "/slices" + hquery(sort := 'date), "List by date"), |
1384 | ahref(agiBlueURL() + "/createSlice", "Create slice..."))) |
1385 | |
1386 | + htmlTable2(map(slices, slice -> |
1387 | litorderedmap( |
1388 | "Name" := sliceLinkHTML(slice), |
1389 | "ID" := ahref(sliceHomeURL(slice), str(slice.globalID)), |
1390 | "Owner" := str(slice.owner!) |
1391 | )), |
1392 | htmlEncode := false) |
1393 | ))); |
1394 | } |
1395 | |
1396 | O serveUsersList() { |
1397 | S sort = getAny(params, 'sort, 'by); |
1398 | L<User> users; |
1399 | S shown; |
1400 | if (eqic(sort, 'name)) |
1401 | users = asList(idx_usersByName.objectIterator()); |
1402 | else { |
1403 | sort = 'seen; |
1404 | users= asList(idx_usersBySeen.objectIterator()); |
1405 | } |
1406 | |
1407 | S title = agiBlueNameHTML() + " has " + nUsers(users); |
1408 | new PreIncLongCounter counter; |
1409 | ret hhtml_agiBlue(hhead_title_decode(title) |
1410 | + hbody(hfullcenter( |
1411 | h1(title) |
1412 | + p(joinWithSpacedVBar( |
1413 | ahrefIf(neqic(sort, 'name), agiBlueURL() + "/users" + hquery(sort := 'name), "List by name"), |
1414 | ahrefIf(neqic(sort, 'seen), agiBlueURL() + "/users" + hquery(sort := 'seen), "List by last seen"), |
1415 | ahref(agiBlueURL() + "/createSlice", "Create slice..."))) |
1416 | |
1417 | + htmlTable2(map(users, user -> |
1418 | litorderedmap( |
1419 | "Name" := ahref(userHomeURL(user), htmlEncode2(str(user))), |
1420 | "Last seen" := renderHowLongAgo(user.lastSeen) |
1421 | )), |
1422 | htmlEncode := false) |
1423 | ))); |
1424 | } |
1425 | |
1426 | S sliceInfoHTML() { |
1427 | User owner = slice.sliceConcept.owner!; |
1428 | ret owner == null ? "" : p("This slice is owned by " + userHTML(owner)); |
1429 | } |
1430 | |
1431 | S userHomeURL(User user) { |
1432 | ret user == null ? null : agiBlueURL() + "/user/" + user.globalID; |
1433 | } |
1434 | |
1435 | S userHTML(User user) { |
1436 | ret user == null ? "nobody" : ahref(userHomeURL(user), htmlEncode2(str(user))); |
1437 | } |
1438 | |
1439 | S top() { |
1440 | ret nempty(get) ? "" : hcomment("cookie: " + takeFirst(4, session.cookie)) |
1441 | + hSilentComputatorWithFlag("agi.blue: " + q) |
1442 | + p(ahref(sliceHomeURL(), |
1443 | //hsnippetimg(#1101682, width := 565/5, height := 800/5, title := "Robot by Peerro @ DeviantArt") |
1444 | //hsnippetimg(#1101778, width := 96, height := 96, title := "agi.blue - a database for everything") |
1445 | hsnippetimg(#1101822, width := 314, height := 125, title := "agi.blue - Wikipedia for robots") |
1446 | )) |
1447 | + h2(ahref_unstyled(sliceHomeURL(), htmlEncode2(slice.sliceConcept.name), style := "color: yellow")) |
1448 | + sliceInfoHTML() |
1449 | + p(small( |
1450 | agiBlueNameHTML_boldWithSize() |
1451 | + (agiBlue_isOriginal() ? "" : " " + targetBlank("http://agi.blue", "[original]")) |
1452 | + " | " + ahref(agiBlueURL("/slices"), nSlices(countConcepts(Slice))) |
1453 | + " | " + targetBlank(progLink(), "source code") + " of this web site (" + nLines(sourceCodeLines) + ") | " + |
1454 | /*targetBlank("https://gitter.im/agi-blue/community", "sponsor https?") + " | " */ + "by " + targetBlank("https://BotCompany.de", "BC") + " | " + targetBlank("http://fiverr.tinybrain.de/", "Fiverr") + " | " + targetBlank("https://discordapp.com/invite/SEAjPqk", "Discord") + " | " + targetBlank("https://www.youtube.com/watch?v=b6jtRdV3Ev8", "Video") |
1455 | + " | " + targetBlank("http://code.botcompany.de/1024233", "Notes") |
1456 | + " | " + ahref(agiBlueURL() + "/query", "Query") |
1457 | )); |
1458 | } |
1459 | |
1460 | Slice createSlice(S name) { |
1461 | name = shorten(name, maxSliceNameLength); |
1462 | if (sliceForName(name) != null) |
1463 | fail("A slice with the name " + quote(name) + " exists, please choose another name."); |
1464 | Slice slice = cnew Slice(+name, owner := session.user); |
1465 | cset(slice, caseID := slice.defaultCaseID()); |
1466 | loadSlice(slice).initialSetup(slice.globalID); |
1467 | ret slice; |
1468 | } |
1469 | |
1470 | O serveCheckboxes() { |
1471 | set conceptsSortedByFieldCI_verbose; |
1472 | int showMax = 1000; |
1473 | Cl<Page> pages = takeFirst(showMax, conceptsSortedByFieldCI(slice.cc, Page, 'q)); |
1474 | S title = "Select pages"; |
1475 | ret hhtml_miniPage(title, h3(title) + hpostform( |
1476 | htmlTable2(map(pages, page -> litorderedmap( |
1477 | "Page" := hcheckbox("page_" + page.globalID()) + " " + pageToHTMLLink().get(page) |
1478 | )), |
1479 | htmlEncode := false) |
1480 | + h3("Add an entry to all selected pages") |
1481 | + p("Key: " + hinputfield("key") + |
1482 | " Value: " + hinputfield("value")) |
1483 | + p(hsubmit("Add entries")), action := agiBlueURL() + "/multiAdd")); |
1484 | } |
1485 | |
1486 | O serveMultiAdd() { |
1487 | S key = trim(params.get('key)), value = trim(params.get('value)); |
1488 | if (empty(key) || empty(value)) ret "Need key and value"; |
1489 | new Matches m; |
1490 | new L<Page> pages; |
1491 | for (S a, b : params) |
1492 | if (startsWith(a, "page_", m)) |
1493 | addIfNotNull(pages, conceptWhere(cc, Page, globalID := m.rest())); |
1494 | if (empty(pages)) ret "No pages selected"; |
1495 | for (Page page : pages) |
1496 | agiBlue.new GetEntriesAndPost(cc).go(page, params); |
1497 | ret "Entry added to " + nPages(pages); |
1498 | } |
1499 | |
1500 | S agiBlueNameHTML() { |
1501 | ret ahref(agiBlueURL(), htmlEncode2(agiBlueName())); |
1502 | } |
1503 | |
1504 | S agiBlueNameHTML_boldWithSize() { |
1505 | Long size = agiBlue.sizeOnDisk.peek(); |
1506 | ret b(agiBlueNameHTML()) + (size == null ? "" : " " + spanTitle("Size of agi.blue's database on disk", "(" + toM(size) + " MB)")); |
1507 | } |
1508 | |
1509 | IF1<Page, Map> pageToMap(O... _) { |
1510 | optPar bool withEntries; |
1511 | |
1512 | bool nameOnly = eqOneOf(optPar nameOnly(_), "1", true); |
1513 | if (nameOnly) ret (IF1<Page, Map>) p -> litmap(q := p.q); |
1514 | |
1515 | ret (IF1<Page, Map>) p -> { |
1516 | L<Entry> entries = findBackRefs(p, Entry); |
1517 | ret litorderedmap( |
1518 | q := p.q, |
1519 | nEntries := l(entries), |
1520 | created := p.created, |
1521 | modified := p._modified, |
1522 | entries := !withEntries ? null : map(entries, entryToMap(false))); |
1523 | }; |
1524 | } |
1525 | |
1526 | IF1<Entry, Map> entryToMap(bool withPage) { |
1527 | ret (IF1<Entry, Map>) e -> litorderedmap( |
1528 | created := e.created, |
1529 | i := e.count, |
1530 | key := e.key, |
1531 | value := e.value, |
1532 | q := withPage ? e.page->q : null, |
1533 | signer := getString globalID(e.signer!)); |
1534 | } |
1535 | |
1536 | IF1<Slice, Map> sliceToMap() { |
1537 | ret (IF1<Slice, Map>) s -> litorderedmap( |
1538 | created := s.created, |
1539 | globalID := str(s.globalID), |
1540 | name := s.name, |
1541 | caseID := s.caseID); |
1542 | } |
1543 | |
1544 | O servePageToBot(Page page, SS params) { |
1545 | if (page == null) ret serveJSON(null); |
1546 | params = asCIMap(params); |
1547 | Map map = pageToMap(withEntries := valueIs1 withEntries(params)).get(page); |
1548 | ret serveJSON(map); |
1549 | } |
1550 | |
1551 | SS availableSlices() { |
1552 | L<Slice> slices = asList(idx_slicesByName.objectIterator()); |
1553 | ret mapToOrderedMap(s -> pair(str(s.globalID), s.name + " [ID: " + s.globalID + "]"), slices); |
1554 | } |
1555 | |
1556 | O serveSearchAction() { |
1557 | q = trim(params.get('q)); |
1558 | if (empty(q)) ret "You are searching for nothing"; |
1559 | |
1560 | Map<S, CentralIndexEntry> ci = centralIndex; |
1561 | LS entries = containingIC(keys(ci), q); |
1562 | |
1563 | S title = htmlEncode2("Searching for " + quote(q)); |
1564 | ret hhtml_miniPage(title, h2(agiBlueNameHTML() + " | " + title) |
1565 | + h3("Results in slice " + htmlEncode2(quote(sliceConcept().name))) |
1566 | + p("TODO") |
1567 | + h3(firstToUpper(nResults(entries)) + " in central index (" + l(ci) + ")") |
1568 | + htmlTable2(map(entries, e -> { |
1569 | CentralIndexEntry ee = ci.get(e); |
1570 | ret litorderedmap( |
1571 | "Phrase" := ahref( |
1572 | empty(ee.slices) ? null : agiBlue_linkForPhrase(e, slice := first(ee.slices).globalID), htmlEncode2(e)), |
1573 | "Slices" := |
1574 | joinWithComma(map(takeFirst(3, ee.slices), s -> ahref(agiBlue_linkForPhrase(e, slice := s.globalID), |
1575 | htmlEncode(s.name)))) + |
1576 | (l(ee.slices) > 3 ? " + " + (l(ee.slices)-3) + " more" : "") |
1577 | ); |
1578 | }), htmlEncode := false) |
1579 | ); |
1580 | } |
1581 | |
1582 | } // end of Request |
1583 | |
1584 | } // end of renderer |
1585 | |
1586 | set flag hotwire_here. |
1587 | |
1588 | // share ISpec interface with sub-modules |
1589 | static JavaXClassLoader hotwire_makeClassLoader(L<File> files) { |
1590 | ClassLoader cl = myClassLoader(); |
1591 | |
1592 | // Avoid class loader chaining, always reference base class loader |
1593 | // (TODO) |
1594 | /*if (agiBlue().isClone && agiBlue().cloningSince != 0) { |
1595 | ClassLoader parent = cast getOpt(cl, 'virtualParent); |
1596 | print("Cloned class loader. " + parent); |
1597 | if (parent != null) cl = parent; |
1598 | }*/ |
1599 | |
1600 | ret new JavaXClassLoaderWithParent2(null, files, cl, parseClassesToShareList(classesToShare)); |
1601 | } |
1602 | |
1603 | static AGIBlue agiBlue() { ret agiBlue; } |
1604 | |
1605 | static File sliceDir(Slice slice) { |
1606 | ret programFile(slice.caseID); |
1607 | } |
download show line numbers debug dex old transpilations
Travelled to 8 computer(s): bhatertpkbcr, cfunsshuasjs, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1023558 |
Snippet name: | agi.blue source [LIVE] |
Eternal ID of this version: | #1023558/784 |
Text MD5: | ca99a83f98aea2ac29a66c6410b044aa |
Transpilation MD5: | 2ec59d80c671598010de6661779e6b06 |
Author: | stefan |
Category: | javax / html |
Type: | JavaX module (desktop) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-10-28 14:49:33 |
Source code size: | 61416 bytes / 1607 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 2171 / 10130 |
Version history: | 783 change(s) |
Referenced in: | [show references] |