You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
7.0 KiB
TypeScript
252 lines
7.0 KiB
TypeScript
3 years ago
|
import { isArrayBuffer, isUndefined, omit } from 'lodash';
|
||
|
import {
|
||
|
createAbsolutePathGetter,
|
||
|
createDeleter,
|
||
|
createReader,
|
||
|
createWriterForNew,
|
||
|
getPath,
|
||
|
} from '../attachments/attachments';
|
||
|
import {
|
||
|
autoOrientJPEG,
|
||
|
captureDimensionsAndScreenshot,
|
||
|
deleteData,
|
||
|
loadData,
|
||
|
} from './attachments/migrations';
|
||
|
|
||
|
// tslint:disable: prefer-object-spread
|
||
|
|
||
|
// FIXME audric
|
||
|
// upgrade: exports._mapAttachments(autoOrientJPEG),
|
||
|
// upgrade: exports._mapAttachments(replaceUnicodeOrderOverrides),
|
||
|
// upgrade: _mapAttachments(migrateDataToFileSystem),
|
||
|
// upgrade: ._mapQuotedAttachments(migrateDataToFileSystem),
|
||
|
// upgrade: initializeAttachmentMetadata,
|
||
|
// upgrade: initializeAttachmentMetadata,
|
||
|
// upgrade: _mapAttachments(captureDimensionsAndScreenshot),
|
||
|
// upgrade: _mapAttachments(replaceUnicodeV2),
|
||
|
// upgrade: _mapPreviewAttachments(migrateDataToFileSystem),
|
||
|
|
||
|
export const deleteExternalMessageFiles = async (message: {
|
||
|
attachments: any;
|
||
|
quote: any;
|
||
|
contact: any;
|
||
|
preview: any;
|
||
|
}) => {
|
||
|
const { attachments, quote, contact, preview } = message;
|
||
|
|
||
|
if (attachments && attachments.length) {
|
||
|
await Promise.all(attachments.map(deleteData));
|
||
|
}
|
||
|
|
||
|
if (quote && quote.attachments && quote.attachments.length) {
|
||
|
await Promise.all(
|
||
|
quote.attachments.map(async (attachment: { thumbnail: any }) => {
|
||
|
const { thumbnail } = attachment;
|
||
|
|
||
|
// To prevent spoofing, we copy the original image from the quoted message.
|
||
|
// If so, it will have a 'copied' field. We don't want to delete it if it has
|
||
|
// that field set to true.
|
||
|
if (thumbnail && thumbnail.path && !thumbnail.copied) {
|
||
|
await deleteOnDisk(thumbnail.path);
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (contact && contact.length) {
|
||
|
await Promise.all(
|
||
|
contact.map(async (item: { avatar: any }) => {
|
||
|
const { avatar } = item;
|
||
|
|
||
|
if (avatar && avatar.avatar && avatar.avatar.path) {
|
||
|
await deleteOnDisk(avatar.avatar.path);
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (preview && preview.length) {
|
||
|
await Promise.all(
|
||
|
preview.map(async (item: { image: any }) => {
|
||
|
const { image } = item;
|
||
|
|
||
|
if (image && image.path) {
|
||
|
await deleteOnDisk(image.path);
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
let attachmentsPath: string | undefined;
|
||
|
|
||
|
let internalReadAttachmentData: ((relativePath: string) => Promise<ArrayBufferLike>) | undefined;
|
||
|
let internalGetAbsoluteAttachmentPath: ((relativePath: string) => string) | undefined;
|
||
|
let internalDeleteOnDisk: ((relativePath: string) => Promise<void>) | undefined;
|
||
|
let internalWriteNewAttachmentData: ((arrayBuffer: ArrayBuffer) => Promise<string>) | undefined;
|
||
|
|
||
|
// userDataPath must be app.getPath('userData');
|
||
|
export function initializeAttachmentLogic(userDataPath: string) {
|
||
|
if (attachmentsPath) {
|
||
|
throw new Error('attachmentsPath already initialized');
|
||
|
}
|
||
|
|
||
|
if (!userDataPath || userDataPath.length <= 10) {
|
||
|
throw new Error('userDataPath cannot have length <= 10');
|
||
|
}
|
||
|
attachmentsPath = getPath(userDataPath);
|
||
|
internalReadAttachmentData = createReader(attachmentsPath);
|
||
|
internalGetAbsoluteAttachmentPath = createAbsolutePathGetter(attachmentsPath);
|
||
|
internalDeleteOnDisk = createDeleter(attachmentsPath);
|
||
|
internalWriteNewAttachmentData = createWriterForNew(attachmentsPath);
|
||
|
}
|
||
|
|
||
|
export const getAttachmentPath = () => {
|
||
|
if (!attachmentsPath) {
|
||
|
throw new Error('attachmentsPath not init');
|
||
|
}
|
||
|
return attachmentsPath;
|
||
|
};
|
||
|
|
||
|
export const loadAttachmentData = loadData();
|
||
|
|
||
|
export const loadPreviewData = async (preview: any) => {
|
||
|
if (!preview || !preview.length) {
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
return Promise.all(
|
||
|
preview.map(async (item: any) => {
|
||
|
if (!item.image) {
|
||
|
return item;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
...item,
|
||
|
image: await loadAttachmentData(item.image),
|
||
|
};
|
||
|
})
|
||
|
);
|
||
|
};
|
||
|
|
||
|
export const loadQuoteData = async (quote: any) => {
|
||
|
if (!quote) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
...quote,
|
||
|
attachments: await Promise.all(
|
||
|
(quote.attachments || []).map(async (attachment: any) => {
|
||
|
const { thumbnail } = attachment;
|
||
|
|
||
|
if (!thumbnail || !thumbnail.path) {
|
||
|
return attachment;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
...attachment,
|
||
|
thumbnail: await loadAttachmentData(thumbnail),
|
||
|
};
|
||
|
})
|
||
|
),
|
||
|
};
|
||
|
};
|
||
|
|
||
|
export const processNewAttachment = async (attachment: {
|
||
|
contentType: string;
|
||
|
data: ArrayBuffer;
|
||
|
digest?: string;
|
||
|
path?: string;
|
||
|
isRaw?: boolean;
|
||
|
}) => {
|
||
|
const rotatedData = await autoOrientJPEG(attachment);
|
||
|
const rotatedAttachment = {
|
||
|
...attachment,
|
||
|
contentType: rotatedData.contentType,
|
||
|
data: rotatedData.data,
|
||
|
digest: attachment.digest as string | undefined,
|
||
|
};
|
||
|
if (rotatedData.shouldDeleteDigest) {
|
||
|
delete rotatedAttachment.digest;
|
||
|
}
|
||
|
|
||
|
const onDiskAttachmentPath = await migrateDataToFileSystem(rotatedAttachment.data);
|
||
|
|
||
|
const attachmentWithoutData = omit({ ...attachment, path: onDiskAttachmentPath }, 'data');
|
||
|
const finalAttachment = await captureDimensionsAndScreenshot(attachmentWithoutData);
|
||
|
|
||
|
return finalAttachment;
|
||
|
};
|
||
|
|
||
|
export const readAttachmentData = async (relativePath: string): Promise<ArrayBufferLike> => {
|
||
|
if (!internalReadAttachmentData) {
|
||
|
throw new Error('attachment logic not initialized');
|
||
|
}
|
||
|
return internalReadAttachmentData(relativePath);
|
||
|
};
|
||
|
|
||
|
export const getAbsoluteAttachmentPath = (relativePath?: string): string => {
|
||
|
if (!internalGetAbsoluteAttachmentPath) {
|
||
|
throw new Error('attachment logic not initialized');
|
||
|
}
|
||
|
return internalGetAbsoluteAttachmentPath(relativePath || '');
|
||
|
};
|
||
|
|
||
|
export const deleteOnDisk = async (relativePath: string): Promise<void> => {
|
||
|
if (!internalDeleteOnDisk) {
|
||
|
throw new Error('attachment logic not initialized');
|
||
|
}
|
||
|
return internalDeleteOnDisk(relativePath);
|
||
|
};
|
||
|
|
||
|
export const writeNewAttachmentData = async (arrayBuffer: ArrayBuffer): Promise<string> => {
|
||
|
if (!internalWriteNewAttachmentData) {
|
||
|
throw new Error('attachment logic not initialized');
|
||
|
}
|
||
|
return internalWriteNewAttachmentData(arrayBuffer);
|
||
|
};
|
||
|
|
||
|
// type Context :: {
|
||
|
// writeNewAttachmentData :: ArrayBuffer -> Promise (IO Path)
|
||
|
// }
|
||
|
//
|
||
|
// migrateDataToFileSystem :: Attachment ->
|
||
|
// Context ->
|
||
|
// Promise Attachment
|
||
|
export const migrateDataToFileSystem = async (data?: ArrayBuffer) => {
|
||
|
const hasDataField = !isUndefined(data);
|
||
|
|
||
|
if (!hasDataField) {
|
||
|
throw new Error('attachment has no data in migrateDataToFileSystem');
|
||
|
}
|
||
|
|
||
|
const isValidData = isArrayBuffer(data);
|
||
|
if (!isValidData) {
|
||
|
throw new TypeError(`Expected ${data} to be an array buffer got: ${typeof data}`);
|
||
|
}
|
||
|
|
||
|
const path = await writeNewAttachmentData(data);
|
||
|
|
||
|
return path;
|
||
|
};
|
||
|
|
||
|
export async function deleteExternalFilesOfConversation(conversation: {
|
||
|
avatar: any;
|
||
|
profileAvatar: any;
|
||
|
}) {
|
||
|
if (!conversation) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const { avatar, profileAvatar } = conversation;
|
||
|
|
||
|
if (avatar && avatar.path) {
|
||
|
await deleteOnDisk(avatar.path);
|
||
|
}
|
||
|
|
||
|
if (profileAvatar && profileAvatar.path) {
|
||
|
await deleteOnDisk(profileAvatar.path);
|
||
|
}
|
||
|
}
|