tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1488Shell 182Rust 107Markdown 963619 Definiciones

⎇ 84e3737e10e17927380ed3e232e50cebfdd39123 recipes/atuq/extensions/descargas/fondo.js JavaScript · 136 líneas · 4.7 KB Historia Quién tocó qué crudo

atuq: una descarga deja de ser un archivo con nombre y pasa a ser un objeto con identidad 897fad178 · Sergio ·

1"use strict";
2// descargas — una descarga con identidad (SDD 26 §6.2), v1.
3//
4// Cuando una descarga TERMINA, esta extensión le pasa al host nativo la ruta del
5// fichero, su nombre y de dónde salió. El host lo ingiere a un CAS BLAKE3 —el de
6// `arje-cas`, el mismo formato y el mismo hash que usan arje, takana y tejido— y
7// contesta el hash, el tamaño, con qué nombre apareció la PRIMERA vez y cuántas
8// veces se bajó ese mismo contenido.
9//
10// Para qué: ningún navegador trata una descarga como algo con identidad. Para
11// todos es un blob con nombre, y por eso lo bajás dos veces y no lo sabés. Acá el
12// mismo contenido con otro nombre es UN objeto y dos nombres, y se dice.
13//
14// ── LO QUE NO HACE, Y ES DELIBERADO ──────────────────────────────────────────
15// No toca el fichero que bajaste. El host lo COPIA al CAS y lo deja donde estaba.
16// Mover o borrar lo que alguien acaba de bajar —aunque «ya esté en el CAS»— no es
17// una decisión que le toque a un navegador tomar por su cuenta.
18//
19// Y no intercepta la descarga: la observa cuando terminó. Meterse en el medio
20// obligaría a decidir qué hacer cuando el CAS falla, y la respuesta correcta a eso
21// («que la descarga siga igual») es exactamente lo que se consigue no metiéndose.
22
23const HOST = "puriy_costura";
24
25let puerto = null;
26// La descarga que motivó cada mensaje, para poder pintar la insignia en su
27// respuesta: el host devuelve el `id` que le mandamos, y el `id` de una descarga
28// es justo un entero.
29const pendientes = new Map();
30
31function di(s) {
32  try { dump("DESCARGAS " + s + "\n"); } catch (e) {}
33}
34
35function conectar() {
36  try {
37    puerto = browser.runtime.connectNative(HOST);
38    puerto.onMessage.addListener(alRecibir);
39    puerto.onDisconnect.addListener(p => {
40      di("DESCONECTADO " + (p.error ? p.error.message : "sin error"));
41      puerto = null;
42    });
43    di("CONECTADO " + HOST);
44  } catch (e) {
45    di("SIN HOST " + e.message);
46    puerto = null;
47  }
48}
49
50function enviar(msg) {
51  if (!puerto) { conectar(); }
52  if (!puerto) { return false; }
53  try {
54    puerto.postMessage(msg);
55    return true;
56  } catch (e) {
57    di("ENVÍO FALLÓ " + e.message);
58    puerto = null;
59    return false;
60  }
61}
62
63function alRecibir(r) {
64  if (!r || r.ok !== true) {
65    di("RECHAZO " + (r && r.error ? r.error : "sin error"));
66    return;
67  }
68  if (r.verb !== "cas.ingest" || !r.object) { return; }
69  const o = r.object;
70  di("INGERIDO " + o.hash + " nombre=" + o.name + " bytes=" + o.bytes +
71     " veces=" + o.times + " dedup=" + (r.deduped === true));
72  pendientes.delete(r.id);
73  pintar(o, r.deduped === true);
74}
75
76// El aviso es la insignia del botón, no un modal: es información, no una alarma.
77// Sólo marca cuando hay algo que decir —que ese contenido ya lo tenías—, porque
78// una insignia que está siempre encendida deja de leerse.
79function pintar(o, dedup) {
80  try {
81    if (dedup) {
82      browser.browserAction.setBadgeText({ text: String(o.times) });
83      browser.browserAction.setBadgeBackgroundColor({ color: "#1b6470" });
84      browser.browserAction.setTitle({
85        title: "Esto ya lo tenías: «" + o.name + "»\n" +
86               "es la vez " + o.times + " que bajás el mismo contenido\n" +
87               "blake3:" + o.hash.slice(0, 16) + "…",
88      });
89    } else {
90      browser.browserAction.setBadgeText({ text: "" });
91      browser.browserAction.setTitle({
92        title: "«" + o.name + "» — " + o.bytes + " bytes\nblake3:" +
93               o.hash.slice(0, 16) + "…",
94      });
95    }
96    browser.browserAction.getBadgeText({}).then(
97      t => di("INSIGNIA texto=" + JSON.stringify(t)),
98      e => di("INSIGNIA NO SE PUDO LEER " + e.message));
99  } catch (e) { di("INSIGNIA FALLÓ " + e.message); }
100}
101
102async function terminada(id) {
103  let item;
104  try {
105    const r = await browser.downloads.search({ id });
106    item = r && r[0];
107  } catch (e) {
108    di("BUSCAR FALLÓ " + e.message);
109    return;
110  }
111  if (!item || !item.filename) {
112    di("SIN RUTA para la descarga " + id);
113    return;
114  }
115  // `filename` de la API de descargas es la ruta ABSOLUTA en disco, no el nombre
116  // (el nombre visible es el último segmento). El host además la exige absoluta.
117  const nombre = item.filename.split("/").pop();
118  di("TERMINADA " + id + " " + item.filename + " (" + item.fileSize + " bytes)");
119  pendientes.set(id, nombre);
120  enviar({
121    id,
122    verb: "cas.ingest",
123    path: item.filename,
124    name: nombre,
125    origin: item.url || "",
126  });
127}
128
129browser.downloads.onChanged.addListener(delta => {
130  if (delta.state && delta.state.current === "complete") {
131    terminada(delta.id);
132  }
133});
134
135conectar();
136di("LISTA v1 observa descargas terminadas; no toca el fichero");