// in main DB concept Slice { GlobalID globalID = aGlobalIDObject(); S caseID; S name; bool indexed = true; long sliceDumped; // wall time, set before dump starts new Ref owner; S defaultCaseID() { ret uniqueFileNameUsingMD5_80_v2(name + " " + globalID); } GlobalID globalID() { ret globalID; } } // in any DB concept Page { !include #1024467 // compact global ID //S globalID = aGlobalID(); S q; void _onLoad() { S id = cget(this, 'globalID); if (id != null) { setGlobalID(id); cset(this, globalID := null); } } // for in-VM helper bots void quickPost(S key, S value) { agiBlue().new GetEntriesAndPost(_concepts).go(this, litmap(+key, +value)); } AGIBlue agiBlue() { ret (AGIBlue) mapGet(_concepts.miscMap, 'agiBlue); } } abstract concept AbstractEntry { new Ref page; int count; S key; S ip; new Ref signer; S q() { Page p = page!; ret p == null ? null : page->q; } } concept Entry > AbstractEntry { S globalID = aGlobalID(); // TODO: migrate to object S value; } concept MultiLineEntry > AbstractEntry { GlobalID globalID = aGlobalIDObject(); S value; } concept BlobEntry > AbstractEntry { GlobalID globalID = aGlobalIDObject(); long length; S md5; } // in main DB? concept Signer { S globalID = aGlobalID(); S publicKey; bool trusted; S approvedBy; } concept User { GlobalID globalID = aGlobalIDObject(); long lastSeen; } concept CookieUser > User { S cookie; toString { ret "[cookieUser]"; } } concept GoogleUser > User { long googleLogInDate; S googleEmail, googleFirstName, googleLastName; bool googleEmailVerified; toString { ret googleFirstName + " " + googleLastName; } } concept MinimalUser > User { S userName, encryptedPassword; // TODO toString { ret "[minimalUser] " + userName; } } // in main DB concept Session { S cookie; S selectedSlice; // global ID Page slicePage; // phasing out new Ref user; } // singleton concept in every DB concept SliceInfo { GlobalID globalID; LS mandatoryBotVMBusIDs; } sclass CentralIndexEntry { Set slices = synchroLinkedHashSet(); } abstract sclass AGIBlue { // Customizations: bool asyncSearch = false; bool allowMultipleCasesInValues = true; bool showSliceSelector = true; bool showGoogleLogIn = true; bool makeAllKeysPages = true; bool makeAllValuesPages = true; bool legacy = true; // We still have slices without applied fixes bool dumpSlicesOnAnyChange = true; // testing int maxSliceNameLength = 1024; int displayLength = 140; int sideDisplayLength = 50; // when in side bar int searchResultsToShow = 50; S sideSearchType = 'literal; int maxBackgroundSlices = 1000; // end of customizations // abstract methods abstract void dumpSliceToFile(LoadedSlice slice); abstract Page pageFromQ(Concepts cc, S q); abstract void makeCentralIndex(); class LoadedSlice { // non-persistent class S caseID; Concepts cc; Slice sliceConcept; long lastAccess = sysNow(); ReliableSingleThread rstDumpSlice = new(r { dumpSliceToFile(LoadedSlice.this); rst_index.trigger(); // rebuild index too - TODO: only this slice }); bool haltSliceDumping; ConceptFieldIndexDesc idx_latestEntries, idx_latestCreatedPages, idx_latestChangedPages; *(S *caseID, Concepts *cc) { csetAll(list(cc, Page), slice := null, url := null); // clear legacy fields releaseEmptyFieldValuesOfAllConcepts(cc); indexThings(); // forward changes to Slice concept onConceptsChange(cc, rOnceAtATimeOnly(r { cset(sliceConcept, _modified := now()) ; if (!haltSliceDumping && dumpSlicesOnAnyChange && nempty(caseID)) { rstDumpSlice.trigger(); } })); if (makeAllKeysPages && legacy) for (Entry e : list(cc, Entry)) pageFromQ(e.key); if (makeAllValuesPages && legacy) for (Entry e : list(cc, Entry)) pageFromQ(e.value); } void initialSetup(GlobalID globalID) { SliceInfo info = uniq(cc, SliceInfo); cset(info, +globalID); } // create if not there Page pageFromQ(S q) { ret AGIBlue.this.pageFromQ(cc, q); } void indexThings() { indexConceptFieldsCI(cc, Page, 'q, Entry, 'key, Entry, 'value); indexConceptFields(cc, Signer, 'publicKey); indexConceptFields(cc, Session, 'cookie); indexSingletonConcept(cc, SliceInfo); idx_latestCreatedPages = new ConceptFieldIndexDesc(cc, Page, 'created); idx_latestChangedPages = new ConceptFieldIndexDesc(cc, Page, '_modified); idx_latestEntries = new ConceptFieldIndexDesc(cc, Entry, 'created); } bool isMainSlice() { ret empty(caseID); } GlobalID globalID() { ret sliceConcept.globalID; } S name() { ret sliceConcept.name; } } // global vars ConceptsLoadedOnDemand fan; ConceptsLoadedOnDemand backgroundFan; Map loadedSlices = syncMap(); Map backgroundSlices = syncMap(); Map centralIndex = ciMap(); Map newCentralIndex = ciMap(); long centralIndexMade; // wall time long centralIndexMadeIn; ReliableSingleThread rst_index = new(r makeCentralIndex); ConceptFieldIndexDesc idx_slicesByModification, idx_usersBySeen; ConceptFieldIndexCI idx_slicesByName, idx_usersByName; class GetEntriesAndPost { Concepts cc; L entries; Entry entry; bool newEntry; Signer signer; *(Concepts *cc) {} GetEntriesAndPost go(Page page, SS params) { S key = trim(params.get('key)), value = trim(params.get('value)); print("GetEntriesAndPost: " + quote(page.q) + ", " + quote(key) + " := " + quote(value)); withDBLock { entries = findBackRefs(page, Entry); if (nempty(key) && nempty(value)) { S ip = subBot_clientIP(); entry = firstThat(e -> eqic(e.key, key) && eq_icIf(!allowMultipleCasesInValues, e.value, value) && eq(e.ip, ip), entries); if (entry == null) { print("SAVING"); Entry e = cnew(cc, Entry, +page, +key, +value, +ip, count := l(entries) + 1, +signer); if (makeAllKeysPages) pageFromQ(cc, key); if (makeAllValuesPages) pageFromQ(cc, value); page.change(); // bump modification date entry = e; newEntry = true; entries.add(entry); dbLog("New entry", page := page.q, globalID := e.globalID, count := e.count, +key, +value); } } } sortByFieldInPlace created(entries); numberEntriesInConceptField count(entries); this; } } }