change the way file types are handled

This commit is contained in:
rubikscraft 2022-08-27 14:24:26 +02:00
parent ab600c20b7
commit 0a81b3c25d
No known key found for this signature in database
GPG key ID: 1463EBE9200A5CD4
27 changed files with 405 additions and 375 deletions

View file

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.enum';
import { ImageEntryVariant } from 'picsur-shared/dist/dto/image-entry-variant.enum';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
import { LessThan, Repository } from 'typeorm';
import { EImageDerivativeBackend } from '../../models/entities/image-derivative.entity';
@ -20,19 +20,19 @@ export class ImageFileDBService {
public async setFile(
imageId: string,
type: ImageFileType,
variant: ImageEntryVariant,
file: Buffer,
mime: string,
filetype: string,
): AsyncFailable<true> {
const imageFile = new EImageFileBackend();
imageFile.image_id = imageId;
imageFile.type = type;
imageFile.mime = mime;
imageFile.variant = variant;
imageFile.filetype = filetype;
imageFile.data = file;
try {
await this.imageFileRepo.upsert(imageFile, {
conflictPaths: ['image_id', 'type'],
conflictPaths: ['image_id', 'variant'],
});
} catch (e) {
return Fail(FT.Database, e);
@ -43,11 +43,11 @@ export class ImageFileDBService {
public async getFile(
imageId: string,
type: ImageFileType,
variant: ImageEntryVariant,
): AsyncFailable<EImageFileBackend> {
try {
const found = await this.imageFileRepo.findOne({
where: { image_id: imageId ?? '', type: type ?? '' },
where: { image_id: imageId ?? '', variant: variant ?? '' },
});
if (!found) return Fail(FT.NotFound, 'Image not found');
@ -58,20 +58,20 @@ export class ImageFileDBService {
}
// This is useful because you dont have to pull the whole image file
public async getFileMimes(
public async getFileTypes(
imageId: string,
): AsyncFailable<{ [key in ImageFileType]?: string }> {
): AsyncFailable<{ [key in ImageEntryVariant]?: string }> {
try {
const found = await this.imageFileRepo.find({
where: { image_id: imageId },
select: ['type', 'mime'],
select: ['variant', 'filetype'],
});
if (!found) return Fail(FT.NotFound, 'Image not found');
const result: { [key in ImageFileType]?: string } = {};
const result: { [key in ImageEntryVariant]?: string } = {};
for (const file of found) {
result[file.type] = file.mime;
result[file.variant] = file.filetype;
}
return result;
@ -83,13 +83,13 @@ export class ImageFileDBService {
public async addDerivative(
imageId: string,
key: string,
mime: string,
filetype: string,
file: Buffer,
): AsyncFailable<EImageDerivativeBackend> {
const imageDerivative = new EImageDerivativeBackend();
imageDerivative.image_id = imageId;
imageDerivative.key = key;
imageDerivative.mime = mime;
imageDerivative.filetype = filetype;
imageDerivative.data = file;
imageDerivative.last_read = new Date();

View file

@ -1,9 +1,6 @@
import {
ArgumentMetadata, Injectable,
PipeTransform
} from '@nestjs/common';
import { Ext2Mime } from 'picsur-shared/dist/dto/mimes.dto';
import { Fail, FT } from 'picsur-shared/dist/types';
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { Ext2FileType } from 'picsur-shared/dist/dto/mimes.dto';
import { Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
import { ImageFullId } from '../../models/constants/image-full-id.const';
@ -16,19 +13,19 @@ export class ImageFullIdPipe implements PipeTransform<string, ImageFullId> {
if (!UUIDRegex.test(id))
throw Fail(FT.UsrValidation, 'Invalid image identifier');
const mime = Ext2Mime(ext);
const filetype = Ext2FileType(ext);
if (mime === undefined)
if (HasFailed(filetype))
throw Fail(FT.UsrValidation, 'Invalid image identifier');
return { type: 'normal', id, ext, mime };
return { variant: 'normal', id, ext, filetype };
} else if (split.length === 1) {
const [id] = split;
if (!UUIDRegex.test(id))
throw Fail(FT.UsrValidation, 'Invalid image identifier');
return { type: 'original', id, ext: null, mime: null };
return { variant: 'original', id, ext: null, filetype: null };
} else {
throw Fail(FT.UsrValidation, 'Invalid image identifier');
}

View file

@ -27,7 +27,7 @@ async function bootstrap() {
AppModule,
fastifyAdapter,
{
bufferLogs: true,
bufferLogs: false,
},
);

View file

@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common';
import ms from 'ms';
import { ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto';
import {
FullMime,
SupportedMimeCategory
FileType,
SupportedFileTypeCategory
} from 'picsur-shared/dist/dto/mimes.dto';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
@ -17,28 +17,29 @@ export class ImageConverterService {
public async convert(
image: Buffer,
sourcemime: FullMime,
targetmime: FullMime,
sourceFiletype: FileType,
targetFiletype: FileType,
options: ImageRequestParams,
): AsyncFailable<ImageResult> {
if (sourcemime.type !== targetmime.type) {
if (sourceFiletype.category !== sourceFiletype.category) {
return Fail(
FT.Impossible,
"Can't convert from animated to still or vice versa",
);
}
if (sourcemime.mime === targetmime.mime) {
if (sourceFiletype.identifier === targetFiletype.identifier) {
return {
mime: targetmime.mime,
filetype: targetFiletype.identifier,
image,
};
}
if (targetmime.type === SupportedMimeCategory.Image) {
return this.convertStill(image, sourcemime, targetmime, options);
} else if (targetmime.type === SupportedMimeCategory.Animation) {
return this.convertAnimation(image, targetmime, options);
if (targetFiletype.category === SupportedFileTypeCategory.Image) {
return this.convertStill(image, sourceFiletype, targetFiletype, options);
} else if (targetFiletype.category === SupportedFileTypeCategory.Animation) {
return this.convertStill(image, sourceFiletype, targetFiletype, options);
//return this.convertAnimation(image, targetmime, options);
} else {
return Fail(FT.SysValidation, 'Unsupported mime type');
}
@ -46,8 +47,8 @@ export class ImageConverterService {
private async convertStill(
image: Buffer,
sourcemime: FullMime,
targetmime: FullMime,
sourceFiletype: FileType,
targetFiletype: FileType,
options: ImageRequestParams,
): AsyncFailable<ImageResult> {
const [memLimit, timeLimit] = await Promise.all([
@ -60,7 +61,7 @@ export class ImageConverterService {
const timeLimitMS = ms(timeLimit);
const sharpWrapper = new SharpWrapper(timeLimitMS, memLimit);
const hasStarted = await sharpWrapper.start(image, sourcemime);
const hasStarted = await sharpWrapper.start(image, sourceFiletype);
if (HasFailed(hasStarted)) return hasStarted;
// Do modifications
@ -103,24 +104,24 @@ export class ImageConverterService {
}
// Export
const result = await sharpWrapper.finish(targetmime, options);
const result = await sharpWrapper.finish(targetFiletype, options);
if (HasFailed(result)) return result;
return {
image: result.data,
mime: targetmime.mime,
filetype: targetFiletype.identifier,
};
}
private async convertAnimation(
image: Buffer,
targetmime: FullMime,
targetFiletype: FileType,
options: ImageRequestParams,
): AsyncFailable<ImageResult> {
// Apng and gif are stored as is for now
return {
image: image,
mime: targetmime.mime,
filetype: targetFiletype.identifier,
};
}
}

View file

@ -1,24 +1,27 @@
import { Injectable } from '@nestjs/common';
import {
FullMime,
ImageMime,
SupportedMimeCategory
FileType,
ImageFileType,
SupportedFileTypeCategory
} from 'picsur-shared/dist/dto/mimes.dto';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
import { QOIColorSpace, QOIencode } from 'qoi-img';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { ParseFileType } from 'picsur-shared/dist/util/parse-mime';
import { ImageConverterService } from './image-converter.service';
import { ImageResult } from './imageresult';
import { UniversalSharp } from './universal-sharp';
@Injectable()
export class ImageProcessorService {
constructor(private readonly imageConverter: ImageConverterService) {}
public async process(
image: Buffer,
mime: FullMime,
filetype: FileType,
): AsyncFailable<ImageResult> {
if (mime.type === SupportedMimeCategory.Image) {
return await this.processStill(image, mime);
} else if (mime.type === SupportedMimeCategory.Animation) {
return await this.processAnimation(image, mime);
if (filetype.category === SupportedFileTypeCategory.Image) {
return await this.processStill(image, filetype);
} else if (filetype.category === SupportedFileTypeCategory.Animation) {
return await this.processAnimation(image, filetype);
} else {
return Fail(FT.SysValidation, 'Unsupported mime type');
}
@ -26,48 +29,22 @@ export class ImageProcessorService {
private async processStill(
image: Buffer,
mime: FullMime,
filetype: FileType,
): AsyncFailable<ImageResult> {
let processedMime = mime.mime;
const outputFileType = ParseFileType(ImageFileType.QOI);
if (HasFailed(outputFileType)) return outputFileType;
let sharpImage = UniversalSharp(image, mime);
processedMime = ImageMime.QOI;
sharpImage = sharpImage.toColorspace('srgb');
const processedImage = await sharpImage.raw().toBuffer({
resolveWithObject: true,
});
if (
processedImage.info.width >= 32768 ||
processedImage.info.height >= 32768
) {
return Fail(FT.UsrValidation, 'Image too large');
}
// Png can be more efficient than QOI, but its just sooooooo slow
const qoiImage = QOIencode(processedImage.data, {
channels: processedImage.info.channels,
colorspace: QOIColorSpace.SRGB,
height: processedImage.info.height,
width: processedImage.info.width,
});
return {
image: qoiImage,
mime: processedMime,
};
return this.imageConverter.convert(image, filetype, outputFileType, {});
}
private async processAnimation(
image: Buffer,
mime: FullMime,
filetype: FileType,
): AsyncFailable<ImageResult> {
// Apng and gif are stored as is for now
return {
image: image,
mime: mime.mime,
filetype: filetype.identifier,
};
}
}

View file

@ -2,13 +2,16 @@ import { Injectable, Logger } from '@nestjs/common';
import Crypto from 'crypto';
import { fileTypeFromBuffer, FileTypeResult } from 'file-type';
import { ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto';
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.enum';
import { FullMime } from 'picsur-shared/dist/dto/mimes.dto';
import { ImageEntryVariant } from 'picsur-shared/dist/dto/image-entry-variant.enum';
import { FileType } from 'picsur-shared/dist/dto/mimes.dto';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { UsrPreference } from 'picsur-shared/dist/dto/usr-preferences.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { FindResult } from 'picsur-shared/dist/types/find-result';
import { ParseMime } from 'picsur-shared/dist/util/parse-mime';
import {
ParseFileType,
ParseMime2FileType
} from 'picsur-shared/dist/util/parse-mime';
import { IsQOI } from 'qoi-img';
import { ImageDBService } from '../../collections/image-db/image-db.service';
import { ImageFileDBService } from '../../collections/image-db/image-file-db.service';
@ -57,8 +60,8 @@ export class ImageManagerService {
image: Buffer,
userid: string,
): AsyncFailable<EImageBackend> {
const fullMime = await this.getFullMimeFromBuffer(image);
if (HasFailed(fullMime)) return fullMime;
const fileType = await this.getFileTypeFromBuffer(image);
if (HasFailed(fileType)) return fileType;
// Check if need to save orignal
const keepOriginal = await this.userPref.getBooleanPreference(
@ -68,7 +71,7 @@ export class ImageManagerService {
if (HasFailed(keepOriginal)) return keepOriginal;
// Process
const processResult = await this.processService.process(image, fullMime);
const processResult = await this.processService.process(image, fileType);
if (HasFailed(processResult)) return processResult;
// Save processed to db
@ -77,18 +80,18 @@ export class ImageManagerService {
const imageFileEntity = await this.imageFilesService.setFile(
imageEntity.id,
ImageFileType.MASTER,
ImageEntryVariant.MASTER,
processResult.image,
processResult.mime,
processResult.filetype,
);
if (HasFailed(imageFileEntity)) return imageFileEntity;
if (keepOriginal) {
const originalFileEntity = await this.imageFilesService.setFile(
imageEntity.id,
ImageFileType.ORIGINAL,
ImageEntryVariant.ORIGINAL,
image,
fullMime.mime,
fileType.identifier,
);
if (HasFailed(originalFileEntity)) return originalFileEntity;
}
@ -98,13 +101,13 @@ export class ImageManagerService {
public async getConverted(
imageId: string,
mime: string,
fileType: string,
options: ImageRequestParams,
): AsyncFailable<EImageDerivativeBackend> {
const targetMime = ParseMime(mime);
if (HasFailed(targetMime)) return targetMime;
const targetFileType = ParseFileType(fileType);
if (HasFailed(targetFileType)) return targetFileType;
const converted_key = this.getConvertHash({ mime, ...options });
const converted_key = this.getConvertHash({ mime: fileType, ...options });
const [save_derivatives, allow_editing] = await Promise.all([
this.sysPref.getBooleanPreference(SysPreference.SaveDerivatives),
@ -124,21 +127,21 @@ export class ImageManagerService {
const masterImage = await this.getMaster(imageId);
if (HasFailed(masterImage)) return masterImage;
const sourceMime = ParseMime(masterImage.mime);
if (HasFailed(sourceMime)) return sourceMime;
const sourceFileType = ParseFileType(masterImage.filetype);
if (HasFailed(sourceFileType)) return sourceFileType;
const startTime = Date.now();
const convertResult = await this.convertService.convert(
masterImage.data,
sourceMime,
targetMime,
sourceFileType,
targetFileType,
allow_editing ? options : {},
);
if (HasFailed(convertResult)) return convertResult;
this.logger.verbose(
`Converted ${imageId} from ${sourceMime.mime} to ${
targetMime.mime
`Converted ${imageId} from ${sourceFileType.identifier} to ${
targetFileType.identifier
} in ${Date.now() - startTime}ms`,
);
@ -146,12 +149,12 @@ export class ImageManagerService {
return await this.imageFilesService.addDerivative(
imageId,
converted_key,
convertResult.mime,
convertResult.filetype,
convertResult.image,
);
} else {
const derivative = new EImageDerivativeBackend();
derivative.mime = convertResult.mime;
derivative.filetype = convertResult.filetype;
derivative.data = convertResult.image;
derivative.image_id = imageId;
derivative.key = converted_key;
@ -164,52 +167,52 @@ export class ImageManagerService {
// File getters ==============================================================
public async getMaster(imageId: string): AsyncFailable<EImageFileBackend> {
return this.imageFilesService.getFile(imageId, ImageFileType.MASTER);
return this.imageFilesService.getFile(imageId, ImageEntryVariant.MASTER);
}
public async getMasterMime(imageId: string): AsyncFailable<FullMime> {
const mime = await this.imageFilesService.getFileMimes(imageId);
public async getMasterFileType(imageId: string): AsyncFailable<FileType> {
const mime = await this.imageFilesService.getFileTypes(imageId);
if (HasFailed(mime)) return mime;
if (mime.master === undefined) return Fail(FT.NotFound, 'No master file');
if (mime['master'] === undefined) return Fail(FT.NotFound, 'No master file');
return ParseMime(mime.master);
return ParseFileType(mime['master']);
}
public async getOriginal(imageId: string): AsyncFailable<EImageFileBackend> {
return this.imageFilesService.getFile(imageId, ImageFileType.ORIGINAL);
return this.imageFilesService.getFile(imageId, ImageEntryVariant.ORIGINAL);
}
public async getOriginalMime(imageId: string): AsyncFailable<FullMime> {
const mime = await this.imageFilesService.getFileMimes(imageId);
if (HasFailed(mime)) return mime;
public async getOriginalFileType(imageId: string): AsyncFailable<FileType> {
const filetypes = await this.imageFilesService.getFileTypes(imageId);
if (HasFailed(filetypes)) return filetypes;
if (mime.original === undefined)
if (filetypes['original'] === undefined)
return Fail(FT.NotFound, 'No original file');
return ParseMime(mime.original);
return ParseFileType(filetypes['original']);
}
public async getFileMimes(imageId: string): AsyncFailable<{
[ImageFileType.MASTER]: string;
[ImageFileType.ORIGINAL]: string | undefined;
[ImageEntryVariant.MASTER]: string;
[ImageEntryVariant.ORIGINAL]: string | undefined;
}> {
const result = await this.imageFilesService.getFileMimes(imageId);
const result = await this.imageFilesService.getFileTypes(imageId);
if (HasFailed(result)) return result;
if (result[ImageFileType.MASTER] === undefined) {
if (result[ImageEntryVariant.MASTER] === undefined) {
return Fail(FT.NotFound, 'No master file found');
}
return {
[ImageFileType.MASTER]: result[ImageFileType.MASTER]!,
[ImageFileType.ORIGINAL]: result[ImageFileType.ORIGINAL],
[ImageEntryVariant.MASTER]: result[ImageEntryVariant.MASTER]!,
[ImageEntryVariant.ORIGINAL]: result[ImageEntryVariant.ORIGINAL],
};
}
// Util stuff ==================================================================
private async getFullMimeFromBuffer(image: Buffer): AsyncFailable<FullMime> {
private async getFileTypeFromBuffer(image: Buffer): AsyncFailable<FileType> {
const filetypeResult: FileTypeResult | undefined = await fileTypeFromBuffer(
image,
);
@ -221,8 +224,7 @@ export class ImageManagerService {
mime = filetypeResult.mime;
}
const fullMime = ParseMime(mime ?? 'other/unknown');
return fullMime;
return ParseMime2FileType(mime ?? 'other/unknown');
}
private getConvertHash(options: object) {

View file

@ -1,4 +1,4 @@
export interface ImageResult {
image: Buffer;
mime: string;
filetype: string;
}

View file

@ -1,61 +0,0 @@
import { BMPdecode } from 'bmp-img';
import { FullMime, ImageMime } from 'picsur-shared/dist/dto/mimes.dto';
import { QOIdecode } from 'qoi-img';
import sharp, { Sharp, SharpOptions } from 'sharp';
export function UniversalSharp(
image: Buffer,
mime: FullMime,
options?: SharpOptions,
): Sharp {
// if (mime.mime === ImageMime.ICO) {
// return icoSharp(image, options);
// } else
if (mime.mime === ImageMime.BMP) {
return bmpSharp(image, options);
} else if (mime.mime === ImageMime.QOI) {
return qoiSharp(image, options);
} else {
return sharp(image, options);
}
}
function bmpSharp(image: Buffer, options?: SharpOptions) {
const bitmap = BMPdecode(image);
return sharp(bitmap.pixels, {
...options,
raw: {
width: bitmap.width,
height: bitmap.height,
channels: bitmap.channels,
},
});
}
// function icoSharp(image: Buffer, options?: SharpOptions) {
// const result = decodeico(image);
// // Get biggest image
// const best = result.sort((a, b) => b.width - a.width)[0];
// return sharp(best.data, {
// ...options,
// raw: {
// width: best.width,
// height: best.height,
// channels: 4,
// },
// });
// }
function qoiSharp(image: Buffer, options?: SharpOptions) {
const result = QOIdecode(image);
return sharp(result.pixels, {
...options,
raw: {
width: result.width,
height: result.height,
channels: result.channels,
},
});
}

View file

@ -1,15 +1,15 @@
interface NormalImage {
type: 'normal';
variant: 'normal';
id: string;
ext: string;
mime: string;
filetype: string;
}
interface OriginalImage {
type: 'original';
variant: 'original';
id: string;
ext: null;
mime: null;
filetype: null;
}
export type ImageFullId = NormalImage | OriginalImage;

View file

@ -15,7 +15,7 @@ export class EImageDerivativeBackend {
key: string;
@Column({ nullable: false })
mime: string;
filetype: string;
@Column({ name: 'last_read', nullable: false })
last_read: Date;

View file

@ -1,8 +1,8 @@
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.enum';
import { ImageEntryVariant } from 'picsur-shared/dist/dto/image-entry-variant.enum';
import { Column, Entity, Index, PrimaryGeneratedColumn, Unique } from 'typeorm';
@Entity()
@Unique(['image_id', 'type'])
@Unique(['image_id', 'variant'])
export class EImageFileBackend {
@PrimaryGeneratedColumn('uuid')
private _id?: string;
@ -12,11 +12,11 @@ export class EImageFileBackend {
image_id: string;
@Index()
@Column({ nullable: false, enum: ImageFileType })
type: ImageFileType;
@Column({ nullable: false, enum: ImageEntryVariant })
variant: ImageEntryVariant;
@Column({ nullable: false })
mime: string;
filetype: string;
// Binary data
@Column({ type: 'bytea', nullable: false })

View file

@ -4,10 +4,7 @@ import {
AllPermissionsResponse,
InfoResponse
} from 'picsur-shared/dist/dto/api/info.dto';
import {
AnimMime2ExtMap,
ImageMime2ExtMap
} from 'picsur-shared/dist/dto/mimes.dto';
import { FileType2Ext, FileType2Mime, SupportedAnimFileTypes, SupportedImageFileTypes } from 'picsur-shared/dist/dto/mimes.dto';
import { HostConfigService } from '../../../config/early/host.config.service';
import { NoPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator';
@ -42,8 +39,18 @@ export class InfoController {
@Returns(AllFormatsResponse)
async getFormats(): Promise<AllFormatsResponse> {
return {
image: ImageMime2ExtMap,
anim: AnimMime2ExtMap,
image: Object.fromEntries(
SupportedImageFileTypes.map((filetype) => [
FileType2Mime(filetype),
FileType2Ext(filetype),
]),
),
anim: Object.fromEntries(
SupportedAnimFileTypes.map((filetype) => [
FileType2Mime(filetype),
FileType2Ext(filetype),
]),
),
};
}
}

View file

@ -1,14 +1,11 @@
import {
Controller,
Get,
Head, Logger, Query,
Res
} from '@nestjs/common';
import { Controller, Get, Head, Logger, Query, Res } from '@nestjs/common';
import type { FastifyReply } from 'fastify';
import {
ImageMetaResponse,
ImageRequestParams
} from 'picsur-shared/dist/dto/api/image.dto';
import { ImageEntryVariant } from 'picsur-shared/dist/dto/image-entry-variant.enum';
import { FileType2Mime } from 'picsur-shared/dist/dto/mimes.dto';
import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { UsersService } from '../../collections/user-db/user-db.service';
import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator';
@ -36,16 +33,16 @@ export class ImageController {
@Res({ passthrough: true }) res: FastifyReply,
@ImageFullIdParam() fullid: ImageFullId,
) {
if (fullid.type === 'original') {
const fullmime = ThrowIfFailed(
await this.imagesService.getOriginalMime(fullid.id),
if (fullid.variant === ImageEntryVariant.ORIGINAL) {
const filetype = ThrowIfFailed(
await this.imagesService.getOriginalFileType(fullid.id),
);
res.type(fullmime.mime);
res.type(ThrowIfFailed(FileType2Mime(filetype.identifier)));
return;
}
res.type(fullid.mime);
res.type(ThrowIfFailed(FileType2Mime(fullid.filetype)));
}
@Get(':id')
@ -56,20 +53,20 @@ export class ImageController {
@ImageFullIdParam() fullid: ImageFullId,
@Query() params: ImageRequestParams,
): Promise<Buffer> {
if (fullid.type === 'original') {
if (fullid.variant === ImageEntryVariant.ORIGINAL) {
const image = ThrowIfFailed(
await this.imagesService.getOriginal(fullid.id),
);
res.type(image.mime);
res.type(ThrowIfFailed(FileType2Mime(image.filetype)));
return image.data;
}
const image = ThrowIfFailed(
await this.imagesService.getConverted(fullid.id, fullid.mime, params),
await this.imagesService.getConverted(fullid.id, fullid.filetype, params),
);
res.type(image.mime);
res.type(ThrowIfFailed(FileType2Mime(image.filetype)));
return image.data;
}
@ -81,11 +78,11 @@ export class ImageController {
const [fileMimesRes, imageUserRes] = await Promise.all([
this.imagesService.getFileMimes(id),
this.userService.findOne(image.user_id),
])
]);
const fileMimes = ThrowIfFailed(fileMimesRes);
const fileTypes = ThrowIfFailed(fileMimesRes);
const imageUser = ThrowIfFailed(imageUserRes);
return { image, user: EUserBackend2EUser(imageUser), fileMimes };
return { image, user: EUserBackend2EUser(imageUser), fileTypes };
}
}

View file

@ -2,7 +2,7 @@ import { Logger } from '@nestjs/common';
import { ChildProcess, fork } from 'child_process';
import pTimeout from 'p-timeout';
import path from 'path';
import { FullMime } from 'picsur-shared/dist/dto/mimes.dto';
import { FileType } from 'picsur-shared/dist/dto/mimes.dto';
import {
AsyncFailable,
Fail,
@ -41,7 +41,7 @@ export class SharpWrapper {
private readonly memory_limit: number,
) {}
public async start(image: Buffer, mime: FullMime): AsyncFailable<true> {
public async start(image: Buffer, filetype: FileType): AsyncFailable<true> {
this.worker = fork(SharpWrapper.WORKER_PATH, {
serialization: 'advanced',
timeout: this.instance_timeout,
@ -79,7 +79,7 @@ export class SharpWrapper {
const hasSent = this.sendToWorker({
type: 'init',
image,
mime,
filetype,
});
if (HasFailed(hasSent)) {
this.purge();
@ -117,7 +117,7 @@ export class SharpWrapper {
}
public async finish(
targetMime: FullMime,
targetFiletype: FileType,
options?: SharpWorkerFinishOptions,
): AsyncFailable<SharpResult> {
if (!this.worker) {
@ -126,7 +126,7 @@ export class SharpWrapper {
const hasSent = this.sendToWorker({
type: 'finish',
mime: targetMime,
filetype: targetFiletype,
options: options ?? {},
});
if (HasFailed(hasSent)) {

View file

@ -1,4 +1,4 @@
import { FullMime } from 'picsur-shared/dist/dto/mimes.dto';
import { FileType } from 'picsur-shared/dist/dto/mimes.dto';
import { Sharp } from 'sharp';
import { SharpResult } from './universal-sharp';
@ -33,7 +33,7 @@ export interface SharpWorkerFinishOptions {
export interface SharpWorkerInitMessage {
type: 'init';
image: Buffer;
mime: FullMime;
filetype: FileType;
}
export interface SharpWorkerOperationMessage {
@ -43,7 +43,7 @@ export interface SharpWorkerOperationMessage {
export interface SharpWorkerFinishMessage {
type: 'finish';
mime: FullMime;
filetype: FileType;
options: SharpWorkerFinishOptions;
}

View file

@ -1,4 +1,4 @@
import { FullMime } from 'picsur-shared/dist/dto/mimes.dto';
import { FileType } from 'picsur-shared/dist/dto/mimes.dto';
import posix from 'posix.js';
import { Sharp } from 'sharp';
import {
@ -47,7 +47,7 @@ export class SharpWorker {
} else if (message.type === 'operation') {
this.operation(message);
} else if (message.type === 'finish') {
this.finish(message.mime, message.options);
this.finish(message.filetype, message.options);
} else {
return this.purge('Unknown message type');
}
@ -59,7 +59,7 @@ export class SharpWorker {
}
this.startTime = Date.now();
this.sharpi = UniversalSharpIn(message.image, message.mime);
this.sharpi = UniversalSharpIn(message.image, message.filetype);
}
private operation(message: SharpWorkerOperationMessage): void {
@ -74,7 +74,7 @@ export class SharpWorker {
}
private async finish(
mime: FullMime,
filetype: FileType,
options: SharpWorkerFinishOptions,
): Promise<void> {
if (this.sharpi === null) {
@ -85,7 +85,7 @@ export class SharpWorker {
this.sharpi = null;
try {
const result = await UniversalSharpOut(sharpi, mime, options);
const result = await UniversalSharpOut(sharpi, filetype, options);
const processingTime = Date.now() - this.startTime;
this.sendMessage({

View file

@ -1,5 +1,5 @@
import { BMPdecode, BMPencode } from 'bmp-img';
import { FullMime, ImageMime } from 'picsur-shared/dist/dto/mimes.dto';
import { FileType, ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
import { QOIdecode, QOIencode } from 'qoi-img';
import sharp, { Sharp, SharpOptions } from 'sharp';
@ -10,16 +10,21 @@ export interface SharpResult {
export function UniversalSharpIn(
image: Buffer,
mime: FullMime,
filetype: FileType,
options?: SharpOptions,
): Sharp {
// if (mime.mime === ImageMime.ICO) {
// if (mime.mime === ImageFileType.ICO) {
// return icoSharpIn(image, options);
// } else
if (mime.mime === ImageMime.BMP) {
if (filetype.identifier === ImageFileType.BMP) {
return bmpSharpIn(image, options);
} else if (mime.mime === ImageMime.QOI) {
} else if (filetype.identifier === ImageFileType.QOI) {
return qoiSharpIn(image, options);
// } else if (filetype.identifier === AnimFileType.GIF) {
// return sharp(image, {
// ...options,
// animated: true,
// });
} else {
return sharp(image, options);
}
@ -67,40 +72,43 @@ function qoiSharpIn(image: Buffer, options?: SharpOptions) {
export async function UniversalSharpOut(
image: Sharp,
mime: FullMime,
filetype: FileType,
options?: {
quality?: number;
},
): Promise<SharpResult> {
let result: SharpResult | undefined;
switch (mime.mime) {
case ImageMime.PNG:
switch (filetype.identifier) {
case ImageFileType.PNG:
result = await image
.png({ quality: options?.quality })
.toBuffer({ resolveWithObject: true });
break;
case ImageMime.JPEG:
case ImageFileType.JPEG:
result = await image
.jpeg({ quality: options?.quality })
.toBuffer({ resolveWithObject: true });
break;
case ImageMime.TIFF:
case ImageFileType.TIFF:
result = await image
.tiff({ quality: options?.quality })
.toBuffer({ resolveWithObject: true });
break;
case ImageMime.WEBP:
case ImageFileType.WEBP:
result = await image
.webp({ quality: options?.quality })
.toBuffer({ resolveWithObject: true });
break;
case ImageMime.BMP:
case ImageFileType.BMP:
result = await bmpSharpOut(image);
break;
case ImageMime.QOI:
case ImageFileType.QOI:
result = await qoiSharpOut(image);
break;
// case AnimFileType.GIF:
// result = await image.gif().toBuffer({ resolveWithObject: true });
// break;
default:
throw new Error('Unsupported mime type');
}

View file

@ -8,10 +8,10 @@ import {
SimpleChanges,
ViewChild
} from '@angular/core';
import { FullMime, ImageMime } from 'picsur-shared/dist/dto/mimes.dto';
import { FileType, ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { URLRegex } from 'picsur-shared/dist/util/common-regex';
import { ParseMime } from 'picsur-shared/dist/util/parse-mime';
import { ParseMime2FileType } from 'picsur-shared/dist/util/parse-mime';
import { ApiService } from 'src/app/services/api/api.service';
import { Logger } from 'src/app/services/logger/logger.service';
import { QoiWorkerService } from 'src/app/workers/qoi-worker.service';
@ -69,10 +69,10 @@ export class PicsurImgComponent implements OnChanges {
}
private async update(url: string): AsyncFailable<void> {
const mime = await this.getMime(url);
if (HasFailed(mime)) return mime;
const filetype = await this.getFileType(url);
if (HasFailed(filetype)) return filetype;
if (mime.mime === ImageMime.QOI) {
if (filetype.identifier === ImageFileType.QOI) {
const result = await this.qoiWorker.decode(url);
if (HasFailed(result)) return result;
@ -88,7 +88,7 @@ export class PicsurImgComponent implements OnChanges {
this.changeDetector.markForCheck();
}
private async getMime(url: string): AsyncFailable<FullMime> {
private async getFileType(url: string): AsyncFailable<FileType> {
const response = await this.apiService.head(url);
if (HasFailed(response)) {
return response;
@ -97,8 +97,7 @@ export class PicsurImgComponent implements OnChanges {
const mimeHeader = response.get('content-type') ?? '';
const mime = mimeHeader.split(';')[0];
const fullMime = ParseMime(mime);
return fullMime;
return ParseMime2FileType(mime);
}
onInview(e: any) {

View file

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { ImageMime } from 'picsur-shared/dist/dto/mimes.dto';
import { ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
import { EImage } from 'picsur-shared/dist/entities/image.entity';
import { HasFailed } from 'picsur-shared/dist/types/failable';
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
@ -71,7 +71,7 @@ export class ImagesComponent implements OnInit {
getThumbnailUrl(image: EImage) {
return (
this.imageService.GetImageURL(image.id, ImageMime.QOI) + '?height=480'
this.imageService.GetImageURL(image.id, ImageFileType.QOI) + '?height=480'
);
}

View file

@ -2,19 +2,20 @@ import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ImageLinks } from 'picsur-shared/dist/dto/image-links.class';
import {
AnimMime,
FullMime,
ImageMime,
Mime2Ext,
SupportedAnimMimes,
SupportedImageMimes,
SupportedMimeCategory
AnimFileType,
FileType,
FileType2Ext,
ImageFileType,
SupportedAnimFileTypes,
SupportedFileTypeCategory,
SupportedImageFileTypes
} from 'picsur-shared/dist/dto/mimes.dto';
import { EImage } from 'picsur-shared/dist/entities/image.entity';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { HasFailed, HasSuccess } from 'picsur-shared/dist/types';
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
import { ParseMime } from 'picsur-shared/dist/util/parse-mime';
import { ParseFileType } from 'picsur-shared/dist/util/parse-mime';
import { ImageService } from 'src/app/services/api/image.service';
import { UtilService } from 'src/app/util/util-module/util.service';
import {
@ -36,18 +37,18 @@ export class ViewComponent implements OnInit {
private id: string;
private hasOriginal: boolean = false;
private masterMime: FullMime = {
mime: ImageMime.JPEG,
type: SupportedMimeCategory.Image,
private masterFileType: FileType = {
identifier: ImageFileType.JPEG,
category: SupportedFileTypeCategory.Image,
};
private currentSelectedFormat: string = ImageMime.JPEG;
private currentSelectedFormat: string = ImageFileType.JPEG;
public formatOptions: {
value: string;
key: string;
}[] = [];
public setSelectedFormat: string = ImageMime.JPEG;
public setSelectedFormat: string = ImageFileType.JPEG;
public previewLink = '';
public imageLinks = new ImageLinks();
@ -69,25 +70,27 @@ export class ViewComponent implements OnInit {
this.previewLink = this.imageService.GetImageURL(
this.id,
metadata.fileMimes.master,
metadata.fileTypes.master,
);
this.hasOriginal = metadata.fileMimes.original !== undefined;
this.hasOriginal = metadata.fileTypes.original !== undefined;
this.imageUser = metadata.user;
this.image = metadata.image;
const masterMime = ParseMime(metadata.fileMimes.master);
if (HasSuccess(masterMime)) {
this.masterMime = masterMime;
const masterFiletype = ParseFileType(metadata.fileTypes.master);
if (HasSuccess(masterFiletype)) {
this.masterFileType = masterFiletype;
}
if (this.masterMime.type === SupportedMimeCategory.Image) {
this.setSelectedFormat = ImageMime.JPEG;
} else if (this.masterMime.type === SupportedMimeCategory.Animation) {
this.setSelectedFormat = AnimMime.GIF;
if (this.masterFileType.category === SupportedFileTypeCategory.Image) {
this.setSelectedFormat = ImageFileType.JPEG;
} else if (
this.masterFileType.category === SupportedFileTypeCategory.Animation
) {
this.setSelectedFormat = AnimFileType.GIF;
} else {
this.setSelectedFormat = metadata.fileMimes.master;
this.setSelectedFormat = metadata.fileTypes.master;
}
this.selectedFormat(this.setSelectedFormat);
@ -122,7 +125,7 @@ export class ViewComponent implements OnInit {
};
if (options.selectedFormat === 'original') {
options.selectedFormat = this.masterMime.mime;
options.selectedFormat = this.masterFileType.identifier;
}
await this.utilService.showCustomDialog(CustomizeDialogComponent, options, {
@ -157,19 +160,29 @@ export class ViewComponent implements OnInit {
key: string;
}[] = [];
if (this.masterMime.type === SupportedMimeCategory.Image) {
if (this.masterFileType.category === SupportedFileTypeCategory.Image) {
newOptions.push(
...SupportedImageMimes.map((mime) => ({
value: Mime2Ext(mime)?.toUpperCase() ?? 'Error',
key: mime,
})),
...SupportedImageFileTypes.map((mime) => {
let ext = FileType2Ext(mime);
if (HasFailed(ext)) ext = 'Error';
return {
value: ext.toUpperCase(),
key: mime,
};
}),
);
} else if (this.masterMime.type === SupportedMimeCategory.Animation) {
} else if (
this.masterFileType.category === SupportedFileTypeCategory.Animation
) {
newOptions.push(
...SupportedAnimMimes.map((mime) => ({
value: Mime2Ext(mime)?.toUpperCase() ?? 'Error',
key: mime,
})),
...SupportedAnimFileTypes.map((mime) => {
let ext = FileType2Ext(mime);
if (HasFailed(ext)) ext = 'Error';
return {
value: ext.toUpperCase(),
key: mime,
};
}),
);
}

View file

@ -1,9 +1,16 @@
import { Inject, Injectable } from '@angular/core';
import { WINDOW } from '@ng-web-apis/common';
import { ApiResponseSchema } from 'picsur-shared/dist/dto/api/api.dto';
import { Mime2Ext } from 'picsur-shared/dist/dto/mimes.dto';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { FileType2Ext } from 'picsur-shared/dist/dto/mimes.dto';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
HasSuccess
} from 'picsur-shared/dist/types';
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
import { ParseMime2FileType } from 'picsur-shared/dist/util/parse-mime';
import { Subject } from 'rxjs';
import { ApiBuffer } from 'src/app/models/dto/api-buffer.dto';
import { ApiError } from 'src/app/models/dto/api-error.dto';
@ -142,9 +149,14 @@ export class ApiService {
}
}
const mimeTypeExt = Mime2Ext(mimeType);
if (mimeTypeExt !== undefined && !name.endsWith(mimeTypeExt)) {
name += '.' + mimeTypeExt;
const filetype = ParseMime2FileType(mimeType);
if (HasSuccess(filetype)) {
const ext = FileType2Ext(filetype.identifier);
if (HasSuccess(ext)) {
if (name.endsWith(ext)) {
name += '.' + ext;
}
}
}
try {

View file

@ -12,10 +12,10 @@ import {
ImageRequestParams
} from 'picsur-shared/dist/dto/api/image.dto';
import { ImageLinks } from 'picsur-shared/dist/dto/image-links.class';
import { Mime2Ext } from 'picsur-shared/dist/dto/mimes.dto';
import { FileType2Ext } from 'picsur-shared/dist/dto/mimes.dto';
import { EImage } from 'picsur-shared/dist/entities/image.entity';
import { AsyncFailable } from 'picsur-shared/dist/types';
import { Fail, FT, HasFailed, Open } from 'picsur-shared/dist/types/failable';
import { Fail, FT, HasFailed, HasSuccess, Open } from 'picsur-shared/dist/types/failable';
import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto';
import { ApiService } from './api.service';
import { UserService } from './user.service';
@ -103,19 +103,19 @@ export class ImageService {
// Non api calls
public GetImageURL(image: string, mime: string | null): string {
public GetImageURL(image: string, filetype: string | null): string {
const baseURL = this.location.protocol + '//' + this.location.host;
const extension = mime !== null ? Mime2Ext(mime) : null;
const extension = FileType2Ext(filetype ?? '');
return `${baseURL}/i/${image}${extension !== null ? '.' + extension : ''}`;
return `${baseURL}/i/${image}${HasSuccess(extension) ? '.' + extension : ''}`;
}
public GetImageURLCustomized(
image: string,
mime: string | null,
filetype: string | null,
options: ImageRequestParams,
): string {
const baseURL = this.GetImageURL(image, mime);
const baseURL = this.GetImageURL(image, filetype);
const betterOptions = ImageRequestParams.zodSchema.safeParse(options);
if (!betterOptions.success) return baseURL;

View file

@ -2,7 +2,7 @@ import { z } from 'zod';
import { EImageSchema } from '../../entities/image.entity';
import { EUserSchema } from '../../entities/user.entity';
import { createZodDto } from '../../util/create-zod-dto';
import { ImageFileType } from '../image-file-types.enum';
import { ImageEntryVariant } from '../image-entry-variant.enum';
const parseBool = (value: unknown): boolean | null => {
if (value === true || value === 'true' || value === '1' || value === 'yes')
@ -36,9 +36,9 @@ export class ImageRequestParams extends createZodDto(
export const ImageMetaResponseSchema = z.object({
image: EImageSchema,
user: EUserSchema,
fileMimes: z.object({
[ImageFileType.MASTER]: z.string(),
[ImageFileType.ORIGINAL]: z.union([z.string(), z.undefined()]),
fileTypes: z.object({
[ImageEntryVariant.MASTER]: z.string(),
[ImageEntryVariant.ORIGINAL]: z.union([z.string(), z.undefined()]),
}),
});
export class ImageMetaResponse extends createZodDto(ImageMetaResponseSchema) {}

View file

@ -1,4 +1,4 @@
export enum ImageFileType {
export enum ImageEntryVariant {
ORIGINAL = 'original',
MASTER = 'master',
}

View file

@ -1,69 +1,110 @@
import { Fail, Failable, FT } from '../types';
// Config
export enum ImageMime {
QOI = 'image/x-qoi',
JPEG = 'image/jpeg',
PNG = 'image/png',
WEBP = 'image/webp',
TIFF = 'image/tiff',
BMP = 'image/bmp',
// ICO = 'image/x-icon',
export enum ImageFileType {
QOI = 'image:qoi',
JPEG = 'image:jpeg',
PNG = 'image:png',
WEBP = 'image:webp',
TIFF = 'image:tiff',
BMP = 'image:bmp',
// ICO = 'image:ico',
}
export enum AnimMime {
APNG = 'image/apng',
GIF = 'image/gif',
export enum AnimFileType {
GIF = 'anim:gif',
WEBP = 'anim:webp',
//APNG = 'anim:apng',
}
// Derivatives
export const SupportedImageMimes: string[] = Object.values(ImageMime);
export const SupportedAnimMimes: string[] = Object.values(AnimMime);
export const SupportedMimes: string[] = Object.values({ ...ImageMime, ...AnimMime });
export const SupportedImageFileTypes: string[] = Object.values(ImageFileType);
export const SupportedAnimFileTypes: string[] = Object.values(AnimFileType);
export const SupportedFileTypes: string[] = Object.values({
...ImageFileType,
...AnimFileType,
});
export enum SupportedMimeCategory {
export enum SupportedFileTypeCategory {
Image = 'image',
Animation = 'anim',
}
export interface FullMime {
mime: string;
type: SupportedMimeCategory;
export interface FileType {
identifier: string;
category: SupportedFileTypeCategory;
}
export const ImageMime2ExtMap: {
[key in ImageMime]: string;
// Converters
// -- Ext
const FileType2ExtMap: {
[key in ImageFileType | AnimFileType]: string;
} = {
[ImageMime.QOI]: 'qoi',
[ImageMime.JPEG]: 'jpg',
[ImageMime.PNG]: 'png',
[ImageMime.WEBP]: 'webp',
[ImageMime.TIFF]: 'tiff',
[ImageMime.BMP]: 'bmp',
// [ImageMime.ICO]: 'ico',
[AnimFileType.GIF]: 'gif',
[AnimFileType.WEBP]: 'webp',
// [AnimFileType.APNG]: 'apng',
[ImageFileType.QOI]: 'qoi',
[ImageFileType.JPEG]: 'jpg',
[ImageFileType.PNG]: 'png',
[ImageFileType.WEBP]: 'webp',
[ImageFileType.TIFF]: 'tiff',
[ImageFileType.BMP]: 'bmp',
// [ImageFileType.ICO]: 'ico',
};
export const AnimMime2ExtMap: {
[key in AnimMime]: string;
} = {
[AnimMime.GIF]: 'gif',
[AnimMime.APNG]: 'apng',
};
export const Mime2ExtMap: {
[key in ImageMime | AnimMime]: string;
} = {
...ImageMime2ExtMap,
...AnimMime2ExtMap,
};
export const Ext2MimeMap: {
const Ext2FileTypeMap: {
[key: string]: string;
} = Object.fromEntries(Object.entries(Mime2ExtMap).map(([k, v]) => [v, k]));
} = Object.fromEntries(Object.entries(FileType2ExtMap).map(([k, v]) => [v, k]));
export const Mime2Ext = (mime: string): string | undefined => {
return Mime2ExtMap[mime as ImageMime | AnimMime];
export const FileType2Ext = (mime: string): Failable<string> => {
const result = FileType2ExtMap[mime as ImageFileType | AnimFileType];
if (result === undefined)
return Fail(FT.Internal, undefined, `Unsupported mime type: ${mime}`);
return result;
};
export const Ext2Mime = (ext: string): string | undefined => {
return Ext2MimeMap[ext];
export const Ext2FileType = (ext: string): Failable<string> => {
const result = Ext2FileTypeMap[ext];
if (result === undefined)
return Fail(FT.Internal, undefined, `Unsupported ext: ${ext}`);
return result;
};
// -- Mime
const FileType2MimeMap: {
[key in ImageFileType | AnimFileType]: string;
} = {
[AnimFileType.GIF]: 'image/gif',
[AnimFileType.WEBP]: 'image/webp',
// [AnimFileType.APNG]: 'image/apng',
[ImageFileType.QOI]: 'image/x-qoi',
[ImageFileType.JPEG]: 'image/jpeg',
[ImageFileType.PNG]: 'image/png',
[ImageFileType.WEBP]: 'image/webp',
[ImageFileType.TIFF]: 'image/tiff',
[ImageFileType.BMP]: 'image/bmp',
// [ImageFileType.ICO]: 'image/x-icon',
};
const Mime2FileTypeMap: {
[key: string]: string;
} = Object.fromEntries(
Object.entries(FileType2MimeMap).map(([k, v]) => [v, k]),
);
export const Mime2FileType = (mime: string): Failable<string> => {
const result = Mime2FileTypeMap[mime as ImageFileType | AnimFileType];
if (result === undefined)
return Fail(FT.Internal, undefined, `Unsupported mime type: ${mime}`);
return result;
};
export const FileType2Mime = (filetype: string): Failable<string> => {
const result = FileType2MimeMap[filetype as ImageFileType | AnimFileType];
if (result === undefined)
return Fail(FT.Internal, undefined, `Unsupported filetype: ${filetype}`);
return result;
};

View file

@ -141,10 +141,20 @@ export function Fail(type: FT, reason?: any, dbgReason?: any): Failure {
if (dbgReason === undefined || dbgReason === null) {
if (reason === undefined || reason === null) {
// If both are null, just return a default error message
return new Failure(type, FTProps[type].message, undefined, undefined);
return new Failure(
type,
FTProps[type].message,
new Error(String(FTProps[type].message)).stack,
undefined,
);
} else if (typeof reason === 'string') {
// If it is a string, this was intentionally specified, so pass it through
return new Failure(type, reason, undefined, undefined);
return new Failure(
type,
reason,
new Error(String(reason)).stack,
undefined,
);
} else if (reason instanceof Error) {
// In case of an error, we want to keep that hidden, so return the default message
// Only send the specifics to debug
@ -159,7 +169,7 @@ export function Fail(type: FT, reason?: any, dbgReason?: any): Failure {
return new Failure(
type,
FTProps[type].message,
undefined,
new Error(String(reason)).stack,
String(reason),
);
}
@ -168,11 +178,21 @@ export function Fail(type: FT, reason?: any, dbgReason?: any): Failure {
const strReason = reason?.toString() ?? FTProps[type].message;
if (typeof dbgReason === 'string') {
return new Failure(type, strReason, undefined, dbgReason);
return new Failure(
type,
strReason,
new Error(String(dbgReason)).stack,
dbgReason,
);
} else if (dbgReason instanceof Error) {
return new Failure(type, strReason, dbgReason.stack, dbgReason.message);
} else {
return new Failure(type, strReason, undefined, String(dbgReason));
return new Failure(
type,
strReason,
new Error(String(dbgReason)).stack,
String(dbgReason),
);
}
}
}

View file

@ -1,17 +1,34 @@
import {
FullMime,
SupportedAnimMimes,
SupportedImageMimes,
SupportedMimeCategory
Ext2FileType,
FileType,
Mime2FileType,
SupportedAnimFileTypes,
SupportedFileTypeCategory,
SupportedImageFileTypes
} from '../dto/mimes.dto';
import { Fail, Failable, FT } from '../types';
import { Fail, Failable, FT, HasFailed } from '../types';
export function ParseMime(mime: string): Failable<FullMime> {
if (SupportedImageMimes.includes(mime))
return { mime, type: SupportedMimeCategory.Image };
export function ParseFileType(filetype: string): Failable<FileType> {
if (SupportedImageFileTypes.includes(filetype))
return { identifier: filetype, category: SupportedFileTypeCategory.Image };
if (SupportedAnimMimes.includes(mime))
return { mime, type: SupportedMimeCategory.Animation };
if (SupportedAnimFileTypes.includes(filetype))
return {
identifier: filetype,
category: SupportedFileTypeCategory.Animation,
};
return Fail(FT.UsrValidation, 'Unsupported mime type');
return Fail(FT.UsrValidation, 'Unsupported file type');
}
export function ParseExt2FileType(ext: string): Failable<FileType> {
const result = Ext2FileType(ext);
if (HasFailed(result)) return result;
return ParseFileType(result);
}
export function ParseMime2FileType(mime: string): Failable<FileType> {
const result = Mime2FileType(mime);
if (HasFailed(result)) return result;
return ParseFileType(result);
}