Skip to content

Commit 3d616aa

Browse files
feat: introduce jsonFileSettingsService to work with JSON files
Intorduce a new service - `jsonFileSettingsService` to work with JSON files in which you need to persist specific settings. The read/write operation of the file are locked, which makes the service usable in multiple cases. Having a separate service allows us to use multiple files to store data instead of writing everything in the user-settings.json file. To create the service reuse the logic from the old UserSettingsServiceBase and make it available through `$injector`. Add logic in the service to allow writing/reading data within specific cache timeout. In case you want to save data with cache information, just pass this setting to saveSetting method and the service will automatically add required information in the file. When you read information from the file, the service will check if you have passed cacheTimeout and will respect it when checking the value. In case you've passed cacheTimeout, but the value in the file does not have cache information, the service will return null as it is unable to determine if the cache is valid. In case there's cache information, the service will return the value in case the cache had not expired and null otherwise. In case the value in the file has cache information, but the readSetting is called without cacheTimeout, the value is returned immediately without checking the cache. The service is marked as non-shared, so for each `$injector.resolve("jsonFileSettingsService", { jsonFileSettingsPath: "myPath.json" })`, CLI will create a new instance of the service. As all actions are locked via lockfile, this shouldn't be a problem.
1 parent 62b1e19 commit 3d616aa

File tree

9 files changed

+548
-128
lines changed

9 files changed

+548
-128
lines changed

lib/bootstrap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,4 @@ $injector.require("watchIgnoreListService", "./services/watch-ignore-list-servic
228228

229229
$injector.requirePublicClass("initializeService", "./services/initialize-service");
230230
$injector.require("ipService", "./services/ip-service");
231+
$injector.require("jsonFileSettingsService", "./common/services/json-file-settings-service");

lib/common/declarations.d.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,9 +1222,8 @@ interface IPlistParser {
12221222
parseFileSync(plistFilePath: string): any;
12231223
}
12241224

1225-
interface IUserSettingsService extends UserSettings.IUserSettingsService {
1226-
loadUserSettingsFile(): Promise<void>;
1227-
saveSettings(data: IDictionary<{}>): Promise<void>;
1225+
interface IUserSettingsService extends IJsonFileSettingsService {
1226+
// keep for backwards compatibility
12281227
}
12291228

12301229
/**
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
interface ICacheTimeoutOpts {
2+
cacheTimeout: number;
3+
}
4+
5+
interface IUseCacheOpts {
6+
useCaching: boolean;
7+
}
8+
9+
interface IJsonFileSettingsService {
10+
getSettingValue<T>(settingName: string, cacheOpts?: ICacheTimeoutOpts): Promise<T>;
11+
saveSetting<T>(key: string, value: T, cacheOpts?: IUseCacheOpts): Promise<void>;
12+
removeSetting(key: string): Promise<void>;
13+
loadUserSettingsFile(): Promise<void>;
14+
saveSettings(data: IDictionary<{}>, cacheOpts?: IUseCacheOpts): Promise<void>;
15+
}

lib/common/definitions/user-settings.d.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import * as path from "path";
2+
import { parseJson } from "../helpers";
3+
4+
export class JsonFileSettingsService implements IJsonFileSettingsService {
5+
private jsonSettingsFilePath: string = null;
6+
protected jsonSettingsData: any = null;
7+
private get lockFilePath(): string {
8+
return `${this.jsonSettingsFilePath}.lock`;
9+
}
10+
11+
constructor(jsonFileSettingsPath: string,
12+
private $fs: IFileSystem,
13+
private $lockService: ILockService,
14+
private $logger: ILogger) {
15+
this.jsonSettingsFilePath = jsonFileSettingsPath;
16+
}
17+
18+
public async getSettingValue<T>(settingName: string, cacheOpts?: { cacheTimeout: number }): Promise<T> {
19+
const action = async (): Promise<T> => {
20+
await this.loadUserSettingsFile();
21+
22+
if (this.jsonSettingsData && _.has(this.jsonSettingsData, settingName)) {
23+
const data = this.jsonSettingsData[settingName];
24+
const dataToReturn = data.modifiedByCacheMechanism ? data.value : data;
25+
if (cacheOpts && cacheOpts.cacheTimeout) {
26+
if (!data.modifiedByCacheMechanism) {
27+
// If data has no cache, but we want to check the timeout, consider the data as outdated.
28+
// this should be a really rare case
29+
return null;
30+
}
31+
32+
const currentTime = Date.now();
33+
if ((currentTime - data.time) > cacheOpts.cacheTimeout) {
34+
return null;
35+
}
36+
}
37+
38+
return dataToReturn;
39+
}
40+
41+
return null;
42+
};
43+
44+
return this.$lockService.executeActionWithLock<T>(action, this.lockFilePath);
45+
}
46+
47+
public async saveSetting<T>(key: string, value: T, cacheOpts?: { useCaching: boolean }): Promise<void> {
48+
const settingObject: any = {};
49+
settingObject[key] = value;
50+
51+
return this.saveSettings(settingObject, cacheOpts);
52+
}
53+
54+
public async removeSetting(key: string): Promise<void> {
55+
const action = async (): Promise<void> => {
56+
await this.loadUserSettingsFile();
57+
58+
delete this.jsonSettingsData[key];
59+
await this.saveSettings();
60+
};
61+
62+
return this.$lockService.executeActionWithLock<void>(action, this.lockFilePath);
63+
}
64+
65+
public saveSettings(data?: any, cacheOpts?: { useCaching: boolean }): Promise<void> {
66+
const action = async (): Promise<void> => {
67+
await this.loadUserSettingsFile();
68+
this.jsonSettingsData = this.jsonSettingsData || {};
69+
70+
_(data)
71+
.keys()
72+
.each(propertyName => {
73+
this.jsonSettingsData[propertyName] = cacheOpts && cacheOpts.useCaching && !data[propertyName].modifiedByCacheMechanism ? {
74+
time: Date.now(),
75+
value: data[propertyName],
76+
modifiedByCacheMechanism: true
77+
} : data[propertyName];
78+
});
79+
80+
this.$fs.writeJson(this.jsonSettingsFilePath, this.jsonSettingsData);
81+
};
82+
83+
return this.$lockService.executeActionWithLock<void>(action, this.lockFilePath);
84+
}
85+
86+
public async loadUserSettingsFile(): Promise<void> {
87+
if (!this.jsonSettingsData) {
88+
await this.loadUserSettingsData();
89+
}
90+
}
91+
92+
private async loadUserSettingsData(): Promise<void> {
93+
if (!this.$fs.exists(this.jsonSettingsFilePath)) {
94+
const unexistingDirs = this.getUnexistingDirectories(this.jsonSettingsFilePath);
95+
96+
this.$fs.writeFile(this.jsonSettingsFilePath, null);
97+
98+
// when running under 'sudo' we create the <path to home dir>/.local/share/.nativescript-cli dir with root as owner
99+
// and other Applications cannot access this directory anymore. (bower/heroku/etc)
100+
if (process.env.SUDO_USER) {
101+
for (const dir of unexistingDirs) {
102+
await this.$fs.setCurrentUserAsOwner(dir, process.env.SUDO_USER);
103+
}
104+
}
105+
}
106+
107+
const data = this.$fs.readText(this.jsonSettingsFilePath);
108+
109+
try {
110+
this.jsonSettingsData = parseJson(data);
111+
} catch (err) {
112+
this.$logger.trace(`Error while trying to parseJson ${data} data from ${this.jsonSettingsFilePath} file. Err is: ${err}`);
113+
this.$fs.deleteFile(this.jsonSettingsFilePath);
114+
}
115+
}
116+
117+
private getUnexistingDirectories(filePath: string): Array<string> {
118+
const unexistingDirs: Array<string> = [];
119+
let currentDir = path.join(filePath, "..");
120+
while (true) {
121+
// this directory won't be created.
122+
if (this.$fs.exists(currentDir)) {
123+
break;
124+
}
125+
unexistingDirs.push(currentDir);
126+
currentDir = path.join(currentDir, "..");
127+
}
128+
return unexistingDirs;
129+
}
130+
}
131+
132+
$injector.register("jsonFileSettingsService", JsonFileSettingsService, false);

lib/common/services/user-settings-service.ts

Lines changed: 0 additions & 107 deletions
This file was deleted.

0 commit comments

Comments
 (0)