fix(files): filepicker circular dependency

This commit is contained in:
Karol Sójko
2022-08-04 17:32:49 +02:00
parent 696b82b9d3
commit 183f68c9c1
33 changed files with 64 additions and 64 deletions

View File

@@ -0,0 +1,70 @@
import { ByteChunker } from './ByteChunker'
const chunkOfSize = (size: number) => {
return new TextEncoder().encode('a'.repeat(size))
}
describe('byte chunker', () => {
it('should hold back small chunks until minimum size is met', async () => {
let receivedBytes = new Uint8Array()
let numChunks = 0
const chunker = new ByteChunker(100, async (bytes) => {
numChunks++
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
})
await chunker.addBytes(chunkOfSize(50), false)
await chunker.addBytes(chunkOfSize(50), false)
await chunker.addBytes(chunkOfSize(50), false)
await chunker.addBytes(chunkOfSize(50), true)
expect(numChunks).toEqual(2)
expect(receivedBytes.length).toEqual(200)
})
it('should send back big chunks immediately', async () => {
let receivedBytes = new Uint8Array()
let numChunks = 0
const chunker = new ByteChunker(100, async (bytes) => {
numChunks++
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
})
await chunker.addBytes(chunkOfSize(150), false)
await chunker.addBytes(chunkOfSize(150), false)
await chunker.addBytes(chunkOfSize(150), false)
await chunker.addBytes(chunkOfSize(50), true)
expect(numChunks).toEqual(4)
expect(receivedBytes.length).toEqual(500)
})
it('last chunk should be popped regardless of size', async () => {
let receivedBytes = new Uint8Array()
let numChunks = 0
const chunker = new ByteChunker(100, async (bytes) => {
numChunks++
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
})
await chunker.addBytes(chunkOfSize(50), false)
await chunker.addBytes(chunkOfSize(25), true)
expect(numChunks).toEqual(1)
expect(receivedBytes.length).toEqual(75)
})
it('single chunk should be popped immediately', async () => {
let receivedBytes = new Uint8Array()
let numChunks = 0
const chunker = new ByteChunker(100, async (bytes) => {
numChunks++
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
})
await chunker.addBytes(chunkOfSize(50), true)
expect(numChunks).toEqual(1)
expect(receivedBytes.length).toEqual(50)
})
})

View File

@@ -0,0 +1,35 @@
import { OnChunkCallback } from './OnChunkCallback'
export class ByteChunker {
public loggingEnabled = false
private bytes = new Uint8Array()
private index = 1
constructor(private minimumChunkSize: number, private onChunk: OnChunkCallback) {}
private log(...args: any[]): void {
if (!this.loggingEnabled) {
return
}
// eslint-disable-next-line no-console
console.log(args)
}
public async addBytes(bytes: Uint8Array, isLast: boolean): Promise<void> {
this.bytes = new Uint8Array([...this.bytes, ...bytes])
this.log(`Chunker adding ${bytes.length}, total size ${this.bytes.length}`)
if (this.bytes.length >= this.minimumChunkSize || isLast) {
await this.popBytes(isLast)
}
}
private async popBytes(isLast: boolean): Promise<void> {
const maxIndex = Math.max(this.minimumChunkSize, this.bytes.length)
const chunk = this.bytes.slice(0, maxIndex)
this.bytes = new Uint8Array([...this.bytes.slice(maxIndex)])
this.log(`Chunker popping ${chunk.length}, total size in queue ${this.bytes.length}`)
await this.onChunk(chunk, this.index++, isLast)
}
}

View File

@@ -0,0 +1 @@
export type OnChunkCallback = (chunk: Uint8Array, index: number, isLast: boolean) => Promise<void>

View File

@@ -0,0 +1,23 @@
import { OrderedByteChunker } from './OrderedByteChunker'
const chunkOfSize = (size: number) => {
return new TextEncoder().encode('a'.repeat(size))
}
describe('ordered byte chunker', () => {
it('should callback multiple times if added bytes matches multiple chunk sizes', async () => {
const chunkSizes = [10, 10, 10]
let receivedBytes = new Uint8Array()
let numCallbacks = 0
const chunker = new OrderedByteChunker(chunkSizes, async (bytes) => {
numCallbacks++
receivedBytes = new Uint8Array([...receivedBytes, ...bytes])
})
await chunker.addBytes(chunkOfSize(30))
expect(numCallbacks).toEqual(3)
expect(receivedBytes.length).toEqual(30)
})
})

View File

@@ -0,0 +1,40 @@
export class OrderedByteChunker {
private bytes = new Uint8Array()
private index = 1
private remainingChunks: number[] = []
constructor(
private chunkSizes: number[],
private onChunk: (chunk: Uint8Array, index: number, isLast: boolean) => Promise<void>,
) {
this.remainingChunks = chunkSizes.slice()
}
private needsPop(): boolean {
return this.remainingChunks.length > 0 && this.bytes.length >= this.remainingChunks[0]
}
public async addBytes(bytes: Uint8Array): Promise<void> {
this.bytes = new Uint8Array([...this.bytes, ...bytes])
if (this.needsPop()) {
await this.popBytes()
}
}
private async popBytes(): Promise<void> {
const readUntil = this.remainingChunks[0]
const chunk = this.bytes.slice(0, readUntil)
this.bytes = new Uint8Array([...this.bytes.slice(readUntil)])
this.remainingChunks.shift()
await this.onChunk(chunk, this.index++, this.index === this.chunkSizes.length - 1)
if (this.needsPop()) {
await this.popBytes()
}
}
}

View File

@@ -4,8 +4,9 @@ import { FileDecryptor } from '../UseCase/FileDecryptor'
import { FileDownloadProgress } from '../Types/FileDownloadProgress'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { FileContent } from '@standardnotes/models'
import { DecryptedBytes, EncryptedBytes } from '@standardnotes/filepicker'
import { FilesApiInterface } from '../Api/FilesApiInterface'
import { DecryptedBytes } from '../Types/DecryptedBytes'
import { EncryptedBytes } from '../Types/EncryptedBytes'
export type DownloadAndDecryptResult = { success: boolean; error?: ClientDisplayableError; aborted?: boolean }

View File

@@ -1,9 +1,9 @@
import { FileContent } from '@standardnotes/models'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { OrderedByteChunker } from '@standardnotes/filepicker'
import { FileDecryptor } from '../UseCase/FileDecryptor'
import { FileSystemApi } from '../Api/FileSystemApi'
import { FileHandleRead } from '../Api/FileHandleRead'
import { OrderedByteChunker } from '../Chunker/OrderedByteChunker'
export async function readAndDecryptBackupFile(
fileHandle: FileHandleRead,

View File

@@ -0,0 +1,3 @@
export type DecryptedBytes = {
decryptedBytes: Uint8Array
}

View File

@@ -0,0 +1,3 @@
export type EncryptedBytes = {
encryptedBytes: Uint8Array
}

View File

@@ -5,6 +5,9 @@ export * from './Api/FileSystemApi'
export * from './Api/FileSystemNoSelection'
export * from './Api/FileSystemResult'
export * from './Api/FilesApiInterface'
export * from './Chunker/ByteChunker'
export * from './Chunker/OnChunkCallback'
export * from './Chunker/OrderedByteChunker'
export * from './Device/FileBackupMetadataFile'
export * from './Device/FileBackupsConstantsV1'
export * from './Device/FileBackupsDevice'
@@ -17,6 +20,8 @@ export * from './UseCase/FileDecryptor'
export * from './UseCase/FileUploader'
export * from './UseCase/FileEncryptor'
export * from './UseCase/FileDownloader'
export * from './Types/DecryptedBytes'
export * from './Types/EncryptedBytes'
export * from './Types/FileDownloadProgress'
export * from './Types/FileUploadProgress'
export * from './Types/FileUploadResult'