-
Notifications
You must be signed in to change notification settings - Fork 336
Add support for fingerprint generation in FileSource classes #774
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
4ad157f
c22f67f
216e0a0
021960d
4bb5278
906105c
f983c63
18fe753
341742f
efda4bf
cfc3111
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import type { Readable } from 'node:stream' | ||
| import type { FileSource } from '../../options.js' | ||
| import { Readable, Transform } from 'node:stream' | ||
| import type { FileSource, UploadOptions } from '../../options.js' | ||
|
|
||
| /** | ||
| * readChunk reads a chunk with the given size from the given | ||
|
|
@@ -74,9 +74,16 @@ export class NodeStreamFileSource implements FileSource { | |
| }) | ||
| } | ||
|
|
||
| // TODO: Implement fingerprint calculation for NodeStreamFileSource. | ||
| // This should likely involve reading the stream and creating a hash/checksum | ||
| // while preserving the stream's content for later use. | ||
| fingerprint(options: UploadOptions): Promise<string | null> { | ||
| throw new Error("fingerprint not implemented for NodeStreamFileSource") | ||
|
||
| } | ||
|
|
||
| async slice(start: number, end: number) { | ||
| // Fail fast if the caller requests a proportion of the data which is not | ||
| // available any more. | ||
| // available anymore. | ||
| if (start < this._bufPos) { | ||
| throw new Error('cannot slice from position which we already seeked away') | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { type ReadStream, createReadStream, promises as fsPromises } from 'node:fs' | ||
| import type { FileSource, PathReference } from '../../options.js' | ||
| import { createReadStream, promises as fsPromises, type ReadStream } from 'node:fs' | ||
| import type { FileSource, PathReference, UploadOptions } from '../../options.js' | ||
|
|
||
| export async function getFileSourceFromPath(file: PathReference): Promise<PathFileSource> { | ||
| const path = file.path.toString() | ||
|
|
@@ -34,6 +34,14 @@ export class PathFileSource implements FileSource { | |
| this.size = size | ||
| } | ||
|
|
||
| fingerprint(options: UploadOptions): Promise<string | null> { | ||
| if (typeof options.fingerprint === 'function') { | ||
| return Promise.resolve(options.fingerprint(this._file, options)) | ||
| } | ||
|
||
|
|
||
| return Promise.resolve([this._file, this.size, this._path, options.endpoint, options.projectId].join('-')) | ||
| } | ||
|
|
||
| slice(start: number, end: number) { | ||
| // TODO: Does this create multiple file descriptors? Can we reduce this by | ||
| // using a file handle instead? | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ export interface ReactNativeFile { | |
| /** | ||
| * PathReference is a reference to a file on disk. Currently, it's only supported | ||
| * in Node.js. It can be supplied as a normal object or as an instance of `fs.ReadStream`, | ||
| * which also statisfies this interface. | ||
| * which also satisfies this interface. | ||
| * | ||
| * Optionally, a start and/or end position can be defined to define a range of bytes from | ||
| * the file that should be uploaded instead of the entire file. Both start and end are | ||
|
|
@@ -51,6 +51,8 @@ export type UploadInput = | |
| export interface UploadOptions { | ||
| endpoint?: string | ||
|
|
||
| projectId?: string | ||
|
||
|
|
||
| uploadUrl?: string | ||
| metadata: { [key: string]: string } | ||
| metadataForPartialUploads: UploadOptions['metadata'] | ||
|
|
@@ -117,6 +119,7 @@ export interface FileSource { | |
| size: number | null | ||
| slice(start: number, end: number): Promise<SliceResult> | ||
| close(): void | ||
| fingerprint(options: UploadOptions): Promise<string | null> | ||
| } | ||
|
|
||
| // TODO: Allow Web Streams' ReadableStream as well | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { FileSource, ReactNativeFile, SliceResult, UploadOptions } from "../options.js"; | ||
|
|
||
| export class ReactNativeFileSource implements FileSource { | ||
| private readonly _file: ReactNativeFile | ||
| size: number; | ||
|
|
||
| constructor(file: ReactNativeFile) { | ||
| this._file = file | ||
| this.size = Number(file.size) | ||
| } | ||
|
|
||
| fingerprint(options: UploadOptions): Promise<string | null> { | ||
| if (typeof options.fingerprint === 'function') { | ||
| return Promise.resolve(options.fingerprint(this._file, options)) | ||
| } | ||
|
|
||
| return Promise.resolve(this.reactNativeFingerprint(this._file, options)) | ||
| } | ||
|
|
||
| // TODO: Implement the slice method to read file content from start to end positions | ||
| // The implementation should: | ||
| // 1. Read the file content from the ReactNative file URI | ||
| // 2. Return the sliced content as value | ||
| // 3. Calculate proper size and done values | ||
| async slice(start: number, end: number): Promise<SliceResult> { | ||
| let value = null, size = null, done = true; | ||
|
|
||
| return { value, size, done } | ||
| } | ||
|
|
||
| close(): void { | ||
| // Nothing to do here since we don't need to release any resources. | ||
| } | ||
|
|
||
| private reactNativeFingerprint(file: ReactNativeFile, options: UploadOptions): string { | ||
| const exifHash = file.exif ? this.hashCode(JSON.stringify(file.exif)) : 'noexif' | ||
| return ['tus-rn', file.name || 'noname', file.size || 'nosize', exifHash, options.endpoint].join( | ||
| '/', | ||
| ) | ||
| } | ||
|
|
||
| private hashCode(str: string): number { | ||
| // from https://stackoverflow.com/a/8831937/151666 | ||
| let hash = 0 | ||
| if (str.length === 0) { | ||
| return hash | ||
| } | ||
| for (let i = 0; i < str.length; i++) { | ||
| const char = str.charCodeAt(i) | ||
| hash = (hash << 5) - hash + char | ||
| hash &= hash // Convert to 32bit integer | ||
| } | ||
| return hash | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import type { FileSource, SliceResult } from '../options.js' | ||
| import type {FileSource, SliceResult, UploadOptions} from '../options.js' | ||
|
|
||
| function len(blobOrArray: WebStreamFileSource['_buffer']): number { | ||
| if (blobOrArray === undefined) return 0 | ||
|
|
@@ -28,7 +28,7 @@ function concat<T extends WebStreamFileSource['_buffer']>(a: T, b: T): T { | |
| */ | ||
| // TODO: Can we share code with NodeStreamFileSource? | ||
| export class WebStreamFileSource implements FileSource { | ||
| private _reader: ReadableStreamDefaultReader<Uint8Array> | ||
| private readonly _reader: ReadableStreamDefaultReader<Uint8Array> | ||
|
|
||
| private _buffer: Blob | Uint8Array | undefined | ||
|
|
||
|
|
@@ -39,6 +39,8 @@ export class WebStreamFileSource implements FileSource { | |
|
|
||
| private _done = false | ||
|
|
||
| private _stream: ReadableStream | ||
|
||
|
|
||
| // Setting the size to null indicates that we have no calculation available | ||
| // for how much data this stream will emit requiring the user to specify | ||
| // it manually (see the `uploadSize` option). | ||
|
|
@@ -50,10 +52,14 @@ export class WebStreamFileSource implements FileSource { | |
| 'Readable stream is already locked to reader. tus-js-client cannot obtain a new reader.', | ||
| ) | ||
| } | ||
|
|
||
| this._stream = stream | ||
| this._reader = stream.getReader() | ||
| } | ||
|
|
||
| fingerprint(options: UploadOptions): Promise<string | null> { | ||
| return Promise.resolve(null); | ||
| } | ||
|
|
||
| async slice(start: number, end: number): Promise<SliceResult> { | ||
| if (start < this._bufferOffset) { | ||
| throw new Error("Requested data is before the reader's current offset") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This entire file can probably be removed now, can't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file has been removed.