Compare commits
10 Commits
eb269f371d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28f011419c | ||
|
|
cbe39e76a2 | ||
|
|
134c4b36c9 | ||
|
|
e51fb204e0 | ||
|
|
0af9fa02a0 | ||
|
|
72334f3d78 | ||
|
|
67fe5ae406 | ||
|
|
ddd554d506 | ||
|
|
061f25fdcb | ||
|
|
3a651c5bc8 |
@@ -1 +0,0 @@
|
|||||||
https://github.com/jakeg/heroku-buildpack-bun
|
|
||||||
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
**/node_modules
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.env
|
||||||
|
downloads
|
||||||
|
packages/client/dist
|
||||||
|
packages/server/dist
|
||||||
40
Dockerfile
Normal file
40
Dockerfile
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Use the official Bun image
|
||||||
|
FROM oven/bun:latest
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files first for caching
|
||||||
|
COPY package.json bun.lock ./
|
||||||
|
COPY packages/client/package.json ./packages/client/
|
||||||
|
COPY packages/server/package.json ./packages/server/
|
||||||
|
COPY packages/shared/package.json ./packages/shared/
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy the rest of the application
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the client (Vite) and server (TSC)
|
||||||
|
# ensuring the environment variables are set for the build if needed
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=3000
|
||||||
|
# Build packages individually by changing directory
|
||||||
|
WORKDIR /app/packages/shared
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
WORKDIR /app/packages/server
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
WORKDIR /app/packages/client
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
# Reset working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Expose the port (Dokku will override PORT env var, but 3000 is a good default documentation)
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Start the server
|
||||||
|
CMD ["bun", "packages/server/src/index.ts"]
|
||||||
1
MagnetLink.txt
Normal file
1
MagnetLink.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
magnet:?xt=urn:btih:A18230D43BDA105BE7DEF84CB711859018AAA92D&dn=Snow%20Crash%20by%20Neal%20Stephenson%20EPUB&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.bittor.pw%3A1337%2Fannounce&tr=udp%3A%2F%2Fpublic.popcorn-tracker.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.dler.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce&tr=udp%3A%2F%2Fglotorrents.pw%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftorrent.gresille.org%3A80%2Fannounce&tr=udp%3A%2F%2Fp4p.arenabg.com%3A1337&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337
|
||||||
@@ -66,8 +66,8 @@ export class TorrentSession {
|
|||||||
this.refillWorkers();
|
this.refillWorkers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
// Tracker error
|
console.error(`[Engine] Tracker ${tracker} failed: ${e?.message || e}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ export class TorrentSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const worker of this.workers) {
|
for (const worker of this.workers) {
|
||||||
if (worker.getPendingRequests().size >= 5) continue;
|
if (worker.getPendingRequests().size >= 5 || worker.isChoked()) continue;
|
||||||
|
|
||||||
// 1. Prioritize ongoing piece reassembly
|
// 1. Prioritize ongoing piece reassembly
|
||||||
let foundWork = false;
|
let foundWork = false;
|
||||||
@@ -212,6 +212,7 @@ export class TorrentSession {
|
|||||||
const actualHash = crypto.createHash('sha1').update(fullPiece).digest();
|
const actualHash = crypto.createHash('sha1').update(fullPiece).digest();
|
||||||
|
|
||||||
if (expectedHash && Buffer.compare(actualHash, expectedHash) === 0) {
|
if (expectedHash && Buffer.compare(actualHash, expectedHash) === 0) {
|
||||||
|
console.log(`[Engine] Piece ${index} VERIFIED! Progress: ${this.progress}%`);
|
||||||
this.bitfield.set(index);
|
this.bitfield.set(index);
|
||||||
this.storage?.writePiece(index, this.pieceLength, fullPiece);
|
this.storage?.writePiece(index, this.pieceLength, fullPiece);
|
||||||
this.reassemblers.delete(index);
|
this.reassemblers.delete(index);
|
||||||
@@ -224,11 +225,26 @@ export class TorrentSession {
|
|||||||
console.log(`[Engine] Torrent ${this.hash} download COMPLETED!`);
|
console.log(`[Engine] Torrent ${this.hash} download COMPLETED!`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Hash failed, restart piece
|
const expectedHex = expectedHash ? Buffer.from(expectedHash).toString('hex').slice(0, 8) : 'unknown';
|
||||||
|
const actualHex = actualHash.toString('hex').slice(0, 8);
|
||||||
|
console.error(`[Engine] Piece ${index} Hash Mismatch! Expected ${expectedHex}... Got ${actualHex}...`);
|
||||||
this.reassemblers.delete(index);
|
this.reassemblers.delete(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate granular progress
|
||||||
|
const verifiedBytes = Array.from({ length: this.bitfield.totalPieces })
|
||||||
|
.filter((_, i) => this.bitfield!.has(i))
|
||||||
|
.length * this.pieceLength;
|
||||||
|
|
||||||
|
let bufferedBytes = 0;
|
||||||
|
for (const r of this.reassemblers.values()) {
|
||||||
|
bufferedBytes += r.getBufferedBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentBytes = Math.min(this.totalSize, verifiedBytes + bufferedBytes); // Cap at total size
|
||||||
|
this.progress = Math.floor((currentBytes / this.totalSize) * 100);
|
||||||
|
|
||||||
this.scheduleWork();
|
this.scheduleWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,15 @@ export async function getPeersFromUDPTracker(trackerUrl: string, infoHashHex: st
|
|||||||
|
|
||||||
const connectMsg = Buffer.concat([connectionId, Buffer.from([0, 0, 0, 0]), transactionId]);
|
const connectMsg = Buffer.concat([connectionId, Buffer.from([0, 0, 0, 0]), transactionId]);
|
||||||
|
|
||||||
const timeout = setTimeout(() => { client.close(); resolve([]); }, getCONFIG().TRACKER_TIMEOUT);
|
const timeout = setTimeout(() => {
|
||||||
|
console.warn(`[UDP] Tracker ${url.hostname} timed out`);
|
||||||
|
client.close();
|
||||||
|
resolve([]);
|
||||||
|
}, getCONFIG().TRACKER_TIMEOUT);
|
||||||
|
|
||||||
client.on("message", (msg) => {
|
client.on("message", (msg) => {
|
||||||
|
// ... existing message handling check
|
||||||
|
// For brevity keeping logic same but assuming success logging could be added here if needed
|
||||||
const action = msg.readInt32BE(0);
|
const action = msg.readInt32BE(0);
|
||||||
const receivedTransactionId = msg.slice(4, 8);
|
const receivedTransactionId = msg.slice(4, 8);
|
||||||
if (!transactionId.equals(receivedTransactionId)) return;
|
if (!transactionId.equals(receivedTransactionId)) return;
|
||||||
@@ -47,17 +53,30 @@ export async function getPeersFromUDPTracker(trackerUrl: string, infoHashHex: st
|
|||||||
Buffer.from([0, 0, 0, 2]), Buffer.alloc(4), Buffer.alloc(4),
|
Buffer.from([0, 0, 0, 2]), Buffer.alloc(4), Buffer.alloc(4),
|
||||||
Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]), Buffer.from([0x1B, 0x39])
|
Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]), Buffer.from([0x1B, 0x39])
|
||||||
]);
|
]);
|
||||||
client.send(announceMsg, parseInt(url.port), url.hostname);
|
client.send(announceMsg, parseInt(url.port), url.hostname, (err) => {
|
||||||
|
if (err) console.error(`[UDP] Announce send error to ${url.hostname}:`, err);
|
||||||
|
});
|
||||||
} else if (action === 1) {
|
} else if (action === 1) {
|
||||||
const peers = [];
|
const peers = [];
|
||||||
for (let i = 20; i + 6 <= msg.length; i += 6) {
|
for (let i = 20; i + 6 <= msg.length; i += 6) {
|
||||||
peers.push(`${msg[i]}.${msg[i + 1]}.${msg[i + 2]}.${msg[i + 3]}:${msg.readUInt16BE(i + 4)}`);
|
peers.push(`${msg[i]}.${msg[i + 1]}.${msg[i + 2]}.${msg[i + 3]}:${msg.readUInt16BE(i + 4)}`);
|
||||||
}
|
}
|
||||||
|
console.log(`[UDP] Tracker ${url.hostname} returned ${peers.length} peers`);
|
||||||
clearTimeout(timeout); client.close(); resolve(peers);
|
clearTimeout(timeout); client.close(); resolve(peers);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.send(connectMsg, parseInt(url.port), url.hostname, () => {});
|
client.on('error', (err) => {
|
||||||
|
console.error(`[UDP] Socket error for ${url.hostname}:`, err);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Explicitly handle DNS errors during send
|
||||||
|
client.send(connectMsg, parseInt(url.port), url.hostname, (err) => {
|
||||||
|
if (err) console.error(`[UDP] Connect send error to ${url.hostname}:`, err);
|
||||||
|
else console.log(`[UDP] Sent connect to ${url.hostname}`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,4 +35,12 @@ export class PieceReassembler {
|
|||||||
}
|
}
|
||||||
return missing;
|
return missing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getBufferedBytes(): number {
|
||||||
|
let bytes = 0;
|
||||||
|
for (const data of this.blocks.values()) {
|
||||||
|
bytes += data.length;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ export class PeerWorker {
|
|||||||
this.peerChoked = true;
|
this.peerChoked = true;
|
||||||
break;
|
break;
|
||||||
case 1: // unchoke
|
case 1: // unchoke
|
||||||
|
console.log(`[Worker] Unchoked by ${this.host}`);
|
||||||
this.peerChoked = false;
|
this.peerChoked = false;
|
||||||
this.onReady(); // Ready to request blocks now!
|
this.onReady(); // Ready to request blocks now!
|
||||||
break;
|
break;
|
||||||
@@ -123,6 +124,7 @@ export class PeerWorker {
|
|||||||
const begin = payload.readUInt32BE(4);
|
const begin = payload.readUInt32BE(4);
|
||||||
const block = payload.slice(8);
|
const block = payload.slice(8);
|
||||||
this.pendingRequests.delete(`${index}:${begin}`);
|
this.pendingRequests.delete(`${index}:${begin}`);
|
||||||
|
// console.log(`[Worker] Received block ${index}:${begin} (${block.length} bytes) from ${this.host}`);
|
||||||
this.onPiece(index, begin, block);
|
this.onPiece(index, begin, block);
|
||||||
this.activeRequests--;
|
this.activeRequests--;
|
||||||
break;
|
break;
|
||||||
@@ -166,6 +168,7 @@ export class PeerWorker {
|
|||||||
const len = Buffer.alloc(4);
|
const len = Buffer.alloc(4);
|
||||||
len.writeUInt32BE(13);
|
len.writeUInt32BE(13);
|
||||||
this.socket.write(Buffer.concat([len, req]));
|
this.socket.write(Buffer.concat([len, req]));
|
||||||
|
console.log(`[Worker] Requested block ${index}:${begin} from ${this.host}`);
|
||||||
this.activeRequests++;
|
this.activeRequests++;
|
||||||
this.pendingRequests.add(key);
|
this.pendingRequests.add(key);
|
||||||
}
|
}
|
||||||
@@ -174,4 +177,8 @@ export class PeerWorker {
|
|||||||
public hasPiece(index: number) {
|
public hasPiece(index: number) {
|
||||||
return this.peerBitfield?.has(index) ?? false;
|
return this.peerBitfield?.has(index) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isChoked() {
|
||||||
|
return this.peerChoked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user