Skip to content

Commit 38246fb

Browse files
committed
Add chunked file transfer support
1 parent 7482491 commit 38246fb

File tree

3 files changed

+188
-27
lines changed

3 files changed

+188
-27
lines changed

cloud/server.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -531,9 +531,55 @@ def handle_command_response(data):
531531
command_id = data.get('command_id')
532532
print(f'Received command_response for command_id: {command_id}')
533533
if command_id in pending_commands:
534-
pending_commands[command_id]['response'] = data.get('response')
535-
pending_commands[command_id]['completed'] = True
536-
print(f'Updated pending_commands for command_id: {command_id}')
534+
response_data = data.get('response')
535+
if isinstance(response_data, dict) and response_data.get('chunked'):
536+
print(f'Detected chunked response for command_id: {command_id}')
537+
pending_commands[command_id]['chunked'] = True
538+
pending_commands[command_id]['total_chunks'] = response_data.get('total_chunks', 0)
539+
pending_commands[command_id]['total_size'] = response_data.get('total_size', 0)
540+
pending_commands[command_id]['file_type'] = response_data.get('type', 'text')
541+
pending_commands[command_id]['file_status'] = response_data.get('status', 'File content retrieved')
542+
pending_commands[command_id]['chunks_received'] = {}
543+
pending_commands[command_id]['completed'] = False
544+
else:
545+
pending_commands[command_id]['response'] = response_data
546+
pending_commands[command_id]['completed'] = True
547+
print(f'Updated pending_commands for command_id: {command_id}')
548+
549+
@socketio.on('command_response_chunk')
550+
def handle_command_response_chunk(data):
551+
command_id = data.get('command_id')
552+
chunk_index = data.get('chunk_index')
553+
total_chunks = data.get('total_chunks')
554+
content = data.get('content')
555+
is_final = data.get('is_final', False)
556+
557+
print(f'Received chunk {chunk_index + 1}/{total_chunks} for command_id: {command_id}')
558+
559+
if command_id in pending_commands:
560+
command_data = pending_commands[command_id]
561+
if 'chunks_received' not in command_data:
562+
command_data['chunks_received'] = {}
563+
command_data['chunks_received'][chunk_index] = content
564+
if len(command_data['chunks_received']) == command_data.get('total_chunks', 0):
565+
print(f'All chunks received for command_id: {command_id}, assembling response...')
566+
assembled_content = ""
567+
for i in range(command_data['total_chunks']):
568+
if i in command_data['chunks_received']:
569+
assembled_content += command_data['chunks_received'][i]
570+
final_response = {
571+
'status': command_data.get('file_status', 'File content retrieved'),
572+
'content': assembled_content,
573+
'type': command_data.get('file_type', 'text')
574+
}
575+
command_data['response'] = final_response
576+
command_data['completed'] = True
577+
del command_data['chunks_received']
578+
print(f'Assembled chunked response for command_id: {command_id}')
579+
elif is_final:
580+
print(f'Final chunk received but missing chunks for command_id: {command_id}')
581+
command_data['response'] = {'error': 'Incomplete chunked file transfer'}
582+
command_data['completed'] = True
537583

538584
def send_command_to_server(server_id, command, command_id=None, method='GET', body=None, shareify_jwt=None):
539585
if server_id not in connected_servers:

current/cloud_connection.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,14 @@ def execute_api_request(self, command_id, url, method='GET', body=None, shareify
488488

489489
print(f"Making {method} request to: {full_url}")
490490

491-
if not (url == '/resources' or url == 'resources' or url == '/is_up' or url == 'is_up' or url == '/user/get_self' or url == 'user/get_self' or url == 'user/login' or url == '/user/login' or url == '/get_logs' or url == '/finder' or url == '/get_file'):
491+
allowed_endpoints = [
492+
'/resources', 'resources',
493+
'/is_up', 'is_up',
494+
'/user/get_self', 'user/get_self',
495+
'/user/login', 'user/login',
496+
'/get_logs', '/finder', '/get_file'
497+
]
498+
if not any(url == ep or url.startswith(ep) for ep in allowed_endpoints):
492499
if self.sio.connected:
493500
try:
494501
self.sio.emit('command_response', {
@@ -532,19 +539,12 @@ def execute_api_request(self, command_id, url, method='GET', body=None, shareify
532539
response_data = response.json()
533540
except json.JSONDecodeError:
534541
response_data = response.text
535-
536-
if self.sio.connected:
537-
try:
538-
self.sio.emit('command_response', {
539-
'command_id': command_id,
540-
'response': response_data
541-
})
542-
print(f"Successfully emitted response for command {command_id}")
543-
time.sleep(0.1)
544-
except Exception as emit_error:
545-
print(f"Failed to emit response: {emit_error}")
542+
543+
is_file_endpoint = url.endswith('/get_file') or 'get_file' in url
544+
if is_file_endpoint and isinstance(response_data, dict) and 'content' in response_data:
545+
self.handle_large_file_response(command_id, response_data)
546546
else:
547-
print("Socket not connected, cannot emit response")
547+
self.send_standard_response(command_id, response_data)
548548

549549
except requests.exceptions.Timeout:
550550
print(f"API request timeout for command {command_id}")

host/cloud_connection.py

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -540,18 +540,11 @@ def execute_api_request(self, command_id, url, method='GET', body=None, shareify
540540
except json.JSONDecodeError:
541541
response_data = response.text
542542

543-
if self.sio.connected:
544-
try:
545-
self.sio.emit('command_response', {
546-
'command_id': command_id,
547-
'response': response_data
548-
})
549-
print(f"Successfully emitted response for command {command_id}")
550-
time.sleep(0.1)
551-
except Exception as emit_error:
552-
print(f"Failed to emit response: {emit_error}")
543+
is_file_endpoint = url.endswith('/get_file') or 'get_file' in url
544+
if is_file_endpoint and isinstance(response_data, dict) and 'content' in response_data:
545+
self.handle_large_file_response(command_id, response_data)
553546
else:
554-
print("Socket not connected, cannot emit response")
547+
self.send_standard_response(command_id, response_data)
555548

556549
except requests.exceptions.Timeout:
557550
print(f"API request timeout for command {command_id}")
@@ -585,6 +578,128 @@ def execute_api_request(self, command_id, url, method='GET', body=None, shareify
585578
})
586579
except Exception as emit_error:
587580
print(f"Failed to emit general error: {emit_error}")
581+
582+
def send_standard_response(self, command_id, response_data):
583+
if self.sio.connected:
584+
try:
585+
self.sio.emit('command_response', {
586+
'command_id': command_id,
587+
'response': response_data
588+
})
589+
print(f"Successfully emitted response for command {command_id}")
590+
time.sleep(0.1)
591+
except Exception as emit_error:
592+
print(f"Failed to emit response: {emit_error}")
593+
else:
594+
print("Socket not connected, cannot emit response")
595+
596+
def handle_large_file_response(self, command_id, response_data):
597+
import base64
598+
599+
if 'content' not in response_data:
600+
self.send_standard_response(command_id, response_data)
601+
return
602+
603+
content = response_data['content']
604+
605+
if response_data.get('type') == 'binary':
606+
content_size = len(content) * 3 // 4
607+
else:
608+
content_size = len(content.encode('utf-8'))
609+
610+
chunk_threshold = 4 * 1024 * 1024
611+
612+
if content_size > chunk_threshold:
613+
print(f"File content size ({content_size} bytes) exceeds threshold, chunking...")
614+
self.send_chunked_file_response(command_id, response_data)
615+
else:
616+
print(f"File content size ({content_size} bytes) within limit, sending normally")
617+
self.send_standard_response(command_id, response_data)
618+
619+
def send_chunked_file_response(self, command_id, response_data):
620+
import base64
621+
622+
content = response_data['content']
623+
file_type = response_data.get('type', 'text')
624+
status = response_data.get('status', 'File content retrieved')
625+
626+
chunk_size = 3 * 1024 * 1024
627+
628+
if file_type == 'binary':
629+
try:
630+
original_bytes = base64.b64decode(content)
631+
total_size = len(original_bytes)
632+
chunks_needed = (total_size + chunk_size - 1) // chunk_size
633+
634+
print(f"Sending binary file in {chunks_needed} chunks...")
635+
636+
self.sio.emit('command_response', {
637+
'command_id': command_id,
638+
'response': {
639+
'status': status,
640+
'type': file_type,
641+
'chunked': True,
642+
'total_chunks': chunks_needed,
643+
'total_size': total_size,
644+
'chunk_index': 0
645+
}
646+
})
647+
time.sleep(0.1)
648+
649+
for i in range(chunks_needed):
650+
start_pos = i * chunk_size
651+
end_pos = min(start_pos + chunk_size, total_size)
652+
chunk_bytes = original_bytes[start_pos:end_pos]
653+
chunk_b64 = base64.b64encode(chunk_bytes).decode('utf-8')
654+
655+
self.sio.emit('command_response_chunk', {
656+
'command_id': command_id,
657+
'chunk_index': i,
658+
'total_chunks': chunks_needed,
659+
'content': chunk_b64,
660+
'is_final': (i == chunks_needed - 1)
661+
})
662+
print(f"Sent chunk {i + 1}/{chunks_needed}")
663+
time.sleep(0.05)
664+
665+
except Exception as e:
666+
print(f"Error chunking binary content: {e}")
667+
self.send_standard_response(command_id, {'error': 'Failed to chunk binary content'})
668+
else:
669+
content_bytes = content.encode('utf-8')
670+
total_size = len(content_bytes)
671+
chunks_needed = (total_size + chunk_size - 1) // chunk_size
672+
673+
print(f"Sending text file in {chunks_needed} chunks...")
674+
675+
self.sio.emit('command_response', {
676+
'command_id': command_id,
677+
'response': {
678+
'status': status,
679+
'type': file_type,
680+
'chunked': True,
681+
'total_chunks': chunks_needed,
682+
'total_size': total_size,
683+
'chunk_index': 0
684+
}
685+
})
686+
time.sleep(0.1)
687+
688+
for i in range(chunks_needed):
689+
start_pos = i * chunk_size
690+
end_pos = min(start_pos + chunk_size, total_size)
691+
chunk_bytes = content_bytes[start_pos:end_pos]
692+
chunk_text = chunk_bytes.decode('utf-8', errors='ignore')
693+
694+
self.sio.emit('command_response_chunk', {
695+
'command_id': command_id,
696+
'chunk_index': i,
697+
'total_chunks': chunks_needed,
698+
'content': chunk_text,
699+
'is_final': (i == chunks_needed - 1)
700+
})
701+
print(f"Sent chunk {i + 1}/{chunks_needed}")
702+
time.sleep(0.05)
588703

589704
def main():
590705
try:

0 commit comments

Comments
 (0)