1 | lib 1400521 // FlatLAF |
2 | |
3 | set flag PingV3. |
4 | set flag NotifyingPrintLog. |
5 | set flag SymbolAsString. |
6 | |
7 | // Do the JavaX init |
8 | svoid standaloneInit { |
9 | __javax = x30.class; |
10 | x30.__javax = x30.class; // for hotwire |
11 | x30_pkg.x30_util.__setJavaX(x30.class); |
12 | x30.cleanKillMsg = ""; |
13 | callOnLoadMethods(mc()); |
14 | } |
15 | |
16 | concept PrintLogModule > DynPrintLog { |
17 | start { setModuleName("Log"); } |
18 | |
19 | S getPrintLog() { |
20 | ret printLog(); |
21 | } |
22 | |
23 | bool useErrorHandling() { false; } |
24 | } |
25 | |
26 | concept Stem extends ConceptWithChangeListeners is AutoCloseable { |
27 | transient GazelleHost host; |
28 | transient LoadedDB db; |
29 | DynModule module; |
30 | settableWithVar Rect frameRect; |
31 | bool minimized, maximized, alwaysOnTop, minimizeToTray; |
32 | |
33 | transient gettable JFrame window; |
34 | transient JButton btnMax; |
35 | transient JLabel titleComponent; |
36 | transient gettable JButton compileButton; |
37 | //transient bool autoUpdateDisabled; |
38 | transient bool started; |
39 | transient settable long startedWhen; // sys time |
40 | |
41 | *() {} |
42 | *(LoadedDB *db) { host = db.host; } |
43 | *(LoadedDB db, DynModule *module) { this(db); } |
44 | |
45 | transient Component selfVis; // how the module visualized itself |
46 | |
47 | // Not sure why we use LazyVar here instead of simplyCached |
48 | |
49 | transient LazyVar<SingleComponentPanel> scpMainWindowContents = new(-> scp()); |
50 | SingleComponentPanel scpMainWindowContents() { ret scpMainWindowContents!; } |
51 | |
52 | transient LazyVar<SingleComponentPanel> scpMainWindowWithTitle = new(-> scp()); |
53 | SingleComponentPanel scpMainWindowWithTitle() { ret scpMainWindowWithTitle!; } |
54 | |
55 | transient LazyVar<SingleComponentPanel> scpFrameIcon = new(-> scp()); |
56 | SingleComponentPanel scpFrameIcon() { ret scpFrameIcon!; } |
57 | |
58 | Rect grabFrameRect() { |
59 | ret toRect(getBounds(window)); |
60 | } |
61 | |
62 | void saveFrameRect { |
63 | frameRect(grabFrameRect()); |
64 | } |
65 | |
66 | void restoreFrameRect { |
67 | if (frameRect != null) |
68 | setBounds(window, frameRect); |
69 | } |
70 | |
71 | void setAlwaysOnTop(bool alwaysOnTop) { |
72 | this.alwaysOnTop = alwaysOnTop; |
73 | change(); |
74 | alwaysOnTop(window, alwaysOnTop); |
75 | } |
76 | |
77 | void setMinimizeToTray(bool minimizeToTray) { |
78 | cset(this, +minimizeToTray); |
79 | } |
80 | |
81 | void setWindow(JFrame window) { |
82 | this.window = window; |
83 | restoreFrameRect(); |
84 | //onWindowClosing(window, -> host.cleanExit()); |
85 | onBoundsChange(window, r saveFrameRect); |
86 | |
87 | if (host.restrictWindowsToScreenSize) |
88 | restrictWindowToScreenSize(window); |
89 | |
90 | alwaysOnTop(window, alwaysOnTop); |
91 | } |
92 | |
93 | void activateWindow { |
94 | print("activateWindow: " + window); |
95 | if (window != null) |
96 | activateFrame_v3(window); |
97 | else |
98 | startAndShow(false); |
99 | } |
100 | |
101 | bool isMain() { ret this == db.mainStem; } |
102 | |
103 | void minimize { |
104 | cset(this, minimized := true); |
105 | if (!minimizeToTray || host.trayIcon == null || !isMain()) |
106 | minimizeWindow(window); |
107 | else |
108 | hideWindow(window); |
109 | } |
110 | |
111 | void deleteMe { |
112 | temp tempPrintAlsoToSystemOut(); |
113 | print("deleteMe " + isMain()); |
114 | if (isMain()) |
115 | host.closeDatabase(db); |
116 | else |
117 | delete(); |
118 | } |
119 | |
120 | close { |
121 | disposeWindow(window); |
122 | cleanUp(module); |
123 | } |
124 | |
125 | void delete :: before { |
126 | close(); |
127 | } |
128 | |
129 | void grabWindowState { |
130 | setField(maximized := !isExtendedStateNormal(window)); |
131 | } |
132 | |
133 | void updateBtnMax { |
134 | if (!isShowing(btnMax)) ret; |
135 | grabWindowState(); |
136 | setText(btnMax, maximized ? "NORM" : "MAX"); |
137 | } |
138 | |
139 | JComponent makeWindowBorderAndTitle(JComponent contents, S title) { |
140 | //ret jCenteredSection_fontSizePlus(10, title, vis); |
141 | |
142 | var icons = jline(); |
143 | if (host.allowFrameMaximization) { |
144 | icons.add(btnMax = jbutton("", rThread { |
145 | maximizeOrRestoreFrame(window); |
146 | updateBtnMax(); |
147 | })); |
148 | onFirstShowing(btnMax, r updateBtnMax); |
149 | } |
150 | |
151 | icons.add(toolTip("Minimize this window", jbutton("MIN", r minimize))); |
152 | icons.add(toolTip("Close this window", jimageButtonScaledToWidth(16, #1103067, rThread deleteMe))); |
153 | |
154 | //setHorizontalMarginForAllButtons(icons, 4); |
155 | icons.add(new JPopDownButton(menu -> fillJPopupMenu(menu, flattenToList( |
156 | |
157 | // always on top |
158 | |
159 | jCheckBoxMenuItem/*_dyn*/("Always on top", |
160 | /*->*/ alwaysOnTop, |
161 | alwaysOnTop -> setAlwaysOnTop(alwaysOnTop)), |
162 | |
163 | // quit, restart + update |
164 | |
165 | "---", |
166 | "Exit Gazelle", rThread { host.cleanExit() }, |
167 | "Restart", rThread { host.restart() }, |
168 | "Re-open project", rThread { host.reopenDB(db) }, |
169 | "Hide project", rThread { db.hide() }, |
170 | "Update Gazelle", rThread { host.upgradeGazelle() }, |
171 | !isMain() ? null : jCheckBoxMenuItem/*_dyn*/("Auto-Update", |
172 | /*->*/ print(autoUpdate := db.g22utils.autoUpdateEnabled()), |
173 | b -> { |
174 | // save flag |
175 | db.g22utils.setAutoUpdate(b); |
176 | |
177 | // if user just turned on auto-update, do the update |
178 | if (b) thread { host.upgradeGazelle(); } |
179 | }), |
180 | |
181 | // debugging + screenshooting gazelle |
182 | |
183 | "---", |
184 | !isMain() ? null : ll( |
185 | "Screenshoot this window", rThread shootWindow, |
186 | "Dump threads", rThread { showText("User threads", renderUserThreadsWithStackTraces()); }, |
187 | "Log", !isMain() ? null : r { db.showPrintLog() }, |
188 | "Copy log to clipboard", !isMain() ? null : rThread { |
189 | copyToClipboard(printLog()); |
190 | infoBox("Copied log to clipboard"); |
191 | }, |
192 | "10 Second Profiler", rThread runTenSecondProfiler, |
193 | ), |
194 | |
195 | ccallOpt(module, "popDownItems") |
196 | )))); |
197 | |
198 | JComponent actualTitle = titleComponent = fontSizePlus(7, jCenteredLabel(title)); |
199 | installWindowDragger(actualTitle); |
200 | |
201 | if (/*isMain() &&*/ host.devMode()) |
202 | actualTitle = centerAndEastWithMargin(actualTitle, |
203 | jline( |
204 | compileButton = makeCompileButton(), |
205 | installWindowDragger(lblComputerAndUserCount()), |
206 | )); |
207 | |
208 | updateFrameIcon(); |
209 | |
210 | var spacer = gazelle_wavySpacer(color1(), color2()); |
211 | var titleBarMain = setOpaqueBackground(color1(), |
212 | jtransparent_recursive(westCenterAndEastWithMargin( |
213 | scpFrameIcon(), |
214 | actualTitle, |
215 | setOpaqueBackground(color2(), spacer))); |
216 | |
217 | installWindowDragger(spacer); |
218 | |
219 | var titleBar = setBackground(color2(), centerAndEast( |
220 | titleBarMain, |
221 | icons)); |
222 | |
223 | var border = |
224 | // createBevelBorder(); |
225 | BorderFactory.createLineBorder(color1(), host.borderSize); |
226 | |
227 | var outerPanel = withBorder(border, |
228 | northAndCenter(titleBar, contents)); |
229 | |
230 | installWindowResizeDraggerOnBorder(outerPanel); |
231 | ret markVisualizer(this, outerPanel); |
232 | } |
233 | |
234 | swappable S frameIconID() { ret host.trayIconImageID; } |
235 | swappable int frameIconHeight() { ret 24; } |
236 | |
237 | selfType frameIconID(S iconID) { |
238 | frameIconID = -> iconID; |
239 | updateFrameIcon(); |
240 | this; |
241 | } |
242 | |
243 | swappable JComponent makeFrameIcon() { |
244 | ret jImage_scaledToHeight(frameIconHeight(), frameIconID()); |
245 | } |
246 | |
247 | void updateFrameIcon { |
248 | scpFrameIcon().set(makeFrameIcon()); |
249 | } |
250 | |
251 | swappable Color color1() { ret host.color1; } |
252 | swappable Color color2() { ret host.color2; } |
253 | |
254 | JLabel lblComputerAndUserCount() { |
255 | JLabel label = jlabel(); |
256 | Runnable update = -> { |
257 | Int computerCount = host.computerCount(); |
258 | Int userCount = host.lvGazelleUserCount()!; |
259 | setText(label, |
260 | (computerCount == null ? "" : n2(computerCount)) |
261 | + appendSquareBracketed( |
262 | userCount == null ? "" : n2(userCount))); |
263 | }; |
264 | bindChangeListenerToComponent(label, host.lvComputerCount(), update); |
265 | bindChangeListenerToComponent(label, host.lvGazelleUserCount(), update); |
266 | ret label; |
267 | } |
268 | |
269 | JButton makeCompileButton() { |
270 | var gears = resizeImageIcon(imageIcon(#1103061), 16); |
271 | |
272 | var btn = jimageButtonScaledToWidth(16, #1101268, "Compile & update", |
273 | rThread { host.compile() }); |
274 | //enableComponentAccordingToVar(host.compileButtonEnabled, btn); |
275 | |
276 | componentPopupMenuItems(btn, |
277 | "Compile all", rThread { host.compileAll() }, |
278 | "Compile main library", rThread { host.compileUtilsAndRepackage() }, |
279 | "Repackage", rThread { host.repackage() }, |
280 | ); |
281 | |
282 | ret btn; |
283 | } |
284 | |
285 | JComponent wrapVis(JComponent vis) { |
286 | //ret jMinSize0(vis); |
287 | ret vis; |
288 | } |
289 | |
290 | void startAndShow(bool hidden default false) { |
291 | if (module._host != this) { |
292 | module._host = this; |
293 | setOpt_raw(module, +threadPool); // only if the field exists |
294 | setOpt_raw(module, concepts := db.concepts); |
295 | setOpt_raw(module, g22utils := db.g22utils); |
296 | copyLocalLog(module, mc()); |
297 | } |
298 | |
299 | var vis, title = unpair startAndVisualize(); |
300 | |
301 | if (!hidden) { |
302 | printWithMS("Show frame"); |
303 | |
304 | JFrame frame = makeUndecoratedFrame(title, wrapVis(vis)); |
305 | setFrameIcon(frame, host.trayIconImageID); |
306 | setWindow(frame); |
307 | |
308 | frame.addPropertyChangeListener("title", evt -> |
309 | setText(titleComponent, frame.getTitle())); |
310 | |
311 | if (minimized) minimize(); |
312 | onWindowDeiconified(frame, -> cset(this, minimized := false)); |
313 | |
314 | showWindow(window); |
315 | if (maximized && host.allowFrameMaximization) maximizeFrame(window); |
316 | } |
317 | } |
318 | |
319 | Pair<JComponent, S> startAndVisualize() { |
320 | JComponent vis = null; |
321 | { |
322 | printWithMS("Starting " + module); |
323 | try { |
324 | if (!started) { |
325 | set started; |
326 | startedWhen(sysNow()); |
327 | temp module.enter(); |
328 | module.start(); |
329 | } |
330 | |
331 | printWithMS("Visualize"); |
332 | vis = swing(-> { |
333 | temp module.enter(); |
334 | ret module.visualize(); |
335 | }); |
336 | } on fail e { |
337 | waitForFrameClose(showErrorFrame(e)); |
338 | } |
339 | } |
340 | |
341 | selfVis = vis; |
342 | |
343 | var scp1 = scpMainWindowContents(); |
344 | scp1.set(vis); |
345 | |
346 | S title = createTitleText(); |
347 | var vis2 = makeWindowBorderAndTitle(scp1, title); |
348 | |
349 | var scp2 = scpMainWindowWithTitle(); |
350 | scp2.set(vis2); |
351 | ret pair(scp2, title); |
352 | } |
353 | |
354 | S createTitleText() { |
355 | S moduleName = takeFirst_unnull(800, module.moduleName()); |
356 | |
357 | if (isMain()) |
358 | // OLD: ret host.windowTitle + appendWithColon(moduleName); |
359 | ret joinNemptiesWithVBar(moduleName, host.windowTitle); |
360 | else |
361 | ret moduleName; |
362 | } |
363 | |
364 | void revisualize { |
365 | if (window == null) ret; |
366 | |
367 | pcall { |
368 | module.unvisualize(); |
369 | } |
370 | |
371 | var vis, title = unpair startAndVisualize(); |
372 | setFrameContents(window, wrapVis(vis)); |
373 | } |
374 | |
375 | void shootWindow { |
376 | var img = renderComponentToImage(window); |
377 | copyImageToClipboard(img); |
378 | File imgFile = saveInImageDirectoryWithCounter(gazelle22_imagesSubDirName() + "/Gazelle", img); |
379 | pcallOpt(module, "addToGallery", imgFile); |
380 | } |
381 | |
382 | // Breaking change 2022 Dec 10 (is this even used?) |
383 | // old: Component vis() { ret getFrameContents(window); } |
384 | Component vis() { ret scpMainWindowContents()!; } |
385 | |
386 | S tenSecondProfilerScript = [[ |
387 | temp profiler <- new AllThreadsProfiler |
388 | infoBox "Profiling..." |
389 | profiler start |
390 | sleepSeconds 10 |
391 | showText "10 Second Profile" < profiler renderFullResults false |
392 | ]]; |
393 | |
394 | File byteCodePath() { |
395 | ret assertNotNull(getBytecodePathForClass(this)); |
396 | } |
397 | |
398 | ClassNameResolver classNameResolver() { |
399 | ret new ClassNameResolver().byteCodePath(byteCodePath()).init(); |
400 | } |
401 | |
402 | GazelleV_LeftArrowScriptParser leftArrowParser() { |
403 | new GazelleV_LeftArrowScriptParser parser; |
404 | parser.classNameResolver(classNameResolver()); |
405 | parser.allowTheWorld(mc(), utils.class); |
406 | ret parser; |
407 | } |
408 | |
409 | void runTenSecondProfiler { |
410 | leftArrow(leftArrowParser(), tenSecondProfilerScript); |
411 | } |
412 | } // end of Stem |
413 | |
414 | sclass LoadedDB is AutoCloseable, IG22LoadedDB { |
415 | GazelleHost host; |
416 | gettable Concepts concepts; |
417 | gettable new G22Utils g22utils; |
418 | Stem mainStem; |
419 | gettable bool hidden; |
420 | |
421 | *(GazelleHost *host, Concepts *concepts, bool *hidden) { |
422 | g22utils.concepts(concepts); |
423 | g22utils.masterStuff(host); |
424 | } |
425 | |
426 | S name() { |
427 | ret fileName(conceptsDir(concepts)); |
428 | } |
429 | |
430 | void start { |
431 | concepts.grabThroughSocket(false); |
432 | concepts.makeStructureData = -> { |
433 | structure_Data data = concepts.makeStructureData_base(); |
434 | data.realShortName = name -> { |
435 | S n = dropPrefixOrNull("userCode.", name); |
436 | if (n != null) |
437 | ret takeFirst(n, indexOf(n, '_')); |
438 | ret data.realShortName_base(name); |
439 | }; |
440 | data.mcDollar = mcDollar(); |
441 | ret data; |
442 | }; |
443 | var lock = concepts.fileLock(); |
444 | try { |
445 | lock.contentsForLockFile("Locked by computer " + computerID() + "\n"); |
446 | lock.lockOrFail(); |
447 | } catch print e { |
448 | if (cic(str(e), "Couldn't aquire lock")) { |
449 | if (!lock.hasExpectedContents()) { |
450 | infoBox("Overriding project lock (" + lock.actualContents() + ")"); |
451 | lock.forceLock(); |
452 | } else { |
453 | S msg = "This project is already opened in another process"; |
454 | infoBox(msg, 10.0); |
455 | sleepSeconds(1); |
456 | fail(msg); |
457 | } |
458 | } else |
459 | throw rethrow(e); |
460 | } |
461 | concepts.fileLock().deleteOnExit(); |
462 | thinAProgramsBackups(concepts.conceptsDir(), true); |
463 | |
464 | //concepts.persist(); |
465 | concepts.loadConcepts(); |
466 | |
467 | // Check that we are not loading with an old program version |
468 | var projectInfo = optimizedUniq(concepts, G22ProjectInfo); |
469 | S compilationDate = compilationDateFromClassPath(this); |
470 | print("Compilation date in project: " + projectInfo.compilationDate); |
471 | |
472 | if (nempty(projectInfo.compilationDate) |
473 | && lessThan(compilationDate, projectInfo.compilationDate)) { |
474 | messageBoxAndFail("Trying to open project with old Gazelle version - please update Gazelle first (" + compilationDate + " vs " + projectInfo.compilationDate + ")"); |
475 | } |
476 | |
477 | if (cset(projectInfo, +compilationDate) != 0) |
478 | print("Bumped compilation date in project to " + compilationDate); |
479 | |
480 | concepts.autoSaveConcepts(); |
481 | |
482 | //db_setMainConcepts(concepts); |
483 | |
484 | //print("Concepts loaded: " + map className(allConcepts(concepts)); |
485 | |
486 | var stems = stems(); |
487 | print("Stems in DB: " + l(stems)); |
488 | mainStem = first(stems); |
489 | if (mainStem == null) { |
490 | print("Starting new module"); |
491 | var module = host.makeModule(); |
492 | mainStem = registerConcept(concepts, new Stem(this, module)); |
493 | print("Stems in DB: " + l(stems())); |
494 | } |
495 | |
496 | for (stem : stems()) { |
497 | stem.db = this; |
498 | stem.host = host; |
499 | stem.startAndShow(hidden); |
500 | } |
501 | |
502 | print("Stems in DB: " + l(stems())); |
503 | } |
504 | |
505 | L<Stem> stems() { ret list Stem(concepts); } |
506 | |
507 | Q systemQ() { ret host.systemQ; } |
508 | |
509 | Stem findPrintLog() { |
510 | ret firstThat(list(concepts, Stem), |
511 | stem -> stem.module instanceof PrintLogModule); |
512 | } |
513 | |
514 | void showPrintLog { |
515 | systemQ().add(-> { |
516 | var logModule = findPrintLog(); |
517 | if (logModule != null) |
518 | logModule.activateWindow(); |
519 | else |
520 | registerConcept(concepts, new Stem(this, new PrintLogModule)).startAndShow(); |
521 | }); |
522 | } |
523 | |
524 | void hidePrintLog { |
525 | systemQ().add(-> deleteConcept(findPrintLog())); |
526 | } |
527 | |
528 | void activate { |
529 | print("Activating DB: " + conceptsFile(concepts)); |
530 | if (hidden) { |
531 | hidden = false; |
532 | host.loadedDBsChange(); |
533 | } |
534 | mainStem?.activateWindow(); |
535 | } |
536 | |
537 | void hide { |
538 | hideWindow(mainWindow()); |
539 | if (!hidden) { |
540 | hidden = true; |
541 | host.loadedDBsChange(); |
542 | } |
543 | } |
544 | |
545 | public JFrame mainWindow() { |
546 | ret mainStem?.window; |
547 | } |
548 | |
549 | close { |
550 | print("Cleaning up " + concepts); |
551 | print("Stems in DB: " + l(stems())); |
552 | |
553 | for (Concept c : allConcepts(concepts)) |
554 | cleanUp(c); |
555 | |
556 | print("Releasing DB (" + n2(list(concepts, Stem), "stem") + ")"); |
557 | dispose concepts; |
558 | |
559 | host.loadedDBs.remove(this); |
560 | host.loadedDBsChange(); |
561 | |
562 | print("Remaining DBs: " + l(host.loadedDBs)); |
563 | } |
564 | |
565 | toString { ret name(); } |
566 | |
567 | DynModule module() { |
568 | ret mainStem?.module; |
569 | } |
570 | |
571 | public G22ProjectActions projectActions() { |
572 | ret (G22ProjectActions) module(); |
573 | } |
574 | } // end of LoadedDB |
575 | |
576 | // This replaces the Stefan's OS main class. It is not persisted |
577 | // and there is only one instance. |
578 | |
579 | transient sclass GazelleHost is G22MasterStuff { |
580 | Set<LoadedDB> loadedDBs = syncLinkedHashSet(); |
581 | event loadedDBsChange; |
582 | event newClassesDefined; |
583 | |
584 | transient S repackageURL = "https://botcompany.de/jar/1033860?withX30=1&withLibs=1&noSrc=1&mainClassForManifest=Starter&repackage=1"; |
585 | |
586 | // Title of this Gazelle instance |
587 | transient S windowTitle = "Gazelle 22"; |
588 | |
589 | transient S trayIconImageID = #1103047; |
590 | transient int borderSize = 5; |
591 | transient Color color1 = awtColor("ADD8E6"); |
592 | transient Color color2 = awtColor("EEEEEE"); |
593 | transient Color color3 = awtColor("A3C0AA"); |
594 | transient IF0<DynModule> moduleMaker; |
595 | transient ThreadPool threadPool; |
596 | |
597 | transient File dbMotherDir; |
598 | |
599 | transient TrayIcon trayIcon; |
600 | transient TrayIconLastPosition trayIconLastPosition; |
601 | |
602 | transient S[] args; |
603 | transient Set<S> argsSet; |
604 | transient bool incrementalDownloads = true; |
605 | transient volatile bool shuttingDown; |
606 | |
607 | transient bool restrictWindowsToScreenSize /*= true*/; |
608 | |
609 | // This caused problems, disabled for now |
610 | transient bool allowFrameMaximization; |
611 | |
612 | transient G22Utils g22utils; |
613 | transient StefansOS_ConnectToServer serverConnector; |
614 | transient ComputerCountListener computerCount; |
615 | transient GazellePresenceListener gazellePresenceListener; |
616 | |
617 | transient gettable Q systemQ = startQ("System Q"); |
618 | |
619 | transient AutoCloseable pimpedPopups; |
620 | |
621 | // Let's use one of these centrally because it blocks a thread |
622 | // and does next to nothing most of the time. |
623 | gettable new RunnablesReferenceQueue runnablesReferenceQueue; |
624 | |
625 | transient gettable ILASClassLoader lasClassLoader = |
626 | //new LASClassLoader(getClass()); |
627 | new LASMultiClassLoader(mc()).runnablesReferenceQueue(assertNotNull(runnablesReferenceQueue)); |
628 | |
629 | //transient BoolVarWithNotify compileButtonEnabled = new(true); |
630 | |
631 | gettable EphemeralObjectIDs ephemeralObjectIDs = new() |
632 | .runnablesReferenceQueue(assertNotNull(runnablesReferenceQueue)); |
633 | |
634 | transient bool autoExitDisabled; |
635 | |
636 | settable transient volatile bool booted; |
637 | |
638 | event fillingTrayIconMenu(PopupMenu menu); |
639 | |
640 | // add fields here |
641 | |
642 | *(ThreadPool *threadPool, IF0<DynModule> *moduleMaker) { |
643 | g22utils = new G22Utils().masterStuff(this); |
644 | |
645 | onLoadedDBsChange(-> { |
646 | if (shuttingDown) ret; |
647 | var dbs = getLoadedDBs(); |
648 | if (nempty(dbs)) |
649 | g22utils.setOpenDBs(dbs); |
650 | updateTrayIconMenu(); |
651 | }); |
652 | } |
653 | |
654 | // Reminder: We are in GazelleHost |
655 | |
656 | <A> A inSystemQ(IF0<A> f) { |
657 | ret evalInQ(systemQ(), f); |
658 | } |
659 | |
660 | JFrame mainWindow() { |
661 | ret firstNonNull(loadedDBs(), db -> db.mainWindow()); |
662 | } |
663 | |
664 | void trayIconLeftClick { |
665 | first(loadedDBs()).activate(); |
666 | } |
667 | |
668 | void makeTrayIcon { |
669 | pcall-short { |
670 | trayIcon_imageAutoSize = false; |
671 | trayIcon = installTrayIcon(trayIconImageID, windowTitle, |
672 | r trayIconLeftClick, |
673 | /*"Show " + windowTitle, r trayIconLeftClick, |
674 | "Exit " + windowTitle, r cleanExit*/ |
675 | ); |
676 | |
677 | updateTrayIconMenu(); |
678 | trayIconLastPosition = new TrayIconLastPosition(trayIcon); |
679 | } |
680 | } |
681 | |
682 | void updateTrayIconMenu { rstUpdateTrayIconMenu.trigger(); } |
683 | |
684 | transient ReliableSingleThread rstUpdateTrayIconMenu = rstWithPostDelay(1000, l0 updateTrayIconMenuImpl); |
685 | |
686 | void updateTrayIconMenuImpl { |
687 | if (trayIcon == null) ret; |
688 | |
689 | new PopupMenu menu; |
690 | var projects = openProjects(); |
691 | printVars("updateTrayIconMenuImpl", +projects); |
692 | for (IG22LoadedDB otherDB : projects) { |
693 | S name = otherDB.g22utils().projectName(); |
694 | S status = otherDB.hidden() ? "Hidden" : ""; |
695 | addMenuItem(menu, name + appendSquareBracketed(status), rThread { otherDB.activate() }); |
696 | } |
697 | |
698 | fillingTrayIconMenu(menu); |
699 | |
700 | addMenuItem(menu, "Exit " + windowTitle, rThread cleanExit); |
701 | trayIcon.setPopupMenu(menu); |
702 | } |
703 | |
704 | void run(S[] args) { |
705 | try { |
706 | this.args = args; |
707 | run2(args); |
708 | } catch print e { |
709 | //messageBox(e); |
710 | hideTrayIcon(trayIcon); |
711 | onWindowClosing(-> systemExit(1), getWindow( |
712 | showText_fast_noWrap("Gazelle Error", renderStackTrace(e)))); |
713 | } |
714 | } |
715 | |
716 | void run2(S[] args) { |
717 | argsSet = asSet(args); |
718 | |
719 | S s = first(args); |
720 | bool isDirectory = isDirectory(s); |
721 | if (isDirectory) |
722 | dbMotherDir = canonicalFile(s); |
723 | printVars(args := asList(args), +s, +isDirectory, +dbMotherDir); |
724 | |
725 | print("Using mother directory: " + databasesMotherDir()); |
726 | |
727 | windowTitle = or2(loadTextFile(newFile(dbMotherDir, "Gazelle Name")), windowTitle); |
728 | |
729 | if (!argsSet.contains("noflatlaf")) |
730 | com.formdev.flatlaf.FlatLightLaf.setup(); |
731 | |
732 | if (contains(args, "profile")) |
733 | profileToConsole(() -> actualMain(args)); |
734 | else |
735 | actualMain(args); |
736 | |
737 | if (argsSet.contains("brexit")) System.exit(0); |
738 | } |
739 | |
740 | void actualMain(S... args) { |
741 | vm_generalMap_put(stefansOS := this); |
742 | |
743 | System.out.println(hmsWithColonsAndMS() + ": Init"); |
744 | //x30.coreInit(); |
745 | |
746 | // Ready to roll |
747 | |
748 | printJavaVersion(); |
749 | |
750 | //tempAddGlobalCtrlKeyListener(b -> print("Ctrl key: " + b)); |
751 | |
752 | if (contains(argsSet, "profile")) |
753 | doEvery(1.0, r printRunningThreads); |
754 | |
755 | if (containsOneOf(argsSet, "upgrade", "update")) |
756 | ret with upgradeGazelle(); |
757 | |
758 | makeTrayIcon(); |
759 | |
760 | initAutoUpdate(); |
761 | |
762 | if (devMode()) { |
763 | print("Pimping popup menus"); |
764 | PopupMenuMaker.fix(); |
765 | pimpedPopups = tempGlobalPopupMenu((c, menu) -> { |
766 | printVars("pimpedPopups", c := className(c)); |
767 | |
768 | // Skip linux tray icon popups |
769 | if (contains(className(c), "XTrayIcon")) ret; |
770 | |
771 | // This is for popup menus displayed via setComponentPopupMenu |
772 | // which are re-used instances |
773 | if (any(getMenuItems(menu), menuItem |
774 | -> cicOneOf(menuItem.getText(), "Component: ", "src: "))) ret; |
775 | |
776 | O src = componentMetaSrc(c); |
777 | menu.add(commaCombine( |
778 | classNameAndEphString(c), |
779 | src == null ?: "src: " + classNameAndEphString(src))); |
780 | }); |
781 | } |
782 | |
783 | // Start DB |
784 | |
785 | LS dbNames = g22utils.dbsToOpen(); |
786 | for (S dbName : dbNames) { |
787 | S name2 = dropPrefix("*", dbName); |
788 | openDB(name2, !eq(dbName, name2)); |
789 | } |
790 | |
791 | booted(true); |
792 | |
793 | exitIfAllDBsClosed(); |
794 | |
795 | printWithMS("Dudadoneski"); |
796 | } |
797 | |
798 | public bool projectExists(S dbName) { |
799 | File conceptsFile = newFile(newFile(databasesMotherDir(), dbName), conceptsFileName()); |
800 | ret isFile(conceptsFile); |
801 | } |
802 | |
803 | LoadedDB openDB(S dbName, bool hidden default false) { |
804 | File conceptsFile = newFile(newFile(databasesMotherDir(), dbName), conceptsFileName()); |
805 | ret openDB(conceptsFile, hidden); |
806 | } |
807 | |
808 | public IG22LoadedDB getLoadedDB(Concepts concepts) { |
809 | ret firstThat(cloneList(loadedDBs), db -> db.concepts == concepts); |
810 | } |
811 | |
812 | IG22LoadedDB getLoadedDBForConceptDir(File dir) { |
813 | ret findLoadedDBForConceptsDir(dir); |
814 | } |
815 | |
816 | LoadedDB findLoadedDBForConceptsDir(File conceptsDir) { |
817 | ret firstThat(cloneList(loadedDBs), db -> sameFile( |
818 | conceptsDir(db.concepts), conceptsDir)); |
819 | } |
820 | |
821 | LoadedDB findLoadedDBForConceptsFile(File conceptsFile) { |
822 | ret firstThat(cloneList(loadedDBs), db -> sameFile( |
823 | conceptsFile(db.concepts), conceptsFile)); |
824 | } |
825 | |
826 | public IG22LoadedDB reopenDB(IG22LoadedDB db) { |
827 | ret reopenDB((LoadedDB) db); |
828 | } |
829 | |
830 | LoadedDB reopenDB(LoadedDB db) { |
831 | File conceptsFile = db.concepts.conceptsFile(); |
832 | try { |
833 | autoExitDisabled = true; |
834 | closeDatabase(db); |
835 | ret openDB(conceptsFile); |
836 | } finally { |
837 | autoExitDisabled = false; |
838 | } |
839 | } |
840 | |
841 | LoadedDB openDB(File conceptsFile, bool hidden default false) { |
842 | // First try to find a loaded concept without going through system queue |
843 | var loadedDB_quick = findLoadedDBForConceptsFile(conceptsFile); |
844 | if (loadedDB_quick != null) { |
845 | if (!hidden) |
846 | loadedDB_quick.activate(); |
847 | ret loadedDB_quick; |
848 | } |
849 | |
850 | ret inSystemQ(-> { |
851 | var loadedDB = findLoadedDBForConceptsFile(conceptsFile); |
852 | if (loadedDB != null) { |
853 | if (!hidden) |
854 | loadedDB.activate(); |
855 | ret loadedDB; |
856 | } |
857 | |
858 | temp tempInfoBoxNoHide("Opening Gazelle project " + quote(fileName(dirOfFile(conceptsFile)))); |
859 | |
860 | // profile project opening |
861 | //temp tempProfileSingleThreadToConsole(); |
862 | |
863 | print("Loading new DB: " + f2s(conceptsFile)); |
864 | g22utils.addToRecentDBNames(parentName(conceptsFile), hidden); |
865 | Concepts concepts = newConceptsWithClassFinder(conceptsFile, makeClassFinder()); |
866 | |
867 | concepts.onSaveError(l1 infoBox); |
868 | |
869 | // to clean-up resources in project variables |
870 | concepts.closeConceptsOnExit(true); |
871 | |
872 | var db = new LoadedDB(this, concepts, hidden); |
873 | loadedDBs.add(db); |
874 | try { |
875 | db.start(); |
876 | loadedDBsChange(); |
877 | } catch print e { |
878 | loadedDBs.remove(db); |
879 | _close(db); |
880 | } |
881 | ret db; |
882 | }); |
883 | } |
884 | |
885 | public Cl<File> openConceptDirs() { |
886 | ret cloneMap(loadedDBs, _db -> conceptsDir(_db.concepts)); |
887 | } |
888 | |
889 | O resolveModule(O moduleOrStem) { |
890 | ret moduleOrStem cast Stem ? moduleOrStem.module : moduleOrStem; |
891 | } |
892 | |
893 | void cleanExit { |
894 | set shuttingDown; |
895 | print("Closing " + n2(loadedDBs, "database")); |
896 | closeAndClear(loadedDBs); |
897 | System.exit(0); |
898 | } |
899 | |
900 | File myJar() { ret getBytecodePathForClass(this); } |
901 | |
902 | AutoCloseable tempDisableCompileButtons() { |
903 | ret tempDisableButtons(compileButtons()); |
904 | } |
905 | |
906 | swappable void upgradeGazelleForeplay { |
907 | infoBox("Updating Gazelle in 10..."); |
908 | sleepSeconds(10); |
909 | upgradeGazelle(); |
910 | } |
911 | |
912 | void upgradeGazelle { |
913 | try { |
914 | temp tempPrintAlsoToSystemOut(); |
915 | |
916 | // Show an infoBox to make sure the code for infoBox is loaded |
917 | temp tempInfoBox("Updating Gazelle..."); |
918 | |
919 | //temp tempSetBoolVar(compileButtonEnabled, false); |
920 | temp tempDisableCompileButtons(); |
921 | //for (f : allJFrames()) setFrameTitle(f, "Updating..."); |
922 | |
923 | File myJar = myJar(); |
924 | print(+myJar); |
925 | |
926 | S existingFilesComp = !incrementalDownloads ? null |
927 | : existingFilesInfo(); |
928 | |
929 | S date = ymdMinusHMS(); |
930 | File f = javaxCodeDir("Downloaded Updates/" + "gazelle-" + date + ".jar"); |
931 | temp progress = tempProgressBar_addToWindowIfPossible(mainWindow(), "Updating Gazelle"); |
932 | |
933 | progress.setText("Downloading Update..."); |
934 | time "Downloading zip" { |
935 | postBinaryPageToFile(downloadURL(), f, +existingFilesComp); |
936 | } |
937 | printFileInfo(f); |
938 | if (!isNonEmptySingleZip_byMagicHeader(f)) |
939 | ret with infoBox("Bad file downloaded... :("); |
940 | |
941 | bool replaced; |
942 | if (isFile(myJar)) { |
943 | // load all my classes to be sure (TODO: could do this in parallel to downloading) |
944 | //print("Loaded " + nClasses(loadAllClassesInByteCodePath(myJar))); |
945 | |
946 | progress.setText("Replacing with new version: " + renderFileInfo(myJar)); |
947 | File myJarSaved = fileInSubDir("old-code", |
948 | appendToBaseName(myJar, ".bak." + date)); |
949 | File myJarNew = appendToFileName(myJar, ".new"); |
950 | |
951 | Set<S> toKeep = asSet(tlft(loadTextFileFromZip(f, "to-keep"))); |
952 | if (nempty(toKeep)) { |
953 | int toUpdate = countFilesInZip(f)-1; |
954 | //progress.setText("Keeping " + nFiles(toKeep)); |
955 | progress.setText("Updating " + nFiles(toUpdate)); |
956 | |
957 | // Do we have anything to updatE? |
958 | |
959 | int have = countFilesInZip(myJar); |
960 | if (l(toKeep) == have && countFilesInZip(f) == 1) { |
961 | infoBox("Nothing to update - Gazelle is up to date!"); |
962 | deleteFile(myJarNew); |
963 | deleteFile(myJarSaved); |
964 | ret; |
965 | } |
966 | |
967 | temp Zip4j_MergeZipFilesWithFilter merger = new(myJarNew); |
968 | merger.addZipFile(myJar, name -> contains(toKeep, name)); |
969 | merger.addZipFile(f, name -> !eq(name, "to-keep")); |
970 | merger.finish(); |
971 | } else |
972 | myJarNew = f; |
973 | |
974 | if (isWindows()) { |
975 | copyFileVerbose(myJar, myJarSaved); |
976 | copyFileVerbose(myJarNew, myJar); |
977 | deleteFileVerbose_assertSuccess(myJarNew); |
978 | } else { |
979 | renameFileVerbose_assertSuccess(myJar, myJarSaved); |
980 | renameFileVerbose_assertSuccess(myJarNew, myJar); |
981 | } |
982 | |
983 | printFileInfo(myJar); |
984 | set replaced; |
985 | } |
986 | |
987 | progress.setText("Done"); |
988 | |
989 | if (replaced) |
990 | infoBox("Installed update, replaced " + f2s(myJar) + " - now starting"); |
991 | else |
992 | infoBox("Updated but could not replace originally downloaded jar"); |
993 | |
994 | restart(replaced ? myJar : f); |
995 | } on fail e { printStackTrace(e); } |
996 | } |
997 | |
998 | void restart(File jar default myJar()) { |
999 | temp tempPrintAlsoToSystemOut(); |
1000 | |
1001 | S javaCmd = or2(currentProcessCommand(), "java"); |
1002 | S cmd = platformQuoteArgs(flattenToList( |
1003 | javaCmd, "-jar", jar, args)); |
1004 | print(cmd); |
1005 | nohup(cmd); |
1006 | cleanExit(); |
1007 | } |
1008 | |
1009 | DynModule makeModule() { |
1010 | ret callF(moduleMaker); |
1011 | } |
1012 | |
1013 | void reloadModuleInBackground(Stem mod) { |
1014 | restart(); |
1015 | } |
1016 | |
1017 | void initAutoUpdate { |
1018 | print("Making server connection for updates."); |
1019 | serverConnector = snippetUpdateConnector(/*verbose := true*/); |
1020 | //serverConnector.verbose = true; |
1021 | |
1022 | vmBus_onMessage snippetUpdate(voidfunc(L l) { |
1023 | S uri = getString(l, 1); |
1024 | new Matches m; |
1025 | if (swic(uri, "/transpileOK/", m)) { |
1026 | if (sameSnippetID(programID(), firstIntAsString(m.rest()))) { |
1027 | if (g22utils.autoUpdateEnabled() /*&& autoUpdateDisabled*/) { |
1028 | upgradeGazelleForeplay(); |
1029 | } |
1030 | //infoBox("Gazelle can be updated!"); |
1031 | } |
1032 | } else if (swic(uri, "/edit/", m)) { |
1033 | S snippetID = fsI(firstIntAsString(m.rest()); |
1034 | if (contains(stdFunctionListSnippetIDs(), snippetID)) |
1035 | stdFunctions_clearCache(); |
1036 | if (contains(standardClassesSnippetIDs(), snippetID)) |
1037 | standardClassesMap_clearCache(); |
1038 | } |
1039 | }); |
1040 | |
1041 | pcall { |
1042 | computerCount = new ComputerCountListener(serverConnector); |
1043 | computerCount.init(); |
1044 | } |
1045 | |
1046 | pcall { |
1047 | gazellePresenceListener = GazellePresenceListener(serverConnector); |
1048 | gazellePresenceListener.init(); |
1049 | } |
1050 | } |
1051 | |
1052 | S downloadURL() { |
1053 | ret "https://botcompany.de/jar/" |
1054 | + psI(programID()) + "?withLibs=1&withX30=1" |
1055 | + "&mainClassForManifest=Starter" |
1056 | + (!downloadJarWithSrc() ? "&noSrc=1" : "") |
1057 | + (downloadUncompressedJAR() ? "&uncompressed=1" : ""); |
1058 | } |
1059 | |
1060 | bool downloadJarWithSrc() { true; } |
1061 | bool downloadUncompressedJAR() { false; } |
1062 | |
1063 | // called by DynModule |
1064 | void revisualizeModule(Stem stem) { |
1065 | stem.revisualize(); |
1066 | } |
1067 | |
1068 | Int computerCount() { ret computerCount == null ? (Int) 0 : computerCount!; } |
1069 | LiveValue<Int> lvComputerCount() { ret computerCount?.liveValue(); } |
1070 | |
1071 | public IGetterWithNotify<Int> lvGazelleUserCount() { ret gazellePresenceListener?.userCount; } |
1072 | |
1073 | L<JButton> compileButtons() { ret mapNonNulls(allStems(), s -> s.compileButton()); } |
1074 | |
1075 | L<Stem> allStems() { |
1076 | ret concatLists(cloneMap(loadedDBs, db -> list(db.concepts, Stem))); |
1077 | } |
1078 | |
1079 | // compile main program |
1080 | void compile { |
1081 | compile(programID, null); |
1082 | } |
1083 | |
1084 | void resetNeedLatest { |
1085 | editSnippet(#1035306, "need latest ."); |
1086 | } |
1087 | |
1088 | void compileAll { |
1089 | resetNeedLatest(); |
1090 | compile(utilsSnippetID(), l0 compile); |
1091 | } |
1092 | |
1093 | void compileUtilsAndRepackage { |
1094 | compile(utilsSnippetID(), l0 repackage); |
1095 | } |
1096 | |
1097 | void compile(S snippetID, Runnable onSuccess) { |
1098 | print("Compiling " + snippetID); |
1099 | |
1100 | var gears = resizeImageIcon(imageIcon(#1103061), 16); |
1101 | |
1102 | temp tempDisableCompileButtons(); |
1103 | temp tempSetButtonImages(compileButtons(), gears); |
1104 | |
1105 | thread { existingFilesInfo(); } // precompute |
1106 | |
1107 | var tr = TranspileWithErrorWindow().snippetID(snippetID); |
1108 | /*tr.transpileAction(-> |
1109 | new TranspileForServer_v2(snippetID).run());*/ |
1110 | tr.transpileAction(-> { |
1111 | var p = transpileOnNewServer(snippetID, "medium"); |
1112 | if (!p.a) fail(p.b); |
1113 | }); |
1114 | tr.onSuccess(onSuccess); |
1115 | tr.run(); |
1116 | |
1117 | print("Done transpilation of " + snippetID); |
1118 | } |
1119 | |
1120 | void repackage { |
1121 | File f = javaxCachesDir("Gazelle-22/repackaged.jar"); |
1122 | deleteFile(f); |
1123 | loadBinaryPageToFile(appendParamsToURL(repackageURL, uniq := randomID()), f); |
1124 | if (isJAR(f)) |
1125 | infoBox("Repackaged."); |
1126 | else |
1127 | messageBox("Oh shit"); |
1128 | } |
1129 | |
1130 | public void closeDatabase(LoadedDB db) { |
1131 | db.close(); |
1132 | exitIfAllDBsClosed(); |
1133 | } |
1134 | |
1135 | void exitIfAllDBsClosed { |
1136 | if (!autoExitDisabled && empty(loadedDBs)) |
1137 | cleanExit(); |
1138 | } |
1139 | |
1140 | Cl<? extends IG22LoadedDB> getLoadedDBs aka loadedDBs() { |
1141 | ret cloneList(loadedDBs); |
1142 | } |
1143 | |
1144 | public void closeDatabase(File conceptsDir) { |
1145 | for (db : cloneList(loadedDBs)) |
1146 | if (sameFile(conceptsDir, conceptsDir(db.concepts))) |
1147 | db.close(); |
1148 | } |
1149 | |
1150 | public void switchToDatabase(File conceptsDir) { |
1151 | if (conceptsDir == null) ret; |
1152 | // open/activate the desired database first |
1153 | var db = openDatabase(conceptsDir); |
1154 | |
1155 | if (db == null) ret; // probably impossible but whatever |
1156 | |
1157 | closeAllDBsExcept(db); |
1158 | } |
1159 | |
1160 | void closeAllDBsExcept(LoadedDB db) { |
1161 | closeAll(listMinus(loadedDBs, db)); |
1162 | } |
1163 | |
1164 | public LoadedDB openDatabase(File conceptsDir, bool hidden default false) { |
1165 | temp tempPrintAlsoToSystemOut(); |
1166 | |
1167 | File conceptsFile = newFile(conceptsDir, conceptsFileName()); |
1168 | if (!fileExists(conceptsFile)) |
1169 | fail(infoBox("Database not found: " + conceptsFile)); |
1170 | |
1171 | var db = openDB(conceptsFile, hidden); |
1172 | |
1173 | print("Database opening complete"); |
1174 | ret db; |
1175 | } |
1176 | |
1177 | Map<S, Class> classMigrations() { |
1178 | ret litmap( |
1179 | G22RecognizerScript := G22Analyzer, |
1180 | GalleryImage := G22GalleryImage); |
1181 | } |
1182 | |
1183 | public IF1<S, Class> makeClassFinder() { |
1184 | IF1<S, Class> defaultClassFinder = toIF1(_defaultClassFinder()); |
1185 | var migrations = classMigrations(); |
1186 | IF1<S, Class> classFinder = name -> { |
1187 | // class migration |
1188 | S shortName = dropPrefixOrNull("main$", name); |
1189 | if (shortName != null) { |
1190 | Class c = migrations.get(shortName); |
1191 | if (c != null) { |
1192 | S newName = className(c); |
1193 | print("Migrating legacy class " + name + " to " + newName); |
1194 | name = newName; |
1195 | } |
1196 | } |
1197 | |
1198 | ret defaultClassFinder.get(name); |
1199 | }; |
1200 | |
1201 | // Test classFinder sanity |
1202 | |
1203 | assertEquals(callF(classFinder, "main$Stem"), Stem.class); |
1204 | |
1205 | ret classFinder; |
1206 | } |
1207 | |
1208 | transient cached S existingFilesInfo() { |
1209 | L<Map> fingerprints = zipFileToJSONFingerprint_md5(myJar()); |
1210 | S s = base64encode(gzipString(jsonEncode(fingerprints))); |
1211 | print(existingFilesInfo := nBytes(l(s))); |
1212 | ret s; |
1213 | } |
1214 | |
1215 | bool devMode() { ret g22_devMode(); } |
1216 | |
1217 | // remember ephemeral object, return script code to get it back |
1218 | S ephString(O o) { |
1219 | ret o == null ? "" : "eph " + ephemeralObjectIDs.remember(o); |
1220 | } |
1221 | |
1222 | S classNameAndEphString(O o) { |
1223 | ret o == null ?: spaceCombine(ephString(o), className(o)); |
1224 | } |
1225 | |
1226 | public File databasesMotherDir() { |
1227 | if (dbMotherDir == null) |
1228 | dbMotherDir = javaxDataDir("Gazelle-22"); |
1229 | ret dbMotherDir; |
1230 | } |
1231 | |
1232 | public bool isLoading() { |
1233 | ret !booted; |
1234 | } |
1235 | } // end of GazelleHost [G22MasterStuff] |
Began life as a copy of #1033540
download show line numbers debug dex old transpilations
Travelled to 6 computer(s): bhatertpkbcr, ekrmjmnbrukm, iveijnkanddl, mowyntqkapby, mqqgnosmbjvj, wnsclhtenguj
No comments. add comment
Snippet ID: | #1033891 |
Snippet name: | GazelleHost + Stem [dev] |
Eternal ID of this version: | #1033891/372 |
Text MD5: | cd46f38f1954d982fea5b4f39143ee3f |
Author: | stefan |
Category: | javax / gazelle 22 |
Type: | JavaX (incomplete) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2024-08-27 15:04:21 |
Source code size: | 36520 bytes / 1235 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 877 / 3773 |
Version history: | 371 change(s) |
Referenced in: | [show references] |