diff --git a/spec-es6/data_dir.spec.ts b/spec-es6/data_dir.spec.ts new file mode 100644 index 000000000..70a329ff5 --- /dev/null +++ b/spec-es6/data_dir.spec.ts @@ -0,0 +1,138 @@ +import { describe, it, execute, expect } from "./mini_test.ts"; + +import { getPlatformAppDataDir, getDataDirs} from "../src/services/data_dir.ts" + + + +describe("data_dir.ts unit tests", () => { + + describe("#getPlatformAppDataDir()", () => { + + type TestCaseGetPlatformAppDataDir = [ + description: string, + fnValue: Parameters, + expectedValueFn: (val: ReturnType) => boolean + ] + const testCases: TestCaseGetPlatformAppDataDir[] = [ + + [ + "w/ unsupported OS it should return 'null'", + ["aix", undefined], + (val) => val === null + ], + + [ + "w/ win32 and no APPDATA set it should return 'null'", + ["win32", undefined], + (val) => val === null + ], + + [ + "w/ win32 and set APPDATA it should return set 'APPDATA'", + ["win32", "AppData"], + (val) => val === "AppData" + ], + + [ + "w/ linux it should return '/.local/share'", + ["linux", undefined], + (val) => val !== null && val.endsWith("/.local/share") + ], + + [ + "w/ linux and wrongly set APPDATA it should ignore APPDATA and return /.local/share", + ["linux", "FakeAppData"], + (val) => val !== null && val.endsWith("/.local/share") + ], + + [ + "w/ darwin it should return /Library/Application Support", + ["darwin", undefined], + (val) => val !== null && val.endsWith("/Library/Application Support") + ], + ]; + + testCases.forEach(testCase => { + const [testDescription, value, isExpected] = testCase; + return it(testDescription, () => { + const actual = getPlatformAppDataDir(...value); + const result = isExpected(actual); + expect(result).toBeTruthy() + + }) + }) + + + }) + + describe("#getTriliumDataDir", () => { + // TODO + }) + + describe("#getDataDirs()", () => { + + const envKeys: Omit, "TRILIUM_DATA_DIR">[] = [ + "DOCUMENT_PATH", + "BACKUP_DIR", + "LOG_DIR", + "ANONYMIZED_DB_DIR", + "CONFIG_INI_PATH", + ]; + + const setMockedEnv = (prefix: string | null) => { + envKeys.forEach(key => { + if (prefix) { + process.env[`TRILIUM_${key}`] = `${prefix}_${key}` + } else { + delete process.env[`TRILIUM_${key}`] + } + }) + }; + + it("w/ process.env values present, it should return an object using values from process.env", () => { + + // set mocked values + const mockValuePrefix = "MOCK"; + setMockedEnv(mockValuePrefix); + + // get result + const result = getDataDirs(`${mockValuePrefix}_TRILIUM_DATA_DIR`); + + for (const key in result) { + expect(result[key]).toEqual(`${mockValuePrefix}_${key}`) + } + }) + + it("w/ NO process.env values present, it should return an object using supplied TRILIUM_DATA_DIR as base", () => { + + // make sure values are undefined + setMockedEnv(null); + + const mockDataDir = "/home/test/MOCK_TRILIUM_DATA_DIR" + const result = getDataDirs(mockDataDir); + + for (const key in result) { + expect(result[key].startsWith(mockDataDir)).toBeTruthy() + } + }) + + it("should ignore attempts to change a property on the returned object", () => { + + // make sure values are undefined + setMockedEnv(null); + + const mockDataDir = "/home/test/MOCK_TRILIUM_DATA_DIR" + const result = getDataDirs(mockDataDir); + + //@ts-expect-error - attempt to change value of readonly property + result.BACKUP_DIR = "attempt to change"; + + for (const key in result) { + expect(result[key].startsWith(mockDataDir)).toBeTruthy() + } + }) + }) + +}); + +execute() \ No newline at end of file diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index b87b00d32..2e0c2f522 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -2,75 +2,83 @@ /* * This file resolves trilium data path in this order of priority: - * - if TRILIUM_DATA_DIR environment variable exists, then its value is used as the path - * - if "trilium-data" dir exists directly in the home dir, then it is used - * - based on OS convention, if the "app data directory" exists, we'll use or create "trilium-data" directory there - * - as a fallback if the previous step fails, we'll use home dir + * - case A) if TRILIUM_DATA_DIR environment variable exists, then its value is used as the path + * - case B) if "trilium-data" dir exists directly in the home dir, then it is used + * - case C) based on OS convention, if the "app data directory" exists, we'll use or create "trilium-data" directory there + * - case D) as a fallback if the previous step fails, we'll use home dir */ import os from "os"; import fs from "fs"; -import path from "path"; +import { join as pathJoin } from "path"; -function getAppDataDir() { - let appDataDir = os.homedir(); // fallback if OS is not recognized +const DIR_NAME = "trilium-data"; +const FOLDER_PERMISSIONS = 0o700; - if (os.platform() === "win32" && process.env.APPDATA) { - appDataDir = process.env.APPDATA; - } else if (os.platform() === "linux") { - appDataDir = `${os.homedir()}/.local/share`; - } else if (os.platform() === "darwin") { - appDataDir = `${os.homedir()}/Library/Application Support`; +export function getTriliumDataDir(dataDirName: string) { + // case A + if (process.env.TRILIUM_DATA_DIR) { + createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); + return process.env.TRILIUM_DATA_DIR; } - if (!fs.existsSync(appDataDir)) { - // expected app data path doesn't exist, let's use fallback - appDataDir = os.homedir(); + // case B + const homePath = pathJoin(os.homedir(), dataDirName); + if (fs.existsSync(homePath)) { + return homePath; } - return appDataDir; + // case C + const platformAppDataDir = getPlatformAppDataDir(os.platform(), process.env.APPDATA); + if (platformAppDataDir && fs.existsSync(platformAppDataDir)) { + const appDataDirPath = pathJoin(platformAppDataDir, dataDirName); + createDirIfNotExisting(appDataDirPath); + return appDataDirPath; + } + + // case D + createDirIfNotExisting(homePath); + return homePath; } -const DIR_NAME = "trilium-data"; +export function getDataDirs(TRILIUM_DATA_DIR: string) { + const dataDirs = { + TRILIUM_DATA_DIR: TRILIUM_DATA_DIR, + DOCUMENT_PATH: process.env.TRILIUM_DOCUMENT_PATH || pathJoin(TRILIUM_DATA_DIR, "document.db"), + BACKUP_DIR: process.env.TRILIUM_BACKUP_DIR || pathJoin(TRILIUM_DATA_DIR, "backup"), + LOG_DIR: process.env.TRILIUM_LOG_DIR || pathJoin(TRILIUM_DATA_DIR, "log"), + ANONYMIZED_DB_DIR: process.env.TRILIUM_ANONYMIZED_DB_DIR || pathJoin(TRILIUM_DATA_DIR, "anonymized-db"), + CONFIG_INI_PATH: process.env.TRILIUM_CONFIG_INI_PATH || pathJoin(TRILIUM_DATA_DIR, "config.ini") + } as const; + + Object.freeze(dataDirs); + return dataDirs; +} -function getTriliumDataDir() { - if (process.env.TRILIUM_DATA_DIR) { - if (!fs.existsSync(process.env.TRILIUM_DATA_DIR)) { - fs.mkdirSync(process.env.TRILIUM_DATA_DIR, 0o700); - } +export function getPlatformAppDataDir(platform: ReturnType, ENV_APPDATA_DIR: string | undefined = process.env.APPDATA) { + switch (true) { + case platform === "win32" && !!ENV_APPDATA_DIR: + return ENV_APPDATA_DIR; - return process.env.TRILIUM_DATA_DIR; - } + case platform === "linux": + return `${os.homedir()}/.local/share`; - const homePath = os.homedir() + path.sep + DIR_NAME; + case platform === "darwin": + return `${os.homedir()}/Library/Application Support`; - if (fs.existsSync(homePath)) { - return homePath; + default: + // if OS is not recognized + return null; } +} - const appDataPath = getAppDataDir() + path.sep + DIR_NAME; - - if (!fs.existsSync(appDataPath)) { - fs.mkdirSync(appDataPath, 0o700); +function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOLDER_PERMISSIONS) { + if (!fs.existsSync(path)) { + fs.mkdirSync(path, permissionMode); } - - return appDataPath; } -const TRILIUM_DATA_DIR = getTriliumDataDir(); -const DIR_SEP = TRILIUM_DATA_DIR + path.sep; - -const DOCUMENT_PATH = process.env.TRILIUM_DOCUMENT_PATH || `${DIR_SEP}document.db`; -const BACKUP_DIR = process.env.TRILIUM_BACKUP_DIR || `${DIR_SEP}backup`; -const LOG_DIR = process.env.TRILIUM_LOG_DIR || `${DIR_SEP}log`; -const ANONYMIZED_DB_DIR = process.env.TRILIUM_ANONYMIZED_DB_DIR || `${DIR_SEP}anonymized-db`; -const CONFIG_INI_PATH = process.env.TRILIUM_CONFIG_INI_PATH || `${DIR_SEP}config.ini`; - -export default { - TRILIUM_DATA_DIR, - DOCUMENT_PATH, - BACKUP_DIR, - LOG_DIR, - ANONYMIZED_DB_DIR, - CONFIG_INI_PATH -}; +const TRILIUM_DATA_DIR = getTriliumDataDir(DIR_NAME); +const dataDirs = getDataDirs(TRILIUM_DATA_DIR); + +export default dataDirs;