diff --git a/src/myftp/client.py b/src/myftp/client.py index 1c155ae..20a751c 100644 --- a/src/myftp/client.py +++ b/src/myftp/client.py @@ -4,7 +4,7 @@ from socket import socket, AF_INET, SOCK_DGRAM -from typing import Pattern, Tuple +from typing import Pattern, Tuple, Optional from argparse import ArgumentParser import traceback import os @@ -30,7 +30,7 @@ unknown_request_opcode: int = 0b101 rescode_dict: dict[int, str] = { 0b011: "File Not Found Error", 0b100: "Unknown Request", - 0b101: "Change Unsuccessful Error", + 0b101: "Change/Put Unsuccessful Error", 0b000: "Put/Change Request Successful", 0b001: "Get Request Successful", 0b010: "Summary Request Successful", @@ -87,7 +87,7 @@ class Client: first_byte = (get_request_opcode << 5) + len(filename) - second_byte_to_n_byte: bytes = filename.encode("ascii") + second_byte_to_n_byte = filename.encode("ascii") print( f"myftp> - {self.protocol} - Getting file {filename} from the server" @@ -96,6 +96,11 @@ class Client: # put command handling elif put_command_pattern.match(command): command_name, filename = command.split(" ", 1) + + first_byte, second_byte_to_n_byte, data = self.put_payload_handling( + filename + ) + print( f"myftp> - {self.protocol} - Putting file {filename} into the server" ) if self.debug else None @@ -120,9 +125,16 @@ class Client: first_byte: int = unknown_request_opcode << 5 # get or put case - if command_name == "get" or command_name == "put": + if command_name == "get": payload = first_byte.to_bytes(1, "big") + second_byte_to_n_byte # type: ignore + elif command_name == "put": + payload = ( + first_byte.to_bytes(1, "big") + second_byte_to_n_byte + data # type: ignore + if second_byte_to_n_byte is not None and data is not None # type: ignore + else first_byte.to_bytes(1, "big") # type: ignore + ) + elif command_name == "summary": pass @@ -189,19 +201,50 @@ class Client: # successful rescodes else: + # help rescode and successful change or put rescode if rescode == 0b110: print(f"myftp> - {self.protocol} - {response_data.decode('ascii')}") - else: + elif rescode == 0b000: + print(f"myftp> - {self.protocol} - {rescode_dict[rescode]}") + # get rescode + elif rescode == 0b001: self.handle_get_response_from_server(filename_length, response_data) + def put_payload_handling( + self, filename: str + ) -> Tuple[int, Optional[bytes], Optional[bytes]]: + """ + Assemble the pay load to put the file onto server + + Return first_byte, second_byte_to_n_byte and data if successful + Or (None, None, None) if file not found + """ + try: + with open(os.path.join(self.directory_path, filename), "rb") as file: + content = file.read() + content_length = len(content) + + first_byte = (put_request_opcode << 5) + len(filename) + + second_byte_to_n_byte = filename.encode( + "ascii" + ) + content_length.to_bytes(4, "big") + + data = content + + return (first_byte, second_byte_to_n_byte, data) + + except FileNotFoundError: + return ((put_request_opcode << 5), None, None) + def handle_get_response_from_server( self, filename_length: int, response_data: bytes ): """ - response_data is - file name (filename_length bytes) + - file size (4 bytes) + - file content (rest of the bytes) + Response_data is + File name (filename_length bytes) + + File size (4 bytes) + + File content (rest of the bytes) """ try: filename = response_data[:filename_length].decode("ascii") diff --git a/src/myftp/server.py b/src/myftp/server.py index 9acfd20..b842afe 100644 --- a/src/myftp/server.py +++ b/src/myftp/server.py @@ -6,6 +6,7 @@ from socket import socket, AF_INET, SOCK_DGRAM from argparse import ArgumentParser from typing import Optional, Tuple +import traceback import os # Res-codes @@ -68,7 +69,9 @@ class Server: first_byte = bytes([req_payload[0]]) - request_type, filename_length = self.decode_first_byte(first_byte) + request_type, filename_length_in_bytes = self.decode_first_byte( + first_byte + ) print( f"myftp> - {self.protocol} - Received message from client at {clientAddress}: {req_payload}" @@ -83,7 +86,7 @@ class Server: rescode = rescode_success_dict["help_rescode"] response_data = "get,put,summary,change,help,bye".encode("ascii") filename = None - filename_length = None + filename_length_in_bytes = None elif request_type == "get": pre_payload = self.process_get_req(req_payload[1:]) @@ -95,24 +98,41 @@ class Server: ): rescode = rescode_success_dict["correct_get_request_rescode"] filename = pre_payload[0] - filename_length = pre_payload[2] + filename_length_in_bytes = pre_payload[2] response_data = pre_payload[1] else: rescode = rescode_fail_dict["file_not_error_rescode"] - filename_length = None + filename_length_in_bytes = None + filename = None + response_data = None + + elif request_type == "put": + # put request failed since there wasnt a file sent from client + if filename_length_in_bytes == 0: + rescode = rescode_fail_dict["unsuccessful_change_rescode"] + filename_length_in_bytes = None + filename = None + response_data = None + + # put request success + else: + rescode = self.process_put_req( + filename_length_in_bytes, req_payload[1:] + ) + filename_length_in_bytes = None filename = None response_data = None elif request_type == "unknown": rescode = rescode_fail_dict["unknown_request_rescode"] - filename_length = None + filename_length_in_bytes = None filename = None response_data = None res_payload: bytes = self.build_res_payload( rescode=rescode, # type: ignore - filename_length=filename_length, + filename_length=filename_length_in_bytes, filename=filename, # type: ignore response_data=response_data, # type:ignore ) @@ -154,6 +174,38 @@ class Server: return request_type, filename_length_in_bytes + def process_put_req(self, filename_length: int, req_payload: bytes) -> int: + """ + Reconstruct file put by client + """ + filename = req_payload[:filename_length].decode("ascii") + filesize = int.from_bytes( + req_payload[filename_length : filename_length + 4], "big" + ) + file_content = req_payload[filename_length + 4 :] + + print( + f"myftp> - {self.protocol} - Reconstructing the file {filename} of size {filesize} bytes on the server after the client finished sending" + ) + + try: + with open(os.path.join(self.directory_path, filename), "wb") as file: + file.write(file_content) + + print( + f"myftp> - {self.protocol} - File {filename} uploaded successfully" + ) + + return rescode_success_dict["correct_put_and_change_request_rescode"] + + except Exception as error: + traceback_info = traceback.format_exc() + + print(f"myftp> - {self.protocol} - {error} happened.") + + print(traceback_info) + return rescode_fail_dict["unsuccessful_change_rescode"] + def process_get_req( self, second_byte_to_byte_n: bytes ) -> Tuple[Optional[str], Optional[bytes], Optional[int]]: @@ -219,6 +271,7 @@ class Server: if filename is None: second_byte_to_FL_plus_five = None else: + # get case second_byte_to_FL_plus_five = ( filename.encode() + len(response_data).to_bytes(4, "big") if response_data is not None