Skip to content

Commit

Permalink
handle restart existing local server
Browse files Browse the repository at this point in the history
  • Loading branch information
liuliu-dev committed Feb 12, 2025
1 parent 9909e8b commit 6606b42
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 105 deletions.
1 change: 1 addition & 0 deletions graphql-server/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ type Query {
currentConnection: DatabaseConnection
storedConnections: [DatabaseConnection!]!
databases: [String!]!
checkConnection(connectionUrl: String!, name: String!, port: String, hideDoltFeatures: Boolean, useSSL: Boolean, type: DatabaseType, isLocalDolt: Boolean): Boolean!
databasesByConnection(connectionUrl: String!, name: String!, port: String, hideDoltFeatures: Boolean, useSSL: Boolean, type: DatabaseType, isLocalDolt: Boolean): [String!]!
schemas(databaseName: String!, refName: String!): [String!]!
doltDatabaseDetails: DoltDatabaseDetails!
Expand Down
16 changes: 16 additions & 0 deletions graphql-server/src/databases/database.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,22 @@ export class DatabaseResolver {
return dbs;
}

@Query(_returns => Boolean)

Check failure on line 128 in graphql-server/src/databases/database.resolver.ts

View workflow job for this annotation

GitHub Actions / ci

Expected 'this' to be used by class async method 'checkConnection'
async checkConnection(
@Args() args: AddDatabaseConnectionArgs,
): Promise<Boolean> {

Check failure on line 131 in graphql-server/src/databases/database.resolver.ts

View workflow job for this annotation

GitHub Actions / ci

Don't use `Boolean` as a type. Use boolean instead
const workbenchConfig = getWorkbenchConfigFromArgs(args);
try {
const ds = getDataSource(workbenchConfig);
await ds.initialize();
const res = await ds.query("SELECT 1");

Check failure on line 136 in graphql-server/src/databases/database.resolver.ts

View workflow job for this annotation

GitHub Actions / ci

'res' is assigned a value but never used. Allowed unused vars must match /^_/u
return true;
} catch (error) {
console.error("Error checking connection:", error.message);

Check warning on line 139 in graphql-server/src/databases/database.resolver.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected console statement
return false;
}
}

@Query(_returns => [String])
async databasesByConnection(
@Args() args: AddDatabaseConnectionArgs,
Expand Down
3 changes: 3 additions & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ yalc.lock
.env

/build/mac/*.provisionprofile
/build/mac/dolt
/build/appx/dolt.exe
/build/databases

# electron app build
/app
Expand Down
9 changes: 5 additions & 4 deletions web/main/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import serve from "electron-serve";
import { createWindow } from "./helpers";
import { initMenu } from "./helpers/menu";
import { removeDoltServerFolder, startDoltServer } from "./doltServer";
import { removeDoltServerFolder, startServer } from "./doltServer";

const isProd = process.env.NODE_ENV === "production";
const userDataPath = app.getPath("userData");
Expand Down Expand Up @@ -227,12 +227,13 @@ function getErrorMessage(error: unknown): string {

ipcMain.handle(
"start-dolt-server",
async (event, connectionName: string, port: string) => {
async (event, connectionName: string, port: string, init?: boolean) => {
try {
await startDoltServer(mainWindow, connectionName, port);
return "Server started successfully";
await startServer(mainWindow, connectionName, port, init);
} catch (error) {
throw new Error(getErrorMessage(error));
} finally {
return "Server started successfully";
}
},
);
Expand Down
223 changes: 136 additions & 87 deletions web/main/doltServer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from "fs";
import path from "path";
import path, { resolve } from "path";
import { app, BrowserWindow } from "electron";
import { exec } from "child_process";

Expand All @@ -11,7 +11,6 @@ type CreateFolderReturnType = {
const isProd = process.env.NODE_ENV === "production";

export function createFolder(folderPath: string): CreateFolderReturnType {
console.log(folderPath, fs.existsSync);
if (!fs.existsSync(folderPath)) {
fs.mkdirSync(folderPath, { recursive: true }); // Create parent directories if they don't exist
console.log(`Folder created at: ${folderPath}`);
Expand All @@ -38,114 +37,164 @@ function getDoltPaths(): string {
}
}

export function startDoltServer(
mainWindow: BrowserWindow,
//initialize the Dolt repository
function initializeDoltRepository(
doltPath: string,
dbFolderPath: string,
connectionName: string,
port: string,
port: number,
mainWindow: BrowserWindow,
): Promise<void> {
return new Promise((resolve, reject) => {
const dbFolderPath = path.join(
app.getPath("userData"),
"databases",
connectionName,
);

// Create the folder for the connection
const { error, errorMsg } = createFolder(dbFolderPath);
if (error) {
mainWindow.webContents.send("server-error", errorMsg);
reject(new Error(errorMsg));
return;
}
const doltPath = getDoltPaths();

// Initialize Dolt repository
exec(`${doltPath} init`, { cwd: dbFolderPath }, (error, stdout, stderr) => {
if (error) {
const errorMessage = `Error initializing Dolt: ${stderr}`;
console.error(errorMessage);
mainWindow.webContents.send("server-error", errorMessage);

// Clean up: Delete the folder
removeDoltServerFolder(connectionName, port);
removeDoltServerFolder(connectionName, port.toString());

reject(new Error(errorMessage));
return;
}

console.log(`Dolt initialized: ${stdout}`);
mainWindow.webContents.send("server-log", `Dolt initialized: ${stdout}`);
resolve();
});
});
}

// Start Dolt SQL server
const serverProcess = exec(`${doltPath} sql-server -P ${port}`, {
// start the Dolt SQL server
function startServerProcess(
doltPath: string,
dbFolderPath: string,
port: string,
mainWindow: BrowserWindow,
connectionName: string,
): Promise<void> {
return new Promise((resolve, reject) => {
const serverProcess = exec(
`${doltPath} sql-server -P ${port}`,
{
cwd: dbFolderPath,
});

const handleServerLog = (chunk: Buffer) => {
const logMessage = chunk.toString("utf8");
console.log("Server Log:", logMessage);
mainWindow.webContents.send("server-log", logMessage);
if (logMessage.includes("already in use.")) {
mainWindow.webContents.send("server-error", logMessage);
// Clean up: Delete the folder
removeDoltServerFolder(connectionName, port);

reject(new Error(logMessage));
}

// Resolve the promise when the server is ready
if (logMessage.includes("Server ready")) {
resolve();
}
};

const handleServerError = (chunk: Buffer) => {
const errorMessage = chunk.toString("utf8");
console.error("Server Error:", errorMessage);

// Check if the message is a warning or an error
if (errorMessage.includes("level=warning")) {
// Treat warnings as non-fatal
mainWindow.webContents.send("server-warning", errorMessage);
} else if (errorMessage.includes("level=error")) {
// Treat errors as fatal
mainWindow.webContents.send("server-error", errorMessage);

// Clean up: Delete the folder
removeDoltServerFolder(connectionName, port);

reject(new Error(errorMessage));
}
// Resolve the promise when the server is ready
if (errorMessage.includes("Server ready")) {
resolve();
}
};

serverProcess.stdout?.on("data", handleServerLog);
serverProcess.stderr?.on("data", handleServerError);

serverProcess.on("close", code => {
const logMessage = `Dolt SQL Server process exited with code ${code}`;
mainWindow.webContents.send("server-log", logMessage);

if (code !== 0) {
// Clean up: Delete the folder
removeDoltServerFolder(connectionName, port);

reject(new Error(logMessage));
}

// Ensure the process is not hanging and the port is released
if (!serverProcess.killed) {
console.log("Ensuring the Dolt SQL Server process is terminated");
serverProcess.kill("SIGTERM"); // Attempt to kill the process gracefully
}
});
},
(error, stdout, stderr) => {
console.log("error", error);
console.log("stdout", stdout);
console.log("stderr", stderr);
},
);

const handleServerLog = (chunk: Buffer) => {
const logMessage = chunk.toString("utf8");
console.log("Server Log:", logMessage);
mainWindow.webContents.send("server-log", logMessage);

// Resolve the promise when the server is ready
if (logMessage.includes("Server ready")) {
resolve();
}
};

const handleServerError = (chunk: Buffer) => {
const errorMessage = chunk.toString("utf8");
console.error("Server Error:", errorMessage);

// Check if the message is a warning or an error
if (errorMessage.includes("level=warning")) {
// Treat warnings as non-fatal
mainWindow.webContents.send("server-warning", errorMessage);
} else if (errorMessage.includes("level=error")) {
// Treat errors as fatal
mainWindow.webContents.send("server-error", errorMessage);

reject(new Error(errorMessage));
} else {
mainWindow.webContents.send("server-log", errorMessage);
}

// Resolve the promise when the server is ready
if (errorMessage.includes("Server ready")) {
resolve();
}
};

serverProcess.stdout?.on("data", handleServerLog);
serverProcess.stderr?.on("data", handleServerError);

serverProcess.on("exit", (code: number) => {
const logMessage = `Dolt SQL Server process exited with code ${code}`;
mainWindow.webContents.send("server-log", logMessage);

if (code !== 0) {
reject(new Error(logMessage));
}

// Ensure the process is not hanging and the port is released
if (!serverProcess.killed) {
console.log("Ensuring the Dolt SQL Server process is terminated");
serverProcess.kill("SIGTERM"); // Attempt to kill the process gracefully
}
});
});
}

export async function startServer(
mainWindow: BrowserWindow,
connectionName: string,
port: string,
init?: boolean,
): Promise<void> {
const dbFolderPath = isProd
? path.join(app.getPath("userData"), "databases", connectionName)
: path.join(__dirname, "..", "build", "databases", connectionName);

const doltPath = getDoltPaths();

try {
if (init) {
// Create the folder for the connection
const { error, errorMsg } = createFolder(dbFolderPath);
if (error) {
mainWindow.webContents.send("server-error", errorMsg);
throw new Error(errorMsg);
}

// Initialize and start the server without checking if it's already running
await initializeDoltRepository(
doltPath,
dbFolderPath,
connectionName,
parseInt(port),
mainWindow,
);
await startServerProcess(
doltPath,
dbFolderPath,
port,
mainWindow,
connectionName,
);
} else {
// Check if the server is already running

// Start the server if it's not running
await startServerProcess(
doltPath,
dbFolderPath,
port,
mainWindow,
connectionName,
);
}
} catch (error) {
console.error("Failed to set up Dolt server:", error);
throw error;
}
}

export function removeDoltServerFolder(connectionName: string, port: string) {
const dbFolderPath = path.join(
app.getPath("userData"),
Expand Down
4 changes: 2 additions & 2 deletions web/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const handler = {
},
toggleLeftSidebar: (callback: () => {}) =>
ipcRenderer.on("toggle-left-sidebar", _event => callback()),
startDoltServer: (connectionName: string) =>
ipcRenderer.send("start-dolt-server", connectionName),
startDoltServer: (connectionName: string, port: string, init?: boolean) =>
ipcRenderer.send("start-dolt-server", connectionName, port, init),
removeDoltConnection: (connectionName: string, port: string) =>
ipcRenderer.send("remove-dolt-connection", connectionName, port),
getDoltServerError: (callback: (value: string) => {}) =>
Expand Down
2 changes: 1 addition & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,4 @@
"@types/react": "18.3.5",
"webpack": "5.94.0"
}
}
}
18 changes: 18 additions & 0 deletions web/renderer/components/DatabaseHeaderAndNav/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,21 @@ export const DATABASES_BY_CONNECTION = gql`
)
}
`;

export const CHECK_CONNECTION = gql`
query CheckConnection(
$connectionUrl: String!
$name: String!
$hideDoltFeatures: Boolean
$useSSL: Boolean
$type: DatabaseType
) {
checkConnection(
connectionUrl: $connectionUrl
name: $name
hideDoltFeatures: $hideDoltFeatures
useSSL: $useSSL
type: $type
)
}
`;
Loading

0 comments on commit 6606b42

Please sign in to comment.