"use strict"; var g_following_cache = {}; var g_following_deep_cache = {}; var g_about_cache = {}; async function following(db, id) { if (g_following_cache[id]) { return g_following_cache[id]; } var o = await db.get(id + ":following"); const k_version = 5; var f = o ? JSON.parse(o) : o; if (!f || f.version != k_version) { f = {users: [], sequence: 0, version: k_version}; } f.users = new Set(f.users); await ssb.sqlStream( "SELECT "+ " sequence, "+ " json_extract(content, '$.contact') AS contact, "+ " json_extract(content, '$.following') AS following "+ "FROM messages "+ "WHERE "+ " author = ?1 AND "+ " sequence > ?2 AND "+ " json_extract(content, '$.type') = 'contact' "+ "UNION SELECT MAX(sequence) AS sequence, NULL, NULL FROM messages WHERE author = ?1 "+ "ORDER BY sequence", [id, f.sequence], function(row) { if (row.following) { f.users.add(row.contact); } else { f.users.delete(row.contact); } f.sequence = row.sequence; }); var as_set = f.users; f.users = Array.from(f.users).sort(); var j = JSON.stringify(f); if (o != j) { await db.set(id + ":following", j); } f.users = as_set; g_following_cache[id] = f.users; return f.users; } async function followingDeep(db, seed_ids, depth) { if (depth <= 0) { return seed_ids; } var key = JSON.stringify([seed_ids, depth]); if (g_following_deep_cache[key]) { return g_following_deep_cache[key]; } var f = await Promise.all(seed_ids.map(x => following(db, x).then(x => [...x]))); var ids = [].concat(...f); var x = await followingDeep(db, [...new Set(ids)].sort(), depth - 1); x = [...new Set([].concat(...x, ...seed_ids))].sort(); g_following_deep_cache[key] = x; return x; } async function getAbout(db, id) { if (g_about_cache[id]) { return g_about_cache[id]; } var o = await db.get(id + ":about"); const k_version = 4; var f = o ? JSON.parse(o) : o; if (!f || f.version != k_version) { f = {about: {}, sequence: 0, version: k_version}; } await ssb.sqlStream( "SELECT "+ " sequence, "+ " content "+ "FROM messages "+ "WHERE "+ " author = ?1 AND "+ " sequence > ?2 AND "+ " json_extract(content, '$.type') = 'about' AND "+ " json_extract(content, '$.about') = ?1 "+ "UNION SELECT MAX(sequence) as sequence, NULL FROM messages WHERE author = ?1 "+ "ORDER BY sequence", [id, f.sequence], function(row) { f.sequence = row.sequence; if (row.content) { var about = {}; try { about = JSON.parse(row.content); } catch { } delete about.about; delete about.type; f.about = Object.assign(f.about, about); } }); var j = JSON.stringify(f); if (o != j) { await db.set(id + ":about", j); } g_about_cache[id] = f.about; return f.about; } async function getSize(db, id) { let size = 0; await ssb.sqlStream( "SELECT (SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1", [id], function (row) { size += row.size; }); return size; } function niceSize(bytes) { let value = bytes; let unit = 'B'; const k_units = ['kB', 'MB', 'GB', 'TB']; for (let u of k_units) { if (value >= 1024) { value /= 1024; unit = u; } else { break; } } return Math.round(value * 10) / 10 + ' ' + unit; } async function buildTree(db, root, indent, depth) { var f = await following(db, root); var result = indent + '[' + f.size + '] ' + '' + ((await getAbout(db, root)).name || root) + ' ' + niceSize(await getSize(db, root)) + '\n'; if (depth > 0) { for (let next of f) { result += await buildTree(db, next, indent + ' ', depth - 1); } } return result; } async function main() { await app.setDocument('
building...
'); var db = await database('ssb'); var whoami = await ssb.getIdentities(); var tree = ''; for (let id of whoami) { await app.setDocument(`
building... ${id}
`); tree += await buildTree(db, id, '', 2); } await app.setDocument('
FOLLOWING:\n' + tree + '
'); } main();