File transfer with binary content working in Python.

This commit is contained in:
Ghislain MARY 2014-12-11 15:59:29 +01:00
parent 8b33ed404e
commit 340d927c3c
4 changed files with 219 additions and 92 deletions

View file

@ -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),

View file

@ -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);

View file

@ -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;

View file

@ -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)