Convert and resize avatars to fit the displayed images
This commit is contained in:
@ -1,14 +1,12 @@
|
||||
import { IGNORE_USERS, MASTODON_INSTANCE } from '$env/static/private';
|
||||
import { enableVerboseLog, log } from '$lib/log';
|
||||
import type { Account, Post, Tag } from '$lib/mastodon/response';
|
||||
import type { Account, AccountAvatar, Post, Tag } from '$lib/mastodon/response';
|
||||
import type { SongInfo } from '$lib/odesliResponse';
|
||||
import { TimelineReader } from '$lib/server/timeline';
|
||||
import sqlite3 from 'sqlite3';
|
||||
|
||||
const { DEV } = import.meta.env;
|
||||
|
||||
type FilterParameter = {
|
||||
$limit: number | undefined | null;
|
||||
$limit?: number | undefined | null;
|
||||
$since?: string | undefined | null;
|
||||
$before?: string | undefined | null;
|
||||
[x: string]: string | number | undefined | null;
|
||||
@ -44,6 +42,12 @@ type SongRow = {
|
||||
thumbnailUrl?: string;
|
||||
};
|
||||
|
||||
type AccountAvatarRow = {
|
||||
account_url: string;
|
||||
file: string;
|
||||
sizeDescriptor: string;
|
||||
};
|
||||
|
||||
type Migration = {
|
||||
id: number;
|
||||
name: string;
|
||||
@ -270,6 +274,17 @@ function getMigrations(): Migration[] {
|
||||
id: 4,
|
||||
name: 'song info for existing posts',
|
||||
statement: ``
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'resized avatars',
|
||||
statement: `
|
||||
CREATE TABLE accountsavatars (
|
||||
file TEXT NOT NULL PRIMARY KEY,
|
||||
account_url TEXT NOT NULL,
|
||||
sizeDescriptor TEXT NOT NULL,
|
||||
FOREIGN KEY (account_url) REFERENCES accounts(url)
|
||||
);`
|
||||
}
|
||||
];
|
||||
}
|
||||
@ -278,13 +293,9 @@ async function waitReady(): Promise<void> {
|
||||
// Simpler than a semaphore and is really only needed on startup
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (DEV) {
|
||||
log.debug('Waiting for database to be ready');
|
||||
}
|
||||
log.verbose('Waiting for database to be ready');
|
||||
if (databaseReady) {
|
||||
if (DEV) {
|
||||
log.debug('DB is ready');
|
||||
}
|
||||
log.verbose('DB is ready');
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
@ -540,6 +551,40 @@ function getSongData(postIdsParams: String, postIds: string[]): Promise<Map<stri
|
||||
});
|
||||
}
|
||||
|
||||
function getAvatarData(
|
||||
accountUrlsParams: String,
|
||||
accountUrls: string[]
|
||||
): Promise<Map<string, AccountAvatar[]>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT account_url, file, sizeDescriptor
|
||||
FROM accountsavatars
|
||||
WHERE account_url IN (${accountUrlsParams});`,
|
||||
accountUrls,
|
||||
(err, rows: AccountAvatarRow[]) => {
|
||||
if (err != null) {
|
||||
log.error('Error loading avatars', err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const avatarMap: Map<string, AccountAvatar[]> = rows.reduce(
|
||||
(result: Map<string, AccountAvatar[]>, item) => {
|
||||
const info: AccountAvatar = {
|
||||
accountUrl: item.account_url,
|
||||
file: item.file,
|
||||
sizeDescriptor: item.sizeDescriptor
|
||||
};
|
||||
result.set(item.account_url, [...(result.get(item.account_url) || []), info]);
|
||||
return result;
|
||||
},
|
||||
new Map()
|
||||
);
|
||||
resolve(avatarMap);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPosts(
|
||||
since: string | null,
|
||||
before: string | null,
|
||||
@ -589,6 +634,11 @@ async function getPostsInternal(
|
||||
const postIds = rows.map((r: PostRow) => r.url);
|
||||
const tagMap = await getTagData(postIdsParams, postIds);
|
||||
const songMap = await getSongData(postIdsParams, postIds);
|
||||
|
||||
const accountUrls = [...new Set(rows.map((r: PostRow) => r.account_url))];
|
||||
const accountUrlsParams = accountUrls.map(() => '?').join(', ');
|
||||
|
||||
const avatars = await getAvatarData(accountUrlsParams, accountUrls);
|
||||
const posts = rows.map((row) => {
|
||||
return {
|
||||
id: row.id,
|
||||
@ -602,10 +652,96 @@ async function getPostsInternal(
|
||||
username: row.username,
|
||||
display_name: row.display_name,
|
||||
url: row.account_url,
|
||||
avatar: row.avatar
|
||||
avatar: row.avatar,
|
||||
resizedAvatars: avatars.get(row.account_url) || []
|
||||
} as Account,
|
||||
songs: songMap.get(row.url) || []
|
||||
} as Post;
|
||||
});
|
||||
return posts;
|
||||
}
|
||||
|
||||
export async function removeAvatars(accountUrl: string): Promise<void> {
|
||||
const params: FilterParameter = { $account: accountUrl };
|
||||
const sql = `
|
||||
DELETE
|
||||
FROM accountsavatars
|
||||
WHERE account_url = $account`;
|
||||
await waitReady();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveAvatar(avatar: AccountAvatar): Promise<void> {
|
||||
// Will be null if file already existed
|
||||
if (avatar.file === null) {
|
||||
return;
|
||||
}
|
||||
const params: FilterParameter = {
|
||||
$accountUrl: avatar.accountUrl,
|
||||
$file: avatar.file,
|
||||
$sizeDescriptor: avatar.sizeDescriptor
|
||||
};
|
||||
const sql = `
|
||||
INSERT INTO accountsavatars
|
||||
(account_url, file, sizeDescriptor) VALUES ($accountUrl, $file, $sizeDescriptor)
|
||||
ON CONFLICT(file) DO UPDATE SET
|
||||
account_url=excluded.account_url,
|
||||
sizeDescriptor=excluded.sizeDescriptor;`;
|
||||
await waitReady();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAvatars(
|
||||
accountUrl: string,
|
||||
limit: number | undefined
|
||||
): Promise<AccountAvatar[]> {
|
||||
// TODO: Refactor to use `getAvatarData`
|
||||
await waitReady();
|
||||
let limitFilter = '';
|
||||
const params: FilterParameter = {
|
||||
$account: accountUrl,
|
||||
$limit: 100
|
||||
};
|
||||
if (limit !== undefined) {
|
||||
limitFilter = 'LIMIT $limit';
|
||||
params.$limit = limit;
|
||||
}
|
||||
const sql = `
|
||||
SELECT account_url, file, sizeDescriptor
|
||||
FROM accountsavatars
|
||||
WHERE account_url = $account
|
||||
${limitFilter};`;
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(sql, params, (err, rows: AccountAvatarRow[]) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(
|
||||
rows.map((r) => {
|
||||
return {
|
||||
accountUrl: r.account_url,
|
||||
file: r.file,
|
||||
sizeDescriptor: r.sizeDescriptor
|
||||
} as AccountAvatar;
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user