fix(files): filepicker circular dependency
This commit is contained in:
70
packages/files/src/Domain/Chunker/ByteChunker.spec.ts
Normal file
70
packages/files/src/Domain/Chunker/ByteChunker.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
35
packages/files/src/Domain/Chunker/ByteChunker.ts
Normal file
35
packages/files/src/Domain/Chunker/ByteChunker.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
1
packages/files/src/Domain/Chunker/OnChunkCallback.ts
Normal file
1
packages/files/src/Domain/Chunker/OnChunkCallback.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type OnChunkCallback = (chunk: Uint8Array, index: number, isLast: boolean) => Promise<void>
|
||||
23
packages/files/src/Domain/Chunker/OrderedByteChunker.spec.ts
Normal file
23
packages/files/src/Domain/Chunker/OrderedByteChunker.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
40
packages/files/src/Domain/Chunker/OrderedByteChunker.ts
Normal file
40
packages/files/src/Domain/Chunker/OrderedByteChunker.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
3
packages/files/src/Domain/Types/DecryptedBytes.ts
Normal file
3
packages/files/src/Domain/Types/DecryptedBytes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type DecryptedBytes = {
|
||||
decryptedBytes: Uint8Array
|
||||
}
|
||||
3
packages/files/src/Domain/Types/EncryptedBytes.ts
Normal file
3
packages/files/src/Domain/Types/EncryptedBytes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type EncryptedBytes = {
|
||||
encryptedBytes: Uint8Array
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user