diff --git a/src/configmenu.js b/src/configmenu.js deleted file mode 100644 index d0696a2..0000000 --- a/src/configmenu.js +++ /dev/null @@ -1,162 +0,0 @@ -import inquirer from "inquirer"; -import chalk from "chalk"; -import { showErrorMessage, showInfoMessage } from "./utils/messages.js"; -import { isDir, showPathSelector } from "./utils/pathSelector.js"; -import { saveConfig } from "./services/config.js"; -import { showNumberSelector } from "./utils/numberSelector.js"; -import fs from "fs-extra"; - -function bytesAmountToString(numBytes) { - const units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - - var value = numBytes; - var index = 0; - while (value > 1024) { - index = index + 1; - value = value / 1024; - } - - if (index == 0) return `${numBytes} Bytes`; - return `${numBytes} Bytes (${value} ${units[index]})`; -} - -async function showStorageQuotaSelector(config) { - console.log(showInfoMessage('You can use: "GB" or "gb", etc.')); - const result = await showNumberSelector( - config.storageQuota, - "Storage quota", - true, - ); - if (result < 100 * 1024 * 1024) { - console.log(showErrorMessage("Storage quote should be >= 100mb.")); - return config.storageQuota; - } - return result; -} - -export async function showConfigMenu(config) { - var newDataDir = config.dataDir; - try { - while (true) { - console.log(showInfoMessage("Codex Configuration")); - const { choice } = await inquirer - .prompt([ - { - type: "list", - name: "choice", - message: "Select to edit:", - choices: [ - `1. Data path = "${newDataDir}"`, - `2. Logs path = "${config.logsDir}"`, - `3. Storage quota = ${bytesAmountToString(config.storageQuota)}`, - `4. Discovery port = ${config.ports.discPort}`, - `5. P2P listen port = ${config.ports.listenPort}`, - `6. API port = ${config.ports.apiPort}`, - "7. Save changes and exit", - "8. Discard changes and exit", - ], - pageSize: 8, - loop: true, - }, - ]) - .catch(() => { - return; - }); - - switch (choice.split(".")[0]) { - case "1": - newDataDir = await showPathSelector(config.dataDir, false); - if (isDir(newDataDir)) { - console.log( - showInfoMessage( - "Warning: The new data path already exists. Make sure you know what you're doing.", - ), - ); - } - break; - case "2": - config.logsDir = await showPathSelector(config.logsDir, true); - break; - case "3": - config.storageQuota = await showStorageQuotaSelector(config); - break; - case "4": - config.ports.discPort = await showNumberSelector( - config.ports.discPort, - "Discovery Port (UDP)", - false, - ); - break; - case "5": - config.ports.listenPort = await showNumberSelector( - config.ports.listenPort, - "Listen Port (TCP)", - false, - ); - break; - case "6": - config.ports.apiPort = await showNumberSelector( - config.ports.apiPort, - "API Port (TCP)", - false, - ); - break; - case "7": - // save changes, back to main menu - config = updateDataDir(config, newDataDir); - saveConfig(config); - return; - case "8": - // discard changes, back to main menu - return; - } - } - } catch (error) { - console.error(chalk.red("An error occurred:", error.message)); - return; - } -} - -function updateDataDir(config, newDataDir) { - if (config.dataDir == newDataDir) return config; - - // The Codex dataDir is a little strange: - // If the old one is empty: The new one should not exist, so that codex creates it - // with the correct security permissions. - // If the old one does exist: We move it. - - if (isDir(config.dataDir)) { - console.log( - showInfoMessage( - "Moving Codex data folder...\n" + - `From: "${config.dataDir}"\n` + - `To: "${newDataDir}"`, - ), - ); - - try { - fs.moveSync(config.dataDir, newDataDir); - } catch (error) { - console.log( - showErrorMessage("Error while moving dataDir: " + error.message), - ); - throw error; - } - } else { - // Old data dir does not exist. - if (isDir(newDataDir)) { - console.log( - showInfoMessage( - "Warning: the selected data path already exists.\n" + - `New data path = "${newDataDir}"\n` + - "Codex may overwrite data in this folder.\n" + - "Codex will fail to start if this folder does not have the required\n" + - "security permissions.", - ), - ); - } - } - - config.dataDir = newDataDir; - return config; -} diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 0bd5483..a2e4d48 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -12,7 +12,6 @@ import { showSuccessMessage, } from "../utils/messages.js"; import { checkDependencies } from "../services/nodeService.js"; -import { saveConfig } from "../services/config.js"; import { getCodexRootPath, getCodexBinPath } from "../utils/appdata.js"; const platform = os.platform(); diff --git a/src/main.js b/src/main.js index c00e9c3..c8a2db7 100644 --- a/src/main.js +++ b/src/main.js @@ -19,8 +19,7 @@ import { } from "./handlers/installationHandlers.js"; import { runCodex, checkNodeStatus } from "./handlers/nodeHandlers.js"; import { showInfoMessage } from "./utils/messages.js"; -import { loadConfig } from "./services/config.js"; -import { showConfigMenu } from "./configmenu.js"; +import { ConfigService } from "./services/configService.js"; import { openCodexApp } from "./services/codexapp.js"; import { MainMenu } from "./ui/mainmenu.js"; @@ -90,9 +89,9 @@ export async function main() { process.on("SIGTERM", handleExit); process.on("SIGQUIT", handleExit); - const config = loadConfig(); + const configService = new ConfigService(); const uiService = new UiService(); - const installMenu = new InstallMenu(uiService, config); + const installMenu = new InstallMenu(uiService, configService); const mainMenu = new MainMenu(uiService, installMenu); await mainMenu.show(); diff --git a/src/services/config.js b/src/services/config.js deleted file mode 100644 index 7bbc2de..0000000 --- a/src/services/config.js +++ /dev/null @@ -1,54 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { getAppDataDir } from "../utils/appdata.js"; -import { - getCodexBinPath, - getCodexDataDirDefaultPath, - getCodexLogsDefaultPath, -} from "../utils/appdata.js"; - -const defaultConfig = { - codexExe: "", - // User-selected config options: - codexPath: getCodexBinPath(), - dataDir: getCodexDataDirDefaultPath(), - logsDir: getCodexLogsDefaultPath(), - storageQuota: 8 * 1024 * 1024 * 1024, - ports: { - discPort: 8090, - listenPort: 8070, - apiPort: 8080, - }, -}; - -function getConfigFilename() { - return path.join(getAppDataDir(), "config.json"); -} - -export function saveConfig(config) { - const filePath = getConfigFilename(); - try { - fs.writeFileSync(filePath, JSON.stringify(config)); - } catch (error) { - console.error( - `Failed to save config file to '${filePath}' error: '${error}'.`, - ); - throw error; - } -} - -export function loadConfig() { - const filePath = getConfigFilename(); - try { - if (!fs.existsSync(filePath)) { - saveConfig(defaultConfig); - return defaultConfig; - } - return JSON.parse(fs.readFileSync(filePath)); - } catch (error) { - console.error( - `Failed to load config file from '${filePath}' error: '${error}'.`, - ); - throw error; - } -} diff --git a/src/services/configService.js b/src/services/configService.js new file mode 100644 index 0000000..7a2539b --- /dev/null +++ b/src/services/configService.js @@ -0,0 +1,64 @@ +import fs from "fs"; +import path from "path"; +import { getAppDataDir } from "../utils/appdata.js"; +import { + getCodexBinPath, + getCodexDataDirDefaultPath, + getCodexLogsDefaultPath, +} from "../utils/appdata.js"; + +const defaultConfig = { + codexExe: "", + // User-selected config options: + codexPath: getCodexBinPath(), + dataDir: getCodexDataDirDefaultPath(), + logsDir: getCodexLogsDefaultPath(), + storageQuota: 8 * 1024 * 1024 * 1024, + ports: { + discPort: 8090, + listenPort: 8070, + apiPort: 8080, + }, +}; + +export class ConfigService { + constructor() { + this.loadConfig(); + } + + get = () => { + return this.config; + } + + loadConfig = () => { + const filePath = this.getConfigFilename(); + try { + if (!fs.existsSync(filePath)) { + this.config = defaultConfig; + this.saveConfig(); + } + this.config = JSON.parse(fs.readFileSync(filePath)); + } catch (error) { + console.error( + `Failed to load config file from '${filePath}' error: '${error}'.`, + ); + throw error; + } + } + + saveConfig = () => { + const filePath = this.getConfigFilename(); + try { + fs.writeFileSync(filePath, JSON.stringify(this.config)); + } catch (error) { + console.error( + `Failed to save config file to '${filePath}' error: '${error}'.`, + ); + throw error; + } + } + + getConfigFilename = () => { + return path.join(getAppDataDir(), "config.json"); + } +} diff --git a/src/ui/configmenu.js b/src/ui/configmenu.js new file mode 100644 index 0000000..c884412 --- /dev/null +++ b/src/ui/configmenu.js @@ -0,0 +1,167 @@ +export class ConfigMenu { + constructor(uiService, configService, pathSelector, numberSelector) { + this.ui = uiService; + this.configService = configService; + this.pathSelector = pathSelector; + this.numberSelector = numberSelector; + } + + show = async() => { + this.running = true; + this.config = this.configService.get(); + while (this.running) { + this.ui.showInfoMessage("Codex Configuration"); + await this.ui.askMultipleChoice("Select to edit:",[ + { + label: `Data path = "${this.config.dataDir}"`, + action: this.editDataDir, + }, + { + label: `Logs path = "${this.config.logsDir}"`, + action: this.editLogsDir, + }, + { + label: `Storage quota = ${bytesAmountToString(this.config.storageQuota)}`, + action: this.editStorageQuota, + }, + { + label: `Discovery port = ${this.config.ports.discPort}`, + action: this.editDiscPort, + }, + { + label: `P2P listen port = ${this.config.ports.listenPort}`, + action: this.editListenPort, + }, + { + label: `API port = ${this.config.ports.apiPort}`, + action: this.editApiPort, + }, + { + label: "Save changes and exit", + action: this.saveChangesAndExit, + }, + { + label: "Discard changes and exit", + action: this.discardChangesAndExit, + } + ] + ) + } + } + + bytesAmountToString = (numBytes) => { + const units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + + var value = numBytes; + var index = 0; + while (value > 1024) { + index = index + 1; + value = value / 1024; + } + + if (index == 0) return `${numBytes} Bytes`; + return `${numBytes} Bytes (${value} ${units[index]})`; + } + + editDataDir = async () => { + // todo + // function updateDataDir(config, newDataDir) { + // if (config.dataDir == newDataDir) return config; + + // // The Codex dataDir is a little strange: + // // If the old one is empty: The new one should not exist, so that codex creates it + // // with the correct security permissions. + // // If the old one does exist: We move it. + + // if (isDir(config.dataDir)) { + // console.log( + // showInfoMessage( + // "Moving Codex data folder...\n" + + // `From: "${config.dataDir}"\n` + + // `To: "${newDataDir}"`, + // ), + // ); + + // try { + // fs.moveSync(config.dataDir, newDataDir); + // } catch (error) { + // console.log( + // showErrorMessage("Error while moving dataDir: " + error.message), + // ); + // throw error; + // } + // } else { + // // Old data dir does not exist. + // if (isDir(newDataDir)) { + // console.log( + // showInfoMessage( + // "Warning: the selected data path already exists.\n" + + // `New data path = "${newDataDir}"\n` + + // "Codex may overwrite data in this folder.\n" + + // "Codex will fail to start if this folder does not have the required\n" + + // "security permissions.", + // ), + // ); + // } + // } + + // config.dataDir = newDataDir; + // return config; + // } + } + + editLogsDir = async () => { + this.config.logsDir = await this.pathSelector.show(this.config.logsDir, true); + } + + editStorageQuota = async () => { + this.ui.showInfoMessage("You can use: 'GB' or 'gb', etc."); + const newQuota = await this.numberSelector.show(this.config.storageQuota, "Storage quota", true); + if (newQuota < 100 * 1024 * 1024) { + this.ui.showErrorMessage("Storage quote should be >= 100mb."); + } else { + this.config.storageQuota = newQuota; + } + } + + editDiscPort = async () => { + const newPort = await this.numberSelector.show(this.config.ports.discPort, "Discovery port", false); + if (this.isInPortRange(newPort)) { + this.config.ports.discPort = newPort; + } + } + + editListenPort = async () => { + const newPort = await this.numberSelector.show(this.config.ports.listenPort, "P2P listen port", false); + if (this.isInPortRange(newPort)) { + this.config.ports.listenPort = newPort; + } + } + + editApiPort = async () => { + const newPort = await this.numberSelector.show(this.config.ports.apiPort, "API port", false); + if (this.isInPortRange(newPort)) { + this.config.ports.apiPort = newPort; + } + } + + isInPortRange = (number) => { + if (number < 1024 || number > 65535) { + this.ui.showErrorMessage("Port should be between 1024 and 65535."); + return false; + } + return true; + } + + saveChangesAndExit = async () => { + this.configService.saveConfig(); + this.ui.showInfoMessage("Configuration changes saved."); + this.running = false; + } + + discardChangesAndExit = async () => { + this.configService.loadConfig(); + this.ui.showInfoMessage("Changes discarded."); + this.running = false; + } +} diff --git a/src/ui/installmenu.js b/src/ui/installmenu.js index 86a8a45..f00c620 100644 --- a/src/ui/installmenu.js +++ b/src/ui/installmenu.js @@ -1,7 +1,7 @@ export class InstallMenu { - constructor(uiService, config) { + constructor(uiService, configService) { this.ui = uiService; - this.config = config; + this.config = configService.get(); } show = async () => { diff --git a/src/utils/numberSelector.js b/src/utils/numberSelector.js index 1dcc10c..d790a0b 100644 --- a/src/utils/numberSelector.js +++ b/src/utils/numberSelector.js @@ -3,7 +3,7 @@ export class NumberSelector { this.uiService = uiService; } - showNumberSelector = async ( + show = async ( currentValue, promptMessage, allowMetricPostfixes, diff --git a/src/utils/pathSelector.js b/src/utils/pathSelector.js index 6e6c473..a0de9f4 100644 --- a/src/utils/pathSelector.js +++ b/src/utils/pathSelector.js @@ -6,14 +6,16 @@ export class PathSelector { this.pathMustExist = true; } - showPathSelector = async (startingPath, pathMustExist) => { + show = async (startingPath, pathMustExist) => { + this.running = true; + this.startingPath = startingPath; this.pathMustExist = pathMustExist; this.roots = this.fs.getAvailableRoots(); this.currentPath = this.splitPath(startingPath); if (!this.hasValidRoot(this.currentPath)) { this.currentPath = [roots[0]]; } - while (true) { + while (this.running) { this.showCurrent(); this.ui.askMultiChoice("Select an option:", [ { @@ -42,6 +44,8 @@ export class PathSelector { }, ]); } + + return this.resultingPath; }; splitPath = (str) => { @@ -166,4 +170,14 @@ export class PathSelector { if (name.length < 1) return; this.updateCurrentIfValidParts([...currentPath, name]); }; + + selectThisPath = async () => { + this.resultingPath = this.combine(this.currentPath); + this.running = false; + } + + cancel = async () => { + this.resultingPath = this.startingPath; + this.running = false; + }; }