stats_model.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. const pull = require('../server/node_modules/pull-stream');
  2. const os = require('os');
  3. const fs = require('fs');
  4. const { getConfig } = require('../configs/config-manager.js');
  5. const logLimit = getConfig().ssbLogStream?.limit || 1000;
  6. module.exports = ({ cooler }) => {
  7. let ssb;
  8. const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; };
  9. const types = [
  10. 'bookmark','event','task','votes','report','feed',
  11. 'image','audio','video','document','transfer','post','tribe','market','forum','job'
  12. ];
  13. const getFolderSize = (folderPath) => {
  14. const files = fs.readdirSync(folderPath);
  15. let totalSize = 0;
  16. for (const file of files) {
  17. const filePath = `${folderPath}/${file}`;
  18. const stats = fs.statSync(filePath);
  19. totalSize += stats.isDirectory() ? getFolderSize(filePath) : stats.size;
  20. }
  21. return totalSize;
  22. };
  23. const formatSize = (sizeInBytes) => {
  24. if (sizeInBytes < 1024) return `${sizeInBytes} B`;
  25. const kb = 1024, mb = kb * 1024, gb = mb * 1024, tb = gb * 1024;
  26. if (sizeInBytes < mb) return `${(sizeInBytes / kb).toFixed(2)} KB`;
  27. if (sizeInBytes < gb) return `${(sizeInBytes / mb).toFixed(2)} MB`;
  28. if (sizeInBytes < tb) return `${(sizeInBytes / gb).toFixed(2)} GB`;
  29. return `${(sizeInBytes / tb).toFixed(2)} TB`;
  30. };
  31. const getStats = async (filter = 'ALL') => {
  32. const ssbClient = await openSsb();
  33. const userId = ssbClient.id;
  34. const messages = await new Promise((res, rej) => {
  35. pull(
  36. ssbClient.createLogStream({ limit: logLimit }),
  37. pull.collect((err, msgs) => err ? rej(err) : res(msgs))
  38. );
  39. });
  40. const allMsgs = messages.filter(m => m.value?.content);
  41. const tombTargets = new Set(
  42. allMsgs
  43. .filter(m => m.value.content.type === 'tombstone' && m.value.content.target)
  44. .map(m => m.value.content.target)
  45. );
  46. const scopedMsgs = filter === 'MINE' ? allMsgs.filter(m => m.value.author === userId) : allMsgs;
  47. const byType = {};
  48. const parentOf = {};
  49. for (const t of types) {
  50. byType[t] = new Map();
  51. parentOf[t] = new Map();
  52. }
  53. for (const m of scopedMsgs) {
  54. const k = m.key;
  55. const c = m.value.content;
  56. const t = c.type;
  57. if (!types.includes(t)) continue;
  58. byType[t].set(k, { key: k, ts: m.value.timestamp, content: c });
  59. if (c.replaces) parentOf[t].set(k, c.replaces);
  60. }
  61. const findRoot = (t, id) => {
  62. let cur = id;
  63. const pMap = parentOf[t];
  64. while (pMap.has(cur)) cur = pMap.get(cur);
  65. return cur;
  66. };
  67. const tipOf = {};
  68. for (const t of types) {
  69. tipOf[t] = new Map();
  70. const pMap = parentOf[t];
  71. const fwd = new Map();
  72. for (const [child, parent] of pMap.entries()) {
  73. fwd.set(parent, child);
  74. }
  75. const allMap = byType[t];
  76. const roots = new Set(Array.from(allMap.keys()).map(id => findRoot(t, id)));
  77. for (const root of roots) {
  78. let tip = root;
  79. while (fwd.has(tip)) tip = fwd.get(tip);
  80. if (tombTargets.has(tip)) continue;
  81. const node = allMap.get(tip) || allMap.get(root);
  82. if (node) tipOf[t].set(root, node);
  83. }
  84. }
  85. const content = {};
  86. const opinions = {};
  87. for (const t of types) {
  88. let vals = Array.from(tipOf[t].values()).map(v => v.content);
  89. if (t === 'forum') {
  90. vals = vals.filter(c => !(c.root && tombTargets.has(c.root)));
  91. }
  92. content[t] = vals.length;
  93. opinions[t] = vals.filter(e => Array.isArray(e.opinions_inhabitants) && e.opinions_inhabitants.length > 0).length;
  94. }
  95. const tribeVals = Array.from(tipOf['tribe'].values()).map(v => v.content);
  96. const memberTribes = tribeVals
  97. .filter(c => Array.isArray(c.members) && c.members.includes(userId))
  98. .map(c => c.name || c.title || c.id);
  99. const inhabitants = new Set(allMsgs.map(m => m.value.author)).size;
  100. const secretStat = fs.statSync(`${os.homedir()}/.ssb/secret`);
  101. const createdAt = secretStat.birthtime.toLocaleString();
  102. const folderSize = getFolderSize(`${os.homedir()}/.ssb`);
  103. const flumeSize = getFolderSize(`${os.homedir()}/.ssb/flume`);
  104. const blobsSize = getFolderSize(`${os.homedir()}/.ssb/blobs`);
  105. return {
  106. id: userId,
  107. createdAt,
  108. inhabitants,
  109. content,
  110. opinions,
  111. memberTribes,
  112. userTombstoneCount: scopedMsgs.filter(m => m.value.content.type === 'tombstone').length,
  113. networkTombstoneCount: allMsgs.filter(m => m.value.content.type === 'tombstone').length,
  114. folderSize: formatSize(folderSize),
  115. statsBlockchainSize: formatSize(flumeSize),
  116. statsBlobsSize: formatSize(blobsSize)
  117. };
  118. };
  119. return { getStats };
  120. };