feat: add job on start option for icons updater (#442)
This commit is contained in:
@@ -8,83 +8,81 @@ import { logger } from "@homarr/log";
|
|||||||
import { EVERY_WEEK } from "~/lib/cron-job/constants";
|
import { EVERY_WEEK } from "~/lib/cron-job/constants";
|
||||||
import { createCronJob } from "~/lib/cron-job/creator";
|
import { createCronJob } from "~/lib/cron-job/creator";
|
||||||
|
|
||||||
export const iconsUpdaterJob = createCronJob(EVERY_WEEK).withCallback(
|
export const iconsUpdaterJob = createCronJob(EVERY_WEEK, {
|
||||||
async () => {
|
runOnStart: true,
|
||||||
logger.info(`Updating icon repository cache...`);
|
}).withCallback(async () => {
|
||||||
const stopWatch = new Stopwatch();
|
logger.info("Updating icon repository cache...");
|
||||||
const repositoryIconGroups = await fetchIconsAsync();
|
const stopWatch = new Stopwatch();
|
||||||
const countIcons = repositoryIconGroups
|
const repositoryIconGroups = await fetchIconsAsync();
|
||||||
.map((group) => group.icons.length)
|
const countIcons = repositoryIconGroups
|
||||||
.reduce((partialSum, arrayLength) => partialSum + arrayLength, 0);
|
.map((group) => group.icons.length)
|
||||||
logger.info(
|
.reduce((partialSum, arrayLength) => partialSum + arrayLength, 0);
|
||||||
`Successfully fetched ${countIcons} icons from ${repositoryIconGroups.length} repositories within ${stopWatch.getElapsedInHumanWords()}`,
|
logger.info(
|
||||||
);
|
`Successfully fetched ${countIcons} icons from ${repositoryIconGroups.length} repositories within ${stopWatch.getElapsedInHumanWords()}`,
|
||||||
|
);
|
||||||
|
|
||||||
const databaseIconGroups = await db.query.iconRepositories.findMany({
|
const databaseIconGroups = await db.query.iconRepositories.findMany({
|
||||||
with: {
|
with: {
|
||||||
icons: true,
|
icons: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const skippedChecksums: string[] = [];
|
const skippedChecksums: string[] = [];
|
||||||
let countDeleted = 0;
|
let countDeleted = 0;
|
||||||
let countInserted = 0;
|
let countInserted = 0;
|
||||||
|
|
||||||
logger.info(`Updating icons in database...`);
|
logger.info("Updating icons in database...");
|
||||||
stopWatch.reset();
|
stopWatch.reset();
|
||||||
|
|
||||||
await db.transaction(async (transaction) => {
|
await db.transaction(async (transaction) => {
|
||||||
for (const repositoryIconGroup of repositoryIconGroups) {
|
for (const repositoryIconGroup of repositoryIconGroups) {
|
||||||
if (!repositoryIconGroup.success) {
|
if (!repositoryIconGroup.success) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const repositoryInDb = databaseIconGroups.find(
|
||||||
|
(dbIconGroup) => dbIconGroup.slug === repositoryIconGroup.slug,
|
||||||
|
);
|
||||||
|
const repositoryIconGroupId: string = repositoryInDb?.id ?? createId();
|
||||||
|
if (!repositoryInDb?.id) {
|
||||||
|
await transaction.insert(iconRepositories).values({
|
||||||
|
id: repositoryIconGroupId,
|
||||||
|
slug: repositoryIconGroup.slug,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const icon of repositoryIconGroup.icons) {
|
||||||
|
if (
|
||||||
|
databaseIconGroups
|
||||||
|
.flatMap((group) => group.icons)
|
||||||
|
.some((dbIcon) => dbIcon.checksum === icon.checksum)
|
||||||
|
) {
|
||||||
|
skippedChecksums.push(icon.checksum);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const repositoryInDb = databaseIconGroups.find(
|
await transaction.insert(icons).values({
|
||||||
(dbIconGroup) => dbIconGroup.slug === repositoryIconGroup.slug,
|
id: createId(),
|
||||||
);
|
checksum: icon.checksum,
|
||||||
const repositoryIconGroupId: string = repositoryInDb?.id ?? createId();
|
name: icon.fileNameWithExtension,
|
||||||
if (!repositoryInDb?.id) {
|
url: icon.imageUrl.href,
|
||||||
await transaction.insert(iconRepositories).values({
|
iconRepositoryId: repositoryIconGroupId,
|
||||||
id: repositoryIconGroupId,
|
});
|
||||||
slug: repositoryIconGroup.slug,
|
countInserted++;
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const icon of repositoryIconGroup.icons) {
|
|
||||||
if (
|
|
||||||
databaseIconGroups
|
|
||||||
.flatMap((group) => group.icons)
|
|
||||||
.some((dbIcon) => dbIcon.checksum == icon.checksum)
|
|
||||||
) {
|
|
||||||
skippedChecksums.push(icon.checksum);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await transaction.insert(icons).values({
|
|
||||||
id: createId(),
|
|
||||||
checksum: icon.checksum,
|
|
||||||
name: icon.fileNameWithExtension,
|
|
||||||
url: icon.imageUrl.href,
|
|
||||||
iconRepositoryId: repositoryIconGroupId,
|
|
||||||
});
|
|
||||||
countInserted++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const deadIcons = databaseIconGroups
|
const deadIcons = databaseIconGroups
|
||||||
.flatMap((group) => group.icons)
|
.flatMap((group) => group.icons)
|
||||||
.filter((icon) => !skippedChecksums.includes(icon.checksum));
|
.filter((icon) => !skippedChecksums.includes(icon.checksum));
|
||||||
|
|
||||||
for (const icon of deadIcons) {
|
for (const icon of deadIcons) {
|
||||||
await transaction
|
await transaction.delete(icons).where(eq(icons.checksum, icon.checksum));
|
||||||
.delete(icons)
|
countDeleted++;
|
||||||
.where(eq(icons.checksum, icon.checksum));
|
}
|
||||||
countDeleted++;
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`Updated database within ${stopWatch.getElapsedInHumanWords()} (-${countDeleted}, +${countInserted})`,
|
`Updated database within ${stopWatch.getElapsedInHumanWords()} (-${countDeleted}, +${countInserted})`,
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|||||||
@@ -2,9 +2,20 @@ import cron from "node-cron";
|
|||||||
|
|
||||||
import type { MaybePromise } from "@homarr/common/types";
|
import type { MaybePromise } from "@homarr/common/types";
|
||||||
|
|
||||||
export const createCronJob = (cronExpression: string) => {
|
interface CreateCronJobOptions {
|
||||||
|
runOnStart?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createCronJob = (
|
||||||
|
cronExpression: string,
|
||||||
|
options: CreateCronJobOptions = { runOnStart: false },
|
||||||
|
) => {
|
||||||
return {
|
return {
|
||||||
withCallback: (callback: () => MaybePromise<void>) => {
|
withCallback: (callback: () => MaybePromise<void>) => {
|
||||||
|
if (options.runOnStart) {
|
||||||
|
void callback();
|
||||||
|
}
|
||||||
|
|
||||||
const task = cron.schedule(cronExpression, () => void callback(), {
|
const task = cron.schedule(cronExpression, () => void callback(), {
|
||||||
scheduled: false,
|
scheduled: false,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user