/* =================================================================== TODAS as trilhas -- baixa pelo navegador, ponto. MUDANCA DE PLANO (2026-09-10): o achado 2.4 do PLANO.md ("bitstreams passam por curl") nao se sustentou. Testado ao vivo: item nunca antes acessado, buscado pelo navegador (200 OK) e IMEDIATAMENTE depois via curl na Finlandia -- 403, pagina real do Cloudflare ("Attention Required"). O unico item que passa por curl (6066) so passa porque ja tinha sido baixado com sucesso antes e ficou em cache de borda (cf-cache-status: HIT) -- nao e regra geral, e sobra de um caso. Ou seja: SEM navegador vivo, sem imagem, pra praticamente tudo -- nao so os nomes com encoding (trilha B). baixador.py na Finlandia fica pausado ate se achar outra saida. Por isso esta versao pega TODO item com preservation_url, nao so os de nome com encoding. A logica de baixar/nomear/salvar e a mesma que ja validava a trilha B -- so mudou o filtro de quem entra na fila. COMO USAR (depois que o coletor.js terminar) 1. Na mesma aba do coletor, cole este arquivo. 2. BAIXA.quantos() quantos itens precisam desta trilha 3. BAIXA.iniciar() comeca; os arquivos caem na pasta Downloads 4. Em outro terminal, rode python3 subidor.py --catalogo catalogo.jsonl Ele sobe cada arquivo pro S3 e APAGA na hora, para o disco nao encher. BAIXA.status() / BAIXA.parar() / BAIXA.retomar() =================================================================== */ (() => { const PAUSA = 3000; // arquivos sao grandes; va devagar const sleep = ms => new Promise(r => setTimeout(r, ms)); const jit = () => PAUSA + Math.floor(Math.random() * 1000); let db; const abrir = () => new Promise((res, rej) => { const q = indexedDB.open('bras', 1); q.onupgradeneeded = e => { const d = e.target.result; if (!d.objectStoreNames.contains('fila')) d.createObjectStore('fila'); if (!d.objectStoreNames.contains('itens')) d.createObjectStore('itens'); }; q.onsuccess = e => res(e.target.result); q.onerror = () => rej(q.error); }); const getAll = st => new Promise((res, rej) => { const r = db.transaction(st).objectStore(st).getAll(); r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); }); /* curl nao e mais confiavel pra quase nada (ver nota no topo) -- qualquer item com preservation_url entra na fila, nome simples ou com encoding, tanto faz. */ const precisaNavegador = u => !!u; const S = {fase: 'ocioso', feito: 0, erros: 0, total: 0, atual: '', parar: false}; const FEITOS = new Set(JSON.parse(localStorage.getItem('bras_baixados') || '[]')); const marcar = h => { FEITOS.add(h); localStorage.setItem('bras_baixados', JSON.stringify([...FEITOS])); }; async function alvos() { const itens = await getAll('itens'); return itens.filter(o => !o.erro && precisaNavegador(o.preservation_url)); } async function iniciar() { S.parar = false; S.fase = 'baixando'; const lista = (await alvos()).filter(o => !FEITOS.has(o.handle)); S.total = lista.length; console.log(`${lista.length} arquivos por esta trilha (~${(lista.length*PAUSA/3600000).toFixed(1)} h)`); for (let i = 0; i < lista.length; i++) { if (S.parar) { S.fase = 'parado'; return; } const o = lista[i]; S.atual = `${i + 1}/${lista.length}`; try { const r = await fetch(o.preservation_url, {credentials: 'same-origin'}); if (!r.ok) throw new Error('HTTP ' + r.status); const b = await r.blob(); const orig = decodeURIComponent(o.preservation_url.split('/').pop()); // o prefixo carrega o handle: e assim que o subidor sabe onde guardar const nome = `BRAS__${o.handle.replace(/\//g, '_')}__${orig}`.replace(/[\\/:*?"<>|]/g, '_'); const a = document.createElement('a'); a.href = URL.createObjectURL(b); a.download = nome; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(a.href), 30000); marcar(o.handle); S.feito++; } catch (e) { S.erros++; console.warn(` ${o.handle}: ${e.message}`); if (/HTTP 4|HTTP 5/.test(String(e))) await sleep(60000); } if (i % 25 === 0) console.log(` ${i}/${lista.length} ok=${S.feito} erro=${S.erros}`); await sleep(jit()); } S.fase = 'concluido'; console.log(`\nConcluido. ok=${S.feito} erro=${S.erros}`); } window.BAIXA = { quantos: async () => { const a = await alvos(); return {precisamDoNavegador: a.length, jaBaixados: FEITOS.size, restam: a.length - FEITOS.size}; }, iniciar, status: () => ({...S}), parar: () => { S.parar = true; }, retomar: () => iniciar(), limparMarcas: () => localStorage.removeItem('bras_baixados'), }; (async () => { db = await abrir(); const q = await window.BAIXA.quantos(); console.log('BAIXA pronto:', q, '\nRode BAIXA.iniciar() para comecar.'); })(); })();