Skip to content

Commit 553af55

Browse files
committed
refactor: replace BufferStream with createReadStream for file uploads and update thumbnail handling
1 parent 9938548 commit 553af55

6 files changed

Lines changed: 86 additions & 81 deletions

File tree

src/commands/upload-file.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { EncryptionVersion } from '@internxt/sdk/dist/drive/storage/types';
1111
import { ThumbnailService } from '../services/thumbnail.service';
1212
import { AuthService } from '../services/auth.service';
1313
import { UploadUtils } from '../utils/upload.utils';
14-
import { BufferStream } from '../utils/stream.utils';
1514

1615
export default class UploadFile extends Command {
1716
static readonly args = {};
@@ -81,14 +80,11 @@ export default class UploadFile extends Command {
8180
progressBar?.start(100, 0);
8281

8382
let fileId: string | undefined;
84-
let thumbnailStream: BufferStream | undefined;
8583
const fileSize = stats.size ?? 0;
8684

8785
if (fileSize > 0) {
8886
// Upload file to the Network
8987
const readStream = createReadStream(filePath);
90-
const preparedStreams = UploadUtils.prepareUploadStreams(readStream, fileType);
91-
thumbnailStream = preparedStreams.thumbnailStream;
9288

9389
const progressCallback = (progress: number) => {
9490
progressBar?.update(progress * 100 * 0.99);
@@ -97,7 +93,7 @@ export default class UploadFile extends Command {
9793
const abortable = new AbortController();
9894

9995
fileId = await networkFacade.uploadFile({
100-
from: preparedStreams.fileStream,
96+
from: readStream,
10197
size: fileSize,
10298
bucketId: bucket,
10399
progressCallback,
@@ -128,7 +124,7 @@ export default class UploadFile extends Command {
128124

129125
const thumbnailTimer = CLIUtils.timer();
130126
await ThumbnailService.instance.tryUploadThumbnail({
131-
bufferStream: thumbnailStream,
127+
input: filePath,
132128
fileType,
133129
bucket,
134130
fileUuid: createdDriveFile.uuid,

src/services/network/upload/upload-file.service.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import { dirname, extname } from 'node:path';
1111
import { ErrorUtils } from '../../../utils/errors.utils';
1212
import { stat } from 'node:fs/promises';
1313
import { EncryptionVersion } from '@internxt/sdk/dist/drive/storage/types';
14-
import { BufferStream } from '../../../utils/stream.utils';
1514
import { DriveFileItem } from '../../../types/drive.types';
1615
import { CLIUtils } from '../../../utils/cli.utils';
1716
import { ThumbnailService } from '../../thumbnail.service';
1817
import { FormatUtils } from '../../../utils/format.utils';
18+
import { createReadStream } from 'node:fs';
1919

2020
export class UploadFileService {
2121
static readonly instance = new UploadFileService();
@@ -84,7 +84,6 @@ export class UploadFileService {
8484
const fileType = extname(file.absolutePath).replaceAll('.', '');
8585

8686
let fileId: string | undefined;
87-
let thumbnailStream: BufferStream | undefined;
8887

8988
const timings = {
9089
networkUpload: 0,
@@ -93,16 +92,12 @@ export class UploadFileService {
9392
};
9493

9594
if (fileSize > 0) {
96-
const { fileStream, bufferStream } = ThumbnailService.instance.createFileStreamWithBuffer({
97-
path: file.absolutePath,
98-
fileType,
99-
});
95+
const readStream = createReadStream(file.absolutePath);
10096

10197
const uploadTimer = CLIUtils.timer();
102-
thumbnailStream = bufferStream;
10398

10499
fileId = await network.uploadFile({
105-
from: fileStream,
100+
from: readStream,
106101
size: fileSize,
107102
bucketId: bucket,
108103
progressCallback: () => {},
@@ -125,9 +120,9 @@ export class UploadFileService {
125120
timings.driveUpload = driveTimer.stop();
126121

127122
const thumbnailTimer = CLIUtils.timer();
128-
if (thumbnailStream && fileSize > 0) {
123+
if (fileSize > 0) {
129124
await ThumbnailService.instance.tryUploadThumbnail({
130-
bufferStream: thumbnailStream,
125+
input: file.absolutePath,
131126
fileType,
132127
bucket,
133128
fileUuid: createdDriveFile.uuid,

src/services/thumbnail.service.ts

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { Readable } from 'node:stream';
2-
import { createReadStream } from 'node:fs';
32
import { DriveFileService } from './drive/drive-file.service';
43
import { StorageTypes } from '@internxt/sdk/dist/drive';
54
import { NetworkFacade } from './network/network-facade.service';
65
import { ThumbnailConfig, ThumbnailUtils } from '../utils/thumbnail.utils';
7-
import { BufferStream } from '../utils/stream.utils';
86
import { ErrorUtils } from '../utils/errors.utils';
97
import { AsyncUtils } from '../utils/async.utils';
108

@@ -26,7 +24,7 @@ export class ThumbnailService {
2624
private static readonly MAX_THUMBNAIL_TIMEOUT = 30000;
2725

2826
public uploadThumbnail = async (
29-
fileContent: Buffer,
27+
input: string | Buffer,
3028
fileType: string,
3129
userBucket: string,
3230
file_id: string,
@@ -35,7 +33,7 @@ export class ThumbnailService {
3533
): Promise<StorageTypes.Thumbnail | undefined> => {
3634
let thumbnailBuffer: Buffer | undefined;
3735
if (ThumbnailUtils.isImageThumbnailable(fileType, fileSize)) {
38-
thumbnailBuffer = await this.getThumbnailFromImageBuffer(fileContent);
36+
thumbnailBuffer = await this.generateThumbnail(input);
3937
}
4038
if (thumbnailBuffer) {
4139
const size = thumbnailBuffer.length;
@@ -61,10 +59,10 @@ export class ThumbnailService {
6159
}
6260
};
6361

64-
private readonly getThumbnailFromImageBuffer = async (buffer: Buffer): Promise<Buffer | undefined> => {
62+
private readonly generateThumbnail = async (input: string | Buffer): Promise<Buffer | undefined> => {
6563
const sharp = await getSharp();
6664
if (sharp) {
67-
return sharp(buffer, { failOn: 'none' })
65+
return sharp(input, { failOn: 'none' })
6866
.resize({
6967
height: ThumbnailConfig.MaxHeight,
7068
width: ThumbnailConfig.MaxWidth,
@@ -78,25 +76,24 @@ export class ThumbnailService {
7876
};
7977

8078
public tryUploadThumbnail = async ({
81-
bufferStream,
79+
input,
8280
fileType,
8381
bucket,
8482
fileUuid,
8583
networkFacade,
8684
size,
8785
}: {
88-
bufferStream?: BufferStream;
86+
input?: string | Buffer;
8987
fileType: string;
9088
bucket: string;
9189
fileUuid: string;
9290
networkFacade: NetworkFacade;
9391
size: number;
9492
}) => {
9593
try {
96-
const thumbnailBuffer = bufferStream?.getBuffer();
97-
if (thumbnailBuffer && size > 0) {
94+
if (input && size > 0) {
9895
await AsyncUtils.withTimeout(
99-
ThumbnailService.instance.uploadThumbnail(thumbnailBuffer, fileType, bucket, fileUuid, networkFacade, size),
96+
ThumbnailService.instance.uploadThumbnail(input, fileType, bucket, fileUuid, networkFacade, size),
10097
ThumbnailService.MAX_THUMBNAIL_TIMEOUT,
10198
'Thumbnail upload timeout',
10299
);
@@ -105,25 +102,4 @@ export class ThumbnailService {
105102
ErrorUtils.report(error);
106103
}
107104
};
108-
109-
public createFileStreamWithBuffer = ({
110-
path,
111-
fileType,
112-
}: {
113-
path: string;
114-
fileType: string;
115-
}): {
116-
bufferStream?: BufferStream;
117-
fileStream: Readable;
118-
} => {
119-
const readable: Readable = createReadStream(path);
120-
if (ThumbnailUtils.isFileThumbnailable(fileType)) {
121-
const bufferStream = new BufferStream();
122-
return {
123-
bufferStream,
124-
fileStream: readable.pipe(bufferStream),
125-
};
126-
}
127-
return { fileStream: readable };
128-
};
129105
}

src/webdav/handlers/PUT.handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class PUTRequestHandler implements WebDavMethodHandler {
128128
const thumbnailTimer = CLIUtils.timer();
129129
await ThumbnailService.instance.tryUploadThumbnail({
130130
fileUuid: file.uuid,
131-
bufferStream: thumbnailStream,
131+
input: thumbnailStream?.getBuffer(),
132132
fileType,
133133
bucket,
134134
networkFacade,

test/services/network/upload/upload-file.service.test.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
createProgressFixtures,
1414
} from './upload.service.helpers';
1515
import { newFileItem } from '../../../fixtures/drive.fixture';
16-
import { ThumbnailUtils } from '../../../../src/utils/thumbnail.utils';
1716
import { ThumbnailService } from '../../../../src/services/thumbnail.service';
1817

1918
vi.mock('fs', () => ({
@@ -37,12 +36,7 @@ describe('Upload File Service', () => {
3736
vi.mocked(stat).mockResolvedValue(createMockStats(1024) as Awaited<ReturnType<typeof stat>>);
3837
vi.mocked(createReadStream).mockReturnValue(createMockReadStream() as ReturnType<typeof createReadStream>);
3938
vi.spyOn(ErrorUtils, 'isAlreadyExistsError').mockReturnValue(false);
40-
vi.spyOn(ThumbnailUtils, 'isFileThumbnailable').mockReturnValue(false);
4139
vi.spyOn(ThumbnailService.instance, 'tryUploadThumbnail').mockResolvedValue(undefined);
42-
vi.spyOn(ThumbnailService.instance, 'createFileStreamWithBuffer').mockReturnValue({
43-
fileStream: createMockReadStream() as ReturnType<typeof createReadStream>,
44-
bufferStream: undefined,
45-
});
4640
vi.spyOn(DriveFileService.instance, 'createFile').mockResolvedValue(mockFile);
4741
});
4842

@@ -300,14 +294,6 @@ describe('Upload File Service', () => {
300294
});
301295

302296
test('when a thumbnailable file is uploaded, then a thumbnail is generated', async () => {
303-
const mockBufferStream = { getBuffer: vi.fn() };
304-
vi.spyOn(ThumbnailService.instance, 'createFileStreamWithBuffer').mockReturnValue({
305-
fileStream: createMockReadStream() as ReturnType<typeof createReadStream>,
306-
bufferStream: mockBufferStream as unknown as ReturnType<
307-
typeof ThumbnailService.instance.createFileStreamWithBuffer
308-
>['bufferStream'],
309-
});
310-
311297
const file = createFileSystemNodeFixture({
312298
type: 'file',
313299
name: 'image.png',
@@ -325,7 +311,7 @@ describe('Upload File Service', () => {
325311
});
326312

327313
expect(ThumbnailService.instance.tryUploadThumbnail).toHaveBeenCalledWith({
328-
bufferStream: mockBufferStream,
314+
input: file.absolutePath,
329315
fileType: 'png',
330316
bucket,
331317
fileUuid: mockFile.uuid,
Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,80 @@
1-
import { describe, expect, test } from 'vitest';
2-
import { BufferStream } from '../../src/utils/stream.utils';
1+
import { afterEach, describe, expect, test, vi } from 'vitest';
32
import { ThumbnailService } from '../../src/services/thumbnail.service';
4-
import path from 'node:path';
5-
import { Readable } from 'node:stream';
3+
import { NetworkFacade } from '../../src/services/network/network-facade.service';
64

75
describe('Thumbnail Service tests', () => {
8-
const testFilePath = path.join(process.cwd(), 'test/fixtures/test-content.fixture.txt');
6+
const networkFacade = {} as NetworkFacade;
97

10-
describe('createFileStreamWithBuffer', () => {
11-
test('when the file has a supported image type, then a buffer stream and file stream are created', () => {
12-
const result = ThumbnailService.instance.createFileStreamWithBuffer({ path: testFilePath, fileType: 'png' });
8+
afterEach(() => {
9+
vi.restoreAllMocks();
10+
});
11+
12+
describe('tryUploadThumbnail', () => {
13+
test('when an input is provided and the size is greater than zero, then the thumbnail is uploaded', async () => {
14+
const uploadThumbnailSpy = vi.spyOn(ThumbnailService.instance, 'uploadThumbnail').mockResolvedValue(undefined);
15+
16+
await ThumbnailService.instance.tryUploadThumbnail({
17+
input: '/path/to/image.png',
18+
fileType: 'png',
19+
bucket: 'bucket-id',
20+
fileUuid: 'file-uuid',
21+
networkFacade,
22+
size: 1024,
23+
});
24+
25+
expect(uploadThumbnailSpy).toHaveBeenCalledWith(
26+
'/path/to/image.png',
27+
'png',
28+
'bucket-id',
29+
'file-uuid',
30+
networkFacade,
31+
1024,
32+
);
33+
});
34+
35+
test('when no input is provided, then no thumbnail is uploaded', async () => {
36+
const uploadThumbnailSpy = vi.spyOn(ThumbnailService.instance, 'uploadThumbnail').mockResolvedValue(undefined);
37+
38+
await ThumbnailService.instance.tryUploadThumbnail({
39+
input: undefined,
40+
fileType: 'png',
41+
bucket: 'bucket-id',
42+
fileUuid: 'file-uuid',
43+
networkFacade,
44+
size: 1024,
45+
});
46+
47+
expect(uploadThumbnailSpy).not.toHaveBeenCalled();
48+
});
49+
50+
test('when the size is zero, then no thumbnail is uploaded', async () => {
51+
const uploadThumbnailSpy = vi.spyOn(ThumbnailService.instance, 'uploadThumbnail').mockResolvedValue(undefined);
52+
53+
await ThumbnailService.instance.tryUploadThumbnail({
54+
input: '/path/to/image.png',
55+
fileType: 'png',
56+
bucket: 'bucket-id',
57+
fileUuid: 'file-uuid',
58+
networkFacade,
59+
size: 0,
60+
});
1361

14-
expect(result.bufferStream).toBeDefined();
15-
expect(result.bufferStream).toBeInstanceOf(BufferStream);
16-
expect(result.fileStream).toBeDefined();
17-
expect(result.fileStream).toBeInstanceOf(Readable);
62+
expect(uploadThumbnailSpy).not.toHaveBeenCalled();
1863
});
1964

20-
test('when the file has an unsupported type, then only a file stream is created without buffering', () => {
21-
const result = ThumbnailService.instance.createFileStreamWithBuffer({ path: testFilePath, fileType: 'txt' });
65+
test('when the thumbnail upload fails, then the error is swallowed', async () => {
66+
vi.spyOn(ThumbnailService.instance, 'uploadThumbnail').mockRejectedValue(new Error('upload failed'));
2267

23-
expect(result.bufferStream).toBeUndefined();
24-
expect(result.fileStream).toBeDefined();
25-
expect(result.fileStream).toBeInstanceOf(Readable);
68+
await expect(
69+
ThumbnailService.instance.tryUploadThumbnail({
70+
input: '/path/to/image.png',
71+
fileType: 'png',
72+
bucket: 'bucket-id',
73+
fileUuid: 'file-uuid',
74+
networkFacade,
75+
size: 1024,
76+
}),
77+
).resolves.toBeUndefined();
2678
});
2779
});
2880
});

0 commit comments

Comments
 (0)