1- import { existsSync , readFileSync , statSync , unlinkSync } from 'fs' ; // eslint-disable-line no-restricted-imports -- files being checked
1+ import { existsSync , readFileSync , statSync , unlinkSync , writeFileSync } from 'fs' ; // eslint-disable-line no-restricted-imports -- files being checked
22import { writeFile } from 'fs/promises' ;
33import { join } from 'path' ;
44import { Mutex } from 'async-mutex' ;
@@ -13,7 +13,7 @@ export class EncryptedFileStore implements DataStore {
1313 private readonly log : Logger ;
1414
1515 private readonly file : string ;
16- private content ? : Record < string , unknown > ;
16+ private content : Record < string , unknown > ;
1717 private readonly telemetry : ScopedTelemetry ;
1818 private readonly lock = new Mutex ( ) ;
1919
@@ -24,43 +24,36 @@ export class EncryptedFileStore implements DataStore {
2424 ) {
2525 this . log = LoggerFactory . getLogger ( `FileStore.${ name } ` ) ;
2626 this . file = join ( fileDbDir , `${ name } .enc` ) ;
27-
2827 this . telemetry = TelemetryService . instance . get ( `FileStore.${ name } ` ) ;
28+
29+ this . content = { } ;
30+ if ( existsSync ( this . file ) ) {
31+ try {
32+ const decrypted = decrypt ( this . KEY , readFileSync ( this . file ) ) ;
33+ this . content = JSON . parse ( decrypted ) as Record < string , unknown > ;
34+ } catch ( error ) {
35+ this . log . error ( error , 'Failed to decrypt file store, recreating store' ) ;
36+ this . telemetry . count ( 'filestore.recreate' , 1 ) ;
37+
38+ this . deleteStore ( ) ;
39+ writeFileSync ( this . file , encrypt ( this . KEY , JSON . stringify ( this . content ) ) ) ;
40+ }
41+ } else {
42+ writeFileSync ( this . file , encrypt ( this . KEY , JSON . stringify ( this . content ) ) ) ;
43+ }
2944 }
3045
3146 get < T > ( key : string ) : T | undefined {
3247 return this . telemetry . countExecution ( 'get' , ( ) => {
33- if ( this . content ) {
34- return this . content [ key ] as T | undefined ;
35- }
36-
37- if ( ! existsSync ( this . file ) ) {
38- return ;
39- }
40-
41- if ( this . lock . isLocked ( ) ) {
42- return this . content ?. [ key ] ;
43- }
44-
45- const decrypted = decrypt ( this . KEY , readFileSync ( this . file ) ) ;
46- this . content = JSON . parse ( decrypted ) as Record < string , unknown > ;
4748 return this . content [ key ] as T | undefined ;
4849 } ) ;
4950 }
5051
5152 put < T > ( key : string , value : T ) : Promise < boolean > {
5253 return this . lock . runExclusive ( ( ) =>
5354 this . telemetry . measureAsync ( 'put' , async ( ) => {
54- if ( ! this . content ) {
55- this . get ( key ) ;
56- }
57-
58- this . content = {
59- ...this . content ,
60- [ key ] : value ,
61- } ;
62- const encrypted = encrypt ( this . KEY , JSON . stringify ( this . content ) ) ;
63- await writeFile ( this . file , encrypted ) ;
55+ this . content [ key ] = value ;
56+ await this . save ( ) ;
6457 return true ;
6558 } ) ,
6659 ) ;
@@ -69,17 +62,12 @@ export class EncryptedFileStore implements DataStore {
6962 remove ( key : string ) : Promise < boolean > {
7063 return this . lock . runExclusive ( ( ) => {
7164 return this . telemetry . measureAsync ( 'remove' , async ( ) => {
72- if ( ! this . content ) {
73- this . get ( key ) ;
74- }
75-
76- if ( ! this . content || ! ( key in this . content ) ) {
65+ if ( ! ( key in this . content ) ) {
7766 return false ;
7867 }
7968
8069 delete this . content [ key ] ;
81- const encrypted = encrypt ( this . KEY , JSON . stringify ( this . content ) ) ;
82- await writeFile ( this . file , encrypted ) ;
70+ await this . save ( ) ;
8371 return true ;
8472 } ) ;
8573 } ) ;
@@ -88,35 +76,35 @@ export class EncryptedFileStore implements DataStore {
8876 clear ( ) : Promise < void > {
8977 return this . lock . runExclusive ( ( ) => {
9078 return this . telemetry . countExecutionAsync ( 'clear' , ( ) => {
91- if ( existsSync ( this . file ) ) {
92- unlinkSync ( this . file ) ;
93- }
94- this . content = undefined ;
79+ this . deleteStore ( ) ;
9580 return Promise . resolve ( ) ;
9681 } ) ;
9782 } ) ;
9883 }
9984
10085 keys ( limit : number ) : ReadonlyArray < string > {
10186 return this . telemetry . countExecution ( 'keys' , ( ) => {
102- if ( ! this . content ) {
103- this . get ( 'ANY_KEY' ) ;
104- }
105-
106- return Object . keys ( this . content ?? { } ) . slice ( 0 , limit ) ;
87+ return Object . keys ( this . content ) . slice ( 0 , limit ) ;
10788 } ) ;
10889 }
10990
11091 stats ( ) : FileStoreStats {
111- if ( ! this . content ) {
112- this . get ( 'ANY_KEY' ) ;
113- }
114-
11592 return {
116- entries : Object . keys ( this . content ?? { } ) . length ,
93+ entries : Object . keys ( this . content ) . length ,
11794 totalSize : existsSync ( this . file ) ? statSync ( this . file ) . size : 0 ,
11895 } ;
11996 }
97+
98+ private deleteStore ( ) {
99+ if ( existsSync ( this . file ) ) {
100+ unlinkSync ( this . file ) ;
101+ }
102+ this . content = { } ;
103+ }
104+
105+ private async save ( ) {
106+ await writeFile ( this . file , encrypt ( this . KEY , JSON . stringify ( this . content ) ) ) ;
107+ }
120108}
121109
122110export type FileStoreStats = {
0 commit comments