Fix issue with duplicate images (#30073)

* ensure export file paths are unique

* add unit test for filepath uniqueness. fix createMessagesRequest mock.

* add return types
This commit is contained in:
Matt Lewis
2025-06-04 13:54:47 +01:00
committed by GitHub
parent f97df3eb3b
commit 9c0604f849
2 changed files with 60 additions and 3 deletions

View File

@@ -25,8 +25,17 @@ type BlobFile = {
blob: Blob;
};
type FileDetails = {
directory: string;
name: string;
date: string;
extension: string;
count?: number;
};
export default abstract class Exporter {
protected files: BlobFile[] = [];
protected fileNames: Map<string, number> = new Map();
protected cancelled = false;
protected constructor(
@@ -241,6 +250,19 @@ export default abstract class Exporter {
return [fileName, "." + ext];
}
protected makeUniqueFilePath(details: FileDetails): string {
const makePath = ({ directory, name, date, extension, count = 0 }: FileDetails): string =>
`${directory}/${name}-${date}${count > 0 ? ` (${count})` : ""}${extension}`;
const defaultPath = makePath(details);
const count = this.fileNames.get(defaultPath) || 0;
this.fileNames.set(defaultPath, count + 1);
if (count > 0) {
return makePath({ ...details, count });
}
return defaultPath;
}
public getFilePath(event: MatrixEvent): string {
const mediaType = event.getContent().msgtype;
let fileDirectory: string;
@@ -263,7 +285,12 @@ export default abstract class Exporter {
if (event.getType() === "m.sticker") fileExt = ".png";
if (isVoiceMessage(event)) fileExt = ".ogg";
return fileDirectory + "/" + fileName + "-" + fileDate + fileExt;
return this.makeUniqueFilePath({
directory: fileDirectory,
name: fileName,
date: fileDate,
extension: fileExt,
});
}
protected isReply(event: MatrixEvent): boolean {

View File

@@ -104,8 +104,8 @@ describe("HTMLExport", () => {
const chunk = events.slice(from, limit);
return Promise.resolve({
chunk,
from: from.toString(),
to: (from + limit).toString(),
start: from.toString(),
end: (from + limit).toString(),
});
});
}
@@ -419,6 +419,36 @@ describe("HTMLExport", () => {
expect(text).toBe(attachmentBody);
});
it("should handle attachments with identical names and dates", async () => {
mockMessages(EVENT_MESSAGE, EVENT_ATTACHMENT, EVENT_ATTACHMENT);
const exporter = new HTMLExporter(
room,
ExportType.LastNMessages,
{
attachmentsIncluded: true,
maxSize: 1_024 * 1_024,
numberOfMessages: 40,
},
() => {},
);
await exporter.export();
const files = getFiles(exporter);
// There should be 5 files: 2 attachments, 1 html file, 1 css file and 1 js file
expect(Object.keys(files)).toHaveLength(5);
// Ensure that the attachment is present
const file = files[Object.keys(files).find((k) => k.endsWith(".txt"))!];
expect(file).not.toBeUndefined();
// Ensure that the duplicate attachment has been uniquely named
const duplicateFile = files[Object.keys(files).find((k) => k.endsWith("(1).txt"))!];
expect(duplicateFile).not.toBeUndefined();
});
it("should handle when attachment cannot be fetched", async () => {
mockMessages(EVENT_MESSAGE, EVENT_ATTACHMENT_MALFORMED, EVENT_ATTACHMENT);
const attachmentBody = "Lorem ipsum dolor sit amet";