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.
		
		
		
		
		
			
		
			
				
	
	
		
			261 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			261 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			TypeScript
		
	
import { ipcRenderer } from 'electron';
 | 
						|
import { isArrayBuffer, isEmpty, isUndefined, omit } from 'lodash';
 | 
						|
import {
 | 
						|
  createAbsolutePathGetter,
 | 
						|
  createReader,
 | 
						|
  createWriterForNew,
 | 
						|
} from '../util/attachments_files';
 | 
						|
import { createDeleter, getAttachmentsPath } from '../shared/attachments/shared_attachments';
 | 
						|
import {
 | 
						|
  autoOrientJPEGAttachment,
 | 
						|
  captureDimensionsAndScreenshot,
 | 
						|
  deleteData,
 | 
						|
  loadData,
 | 
						|
  replaceUnicodeV2,
 | 
						|
} from './attachments/migrations';
 | 
						|
 | 
						|
// tslint:disable: prefer-object-spread
 | 
						|
 | 
						|
// FIXME audric
 | 
						|
// upgrade: exports._mapAttachments(autoOrientJPEGAttachment),
 | 
						|
// upgrade: exports._mapAttachments(replaceUnicodeOrderOverrides),
 | 
						|
// upgrade: _mapAttachments(migrateDataToFileSystem),
 | 
						|
// upgrade: ._mapQuotedAttachments(migrateDataToFileSystem),
 | 
						|
// upgrade: initializeAttachmentMetadata,
 | 
						|
// upgrade: initializeAttachmentMetadata,
 | 
						|
// upgrade: _mapAttachments(captureDimensionsAndScreenshot),
 | 
						|
// upgrade: _mapAttachments(replaceUnicodeV2),
 | 
						|
// upgrade: _mapPreviewAttachments(migrateDataToFileSystem),
 | 
						|
 | 
						|
// I think this is only used on the renderer side, but how?!
 | 
						|
 | 
						|
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 async function initializeAttachmentLogic() {
 | 
						|
  const userDataPath = await ipcRenderer.invoke('get-user-data-path');
 | 
						|
 | 
						|
  // const userDataPath = remote.app.getPath('userData');
 | 
						|
  if (attachmentsPath) {
 | 
						|
    throw new Error('attachmentsPath already initialized');
 | 
						|
  }
 | 
						|
 | 
						|
  if (!userDataPath || userDataPath.length <= 10) {
 | 
						|
    throw new Error('userDataPath cannot have length <= 10');
 | 
						|
  }
 | 
						|
  attachmentsPath = getAttachmentsPath(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): Promise<Array<any>> => {
 | 
						|
  if (!preview || !preview.length || isEmpty(preview[0])) {
 | 
						|
    return [];
 | 
						|
  }
 | 
						|
 | 
						|
  const firstPreview = preview[0];
 | 
						|
  if (!firstPreview.image) {
 | 
						|
    return [firstPreview];
 | 
						|
  }
 | 
						|
 | 
						|
  return [
 | 
						|
    {
 | 
						|
      ...firstPreview,
 | 
						|
      image: await loadAttachmentData(firstPreview.image),
 | 
						|
    },
 | 
						|
  ];
 | 
						|
};
 | 
						|
 | 
						|
export const loadQuoteData = async (quote: any) => {
 | 
						|
  if (!quote) {
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
  if (!quote.attachments?.length || isEmpty(quote.attachments[0])) {
 | 
						|
    return quote;
 | 
						|
  }
 | 
						|
 | 
						|
  const quotedFirstAttachment = await quote.attachments[0];
 | 
						|
 | 
						|
  const { thumbnail } = quotedFirstAttachment;
 | 
						|
 | 
						|
  if (!thumbnail || !thumbnail.path) {
 | 
						|
    return {
 | 
						|
      ...quote,
 | 
						|
      attachments: [quotedFirstAttachment],
 | 
						|
    };
 | 
						|
  }
 | 
						|
  const quotedAttachmentWithThumbnail = {
 | 
						|
    ...quotedFirstAttachment,
 | 
						|
    thumbnail: await loadAttachmentData(thumbnail),
 | 
						|
  };
 | 
						|
 | 
						|
  return {
 | 
						|
    ...quote,
 | 
						|
    attachments: [quotedAttachmentWithThumbnail],
 | 
						|
  };
 | 
						|
};
 | 
						|
 | 
						|
export const processNewAttachment = async (attachment: {
 | 
						|
  fileName?: string;
 | 
						|
  contentType: string;
 | 
						|
  data: ArrayBuffer;
 | 
						|
  digest?: string;
 | 
						|
  path?: string;
 | 
						|
  isRaw?: boolean;
 | 
						|
}) => {
 | 
						|
  const fileName = attachment.fileName ? replaceUnicodeV2(attachment.fileName) : '';
 | 
						|
  // this operation might change the size (as we might print the content to a canvas and get the data back)
 | 
						|
  const rotatedData = await autoOrientJPEGAttachment(attachment);
 | 
						|
 | 
						|
  const onDiskAttachmentPath = await migrateDataToFileSystem(rotatedData.data);
 | 
						|
 | 
						|
  const attachmentWithoutData = omit({ ...attachment, fileName, path: onDiskAttachmentPath }, [
 | 
						|
    'data',
 | 
						|
  ]);
 | 
						|
  if (rotatedData.shouldDeleteDigest) {
 | 
						|
    delete attachmentWithoutData.digest;
 | 
						|
  }
 | 
						|
  const finalAttachment = await captureDimensionsAndScreenshot(attachmentWithoutData);
 | 
						|
 | 
						|
  return { ...finalAttachment, fileName, size: rotatedData.data.byteLength };
 | 
						|
};
 | 
						|
 | 
						|
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);
 | 
						|
  }
 | 
						|
}
 |