diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index ff62efd30..d6746ad08 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -38,9 +38,6 @@ blacklisted_events = [ 'LinphoneCoreFileTransferSendCb' # missing LinphoneContent ] blacklisted_functions = [ - 'linphone_buffer_new_from_data', - 'linphone_buffer_get_content', - 'linphone_buffer_set_content', 'linphone_call_log_get_local_stats', # missing rtp_stats_t 'linphone_call_log_get_remote_stats', # missing rtp_stats_t 'linphone_call_params_get_privacy', # missing LinphonePrivacyMask @@ -74,7 +71,8 @@ blacklisted_functions = [ 'lp_config_section_to_dict' # missing LinphoneDictionary ] hand_written_functions = [ - HandWrittenInstanceMethod('ChatRoom', 'send_message2', 'linphone_chat_room_send_message2'), + HandWrittenClassMethod('Buffer', 'new_from_data', 'linphone_buffer_new_from_data'), + HandWrittenProperty('Buffer', 'content', 'linphone_buffer_get_content', 'linphone_buffer_set_content'), HandWrittenProperty('Content', 'buffer', 'linphone_content_get_buffer', 'linphone_content_set_buffer'), HandWrittenProperty('Core', 'sound_devices', 'linphone_core_get_sound_devices', None), HandWrittenProperty('Core', 'video_devices', 'linphone_core_get_video_devices', None), diff --git a/tools/python/apixml2python/handwritten_declarations.mustache b/tools/python/apixml2python/handwritten_declarations.mustache index 78fcebc22..a9c2d1ee6 100644 --- a/tools/python/apixml2python/handwritten_declarations.mustache +++ b/tools/python/apixml2python/handwritten_declarations.mustache @@ -25,5 +25,8 @@ PyObject * PyLinphoneSipTransports_FromLCSipTransports(LCSipTransports lcst); time_t PyDateTime_As_time_t(PyObject *obj); PyObject * PyDateTime_From_time_t(time_t t); +static PyObject * pylinphone_Buffer_get_content(PyObject *self, void *closure); +static int pylinphone_Buffer_set_content(PyObject *self, PyObject *value, void *closure); + static PyObject * pylinphone_Content_get_buffer(PyObject *self, void *closure); static int pylinphone_Content_set_buffer(PyObject *self, PyObject *value, void *closure); diff --git a/tools/python/apixml2python/handwritten_definitions.mustache b/tools/python/apixml2python/handwritten_definitions.mustache index 3ff9db17b..6b2e176f1 100644 --- a/tools/python/apixml2python/handwritten_definitions.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -259,66 +259,6 @@ static PyObject * pylinphone_Core_class_method_new_with_config(PyObject *cls, Py } -static void pylinphone_ChatRoom_callback_chat_message_state_changed(LinphoneChatMessage *msg, LinphoneChatMessageState state, void *ud) { - PyGILState_STATE pygil_state; - PyObject *pycm = NULL; - PyObject *_dict = (PyObject *)ud; - PyObject *_cb = PyDict_GetItemString(_dict, "callback"); - PyObject *_ud = PyDict_GetItemString(_dict, "user_data"); - - pygil_state = PyGILState_Ensure(); - pycm = pylinphone_ChatMessage_from_native_ptr(&pylinphone_ChatMessageType, msg); - pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p, %p [%p], %d, %p)", __FUNCTION__, pycm, msg, state, ud); - if ((_cb != NULL) && PyCallable_Check(_cb)) { - PyObject *args = Py_BuildValue("OiO", pycm, state, _ud); - if (PyEval_CallObject(_cb, args) == NULL) { - PyErr_Print(); - } - Py_DECREF(args); - } - pylinphone_trace(-1, "[PYLINPHONE] <<< %s", __FUNCTION__); - PyGILState_Release(pygil_state); -} - -static PyObject * pylinphone_ChatRoom_instance_method_send_message2(PyObject *self, PyObject *args) { - PyObject *_chat_message; - PyObject *_dict; - PyObject *_cb; - PyObject *_ud; - LinphoneChatMessage * _chat_message_native_ptr; - LinphoneChatRoom *native_ptr = pylinphone_ChatRoom_get_native_ptr(self); - - if (native_ptr == NULL) { - PyErr_SetString(PyExc_TypeError, "Invalid linphone.ChatRoom instance"); - return NULL; - } - if (!PyArg_ParseTuple(args, "OOO", &_chat_message, &_cb, &_ud)) { - return NULL; - } - if (!PyObject_IsInstance(_chat_message, (PyObject *)&pylinphone_ChatMessageType)) { - PyErr_SetString(PyExc_TypeError, "The msg argument must be a linphone.ChatMessage"); - return NULL; - } - if ((_cb != Py_None) && !PyCallable_Check(_cb)) { - PyErr_SetString(PyExc_TypeError, "The status_cb argument must be a callable"); - return NULL; - } - if ((_chat_message_native_ptr = pylinphone_ChatMessage_get_native_ptr(_chat_message)) == NULL) { - return NULL; - } - _dict = PyDict_New(); - PyDict_SetItemString(_dict, "callback", _cb); - PyDict_SetItemString(_dict, "user_data", _ud); - - pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p [%p], %p [%p], %p, %p)", __FUNCTION__, self, native_ptr, _chat_message, _chat_message_native_ptr, _cb, _ud); - linphone_chat_room_send_message2(native_ptr, _chat_message_native_ptr, pylinphone_ChatRoom_callback_chat_message_state_changed, _dict); - pylinphone_dispatch_messages(); - - pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> None", __FUNCTION__); - Py_RETURN_NONE; -} - - static void pylinphone_VideoSize_dealloc(PyObject *self) { pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p)", __FUNCTION__, self); @@ -647,6 +587,91 @@ static PyMethodDef pylinphone_PayloadTypeType_ModuleMethods[] = { }; +static PyObject * pylinphone_Buffer_class_method_new_from_data(PyObject *cls, PyObject *args) { + LinphoneBuffer * cresult; + pylinphone_BufferObject *self; + PyObject * pyret; + PyObject * _byte_array; + + if (!PyArg_ParseTuple(args, "O", &_byte_array)) { + return NULL; + } + if (!PyByteArray_Check(_byte_array)) { + PyErr_SetString(PyExc_TypeError, "The argument must be a ByteArray"); + return NULL; + } + + self = (pylinphone_BufferObject *)PyObject_CallObject((PyObject *) &pylinphone_BufferType, NULL); + if (self == NULL) { + return NULL; + } + + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p)", __FUNCTION__, _byte_array); + cresult = linphone_buffer_new_from_data((uint8_t *)PyByteArray_AsString(_byte_array), PyByteArray_Size(_byte_array)); + self->native_ptr = cresult; + + pyret = Py_BuildValue("O", self); + + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> %p", __FUNCTION__, pyret); + Py_DECREF(self); + return pyret; +} + +static PyObject * pylinphone_Buffer_get_content(PyObject *self, void *closure) { + const uint8_t * ccontent; + size_t csize; + PyObject * pyresult; + PyObject * pyret; + const char *pyret_fmt; + const LinphoneBuffer *native_ptr; + native_ptr = pylinphone_Buffer_get_native_ptr(self); + if (native_ptr == NULL) { + PyErr_SetString(PyExc_TypeError, "Invalid linphone.Buffer instance"); + return NULL; + } + + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p [%p])", __FUNCTION__, self, native_ptr); + ccontent = linphone_buffer_get_content(native_ptr); + csize = linphone_buffer_get_size(native_ptr); + pylinphone_dispatch_messages(); + + pyresult = PyByteArray_FromStringAndSize((const char *)ccontent, csize); + pyret = Py_BuildValue("O", pyresult); + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> %p", __FUNCTION__, pyret); + return pyret; +} + +static int pylinphone_Buffer_set_content(PyObject *self, PyObject *value, void *closure) { + LinphoneBuffer *native_ptr; + uint8_t * _content; + size_t _size; + LinphonePresenceModel * _presence_native_ptr = NULL; + native_ptr = pylinphone_Buffer_get_native_ptr(self); + if (native_ptr == NULL) { + PyErr_SetString(PyExc_TypeError, "Invalid linphone.Buffer instance"); + return -1; + } + + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete the 'content' attribute."); + return -1; + } + if ((value != Py_None) && !PyByteArray_Check(value)) { + PyErr_SetString(PyExc_TypeError, "The 'content' attribute value must be a ByteArray instance."); + return -1; + } + + _content = (uint8_t *)PyByteArray_AsString(value); + _size = PyByteArray_Size(value); + + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p [%p], %p [%p])", __FUNCTION__, self, native_ptr, value, _content); + linphone_buffer_set_content(native_ptr, _content, _size); + pylinphone_dispatch_messages(); + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> 0", __FUNCTION__); + return 0; +} + + static PyObject * pylinphone_Content_get_buffer(PyObject *self, void *closure) { void * cbuffer; size_t csize; diff --git a/tools/python/unittests/test_message.py b/tools/python/unittests/test_message.py index a17b8f47e..d4b83971e 100644 --- a/tools/python/unittests/test_message.py +++ b/tools/python/unittests/test_message.py @@ -1,5 +1,6 @@ from nose.tools import assert_equals from copy import deepcopy +import filecmp import linphone from linphonetester import * import os @@ -9,11 +10,6 @@ import time class TestMessage: - @classmethod - def teardown_class(cls): - if os.path.exists('receive_file.dump'): - os.remove('receive_file.dump') - @classmethod def msg_state_changed(cls, msg, state): stats = msg.chat_room.core.user_data.stats @@ -29,24 +25,63 @@ class TestMessage: else: linphonetester_logger.error("[TESTER] Unexpected state [{state}] for message [{msg}]".format(msg=msg, state=linphone.ChatMessageState.string(state))) + @classmethod + def file_transfer_progress_indication(cls, msg, content, offset, total): + stats = msg.chat_room.core.user_data.stats + progress = int((offset * 100) / total) + direction = 'received' + tofrom = 'from' + address = msg.from_address + if msg.outgoing: + direction = 'sent' + tofrom = 'to' + address = msg.to_address + linphonetester_logger.info("[TESTER] File transfer [{progress}%] {direction} of type [{type}/{subtype}] {tofrom} {address}".format( + progress=progress, direction=direction, type=content.type, subtype=content.subtype, tofrom=tofrom, address=address.as_string())); + stats.progress_of_LinphoneFileTransfer = progress + @classmethod def file_transfer_send(cls, msg, content, offset, size): - if offset >= len(msg.user_data): + send_filepath = msg.user_data + send_filesize = os.path.getsize(send_filepath) + if offset >= send_filesize: return linphone.Buffer.new() # end of file - return linphone.Buffer.new_from_string(msg.user_data[offset:offset+size]) + f = open(send_filepath, 'rb') + f.seek(offset, 0) + if (send_filesize - offset) < size: + size = send_filesize - offset + lb = linphone.Buffer.new_from_data(bytearray(f.read(size))) + f.close() + return lb @classmethod def file_transfer_recv(cls, msg, content, buf): + receive_filepath = msg.user_data stats = msg.chat_room.core.user_data.stats - if msg.user_data is None: - msg.user_data = open('receive_file.dump', 'wb') - msg.user_data.write(buf.string_content) - else: - if buf.size == 0: # Transfer complete - stats.number_of_LinphoneMessageExtBodyReceived += 1 - msg.user_data.close() - else: # Store content - msg.user_data.write(buf.string_content) + if buf.empty: # Transfer complete + stats.number_of_LinphoneMessageExtBodyReceived += 1 + else: # Store content + f = open(receive_filepath, 'ab') + f.write(buf.content) + f.close() + + @classmethod + def memory_file_transfer_send(cls, msg, content, offset, size): + send_buf = msg.user_data + send_size = len(send_buf) + if offset >= send_size: + return linphone.Buffer.new() + if (send_size - offset) < size: + size = send_size - offset + return linphone.Buffer.new_from_string(send_buf[offset:offset+size]) + + @classmethod + def memory_file_transfer_recv(cls, msg, content, buf): + stats = msg.chat_room.core.user_data.stats + if buf.empty: # Transfer complete + stats.number_of_LinphoneMessageExtBodyReceived += 1 + else: # Store content + msg.user_data += buf.string_content def wait_for_server_to_purge_messages(self, manager1, manager2): # Wait a little bit just to have time to purge message stored in the server @@ -81,24 +116,19 @@ class TestMessage: pauline.stop() def test_file_transfer_message(self): - big_file = "big file" marie = CoreManager('marie_rc') pauline = CoreManager('pauline_rc') - while len(big_file) < 128000: - big_file += big_file - l = list(big_file) - l[0] = 'S' - l[-1] = 'E' - big_file = ''.join(l) + send_filepath = os.path.join(tester_resources_path, 'images', 'nowebcamCIF.jpg') + receive_filepath = 'receive_file.dump' pauline.lc.file_transfer_server = "https://www.linphone.org:444/lft.php" chat_room = pauline.lc.get_chat_room(marie.identity) content = pauline.lc.create_content() - content.type = 'text' - content.subtype = 'plain' - content.size = len(big_file) # total size to be transfered - content.name = 'bigfile.txt' + content.type = 'image' + content.subtype = 'jpeg' + content.size = os.path.getsize(send_filepath) # total size to be transfered + content.name = 'nowebcamCIF.jpg' message = chat_room.create_file_transfer_message(content) - message.user_data = big_file # Store the file in the user data of the chat message + message.user_data = send_filepath self.wait_for_server_to_purge_messages(marie, pauline) message.callbacks.msg_state_changed = TestMessage.msg_state_changed message.callbacks.file_transfer_send = TestMessage.file_transfer_send @@ -108,8 +138,79 @@ class TestMessage: cbs = marie.stats.last_received_chat_message.callbacks cbs.msg_state_changed = TestMessage.msg_state_changed cbs.file_transfer_recv = TestMessage.file_transfer_recv + marie.stats.last_received_chat_message.user_data = receive_filepath marie.stats.last_received_chat_message.download_file() assert_equals(CoreManager.wait_for(pauline, marie, lambda pauline, marie: marie.stats.number_of_LinphoneMessageExtBodyReceived == 1), True) assert_equals(pauline.stats.number_of_LinphoneMessageInProgress, 1) assert_equals(pauline.stats.number_of_LinphoneMessageDelivered, 1) assert_equals(marie.stats.number_of_LinphoneMessageExtBodyReceived, 1) + assert_equals(filecmp.cmp(send_filepath, receive_filepath, shallow=False), True) + if os.path.exists(receive_filepath): + os.remove(receive_filepath) + + def test_small_file_transfer_message(self): + send_buf = "small file" + marie = CoreManager('marie_rc') + pauline = CoreManager('pauline_rc') + while len(send_buf) < 160: + send_buf += send_buf + l = list(send_buf[0:160]) + l[0] = 'S' + l[-1] = 'E' + send_buf = ''.join(l) + pauline.lc.file_transfer_server = "https://www.linphone.org:444/lft.php" + chat_room = pauline.lc.get_chat_room(marie.identity) + content = pauline.lc.create_content() + content.type = 'text' + content.subtype = 'plain' + content.size = len(send_buf) # total size to be transfered + content.name = 'small_file.txt' + message = chat_room.create_file_transfer_message(content) + message.user_data = send_buf + self.wait_for_server_to_purge_messages(marie, pauline) + message.callbacks.msg_state_changed = TestMessage.msg_state_changed + message.callbacks.file_transfer_send = TestMessage.memory_file_transfer_send + chat_room.send_chat_message(message) + assert_equals(CoreManager.wait_for(pauline, marie, lambda pauline, marie: marie.stats.number_of_LinphoneMessageReceivedWithFile == 1), True) + if marie.stats.last_received_chat_message is not None: + cbs = marie.stats.last_received_chat_message.callbacks + cbs.msg_state_changed = TestMessage.msg_state_changed + cbs.file_transfer_recv = TestMessage.memory_file_transfer_recv + marie.stats.last_received_chat_message.user_data = '' + marie.stats.last_received_chat_message.download_file() + assert_equals(CoreManager.wait_for(pauline, marie, lambda pauline, marie: marie.stats.number_of_LinphoneMessageExtBodyReceived == 1), True) + assert_equals(pauline.stats.number_of_LinphoneMessageInProgress, 1) + assert_equals(pauline.stats.number_of_LinphoneMessageDelivered, 1) + assert_equals(marie.stats.number_of_LinphoneMessageExtBodyReceived, 1) + assert_equals(send_buf, marie.stats.last_received_chat_message.user_data) + + def test_file_transfer_message_upload_cancelled(self): + send_buf = "big file" + marie = CoreManager('marie_rc') + pauline = CoreManager('pauline_rc') + while len(send_buf) < 128000: + send_buf += send_buf + l = list(send_buf[0:128000]) + l[0] = 'S' + l[-1] = 'E' + send_buf = ''.join(l) + pauline.lc.file_transfer_server = "https://www.linphone.org:444/lft.php" + chat_room = pauline.lc.get_chat_room(marie.identity) + content = pauline.lc.create_content() + content.type = 'text' + content.subtype = 'plain' + content.size = len(send_buf) # total size to be transfered + content.name = 'big_file.txt' + message = chat_room.create_file_transfer_message(content) + message.user_data = send_buf + self.wait_for_server_to_purge_messages(marie, pauline) + message.callbacks.msg_state_changed = TestMessage.msg_state_changed + message.callbacks.file_transfer_send = TestMessage.memory_file_transfer_send + message.callbacks.file_transfer_progress_indication = TestMessage.file_transfer_progress_indication + chat_room.send_chat_message(message) + # Wait for file to be at least 50% uploaded and cancel the transfer + assert_equals(CoreManager.wait_for(pauline, marie, lambda pauline, marie: pauline.stats.progress_of_LinphoneFileTransfer >= 50), True) + message.cancel_file_transfer() + assert_equals(CoreManager.wait_for(pauline, marie, lambda pauline, marie: pauline.stats.number_of_LinphoneMessageNotDelivered == 1), True) + assert_equals(pauline.stats.number_of_LinphoneMessageNotDelivered, 1) + assert_equals(marie.stats.number_of_LinphoneMessageExtBodyReceived, 0)