Skip to content

Commit 4e3a0f7

Browse files
committed
refactor: extract StorageSync class
1 parent 056f6f4 commit 4e3a0f7

5 files changed

Lines changed: 285 additions & 225 deletions

File tree

store/StorageSync.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
/**
2-
* @todo how to refactor with this logic?
3-
*/
4-
export abstract class StorageSync {
5-
autoSaveToLocalStorage() {}
6-
autoSaveToDevice() {}
7-
mergedDeviceDataWithStorage() {}
8-
getDeviceData() {}
9-
resetDeviceData() {}
10-
}
11-
export interface IStorageSync {
12-
autoSaveToLocalStorage: () => void;
13-
autoSaveToDevice: () => void;
14-
mergedDeviceDataWithStorage: () => void;
15-
getDeviceData: () => void;
16-
resetDeviceData: () => void;
1+
export interface StorageSync {
2+
mergedDeviceDataWithStorage(): unknown;
3+
changeStoragePlace(isSaveDataToDevice: boolean): unknown;
4+
autoSaveToLocalStorage(): unknown;
5+
autoSaveToDevice(): unknown;
6+
getDeviceData(): unknown;
7+
resetDeviceData(): unknown;
8+
resetStorageData(): unknown;
9+
clear?(): unknown;
1710
}

store/TestStatistics.ts

Lines changed: 30 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { userSettingsStore } from "@/store/UserSettings";
2-
import { readFile, STATISTICS_FILEPATH, writeFile } from "./../storage/fileAccess";
3-
import { getStoredJson, storeJson } from "@/storage/localStorage";
4-
import { debounce, round, uniq } from "lodash-es";
2+
import { round } from "lodash-es";
53
import { RequestStatus } from "./../typings/index";
64
import { CfIpResponse } from "@/screens/TestRunScreen/model";
7-
import { deepObserve, IDisposer } from "mobx-utils";
85
import { action, computed, makeObservable, observable, reaction } from "mobx";
6+
import { TestStatisticsStorageSync } from "./TestStatisticsStorageSync";
97
export type CfIpSummary = {
108
ip: string;
119
respondSuccessCount: number;
@@ -25,12 +23,10 @@ export type CfIpStatistics = CfIpSummary & {
2523
downloadSuccessRate: number;
2624
};
2725
export type CfIpStatisticsMap = Record<string, CfIpSummary>;
28-
const STORAGE_KEY_TEST_STATISTICS = "cf-ip-tester-app__test-statistics";
26+
2927
export class TestStatistics {
30-
statistics: CfIpStatisticsMap = {};
31-
private unsubscribeSaveToLocalStorage: IDisposer | undefined;
32-
private unsubscribeSaveToDevice: IDisposer | undefined;
33-
private filePath = STATISTICS_FILEPATH;
28+
readonly statistics: CfIpStatisticsMap = {};
29+
private storageSync: TestStatisticsStorageSync;
3430

3531
public get computedRecordList(): CfIpStatistics[] {
3632
const result = this.getRawRecordList().map((cfIpSummary) => {
@@ -71,122 +67,26 @@ export class TestStatistics {
7167
addRecord: action,
7268
updateRecord: action,
7369
});
74-
this.mergedDeviceDataWithStorage()
75-
.catch((err) => {
76-
console.log(err, "merge statistics failed");
77-
})
78-
.then(() => {
70+
this.storageSync = new TestStatisticsStorageSync(this.statistics);
71+
this.storageSync
72+
.mergedDeviceDataWithStorage()
73+
.then((statistics) => {
74+
this.changeStatistics(statistics);
7975
reaction(
8076
() => userSettingsStore.userSetting.isSaveDataToDevice,
81-
this.onSavingDataToDeviceChange.bind(this),
77+
async (isSaveDataToDevice) => {
78+
const statistics = await this.storageSync.changeStoragePlace(
79+
isSaveDataToDevice
80+
);
81+
this.changeStatistics(statistics);
82+
},
8283
{ fireImmediately: true }
8384
);
85+
})
86+
.catch((err) => {
87+
console.log(err, "merge statistics failed");
8488
});
8589
}
86-
private async onSavingDataToDeviceChange(isSaveDataToDevice: boolean) {
87-
if (isSaveDataToDevice) {
88-
await this.autoSaveToDevice();
89-
this.unsubscribeSaveToLocalStorage?.();
90-
this.resetStorageData();
91-
return;
92-
}
93-
this.autoSaveToLocalStorage();
94-
this.unsubscribeSaveToDevice?.();
95-
this.resetDeviceData();
96-
}
97-
private async resetDeviceData() {
98-
await writeFile(this.filePath, "");
99-
}
100-
private async resetStorageData() {
101-
await storeJson(STORAGE_KEY_TEST_STATISTICS, {});
102-
}
103-
private autoSaveToLocalStorage() {
104-
this.unsubscribeSaveToLocalStorage = deepObserve(this.statistics, () => {
105-
storeJson(STORAGE_KEY_TEST_STATISTICS, this.statistics);
106-
});
107-
}
108-
private async autoSaveToDevice() {
109-
await this.mergedDeviceDataWithStorage();
110-
this.unsubscribeSaveToDevice = deepObserve(
111-
this.statistics,
112-
debounce(
113-
() => {
114-
writeFile(this.filePath, JSON.stringify(this.statistics));
115-
},
116-
3000,
117-
{ leading: true, trailing: true, maxWait: 5000 }
118-
)
119-
);
120-
}
121-
private async mergedDeviceDataWithStorage() {
122-
const deviceData = await this.getDeviceData();
123-
const storageData = await getStoredJson<CfIpStatisticsMap>(
124-
STORAGE_KEY_TEST_STATISTICS,
125-
{}
126-
);
127-
const statistics = this.getDeviceStorageMergedData(deviceData, storageData);
128-
129-
this.changeStatistics(statistics);
130-
}
131-
private getDeviceStorageMergedData(
132-
statisticsMapA: CfIpStatisticsMap,
133-
statisticsMapB: CfIpStatisticsMap
134-
): CfIpStatisticsMap {
135-
const resultMap: CfIpStatisticsMap = {};
136-
const uniqueKeys = uniq([
137-
...Object.keys(statisticsMapA),
138-
...Object.keys(statisticsMapB),
139-
]);
140-
uniqueKeys.forEach((key) => {
141-
const mapAValue = statisticsMapA[key];
142-
const mapBValue = statisticsMapB[key];
143-
if (!mapAValue) {
144-
resultMap[key] = mapBValue;
145-
return;
146-
}
147-
if (!mapBValue) {
148-
resultMap[key] = mapAValue;
149-
return;
150-
}
151-
resultMap[key] = this.mergeStatisticsWithSameKeys(mapAValue, mapBValue);
152-
});
153-
return resultMap;
154-
}
155-
private mergeStatisticsWithSameKeys(
156-
statisticsA: CfIpSummary,
157-
statisticsB: CfIpSummary
158-
): CfIpSummary {
159-
return {
160-
ip: statisticsA.ip,
161-
respondSuccessCount:
162-
statisticsA.respondSuccessCount + statisticsB.respondSuccessCount,
163-
respondFailCount:
164-
statisticsA.respondFailCount + statisticsB.respondFailCount,
165-
totalRespondTime:
166-
statisticsA.totalRespondTime + statisticsB.totalRespondTime,
167-
downloadSuccessCount:
168-
statisticsA.downloadSuccessCount + statisticsB.downloadSuccessCount,
169-
downloadFailCount:
170-
statisticsA.downloadFailCount + statisticsB.downloadFailCount,
171-
totalDownloadSpeed: round(
172-
statisticsA.totalDownloadSpeed + statisticsB.totalDownloadSpeed,
173-
4
174-
),
175-
};
176-
}
177-
private async getDeviceData(): Promise<CfIpStatisticsMap> {
178-
const jsonStr = await readFile(this.filePath);
179-
if (!jsonStr) {
180-
return {};
181-
}
182-
let result = {};
183-
try {
184-
result = JSON.parse(jsonStr);
185-
} catch (error) {
186-
return {};
187-
}
188-
return result;
189-
}
19090
isRecordExist(cfIpResponse: CfIpResponse) {
19191
return !!this.statistics[cfIpResponse.ip];
19292
}
@@ -231,8 +131,16 @@ export class TestStatistics {
231131
private getRecord(ip: string) {
232132
return this.statistics[ip];
233133
}
134+
/**
135+
* @description keep the reference to make sure it trackable for other places, especially for TestStatisticsStorageSync. Seems not a good way, except provide vm to TestStatisticsStorageSync
136+
*/
234137
changeStatistics(statistics: CfIpStatisticsMap) {
235-
this.statistics = statistics;
138+
Object.keys(this.statistics).forEach((key) => {
139+
delete this.statistics[key];
140+
});
141+
Object.keys(statistics).forEach((key) => {
142+
this.statistics[key] = statistics[key];
143+
});
236144
}
237145
private getRawRecordList() {
238146
return Object.values(this.statistics);
@@ -250,11 +158,10 @@ export class TestStatistics {
250158
return cfIpResponse.downloadSpeedTestStatus === RequestStatus.Error;
251159
}
252160
public async clear() {
253-
await this.resetDeviceData();
254-
await this.resetStorageData();
161+
await this.storageSync.clear();
255162
}
256163
}
257164

258165
const testStatisticsStore = new TestStatistics();
259166

260-
export { testStatisticsStore };
167+
export { testStatisticsStore };

store/TestStatisticsStorageSync.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { IDisposer, deepObserve } from "mobx-utils";
2+
import { STATISTICS_FILEPATH, readFile, writeFile } from "@/storage/fileAccess";
3+
import { getStoredJson, storeJson } from "@/storage/localStorage";
4+
import { uniq, round, debounce } from "lodash-es";
5+
import { CfIpStatisticsMap, CfIpSummary } from "./TestStatistics";
6+
import { StorageSync } from "./StorageSync";
7+
const STORAGE_KEY_TEST_STATISTICS = "cf-ip-tester-app__test-statistics";
8+
9+
export class TestStatisticsStorageSync implements StorageSync {
10+
private filePath = STATISTICS_FILEPATH;
11+
12+
private unsubscribeSaveToLocalStorage: IDisposer | undefined;
13+
private unsubscribeSaveToDevice: IDisposer | undefined;
14+
private statistics: CfIpStatisticsMap;
15+
16+
constructor(data: CfIpStatisticsMap) {
17+
this.statistics = data;
18+
}
19+
20+
public async mergedDeviceDataWithStorage() {
21+
const deviceData = await this.getDeviceData();
22+
const storageData = await getStoredJson<CfIpStatisticsMap>(
23+
STORAGE_KEY_TEST_STATISTICS,
24+
{}
25+
);
26+
const statistics = this.getDeviceStorageMergedData(deviceData, storageData);
27+
return statistics;
28+
}
29+
private getDeviceStorageMergedData(
30+
statisticsMapA: CfIpStatisticsMap,
31+
statisticsMapB: CfIpStatisticsMap
32+
): CfIpStatisticsMap {
33+
const resultMap: CfIpStatisticsMap = {};
34+
const uniqueKeys = uniq([
35+
...Object.keys(statisticsMapA),
36+
...Object.keys(statisticsMapB),
37+
]);
38+
uniqueKeys.forEach((key) => {
39+
const mapAValue = statisticsMapA[key];
40+
const mapBValue = statisticsMapB[key];
41+
if (!mapAValue) {
42+
resultMap[key] = mapBValue;
43+
return;
44+
}
45+
if (!mapBValue) {
46+
resultMap[key] = mapAValue;
47+
return;
48+
}
49+
resultMap[key] = this.mergeStatisticsWithSameKeys(mapAValue, mapBValue);
50+
});
51+
return resultMap;
52+
}
53+
private mergeStatisticsWithSameKeys(
54+
statisticsA: CfIpSummary,
55+
statisticsB: CfIpSummary
56+
): CfIpSummary {
57+
return {
58+
ip: statisticsA.ip,
59+
respondSuccessCount:
60+
statisticsA.respondSuccessCount + statisticsB.respondSuccessCount,
61+
respondFailCount:
62+
statisticsA.respondFailCount + statisticsB.respondFailCount,
63+
totalRespondTime:
64+
statisticsA.totalRespondTime + statisticsB.totalRespondTime,
65+
downloadSuccessCount:
66+
statisticsA.downloadSuccessCount + statisticsB.downloadSuccessCount,
67+
downloadFailCount:
68+
statisticsA.downloadFailCount + statisticsB.downloadFailCount,
69+
totalDownloadSpeed: round(
70+
statisticsA.totalDownloadSpeed + statisticsB.totalDownloadSpeed,
71+
4
72+
),
73+
};
74+
}
75+
public async getDeviceData(): Promise<CfIpStatisticsMap> {
76+
const jsonStr = await readFile(this.filePath);
77+
if (!jsonStr) {
78+
return {};
79+
}
80+
let result = {};
81+
try {
82+
result = JSON.parse(jsonStr);
83+
} catch (error) {
84+
return {};
85+
}
86+
return result;
87+
}
88+
public async resetDeviceData() {
89+
await writeFile(this.filePath, "");
90+
}
91+
public async resetStorageData() {
92+
await storeJson(STORAGE_KEY_TEST_STATISTICS, {});
93+
}
94+
public async autoSaveToLocalStorage() {
95+
const result = await this.mergedDeviceDataWithStorage();
96+
this.unsubscribeSaveToLocalStorage = deepObserve(this.statistics, () => {
97+
storeJson(STORAGE_KEY_TEST_STATISTICS, this.statistics);
98+
});
99+
return result;
100+
}
101+
public async autoSaveToDevice() {
102+
const result = await this.mergedDeviceDataWithStorage();
103+
this.unsubscribeSaveToDevice = deepObserve(
104+
this.statistics,
105+
debounce(
106+
() => {
107+
writeFile(this.filePath, JSON.stringify(this.statistics));
108+
},
109+
3000,
110+
{ leading: true, trailing: true, maxWait: 5000 }
111+
)
112+
);
113+
return result;
114+
}
115+
public async changeStoragePlace(isSaveDataToDevice: boolean) {
116+
if (isSaveDataToDevice) {
117+
const result = await this.autoSaveToDevice();
118+
this.unsubscribeSaveToLocalStorage?.();
119+
this.resetStorageData();
120+
return result;
121+
}
122+
const result = await this.autoSaveToLocalStorage();
123+
this.unsubscribeSaveToDevice?.();
124+
this.resetDeviceData();
125+
return result;
126+
}
127+
public async clear() {
128+
await this.resetDeviceData();
129+
await this.resetStorageData();
130+
}
131+
}

0 commit comments

Comments
 (0)