1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | import { Storage, StoreItems, StoreItem } from 'botbuilder';
|
13 | import * as path from 'path';
|
14 | import * as fs from 'async-file';
|
15 | import * as file from 'fs';
|
16 | import * as filenamify from 'filenamify';
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | export class FileStorage implements Storage {
|
33 | static nextTag = 0;
|
34 | private pEnsureFolder: Promise<void> | undefined
|
35 | protected readonly path: string
|
36 | |
37 |
|
38 |
|
39 |
|
40 | public constructor(filePath: string) {
|
41 | this.path = filePath
|
42 | }
|
43 |
|
44 | public async read(keys: string[]): Promise<StoreItems> {
|
45 | await this.ensureFolder()
|
46 | const data: StoreItems = {}
|
47 | const promises: Promise<any>[] = keys.map(key => {
|
48 | const filePath = this.getFilePath(key)
|
49 | return parseFile(filePath)
|
50 | .then((obj) => {
|
51 | if (obj) {
|
52 | data[key] = obj
|
53 | }
|
54 | })
|
55 | })
|
56 |
|
57 | await Promise.all(promises)
|
58 | return data
|
59 | }
|
60 |
|
61 | public async write(changes: StoreItems): Promise<void> {
|
62 | await this.ensureFolder()
|
63 | const promises: Promise<void>[] = Object.keys(changes).map(key => {
|
64 | const filePath = this.getFilePath(key)
|
65 | return fs.exists(filePath)
|
66 | .then((exists) => {
|
67 | const newObj: StoreItem = { ...changes[key] }
|
68 | newObj.eTag = (parseInt(newObj.eTag || '0', 10) + 1).toString()
|
69 | return fs.writeTextFile(filePath, JSON.stringify(newObj))
|
70 | })
|
71 | })
|
72 |
|
73 | await Promise.all(promises)
|
74 | }
|
75 |
|
76 | public async delete(keys: string[]): Promise<void> {
|
77 | await this.ensureFolder()
|
78 | const promises = keys.map(key => {
|
79 | const filePath = this.getFilePath(key)
|
80 | return fs.exists(filePath)
|
81 | .then((exists) => {
|
82 | if (exists) {
|
83 | file.unlinkSync(filePath)
|
84 | }
|
85 | })
|
86 | })
|
87 |
|
88 | await Promise.all(promises)
|
89 | }
|
90 |
|
91 | private async ensureFolder(): Promise<void> {
|
92 | if (this.pEnsureFolder) {
|
93 | return this.pEnsureFolder;
|
94 | }
|
95 |
|
96 | const exists = await fs.exists(this.path)
|
97 | if (!exists) {
|
98 | this.pEnsureFolder = fs.mkdirp(this.path)
|
99 | }
|
100 | }
|
101 |
|
102 | private getFileName(key: string): string {
|
103 | return filenamify(key);
|
104 | }
|
105 |
|
106 | private getFilePath(key: string): string {
|
107 | return path.join(this.path, this.getFileName(key));
|
108 | }
|
109 | }
|
110 |
|
111 | async function parseFile(filePath: string): Promise<Object | undefined> {
|
112 | try {
|
113 | const exists = await fs.exists(filePath)
|
114 | const data = await (exists
|
115 | ? fs.readTextFile(filePath)
|
116 | : Promise.resolve(undefined))
|
117 | try {
|
118 | if (data) {
|
119 | return JSON.parse(data)
|
120 | }
|
121 | }
|
122 | catch (err) {
|
123 | console.warn(`FileStorage: error parsing "${filePath}": ${err.toString()}`)
|
124 | }
|
125 |
|
126 | return undefined
|
127 | }
|
128 | catch (error) {
|
129 |
|
130 | if (error.code != "ENOENT") {
|
131 | console.warn(`FileStorage: error reading "${filePath}": ${error.toString()}`)
|
132 | }
|
133 | return undefined
|
134 | }
|
135 | }
|