@ -12,65 +12,67 @@ const NoteRevision = require('../entities/note_revision');
const RecentNote = require ( '../entities/recent_note' ) ;
const Option = require ( '../entities/option' ) ;
async function get Hash( tableName , primaryKeyName , whereBranch ) {
async function get Sector Hashes ( tableName , primaryKeyName , whereBranch ) {
// subselect is necessary to have correct ordering in GROUP_CONCAT
const query = ` SELECT GROUP_CONCAT(hash) FROM (SELECT hash FROM ${ tableName } `
+ ( whereBranch ? ` WHERE ${ whereBranch } ` : '' ) + ` ORDER BY ${ primaryKeyName } ) ` ;
const query = ` SELECT SUBSTR(${ primaryKeyName } , 1, 1), GROUP_CONCAT(hash) FROM ${ tableName } `
+ ( whereBranch ? ` WHERE ${ whereBranch } ` : '' ) + ` GROUP BY SUBSTR(${ primaryKeyName } , 1, 1) ORDER BY ${ primaryKeyName } ` ;
let contentToHash = await sql . getValue ( query ) ;
const map = await sql . getMap ( query ) ;
if ( ! contentToHash ) { // might be null in case of no rows
contentToHash = "" ;
for ( const key in map ) {
map[ key ] = utils . hash ( map [ key ] ) ;
}
return utils. hash ( contentToHash ) ;
return map ;
}
async function get Hashes( ) {
async function get Entity Hashes( ) {
const startTime = new Date ( ) ;
const hashes = {
notes : await get Hash( Note . entityName , Note . primaryKeyName ) ,
note _contents : await get Hash( "note_contents" , "noteId" ) ,
branches : await get Hash( Branch . entityName , Branch . primaryKeyName ) ,
note _revisions : await get Hash( NoteRevision . entityName , NoteRevision . primaryKeyName ) ,
note _revision _contents : await get Hash( "note_revision_contents" , "noteRevisionId" ) ,
recent _notes : await get Hash( RecentNote . entityName , RecentNote . primaryKeyName ) ,
options : await get Hash( Option . entityName , Option . primaryKeyName , "isSynced = 1" ) ,
attributes : await get Hash( Attribute . entityName , Attribute . primaryKeyName ) ,
api _tokens : await get Hash( ApiToken . entityName , ApiToken . primaryKeyName ) ,
notes : await get Sector Hashes ( Note . entityName , Note . primaryKeyName ) ,
note _contents : await get Sector Hashes ( "note_contents" , "noteId" ) ,
branches : await get Sector Hashes ( Branch . entityName , Branch . primaryKeyName ) ,
note _revisions : await get Sector Hashes ( NoteRevision . entityName , NoteRevision . primaryKeyName ) ,
note _revision _contents : await get Sector Hashes ( "note_revision_contents" , "noteRevisionId" ) ,
recent _notes : await get Sector Hashes ( RecentNote . entityName , RecentNote . primaryKeyName ) ,
options : await get Sector Hashes ( Option . entityName , Option . primaryKeyName , "isSynced = 1" ) ,
attributes : await get Sector Hashes ( Attribute . entityName , Attribute . primaryKeyName ) ,
api _tokens : await get Sector Hashes ( ApiToken . entityName , ApiToken . primaryKeyName ) ,
} ;
const elapse TimeMs = Date . now ( ) - startTime . getTime ( ) ;
const elapse d TimeMs = Date . now ( ) - startTime . getTime ( ) ;
log . info ( ` Content hash computation took ${ elapse TimeMs} ms ` ) ;
log . info ( ` Content hash computation took ${ elapse d TimeMs} ms ` ) ;
return hashes ;
}
async function checkContentHashes ( otherHashes ) {
const hashes = await get Hashes( ) ;
let allChecksPassed = true ;
const entityHashes = await getEntity Hashes( ) ;
const failedChecks = [ ] ;
for ( const key in h ashes) {
if ( hashes [ key ] !== otherHashes [ key ] ) {
allChecksPassed = false ;
for ( const entityName in entityH ashes) {
const thisSectorHashes = entityHashes [ entityName ] ;
const otherSectorHashes = otherHashes [ entityName ] ;
log . info ( ` Content hash check for ${ key } FAILED. Local is ${ hashes [ key ] } , remote is ${ otherHashes [ key ] } ` ) ;
const sectors = new Set ( Object . keys ( entityHashes ) . concat ( Object . keys ( otherHashes ) ) ) ;
if ( key !== 'recent_notes' ) {
// let's not get alarmed about recent notes which get updated often and can cause failures in race conditions
ws . sendMessageToAllClients ( { type : 'sync-hash-check-failed' } ) ;
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 } ) ;
}
}
}
if ( allChecksPassed ) {
if ( failedChecks. length === 0 ) {
log . info ( "Content hash checks PASSED" ) ;
}
}
module . exports = {
getHashes ,
getHashes : getEntityHashes ,
checkContentHashes
} ;