Files
Trilium/src/services/content_hash.ts

87 lines
2.7 KiB
TypeScript
Raw Normal View History

2018-04-07 22:59:47 -04:00
"use strict";
import sql from "./sql.js";
import { hash } from "./utils.js";
import log from "./log.js";
import eraseService from "./erase.js";
2024-02-18 12:50:48 +02:00
type SectorHash = Record<string, string>;
2020-06-20 12:31:38 +02:00
function getEntityHashes() {
// blob erasure is not synced, we should check before each sync if there's some blob to erase
eraseService.eraseUnusedBlobs();
const startTime = new Date();
2023-10-20 09:36:57 +02:00
// we know this is slow and the total content hash calculation time is logged
2025-01-09 18:07:02 +02:00
type HashRow = [string, string, string, boolean];
const hashRows = sql.disableSlowQueryLogging(() =>
sql.getRawRows<HashRow>(`
2023-10-20 09:36:57 +02:00
SELECT entityName,
entityId,
hash,
isErased
2023-10-20 09:36:57 +02:00
FROM entity_changes
WHERE isSynced = 1
AND entityName != 'note_reordering'`)
2023-10-20 09:36:57 +02:00
);
// sorting is faster in memory
// sorting by entityId is enough, hashes will be segmented by entityName later on anyway
2025-01-09 18:07:02 +02:00
hashRows.sort((a, b) => (a[1] < b[1] ? -1 : 1));
2024-02-18 12:50:48 +02:00
const hashMap: Record<string, SectorHash> = {};
2023-07-29 21:59:20 +02:00
for (const [entityName, entityId, hash, isErased] of hashRows) {
2025-01-09 18:07:02 +02:00
const entityHashMap = (hashMap[entityName] = hashMap[entityName] || {});
const sector = entityId[0];
2023-07-29 21:59:20 +02:00
// if the entity is erased, its hash is not updated, so it has to be added extra
entityHashMap[sector] = (entityHashMap[sector] || "") + hash + isErased;
}
for (const entityHashMap of Object.values(hashMap)) {
for (const key in entityHashMap) {
entityHashMap[key] = hash(entityHashMap[key]);
}
}
const elapsedTimeMs = Date.now() - startTime.getTime();
log.info(`Content hash computation took ${elapsedTimeMs}ms`);
return hashMap;
2017-11-21 22:11:27 -05:00
}
2024-02-18 12:50:48 +02:00
function checkContentHashes(otherHashes: Record<string, SectorHash>) {
2020-06-20 12:31:38 +02:00
const entityHashes = getEntityHashes();
const failedChecks = [];
2018-04-07 22:59:47 -04:00
for (const entityName in entityHashes) {
2024-02-18 12:50:48 +02:00
const thisSectorHashes: SectorHash = entityHashes[entityName] || {};
const otherSectorHashes: SectorHash = otherHashes[entityName] || {};
2018-04-07 22:59:47 -04:00
2019-12-24 16:00:31 +01:00
const sectors = new Set(Object.keys(thisSectorHashes).concat(Object.keys(otherSectorHashes)));
2019-11-19 19:07:14 +01:00
for (const sector of sectors) {
if (thisSectorHashes[sector] !== otherSectorHashes[sector]) {
log.info(`Content hash check for ${entityName} sector ${sector} FAILED. Local is ${thisSectorHashes[sector]}, remote is ${otherSectorHashes[sector]}`);
failedChecks.push({ entityName, sector });
2018-04-07 22:59:47 -04:00
}
}
}
if (failedChecks.length === 0) {
2018-04-07 22:59:47 -04:00
log.info("Content hash checks PASSED");
}
return failedChecks;
2018-04-07 22:59:47 -04:00
}
export default {
getEntityHashes,
2018-04-07 22:59:47 -04:00
checkContentHashes
2020-06-20 12:31:38 +02:00
};