Skip to content

Commit

Permalink
Merge stable into release
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmz committed Feb 19, 2025
2 parents 4718c77 + b6086ff commit 0073272
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.24.0] - Not released

## [2.23.8] - 2025-02-17
### Fixed
- Fix the case when the uploaded video original is for some reason missing at
Expand Down
1 change: 1 addition & 0 deletions app/models.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export class Attachment {
maxSizedVariant(mediaType: 'image' | 'video'): string | null;
downloadOriginal(): Promise<string>;
sanitizeOriginal(): Promise<boolean>;
recreatePreviews(): Promise<boolean>;
destroy(destroyedBy?: User): Promise<void>;
}

Expand Down
69 changes: 69 additions & 0 deletions app/models/attachment.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,75 @@ export function addModel(dbAdapter) {
}
}

/**
* Re-create previews for this attachment. The image original is downloaded
* from the storage and all previews are generated from scratch. Next, old
* previews are deleted and new ones are uploaded. The updated attachment
* props are updated in the DB.
*
* This method is useful for re-encoding of the old attachments, that have
* 'image_sizes' field instead of 'previews'.
*
* Only the 'image' type (and not SVG or animated GIF) is supported at the
* moment.
*
* @returns {Promise<void>}
*/
async recreatePreviews() {
if (this.mediaType !== 'image' || this.fileExtension === 'svg') {
throw new Error('Unsupported media type');
}

const originalPath = await this.downloadOriginal();
const { files = {}, ...mediaData } = await processMediaFile(originalPath, this.fileName);

try {
if (mediaData.mediaType !== 'image') {
// It may happen if the original file is an animated gif
throw new Error(
`The resulting media type is not an image (got '${mediaData.mediaType}')`,
);
}

// We may already have some previews. We need to keep them (to keep all
// possible existing links) and don't delete or even replace them. We
// also shouldn't re-upload the original file.
const filesToUpload = {};

for (const [variant, info] of Object.entries(files)) {
if (this.previews.image[variant]) {
// This variant already exists, keep it as is
mediaData.previews.image[variant] = this.previews.image[variant];
} else if (variant === '') {
// Skip the original
} else {
filesToUpload[variant] = info;
}
}

await this._placeFiles(filesToUpload);
await dbAdapter.updateAttachment(this.id, {
...mediaData,
imageSizes: null,
updatedAt: 'now',
});
} finally {
// Remove the rest of local files
const paths = [originalPath, ...Object.values(files).map(({ path }) => path)];
await Promise.all(
paths.map((path) => {
try {
fs.unlink(path);
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
}),
);
}
}

/**
* Upload or move processed files (original or previews)
*
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "freefeed-server",
"description": "FreeFeed is an open source FriendFeed clone (yes, it is free and open!) based on Pepyatka open-source FriendFeed clone (yes, that one is also free and open). Basically, this is a social real-time feed aggregator that allows you to share cute kittens, coordinate upcoming events, discuss any other cool stuff on the Internet or setup a private Pepyatka instance in your company.",
"homepage": "https://freefeed.net",
"version": "2.23.8",
"version": "2.24.0",
"private": true,
"scripts": {
"start": "cross-env TZ=UTC yarn babel index.js",
Expand Down
37 changes: 37 additions & 0 deletions test/integration/models/attachment.js
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,43 @@ describe('Attachments', () => {
await expect(createAttachment(testFiles.mov, post, user), 'to be fulfilled');
});
});

describe('Recreate previews for old-style attachments', () => {
it('should recreate previews for old-style attachments', async () => {
const { id } = await createAndCheckAttachment(testFiles.large, post, user);
await dbAdapter.updateAttachment(id, {
previews: null,
width: null,
height: null,
imageSizes: {
o: { w: 1500, h: 1000, url: `https://example.com/attachments/${id}.png` },
t: { w: 263, h: 175, url: `https://example.com/attachments/thumbnails/${id}.png` },
t2: { w: 525, h: 350, url: `https://example.com/attachments/thumbnails2/${id}.png` },
},
});
const att = await dbAdapter.getAttachmentById(id);
await att.recreatePreviews();
const row = await dbAdapter.database.getRow(`select * from attachments where uid = :id`, {
id,
});

expect(row, 'to satisfy', {
previews: {
image: {
'': { h: 1000, w: 1500, ext: 'png' },
p1: { h: 283, w: 424, ext: 'webp' },
p2: { h: 516, w: 775, ext: 'webp' },
// Should not be replaced by webp
thumbnails: { h: 175, w: 263, ext: 'png' },
thumbnails2: { h: 350, w: 525, ext: 'png' },
},
},
width: 1500,
height: 1000,
image_sizes: null,
});
});
});
});

async function uploadFile(fileObject) {
Expand Down

0 comments on commit 0073272

Please sign in to comment.