UNPKG

solid-app-set

Version:
600 lines (555 loc) 24.5 kB
// The tools pane is for managing and debugging and maintaining solid contacts databases // /* global confirm */ var UI = require('solid-ui') function toolsPane (selectAllGroups, selectedGroups, groupsMainTable, book, dom, me) { var kb = UI.store const ns = UI.ns const VCARD = ns.vcard const buttonStyle = 'font-size: 100%; margin: 0.8em; padding:0.5em;' var pane = dom.createElement('div') var table = pane.appendChild(dom.createElement('table')) table.setAttribute('style', 'font-size:120%; margin: 1em; border: 0.1em #ccc ;') var headerRow = table.appendChild(dom.createElement('tr')) headerRow.textContent = UI.utils.label(book) + ' - tools' headerRow.setAttribute('style', 'min-width: 20em; padding: 1em; font-size: 150%; border-bottom: 0.1em solid red; margin-bottom: 2em;') var statusRow = table.appendChild(dom.createElement('tr')) var statusBlock = statusRow.appendChild(dom.createElement('div')) statusBlock.setAttribute('style', 'padding: 2em;') var MainRow = table.appendChild(dom.createElement('tr')) var box = MainRow.appendChild(dom.createElement('table')) table.appendChild(dom.createElement('tr')) // bottomRow let context = { target: book, me: me, noun: 'address book', div: pane, dom: dom, statusRegion: statusBlock } box.appendChild(UI.aclControl.ACLControlBox5(book.dir(), dom, 'book', kb, function (ok, body) { if (!ok) box.innerHTML = 'ACL control box Failed: ' + body })) // UI.authn.registrationControl(context, book, ns.vcard('AddressBook')) .then(function (context) { console.log('Registration control finished.') // pane.appendChild(box) }) .catch(function (e) { UI.widgets.complain(context, e) }) // Output stats in line mode form var logSpace = MainRow.appendChild(dom.createElement('pre')) function log (message) { console.log(message) logSpace.textContent += message + '\n' } function stats () { var totalCards = kb.each(undefined, VCARD('inAddressBook'), book).length log('' + totalCards + ' cards loaded. ') var groups = kb.each(book, VCARD('includesGroup')) log('' + groups.length + ' total groups. ') var gg = [] for (let g in selectedGroups) { gg.push(g) } log('' + gg.length + ' selected groups. ') } var loadIndexButton = pane.appendChild(dom.createElement('button')) loadIndexButton.textContent = 'Load main index' loadIndexButton.style.cssText = buttonStyle loadIndexButton.addEventListener('click', function (e) { loadIndexButton.setAttribute('style', 'background-color: #ffc;') var nameEmailIndex = kb.any(book, ns.vcard('nameEmailIndex')) UI.store.fetcher.nowOrWhenFetched(nameEmailIndex, undefined, function (ok, message) { if (ok) { loadIndexButton.setAttribute('style', 'background-color: #cfc;') log(' People index has been loaded\n') } else { loadIndexButton.setAttribute('style', 'background-color: #fcc;') log('Error: People index has NOT been loaded' + message + '\n') } }) }) var statButton = pane.appendChild(dom.createElement('button')) statButton.textContent = 'Statistics' statButton.style.cssText = buttonStyle statButton.addEventListener('click', stats) var checkAccessButton = MainRow.appendChild(dom.createElement('button')) checkAccessButton.textContent = 'Check inidividual card access of selected groups' checkAccessButton.style.cssText = buttonStyle checkAccessButton.addEventListener('click', function (event) { function doCard (card) { UI.widgets.fixIndividualCardACL(card, log, function (ok, message) { if (ok) { log('Sucess for ' + UI.utils.label(card)) } else { log('Failure for ' + UI.utils.label(card) + ': ' + message) } }) } var gg = [] for (let g in selectedGroups) { gg.push(g) } for (var i = 0; i < gg.length; i++) { var g = kb.sym(gg[i]) var a = kb.each(g, ns.vcard('hasMember')) log(UI.utils.label(g) + ': ' + a.length + ' members') for (var j = 0; j < a.length; j++) { var card = a[j] log(UI.utils.label(card)) doCard(card) } } }) // /////////////////////////////////////////////////////////////////////////// // // DUPLICATES CHECK var checkDuplicates = MainRow.appendChild(dom.createElement('button')) checkDuplicates.textContent = 'Find duplicate cards' checkDuplicates.style.cssText = buttonStyle checkDuplicates.addEventListener('click', function (event) { var stats = {} // global god context stats.book = book stats.nameEmailIndex = kb.any(book, ns.vcard('nameEmailIndex')) log('Loading name index...') UI.store.fetcher.nowOrWhenFetched(stats.nameEmailIndex, undefined, function (ok, message) { log('Loaded name index.') stats.cards = [] stats.duplicates = [] stats.definitive = [] stats.nameless = [] stats.exactDuplicates = [] stats.nameOnlyDuplicates = [] stats.uniquesSet = [] stats.groupProblems = [] // Erase one card and all its files -> (err) // /* function eraseOne (card) { return new Promise(function (resolve, reject) { function removeFromMainIndex () { var indexBit = kb.connectedStatements(card, stats.nameEmailIndex) log('Bits of the name index file:' + indexBit) log('Patching main index file...') kb.updater.update(indexBit, [], function (uri, ok, body) { if (ok) { log('Success') resolve(null) } else { log('Error patching index file! ' + body) reject('Error patching index file! ' + body) } }) } var filesToDelete = [ card.doc() ] var photos = kb.each(card, ns.vcard('hasPhoto')) // could be > 1 if (photos.length) { filesToDelete = filesToDelete.concat(photos) } filesToDelete.push(card.dir()) // the folder last log('Files to delete: ' + filesToDelete) if (!confirm('DELETE card ' + card.dir() + ' for "' + kb.any(card, VCARD('fn')) + '", with ' + kb.each(card).length + 'statements?')) { return resolve('Cancelled by user') } function deleteNextFile () { var resource = filesToDelete.shift() if (!resource) { log('All deleted') removeFromMainIndex() resolve() } log('Deleting ... ' + resource) kb.fetcher.delete(resource) .then(function () { log('Deleted ok: ' + resource) deleteNextFile() }) .catch(function (e) { var err = '*** ERROR deleteing ' + resource + ': ' + e log(err) if (confirm('Patch out index file for card ' + card.dir() + ' EVEN THOUGH card DELETE errors?')) { removeFromMainIndex() } else { reject(err) } }) } deleteNextFile() }) // Promise } // erase one */ // Check actual recorrds to see which are exact matches - slow stats.nameDupLog = kb.sym(book.dir().uri + 'dedup-nameDupLog.ttl') stats.exactDupLog = kb.sym(book.dir().uri + 'dedup-exactDupLog.ttl') /* function checkOne (card) { return new Promise(function (resolve, reject) { var name = kb.anyValue(card, ns.vcard('fn')) var other = stats.definitive[name] kb.fetcher.load([card, other]).then(function (xhrs) { var exclude = {} exclude[ns.vcard('hasUID').uri] = true exclude[ns.dc('created').uri] = true exclude[ns.dc('modified').uri] = true function filtered (x) { return kb.statementsMatching(null, null, null, x.doc()).filter(function (st) { return !exclude[st.predicate.uri] }) } var desc = filtered(card) var desc2 = filtered(other) // var desc = connectedStatements(card, card.doc(), exclude) // var desc2 = connectedStatements(other, other.doc(), exclude) if (desc.length !== desc2.length) { log('CARDS to NOT match lengths ') stats.nameOnlyDuplicates.push(card) return resolve(false) } if (!desc.length) { log('@@@@@@ Zero length ') stats.nameOnlyDuplicates.push(card) return resolve(false) } // //////// Compare the two // Cheat: serialize and compare // var cardText = $rdf.serialize(card.doc(), kb, card.doc().uri, 'text/turtle') // var otherText = $rdf.serialize(other.doc(), kb, other.doc().uri, 'text/turtle') var cardText = (new $rdf.Serializer(kb)).setBase(card.doc().uri).statementsToN3(desc) var otherText = (new $rdf.Serializer(kb)).setBase(other.doc().uri).statementsToN3(desc2) // // log('Name: ' + name + ', statements: ' + desc.length) // log('___________________________________________') // log('KEEPING: ' + other.doc() + '\n' + cardText) // log('___________________________________________') // log('DELETING: '+ card.doc() + '\n' + otherText) // log('___________________________________________') // if (cardText !== otherText) { log('Texts differ') stats.nameOnlyDuplicates.push(card) return resolve(false) } var cardGroups = kb.each(null, ns.vcard('hasMember'), card) var otherGroups = kb.each(null, ns.vcard('hasMember'), other) for (var j = 0; j < cardGroups.length; j++) { var found = false for (var k = 0; k < otherGroups.length; k++) { if (otherGroups[k].sameTerm(cardGroups[j])) { found = true } } if (!found) { log('This one groups: ' + cardGroups) log('Other one groups: ' + otherGroups) log('Cant delete this one because it has a group, ' + cardGroups[j] + ', which the other does not.') stats.nameOnlyDuplicates.push(card) return resolve(false) } } console.log('Group check done -- exact duplicate: ' + card) stats.exactDuplicates.push(card) resolve(true) }).catch(function (e) { log('Cant load a card! ' + [card, other] + ': ' + e) stats.nameOnlyDuplicates.push(card) resolve(false) // if (confirm('Patch out index file for card ' + card.dir() + ' EVEN THOUGH card READ errors?')){ // removeFromMainIndex() // } }) }) } // checkOne */ stats.nameOnlyErrors = [] stats.nameLessZeroData = [] stats.nameLessIndex = [] stats.namelessUniques = [] stats.nameOnlyDuplicatesGroupDiff = [] function checkOneNameless (card) { return new Promise(function (resolve, reject) { kb.fetcher.load(card).then(function (xhr) { log(' Nameless check ' + card) var exclude = {} exclude[ns.vcard('hasUID').uri] = true exclude[ns.dc('created').uri] = true exclude[ns.dc('modified').uri] = true function filtered (x) { return kb.statementsMatching(null, null, null, x.doc()).filter(function (st) { return !exclude[st.predicate.uri] }) } var desc = filtered(card) // var desc = connectedStatements(card, card.doc(), exclude) // var desc2 = connectedStatements(other, other.doc(), exclude) if (!desc.length) { log(' Zero length ' + card) stats.nameLessZeroData.push(card) return resolve(false) } // Compare the two // Cheat: serialize and compare // var cardText = $rdf.serialize(card.doc(), kb, card.doc().uri, 'text/turtle') // var otherText = $rdf.serialize(other.doc(), kb, other.doc().uri, 'text/turtle') var cardText = (new $rdf.Serializer(kb)).setBase(card.doc().uri).statementsToN3(desc) var other = stats.nameLessIndex[cardText] if (other) { log(' Matches with ' + other) var cardGroups = kb.each(null, ns.vcard('hasMember'), card) var otherGroups = kb.each(null, ns.vcard('hasMember'), other) for (var j = 0; j < cardGroups.length; j++) { var found = false for (var k = 0; k < otherGroups.length; k++) { if (otherGroups[k].sameTerm(cardGroups[j])) found = true } if (!found) { log('This one groups: ' + cardGroups) log('Other one groups: ' + otherGroups) log('Cant skip this one because it has a group, ' + cardGroups[j] + ', which the other does not.') stats.nameOnlyDuplicatesGroupDiff.push(card) return resolve(false) } } console.log('Group check done -- exact duplicate: ' + card) } else { log('First nameless like: ' + card.doc()) log('___________________________________________') log(cardText) log('___________________________________________') stats.nameLessIndex[cardText] = card stats.namelessUniques.push(card) } resolve(true) }).catch(function (e) { log('Cant load a nameless card!: ' + e) stats.nameOnlyErrors.push(card) resolve(false) }) }) } // checkOneNameless function checkAllNameless () { stats.namelessToCheck = stats.namelessToCheck || stats.nameless.slice() log('Nameless check left: ' + stats.namelessToCheck.length) return new Promise(function (resolve, reject) { var x = stats.namelessToCheck.shift() if (!x) { log('namelessUniques: ' + stats.namelessUniques.length) log('namelessUniques: ' + stats.namelessUniques) if (confirm('Add all ' + stats.namelessUniques.length + ' nameless cards to the rescued set?')) { stats.uniques = stats.uniques.concat(stats.namelessUniques) for (var k = 0; k < stats.namelessUniques.length; k++) { stats.uniqueSet[stats.namelessUniques[k].uri] = true } } return resolve(true) } checkOneNameless(x) .then(function (exact) { log(' Namelessc check returns ' + exact) checkAllNameless() // loop }) }) } function checkGroupMembers () { return new Promise(function (resolve, reject) { // var inUniques = 0 log('Groups loaded') for (let i = 0; i < stats.uniques.length; i++) { stats.uniquesSet[stats.uniques[i].uri] = true } stats.groupMembers = kb.statementsMatching(null, ns.vcard('hasMember')).map(st => st.object) log(' Naive group members ' + stats.groupMembers.length) stats.groupMemberSet = [] for (var j = 0; j < stats.groupMembers.length; j++) { stats.groupMemberSet[stats.groupMembers[j].uri] = stats.groupMembers[j] } stats.groupMembers2 = [] for (var g in stats.groupMemberSet) { stats.groupMembers2.push(stats.groupMemberSet[g]) } log(' Compact group members ' + stats.groupMembers2.length) if ($rdf.keepThisCodeForLaterButDisableFerossConstantConditionPolice) { // Don't inspect as seems groups membership is complete for (let i = 0; i < stats.groupMembers.length; i++) { var card = stats.groupMembers[i] if (stats.uniquesSet[card.uri]) { // inUniques += 1 } else { log(' Not in uniques: ' + card) stats.groupProblems.push(card) if (stats.duplicateSet[card.uri]) { log(' ** IN duplicates alas:' + card) } else { log(' **** WTF?') } } } log('Problem cards: ' + stats.groupProblems.length) } // if resolve(true) }) } // checkGroupMembers function scanForDuplicates () { return new Promise(function (resolve, reject) { stats.cards = kb.each(undefined, VCARD('inAddressBook'), stats.book) log('' + stats.cards.length + ' total cards') var c, card, name for (c = 0; c < stats.cards.length; c++) { card = stats.cards[c] name = kb.anyValue(card, ns.vcard('fn')) if (!name) { stats.nameless.push(card) continue } if (stats.definitive[name] === card) { // pass } else if (stats.definitive[name]) { var n = stats.duplicates.length if ((n < 100) || (n < 1000 && (n % 10 === 0)) || (n % 100 === 0)) { // log('' + n + ') Possible duplicate ' + card + ' of: ' + definitive[name]) } stats.duplicates.push(card) } else { stats.definitive[name] = card } } stats.duplicateSet = [] for (var i = 0; i < stats.duplicates.length; i++) { stats.duplicateSet[stats.duplicates[i].uri] = stats.duplicates[i] } stats.namelessSet = [] for (i = 0; i < stats.nameless.length; i++) { stats.namelessSet[stats.nameless[i].uri] = stats.nameless[i] } stats.uniques = [] stats.uniqueSet = [] for (i = 0; i < stats.cards.length; i++) { var uri = stats.cards[i].uri if (!stats.duplicateSet[uri] && !stats.namelessSet[uri]) { stats.uniques.push(stats.cards[i]) stats.uniqueSet[uri] = stats.cards[i] } } log('Uniques: ' + stats.uniques.length) log('' + stats.nameless.length + ' nameless cards.') log('' + stats.duplicates.length + ' name-duplicate cards, leaving ' + (stats.cards.length - stats.duplicates.length)) resolve(true) }) } // Save a new clean version function saveCleanPeople () { var cleanPeople return Promise.resolve() .then(() => { cleanPeople = kb.sym(stats.book.dir().uri + 'clean-people.ttl') var sts = [] for (let i = 0; i < stats.uniques.length; i++) { sts = sts.concat(kb.connectedStatements(stats.uniques[i], stats.nameEmailIndex)) } var sz = (new $rdf.Serializer(kb)).setBase(stats.nameEmailIndex.uri) log('Serializing index of uniques...') var data = sz.statementsToN3(sts) return kb.fetcher.webOperation('PUT', cleanPeople, { data: data, contentType: 'text/turtle' }) }) .then(function () { log('Done uniques log ' + cleanPeople) return true }) .catch(function (e) { log('Error saving uniques: ' + e) }) } function saveCleanGroup (g) { var cleanGroup return Promise.resolve() .then(() => { var s = g.uri.replace('/Group/', '/NewGroup/') cleanGroup = kb.sym(s) var sts = [] for (let i = 0; i < stats.uniques.length; i++) { sts = sts.concat(kb.connectedStatements(stats.uniques[i], g.doc())) } var sz = (new $rdf.Serializer(kb)).setBase(g.uri) log(' Regenerating group of uniques...' + cleanGroup) var data = sz.statementsToN3(sts) return kb.fetcher.webOperation('PUT', cleanGroup, {data}) }) .then(() => { log(' Done uniques group ' + cleanGroup) return true }) .catch((e) => { log('Error saving : ' + e) }) } function saveAllGroups () { log('Saving ALL GROUPS') return Promise.all(stats.groupObjects.map(saveCleanGroup)) } var getAndSortGroups = function () { let groups = [] if (stats.book) { let books = [ stats.book ] books.map(function (book) { var gs = book ? kb.each(book, ns.vcard('includesGroup')) : [] var gs2 = gs.map(function (g) { return [ book, kb.any(g, ns.vcard('fn')), g ] }) groups = groups.concat(gs2) }) groups.sort() } return groups } var groups = getAndSortGroups() // Needed? stats.groupObjects = groups.map(gstr => gstr[2]) log('Loading ' + stats.groupObjects.length + ' groups... ') kb.fetcher.load(stats.groupObjects) .then(scanForDuplicates) .then(checkGroupMembers) .then(checkAllNameless) .then((resolve, reject) => { if (confirm('Write new clean versions?')) { resolve(true) } else { reject() } }) .then(saveCleanPeople) .then(saveAllGroups) .then(function (resolve, reject) { log('Done!') }) }) }) var checkGroupless = MainRow.appendChild(dom.createElement('button')) checkGroupless.style.cssText = buttonStyle checkGroupless.textContent = 'Find inidividuals with no group' checkGroupless.addEventListener('click', function (event) { log('Loading groups...') selectAllGroups(selectedGroups, groupsMainTable, function (ok, message) { if (!ok) { log('Failed: ' + message) return } var nameEmailIndex = kb.any(book, ns.vcard('nameEmailIndex')) UI.store.fetcher.nowOrWhenFetched(nameEmailIndex, undefined, function (ok, message) { log('Loaded groups and name index.') var reverseIndex = {} var groupless = [] var groups = kb.each(book, VCARD('includesGroup')) log('' + groups.length + ' total groups. ') for (var i = 0; i < groups.length; i++) { var g = groups[i] var a = kb.each(g, ns.vcard('hasMember')) log(UI.utils.label(g) + ': ' + a.length + ' members') for (var j = 0; j < a.length; j++) { kb.allAliases(a[j]).forEach(function (y) { reverseIndex[y.uri] = g }) } } var cards = kb.each(undefined, VCARD('inAddressBook'), book) log('' + cards.length + ' total cards') for (let c = 0; c < cards.length; c++) { if (!reverseIndex[cards[c].uri]) { groupless.push(cards[c]) log(' groupless ' + UI.utils.label(cards[c])) } } log('' + groupless.length + ' groupless cards.') }) }) }) return pane } // toolsPane module.exports = {} module.exports.toolsPane = toolsPane