Let a device hand a file to the server for hashing and validation
This commit is contained in:
@@ -7505,6 +7505,13 @@ async function renderSettings() {
|
||||
<span>This device<small id="p2pDeviceInfo">…</small></span>
|
||||
<span id="p2pHoldingCount" class="set-stat">…</span>
|
||||
</div>
|
||||
<div class="set-row hidden" id="p2pContributeRow">
|
||||
<span>
|
||||
Saved videos the server can't verify yet
|
||||
<small>Uploading lets the server check them (hash + media validation) so other devices can get them. Uses your upload bandwidth.</small>
|
||||
</span>
|
||||
<button id="p2pContributeBtn" class="btn">Verify & share</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="set-group">
|
||||
@@ -7695,6 +7702,26 @@ async function renderSettings() {
|
||||
: P.isConnected() ? `Online · peer ${P.peer()}` : 'Registered · not connected';
|
||||
const files = window.DeviceDB ? await window.DeviceDB.listFiles() : [];
|
||||
cnt.textContent = `${files.filter((f) => f.state === 'verified').length} verified / ${files.length} saved`;
|
||||
const unknown = window.P2PClient ? await window.P2PClient.unknownVideos() : [];
|
||||
const row = $('p2pContributeRow');
|
||||
const btn = $('p2pContributeBtn');
|
||||
if (row && btn && unknown.length) {
|
||||
row.classList.remove('hidden');
|
||||
btn.textContent = `Verify & share ${unknown.length} saved video${unknown.length === 1 ? '' : 's'}`;
|
||||
btn.onclick = async () => {
|
||||
btn.disabled = true;
|
||||
let ok = 0;
|
||||
for (let i = 0; i < unknown.length; i++) {
|
||||
const v = videoById(unknown[i]) || {};
|
||||
btn.textContent = `Uploading ${i + 1} of ${unknown.length}…`;
|
||||
const r = await window.P2PClient.contribute(unknown[i], { title: v.title || '', channel: v.channel || '' });
|
||||
if (r && r.ok) ok++;
|
||||
}
|
||||
toast(`Verified ${ok} of ${unknown.length} saved video${unknown.length === 1 ? '' : 's'}`);
|
||||
btn.disabled = false;
|
||||
row.classList.add('hidden');
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
// ---- Cache management ----
|
||||
|
||||
@@ -108,6 +108,17 @@
|
||||
}
|
||||
},
|
||||
|
||||
// The saved File itself (disk-backed, not read into memory) — used as an
|
||||
// upload body by P2P intake. null when not saved.
|
||||
async getFileObject(videoId) {
|
||||
try {
|
||||
const found = await findHandle(videoId);
|
||||
return found ? await found[0].getFile() : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// Bytes [offset, offset+length) of a saved video — answers the server's
|
||||
// P2P range challenges (p2p-client.js). null when the file is missing.
|
||||
async readRange(videoId, offset, length) {
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
* changed() a save/delete happened → re-report soon
|
||||
* device() { deviceId, secret } or null
|
||||
* authHeaders() { 'X-Device': … } for other P2P calls
|
||||
* contribute(videoId) / unknownVideos()
|
||||
* hand a saved file the server doesn't
|
||||
* know yet to /api/p2p/intake (plan 016)
|
||||
* onMessage(type, fn) / signal(to, data) / peer()
|
||||
* live /ws/p2p socket (plan 014): the
|
||||
* device is "online" while it is open
|
||||
@@ -33,6 +36,7 @@
|
||||
let again = false;
|
||||
let timer = null;
|
||||
let lastSync = 0;
|
||||
let lastUnknown = []; // cids the server did not recognise at the last report
|
||||
|
||||
function device() {
|
||||
try {
|
||||
@@ -130,6 +134,7 @@
|
||||
if (r.status === 401) { try { localStorage.removeItem(KEY); } catch { /* ignore */ } return null; }
|
||||
const j = await r.json();
|
||||
if (!j || !j.ok) return null;
|
||||
lastUnknown = Array.isArray(j.unknown) ? j.unknown : [];
|
||||
const accepted = new Set(j.accepted || []);
|
||||
for (const rec of recs) {
|
||||
if (rec.cid && accepted.has(rec.cid) && rec.state !== 'verified') { rec.state = 'verified'; await window.DeviceDB.putFile(rec); }
|
||||
@@ -151,6 +156,33 @@
|
||||
return j;
|
||||
}
|
||||
|
||||
// ---- intake (plan 016) ---------------------------------------------------------
|
||||
// Send one saved video to the server so it can hash + validate it and add its
|
||||
// cid to the catalog. Uploads the whole file: only on the user's request
|
||||
// ("Verify & share") or when the server asks for a copy (plan 018).
|
||||
async function contribute(videoId, { cid = null, title = '', channel = '' } = {}) {
|
||||
const dev = await ensureDevice();
|
||||
const rec = await window.DeviceDB.getFile(videoId);
|
||||
const file = window.OPFS.getFileObject ? await window.OPFS.getFileObject(videoId) : null;
|
||||
if (!file) return { ok: false, error: 'not saved on this device' };
|
||||
const h = { 'X-Device': dev.deviceId + '.' + dev.secret };
|
||||
const t = await (await fetch('/api/p2p/intake', {
|
||||
method: 'POST', headers: { ...h, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ videoId, cid: cid || (rec && rec.cid) || undefined, size: file.size, title, channel }),
|
||||
})).json().catch(() => ({ ok: false, error: 'server unreachable' }));
|
||||
if (!t.ok || t.known) { if (t.known) changed(); return t; }
|
||||
const r = await (await fetch(t.url, { method: 'PUT', headers: h, body: file }))
|
||||
.json().catch(() => ({ ok: false, error: 'upload failed' }));
|
||||
if (r.ok) changed();
|
||||
return r;
|
||||
}
|
||||
|
||||
async function unknownVideos() {
|
||||
if (!lastUnknown.length || !window.DeviceDB) return [];
|
||||
const recs = await window.DeviceDB.listFiles();
|
||||
return recs.filter((r) => r.cid && lastUnknown.includes(r.cid)).map((r) => r.videoId);
|
||||
}
|
||||
|
||||
// ---- presence socket (/ws/p2p) — plan 014 ------------------------------------
|
||||
// Open while sharing OR receiving is on. Credentials go in the first message,
|
||||
// never the URL. Reconnects with backoff (5 s … 5 min).
|
||||
@@ -258,7 +290,7 @@
|
||||
}
|
||||
|
||||
window.P2PClient = {
|
||||
start, changed, sync, device, authHeaders, config,
|
||||
start, changed, sync, device, authHeaders, config, contribute, unknownVideos,
|
||||
onMessage, signal, peer: () => myPeer, isConnected: () => !!(ws && ws.readyState === 1 && myPeer),
|
||||
};
|
||||
}());
|
||||
|
||||
Reference in New Issue
Block a user