Download Jar. Libraryless. Click here for Pure Java version (17406L/126K).
1 | !7 |
2 | |
3 | // See #1023660 for the older 99 lines version |
4 | |
5 | // phasing out |
6 | concept PhysicalSlice { |
7 | new Ref<Page> slicePage; // page that describes this slice |
8 | } |
9 | |
10 | // in main DB |
11 | concept Slice { |
12 | GlobalID globalID = aGlobalIDObject(); |
13 | S caseID; |
14 | S name; |
15 | |
16 | S defaultCaseID() { |
17 | ret uniqueFileNameUsingMD5_80_v2(name + " " + globalID); |
18 | } |
19 | } |
20 | |
21 | // in any DB |
22 | concept Page { |
23 | new Ref<PhysicalSlice> slice; // where are we stored |
24 | S globalID = aGlobalID(); |
25 | S url, q; |
26 | } |
27 | |
28 | abstract concept AbstractEntry { |
29 | new Ref<Page> page; |
30 | int count; |
31 | S key; |
32 | S ip; |
33 | new Ref<Signer> signer; |
34 | } |
35 | |
36 | concept Entry > AbstractEntry { |
37 | S globalID = aGlobalID(); // TODO: migrate to object |
38 | S value; |
39 | } |
40 | |
41 | concept MultiLineEntry > AbstractEntry { |
42 | GlobalID globalID = aGlobalIDObject(); |
43 | S value; |
44 | } |
45 | |
46 | concept BlobEntry > AbstractEntry { |
47 | GlobalID globalID = aGlobalIDObject(); |
48 | long length; |
49 | S md5; |
50 | } |
51 | |
52 | // in main DB? |
53 | concept Signer { |
54 | S globalID = aGlobalID(); |
55 | S publicKey; |
56 | bool trusted; |
57 | S approvedBy; |
58 | } |
59 | |
60 | // in main DB |
61 | concept Session { |
62 | S cookie; |
63 | S selectedSlice; // global ID |
64 | Page slicePage; // phasing out |
65 | } |
66 | |
67 | static SS searchTypeToText = litmap( |
68 | leven := "Leven", |
69 | literal := "Literal", |
70 | scored := "Scored"); |
71 | |
72 | static int displayLength = 140; |
73 | static int sideDisplayLength = 50; // when in side bar |
74 | static int searchResultsToShow = 50; |
75 | sS sideSearchType = 'literal; |
76 | |
77 | static int lines; |
78 | sbool asyncSearch = false; |
79 | sbool allowMultipleCasesInValues = true; |
80 | sbool showSliceSelector = true; |
81 | |
82 | static ConceptsLoadedOnDemand fan; |
83 | static Map<S, LoadedSlice> loadedSlices = syncMap(); |
84 | |
85 | sclass LoadedSlice { |
86 | S caseID; |
87 | Concepts cc; |
88 | Slice sliceConcept; |
89 | |
90 | ConceptFieldIndexDesc idx_latestEntries, idx_latestCreatedPages, idx_latestChangedPages; |
91 | |
92 | *(S *caseID, Concepts *cc) { |
93 | indexThings(); |
94 | } |
95 | |
96 | Page pageFromQ(S q) { |
97 | ret empty(q) ? null : uniqCI_sync(cc, Page, +q); |
98 | } |
99 | |
100 | void indexThings() { |
101 | indexConceptFieldsCI(cc, Page, 'url, Page, 'q, Entry, 'key, Entry, 'value); |
102 | indexConceptFields(cc, Signer, 'publicKey); |
103 | indexConceptFields(cc, Session, 'cookie); |
104 | idx_latestCreatedPages = new ConceptFieldIndexDesc(cc, Page, 'created); |
105 | idx_latestChangedPages = new ConceptFieldIndexDesc(cc, Page, '_modified); |
106 | idx_latestEntries = new ConceptFieldIndexDesc(cc, Entry, 'created); |
107 | } |
108 | |
109 | bool isMainSlice() { ret empty(caseID); } |
110 | } |
111 | |
112 | p { |
113 | fan = new ConceptsLoadedOnDemand; |
114 | fan.onCaseLoaded(voidfunc(S caseID, Concepts concepts) { |
115 | print("onCaseLoaded " + quote(caseID)); |
116 | loadedSlices.put(caseID, new LoadedSlice(caseID, concepts)); |
117 | }); |
118 | mainConcepts = fan.get(""); |
119 | indexConceptFields(Slice, 'globalID, Slice, 'caseID); |
120 | cset(uniq_returnIfNew Slice(caseID := ""), name := "main slice"); |
121 | |
122 | // legacy conversions! |
123 | deleteConcepts(Slice, globalID := GlobalID('wftlawbagrwprywn)); |
124 | //moveSlicelessPagesToTheWildSlice(); |
125 | |
126 | // Approve this machine's key |
127 | PKIKeyPair machineKey = agiBot_trustedKeyForMachine(); |
128 | if (machineKey != null) { |
129 | print("Approving this machine's key: " + machineKey.publicKey); |
130 | cset(uniq_sync(Signer, publicKey := machineKey.publicKey), trusted := true, approvedBy := "local"); |
131 | } |
132 | |
133 | lines = countLines(mySource()); |
134 | } |
135 | |
136 | // Serve page |
137 | |
138 | set flag NoNanoHTTPD. html { |
139 | ret new Request().serve(uri, params); |
140 | } |
141 | |
142 | sclass Request { |
143 | S cookie; |
144 | Session session; |
145 | S uri; |
146 | SS params; |
147 | Concepts cc; |
148 | LoadedSlice slice; |
149 | |
150 | // should also work for standalone |
151 | bool isHomeDomain() { |
152 | S domain = domain(); |
153 | ret eqic(domain, "www.agi.blue") || !ewic(domain, ".agi.blue"); |
154 | } |
155 | |
156 | O serve(S uri, SS params) { |
157 | this.uri = dropMultipleSlashes(uri); |
158 | this.params = params; |
159 | new Matches m; |
160 | |
161 | print(uri + " ? " + unnull(subBot_query())); |
162 | |
163 | // get cookie & session |
164 | |
165 | cookie = cookieFromUser(); |
166 | if (cookie != null) |
167 | session = uniq_sync(Session, +cookie); |
168 | else |
169 | session = unlistedWithValues(Session); |
170 | |
171 | // check if user wants to change slices |
172 | |
173 | S selectSlice = params.get('slice); |
174 | if (isGlobalID(selectSlice)) |
175 | cset(session, selectedSlice := selectSlice); |
176 | |
177 | // get case ID |
178 | |
179 | S caseID = ""; |
180 | Slice sliceConcept = sliceConceptForGlobalID(session.selectedSlice); |
181 | print("Selected slice: " + session.selectedSlice + ", obj? " + (sliceConcept != null)); |
182 | if (sliceConcept != null) caseID = sliceConcept.caseID; |
183 | print("caseID: " + caseID); |
184 | |
185 | // load slice |
186 | |
187 | slice = assertNotNull(loadSlice(caseID)); |
188 | cc = slice.cc; |
189 | slice.sliceConcept = sliceConcept; |
190 | |
191 | // Check for special URIs |
192 | |
193 | if (swic(uri, "/bot/")) ret serveBot(); |
194 | |
195 | // eleu appends a slash to the URI if it's a single identifier, so we drop it again |
196 | S uri2 = dropTrailingSlash(uri); |
197 | |
198 | if (eqic(uri2, "/search")) ret serveScoredSearch(); |
199 | if (eqic(uri2, "/literalSearch")) ret serveLiteralSearch(); |
200 | if (eqic(uri2, "/levenSearch")) ret serveLevenSearch(); |
201 | |
202 | if (eqic(uri2, "/query")) ret serveQueryPage(); |
203 | if (eqic(uri2, "/createSlice")) ret serveCreateSlicePage(); |
204 | |
205 | S q = params.get('q); |
206 | S domain = or2(params.get('domain), domain()); |
207 | |
208 | S raw = firstKeyWithValue("", params); // agi.blue?something |
209 | if (nempty(raw) && empty(q)) q = raw; |
210 | /*if (nempty(q)) { |
211 | domain = makeAGIDomain(q); |
212 | if (l(domain) > maximumDomainPartLength()) // escape with "domain=" |
213 | ret hrefresh(agiBlueURL() + hquery(+domain, key := "read as", value := q)); |
214 | ret hrefresh("http://" + domain + (eq(q, domain) ? "" : "/" + hquery(key := "read as", value := q))); |
215 | //uri = "/"; replaceMapWithParams(params, key := "read as", value := q); |
216 | }*/ |
217 | S url = domain + dropTrailingSlash(uri); |
218 | |
219 | // domain to query |
220 | //if (empty(q)) q = url; |
221 | if (empty(q)) q = agiBlue_urlToQuery(url); |
222 | |
223 | Page page, bool newPage = unpair uniqCI2_sync(cc, Page, +q); |
224 | if (newPage) dbLog("New page", +q); |
225 | //printStructs(+params, +raw, +q); |
226 | |
227 | S top = hcomment("cookie: " + takeFirst(4, session.cookie)) |
228 | + hSilentComputatorWithFlag("agi.blue: " + q) |
229 | + p(ahref(agiBlueURL(), |
230 | //hsnippetimg(#1101682, width := 565/5, height := 800/5, title := "Robot by Peerro @ DeviantArt") |
231 | hsnippetimg(#1101778, width := 96, height := 96, title := "agi.blue - a database for everything") |
232 | )) |
233 | + p(small( |
234 | b(agiBlueNameHTML()) |
235 | + (agiBlue_isOriginal() ? "" : " " + targetBlank("http://agi.blue", "[original]")) |
236 | + " | " + targetBlank(progLink(), "source code") + " of this web site (" + nLines(lines) + ") | " + 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") |
237 | + " | " + targetBlank("http://code.botcompany.de/1024233", "Notes") |
238 | + " | " + ahref(agiBlueURL() + "/query", "Query") |
239 | )); |
240 | |
241 | if (empty(params.get('q)) && empty(raw) && isHomeDomain()) { |
242 | L<Page> pages = sortedByFieldDesc _modified(list(cc, Page)); // TODO: use index |
243 | int start = parseInt(params.get("start")), step = 100; |
244 | S nav = pageNav2("/", l(pages), start, step, "start"); |
245 | ret hhtml2(hhead_title("agi.blue Overview") // SERVE HOME PAGE |
246 | + hbody(hfullcenterAndTopLeft(top |
247 | + hform(b("GIVE ME INPUT:") + "<br><br>" + htextinput('q, autofocus := true) + " " + hsubmit("Ask")) |
248 | + h1(sliceAsHTML() + " has " + nPages(countConcepts(cc, Page)) + " and " + nEntries(countConcepts(cc, Entry))) |
249 | + p(nav) |
250 | + p_nemptyLines(map(pageToHTMLLink(), subList(pages, start, start+step))), |
251 | sliceSelector() |
252 | ))); |
253 | } |
254 | |
255 | S key = trim(params.get('key)), value = trim(params.get('value)); |
256 | L<Entry> entries = GetEntriesAndPost().go(page, params).entries; |
257 | |
258 | Set<S> get = asCISet(nempties(subBot_paramsAsMultiMap().get('get))); |
259 | //S get = params.get('get); |
260 | if (nempty(get)) |
261 | ret serveJSON(collect value(llNotNulls(firstThat(entries, e -> get.contains(e.key))))); |
262 | |
263 | S key2 = key, value2 = value; if (nempty(key) && nempty(value)) key2 = value2 = ""; // input reset |
264 | |
265 | bool withHidden = eq(params.get('withHidden), "1"); |
266 | new Set<Int> hide; |
267 | if (!withHidden) for (Entry e : entries) if (eqic(e.key, 'hide) && isSquareBracketedInt(e.value)) addAll(hide, e.count, parseInt(unSquareBracket(e.value))); |
268 | new MultiMap<Int, S> mmMeta; |
269 | for (Entry e : entries) if (isSquareBracketedInt(e.key)) mmMeta.put(parseInt(unSquareBracket(e.key)), e.value); |
270 | new MultiMap<S> mm; |
271 | for (Entry e : entries) mm.put(e.key, e.value); |
272 | |
273 | //S name = or2(/* ouch */ last(mm.get("read as")), /* end ouch */ unpackAGIDomain(page.url), page.url); |
274 | S name = page.q; |
275 | |
276 | // Find references |
277 | |
278 | L<Entry> refs = concatLists(conceptsWhereIC Entry(value := name), |
279 | conceptsWhereIC Entry(key := name)); |
280 | Set<Page> refPages = asSet(ccollect page(refs)); |
281 | refPages.remove(page); // don't list current page as reference |
282 | |
283 | // Search in page names (depending on default search type) |
284 | |
285 | L<Page> searchResults; |
286 | if (eq(sideSearchType, 'leven)) |
287 | searchResults = levenSearch(page.q, max := 50); |
288 | else if (eq(sideSearchType, 'literal)) |
289 | searchResults = literalSearch(page.q, max := 50); |
290 | else |
291 | searchResults = (L<Page>) dm_call('agiBlueSearch, 'search, page.q, maxResult := searchResultsToShow+1, agiBlueBotID := programID()); |
292 | searchResults.remove(page); |
293 | |
294 | S mainContents = |
295 | top + h1(ahref_unstyled("http://" + url + hquery(+q), htmlEncode2(shorten(displayLength, name))) + (newPage ? " [huh????]" : "")) |
296 | + p_nemptyLines(map(entries, func(Entry e) -> S { |
297 | !withHidden && (hide.contains(e.count) || eqic(e.key, "read as") && eqic(e.value, name)) ? "" |
298 | : "[" + e.count + "] " + |
299 | renderThing(e.key, false) + ": " + |
300 | b(renderThing(e.value, cic(mmMeta.get(e.count), "is a URL"))) |
301 | })) |
302 | + hpostform(h3("Add an entry") |
303 | + "Key: " + hinputfield(key := key2) + " Value: " + hinputfield(value := value2) + "<br><br>" + hsubmit("Add") |
304 | ) |
305 | |
306 | + p(ahref(agiBlueURL() + "/literalSearch" + hquery(q := page.q), "[literal search]", title := "Search pages with a name containing this page's name literally") |
307 | + " " + |
308 | 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") |
309 | + " " + |
310 | ahref(agiBlueURL() + "/search" + hquery(q := page.q), "[scored search]", title := "Search pages with ScoredSearch")); |
311 | |
312 | S sideContents = |
313 | hform(b("GIVE ME INPUT:") + " " |
314 | + htextinput('q) + " " |
315 | + hsubmit("Ask", onclick := "document.getElementById('newInputForm').target = '';") + " " |
316 | + hsubmit("+Tab", title := "Ask and show result in a new tab", onclick := "document.getElementById('newInputForm').target = '_blank';"), |
317 | id := 'newInputForm) |
318 | |
319 | + h3("References (" + l(refPages) + ")") |
320 | |
321 | + p_nemptyLines_showFirst(10, map(pageToHTMLLink(displayLength := sideDisplayLength), refPages)) |
322 | |
323 | + h3(searchTypeToText.get(sideSearchType) + " search results (" + (l(searchResults) >= searchResultsToShow ? searchResultsToShow + "+" : str(l(searchResults))) + ")") |
324 | |
325 | + p_nemptyLines_showFirst(searchResultsToShow, map(pageToHTMLLink(displayLength := sideDisplayLength), searchResults)) |
326 | |
327 | + hdiv("", id := 'extraStuff); |
328 | |
329 | // TODO: sync search delivery with WebSocket creation |
330 | if (asyncSearch) |
331 | doLater(6.0, r { dm_call('agiBlueSearch, 'searchAndPost, page.q, agiBlueBotID := programID()) }); |
332 | |
333 | // serve a concept page |
334 | ret hhtml2(hhead_title(pageDisplayName(page)) + hbody( |
335 | tag('table, |
336 | tr( |
337 | td(sliceSelector(), valign := 'top) |
338 | + td(sideContents, align := 'right, valign := 'top, rowspan := 2)) |
339 | + tr(td(mainContents, align := 'center, valign := 'top)), |
340 | width := "100%", height := "100%"))); |
341 | } |
342 | |
343 | O servePagesToBot(Iterable<Page> pages) { |
344 | ret serveListToBot(map(pageToMap(wrapMapAsParams(params)), pages)); |
345 | } |
346 | |
347 | O serveListToBot(Collection l) { |
348 | if (nempty(params.get('max))) |
349 | l = takeFirst(parseInt(params.get('max)), l); |
350 | ret serveJSON(l); |
351 | } |
352 | |
353 | // uri starts with "/bot/" |
354 | O serveBot() { |
355 | S q = params.get('q); |
356 | |
357 | if (eqic(uri, "/bot/hello")) ret serveJSON("hello"); |
358 | if (eqic(uri, "/bot/hasPage")) ret serveJSON(hasPage(q)); |
359 | if (eqic(uri, "/bot/randomPageContaining")) { |
360 | assertNempty(q); |
361 | ret servePageToBot(random(filter(list(cc, Page), p -> cic(p.q, q))), params); |
362 | } |
363 | if (eqic(uri, "/bot/allPages")) |
364 | ret servePagesToBot(list(cc, Page)); |
365 | if (eqic(uri, "/bot/allPagesStartingWith")) { |
366 | assertNempty(q); |
367 | ret servePagesToBot(filter(list(cc, Page), p -> swic(p.q, q))); |
368 | } |
369 | if (eqic(uri, "/bot/allPagesEndingWith")) { |
370 | assertNempty(q); |
371 | ret servePagesToBot(filter(list(cc, Page), p -> ewic(p.q, q))); |
372 | } |
373 | if (eqic(uri, "/bot/allPagesContaining")) { |
374 | assertNempty(q); |
375 | ret servePagesToBot(filter(list(cc, Page), p -> cic(p.q, q))); |
376 | } |
377 | if (eqic(uri, "/bot/allPagesContainingRegexp")) { |
378 | assertNempty(q); |
379 | Pattern pat = regexpIC(q); |
380 | ret servePagesToBot(filter(list(cc, Page), p -> regexpFindIC(pat, p.q))); |
381 | } |
382 | |
383 | if (eqicOneOf(uri, "/bot/postSigned", "/bot/makePhysicalSlice", "/bot/approveTrustRequest")) { |
384 | S text = rtrim(params.get('text)); |
385 | S key = getSignerKey(text); |
386 | if (empty(key)) ret subBot_serve500("Please include your public key"); |
387 | if (!isSignedWithKey(text, key)) ret subBot_serve500("Signature didn't verify"); |
388 | text = dropLastTwoLines(text); // drop signer + sig line |
389 | |
390 | Signer signer = uniq_sync Signer(publicKey := key); |
391 | |
392 | if (eqic(uri, "/bot/makePhysicalSlice")) { |
393 | if (!signer.trusted) ret subBot_serve500("Untrusted signer"); |
394 | Page page = findPageFromParams(jsonDecodeMap(text)); |
395 | if (page == null) ret subBot_serve500("Page not found"); |
396 | ret serveJSON(uniq2_sync(PhysicalSlice, slicePage := page).b ? "Slice made" : "Slice exists"); |
397 | } |
398 | |
399 | if (eqic(uri, "/bot/postSigned")) { |
400 | new L out; |
401 | for (S line : tlft(text)) { |
402 | SS map = jsonDecodeMap(line); |
403 | new GetEntriesAndPost x; |
404 | x.signer = signer; |
405 | Page page = findOrMakePageFromParams(map); |
406 | if (page == null) continue with out.add("Invalid page reference"); |
407 | x.go(page, map); |
408 | out.add(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); |
409 | } |
410 | ret serveJSON(out); |
411 | } |
412 | |
413 | if (eqic(uri, "/bot/approveTrustRequest")) { |
414 | if (!signer.trusted) ret subBot_serve500("Untrusted signer"); |
415 | Signer toApprove = conceptWhere Signer(publicKey := trim(text)); |
416 | if (toApprove == null) ret subBot_serve500("Signer to approve not found"); |
417 | cset(toApprove, trusted := true, approvedBy := signer.globalID); |
418 | ret serveJSON("Approved: " + trim(text)); |
419 | } |
420 | |
421 | ret subBot_serve500("CONFUSION"); |
422 | } |
423 | |
424 | if (eqic(uri, "/bot/post")) { |
425 | new GetEntriesAndPost x; |
426 | x.go(pageFromQ(q), params); |
427 | ret serveJSON(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); |
428 | } |
429 | |
430 | if (eqic(uri, "/bot/entriesOnPage")) |
431 | ret serveJSON(map(entriesOnPage(findPageFromParams(params)), entryToMap(false))); |
432 | |
433 | if (eqic(uri, "/bot/lookup")) { |
434 | S key = params.get('key); |
435 | if (empty(key)) ret serveJSON("Need key"); |
436 | S value = getValue(findPageFromParams(params), key); |
437 | ret serveJSON(empty(value) ? "" : litmap(+value)); |
438 | } |
439 | |
440 | if (eqic(uri, "/bot/latestEntries")) |
441 | ret serveJSON(map(takeFirst(10, slice.idx_latestEntries.objectIterator()), entryToMap(true))); |
442 | if (eqic(uri, "/bot/latestPages")) |
443 | ret serveJSON(map(takeFirst(10, slice.idx_latestCreatedPages.objectIterator()), pageToMap())); |
444 | if (eqic(uri, "/bot/latestChangedPages")) |
445 | ret serveJSON(map(takeFirst(10, slice.idx_latestChangedPages.objectIterator()), pageToMap())); |
446 | |
447 | if (eqic(uri, "/bot/totalPageCount")) |
448 | ret serveJSON(countConcepts(Page)); |
449 | if (eqic(uri, "/bot/pageWithoutPhysicalSliceCount")) |
450 | ret serveJSON(countConceptsWhere(Page, slice := null)); |
451 | if (eqic(uri, "/bot/physicalSliceCount")) |
452 | ret serveJSON(countConcepts(PhysicalSlice)); |
453 | if (eqic(uri, "/bot/trustedSignersCount")) |
454 | ret serveJSON(countConcepts(Signer, trusted := true)); |
455 | |
456 | if (eqic(uri, "/bot/valueSearch")) { |
457 | S value = params.get('value); |
458 | L<Entry> entries = conceptsWhereIC Entry(+value); |
459 | ret serveJSON(map(takeFirst(100, entries), entryToMap(true))); |
460 | } |
461 | |
462 | if (eqic(uri, "/bot/keyAndValueSearch")) { |
463 | S key = params.get('key), value = params.get('value); |
464 | Cl<Page> pages = pagesForKeyAndValue(key, value); |
465 | ret servePagesToBot(pages); |
466 | } |
467 | |
468 | if (eqic(uri, "/bot/keyValuePairsByPopularity")) { |
469 | L<PairS> pairs = map(list(Entry), e -> pair(e.key, e.value)); |
470 | LPair<S, Int> pairs2 = multiSetTopPairs(ciMultiSet(map pairToUglyStringForCIComparison(pairs))); |
471 | ret serveJSON(map(pairs2, p -> { |
472 | S key, value = unpair pairFromUglyString(p.a); |
473 | ret litorderedmap(n := p.b, +key, +value); |
474 | })); |
475 | } |
476 | |
477 | if (eqic(uri, "/bot/allKeys")) |
478 | ret serveListToBot(distinctCIFieldValuesOfConcepts Entry('key)); |
479 | |
480 | if (eqic(uri, "/bot/allKeysByPopularity")) |
481 | ret serveListToBot(mapMultiSetByPopularity(distinctCIFieldValuesOfConcepts_multiSet Entry('key), (key, n) -> litorderedmap(+n, +key))); |
482 | |
483 | if (eqic(uri, "/bot/dbSize")) |
484 | ret serveJSON(l(conceptsFile())); |
485 | |
486 | if (eqic(uri, "/bot/query")) { |
487 | S query = params.get('query); |
488 | L<ALQLLine> lines = agiBlue_parseQueryScript(query); |
489 | |
490 | SS vars = ciMap(); |
491 | for (ALQLLine line : lines) { |
492 | if (line cast ALQLReturn) |
493 | ret serveJSON(ll(getOrKeep(vars, line.var))); |
494 | else if (line cast ALQLTriple) { |
495 | T3S t = line.triple; |
496 | t = tripleMap(t, s -> getOrKeep(vars, s)); |
497 | Cl<Page> pages; |
498 | S var; |
499 | if (isDollarVar(t.c)) { |
500 | var = t.c; |
501 | if (isDollarVar(t.a)) { |
502 | if (isDollarVar(t.b)) todo(t); |
503 | Entry e = random(conceptsWhereCI Entry(key := t.b)); |
504 | if (e == null) ret serveJSON("No results for " + var); |
505 | pages = pagesForKeyAndValue(t.b, t.c); |
506 | vars.put(t.a, e.page->q); |
507 | vars.put(t.c, e.value); |
508 | continue; |
509 | } else if (isDollarVar(t.b)) { |
510 | Page page = findPageFromQ(t.a); |
511 | Entry e = random(findBackRefs(page, Entry)); |
512 | if (e == null) ret serveJSON("No results for " + var); |
513 | vars.put(t.b, e.key); |
514 | vars.put(t.c, e.value); |
515 | continue; |
516 | } else { |
517 | S val = getValue(t.a, t.b); |
518 | if (val == null) ret serveJSON("No results for " + var); |
519 | pages = ll(pageFromQ(val)); |
520 | } |
521 | } else if (isDollarVar(t.b)) { |
522 | var = t.b; |
523 | if (isDollarVar(t.c)) todo(t); |
524 | if (isDollarVar(t.a)) { |
525 | L<Entry> entries = conceptsWhereCI Entry(value := t.c); |
526 | if (empty(entries)) ret serveJSON("No results for " + var); |
527 | Entry e = random(entries); |
528 | vars.put(t.a, e.page->q); |
529 | vars.put(t.b, e.key); |
530 | continue; |
531 | } else { |
532 | Cl<S> keys = keysForPageAndValue(t.a, t.c); |
533 | if (empty(keys)) ret serveJSON("No results for " + var); |
534 | pages = map(f<S, Page> pageFromQ, keys); |
535 | } |
536 | } else { |
537 | var = t.a; |
538 | if (!isDollarVar(t.a)) todo(t); |
539 | if (isDollarVar(t.b)) todo(t); |
540 | if (isDollarVar(t.c)) todo(t); |
541 | pages = pagesForKeyAndValue(t.b, t.c); |
542 | } |
543 | if (empty(pages)) ret serveJSON("No results for " + var); |
544 | vars.put(var, random(pages).q); |
545 | } else |
546 | fail("Can't interpret: " + line); |
547 | } |
548 | |
549 | ret serveJSON("No return statement"); |
550 | } |
551 | |
552 | if (eqic(uri, "/bot/createSlice")) { |
553 | Slice slice = createSlice(assertNempty(params.get('name))); |
554 | ret serveJSON(litorderedmap(id := str(slice.globalID), name := slice.name)); |
555 | } |
556 | |
557 | // end of serveBot() |
558 | |
559 | ret subBot_serve404(); |
560 | } |
561 | |
562 | O serveLiteralSearch() { |
563 | S q = params.get('q); |
564 | L<Page> searchResults = literalSearch(q); |
565 | ret serveSearchResults("literal search" , q, searchResultsToShow, searchResults); |
566 | } |
567 | |
568 | L<Page> literalSearch(S q, O... _) { |
569 | int searchResultsToShow = optPar max(_, 100); |
570 | |
571 | // quick search in random order |
572 | //L<Page> searchResults = takeFirst(searchResultsToShow+1, filterIterator(iterator(list(cc, Page)), p -> cic(p.q, q)); |
573 | |
574 | // full search, order by length |
575 | ret takeFirst(searchResultsToShow+1, pagesSortedByLength(filter(list(cc, Page), p -> cic(p.q, q)))); |
576 | } |
577 | |
578 | O serveScoredSearch() { |
579 | S q = params.get('q); |
580 | L<Page> searchResults = (L<Page>) dm_call('agiBlueSearch, 'search, q); |
581 | ret serveSearchResults("scored search" , q, searchResultsToShow, searchResults); |
582 | } |
583 | |
584 | O serveLevenSearch() { |
585 | S q = params.get('q); |
586 | L<Page> searchResults = levenSearch(q); |
587 | ret serveSearchResults("leven search with distance 1" , q, searchResultsToShow, searchResults); |
588 | } |
589 | |
590 | L<Page> levenSearch(S q, O... _) { |
591 | int searchResultsToShow = optPar max(_, 100); |
592 | int maxEditDistance = 1; |
593 | |
594 | new Map<Page, Int> map; |
595 | for (Page p) { |
596 | int distance = leven_limitedIC(q, p.q, maxEditDistance+1); |
597 | if (distance <= maxEditDistance) map.put(p, distance); |
598 | } |
599 | ret takeFirst(searchResultsToShow+1, keysSortedByValue(map)); |
600 | } |
601 | |
602 | O serveSearchResults(S searchType, S q, int searchResultsToShow, Collection<Page> searchResults) { |
603 | S title = "agi.blue " + searchType + " for " + htmlEncode2(quote(q)) + " (" + (l(searchResults) >= searchResultsToShow ? searchResultsToShow + "+ results" : nResults(l(searchResults))) + ")"; |
604 | |
605 | ret hhtml2(hhead_title(htmldecode_dropAllTags(title)) |
606 | + hbody(hfullcenter(//top + |
607 | h3(title) |
608 | + p_nemptyLines_showFirst(searchResultsToShow, map(pageToHTMLLink(), searchResults))))); |
609 | } |
610 | |
611 | O serveQueryPage() { |
612 | S query = params.get('query); |
613 | if (query == null) query = loadSnippet(#1024258); |
614 | S title = agiBlueNameHTML() + " | Execute a query script (" + targetBlank("http://code.botcompany.de/1024274", "ALQL") + ")"; |
615 | ret hhtml2(hhead_title(htmldecode_dropAllTags(title)) |
616 | + hbody(hfullcenter( |
617 | h3(title) |
618 | + form( |
619 | htextarea(query, name := 'query, cols := 80, rows := 10, autofocus := true) |
620 | + "<br><br>" |
621 | + hsubmit("Execute"), action := "/bot/query") |
622 | ))); |
623 | } |
624 | |
625 | O serveCreateSlicePage() { |
626 | S sliceName = trim(params.get('sliceName)); |
627 | if (eq(params.get('doIt), "1") && nempty(sliceName)) { |
628 | // TODO: check for existing name |
629 | Slice slice = createSlice(sliceName); |
630 | ret hrefresh(agiBlueURL() + hquery(slice := slice.globalID)); |
631 | } |
632 | |
633 | S title = agiBlueNameHTML() + " | Create slice"; |
634 | ret hhtml2(hhead_title(htmldecode_dropAllTags(title)) |
635 | + hbody(hfullcenter( |
636 | h3(title) |
637 | + form( |
638 | hhidden(doIt := 1) |
639 | + "Slice name: " + htextinput(+sliceName, autofocus := true) |
640 | + "<br><br>" |
641 | + hsubmit("Create slice")) |
642 | ))); |
643 | } |
644 | |
645 | Page findPageFromParams(Map map) { |
646 | S q = getString q(map); |
647 | ret empty(q) ? null : findPageFromQ(q); |
648 | } |
649 | |
650 | Page findOrMakePageFromParams(Map map) { |
651 | ret pageFromQ(getString q(map)); |
652 | } |
653 | |
654 | Page findPageFromQ(S q) { |
655 | ret conceptWhereCI(cc, Page, +q); |
656 | } |
657 | |
658 | Page pageFromQ(S q) { |
659 | ret slice.pageFromQ(q); |
660 | } |
661 | |
662 | F1<Page, S> pageToHTMLLink(O... _) { |
663 | optPar int displayLength = main.displayLength; |
664 | ret func(Page p) -> S { |
665 | S name = pageDisplayName(p); |
666 | ret ahref(agiBlueURL() + hquery(q := p.q), |
667 | htmlEncode2(shorten(displayLength, name)), |
668 | title := name); |
669 | }; |
670 | } |
671 | |
672 | // DB functions |
673 | |
674 | bool hasPage(S q) { ret hasConceptWhereIC(Page, +q); } |
675 | S getValue(Page page, S key) { |
676 | ret page == null || empty(key) ? null : getString value(highestByField count(objectsWhereIC(findBackRefs(page, Entry), +key))); |
677 | } |
678 | S getValue(S page, S key) { |
679 | ret getValue(findPageFromQ(page), key); |
680 | } |
681 | S pageDisplayName(Page page) { |
682 | /*S name = getValue(page, "read as"); |
683 | bool unnaturalName = nempty(name) && !eq(makeAGIDomain(name), page.url); |
684 | ret unnaturalName ? name + " " + squareBracketed(page.url) : or2(name, unpackAGIDomainOpt(page.url));*/ |
685 | ret page.q; |
686 | } |
687 | |
688 | Set<Page> pagesForKeyAndValue(S key, S value) { |
689 | L<Entry> entries = conceptsWhereIC Entry(+value, +key); |
690 | ret asSet(ccollect page(entries)); |
691 | } |
692 | |
693 | Cl<S> keysForPageAndValue(S q, S value) { |
694 | Page page = findPageFromQ(q); |
695 | if (page == null) null; |
696 | ret collect key(conceptsWhereIC Entry(+page, +value)); |
697 | } |
698 | |
699 | S sliceSelector() { |
700 | ret !showSliceSelector ? "" : hform( |
701 | "Select reality slice: " |
702 | + hselect(availableSlices(), session.selectedSlice, name := 'slice) |
703 | /*+ " " + hsubmit("Go")*/ |
704 | + " " + ahref(agiBlueURL() + "/createSlice", "Create slice...") |
705 | , onchange := "this.form.submit()"); |
706 | } |
707 | |
708 | S sliceAsHTML() { |
709 | if (slice.isMainSlice()) ret htmlEncode2(agiBlueName() + "'s main slice"); |
710 | if (slice.sliceConcept == null) ret "Slice ???"; |
711 | ret htmlEncode2("Slice " + quote(slice.sliceConcept.name)); |
712 | } |
713 | |
714 | } // end of Request |
715 | |
716 | svoid dbLog(O... params) { |
717 | logStructure(programFile("db.log"), ll(params)); |
718 | } |
719 | |
720 | static IF1<Page, Map> pageToMap(O... _) { |
721 | optPar bool withEntries; |
722 | |
723 | bool nameOnly = eqOneOf(optPar nameOnly(_), "1", true); |
724 | if (nameOnly) ret (IF1<Page, Map>) p -> litmap(q := p.q); |
725 | |
726 | ret (IF1<Page, Map>) p -> { |
727 | L<Entry> entries = findBackRefs(p, Entry); |
728 | ret litorderedmap( |
729 | q := p.q, |
730 | nEntries := l(entries), |
731 | created := p.created, |
732 | modified := p._modified, |
733 | entries := !withEntries ? null : map(entries, entryToMap(false))); |
734 | }; |
735 | } |
736 | |
737 | static IF1<Entry, Map> entryToMap(bool withPage) { |
738 | ret (IF1<Entry, Map>) e -> litorderedmap( |
739 | created := e.created, |
740 | i := e.count, |
741 | key := e.key, |
742 | value := e.value, |
743 | q := withPage ? e.page->q : null, |
744 | signer := getString globalID(e.signer!)); |
745 | } |
746 | |
747 | sO servePageToBot(Page page, SS params) { |
748 | if (page == null) ret serveJSON(null); |
749 | params = asCIMap(params); |
750 | Map map = pageToMap(withEntries := valueIs1 withEntries(params)).get(page); |
751 | ret serveJSON(map); |
752 | } |
753 | |
754 | sclass GetEntriesAndPost { |
755 | L<Entry> entries; |
756 | Entry entry; |
757 | bool newEntry; |
758 | Signer signer; |
759 | |
760 | GetEntriesAndPost go(Page page, SS params) { |
761 | S key = trim(params.get('key)), value = trim(params.get('value)); |
762 | print("GetEntriesAndPost: " + quote(page.q) + ", " + quote(key) + " := " + quote(value)); |
763 | withDBLock { |
764 | entries = findBackRefs(page, Entry); |
765 | if (nempty(key) && nempty(value)) { |
766 | S ip = subBot_clientIP(); |
767 | entry = firstThat(e -> eqic(e.key, key) && eq_icIf(!allowMultipleCasesInValues, e.value, value) && eq(e.ip, ip), entries); |
768 | if (entry == null) { |
769 | print("SAVING"); |
770 | Entry e = cnew Entry(+page, +key, +value, +ip, count := l(entries) + 1, +signer); |
771 | page.change(); // bump modification date |
772 | entry = e; |
773 | newEntry = true; |
774 | entries.add(entry); |
775 | dbLog("New entry", page := page.q, globalID := e.globalID, count := e.count, +key, +value); |
776 | } |
777 | } |
778 | } |
779 | |
780 | sortByFieldInPlace created(entries); |
781 | numberEntriesInConceptField count(entries); |
782 | this; |
783 | } |
784 | } |
785 | |
786 | static Collection<S> backSearch(S key, S value) { |
787 | // we query the index for the value field because that yields fewer results |
788 | ret map(conceptsWhereCI Entry(value := "a slice of reality", key := "is"), e -> e.page->q); |
789 | } |
790 | |
791 | static SS availableSlices() { |
792 | ret mapToOrderedMap(s -> pair(str(s.globalID), s.name + " [ID: " + s.globalID + "]"), list(Slice)); |
793 | } |
794 | |
795 | static PhysicalSlice getOrMakePhysicalSlice(Concepts cc, Page p) { |
796 | PhysicalSlice slice = first(findBackRefs PhysicalSlice(p)); |
797 | if (slice == null) |
798 | slice = uniq_sync(cc, PhysicalSlice, slicePage := p); |
799 | ret slice; |
800 | } |
801 | |
802 | static PhysicalSlice theWildSlice() { |
803 | ret getOrMakePhysicalSlice(db_mainConcepts(), loadedSlices.get("").pageFromQ("the wild slice")); |
804 | } |
805 | |
806 | /*svoid moveSlicelessPagesToTheWildSlice() { |
807 | PhysicalSlice slice = theWildSlice(); |
808 | for (Page p : conceptsWhere Page(slice := null)) { |
809 | print("Moving to wild slice: " + p.q); |
810 | cset_sync(p, +slice); |
811 | } |
812 | }*/ |
813 | |
814 | sS renderThing(S s, bool forceURLDisplay) { |
815 | ret forceURLDisplay || isURL(s) || isAGIDomain(s) |
816 | ? ahref(fixAGILink(absoluteURL(s)), htmlencode2(shorten(displayLength, s))) |
817 | : ahref(agiBlue_linkForPhrase(s), htmlencode2(shorten(displayLength, s))); |
818 | } |
819 | |
820 | static L<Entry> entriesOnPage(Page p) { |
821 | ret p == null ? null : sortedByField count(findBackRefs(p, Entry)); |
822 | } |
823 | |
824 | static L<Page> pagesSortedByLength(L<Page> l) { |
825 | ret sortedByCalculatedField(l, p -> l(p.q)); |
826 | } |
827 | |
828 | sS hhtml2(S contents) { |
829 | ret hhtml(hAddToHead_fast(contents, |
830 | hIncludeGoogleFont("Source Sans Pro") |
831 | + hmobilefix() |
832 | + hstylesheet("body { font-family: Source Sans Pro }"))); |
833 | } |
834 | |
835 | sbool agiBlue_isOriginal() { |
836 | ret amProgram(#1023558); |
837 | } |
838 | |
839 | sS agiBlueURL() { |
840 | ret agiBlue_isOriginal() ? "http://agi.blue" : "/" + psI(programID()) + "/raw"; |
841 | } |
842 | |
843 | sS agiBlueName() { |
844 | ret agiBlue_isOriginal() ? "agi.blue" : "agi.blue clone " + programID(); |
845 | } |
846 | |
847 | svoid cleanMeUp { |
848 | cleanUp(fan); |
849 | } |
850 | |
851 | static LoadedSlice loadSlice(S caseID) { |
852 | caseID = unnull(caseID); |
853 | fan.get(caseID); |
854 | ret loadedSlices.get(caseID); |
855 | } |
856 | |
857 | static Slice sliceConceptForGlobalID(S globalID) { |
858 | ret conceptWhere Slice(globalID := toGlobalIDObj(globalID)); |
859 | } |
860 | |
861 | static Slice sliceConceptForCaseID(S caseID) { |
862 | ret conceptWhere Slice(+caseID); |
863 | } |
864 | |
865 | sS agiBlueNameHTML() { |
866 | ret ahref(agiBlueURL(), htmlEncode2(agiBlueName())); |
867 | } |
868 | |
869 | static Slice createSlice(S name) { |
870 | Slice slice = cnew Slice(+name); |
871 | cset(slice, caseID := slice.defaultCaseID()); |
872 | ret slice; |
873 | } |
Began life as a copy of #1024284
download show line numbers debug dex old transpilations
Travelled to 6 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1024288 |
Snippet name: | agi.blue source [adding slices, dev.] |
Eternal ID of this version: | #1024288/60 |
Text MD5: | 94034d028c6e722e81b59c7de83e40f5 |
Transpilation MD5: | e9bf0b87da09144e9d08544afe548ed5 |
Author: | stefan |
Category: | javax / agi.blue |
Type: | JavaX module (desktop) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-07-30 14:07:14 |
Source code size: | 31456 bytes / 873 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 433 / 2215 |
Version history: | 59 change(s) |
Referenced in: | [show references] |