234 lines
6.3 KiB
Plaintext
234 lines
6.3 KiB
Plaintext
// HTTP Download Actor
|
|
// Handles download requests and progress queries
|
|
var http = use('http');
|
|
var os = use('os');
|
|
|
|
// Actor state
|
|
var state = {
|
|
downloading: false,
|
|
current_url: null,
|
|
total_bytes: 0,
|
|
downloaded_bytes: 0,
|
|
start_time: 0,
|
|
error: null,
|
|
connection: null,
|
|
download_msg: null,
|
|
chunks: []
|
|
};
|
|
|
|
// Helper to calculate progress percentage
|
|
function get_progress() {
|
|
if (state.total_bytes == 0) {
|
|
return 0;
|
|
}
|
|
return Math.round((state.downloaded_bytes / state.total_bytes) * 100);
|
|
}
|
|
|
|
// Helper to format status response
|
|
function get_status() {
|
|
if (!state.downloading) {
|
|
return {
|
|
status: 'idle',
|
|
error: state.error
|
|
};
|
|
}
|
|
|
|
var elapsed = os.now() - state.start_time;
|
|
var bytes_per_sec = elapsed > 0 ? state.downloaded_bytes / elapsed : 0;
|
|
|
|
return {
|
|
status: 'downloading',
|
|
url: state.current_url,
|
|
progress: get_progress(),
|
|
downloaded_bytes: state.downloaded_bytes,
|
|
total_bytes: state.total_bytes,
|
|
elapsed_seconds: elapsed,
|
|
bytes_per_second: Math.round(bytes_per_sec)
|
|
};
|
|
}
|
|
|
|
// Main message receiver
|
|
$_.receiver(function(msg) {
|
|
switch (msg.type) {
|
|
case 'download':
|
|
if (state.downloading) {
|
|
send(msg, {
|
|
type: 'error',
|
|
error: 'Already downloading',
|
|
current_url: state.current_url
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!msg.url) {
|
|
send(msg, {
|
|
type: 'error',
|
|
error: 'No URL provided'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Start download
|
|
state.downloading = true;
|
|
state.current_url = msg.url;
|
|
state.total_bytes = 0;
|
|
state.downloaded_bytes = 0;
|
|
state.start_time = os.now();
|
|
state.error = null;
|
|
state.download_msg = msg;
|
|
state.chunks = [];
|
|
|
|
try {
|
|
// Start the connection
|
|
state.connection = http.fetch_start(msg.url, msg.options || {});
|
|
if (!state.connection) {
|
|
throw new Error('Failed to start download');
|
|
}
|
|
|
|
// Schedule the first chunk read
|
|
$_.delay(read_next_chunk, 0);
|
|
|
|
} catch (e) {
|
|
state.error = e.toString();
|
|
state.downloading = false;
|
|
|
|
send(msg, {
|
|
type: 'error',
|
|
error: state.error,
|
|
url: msg.url
|
|
});
|
|
}
|
|
break;
|
|
|
|
case 'status':
|
|
log.console(`got status request. current is ${json.encode(get_status())}`)
|
|
send(msg, {
|
|
type: 'status_response',
|
|
...get_status()
|
|
});
|
|
break;
|
|
|
|
case 'cancel':
|
|
if (state.downloading) {
|
|
// Cancel the download
|
|
if (state.connection) {
|
|
http.fetch_close(state.connection);
|
|
state.connection = null;
|
|
}
|
|
state.downloading = false;
|
|
state.current_url = null;
|
|
state.download_msg = null;
|
|
state.chunks = [];
|
|
|
|
send(msg, {
|
|
type: 'cancelled',
|
|
message: 'Download cancelled',
|
|
url: state.current_url
|
|
});
|
|
} else {
|
|
send(msg, {
|
|
type: 'error',
|
|
error: 'No download in progress'
|
|
});
|
|
}
|
|
break;
|
|
|
|
default:
|
|
send(msg, {
|
|
type: 'error',
|
|
error: 'Unknown message type: ' + msg.type
|
|
});
|
|
}
|
|
});
|
|
|
|
// Non-blocking chunk reader
|
|
function read_next_chunk() {
|
|
if (!state.downloading || !state.connection) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var chunk = http.fetch_read_chunk(state.connection);
|
|
|
|
if (chunk == null) {
|
|
// Download complete
|
|
finish_download();
|
|
return;
|
|
}
|
|
|
|
// Store chunk
|
|
state.chunks.push(chunk);
|
|
|
|
// Update progress
|
|
var info = http.fetch_info(state.connection);
|
|
state.downloaded_bytes = info.bytes_read;
|
|
if (info.headers_complete && info.content_length > 0) {
|
|
state.total_bytes = info.content_length;
|
|
}
|
|
|
|
// Schedule next chunk read
|
|
$_.delay(read_next_chunk, 0);
|
|
|
|
} catch (e) {
|
|
// Error during download
|
|
state.error = e.toString();
|
|
if (state.connection) {
|
|
http.fetch_close(state.connection);
|
|
}
|
|
|
|
if (state.download_msg) {
|
|
send(state.download_msg, {
|
|
type: 'error',
|
|
error: state.error,
|
|
url: state.current_url
|
|
});
|
|
}
|
|
|
|
// Reset state
|
|
state.downloading = false;
|
|
state.connection = null;
|
|
state.download_msg = null;
|
|
state.chunks = [];
|
|
}
|
|
}
|
|
|
|
// Complete the download and send result
|
|
function finish_download() {
|
|
if (state.connection) {
|
|
http.fetch_close(state.connection);
|
|
}
|
|
|
|
// Combine all chunks into single ArrayBuffer
|
|
var total_size = 0;
|
|
for (var i = 0; i < state.chunks.length; i++) {
|
|
total_size += state.chunks[i].byteLength;
|
|
}
|
|
|
|
var result = new ArrayBuffer(total_size);
|
|
var view = new Uint8Array(result);
|
|
var offset = 0;
|
|
|
|
for (var i = 0; i < state.chunks.length; i++) {
|
|
var chunk_view = new Uint8Array(state.chunks[i]);
|
|
view.set(chunk_view, offset);
|
|
offset += state.chunks[i].byteLength;
|
|
}
|
|
|
|
// Send complete message
|
|
if (state.download_msg) {
|
|
send(state.download_msg, {
|
|
type: 'complete',
|
|
url: state.current_url,
|
|
data: result,
|
|
size: result.byteLength,
|
|
duration: os.now() - state.start_time
|
|
});
|
|
}
|
|
|
|
// Reset state
|
|
state.downloading = false;
|
|
state.connection = null;
|
|
state.current_url = null;
|
|
state.download_msg = null;
|
|
state.chunks = [];
|
|
} |