Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

371 improve initialization #427

Merged
merged 13 commits into from
Jun 17, 2024
17 changes: 3 additions & 14 deletions client/client.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
import omit from 'lodash/omit'
import * as settings from './settings'
import * as types from './types'

export class Client {
serverUrl?: string

constructor(props: { serverUrl?: string } = {}) {
this.serverUrl = props.serverUrl
}

async readServerUrl() {
return (
this.serverUrl ||
// @ts-ignore
(await window?.opendataeditor?.readServerUrl()) ||
settings.SERVER_URL
)
constructor() {
this.serverUrl = 'http://localhost:4040'
}

async request(
path: string,
props: { [key: string]: any; file?: File; isBytes?: boolean } = {}
) {
const serverUrl = await this.readServerUrl()
return makeRequest(serverUrl + path, props)
return makeRequest(this.serverUrl + path, props)
}

// Article
Expand Down
3 changes: 1 addition & 2 deletions client/components/Application/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import DeleteFolderDialog from './Dialogs/DeleteFolder'
import IndexFilesDialog from './Dialogs/IndexFiles'
import MoveFileDialog from './Dialogs/MoveFile'
import MoveFolderDialog from './Dialogs/MoveFolder'
import StartDialog from './Dialogs/Start'
import { useStore } from './store'

export default function Dialog() {
const dialog = useStore((state) => state.dialog)
if (!dialog) return null
// @ts-ignore
const Dialog = DIALOGS[dialog]
return <Dialog />
}
Expand All @@ -34,5 +34,4 @@ const DIALOGS = {
indexFiles: IndexFilesDialog,
moveFile: MoveFileDialog,
moveFolder: MoveFolderDialog,
start: StartDialog,
}
20 changes: 0 additions & 20 deletions client/components/Application/Dialogs/Start.tsx

This file was deleted.

6 changes: 2 additions & 4 deletions client/components/Application/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ export function makeStore(props: ApplicationProps) {
// Events

onStart: async () => {
const { client, loadConfig, loadFiles, updateState } = get()
const { client, loadConfig, loadFiles } = get()
// @ts-ignore
const sendFatalError = window?.opendataeditor?.sendFatalError
updateState({ dialog: 'start' })
let ready = false
let attempt = 0
const maxAttempts = sendFatalError ? 300 : 3
Expand All @@ -98,14 +97,13 @@ export function makeStore(props: ApplicationProps) {
} catch (error) {
attempt += 1
if (attempt >= maxAttempts) {
const serverUrl = await client.readServerUrl()
const serverUrl = client.serverUrl
const message = `Client cannot connect to server on "${serverUrl}"`
sendFatalError ? sendFatalError(message) : alert(message)
}
await delay(delaySeconds * 1000)
}
}
updateState({ dialog: undefined })
},
onFileCreate: async (paths) => {
const { loadFiles, selectFile } = get()
Expand Down
43 changes: 0 additions & 43 deletions client/components/Parts/Dialogs/Wait.tsx

This file was deleted.

71 changes: 71 additions & 0 deletions client/loading.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Open Data Editor</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<script>
window?.opendataeditor.ensureLogs((message) => {
if (message == "python") {
var element = document.getElementById("python")
var loader = document.getElementById("loader")
element.classList.toggle('pending')
element.removeChild(loader)
var next = document.getElementById("requirements")
next.appendChild(loader)
}
if (message == "requirements") {
var element = document.getElementById("requirements")
var loader = document.getElementById("loader")
element.classList.toggle('pending')
element.removeChild(loader)
var next = document.getElementById("server")
next.appendChild(loader)
}
if (message == "server") {
var element = document.getElementById("server")
var loader = document.getElementById("loader")
element.classList.toggle('pending')
element.removeChild(loader)
}
})
</script>
<style>
li {
color: black;
}
.pending {
color: grey;
}
.loader {
border: 5px solid grey;
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<section style="display:grid; place-items: center;">
<div>
<h1 style="text-align: center;">Open Data Editor is loading</h1>
<p style="text-align: center;">The first time launching Open Data Editor needs to ensure requirements are installed.</p>
<ul id="logs">
<li class="pending" id="python">Ensure python is installed <span id="loader" class="loader"></span></li>
<li class="pending" id="requirements">Ensure python dependencies are installed</li>
<li class="pending" id="server">Ensure server is running</li>
</ul>
</div>
</section>
</body>
</html>
5 changes: 1 addition & 4 deletions desktop/bridge.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { ipcMain, dialog, app } from 'electron'
import log from 'electron-log'

export function createBridge({ serverPort }: { serverPort: number }) {
ipcMain.handle('readServerUrl', async () => {
return `http://localhost:${serverPort}`
})
export function createBridge() {
ipcMain.handle('sendFatalError', async (_ev, message: string) => {
log.error(message)
await dialog.showMessageBox({
Expand Down
20 changes: 3 additions & 17 deletions desktop/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
import { app, dialog, BrowserWindow } from 'electron'
import { electronApp, optimizer } from '@electron-toolkit/utils'
import { electronApp, optimizer } from '@electron-toolkit/utils'
import { createWindow } from './window'
import { createBridge } from './bridge'
import { is } from '@electron-toolkit/utils'
import { join } from 'path'
import log from 'electron-log'
import * as system from './system'
import * as server from './server'
import * as python from './python'
import * as settings from './settings'
import * as resources from './resources'

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(async () => {
log.info('# Start application')
electronApp.setAppUserModelId(settings.APP_USER_MODEL_ID)
const serverPort = await system.findPort()

log.info('## Start client')
createBridge({ serverPort })
createWindow()

if (!is.dev) {
log.info('## Start server')
await resources.ensureRunner()
await python.ensurePython()
await python.ensureLibraries()
await server.runServer({ serverPort })
}
createBridge()
await createWindow()
})

// Default open or close DevTools by F12 in development
Expand Down
2 changes: 1 addition & 1 deletion desktop/preload/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('opendataeditor', {
readServerUrl: () => ipcRenderer.invoke('readServerUrl'),
sendFatalError: (message: string) => ipcRenderer.invoke('sendFatalError', message),
openDirectoryDialog: () => ipcRenderer.invoke('openDirectoryDialog'),
ensureLogs: (callback: any) => ipcRenderer.on('ensureLogs', (_event, message: string) => callback(message))
})
18 changes: 10 additions & 8 deletions desktop/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ import log from 'electron-log'
import toml from 'toml'
import * as system from './system'

export async function ensurePython() {
log.info('[ensurePython]', { path: settings.APP_PYTHON })
export async function ensurePythonVirtualEnvironment() {
// When running, ODE will ensure that a virtual environment for dependencies exists
log.info('[ensurePythonVirtualEnvironment]', { path: settings.APP_PYTHON_VENV })

let message = 'existed'
if (!fs.existsSync(settings.APP_PYTHON)) {
await system.execFile(settings.PYTHON_SOURCE, ['-m', 'venv', settings.APP_PYTHON])
if (!fs.existsSync(settings.APP_PYTHON_VENV)) {
await system.execFile(settings.PYTHON_SOURCE, ['-m', 'venv', settings.APP_PYTHON_VENV])
message = 'created'
}

log.info('[ensurePython]', { message })
log.info('[ensurePythonVirtualEnvironment]', { message })
}

export async function ensureLibraries() {
log.info('[ensureLibraries]')
export async function ensurePythonRequirements() {
// When running, ODE will ensure that all python dependencies are installed.
log.info('[ensurePythonRequirements]')

const required = await readRequiredLibraries()
const installed = await readInstalledLibraries()
Expand All @@ -35,7 +37,7 @@ export async function ensureLibraries() {
...missing,
])

log.info('[ensureLibraries]', { missing })
log.info('[ensurePythonRequirements]', { missing })
}

export async function readRequiredLibraries() {
Expand Down
14 changes: 8 additions & 6 deletions desktop/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ import fsp from 'fs/promises'
import * as settings from './settings'
import log from 'electron-log'

export async function ensureRunner() {
log.info('[ensureRunner]', { path: settings.APP_RUNNER })
export async function ensurePython() {
// ODE builds a Python 3.10 distribution and ships it with the app.
// It is generated when running make build
log.info('[ensurePython]', { path: settings.APP_PYTHON })

let message = 'existed'
if (!fs.existsSync(settings.APP_RUNNER)) {
await fsp.mkdir(settings.APP_RUNNER, { recursive: true })
await fsp.cp(settings.DIST_RUNNER, settings.APP_RUNNER, {
if (!fs.existsSync(settings.APP_PYTHON)) {
await fsp.mkdir(settings.APP_PYTHON, { recursive: true })
await fsp.cp(settings.DIST_PYTHON, settings.APP_PYTHON, {
recursive: true,
verbatimSymlinks: true,
})
message = 'created'
}

log.info('[ensureRunner]', { message })
log.info('[ensurePython]', { message })
}
8 changes: 4 additions & 4 deletions desktop/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { spawnFile } from './system'
import * as settings from './settings'
import log from 'electron-log'

export async function runServer({ serverPort }: { serverPort: number }) {
log.info('[runServer]', { serverPort })
export async function runServer() {
log.info(`[Run FastAPI server on port 4040`)

// Start server
// Start production server
const proc = spawnFile(
settings.PYTHON_TARGET,
['-m', 'server', settings.APP_TMP, '--port', serverPort.toString()],
['-m', 'server', settings.APP_TMP, '--port', `4040`],
process.resourcesPath
)

Expand Down
13 changes: 5 additions & 8 deletions desktop/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,17 @@ import { join } from 'path'
export const WIN = process.platform === 'win32'
export const HOME = os.homedir()

export const PORT_DEV = 4040
export const PORT_PROD = 4444

export const DIST = process.resourcesPath
export const DIST_RUNNER = join(DIST, 'runner')
export const DIST_PYTHON = join(DIST, 'runner')
export const DIST_SERVER = join(DIST, 'server')

export const APP_NAME = 'opendataeditor'
export const APP_USER_MODEL_ID = 'org.opendataeditor'
export const APP_HOME = join(HOME, `.${APP_NAME}`)
export const APP_RUNNER = join(APP_HOME, 'runner')
export const APP_PYTHON = join(APP_HOME, 'python')
export const APP_PYTHON = join(APP_HOME, 'runner')
export const APP_PYTHON_VENV = join(APP_HOME, 'python')
// APP_TMP will be the folder to upload files the first time the user opens the Application
export const APP_TMP = join(APP_HOME, 'tmp')

export const PYTHON_SOURCE = join(APP_RUNNER, WIN ? 'python.exe' : 'bin/python3')
export const PYTHON_TARGET = join(APP_PYTHON, WIN ? 'Scripts\\python.exe' : 'bin/python3')
export const PYTHON_SOURCE = join(APP_PYTHON, WIN ? 'python.exe' : 'bin/python3')
export const PYTHON_TARGET = join(APP_PYTHON_VENV, WIN ? 'Scripts\\python.exe' : 'bin/python3')
Loading