gallery_server_client_s190js/tools/runFaceClusteringScheduler.js
2026-10-26 11:00:14 +01:00

188 lines
2.8 KiB
JavaScript

// tools/runFaceClusteringScheduler.js
const db = require('../db/knex');
const { spawn } = require('child_process');
let running = false;
async function getUsersNeedingClustering() {
const result = await db.raw(`
SELECT
p.user
FROM faces f
JOIN photos p
ON p.id = f.photo_id
LEFT JOIN (
SELECT
user,
MAX(completed_at) AS last_cluster
FROM face_cluster_runs
GROUP BY user
) r
ON r.user = p.user
GROUP BY p.user
HAVING
MAX(f.created_at)
>
COALESCE(
MAX(r.last_cluster),
'1970-01-01'
)
`);
return Array.isArray(result)
? result
: result?.rows || [];
}
function clusterUser(user) {
return new Promise((resolve, reject) => {
console.log(
'[clusterScheduler] start:',
user,
);
const child = spawn(
process.execPath,
[
'tools/runFaceClustering.js',
'--user',
user,
'--persist',
],
{
stdio: 'inherit',
},
);
child.on('exit', (code) => {
if (code === 0) {
console.log(
'[clusterScheduler] done:',
user,
);
resolve();
return;
}
reject(
new Error(
`Clustering exit code ${code}`,
),
);
});
child.on('error', reject);
});
}
async function clusterUserIfNeeded(user) {
const result = await db.raw(
`
SELECT
MAX(f.created_at) AS last_face,
(
SELECT MAX(completed_at)
FROM face_cluster_runs
WHERE user = ?
) AS last_cluster
FROM faces f
JOIN photos p
ON p.id = f.photo_id
WHERE p.user = ?
`,
[user, user],
);
const rows =
Array.isArray(result)
? result
: result?.rows || [];
const row = rows[0];
if (!row) {
return false;
}
const lastFace =
row.last_face;
const lastCluster =
row.last_cluster;
if (!lastFace) {
return false;
}
if (
lastCluster
&& new Date(lastFace)
<= new Date(lastCluster)
) {
console.log(
'[clusterScheduler] already up to date:',
user,
);
return false;
}
console.log(
'[clusterScheduler] clustering required:',
user,
);
await clusterUser(user);
return true;
}
async function runCycle() {
if (running) {
return;
}
running = true;
try {
const users =
await getUsersNeedingClustering();
if (!users.length) {
console.log(
'[clusterScheduler] no users to cluster',
);
return;
}
for (const row of users) {
const user =
row.user || row.USER;
if (!user) {
continue;
}
await clusterUser(user);
}
} finally {
running = false;
}
}
module.exports = {
runCycle,
clusterUserIfNeeded,
};