1 | sbool maintainPositionalIndex = true; |
2 | |
3 | // in main DB |
4 | concept Slice { |
5 | GlobalID globalID = aGlobalIDObject(); |
6 | S caseID; |
7 | S name; |
8 | bool indexed = true; |
9 | long sliceDumped; // wall time, set before dump starts |
10 | |
11 | new Ref<User> owner; |
12 | |
13 | S defaultCaseID() { |
14 | ret uniqueFileNameUsingMD5_80_v2(name + " " + globalID); |
15 | } |
16 | |
17 | GlobalID globalID() { ret globalID; } |
18 | } |
19 | |
20 | // in any DB |
21 | concept Page { |
22 | !include #1024467 // compact global ID |
23 | short flags; |
24 | |
25 | //S globalID = aGlobalID(); |
26 | S q; |
27 | |
28 | void _onLoad() { |
29 | S id = cget(this, 'globalID); |
30 | if (id != null) { |
31 | setGlobalID(id); |
32 | cset(this, globalID := null); |
33 | } |
34 | } |
35 | |
36 | // for in-VM helper bots |
37 | void quickPost(S key, S value) { |
38 | agiBlue().new GetEntriesAndPost(_concepts).go(this, litmap(+key, +value)); |
39 | } |
40 | |
41 | AGIBlue agiBlue() { ret (AGIBlue) mapGet(_concepts.miscMap, 'agiBlue); } |
42 | AGIBlue.LoadedSlice loadedSlice() { ret (AGIBlue.LoadedSlice) mapGet(_concepts.miscMap, 'loadedSlice); } |
43 | } |
44 | |
45 | abstract concept AbstractEntry { |
46 | new Ref<Page> page; |
47 | int count; |
48 | S key; |
49 | S ip; |
50 | new Ref<Signer> signer; |
51 | |
52 | S q() { Page p = page!; ret p == null ? null : page->q; } |
53 | } |
54 | |
55 | concept Entry > AbstractEntry { |
56 | S globalID = aGlobalID(); // TODO: migrate to object |
57 | S value; |
58 | } |
59 | |
60 | /*concept MultiLineEntry > AbstractEntry { |
61 | GlobalID globalID = aGlobalIDObject(); |
62 | S value; |
63 | }*/ |
64 | |
65 | concept BlobEntry > AbstractEntry { |
66 | GlobalID globalID = aGlobalIDObject(); |
67 | long length; |
68 | S md5; |
69 | S contentType; |
70 | |
71 | GlobalID globalID() { ret globalID; } |
72 | } |
73 | |
74 | // in main DB? |
75 | concept Signer { |
76 | S globalID = aGlobalID(); |
77 | S publicKey; |
78 | bool trusted; |
79 | S approvedBy; |
80 | } |
81 | |
82 | concept User { |
83 | GlobalID globalID = aGlobalIDObject(); |
84 | long lastSeen; |
85 | } |
86 | |
87 | concept CookieUser > User { |
88 | S cookie; |
89 | |
90 | toString { ret "[cookieUser]"; } |
91 | } |
92 | |
93 | concept GoogleUser > User { |
94 | long googleLogInDate; |
95 | S googleEmail, googleFirstName, googleLastName; |
96 | bool googleEmailVerified; |
97 | |
98 | toString { ret googleFirstName + " " + googleLastName; } |
99 | } |
100 | |
101 | concept MinimalUser > User { |
102 | S userName, encryptedPassword; // TODO |
103 | |
104 | toString { ret "[minimalUser] " + userName; } |
105 | } |
106 | |
107 | // supply password with _pass |
108 | concept JustPasswordUser > User { |
109 | S password; |
110 | } |
111 | |
112 | // in main DB |
113 | concept Session { |
114 | S cookie; |
115 | S selectedSlice; // global ID |
116 | Page slicePage; // phasing out |
117 | new Ref<User> user; |
118 | } |
119 | |
120 | // singleton concept in every DB |
121 | concept SliceInfo { |
122 | GlobalID globalID; |
123 | LS mandatoryBotVMBusIDs; |
124 | bool openPosting = true; // allow posting by anyone |
125 | } |
126 | |
127 | sclass CentralIndexEntry { |
128 | Set<Slice> slices = synchroLinkedHashSet(); |
129 | } |
130 | |
131 | abstract sclass AGIBlue { |
132 | // Customizations: |
133 | |
134 | bool asyncSearch = false; |
135 | bool allowMultipleCasesInValues = true; |
136 | bool showSliceSelector = true; |
137 | bool showGoogleLogIn = true; |
138 | bool makeAllKeysPages = true; |
139 | bool makeAllValuesPages = true; |
140 | bool legacy = true; // We still have slices without applied fixes |
141 | bool dumpSlicesOnAnyChange = true; // testing |
142 | |
143 | int maxSliceNameLength = 1024; |
144 | int displayLength = 140; |
145 | int sideDisplayLength = 50; // when in side bar |
146 | int searchResultsToShow = 50; |
147 | S sideSearchType = 'literal; |
148 | int maxBackgroundSlices = 1000; |
149 | |
150 | // end of customizations |
151 | |
152 | long cloningSince, clonedSince; |
153 | bool isClone; |
154 | |
155 | // general methods |
156 | |
157 | File sliceDumpFile(Slice slice) { |
158 | ret programFile("slice-dumps/" + or2(slice.caseID, "main") + ".slice"); |
159 | } |
160 | |
161 | File dumpSliceToFile(LoadedSlice slice) { |
162 | ret withDBLock(slice.cc, func -> File { |
163 | cset(slice.sliceConcept, sliceDumped := now()); |
164 | File file = sliceDumpFile(slice.sliceConcept); |
165 | new LS lines; |
166 | for (Page p : list(slice.cc, Page)) |
167 | lines.add(quote(p.q)); |
168 | for (Entry e : list(slice.cc, Entry)) |
169 | lines.add(sfu(tripleToList(entryToTriple(e)))); |
170 | saveLinesAsTextFile(file, lines); |
171 | ret file; |
172 | }); |
173 | } |
174 | |
175 | Page pageFromQ(Concepts cc, S q) { |
176 | ret empty(q) ? null : uniqCI_sync(cc, Page, +q); |
177 | } |
178 | |
179 | // executed by rst_index |
180 | void makeCentralIndex() { |
181 | newCentralIndex.clear(); |
182 | long time = now(); |
183 | pcall { |
184 | for (Slice slice : conceptsWhere(Slice, indexed := true)) |
185 | addToNewCentralIndex(slice); |
186 | } |
187 | centralIndex.putAll(newCentralIndex); |
188 | removeAllKeysNotIn(centralIndex, newCentralIndex); |
189 | newCentralIndex.clear(); |
190 | centralIndexMade = now(); |
191 | centralIndexMadeIn = now()-time; |
192 | } |
193 | |
194 | void addToNewCentralIndex(Slice slice) { |
195 | for (S s : sliceDump_pageNamesIncludingKeys(sliceDumpFile(slice))) |
196 | newCentralIndexGet(s).slices.add(slice); |
197 | } |
198 | |
199 | CentralIndexEntry centralIndexGet(S s) { ret getOrCreate CentralIndexEntry(centralIndex, s); } |
200 | |
201 | CentralIndexEntry newCentralIndexGet(S s) { ret getOrCreate CentralIndexEntry(newCentralIndex, s); } |
202 | |
203 | Cl<Slice> centralIndexGetSlices(S s) { |
204 | CentralIndexEntry e = centralIndexGet(s); |
205 | ret e == null ? null : e.slices; |
206 | } |
207 | |
208 | void dbLog(O... params) { |
209 | logStructure(programFile("db.log"), ll(params)); |
210 | } |
211 | |
212 | class LoadedSlice { // non-persistent class |
213 | S caseID; |
214 | Concepts cc; |
215 | Slice sliceConcept; |
216 | SliceInfo sliceInfo; |
217 | long lastAccess = sysNow(); |
218 | ReliableSingleThread rstDumpSlice = new(r { |
219 | dumpSliceToFile(LoadedSlice.this); |
220 | rst_index.trigger(); // rebuild index too - TODO: only this slice |
221 | }); |
222 | bool haltSliceDumping; |
223 | |
224 | // indices |
225 | |
226 | ConceptFieldIndexDesc idx_latestEntries, idx_latestCreatedPages, idx_latestChangedPages; |
227 | |
228 | ReliableSingleThread rstMakePositionalIndex = new(r makePositionalIndex_impl); |
229 | PositionalTokenIndex2 positionalIndex; |
230 | |
231 | void makePositionalIndex_impl { |
232 | time "Making positional index" { |
233 | positionalIndex = positionalTokenIndex2_wordTok(collect q(list(cc, Page))); |
234 | } |
235 | } |
236 | |
237 | *(S *caseID, Concepts *cc) { |
238 | csetAll(list(cc, Page), slice := null, url := null); // clear legacy fields |
239 | releaseEmptyFieldValuesOfAllConcepts(cc); |
240 | indexThings(); |
241 | |
242 | // forward changes to Slice concept |
243 | onConceptsChange(cc, rOnceAtATimeOnly(r { |
244 | cset(sliceConcept, _modified := now()) |
245 | ; |
246 | if (!haltSliceDumping && dumpSlicesOnAnyChange && nempty(caseID)) { |
247 | rstDumpSlice.trigger(); |
248 | } |
249 | if (maintainPositionalIndex) rstMakePositionalIndex.trigger(); |
250 | })); |
251 | |
252 | if (makeAllKeysPages && legacy) |
253 | for (Entry e : list(cc, Entry)) |
254 | pageFromQ(e.key); |
255 | |
256 | if (makeAllValuesPages && legacy) |
257 | for (Entry e : list(cc, Entry)) |
258 | pageFromQ(e.value); |
259 | } |
260 | |
261 | void initialSetup(GlobalID globalID) { |
262 | sliceInfo = uniq(cc, SliceInfo); |
263 | cset(sliceInfo, +globalID); |
264 | } |
265 | |
266 | SliceInfo sliceInfo() { ret sliceInfo; } |
267 | |
268 | // create if not there |
269 | Page pageFromQ(S q) { |
270 | ret AGIBlue.this.pageFromQ(cc, q); |
271 | } |
272 | |
273 | void indexThings() { |
274 | indexConceptFieldsCI(cc, Page, 'q, Entry, 'key, Entry, 'value); |
275 | indexConceptFields(cc, Signer, 'publicKey); |
276 | indexConceptFields(cc, Session, 'cookie); |
277 | indexSingletonConcept(cc, SliceInfo); |
278 | idx_latestCreatedPages = new ConceptFieldIndexDesc(cc, Page, 'created); |
279 | idx_latestChangedPages = new ConceptFieldIndexDesc(cc, Page, '_modified); |
280 | idx_latestEntries = new ConceptFieldIndexDesc(cc, Entry, 'created); |
281 | |
282 | if (maintainPositionalIndex) rstMakePositionalIndex.trigger(); |
283 | } |
284 | |
285 | bool isMainSlice() { ret empty(caseID); } |
286 | GlobalID globalID() { ret sliceConcept.globalID; } |
287 | S name() { ret sliceConcept.name; } |
288 | } |
289 | |
290 | // global vars |
291 | |
292 | ConceptsLoadedOnDemand fan; |
293 | ConceptsLoadedOnDemand backgroundFan; |
294 | Map<S, LoadedSlice> loadedSlices = syncMap(); |
295 | Map<S, LoadedSlice> backgroundSlices = syncMap(); |
296 | Map<S, CentralIndexEntry> centralIndex = ciMap(); |
297 | Map<S, CentralIndexEntry> newCentralIndex = ciMap(); |
298 | long centralIndexMade; // wall time |
299 | long centralIndexMadeIn; |
300 | ReliableSingleThread rst_index = new(r makeCentralIndex); |
301 | ConceptFieldIndexDesc idx_slicesByModification, idx_usersBySeen; |
302 | ConceptFieldIndexCI idx_slicesByName, idx_usersByName; |
303 | |
304 | class GetEntriesAndPost { |
305 | Concepts cc; |
306 | L<Entry> entries; |
307 | Entry entry; |
308 | bool newEntry; |
309 | Signer signer; |
310 | Session session; |
311 | |
312 | *(Concepts *cc, Session *session) {} // migrate to this constructor |
313 | *(Concepts *cc) {} |
314 | |
315 | GetEntriesAndPost go(Page page, SS params) { |
316 | S key = trim(params.get('key)), value = trim(params.get('value)); |
317 | print("GetEntriesAndPost: " + quote(page.q) + ", " + quote(key) + " := " + quote(value)); |
318 | withDBLock { |
319 | entries = findBackRefs(page, Entry); |
320 | if (nempty(key) && nempty(value)) { |
321 | Slice slice = page.loadedSlice().sliceConcept; |
322 | SliceInfo sliceInfo = page.loadedSlice().sliceInfo(); |
323 | if (!sliceInfo.openPosting && session != null && session.user! != slice.owner!) |
324 | fail("Not owner of slice"); |
325 | S ip = subBot_clientIP(); |
326 | entry = firstThat(e -> eqic(e.key, key) && eq_icIf(!allowMultipleCasesInValues, e.value, value) && eq(e.ip, ip), entries); |
327 | if (entry == null) { |
328 | print("SAVING"); |
329 | Entry e = cnew(cc, Entry, +page, +key, +value, +ip, count := l(entries) + 1, +signer); |
330 | if (makeAllKeysPages) pageFromQ(cc, key); |
331 | if (makeAllValuesPages) pageFromQ(cc, value); |
332 | page.change(); // bump modification date |
333 | entry = e; |
334 | newEntry = true; |
335 | entries.add(entry); |
336 | dbLog("New entry", page := page.q, globalID := e.globalID, count := e.count, +key, +value); |
337 | } |
338 | } |
339 | } |
340 | |
341 | sortByFieldInPlace created(entries); |
342 | numberEntriesInConceptField count(entries); |
343 | this; |
344 | } |
345 | } |
346 | |
347 | T3S entryToTriple(Entry e) { |
348 | ret e == null ? null : t3(e.page->q, e.key, e.value); |
349 | } |
350 | |
351 | } // end of AGIBlue |
download show line numbers debug dex old transpilations
Travelled to 7 computer(s): bhatertpkbcr, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1024472 |
Snippet name: | agi.blue Core Include |
Eternal ID of this version: | #1024472/33 |
Text MD5: | 4526f1cc5755ea87f92ab26037937966 |
Author: | stefan |
Category: | javax / agi.blue |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-10-14 13:55:16 |
Source code size: | 10131 bytes / 351 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 265 / 690 |
Version history: | 32 change(s) |
Referenced in: | [show references] |