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: |  * 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 |  * - case A) 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 |  * - case B) 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 |  * - case C) 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 D) as a fallback if the previous step fails, we'll use home dir | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import os from "os"; | import os from "os"; | ||||||
| import fs from "fs"; | 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
 |  | ||||||
| 
 |  | ||||||
|     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; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| const DIR_NAME = "trilium-data"; | 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 (process.env.TRILIUM_DATA_DIR) { | ||||||
|         if (!fs.existsSync(process.env.TRILIUM_DATA_DIR)) { |         createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); | ||||||
|             fs.mkdirSync(process.env.TRILIUM_DATA_DIR, 0o700); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return 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)) { |     if (fs.existsSync(homePath)) { | ||||||
|         return homePath; |         return homePath; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const appDataPath = getAppDataDir() + path.sep + DIR_NAME; |     // case C
 | ||||||
| 
 |     const platformAppDataDir = getPlatformAppDataDir(os.platform(), process.env.APPDATA); | ||||||
|     if (!fs.existsSync(appDataPath)) { |     if (platformAppDataDir && fs.existsSync(platformAppDataDir)) { | ||||||
|         fs.mkdirSync(appDataPath, 0o700); |         const appDataDirPath = pathJoin(platformAppDataDir, dataDirName); | ||||||
|  |         createDirIfNotExisting(appDataDirPath); | ||||||
|  |         return appDataDirPath; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return appDataPath; |     // case D
 | ||||||
|  |     createDirIfNotExisting(homePath); | ||||||
|  |     return homePath; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const TRILIUM_DATA_DIR = getTriliumDataDir(); | export function getDataDirs(TRILIUM_DATA_DIR: string) { | ||||||
| const DIR_SEP = TRILIUM_DATA_DIR + path.sep; |     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`; |     Object.freeze(dataDirs); | ||||||
| const BACKUP_DIR = process.env.TRILIUM_BACKUP_DIR || `${DIR_SEP}backup`; |     return dataDirs; | ||||||
| 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 { | export function getPlatformAppDataDir(platform: ReturnType<typeof os.platform>, ENV_APPDATA_DIR: string | undefined = process.env.APPDATA) { | ||||||
|     TRILIUM_DATA_DIR, |     switch (true) { | ||||||
|     DOCUMENT_PATH, |         case platform === "win32" && !!ENV_APPDATA_DIR: | ||||||
|     BACKUP_DIR, |             return ENV_APPDATA_DIR; | ||||||
|     LOG_DIR, | 
 | ||||||
|     ANONYMIZED_DB_DIR, |         case platform === "linux": | ||||||
|     CONFIG_INI_PATH |             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