upstream: integrar Oasis 1.3.1
Base del merge a tres bandas: git 0.9.5 de epsylon, esta rama y el src de la
APK oficial 1.3.1 (la variante movil: OASIS_MOBILE, regex sin \p{}, audio y
video de Android). Corre en el mismo nodejs-mobile 18.20.4 que ya usabamos.
Se conserva de la rama: Karvan, identidades y multicuenta, topbar y barra
inferior, borrado de EXIF sin sharp, filtro hot del foro, RTC sin STUN y los
modulos apagados en movil (wallet, banking, torrents).
Se toma de upstream lo que ya hace de serie: respuesta citada en chats, ssb-lan
en movil, configuracion en ~/.ssb/oasis (sobrevive a reinstalar el backend).
El asistente de bienvenida deja elegir workflow tambien en movil.
This commit is contained in:
parent
8d838dd8b1
commit
c332afd6ea
265 changed files with 80970 additions and 29167 deletions
|
|
@ -2,7 +2,8 @@ const pull = require("../server/node_modules/pull-stream");
|
|||
const { getConfig } = require("../configs/config-manager.js");
|
||||
const categories = require("../backend/opinion_categories");
|
||||
const { buildValidatedTombstoneSet } = require('./tombstone_validator');
|
||||
const { dedupeBy, norm } = require('../backend/dedupe');
|
||||
const { readTyped, memoIndex } = require('./typed_log');
|
||||
const { dedupeBy, norm, versionGroups } = require('../backend/dedupe');
|
||||
|
||||
const logLimit = getConfig().ssbLogStream?.limit || 1000;
|
||||
|
||||
|
|
@ -31,13 +32,9 @@ module.exports = ({ cooler }) => {
|
|||
return ssb;
|
||||
};
|
||||
|
||||
const getAllMessages = async (ssbClient) =>
|
||||
new Promise((resolve, reject) => {
|
||||
pull(
|
||||
ssbClient.createLogStream({ limit: logLimit }),
|
||||
pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs)))
|
||||
);
|
||||
});
|
||||
const IMAGE_TYPES = ["image", "imageOpinion", "tombstone"];
|
||||
|
||||
const getAllMessages = async (ssbClient) => readTyped(ssbClient, IMAGE_TYPES, { limit: logLimit });
|
||||
|
||||
const getMsg = async (ssbClient, key) =>
|
||||
new Promise((resolve, reject) => {
|
||||
|
|
@ -47,9 +44,6 @@ module.exports = ({ cooler }) => {
|
|||
const buildIndex = (messages) => {
|
||||
const tomb = buildValidatedTombstoneSet(messages);
|
||||
const nodes = new Map();
|
||||
const parent = new Map();
|
||||
const child = new Map();
|
||||
const strictChild = new Map();
|
||||
const opinionMsgs = [];
|
||||
|
||||
for (const m of messages) {
|
||||
|
|
@ -65,48 +59,16 @@ module.exports = ({ cooler }) => {
|
|||
const ts = v.timestamp || m.timestamp || 0;
|
||||
let sizeBytes = 0;
|
||||
try { sizeBytes = Buffer.byteLength(JSON.stringify(v), "utf8"); } catch (_) { sizeBytes = 0; }
|
||||
nodes.set(k, { key: k, ts, c, sizeBytes, author: v.author });
|
||||
|
||||
if (c.replaces) {
|
||||
parent.set(k, c.replaces);
|
||||
child.set(c.replaces, k);
|
||||
}
|
||||
nodes.set(k, { key: k, ts, seq: v.sequence || 0, c, sizeBytes, author: v.author });
|
||||
}
|
||||
|
||||
for (const [k, t] of Array.from(parent.entries())) {
|
||||
const cn = nodes.get(k);
|
||||
const pn = nodes.get(t);
|
||||
if (!pn) { parent.delete(k); if (child.get(t) === k) child.delete(t); continue; }
|
||||
if (!cn || String(cn.author) !== String(pn.author)) { parent.delete(k); if (child.get(t) === k) child.delete(t); nodes.delete(k); }
|
||||
}
|
||||
|
||||
for (const [k, node] of nodes) {
|
||||
const t = node.c.replaces;
|
||||
if (t) { const orig = nodes.get(t); if (orig && orig.author === node.author) strictChild.set(t, k); }
|
||||
}
|
||||
|
||||
const rootOf = (id) => {
|
||||
let cur = id, g = 0;
|
||||
while (parent.has(cur) && nodes.has(parent.get(cur)) && g++ < 100000) cur = parent.get(cur);
|
||||
return cur;
|
||||
};
|
||||
|
||||
const contentTipOf = (root) => {
|
||||
let cur = root, g = 0;
|
||||
while (strictChild.has(cur) && g++ < 100000) cur = strictChild.get(cur);
|
||||
const n = nodes.get(cur), rn = nodes.get(root);
|
||||
return (n && rn && n.author === rn.author) ? cur : root;
|
||||
};
|
||||
|
||||
const roots = new Set();
|
||||
for (const id of nodes.keys()) roots.add(rootOf(id));
|
||||
const vg = versionGroups([...nodes.values()].map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: n.c.replaces || null, claimedAuthor: typeof n.c.author === "string" ? n.c.author : null })));
|
||||
const rootOf = vg.rootOf;
|
||||
const contentTipOf = vg.tipOf;
|
||||
|
||||
const opinionsByRoot = new Map();
|
||||
for (const op of opinionMsgs) { if (!nodes.has(op.target)) continue; const r = rootOf(op.target); if (!opinionsByRoot.has(r)) opinionsByRoot.set(r, []); opinionsByRoot.get(r).push(op); }
|
||||
|
||||
const forward = new Map();
|
||||
for (const [newId, oldId] of parent.entries()) forward.set(oldId, newId);
|
||||
|
||||
const resolveGroup = (root) => {
|
||||
const contentTip = contentTipOf(root);
|
||||
const contentNode = nodes.get(contentTip) || nodes.get(root);
|
||||
|
|
@ -123,9 +85,9 @@ module.exports = ({ cooler }) => {
|
|||
};
|
||||
|
||||
const tipByRoot = new Map();
|
||||
for (const r of roots) tipByRoot.set(r, contentTipOf(r));
|
||||
for (const r of vg.roots) tipByRoot.set(r, contentTipOf(r));
|
||||
|
||||
return { tomb, nodes, parent, child, rootOf, tipByRoot, forward, resolveGroup };
|
||||
return { tomb, nodes, rootOf, tipByRoot, resolveGroup };
|
||||
};
|
||||
|
||||
const buildImage = (node, rootId, viewerId, agg) => {
|
||||
|
|
@ -141,6 +103,7 @@ module.exports = ({ cooler }) => {
|
|||
author: node.author,
|
||||
title: c.title || "",
|
||||
description: c.description || "",
|
||||
license: c.license || "",
|
||||
mapUrl: c.mapUrl || "",
|
||||
opinions: agg ? agg.opinions : (c.opinions || {}),
|
||||
opinions_inhabitants: voters,
|
||||
|
|
@ -167,16 +130,12 @@ module.exports = ({ cooler }) => {
|
|||
const messages = await getAllMessages(ssbClient);
|
||||
const idx = buildIndex(messages);
|
||||
|
||||
let tip = id;
|
||||
while (idx.forward.has(tip)) tip = idx.forward.get(tip);
|
||||
if (idx.tomb.has(tip)) throw new Error("Image not found");
|
||||
|
||||
let root = tip;
|
||||
while (idx.parent.has(root)) root = idx.parent.get(root);
|
||||
const root = idx.rootOf(id);
|
||||
if (idx.tomb.has(idx.tipByRoot.get(root) || id)) throw new Error("Image not found");
|
||||
return root;
|
||||
},
|
||||
|
||||
async createImage(blobMarkdown, tagsRaw, title, description, mapUrl) {
|
||||
async createImage(blobMarkdown, tagsRaw, title, description, mapUrl, license) {
|
||||
const ssbClient = await openSsb();
|
||||
const blobId = parseBlobId(blobMarkdown);
|
||||
const tags = normalizeTags(tagsRaw) || [];
|
||||
|
|
@ -193,7 +152,8 @@ module.exports = ({ cooler }) => {
|
|||
description: description || "",
|
||||
mapUrl: mapUrl || "",
|
||||
opinions: {},
|
||||
opinions_inhabitants: []
|
||||
opinions_inhabitants: [],
|
||||
...(license ? { license } : {})
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -201,7 +161,7 @@ module.exports = ({ cooler }) => {
|
|||
});
|
||||
},
|
||||
|
||||
async updateImageById(id, blobMarkdown, tagsRaw, title, description, mapUrl) {
|
||||
async updateImageById(id, blobMarkdown, tagsRaw, title, description, mapUrl, license) {
|
||||
const ssbClient = await openSsb();
|
||||
const userId = ssbClient.id;
|
||||
const tipId = await this.resolveCurrentId(id);
|
||||
|
|
@ -225,7 +185,8 @@ module.exports = ({ cooler }) => {
|
|||
description: description !== undefined ? description || "" : oldMsg.content.description || "",
|
||||
mapUrl: mapUrl !== undefined ? mapUrl || "" : oldMsg.content.mapUrl || "",
|
||||
createdAt: oldMsg.content.createdAt,
|
||||
updatedAt: now
|
||||
updatedAt: now,
|
||||
...(license !== undefined ? { license } : {})
|
||||
};
|
||||
|
||||
const tombstone = { type: "tombstone", target: tipId, deletedAt: now, author: userId };
|
||||
|
|
@ -311,10 +272,10 @@ module.exports = ({ cooler }) => {
|
|||
const viewer = viewerId || ssbClient.id;
|
||||
|
||||
const messages = await getAllMessages(ssbClient);
|
||||
const idx = buildIndex(messages);
|
||||
const idx = memoIndex('images', messages, buildIndex);
|
||||
const rootId = idx.rootOf(id);
|
||||
const agg = idx.resolveGroup(rootId);
|
||||
if (!agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Image not found");
|
||||
if (!idx.tipByRoot.has(rootId) || !agg.contentNode || idx.tomb.has(agg.contentTip)) throw new Error("Image not found");
|
||||
|
||||
return buildImage(agg.contentNode, rootId, viewer, agg);
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue