@@ -42,9 +42,11 @@ export default class GpgPlugin extends Plugin {
42
42
private passphraseRequestPromise : Promise < string | null > | null = null ;
43
43
44
44
private originalAdapterReadFunction : ( normalizedPath : string ) => Promise < string > ;
45
+ private originalAdapterReadBinaryFunction : ( normalizedPath : string ) => Promise < ArrayBuffer > ;
45
46
private originalAdapterWriteFunction : ( normalizedPath : string , data : string , options ?: DataWriteOptions ) => Promise < void >
46
47
private originalAdapterProcessFunction : ( normalizedPath : string , fn : ( data : string ) => string , options ?: DataWriteOptions ) => Promise < string > ;
47
48
private hookedAdapterReadRef : ( normalizedPath : string ) => Promise < string > ;
49
+ private hookedAdapterReadBinaryRef : ( normalizedPath : string ) => Promise < ArrayBuffer > ;
48
50
private hookedAdapterWriteRef : ( normalizedPath : string , data : string , options ?: DataWriteOptions ) => Promise < void > ;
49
51
private hookedAdapterProcessRef : ( normalizedPath : string , fn : ( data : string ) => string , options ?: DataWriteOptions ) => Promise < string > ;
50
52
@@ -126,6 +128,7 @@ export default class GpgPlugin extends Plugin {
126
128
// save the original Obsidiane functions to call them
127
129
// and in case of plugin unload, we restore them
128
130
this . originalAdapterReadFunction = this . app . vault . adapter . read ;
131
+ this . originalAdapterReadBinaryFunction = this . app . vault . adapter . readBinary ;
129
132
this . originalAdapterWriteFunction = this . app . vault . adapter . write ;
130
133
this . originalAdapterProcessFunction = this . app . vault . adapter . process ;
131
134
this . originalVaultCachedReadFunction = this . app . vault . cachedRead ;
@@ -141,12 +144,14 @@ export default class GpgPlugin extends Plugin {
141
144
// by third-party plugins as this could lead to data-loss when
142
145
// gpgCrypt plugin gets unloaded.
143
146
this . hookedAdapterReadRef = this . hookedAdapterRead . bind ( this ) ;
147
+ this . hookedAdapterReadBinaryRef = this . hookedAdapterReadBinary . bind ( this ) ;
144
148
this . hookedAdapterWriteRef = this . hookedAdapterWrite . bind ( this ) ;
145
149
this . hookedAdapterProcessRef = this . hookedAdapterProcess . bind ( this ) ;
146
150
this . hookedVaultCachedReadRef = this . hookedVaultCachedRead . bind ( this ) ;
147
151
this . hookedFileRecoveryOnFileChangeRef = this . hookedFileRecoveryOnFileChange . bind ( this ) ;
148
152
this . hookedFileRecoveryForceAddRef = this . hookedFileRecoveryForceAdd . bind ( this ) ;
149
153
this . app . vault . adapter . read = this . hookedAdapterReadRef ;
154
+ this . app . vault . adapter . readBinary = this . hookedAdapterReadBinaryRef ;
150
155
this . app . vault . adapter . write = this . hookedAdapterWriteRef ;
151
156
this . app . vault . adapter . process = this . hookedAdapterProcessRef ;
152
157
this . app . vault . cachedRead = this . hookedVaultCachedReadRef ;
@@ -213,6 +218,7 @@ export default class GpgPlugin extends Plugin {
213
218
//@ts -ignore
214
219
if (
215
220
this . app . vault . adapter . read != this . hookedAdapterReadRef ||
221
+ this . app . vault . adapter . readBinary != this . hookedAdapterReadBinaryRef ||
216
222
this . app . vault . adapter . write != this . hookedAdapterWriteRef ||
217
223
this . app . vault . adapter . process != this . hookedAdapterProcessRef ||
218
224
this . app . vault . cachedRead != this . hookedVaultCachedReadRef ||
@@ -230,6 +236,7 @@ export default class GpgPlugin extends Plugin {
230
236
231
237
// restore original Obsidian read/write functions
232
238
this . app . vault . adapter . read = this . originalAdapterReadFunction ;
239
+ this . app . vault . adapter . readBinary = this . originalAdapterReadBinaryFunction ;
233
240
this . app . vault . adapter . write = this . originalAdapterWriteFunction ;
234
241
this . app . vault . adapter . process = this . originalAdapterProcessFunction ;
235
242
this . app . vault . cachedRead = this . originalVaultCachedReadFunction ;
@@ -302,6 +309,58 @@ export default class GpgPlugin extends Plugin {
302
309
return this . decryptionCache . get ( content ) ! ;
303
310
}
304
311
312
+ // Gets executed when Obsidian reads a binary file
313
+ // TODO: deduplicate cpde with hookedAdapterRead function
314
+ private async hookedAdapterReadBinary ( normalizedPath : string ) : Promise < ArrayBuffer > {
315
+ _log ( `Hooked Adapter - readBinary (${ normalizedPath } )` ) ;
316
+
317
+ const contentArray = await this . originalReadBinary ( normalizedPath ) ;
318
+ const content = new TextDecoder ( ) . decode ( new Uint8Array ( contentArray ) ) ;
319
+ const isEncrypted = await this . gpgNative . isEncrypted ( content ) ;
320
+
321
+ // in case the file status is already marked as encrypted, we don't set it to plaintext
322
+ // so we get a warning in case of the next write
323
+ if ( ! this . encryptedFileStatus . has ( normalizedPath ) || this . encryptedFileStatus . get ( normalizedPath ) !== true ) {
324
+ this . encryptedFileStatus . set ( normalizedPath , isEncrypted ) ;
325
+ }
326
+
327
+ if ( ! isEncrypted || ! this . settings . compatibilityMode ) {
328
+ return contentArray ;
329
+ }
330
+
331
+ // As Obsidian is doing multiple read calls for one note opening, it doesnt directly output
332
+ // any exceptions anymore to the user. With this, gpgCrypt has to output any errors to the
333
+ // user over Notices. To avoid duplicated Notices for the same error, the complete
334
+ // note decryption part is now taking place in two promises: one external promise which is getting
335
+ // cached for multiple calls for the same note and to throw an error on rejection
336
+ // and an internal promise, to show the Notice only once.
337
+
338
+ // If we already have a request in flight (with the same encrypted text), share it
339
+ if ( this . decryptionCache . has ( content ) ) {
340
+ return new TextEncoder ( ) . encode ( await this . decryptionCache . get ( content ) ! ) ;
341
+ }
342
+
343
+ this . decryptionCache . set ( content , new Promise ( async ( resolve , reject ) => {
344
+ let errorOccurred = false ;
345
+ try {
346
+ //await new Promise(res => setTimeout(res, 10000))
347
+ resolve ( await this . decrypt ( normalizedPath , content ) ) ;
348
+ } catch ( error ) {
349
+ errorOccurred = true ;
350
+ reject ( error )
351
+ } finally {
352
+ // Reset for the next time we need an external decrypt.
353
+ // If cache option is set and no error occured, the entry is kept for faster note reopening
354
+ if ( errorOccurred || ! this . settings . backendWrapper . cache ) {
355
+ // In some cases, the promise is executed too fast so it is getting cached for at least 500ms.
356
+ setTimeout ( ( ) => { _log ( `Delete decryption cache for ${ normalizedPath } ` ) ; this . decryptionCache . delete ( content ) ; } , 500 ) ;
357
+ }
358
+ }
359
+ } ) ) ;
360
+
361
+ return new TextEncoder ( ) . encode ( await this . decryptionCache . get ( content ) ! ) . buffer ;
362
+ }
363
+
305
364
// Gets executed when Obsidian writes a file
306
365
private async hookedAdapterWrite ( normalizedPath : string , data : string , options ?: DataWriteOptions | undefined ) : Promise < void > {
307
366
_log ( `Hooked Adapter - write (${ normalizedPath } )` ) ;
@@ -447,6 +506,10 @@ export default class GpgPlugin extends Plugin {
447
506
return this . originalAdapterReadFunction . call ( this . app . vault . adapter , normalizedPath ) ;
448
507
}
449
508
509
+ async originalReadBinary ( normalizedPath : string ) {
510
+ return this . originalAdapterReadBinaryFunction . call ( this . app . vault . adapter , normalizedPath ) ;
511
+ }
512
+
450
513
async originalWrite ( normalizedPath : string , data : string , options ?: DataWriteOptions | undefined ) {
451
514
return this . originalAdapterWriteFunction . call ( this . app . vault . adapter , normalizedPath , data , options ) ;
452
515
}
@@ -806,6 +869,8 @@ export default class GpgPlugin extends Plugin {
806
869
807
870
fileRecovery : FileRecovery . ENCRYPTED ,
808
871
872
+ compatibilityMode : false ,
873
+
809
874
backend : Backend . NATIVE ,
810
875
811
876
backendNative : {
0 commit comments