mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-31 10:19:23 +00:00
File transfer with binary content working in Python.
This commit is contained in:
parent
8b33ed404e
commit
340d927c3c
4 changed files with 219 additions and 92 deletions
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue