mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-21 00:48:19 +01:00
Reuse files on disk for outgoing messages
This commit is contained in:
@@ -5,8 +5,8 @@ import createDebug from 'debug';
|
||||
import { assert } from 'chai';
|
||||
import { expect } from 'playwright/test';
|
||||
import { type PrimaryDevice, StorageState } from '@signalapp/mock-server';
|
||||
import * as path from 'node:path';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { access, readFile } from 'node:fs/promises';
|
||||
|
||||
import type { App } from '../playwright.node.js';
|
||||
import { Bootstrap } from '../bootstrap.node.js';
|
||||
@@ -20,10 +20,11 @@ import * as durations from '../../util/durations/index.std.js';
|
||||
import { strictAssert } from '../../util/assert.std.js';
|
||||
import { VIDEO_MP4 } from '../../types/MIME.std.js';
|
||||
import { toBase64 } from '../../Bytes.std.js';
|
||||
import type { AttachmentType } from '../../types/Attachment.std.js';
|
||||
|
||||
export const debug = createDebug('mock:test:attachments');
|
||||
|
||||
const CAT_PATH = path.join(
|
||||
const CAT_PATH = join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
@@ -31,7 +32,7 @@ const CAT_PATH = path.join(
|
||||
'fixtures',
|
||||
'cat-screenshot.png'
|
||||
);
|
||||
const VIDEO_PATH = path.join(
|
||||
const VIDEO_PATH = join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
@@ -83,12 +84,11 @@ describe('attachments', function (this: Mocha.Suite) {
|
||||
|
||||
await page.getByTestId(pinned.device.aci).click();
|
||||
|
||||
const [attachmentCat] = await sendMessageWithAttachments(
|
||||
page,
|
||||
pinned,
|
||||
'This is my cat',
|
||||
[CAT_PATH]
|
||||
);
|
||||
const {
|
||||
attachments: [attachmentCat],
|
||||
} = await sendMessageWithAttachments(page, pinned, 'This is my cat', [
|
||||
CAT_PATH,
|
||||
]);
|
||||
|
||||
const Message = getTimelineMessageWithText(page, 'This is my cat');
|
||||
const MessageSent = Message.locator(
|
||||
@@ -212,4 +212,189 @@ describe('attachments', function (this: Mocha.Suite) {
|
||||
assert.strictEqual(attachmentInDB?.chunkSize, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
it('reuses attachment file when receiving and sending same video and deletes only when all references removed', async () => {
|
||||
const page = await app.getWindow();
|
||||
|
||||
const { desktop, phone } = bootstrap;
|
||||
await page.getByTestId(pinned.device.aci).click();
|
||||
|
||||
const plaintextVideo = await readFile(VIDEO_PATH);
|
||||
const ciphertextVideo = await bootstrap.encryptAndStoreAttachmentOnCDN(
|
||||
plaintextVideo,
|
||||
VIDEO_MP4
|
||||
);
|
||||
|
||||
const ciphertextVideo2 = await bootstrap.encryptAndStoreAttachmentOnCDN(
|
||||
plaintextVideo,
|
||||
VIDEO_MP4
|
||||
);
|
||||
|
||||
const phoneTimestamp = bootstrap.getTimestamp();
|
||||
const friendTimestamp = bootstrap.getTimestamp();
|
||||
|
||||
debug('receiving attachment from primary');
|
||||
await page.getByTestId(pinned.device.aci).click();
|
||||
await sendTextMessage({
|
||||
from: phone,
|
||||
to: pinned,
|
||||
text: 'from phone',
|
||||
desktop,
|
||||
attachments: [{ ...ciphertextVideo, width: 568, height: 320 }],
|
||||
timestamp: phoneTimestamp,
|
||||
});
|
||||
await getMessageInTimelineByTimestamp(page, phoneTimestamp)
|
||||
.locator('.module-image--loaded')
|
||||
.waitFor();
|
||||
|
||||
debug('receiving same attachment from contact');
|
||||
await page.getByTestId(pinned.device.aci).click();
|
||||
await sendTextMessage({
|
||||
from: pinned,
|
||||
to: desktop,
|
||||
text: 'from friend',
|
||||
desktop,
|
||||
attachments: [{ ...ciphertextVideo2, width: 568, height: 320 }],
|
||||
timestamp: friendTimestamp,
|
||||
});
|
||||
|
||||
await getMessageInTimelineByTimestamp(page, friendTimestamp)
|
||||
.locator('.module-image--loaded')
|
||||
.waitFor();
|
||||
|
||||
debug('sending same attachment from desktop');
|
||||
const { timestamp: sentTimestamp } = await sendMessageWithAttachments(
|
||||
page,
|
||||
pinned,
|
||||
'from desktop',
|
||||
[VIDEO_PATH]
|
||||
);
|
||||
|
||||
strictAssert(sentTimestamp, 'outgoing timestamp must exist');
|
||||
|
||||
const phoneDBMessage = (await app.getMessagesBySentAt(phoneTimestamp))[0];
|
||||
const phoneDBAttachment: AttachmentType = phoneDBMessage.attachments?.[0];
|
||||
|
||||
const friendDBMessage = (await app.getMessagesBySentAt(friendTimestamp))[0];
|
||||
const friendDBAttachment: AttachmentType = friendDBMessage.attachments?.[0];
|
||||
|
||||
const sentDBMessage = (await app.getMessagesBySentAt(sentTimestamp))[0];
|
||||
const sentDBAttachment: AttachmentType = sentDBMessage.attachments?.[0];
|
||||
|
||||
strictAssert(phoneDBAttachment, 'outgoing sync message exists in DB');
|
||||
strictAssert(friendDBAttachment, 'incoming message exists in DB');
|
||||
strictAssert(sentDBAttachment, 'sent message exists in DB');
|
||||
|
||||
const { path } = phoneDBAttachment;
|
||||
const thumbnailPath = phoneDBAttachment.thumbnail?.path;
|
||||
|
||||
strictAssert(path, 'path exists');
|
||||
strictAssert(thumbnailPath, 'thumbnail path exists');
|
||||
|
||||
debug(
|
||||
'checking that incoming and outgoing messages deduplicated data on disk'
|
||||
);
|
||||
assert.strictEqual(phoneDBAttachment.path, path);
|
||||
assert.strictEqual(friendDBAttachment.path, path);
|
||||
assert.strictEqual(sentDBAttachment.path, path);
|
||||
|
||||
assert.strictEqual(phoneDBAttachment.thumbnail?.path, thumbnailPath);
|
||||
assert.strictEqual(friendDBAttachment.thumbnail?.path, thumbnailPath);
|
||||
assert.strictEqual(sentDBAttachment.thumbnail?.path, thumbnailPath);
|
||||
|
||||
debug('deleting two of the messages');
|
||||
const sentMessage = getMessageInTimelineByTimestamp(page, sentTimestamp);
|
||||
await sentMessage.click({ button: 'right' });
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
await page.getByRole('button', { name: 'Delete for me' }).click();
|
||||
|
||||
await expect(sentMessage).not.toBeVisible();
|
||||
|
||||
const friendMessage = getMessageInTimelineByTimestamp(
|
||||
page,
|
||||
friendTimestamp
|
||||
);
|
||||
await friendMessage.click({ button: 'right' });
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
await page.getByRole('button', { name: 'Delete for me' }).click();
|
||||
await expect(friendMessage).not.toBeVisible();
|
||||
|
||||
await app.waitForMessageToBeCleanedUp(sentDBMessage.id);
|
||||
await app.waitForMessageToBeCleanedUp(friendDBMessage.id);
|
||||
|
||||
assert.strictEqual(
|
||||
(await app.getMessagesBySentAt(friendTimestamp)).length,
|
||||
0
|
||||
);
|
||||
assert.strictEqual(
|
||||
(await app.getMessagesBySentAt(sentTimestamp)).length,
|
||||
0
|
||||
);
|
||||
// Path still exists!
|
||||
await access(bootstrap.getAbsoluteAttachmentPath(path));
|
||||
|
||||
debug('delete last of messages');
|
||||
const phoneMessage = getMessageInTimelineByTimestamp(page, phoneTimestamp);
|
||||
await phoneMessage.click({ button: 'right' });
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Delete for me' }).click();
|
||||
await expect(phoneMessage).not.toBeVisible();
|
||||
|
||||
await app.waitForMessageToBeCleanedUp(phoneDBMessage.id);
|
||||
assert.strictEqual(
|
||||
(await app.getMessagesBySentAt(phoneTimestamp)).length,
|
||||
0
|
||||
);
|
||||
|
||||
await expect(
|
||||
access(bootstrap.getAbsoluteAttachmentPath(path))
|
||||
).rejects.toMatchObject({
|
||||
code: 'ENOENT',
|
||||
});
|
||||
});
|
||||
|
||||
it('reencodes images to same data and reuses same file when sending', async () => {
|
||||
const page = await app.getWindow();
|
||||
|
||||
await page.getByTestId(pinned.device.aci).click();
|
||||
|
||||
const { timestamp: firstTimestamp } = await sendMessageWithAttachments(
|
||||
page,
|
||||
pinned,
|
||||
'first send',
|
||||
[CAT_PATH]
|
||||
);
|
||||
|
||||
const { timestamp: secondTimestamp } = await sendMessageWithAttachments(
|
||||
page,
|
||||
pinned,
|
||||
'second send',
|
||||
[CAT_PATH]
|
||||
);
|
||||
|
||||
const firstAttachment: AttachmentType = (
|
||||
await app.getMessagesBySentAt(firstTimestamp)
|
||||
)[0].attachments?.[0];
|
||||
|
||||
const secondAttachment: AttachmentType = (
|
||||
await app.getMessagesBySentAt(secondTimestamp)
|
||||
)[0].attachments?.[0];
|
||||
|
||||
strictAssert(firstAttachment, 'firstAttachment exists in DB');
|
||||
strictAssert(secondAttachment, 'secondAttachment exists in DB');
|
||||
|
||||
debug(
|
||||
'checking that incoming and outgoing messages deduplicated data on disk'
|
||||
);
|
||||
assert.strictEqual(firstAttachment.path, secondAttachment.path);
|
||||
assert.strictEqual(firstAttachment.localKey, secondAttachment.localKey);
|
||||
assert.strictEqual(
|
||||
firstAttachment.thumbnail?.path,
|
||||
secondAttachment.thumbnail?.path
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user