mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 13:01:31 +08:00 
			
		
		
		
	Merge pull request #880 from pano9000/refactor_data_dir
refactor(data_dir): simplify logic and make code robust and testable
This commit is contained in:
		
						commit
						eb1af98830
					
				
							
								
								
									
										138
									
								
								spec-es6/data_dir.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								spec-es6/data_dir.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<typeof getPlatformAppDataDir>,  | ||||
|       expectedValueFn: (val: ReturnType<typeof getPlatformAppDataDir>) => 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<keyof ReturnType<typeof getDataDirs>, "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() | ||||
| @ -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"; | ||||
| 
 | ||||
| function getAppDataDir() { | ||||
|     let appDataDir = os.homedir(); // fallback if OS is not recognized
 | ||||
| 
 | ||||
|     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`; | ||||
|     } | ||||
| 
 | ||||
|     if (!fs.existsSync(appDataDir)) { | ||||
|         // expected app data path doesn't exist, let's use fallback
 | ||||
|         appDataDir = os.homedir(); | ||||
|     } | ||||
| 
 | ||||
|     return appDataDir; | ||||
| } | ||||
| import { join as pathJoin } from "path"; | ||||
| 
 | ||||
| const DIR_NAME = "trilium-data"; | ||||
| const FOLDER_PERMISSIONS = 0o700; | ||||
| 
 | ||||
| function getTriliumDataDir() { | ||||
| export function getTriliumDataDir(dataDirName: string) { | ||||
|     // case A
 | ||||
|     if (process.env.TRILIUM_DATA_DIR) { | ||||
|         if (!fs.existsSync(process.env.TRILIUM_DATA_DIR)) { | ||||
|             fs.mkdirSync(process.env.TRILIUM_DATA_DIR, 0o700); | ||||
|         } | ||||
| 
 | ||||
|         createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); | ||||
|         return process.env.TRILIUM_DATA_DIR; | ||||
|     } | ||||
| 
 | ||||
|     const homePath = os.homedir() + path.sep + DIR_NAME; | ||||
| 
 | ||||
|     // case B
 | ||||
|     const homePath = pathJoin(os.homedir(), dataDirName); | ||||
|     if (fs.existsSync(homePath)) { | ||||
|         return homePath; | ||||
|     } | ||||
| 
 | ||||
|     const appDataPath = getAppDataDir() + path.sep + DIR_NAME; | ||||
| 
 | ||||
|     if (!fs.existsSync(appDataPath)) { | ||||
|         fs.mkdirSync(appDataPath, 0o700); | ||||
|     // case C
 | ||||
|     const platformAppDataDir = getPlatformAppDataDir(os.platform(), process.env.APPDATA); | ||||
|     if (platformAppDataDir && fs.existsSync(platformAppDataDir)) { | ||||
|         const appDataDirPath = pathJoin(platformAppDataDir, dataDirName); | ||||
|         createDirIfNotExisting(appDataDirPath); | ||||
|         return appDataDirPath; | ||||
|     } | ||||
| 
 | ||||
|     return appDataPath; | ||||
|     // case D
 | ||||
|     createDirIfNotExisting(homePath); | ||||
|     return homePath; | ||||
| } | ||||
| 
 | ||||
| const TRILIUM_DATA_DIR = getTriliumDataDir(); | ||||
| const DIR_SEP = TRILIUM_DATA_DIR + path.sep; | ||||
| 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; | ||||
| 
 | ||||
| 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`; | ||||
|     Object.freeze(dataDirs); | ||||
|     return dataDirs; | ||||
| } | ||||
| 
 | ||||
| export default { | ||||
|     TRILIUM_DATA_DIR, | ||||
|     DOCUMENT_PATH, | ||||
|     BACKUP_DIR, | ||||
|     LOG_DIR, | ||||
|     ANONYMIZED_DB_DIR, | ||||
|     CONFIG_INI_PATH | ||||
| }; | ||||
| export function getPlatformAppDataDir(platform: ReturnType<typeof os.platform>, ENV_APPDATA_DIR: string | undefined = process.env.APPDATA) { | ||||
|     switch (true) { | ||||
|         case platform === "win32" && !!ENV_APPDATA_DIR: | ||||
|             return ENV_APPDATA_DIR; | ||||
| 
 | ||||
|         case platform === "linux": | ||||
|             return `${os.homedir()}/.local/share`; | ||||
| 
 | ||||
|         case platform === "darwin": | ||||
|             return `${os.homedir()}/Library/Application Support`; | ||||
| 
 | ||||
|         default: | ||||
|             // if OS is not recognized
 | ||||
|             return null; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOLDER_PERMISSIONS) { | ||||
|     if (!fs.existsSync(path)) { | ||||
|         fs.mkdirSync(path, permissionMode); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const TRILIUM_DATA_DIR = getTriliumDataDir(DIR_NAME); | ||||
| const dataDirs = getDataDirs(TRILIUM_DATA_DIR); | ||||
| 
 | ||||
| export default dataDirs; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Elian Doran
						Elian Doran