Added ability to upload large files

This commit is contained in:
Lonami 2016-09-16 16:37:45 +02:00
parent 922f17956b
commit 15994d0b78
3 changed files with 31 additions and 27 deletions

View File

@ -216,7 +216,7 @@ if __name__ == '__main__':
client.run() client.run()
except Exception as e: except Exception as e:
print('Unexpected error ({}): {} at\n{}', type(e), e, traceback.format_exc()) print('Unexpected error ({}): {} at\n{}'.format(type(e), e, traceback.format_exc()))
finally: finally:
print_title('Exit') print_title('Exit')

View File

@ -21,7 +21,6 @@ class TcpClient:
"""Connects to the specified IP and port number""" """Connects to the specified IP and port number"""
self.socket.connect((ip, port)) self.socket.connect((ip, port))
self.connected = True self.connected = True
self.socket.setblocking(False)
def close(self): def close(self):
"""Closes the connection""" """Closes the connection"""
@ -34,18 +33,9 @@ class TcpClient:
# Ensure that only one thread can send data at once # Ensure that only one thread can send data at once
with self.lock: with self.lock:
# Do not use .sendall: # Set blocking so it doesn't error
# "on error, an exception is raised, and there is no way to self.socket.setblocking(True)
# determine how much data, if any, was successfully sent." self.socket.sendall(data)
while data:
try:
sent = self.socket.send(data)
data = data[sent:]
except BlockingIOError as e:
if 'Errno 11' in str(e): # Error #11: Resource temporary unavailable
time.sleep(0.1) # Sleep a bit waiting for the resource to be available
else:
raise e
def read(self, buffer_size): def read(self, buffer_size):
"""Reads (receives) the specified bytes from the connected peer""" """Reads (receives) the specified bytes from the connected peer"""
@ -55,6 +45,9 @@ class TcpClient:
# Ensure it is not cancelled at first, so we can enter the loop # Ensure it is not cancelled at first, so we can enter the loop
self.cancelled = False self.cancelled = False
# Set non-blocking so it can be cancelled
self.socket.setblocking(False)
with BinaryWriter() as writer: with BinaryWriter() as writer:
while writer.written_count < buffer_size: while writer.written_count < buffer_size:
# Only do cancel if no data was read yet # Only do cancel if no data was read yet

View File

@ -16,6 +16,7 @@ from tl import MTProtoRequest
from tl import Session from tl import Session
# The Requests and types that we'll be using # The Requests and types that we'll be using
from tl.functions.upload import SaveBigFilePartRequest
from tl.types import \ from tl.types import \
PeerUser, PeerChat, PeerChannel, \ PeerUser, PeerChat, PeerChannel, \
InputPeerUser, InputPeerChat, InputPeerChannel, InputPeerEmpty, \ InputPeerUser, InputPeerChat, InputPeerChannel, InputPeerEmpty, \
@ -270,35 +271,41 @@ class TelegramClient:
def upload_file(self, file_path, part_size_kb=64, file_name=None): def upload_file(self, file_path, part_size_kb=64, file_name=None):
"""Uploads the specified media with the given chunk (part) size, in KB. """Uploads the specified media with the given chunk (part) size, in KB.
If no custom file name is specified, the real file name will be used. If no custom file name is specified, the real file name will be used"""
This method will fail when trying to upload files larger than 10MB!"""
if part_size_kb > 512:
raise ValueError('The part size must be less or equal to 512KB')
part_size = int(part_size_kb * 1024) part_size = int(part_size_kb * 1024)
if part_size % 1024 != 0: if part_size % 1024 != 0:
raise ValueError('The part size must be evenly divisible by 1024') raise ValueError('The part size must be evenly divisible by 1024')
# Determine whether the file is too big (over 10MB) or not
# Telegram does make a distinction between smaller or larger files
file_size = path.getsize(file_path)
is_large = file_size > 10 * 1024 * 1024
part_count = (file_size + part_size - 1) // part_size
# Multiply the datetime timestamp by 10^6 to get the ticks # Multiply the datetime timestamp by 10^6 to get the ticks
# This is high likely going to be unique # This is high likely going to be unique
file_id = int(datetime.now().timestamp() * (10 ** 6)) file_id = int(datetime.now().timestamp() * (10 ** 6))
part_index = 0
hash_md5 = md5() hash_md5 = md5()
with open(file_path, 'rb') as file: with open(file_path, 'rb') as file:
while True: for part_index in range(part_count):
# Read the file by in chunks of size part_size # Read the file by in chunks of size part_size
part = file.read(part_size) part = file.read(part_size)
# If we have read no data (0 bytes), the file is over # The SavePartRequest is different depending on whether
# So there is nothing left to upload # the file is too large or not (over or less than 10MB)
if not part: if is_large:
break request = SaveBigFilePartRequest(file_id, part_index, part_count, part)
else:
print('I read {} out of {}'.format(len(part), part_size)) request = SaveFilePartRequest(file_id, part_index, part)
# Invoke the file upload and increment both the part index and MD5 checksum # Invoke the file upload and increment both the part index and MD5 checksum
result = self.invoke(SaveFilePartRequest(file_id, part_index, part)) result = self.invoke(request)
if result: if result:
part_index += 1
hash_md5.update(part) hash_md5.update(part)
else: else:
raise ValueError('Could not upload file part #{}'.format(part_index)) raise ValueError('Could not upload file part #{}'.format(part_index))
@ -309,7 +316,7 @@ class TelegramClient:
# After the file has been uploaded, we can return a handle pointing to it # After the file has been uploaded, we can return a handle pointing to it
return InputFile(id=file_id, return InputFile(id=file_id,
parts=part_index, parts=part_count,
name=file_name, name=file_name,
md5_checksum=hash_md5.hexdigest()) md5_checksum=hash_md5.hexdigest())
@ -331,6 +338,10 @@ class TelegramClient:
# TODO If the input file is an audio, find out: # TODO If the input file is an audio, find out:
# Performer and song title and add DocumentAttributeAudio # Performer and song title and add DocumentAttributeAudio
] ]
# Ensure we have a mime type, any; but it cannot be None
# «The "octet-stream" subtype is used to indicate that a body contains arbitrary binary data.»
if not mime_type:
mime_type = 'application/octet-stream'
self.send_media_file(InputMediaUploadedDocument(file=input_file, self.send_media_file(InputMediaUploadedDocument(file=input_file,
mime_type=mime_type, mime_type=mime_type,
attributes=attributes, attributes=attributes,