Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

1544
LINES

< > BotCompany Repo | #1024420 // agi.blue source [backup before snapshots]

JavaX module (desktop) [tags: use-pretranspiled]

Download Jar. Uses 1658K of libraries. Click here for Pure Java version (21973L/163K).

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

Author comment

Began life as a copy of #1023558

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: #1024420
Snippet name: agi.blue source [backup before snapshots]
Eternal ID of this version: #1024420/1
Text MD5: e3378ab900f3f8654d6f9ba2d3a1a681
Transpilation MD5: 30a367719fefb00497c6f9a89a7ab61b
Author: stefan
Category: javax / html
Type: JavaX module (desktop)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2019-08-11 19:50:42
Source code size: 54554 bytes / 1544 lines
Pitched / IR pitched: No / No
Views / Downloads: 348 / 2721
Referenced in: [show references]