From 2472742b6f2b2c7038cfc3a2582da04531afb7fa Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 2 Jul 2014 15:05:26 +0200 Subject: [PATCH 001/218] Add tool to generate an XML file describing the Linphone API. --- tools/genapixml.py | 673 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 673 insertions(+) create mode 100755 tools/genapixml.py diff --git a/tools/genapixml.py b/tools/genapixml.py new file mode 100755 index 000000000..32f1d0043 --- /dev/null +++ b/tools/genapixml.py @@ -0,0 +1,673 @@ +#!/usr/bin/python + +import argparse +import string +import sys +import xml.etree.ElementTree as ET +import xml.dom.minidom as minidom + + +class CObject: + def __init__(self, name): + self.name = name.strip() + self.briefDescription = '' + self.detailedDescription = None + self.deprecated = False + + +class CEnumValue(CObject): + pass + + +class CEnum(CObject): + def __init__(self, name): + CObject.__init__(self, name) + self.values = [] + self.associatedTypedef = None + + def addValue(self, value): + self.values.append(value) + + +class CStructMember(CObject): + def __init__(self, name, t): + CObject.__init__(self, name) + self.ctype = t.strip() + + +class CStruct(CObject): + def __init__(self, name): + CObject.__init__(self, name) + self.members = [] + self.associatedTypedef = None + + def addMember(self, member): + self.members.append(member) + + +class CTypedef(CObject): + def __init__(self, name, definition): + CObject.__init__(self, name) + self.definition = definition.strip() + + +class CArgument(CObject): + def __init__(self, t, name = '', enums = [], structs = []): + CObject.__init__(self, name) + self.description = None + keywords = [ 'const', 'struct', 'enum', 'signed', 'unsigned', '*' ] + fullySplittedType = [] + splittedType = t.strip().split(' ') + for s in splittedType: + if s.startswith('*'): + fullySplittedType.append('*') + if len(s) > 1: + fullySplittedType.append(s[1:]) + elif s.endswith('*'): + fullySplittedType.append(s[:-1]) + fullySplittedType.append('*') + else: + fullySplittedType.append(s) + self.completeType = ' '.join(fullySplittedType) + isStruct = False + isEnum = False + for s in fullySplittedType: + if not s in keywords: + self.ctype = s + if s == 'struct': + isStruct = True + if s == 'enum': + isEnum = True + if isStruct: + for st in structs: + if st.associatedTypedef is not None: + self.ctype = st.associatedTypedef.name + elif isEnum: + for e in enums: + if e.associatedTypedef is not None: + self.ctype = e.associatedTypedef.name + + def __str__(self): + return self.completeType + " " + self.name + + +class CArgumentsList: + def __init__(self): + self.arguments = [] + + def addArgument(self, arg): + self.arguments.append(arg) + + def __len__(self): + return len(self.arguments) + + def __getitem__(self, key): + return self.arguments[key] + + def __str__(self): + argstr = [] + for arg in self.arguments: + argstr.append(str(arg)) + return ', '.join(argstr) + + +class CFunction(CObject): + def __init__(self, name, returnarg, argslist): + CObject.__init__(self, name) + self.returnArgument = returnarg + self.arguments = argslist + self.location = None + + +class CEvent(CFunction): + pass + + +class CProperty: + def __init__(self, name): + self.name = name + self.getter = None + self.setter = None + + +class CClass(CObject): + def __init__(self, st): + CObject.__init__(self, st.associatedTypedef.name) + if st.deprecated or st.associatedTypedef.deprecated: + self.deprecated = True + if len(st.associatedTypedef.briefDescription) > 0: + self.briefDescription = st.associatedTypedef.briefDescription + elif len(st.briefDescription) > 0: + self.briefDescription = st.briefDescription + if st.associatedTypedef.detailedDescription is not None: + self.detailedDescription = st.associatedTypedef.detailedDescription + elif st.detailedDescription is not None: + self.detailedDescription = st.detailedDescription + self.__struct = st + self.events = {} + self.classMethods = {} + self.instanceMethods = {} + self.properties = {} + self.__computeCFunctionPrefix() + + def __computeCFunctionPrefix(self): + self.cFunctionPrefix = '' + first = True + for l in self.name: + if l.isupper() and not first: + self.cFunctionPrefix += '_' + self.cFunctionPrefix += l.lower() + first = False + self.cFunctionPrefix += '_' + + def __addPropertyGetter(self, name, f): + if not name in self.properties: + prop = CProperty(name) + self.properties[name] = prop + self.properties[name].getter = f + + def __addPropertySetter(self, name, f): + if not name in self.properties: + prop = CProperty(name) + self.properties[name] = prop + self.properties[name].setter = f + + def __addClassMethod(self, f): + name = f.name[len(self.cFunctionPrefix):] + if string.find(name, 'get_') == 0 and len(f.arguments) == 0: + self.__addPropertyGetter(name[4:], f) + elif string.find(name, 'is_') == 0 and len(f.arguments) == 0 and f.returnArgument.ctype == 'bool_t': + self.__addPropertyGetter(name[3:], f) + elif string.rfind(name, '_enabled') == (len(name) - 8) and len(f.arguments) == 0 and f.returnArgument.ctype == 'bool_t': + self.__addPropertyGetter(name, f) + elif string.find(name, 'set_') == 0 and len(f.arguments) == 1: + self.__addPropertySetter(name[4:], f) + elif string.find(name, 'enable_') == 0 and len(f.arguments) == 1 and f.arguments[0].ctype == 'bool_t': + self.__addPropertySetter(name[7:] + '_enabled', f) + else: + if not f.name in self.classMethods: + self.classMethods[f.name] = f + + def __addInstanceMethod(self, f): + name = f.name[len(self.cFunctionPrefix):] + if string.find(name, 'get_') == 0 and len(f.arguments) == 1: + self.__addPropertyGetter(name[4:], f) + elif string.find(name, 'is_') == 0 and len(f.arguments) == 1 and f.returnArgument.ctype == 'bool_t': + self.__addPropertyGetter(name[3:], f) + elif string.rfind(name, '_enabled') == (len(name) - 8) and len(f.arguments) == 1 and f.returnArgument.ctype == 'bool_t': + self.__addPropertyGetter(name, f) + elif string.find(name, 'set_') == 0 and len(f.arguments) == 2: + self.__addPropertySetter(name[4:], f) + elif string.find(name, 'enable_') == 0 and len(f.arguments) == 2 and f.arguments[1].ctype == 'bool_t': + self.__addPropertySetter(name[7:] + '_enabled', f) + else: + if not f.name in self.instanceMethods: + self.instanceMethods[f.name] = f + + def addEvent(self, ev): + if not ev.name in self.events: + self.events[ev.name] = ev + + def addMethod(self, f): + if len(f.arguments) > 0 and f.arguments[0].ctype == self.name: + self.__addInstanceMethod(f) + else: + self.__addClassMethod(f) + + +class Project: + def __init__(self): + self.verbose = False + self.prettyPrint = False + self.enums = [] + self.__structs = [] + self.__typedefs = [] + self.__events = [] + self.__functions = [] + self.classes = [] + + def add(self, elem): + if isinstance(elem, CClass): + if self.verbose: + print("Adding class " + elem.name) + self.classes.append(elem) + elif isinstance(elem, CEnum): + if self.verbose: + print("Adding enum " + elem.name) + for ev in elem.values: + print("\t" + ev.name) + self.enums.append(elem) + elif isinstance(elem, CStruct): + if self.verbose: + print("Adding struct " + elem.name) + for sm in elem.members: + print("\t" + sm.ctype + " " + sm.name) + self.__structs.append(elem) + elif isinstance(elem, CTypedef): + if self.verbose: + print("Adding typedef " + elem.name) + print("\t" + elem.definition) + self.__typedefs.append(elem) + elif isinstance(elem, CEvent): + if self.verbose: + print("Adding event " + elem.name) + print("\tReturns: " + elem.returnArgument.ctype) + print("\tArguments: " + str(elem.arguments)) + self.__events.append(elem) + elif isinstance(elem, CFunction): + if self.verbose: + print("Adding function " + elem.name) + print("\tReturns: " + elem.returnArgument.ctype) + print("\tArguments: " + str(elem.arguments)) + self.__functions.append(elem) + + def __cleanDescription(self, descriptionNode): + for para in descriptionNode.findall('./para'): + for n in para.findall('./parameterlist'): + para.remove(n) + for n in para.findall("./simplesect[@kind='return']"): + para.remove(n) + for n in para.findall("./simplesect[@kind='see']"): + t = ''.join(n.itertext()) + n.clear() + n.tag = 'see' + n.text = t + for n in para.findall("./simplesect[@kind='note']"): + n.tag = 'note' + n.attrib = {} + for n in para.findall(".//xrefsect"): + para.remove(n) + for n in para.findall('.//ref'): + n.attrib = {} + if descriptionNode.tag == 'parameterdescription': + descriptionNode.tag = 'description' + if descriptionNode.tag == 'simplesect': + descriptionNode.tag = 'description' + descriptionNode.attrib = {} + return descriptionNode + + def __discoverClasses(self): + for td in self.__typedefs: + if string.find(td.definition, 'enum ') == 0: + for e in self.enums: + if (e.associatedTypedef is None) and td.definition[5:] == e.name: + e.associatedTypedef = td + break + elif string.find(td.definition, 'struct ') == 0: + structFound = False + for st in self.__structs: + if (st.associatedTypedef is None) and td.definition[7:] == st.name: + st.associatedTypedef = td + structFound = True + break + if not structFound: + name = td.definition[7:] + print("Structure with no associated typedef: " + name) + st = CStruct(name) + st.associatedTypedef = td + self.add(st) + for td in self.__typedefs: + if string.find(td.definition, 'struct ') == 0: + for st in self.__structs: + if st.associatedTypedef == td: + self.add(CClass(st)) + break + # Sort classes by length of name (longest first), so that methods are put in the right class + self.classes.sort(key = lambda c: len(c.name), reverse = True) + for e in self.__events: + for c in self.classes: + if string.find(e.name, c.name) == 0: + c.addEvent(e) + for f in self.__functions: + for c in self.classes: + if c.cFunctionPrefix == f.name[0 : len(c.cFunctionPrefix)]: + c.addMethod(f) + break + + def __parseCEnumValue(self, node): + ev = CEnumValue(node.find('./name').text) + deprecatedNode = node.find(".//xrefsect[xreftitle='Deprecated']") + if deprecatedNode is not None: + ev.deprecated = True + ev.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() + ev.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) + return ev + + def __parseCEnumMemberdef(self, node): + e = CEnum(node.find('./name').text) + deprecatedNode = node.find(".//xrefsect[xreftitle='Deprecated']") + if deprecatedNode is not None: + e.deprecated = True + e.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() + e.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) + enumvalues = node.findall("enumvalue[@prot='public']") + for enumvalue in enumvalues: + ev = self.__parseCEnumValue(enumvalue) + e.addValue(ev) + return e + + def __findCEnum(self, tree): + memberdefs = tree.findall("./compounddef[@kind='group']/sectiondef[@kind='enum']/memberdef[@kind='enum'][@prot='public']") + for m in memberdefs: + e = self.__parseCEnumMemberdef(m) + self.add(e) + + def __parseCStructMember(self, node, structname): + name = node.find('./name').text + definition = node.find('./definition').text + t = definition[0:string.find(definition, structname + "::" + name)] + sm = CStructMember(name, t) + deprecatedNode = node.find(".//xrefsect[xreftitle='Deprecated']") + if deprecatedNode is not None: + sm.deprecated = True + sm.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() + sm.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) + return sm + + def __parseCStructCompounddef(self, node): + s = CStruct(node.find('./compoundname').text) + deprecatedNode = node.find(".//xrefsect[xreftitle='Deprecated']") + if deprecatedNode is not None: + s.deprecated = True + s.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() + s.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) + structmembers = node.findall("sectiondef/memberdef[@kind='variable'][@prot='public']") + for structmember in structmembers: + sm = self.__parseCStructMember(structmember, s.name) + s.addMember(sm) + return s + + def __findCStruct(self, tree): + compounddefs = tree.findall("./compounddef[@kind='struct'][@prot='public']") + for c in compounddefs: + s = self.__parseCStructCompounddef(c) + self.add(s) + + def __parseCTypedefMemberdef(self, node): + name = node.find('./name').text + definition = node.find('./definition').text + if string.find(definition, 'typedef ') == 0: + definition = definition[8 :] + if string.rfind(name, 'Cb') == len(name) - 2: + pos = string.find(definition, "(*") + if pos == -1: + return None + returntype = definition[0:pos].strip() + if returntype != "void": + return None + returnarg = CArgument(returntype, enums = self.enums, structs = self.__structs) + definition = definition[pos + 2 :] + pos = string.find(definition, "(") + definition = definition[pos + 1 : -1] + argslist = CArgumentsList() + for argdef in definition.split(', '): + argType = '' + starPos = string.rfind(argdef, '*') + spacePos = string.rfind(argdef, ' ') + if starPos != -1: + argType = argdef[0 : starPos + 1] + argName = argdef[starPos + 1 :] + elif spacePos != -1: + argType = argdef[0 : spacePos] + argName = argdef[spacePos + 1 :] + argslist.addArgument(CArgument(argType, argName, self.enums, self.__structs)) + if len(argslist) > 0: + paramdescs = node.findall("detaileddescription/para/parameterlist[@kind='param']/parameteritem") + if paramdescs: + for arg in argslist.arguments: + for paramdesc in paramdescs: + if arg.name == paramdesc.find('./parameternamelist').find('./parametername').text: + arg.description = self.__cleanDescription(paramdesc.find('./parameterdescription')) + missingDocWarning = '' + for arg in argslist.arguments: + if arg.description == None: + missingDocWarning += "\t'" + arg.name + "' parameter not documented\n"; + if missingDocWarning != '': + print(name + ":\n" + missingDocWarning) + f = CEvent(name, returnarg, argslist) + deprecatedNode = node.find(".//xrefsect[xreftitle='Deprecated']") + if deprecatedNode is not None: + f.deprecated = True + f.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() + f.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) + return f + else: + pos = string.rfind(definition, " " + name) + if pos != -1: + definition = definition[0 : pos] + td = CTypedef(name, definition) + deprecatedNode = node.find(".//xrefsect[xreftitle='Deprecated']") + if deprecatedNode is not None: + td.deprecated = True + td.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() + td.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) + return td + return None + + def __findCTypedef(self, tree): + memberdefs = tree.findall("./compounddef[@kind='group']/sectiondef[@kind='typedef']/memberdef[@kind='typedef'][@prot='public']") + for m in memberdefs: + td = self.__parseCTypedefMemberdef(m) + self.add(td) + + def __parseCFunctionMemberdef(self, node): + missingDocWarning = '' + name = node.find('./name').text + t = ''.join(node.find('./type').itertext()) + returnarg = CArgument(t, enums = self.enums, structs = self.__structs) + returndesc = node.find("./detaileddescription/para/simplesect[@kind='return']") + if returndesc is not None: + returnarg.description = self.__cleanDescription(returndesc) + elif returnarg.completeType != 'void': + missingDocWarning += "\tReturn value is not documented\n" + argslist = CArgumentsList() + argslistNode = node.findall('./param') + for argNode in argslistNode: + argType = ''.join(argNode.find('./type').itertext()) + argName = '' + argNameNode = argNode.find('./declname') + if argNameNode is not None: + argName = ''.join(argNameNode.itertext()) + if argType != 'void': + argslist.addArgument(CArgument(argType, argName, self.enums, self.__structs)) + if len(argslist) > 0: + paramdescs = node.findall("./detaileddescription/para/parameterlist[@kind='param']/parameteritem") + if paramdescs: + for arg in argslist.arguments: + for paramdesc in paramdescs: + if arg.name == paramdesc.find('./parameternamelist').find('./parametername').text: + arg.description = self.__cleanDescription(paramdesc.find('./parameterdescription')) + missingDocWarning = '' + for arg in argslist.arguments: + if arg.description == None: + missingDocWarning += "\t'" + arg.name + "' parameter not documented\n"; + f = CFunction(name, returnarg, argslist) + deprecatedNode = node.find(".//xrefsect[xreftitle='Deprecated']") + if deprecatedNode is not None: + f.deprecated = True + f.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() + f.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) + locationNode = node.find('./location') + if locationNode is not None: + f.location = locationNode.get('file') + if not f.location.endswith('.h'): + missingDocWarning += "\tNot documented in a header file ('" + f.location + "')\n"; + if missingDocWarning != '': + print(name + ":\n" + missingDocWarning) + return f + + def __findCFunction(self, tree): + memberdefs = tree.findall("./compounddef[@kind='group']/sectiondef[@kind='func']/memberdef[@kind='function'][@prot='public'][@static='no']") + for m in memberdefs: + f = self.__parseCFunctionMemberdef(m) + self.add(f) + + def initFromFiles(self, xmlfiles): + trees = [] + for f in xmlfiles: + tree = None + try: + if self.verbose: + print("Parsing XML file: " + f.name) + tree = ET.parse(f) + except ET.ParseError as e: + print(e) + if tree is not None: + trees.append(tree) + for tree in trees: + self.__findCEnum(tree) + for tree in trees: + self.__findCStruct(tree) + for tree in trees: + self.__findCTypedef(tree) + for tree in trees: + self.__findCFunction(tree) + self.__discoverClasses() + + def check(self): + for c in self.classes: + for name, p in c.properties.iteritems(): + if p.getter is None and p.setter is not None: + print("Property '" + name + "' of class '" + c.name + "' has a setter but no getter") + + +class Generator: + def __init__(self, outputfile): + self.__outputfile = outputfile + + def __generateEnum(self, cenum, enumsNode): + enumNodeAttributes = { 'name' : cenum.name } + if cenum.associatedTypedef is not None: + enumNodeAttributes['name'] = cenum.associatedTypedef.name + if cenum.deprecated: + enumNodeAttributes['deprecated'] = 'true' + enumNode = ET.SubElement(enumsNode, 'enum', enumNodeAttributes) + if cenum.briefDescription != '': + enumBriefDescriptionNode = ET.SubElement(enumNode, 'briefdescription') + enumBriefDescriptionNode.text = cenum.briefDescription + enumNode.append(cenum.detailedDescription) + if len(cenum.values) > 0: + enumValuesNode = ET.SubElement(enumNode, 'values') + for value in cenum.values: + enumValuesNodeAttributes = { 'name' : value.name } + if value.deprecated: + enumValuesNodeAttributes['deprecated'] = 'true' + valueNode = ET.SubElement(enumValuesNode, 'value', enumValuesNodeAttributes) + if value.briefDescription != '': + valueBriefDescriptionNode = ET.SubElement(valueNode, 'briefdescription') + valueBriefDescriptionNode.text = value.briefDescription + valueNode.append(value.detailedDescription) + + def __generateFunction(self, parentNode, nodeName, f): + functionAttributes = { 'name' : f.name } + if f.location is not None: + functionAttributes['location'] = f.location + if f.deprecated: + functionAttributes['deprecated'] = 'true' + functionNode = ET.SubElement(parentNode, nodeName, functionAttributes) + returnValueAttributes = { 'type' : f.returnArgument.completeType } + returnValueNode = ET.SubElement(functionNode, 'return', returnValueAttributes) + if f.returnArgument.description is not None: + returnValueNode.append(f.returnArgument.description) + argumentsNode = ET.SubElement(functionNode, 'arguments') + for arg in f.arguments: + argumentNodeAttributes = { 'name' : arg.name, 'type' : arg.completeType } + argumentNode = ET.SubElement(argumentsNode, 'argument', argumentNodeAttributes) + if arg.description is not None: + argumentNode.append(arg.description) + if f.briefDescription != '': + functionBriefDescriptionNode = ET.SubElement(functionNode, 'briefdescription') + functionBriefDescriptionNode.text = f.briefDescription + functionNode.append(f.detailedDescription) + + def __generateClass(self, cclass, classesNode): + classNodeAttributes = { 'name' : cclass.name, 'cfunctionprefix' : cclass.cFunctionPrefix } + if cclass.deprecated: + classNodeAttributes['deprecated'] = 'true' + classNode = ET.SubElement(classesNode, 'class', classNodeAttributes) + if len(cclass.events) > 0: + eventsNode = ET.SubElement(classNode, 'events') + eventnames = [] + for eventname in cclass.events: + eventnames.append(eventname) + eventnames.sort() + for eventname in eventnames: + self.__generateFunction(eventsNode, 'event', cclass.events[eventname]) + if len(cclass.classMethods) > 0: + classMethodsNode = ET.SubElement(classNode, 'classmethods') + methodnames = [] + for methodname in cclass.classMethods: + methodnames.append(methodname) + methodnames.sort() + for methodname in methodnames: + self.__generateFunction(classMethodsNode, 'classmethod', cclass.classMethods[methodname]) + if len(cclass.instanceMethods) > 0: + instanceMethodsNode = ET.SubElement(classNode, 'instancemethods') + methodnames = [] + for methodname in cclass.instanceMethods: + methodnames.append(methodname) + methodnames.sort() + for methodname in methodnames: + self.__generateFunction(instanceMethodsNode, 'instancemethod', cclass.instanceMethods[methodname]) + if len(cclass.properties) > 0: + propertiesNode = ET.SubElement(classNode, 'properties') + propnames = [] + for propname in cclass.properties: + propnames.append(propname) + propnames.sort() + for propname in propnames: + propertyNodeAttributes = { 'name' : propname } + propertyNode = ET.SubElement(propertiesNode, 'property', propertyNodeAttributes) + if cclass.properties[propname].getter is not None: + self.__generateFunction(propertyNode, 'getter', cclass.properties[propname].getter) + if cclass.properties[propname].setter is not None: + self.__generateFunction(propertyNode, 'setter', cclass.properties[propname].setter) + if cclass.briefDescription != '': + classBriefDescriptionNode = ET.SubElement(classNode, 'briefdescription') + classBriefDescriptionNode.text = cclass.briefDescription + classNode.append(cclass.detailedDescription) + + def generate(self, project): + print("Generating XML document of Linphone API to '" + self.__outputfile.name + "'") + apiNode = ET.Element('api') + project.enums.sort(key = lambda e: e.name) + if len(project.enums) > 0: + enumsNode = ET.SubElement(apiNode, 'enums') + for cenum in project.enums: + self.__generateEnum(cenum, enumsNode) + if len(project.classes) > 0: + classesNode = ET.SubElement(apiNode, 'classes') + project.classes.sort(key = lambda c: c.name) + for cclass in project.classes: + self.__generateClass(cclass, classesNode) + s = '\n' + s += ET.tostring(apiNode, 'utf-8') + if project.prettyPrint: + s = minidom.parseString(s).toprettyxml(indent='\t') + self.__outputfile.write(s) + + + +def main(argv = None): + if argv is None: + argv = sys.argv + argparser = argparse.ArgumentParser(description="Generate XML version of the Linphone API.") + argparser.add_argument('-o', '--outputfile', metavar='outputfile', type=argparse.FileType('w'), help="Output XML file describing the Linphone API.") + argparser.add_argument('--verbose', help="Increase output verbosity", action='store_true') + argparser.add_argument('--pretty', help="XML pretty print", action='store_true') + argparser.add_argument('xmlfile', metavar='xmlfile', type=argparse.FileType('r'), nargs='+', help="XML file generated by doxygen.") + args = argparser.parse_args() + if args.outputfile == None: + args.outputfile = open('api.xml', 'w') + project = Project() + if args.verbose: + project.verbose = True + if args.pretty: + project.prettyPrint = True + project.initFromFiles(args.xmlfile) + project.check() + gen = Generator(args.outputfile) + gen.generate(project) + +if __name__ == "__main__": + sys.exit(main()) From f0106e285caa1e0a0bccfde559b027723031d398 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Sun, 6 Jul 2014 23:43:09 +0200 Subject: [PATCH 002/218] update ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 6d75b63ac..b57c4697a 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 6d75b63ac302fbcabe9aab003d6c1174441982a1 +Subproject commit b57c4697a35b83267fe96b20734ea70fe3aa8896 From 76c467a163532bc11782b28e332c504922a8dde8 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 2 Jul 2014 16:49:05 +0200 Subject: [PATCH 003/218] Begin generation of python module code. --- tools/python/apixml2python.py | 30 ++++ tools/python/apixml2python/__init__.py | 0 tools/python/apixml2python/linphone.py | 88 ++++++++++ .../apixml2python/linphone_module.mustache | 161 ++++++++++++++++++ tools/python/setup.py | 9 + 5 files changed, 288 insertions(+) create mode 100755 tools/python/apixml2python.py create mode 100644 tools/python/apixml2python/__init__.py create mode 100644 tools/python/apixml2python/linphone.py create mode 100644 tools/python/apixml2python/linphone_module.mustache create mode 100644 tools/python/setup.py diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py new file mode 100755 index 000000000..63f7ed8db --- /dev/null +++ b/tools/python/apixml2python.py @@ -0,0 +1,30 @@ +#!/usr/bin/python + +import argparse +import os +import pystache +import sys +import xml.etree.ElementTree as ET + +sys.path.append(os.path.realpath(__file__)) +from apixml2python.linphone import LinphoneModule + + +def generate(apixmlfile): + tree = ET.parse(apixmlfile) + renderer = pystache.Renderer() + m = LinphoneModule(tree) + f = open("linphone.c", "w") + f.write(renderer.render(m)) + + +def main(argv = None): + if argv is None: + argv = sys.argv + argparser = argparse.ArgumentParser(description="Generate a Python wrapper of the Linphone API.") + argparser.add_argument('apixmlfile', help="XML file of the Linphone API generated by genapixml.py.") + args = argparser.parse_args() + generate(args.apixmlfile) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/python/apixml2python/__init__.py b/tools/python/apixml2python/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py new file mode 100644 index 000000000..f58d923f8 --- /dev/null +++ b/tools/python/apixml2python/linphone.py @@ -0,0 +1,88 @@ +class LinphoneModule(object): + def __init__(self, tree): + self.enums = [] + xml_enums = tree.findall("./enums/enum") + for xml_enum in xml_enums: + e = {} + e['enum_name'] = xml_enum.get('name') + e['enum_doc'] = self.__format_doc(xml_enum.find('briefdescription'), xml_enum.find('detaileddescription')) + e['enum_values'] = [] + xml_enum_values = xml_enum.findall("./values/value") + for xml_enum_value in xml_enum_values: + v = {} + v['enum_value_name'] = xml_enum_value.get('name') + e['enum_values'].append(v) + self.enums.append(e) + self.classes = [] + xml_classes = tree.findall("./classes/class") + for xml_class in xml_classes: + c = {} + c['class_name'] = xml_class.get('name') + c['class_c_function_prefix'] = xml_class.get('cfunctionprefix') + c['class_doc'] = self.__format_doc(xml_class.find('briefdescription'), xml_class.find('detaileddescription')) + c['class_type_methods'] = [] + xml_type_methods = xml_class.findall("./classmethods/classmethod") + for xml_type_method in xml_type_methods: + m = {} + m['method_name'] = xml_type_method.get('name').replace(c['class_c_function_prefix'], '') + c['class_type_methods'].append(m) + c['class_instance_methods'] = [] + xml_instance_methods = xml_class.findall("./instancemethods/instancemethod") + for xml_instance_method in xml_instance_methods: + m = {} + m['method_name'] = xml_instance_method.get('name').replace(c['class_c_function_prefix'], '') + c['class_instance_methods'].append(m) + c['class_properties'] = [] + xml_properties = xml_class.findall("./properties/property") + for xml_property in xml_properties: + p = {} + p['property_name'] = xml_property.get('name') + xml_property_getter = xml_property.find("./getter") + xml_property_setter = xml_property.find("./setter") + if xml_property_getter is not None: + p['getter_name'] = xml_property_getter.get('name').replace(c['class_c_function_prefix'], '') + if xml_property_setter is not None: + p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '') + c['class_properties'].append(p) + self.classes.append(c) + + def __format_node(self, node): + desc = '' + if node.tag == 'para': + desc += node.text.strip() + for n in list(node): + desc += self.__format_node(n) + elif node.tag == 'note': + desc += node.text.strip() + for n in list(node): + desc += self.__format_node(n) + elif node.tag == 'ref': + desc += ' ' + node.text.strip() + ' ' + tail = node.tail.strip() + if tail != '': + if node.tag != 'ref': + desc += '\n' + desc += tail + if node.tag == 'para': + desc += '\n' + return desc + + def __format_doc(self, brief_description, detailed_description): + doc = '' + if brief_description is None: + brief_description = '' + if detailed_description is None: + detailed_description = '' + else: + desc = '' + for node in list(detailed_description): + desc += self.__format_node(node) + '\n' + detailed_description = desc.strip().replace('\n', '\\n') + brief_description = brief_description.strip() + doc += brief_description + if detailed_description != '': + if doc != '': + doc += '\\n\\n' + doc+= detailed_description + doc = '\"' + doc + '\"' + return doc diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache new file mode 100644 index 000000000..52721287a --- /dev/null +++ b/tools/python/apixml2python/linphone_module.mustache @@ -0,0 +1,161 @@ +#include +#include + +{{#classes}} + +typedef struct { + PyObject_HEAD + {{class_name}} *native_ptr; +} pylinphone_{{class_name}}Object; + +static PyObject * pylinphone_{{class_name}}_new(PyTypeObject *type, PyObject *args, PyObject *kw) { + pylinphone_{{class_name}}Object *self = (pylinphone_{{class_name}}Object *)type->tp_alloc(type, 0); + return (PyObject *)self; +} + +static void pylinphone_{{class_name}}_dealloc(PyObject *self) { + self->ob_type->tp_free(self); +} + +{{#class_type_methods}} + +static PyObject * pylinphone_{{class_name}}_type_method_{{method_name}}(PyObject *self, PyObject *args) { + // TODO: Fill implementation + Py_RETURN_NONE; +} + +{{/class_type_methods}} + +static PyMethodDef pylinphone_{{class_name}}_type_methods[] = { + // TODO: Handle doc +{{#class_type_methods}} + { "{{method_name}}", pylinphone_{{class_name}}_type_method_{{method_name}}, METH_VARARGS, "" }, +{{/class_type_methods}} + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +{{#class_instance_methods}} + +static PyObject * pylinphone_{{class_name}}_instance_method_{{method_name}}(PyObject *self, PyObject *args) { + // TODO: Fill implementation + Py_RETURN_NONE; +} + +{{/class_instance_methods}} + +static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { + // TODO: Handle doc +{{#class_instance_methods}} + { "{{method_name}}", pylinphone_{{class_name}}_instance_method_{{method_name}}, METH_VARARGS, "" }, +{{/class_instance_methods}} + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +{{#class_properties}} + +static PyObject * pylinphone_{{class_name}}_{{getter_name}}(pylinphone_{{class_name}}Object *self, void *closure) { + // TODO: Fill implementation + Py_RETURN_NONE; +} + +static int pylinphone_{{class_name}}_{{setter_name}}(pylinphone_{{class_name}}Object *self, PyObject *value, void *closure) { + // TODO: Fill implementation + return 0; +} + +{{/class_properties}} + +static PyGetSetDef pylinphone_{{class_name}}_getseters[] = { + // TODO: Handle doc +{{#class_properties}} + { "{{property_name}}", (getter)pylinphone_{{class_name}}_{{getter_name}}, (setter)pylinphone_{{class_name}}_{{setter_name}}, "" }, +{{/class_properties}} + { NULL, NULL, NULL, NULL, NULL } /* Sentinel */ +}; + +static PyTypeObject pylinphone_{{class_name}}Type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "linphone.{{name}}", /* tp_name */ + sizeof(pylinphone_{{class_name}}Object), /* tp_basicsize */ + 0, /* tp_itemsize */ + pylinphone_{{class_name}}_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + {{{class_doc}}}, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + pylinphone_{{class_name}}_instance_methods, /* tp_methods */ + 0, /* tp_members */ + pylinphone_{{class_name}}_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + pylinphone_{{class_name}}_new, /* tp_new */ + 0, /* tp_free */ +}; + +{{/classes}} + +static PyMethodDef pylinphone_ModuleMethods[] = { + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +static PyMethodDef pylinphone_NoMethods[] = { + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +PyMODINIT_FUNC initlinphone(void) { + PyObject *m; + PyObject *menum; + PyMethodDef *def; + +{{#classes}} + if (PyType_Ready(&pylinphone_{{class_name}}Type) < 0) return; +{{/classes}} + + m = Py_InitModule3("linphone", pylinphone_ModuleMethods, "Python module giving access to the Linphone library."); + if (m == NULL) return; + +{{#enums}} + menum = Py_InitModule3("{{enum_name}}", pylinphone_NoMethods, {{{enum_doc}}}); + if (menum == NULL) return; + if (PyModule_AddObject(m, "{{enum_name}}", menum) < 0) return; +{{#enum_values}} + if (PyModule_AddIntConstant(menum, "{{enum_value_name}}", {{enum_value_name}}) < 0) return; +{{/enum_values}} +{{/enums}} + +{{#classes}} + Py_INCREF(&pylinphone_{{class_name}}Type); + PyModule_AddObject(m, "{{class_name}}", (PyObject *)&pylinphone_{{class_name}}Type); + for (def = pylinphone_{{class_name}}_type_methods; def->ml_name != NULL; def++) { + PyObject *func = PyCFunction_New(def, NULL); + PyObject *method = PyMethod_New(func, NULL, (PyObject *)&pylinphone_{{class_name}}Type); + PyDict_SetItemString(pylinphone_{{class_name}}Type.tp_dict, def->ml_name, method); + Py_DECREF(method); + Py_DECREF(func); + } +{{/classes}} +} diff --git a/tools/python/setup.py b/tools/python/setup.py new file mode 100644 index 000000000..cf88c1b28 --- /dev/null +++ b/tools/python/setup.py @@ -0,0 +1,9 @@ +#!/usr/bin/python + +from distutils.core import setup, Extension + +m = Extension('linphone', + include_dirs = ['/home/ghislain/linphone-install/include'], + sources = ['linphone.c'] +) +setup(name = 'Linphone', version = '1.0', description = 'Linphone package', ext_modules = [m]) From ed6499b896dbffa0d6a1158485ba73a7e8dbe7f4 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 7 Jul 2014 11:29:23 +0200 Subject: [PATCH 004/218] =?UTF-8?q?Fix=20compilation=20warnings:=20"functi?= =?UTF-8?q?on=20declaration=20isn=E2=80=99t=20a=20prototype".?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- coreapi/linphonecore.h | 4 ++-- coreapi/linphonefriend.h | 2 +- coreapi/sipsetup.h | 2 +- mediastreamer2 | 2 +- oRTP | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 1a3bd2be3..1db2b29c0 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -218,7 +218,7 @@ LINPHONE_PUBLIC const char *linphone_error_info_get_details(const LinphoneErrorI LINPHONE_PUBLIC int linphone_error_info_get_protocol_code(const LinphoneErrorInfo *ei); /* linphone dictionary */ -LINPHONE_PUBLIC LinphoneDictionary* linphone_dictionary_new(); +LINPHONE_PUBLIC LinphoneDictionary* linphone_dictionary_new(void); LinphoneDictionary * linphone_dictionary_clone(const LinphoneDictionary* src); LinphoneDictionary * linphone_dictionary_ref(LinphoneDictionary* obj); void linphone_dictionary_unref(LinphoneDictionary* obj); @@ -1980,7 +1980,7 @@ LINPHONE_PUBLIC const char * linphone_core_get_stun_server(const LinphoneCore *l * * @return true if uPnP is available otherwise return false. */ -LINPHONE_PUBLIC bool_t linphone_core_upnp_available(); +LINPHONE_PUBLIC bool_t linphone_core_upnp_available(void); /** * @ingroup network_parameters diff --git a/coreapi/linphonefriend.h b/coreapi/linphonefriend.h index a38db19d0..891bf7631 100644 --- a/coreapi/linphonefriend.h +++ b/coreapi/linphonefriend.h @@ -119,7 +119,7 @@ typedef struct _LinphoneFriend LinphoneFriend; * Contructor * @return a new empty #LinphoneFriend */ -LINPHONE_PUBLIC LinphoneFriend * linphone_friend_new(); +LINPHONE_PUBLIC LinphoneFriend * linphone_friend_new(void); /** * Contructor same as linphone_friend_new() + linphone_friend_set_address() diff --git a/coreapi/sipsetup.h b/coreapi/sipsetup.h index dad51f13e..7b1b2ed44 100644 --- a/coreapi/sipsetup.h +++ b/coreapi/sipsetup.h @@ -117,7 +117,7 @@ typedef struct _SipSetup SipSetup; extern "C"{ #endif -BuddyInfo *buddy_info_new(); +BuddyInfo *buddy_info_new(void); void buddy_info_free(BuddyInfo *info); void buddy_lookup_request_set_key(BuddyLookupRequest *req, const char *key); diff --git a/mediastreamer2 b/mediastreamer2 index b57c4697a..4a919b63a 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit b57c4697a35b83267fe96b20734ea70fe3aa8896 +Subproject commit 4a919b63a6f4583d8dc464e32b542151359d264f diff --git a/oRTP b/oRTP index 9aae6334a..e56d11d0b 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 9aae6334aa4268954e32c7a3382e2dbc40b215d0 +Subproject commit e56d11d0b062ea96c8356513add39511b7cb4043 From 7f4afe2954c1f92df5ca57e2a14e44e152e2b244 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 7 Jul 2014 16:45:22 +0200 Subject: [PATCH 005/218] Fix generation of gitversion.h. --- coreapi/Makefile.am | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/coreapi/Makefile.am b/coreapi/Makefile.am index 547623652..ce7dc56a0 100644 --- a/coreapi/Makefile.am +++ b/coreapi/Makefile.am @@ -130,7 +130,7 @@ test_numbers_LDADD=liblinphone.la $(liblinphone_la_LIBADD) endif AM_CPPFLAGS=\ - -I$(top_srcdir) -I$(top_srcdir)/include + -I$(top_srcdir) -I$(top_srcdir)/include -I$(builddir) AM_CFLAGS=\ $(STRICT_OPTIONS) -DIN_LINPHONE \ @@ -167,21 +167,21 @@ make_gitversion_h: echo "*** PACKAGE_VERSION and git tag differ. Please put them identical."; \ exit 1; \ fi ; \ - $(ECHO) -n "#define LIBLINPHONE_GIT_VERSION \"$(GITDESCRIBE)\"" > $(GITVERSION_FILE_TMP) ; \ + $(ECHO) -n "#define LIBLINPHONE_GIT_VERSION \"$(GITDESCRIBE)\"" > $(builddir)/$(GITVERSION_FILE_TMP) ; \ elif test "$(GITREVISION)" != "" ; then \ - $(ECHO) -n "#define LIBLINPHONE_GIT_VERSION \"$(LINPHONE_VERSION)_$(GITREVISION)\"" > $(GITVERSION_FILE_TMP) ; \ + $(ECHO) -n "#define LIBLINPHONE_GIT_VERSION \"$(LINPHONE_VERSION)_$(GITREVISION)\"" > $(builddir)/$(GITVERSION_FILE_TMP) ; \ else \ - $(ECHO) -n "" > $(GITVERSION_FILE_TMP) ; \ + $(ECHO) -n "" > $(builddir)/$(GITVERSION_FILE_TMP) ; \ fi ; \ else \ - $(ECHO) -n "" > $(GITVERSION_FILE_TMP) ; \ + $(ECHO) -n "" > $(builddir)/$(GITVERSION_FILE_TMP) ; \ fi - if ! test -f $(srcdir)/$(GITVERSION_FILE) ; then \ - cp -f $(srcdir)/$(GITVERSION_FILE_TMP) $(srcdir)/$(GITVERSION_FILE) ; \ + if ! test -f $(builddir)/$(GITVERSION_FILE) ; then \ + cp -f $(builddir)/$(GITVERSION_FILE_TMP) $(builddir)/$(GITVERSION_FILE) ; \ fi - if test "`cat $(GITVERSION_FILE_TMP)`" != "`cat $(srcdir)/$(GITVERSION_FILE)`" ; then \ - cp -f $(GITVERSION_FILE_TMP) $(srcdir)/$(GITVERSION_FILE) ; \ + if test "`cat $(builddir)/$(GITVERSION_FILE_TMP)`" != "`cat $(builddir)/$(GITVERSION_FILE)`" ; then \ + cp -f $(builddir)/$(GITVERSION_FILE_TMP) $(builddir)/$(GITVERSION_FILE) ; \ fi - rm -f $(GITVERSION_FILE_TMP) ; + rm -f $(builddir)/$(GITVERSION_FILE_TMP) ; $(GITVERSION_FILE): make_gitversion_h From d213cc9a731ced958b3bc8578fd4d35a0550f8c5 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Mon, 7 Jul 2014 15:40:48 +0200 Subject: [PATCH 006/218] Add StatisticsCollector in flexisip.conf for unit tests --- tester/flexisip.conf | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tester/flexisip.conf b/tester/flexisip.conf index 6c0d85b29..303a1022f 100755 --- a/tester/flexisip.conf +++ b/tester/flexisip.conf @@ -513,3 +513,25 @@ enabled=true filter = (user-agent contains 'redirect') && !(request.uri.params contains 'redirected') contact= +## +## The purpose of the StatisticsCollector module is to collect call +## statistics (RFC 6035) and store them on the server. +## +[module::StatisticsCollector] +# Indicate whether the module is activated. +# Default value: false +enabled=true + +# A request/response enters module if the boolean filter evaluates +# to true. Ex: from.uri.domain contains 'sip.linphone.org', from.uri.domain +# in 'a.org b.org c.org', (to.uri.domain in 'a.org b.org c.org') +# && (user-agent == 'Linphone v2') +# Default value: +filter= + +# SIP URI of the statistics collector. Note that the messages destinated +# to this address will be deleted by this module and thus not be +# delivered. +# Default value: +collector-address=sip:collector@sip.example.org + From 9280f2ba7371785fa3a9b464fef5aa1b1ef3c9bc Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Mon, 7 Jul 2014 16:20:27 +0200 Subject: [PATCH 007/218] fix unexpected high loss values in RTCP reports when resuming calls --- .gitignore | 1 + mediastreamer2 | 2 +- oRTP | 2 +- tester/call_tester.c | 81 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 047ebb08c..6a94843fa 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ tools/lpc2xml_test tools/xml2lpc_test coreapi/help/filetransfer tester/receive_file.dump +tester/tmp.db diff --git a/mediastreamer2 b/mediastreamer2 index 4a919b63a..0622ff738 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 4a919b63a6f4583d8dc464e32b542151359d264f +Subproject commit 0622ff73822d27191e31391f135c90f6e5e8dcbb diff --git a/oRTP b/oRTP index e56d11d0b..ad02c6d5e 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit e56d11d0b062ea96c8356513add39511b7cb4043 +Subproject commit ad02c6d5ed76092157aff9c1061abfd490ac03ae diff --git a/tester/call_tester.c b/tester/call_tester.c index f5eadb3b6..b507b5855 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -306,6 +306,8 @@ static void call_outbound_with_multiple_proxy() { // calling marie should go through the second proxy config CU_ASSERT_TRUE(call(marie, pauline)); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); } #if 0 /* TODO: activate test when the implementation is ready */ @@ -934,15 +936,11 @@ static void call_paused_resumed(void) { wait_for_until(pauline->lc, marie->lc, NULL, 5, 3000); - int exp_cum = - rtp_session_get_rcv_ext_seq_number(call_pauline->audiostream->ms.sessions.rtp_session); - linphone_core_pause_call(pauline->lc,call_pauline); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallPausing,1)); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallPausedByRemote,1)); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallPaused,1)); - exp_cum += rtp_session_get_seq_number(linphone_core_get_current_call(marie->lc)->audiostream->ms.sessions.rtp_session); - /*stay in pause a little while in order to generate traffic*/ wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); @@ -953,11 +951,58 @@ static void call_paused_resumed(void) { /*same here: wait a while for a bit of a traffic, we need to receive a RTCP packet*/ wait_for_until(pauline->lc, marie->lc, NULL, 5, 5000); - /*there should be a bit of packets loss for the ones sent by PAUSED (pauline) between the latest RTCP SR report received - by PAUSER (marie) because PAUSER will drop any packets received after the pause. Keep a tolerance of 1 more packet lost - in case of ...*/ + /*since RTCP streams are reset when call is paused/resumed, there should be no loss at all*/ stats = rtp_session_get_stats(call_pauline->sessions->rtp_session); - CU_ASSERT_TRUE(abs(stats->cum_packet_loss - exp_cum)<=1); + CU_ASSERT_EQUAL(stats->cum_packet_loss, 0); + + /*just to sleep*/ + linphone_core_terminate_all_calls(pauline->lc); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallEnd,1)); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallEnd,1)); + + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void call_paused_resumed_with_loss(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + LinphoneCall* call_pauline; + const rtp_stats_t * stats; + + + OrtpNetworkSimulatorParams params={0}; + params.enabled=TRUE; + params.loss_rate=25; + + + CU_ASSERT_TRUE(call(pauline,marie)); + call_pauline = linphone_core_get_current_call(pauline->lc); + rtp_session_enable_network_simulation(call_pauline->audiostream->ms.sessions.rtp_session,¶ms); + rtp_session_enable_network_simulation(call_pauline->videostream->ms.sessions.rtp_session,¶ms); + + wait_for_until(pauline->lc, marie->lc, NULL, 5, 4000); + + linphone_core_pause_call(pauline->lc,call_pauline); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallPausing,1)); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallPausedByRemote,1)); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallPaused,1)); + + /*stay in pause a little while in order to generate traffic*/ + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + linphone_core_resume_call(pauline->lc,call_pauline); + + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,2)); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,2)); + /*same here: wait a while for a bit of a traffic, we need to receive a RTCP packet*/ + wait_for_until(pauline->lc, marie->lc, NULL, 5, 5000); + + /*since stats are NOT totally reset during pause, the stats->packet_recv is computed from + the start of call. This test ensures that the loss rate is consistent during the entire call.*/ + stats = rtp_session_get_stats(call_pauline->sessions->rtp_session); + CU_ASSERT_TRUE(((stats->cum_packet_loss * 100.f / stats->packet_recv) / params.loss_rate) > .5f); /*just to sleep*/ linphone_core_terminate_all_calls(pauline->lc); @@ -984,19 +1029,29 @@ static bool_t pause_call_1(LinphoneCoreManager* mgr_1,LinphoneCall* call_1,Linph static void call_paused_resumed_from_callee(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); - LinphoneCall* call_obj; + LinphoneCall* call_marie; + const rtp_stats_t * stats; CU_ASSERT_TRUE(call(pauline,marie)); - call_obj = linphone_core_get_current_call(marie->lc); + call_marie = linphone_core_get_current_call(marie->lc); - linphone_core_pause_call(marie->lc,call_obj); + linphone_core_pause_call(marie->lc,call_marie); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallPausing,1)); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallPausedByRemote,1)); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallPaused,1)); - linphone_core_resume_call(marie->lc,call_obj); + /*stay in pause a little while in order to generate traffic*/ + wait_for_until(pauline->lc, marie->lc, NULL, 5, 2000); + + linphone_core_resume_call(marie->lc,call_marie); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,2)); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,2)); + /*same here: wait a while for a bit of a traffic, we need to receive a RTCP packet*/ + wait_for_until(pauline->lc, marie->lc, NULL, 5, 5000); + + /*since RTCP streams are reset when call is paused/resumed, there should be no loss at all*/ + stats = rtp_session_get_stats(call_marie->sessions->rtp_session); + CU_ASSERT_EQUAL(stats->cum_packet_loss, 0); /*just to sleep*/ linphone_core_terminate_all_calls(pauline->lc); @@ -1007,7 +1062,6 @@ static void call_paused_resumed_from_callee(void) { linphone_core_manager_destroy(pauline); } - #ifdef VIDEO_ENABLED static bool_t add_video(LinphoneCoreManager* caller,LinphoneCoreManager* callee) { LinphoneVideoPolicy caller_policy; @@ -2617,6 +2671,7 @@ test_t call_tests[] = { { "Call terminated by caller", call_terminated_by_caller }, { "Call without SDP", call_with_no_sdp}, { "Call paused resumed", call_paused_resumed }, + { "Call paused resumed with loss", call_paused_resumed_with_loss }, { "Call paused resumed from callee", call_paused_resumed_from_callee }, { "SRTP call", srtp_call }, { "ZRTP call",zrtp_call}, From a3f96a73cb84ff0a58f8497a5f51f3c83100e4fc Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Tue, 8 Jul 2014 08:33:02 +0200 Subject: [PATCH 008/218] add config param to disable vfu request --- coreapi/linphonecall.c | 11 +++++++---- coreapi/linphonecore.c | 1 + coreapi/private.h | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 5ed1b3c24..d78867bfd 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1278,11 +1278,14 @@ void linphone_call_enable_camera (LinphoneCall *call, bool_t enable){ /** * Request remote side to send us a Video Fast Update. **/ -void linphone_call_send_vfu_request(LinphoneCall *call) -{ +void linphone_call_send_vfu_request(LinphoneCall *call) { #ifdef VIDEO_ENABLED - if (LinphoneCallStreamsRunning == linphone_call_get_state(call)) - sal_call_send_vfu_request(call->op); + if (call->core->sip_conf.vfu_with_info) { + if (LinphoneCallStreamsRunning == linphone_call_get_state(call)) + sal_call_send_vfu_request(call->op); + } else { + ms_message("vfu request using sip disabled from config [sip,vfu_with_info]"); + } #endif } diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 76a75f93c..276dd82f7 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -755,6 +755,7 @@ static void sip_config_read(LinphoneCore *lc) sal_use_one_matching_codec_policy(lc->sal,lp_config_get_int(lc->config,"sip","only_one_codec",0)); sal_use_dates(lc->sal,lp_config_get_int(lc->config,"sip","put_date",0)); sal_enable_sip_update_method(lc->sal,lp_config_get_int(lc->config,"sip","sip_update",1)); + lc->sip_conf.vfu_with_info=lp_config_get_int(lc->config,"sip","vfu_with_info",1); } static void rtp_config_read(LinphoneCore *lc) diff --git a/coreapi/private.h b/coreapi/private.h index 4722b4849..32e013c94 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -524,6 +524,7 @@ typedef struct sip_config bool_t ping_with_options; bool_t auto_net_state_mon; bool_t tcp_tls_keepalive; + bool_t vfu_with_info; /*use to enable vfu request using sip info*/ } sip_config_t; typedef struct rtp_config From c18f904b2917c1e8141eccf009e0800dcf9a0d8b Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Tue, 8 Jul 2014 14:30:52 +0200 Subject: [PATCH 009/218] update ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 0622ff738..6155d6437 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 0622ff73822d27191e31391f135c90f6e5e8dcbb +Subproject commit 6155d6437712ac049be34b73ddc51a85d62c9f9b From b5aeb7e7553ed1efc8d91384b29bf4116def4545 Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Tue, 8 Jul 2014 15:41:39 +0200 Subject: [PATCH 010/218] Jni update proxyConfig wrapper --- coreapi/linphonecore_jni.cc | 39 +++++-- coreapi/proxy.c | 3 + .../linphone/core/LinphoneProxyConfig.java | 3 + .../org/linphone/core/LinphoneCoreImpl.java | 18 +-- .../core/LinphoneProxyConfigImpl.java | 110 ++++++++++++++---- 5 files changed, 128 insertions(+), 45 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 3ab985981..78eb797c3 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -77,6 +77,23 @@ extern "C" void libmswebrtc_init(); } +#define RETURN_PROXY_CONFIG_USER_DATA_OBJECT(javaclass, funcprefix, cobj) \ + { \ + jclass jUserDataObjectClass; \ + jmethodID jUserDataObjectCtor; \ + jobject jUserDataObj; \ + jUserDataObj = (jobject)funcprefix ## _get_user_data(cobj); \ + if (jUserDataObj == NULL) { \ + jUserDataObjectClass = (jclass)env->NewGlobalRef(env->FindClass("org/linphone/core/" javaclass)); \ + jUserDataObjectCtor = env->GetMethodID(jUserDataObjectClass,"", "(J)V"); \ + jUserDataObj = env->NewObject(jUserDataObjectClass, jUserDataObjectCtor,(jlong) cobj); \ + jUserDataObj = env->NewGlobalRef(jUserDataObj); \ + funcprefix ## _set_user_data(cobj, jUserDataObj); \ + env->DeleteGlobalRef(jUserDataObjectClass); \ + } \ + return jUserDataObj; \ + } + static JavaVM *jvm=0; static const char* LogDomain = "Linphone"; @@ -880,24 +897,32 @@ extern "C" void Java_org_linphone_core_LinphoneCoreImpl_setDefaultProxyConfig( J extern "C" jlong Java_org_linphone_core_LinphoneCoreImpl_getDefaultProxyConfig( JNIEnv* env ,jobject thiz ,jlong lc) { + LinphoneProxyConfig *config=0; linphone_core_get_default_proxy((LinphoneCore*)lc,&config); return (jlong)config; } -extern "C" jlongArray Java_org_linphone_core_LinphoneCoreImpl_getProxyConfigList(JNIEnv* env, jobject thiz, jlong lc) { +static jobject getOrCreateProxy(JNIEnv* env,LinphoneProxyConfig* proxy){ + RETURN_PROXY_CONFIG_USER_DATA_OBJECT("LinphoneProxyConfigImpl", linphone_proxy_config, proxy); +} + +extern "C" jobjectArray Java_org_linphone_core_LinphoneCoreImpl_getProxyConfigList(JNIEnv* env, jobject thiz, jlong lc) { const MSList* proxies = linphone_core_get_proxy_config_list((LinphoneCore*)lc); int proxyCount = ms_list_size(proxies); - jlongArray jProxies = env->NewLongArray(proxyCount); - jlong *jInternalArray = env->GetLongArrayElements(jProxies, NULL); + jclass cls = env->FindClass("java/lang/Object"); + jobjectArray jProxies = env->NewObjectArray(proxyCount,cls,NULL); for (int i = 0; i < proxyCount; i++ ) { - jInternalArray[i] = (unsigned long) (proxies->data); + LinphoneProxyConfig* proxy = (LinphoneProxyConfig*)proxies->data; + jobject jproxy = getOrCreateProxy(env,proxy); + if(jproxy != NULL){ + env->SetObjectArrayElement(jProxies, i, jproxy); + } else { + return NULL; + } proxies = proxies->next; } - - env->ReleaseLongArrayElements(jProxies, jInternalArray, 0); - return jProxies; } diff --git a/coreapi/proxy.c b/coreapi/proxy.c index a999d3b3c..fb8519c32 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -1116,6 +1116,9 @@ void linphone_core_remove_proxy_config(LinphoneCore *lc, LinphoneProxyConfig *cf linphone_proxy_config_enable_register(cfg,FALSE); linphone_proxy_config_done(cfg); linphone_proxy_config_update(cfg); /*so that it has an effect*/ + + /*as cfg no longer in proxies, unregister will never be issued*/ + _linphone_proxy_config_unregister(cfg); } if (lc->default_proxy==cfg){ lc->default_proxy=NULL; diff --git a/java/common/org/linphone/core/LinphoneProxyConfig.java b/java/common/org/linphone/core/LinphoneProxyConfig.java index ded4f9457..b34363e5f 100644 --- a/java/common/org/linphone/core/LinphoneProxyConfig.java +++ b/java/common/org/linphone/core/LinphoneProxyConfig.java @@ -25,6 +25,9 @@ package org.linphone.core; */ public interface LinphoneProxyConfig { + public void setIsDeleted(boolean b); + public boolean getIsDeleted(); + /** *Starts editing a proxy configuration. *Because proxy configuration must be consistent, applications MUST call {@link #edit()} before doing any attempts to modify proxy configuration (such as identity, proxy address and so on). diff --git a/java/impl/org/linphone/core/LinphoneCoreImpl.java b/java/impl/org/linphone/core/LinphoneCoreImpl.java index bf43810a3..eeebbd328 100644 --- a/java/impl/org/linphone/core/LinphoneCoreImpl.java +++ b/java/impl/org/linphone/core/LinphoneCoreImpl.java @@ -105,7 +105,7 @@ class LinphoneCoreImpl implements LinphoneCore { private native String getRing(long nativePtr); private native void setRootCA(long nativePtr, String path); private native long[] listVideoPayloadTypes(long nativePtr); - private native long[] getProxyConfigList(long nativePtr); + private native LinphoneProxyConfig[] getProxyConfigList(long nativePtr); private native long[] getAuthInfosList(long nativePtr); private native long findAuthInfos(long nativePtr, String username, String realm, String domain); private native long[] listAudioPayloadTypes(long nativePtr); @@ -190,7 +190,7 @@ class LinphoneCoreImpl implements LinphoneCore { public synchronized LinphoneProxyConfig getDefaultProxyConfig() { isValid(); - long lNativePtr = getDefaultProxyConfig(nativePtr); + long lNativePtr = getDefaultProxyConfig(nativePtr); if (lNativePtr!=0) { return new LinphoneProxyConfigImpl(this,lNativePtr); } else { @@ -224,6 +224,7 @@ class LinphoneCoreImpl implements LinphoneCore { isValid(); removeProxyConfig(nativePtr, ((LinphoneProxyConfigImpl)proxyCfg).nativePtr); ((LinphoneProxyConfigImpl)proxyCfg).mCore=null; + ((LinphoneProxyConfigImpl)proxyCfg).deleteNativePtr(); } public synchronized void clearAuthInfos() { isValid(); @@ -512,17 +513,8 @@ class LinphoneCoreImpl implements LinphoneCore { setRootCA(nativePtr, path); } - public synchronized LinphoneProxyConfig[] getProxyConfigList() { - long[] typesPtr = getProxyConfigList(nativePtr); - if (typesPtr == null) return null; - - LinphoneProxyConfig[] proxies = new LinphoneProxyConfig[typesPtr.length]; - - for (int i=0; i < proxies.length; i++) { - proxies[i] = new LinphoneProxyConfigImpl(this,typesPtr[i]); - } - - return proxies; + public synchronized LinphoneProxyConfig[] getProxyConfigList() { + return getProxyConfigList(nativePtr); } public synchronized PayloadType[] getVideoCodecs() { diff --git a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java index aface8e36..1288ac39f 100644 --- a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java +++ b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java @@ -15,20 +15,17 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ + */ package org.linphone.core; import org.linphone.core.LinphoneCore.RegistrationState; - - - - class LinphoneProxyConfigImpl implements LinphoneProxyConfig { - protected final long nativePtr; + protected long nativePtr; protected LinphoneCoreImpl mCore; - + protected boolean isDeleting; + private native int getState(long nativePtr); private native void setExpires(long nativePtr, int delay); private native int getExpires(long nativePtr); @@ -39,14 +36,40 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { setIdentity(identity); setProxy(proxy); setRoute(route); + setIsDeleted(false); enableRegister(enableRegister); ownPtr=true; } - protected LinphoneProxyConfigImpl(LinphoneCoreImpl core, long aNativePtr) { + + protected LinphoneProxyConfigImpl(LinphoneCoreImpl core,long aNativePtr) { + mCore=core; nativePtr = aNativePtr; ownPtr=false; - mCore=core; } + + protected LinphoneProxyConfigImpl(long aNativePtr) { + nativePtr = aNativePtr; + ownPtr=false; + } + + public boolean getIsDeleted() { + return isDeleting; + } + + public void setIsDeleted(boolean b) { + isDeleting = b; + } + + private void isValid() { + if (nativePtr == 0) { + throw new RuntimeException("proxy config removed"); + } + } + + public void deleteNativePtr() { + nativePtr=0; + } + protected void finalize() throws Throwable { //Log.e(LinphoneService.TAG,"fixme, should release underlying proxy config"); if (ownPtr) delete(nativePtr); @@ -56,42 +79,44 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { private native void edit(long ptr); private native void done(long ptr); - + private native void setIdentity(long ptr,String identity); private native String getIdentity(long ptr); private native int setProxy(long ptr,String proxy); private native String getProxy(long ptr); - + private native void enableRegister(long ptr,boolean value); private native boolean isRegisterEnabled(long ptr); - + private native boolean isRegistered(long ptr); private native void setDialPrefix(long ptr, String prefix); private native String getDialPrefix(long ptr); - + private native String normalizePhoneNumber(long ptr,String number); - + private native String getDomain(long ptr); - + private native void setDialEscapePlus(long ptr, boolean value); private native boolean getDialEscapePlus(long ptr); - + private native String getRoute(long ptr); private native int setRoute(long ptr,String uri); private native void enablePublish(long ptr,boolean enable); private native boolean publishEnabled(long ptr); private native void setContactParameters(long ptr, String params); - + private native int lookupCCCFromIso(long nativePtr, String iso); private native int lookupCCCFromE164(long nativePtr, String e164); - + public LinphoneProxyConfig enableRegister(boolean value) { + isValid(); enableRegister(nativePtr,value); return this; } public void done() { + isValid(); Object mutex=mCore!=null ? mCore : this; synchronized(mutex){ done(nativePtr); @@ -99,6 +124,7 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { } public LinphoneProxyConfig edit() { + isValid(); Object mutex=mCore!=null ? mCore : this; synchronized(mutex){ edit(nativePtr); @@ -107,139 +133,173 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { } public void setIdentity(String identity) throws LinphoneCoreException { + isValid(); setIdentity(nativePtr,identity); } public void setProxy(String proxyUri) throws LinphoneCoreException { + isValid(); if (setProxy(nativePtr,proxyUri)!=0) { throw new LinphoneCoreException("Bad proxy address ["+proxyUri+"]"); } } public String normalizePhoneNumber(String number) { + isValid(); return normalizePhoneNumber(nativePtr,number); } public void setDialPrefix(String prefix) { + isValid(); setDialPrefix(nativePtr, prefix); } public String getDialPrefix() { + isValid(); return getDialPrefix(nativePtr); } public String getDomain() { + isValid(); return getDomain(nativePtr); } public void setDialEscapePlus(boolean value) { - setDialEscapePlus(nativePtr,value); + isValid(); + setDialEscapePlus(nativePtr,value); } public boolean getDialEscapePlus() { + isValid(); return getDialEscapePlus(nativePtr); } public String getIdentity() { + isValid(); return getIdentity(nativePtr); } public String getProxy() { + isValid(); return getProxy(nativePtr); } public boolean isRegistered() { + isValid(); return isRegistered(nativePtr); } public boolean registerEnabled() { + isValid(); return isRegisterEnabled(nativePtr); } public String getRoute() { + isValid(); return getRoute(nativePtr); } public void setRoute(String routeUri) throws LinphoneCoreException { + isValid(); if (setRoute(nativePtr, routeUri) != 0) { throw new LinphoneCoreException("cannot set route ["+routeUri+"]"); } } public void enablePublish(boolean enable) { + isValid(); enablePublish(nativePtr,enable); } public RegistrationState getState() { + isValid(); return RegistrationState.fromInt(getState(nativePtr)); } public void setExpires(int delay) { + isValid(); setExpires(nativePtr, delay); } public int getExpires() { + isValid(); return getExpires(nativePtr); } public boolean publishEnabled() { + isValid(); return publishEnabled(nativePtr); } @Override public void setContactParameters(String params) { + isValid(); setContactParameters(nativePtr, params); } @Override public int lookupCCCFromIso(String iso) { + isValid(); return lookupCCCFromIso(nativePtr, iso); } @Override public int lookupCCCFromE164(String e164) { + isValid(); return lookupCCCFromE164(nativePtr, e164); } private native int getError(long nativeptr); @Override public Reason getError() { + isValid(); return Reason.fromInt(getError(nativePtr)); } private native void setPrivacy(long nativePtr, int mask); @Override public void setPrivacy(int privacy_mask) { + isValid(); setPrivacy(nativePtr,privacy_mask); } private native int getPrivacy(long nativePtr); @Override public int getPrivacy() { + isValid(); return getPrivacy(nativePtr); } - + private native void enableAvpf(long nativePtr, boolean enable); @Override public void enableAvpf(boolean enable) { + isValid(); enableAvpf(nativePtr, enable); } private native boolean avpfEnabled(long nativePtr); @Override public boolean avpfEnabled() { + isValid(); return avpfEnabled(nativePtr); } - + private native void setAvpfRRInterval(long nativePtr, int interval); @Override public void setAvpfRRInterval(int interval) { + isValid(); setAvpfRRInterval(nativePtr, interval); } - + private native int getAvpfRRInterval(long nativePtr); @Override public int getAvpfRRInterval() { + isValid(); return getAvpfRRInterval(nativePtr); } - + private native String getContactParameters(long ptr); @Override public String getContactParameters() { + isValid(); return getContactParameters(nativePtr); } - + private native void setContactUriParameters(long ptr, String params); @Override public void setContactUriParameters(String params) { + isValid(); setContactUriParameters(nativePtr,params); } - + private native String getContactUriParameters(long ptr); @Override public String getContactUriParameters() { + isValid(); return getContactUriParameters(nativePtr); } + private native long getErrorInfo(long nativePtr); + @Override public ErrorInfo getErrorInfo() { return new ErrorInfoImpl(getErrorInfo(nativePtr)); From 09be0d018de60f8864f17d477e137aff207fccd7 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Tue, 8 Jul 2014 16:22:54 +0200 Subject: [PATCH 011/218] ortp:fix compilation issue on macosx --- oRTP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oRTP b/oRTP index ad02c6d5e..99f33a0f5 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit ad02c6d5ed76092157aff9c1061abfd490ac03ae +Subproject commit 99f33a0f510310389c22bf88a39582450be38425 From 622e0a581b9cda9ddb6278c3b84ff7f68f98d634 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 8 Jul 2014 16:48:43 +0200 Subject: [PATCH 012/218] Add logging of called function in the python wrapper. --- .../apixml2python/linphone_module.mustache | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 52721287a..f9415d366 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -1,6 +1,43 @@ #include #include + +static PyObject *logging_module = NULL; + + +static void init_logging(void) { + logging_module = PyImport_ImportModule("logging"); + if (logging_module != NULL) { + PyObject *constant; + PyObject *func; + PyObject *kws; + long level = 0; + + constant = PyObject_GetAttrString(logging_module, "DEBUG"); + if (PyInt_Check(constant)) { + level = PyInt_AsLong(constant); + } + Py_DECREF(constant); + func = PyObject_GetAttrString(logging_module, "basicConfig"); + kws = Py_BuildValue("{s:i,s:s}", "level", level, "format", "%(levelname)s: %(message)s"); + PyEval_CallObjectWithKeywords(func, NULL, kws); + Py_DECREF(kws); + Py_DECREF(func); + } +} + +static void pylinphone_log(const char *level, const char *fmt) { + if (logging_module != NULL) { + PyEval_CallMethod(logging_module, level, "(s)", fmt); + } +} + +#define pylinphone_debug(fmt) pylinphone_log("debug", fmt) +#define pylinphone_info(fmt) pylinphone_log("info", fmt) +#define pylinphone_warning(fmt) pylinphone_log("warning", fmt) +#define pylinphone_trace pylinphone_debug + + {{#classes}} typedef struct { @@ -10,10 +47,12 @@ typedef struct { static PyObject * pylinphone_{{class_name}}_new(PyTypeObject *type, PyObject *args, PyObject *kw) { pylinphone_{{class_name}}Object *self = (pylinphone_{{class_name}}Object *)type->tp_alloc(type, 0); + pylinphone_trace(__FUNCTION__); return (PyObject *)self; } static void pylinphone_{{class_name}}_dealloc(PyObject *self) { + pylinphone_trace(__FUNCTION__); self->ob_type->tp_free(self); } @@ -131,6 +170,8 @@ PyMODINIT_FUNC initlinphone(void) { PyObject *menum; PyMethodDef *def; + init_logging(); + {{#classes}} if (PyType_Ready(&pylinphone_{{class_name}}Type) < 0) return; {{/classes}} From d284dd08d73a1adbc3d132f1043e11e83d15f463 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 8 Jul 2014 16:49:27 +0200 Subject: [PATCH 013/218] Start filling methods body in the python wrapper. --- tools/python/apixml2python/linphone.py | 66 +++++++++++++++++-- .../apixml2python/linphone_module.mustache | 3 +- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index f58d923f8..63534f60f 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -25,6 +25,7 @@ class LinphoneModule(object): for xml_type_method in xml_type_methods: m = {} m['method_name'] = xml_type_method.get('name').replace(c['class_c_function_prefix'], '') + m['method_body'] = self.__format_method_body(xml_type_method) c['class_type_methods'].append(m) c['class_instance_methods'] = [] xml_instance_methods = xml_class.findall("./instancemethods/instancemethod") @@ -46,16 +47,73 @@ class LinphoneModule(object): c['class_properties'].append(p) self.classes.append(c) - def __format_node(self, node): + def __ctype_to_parse_tuple_format(self, ctype): + keywords = ['const', 'struct', 'enum', 'signed', 'unsigned', 'short', 'long', '*'] + splitted_type = ctype.split(' ') + for s in splitted_type: + if s not in keywords: + basic_type = s + break + if basic_type == 'char': + if '*' in splitted_type: + return 's' + elif 'unsigned' in splitted_type: + return 'b' + elif basic_type == 'int': + # TODO: + return 'i' + elif basic_type == 'int8_t': + return 'c' + elif basic_type == 'uint8_t': + return 'b' + elif basic_type == 'int16_t': + return 'h' + elif basic_type == 'uint16_t': + return 'H' + elif basic_type == 'int32_t': + return 'l' + elif basic_type == 'uint32_t': + return 'k' + elif basic_type == 'int64_t': + return 'L' + elif basic_type == 'uint64_t': + return 'K' + elif basic_type == 'size_t': + return 'n' + elif basic_type == 'float': + return 'f' + elif basic_type == 'double': + return 'd' + else: + return 'O' + + def __format_method_body(self, method_node): + body = '' + parse_tuple_format = '' + xml_method_args = method_node.findall('./arguments/argument') + arg_names = [] + for xml_method_arg in xml_method_args: + parse_tuple_format += self.__ctype_to_parse_tuple_format(xml_method_arg.get('type')) + body += "\t" + xml_method_arg.get('type') + " " + xml_method_arg.get('name') + ";\n" + arg_names.append(xml_method_arg.get('name')) + body += "\tpylinphone_trace(__FUNCTION__);\n" + if len(xml_method_args) > 0: + body += "\n\tif (!PyArg_ParseTuple(args, \"" + parse_tuple_format + "\"" + body += ', ' + ', '.join(map(lambda a: '&' + a, arg_names)) + body += ")) {\n\t\treturn NULL;\n\t}\n\n" + body += "\tPy_RETURN_NONE;" + return body + + def __format_doc_node(self, node): desc = '' if node.tag == 'para': desc += node.text.strip() for n in list(node): - desc += self.__format_node(n) + desc += self.__format_doc_node(n) elif node.tag == 'note': desc += node.text.strip() for n in list(node): - desc += self.__format_node(n) + desc += self.__format_doc_node(n) elif node.tag == 'ref': desc += ' ' + node.text.strip() + ' ' tail = node.tail.strip() @@ -76,7 +134,7 @@ class LinphoneModule(object): else: desc = '' for node in list(detailed_description): - desc += self.__format_node(node) + '\n' + desc += self.__format_doc_node(node) + '\n' detailed_description = desc.strip().replace('\n', '\\n') brief_description = brief_description.strip() doc += brief_description diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index f9415d366..d788485af 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -59,8 +59,7 @@ static void pylinphone_{{class_name}}_dealloc(PyObject *self) { {{#class_type_methods}} static PyObject * pylinphone_{{class_name}}_type_method_{{method_name}}(PyObject *self, PyObject *args) { - // TODO: Fill implementation - Py_RETURN_NONE; +{{{method_body}}} } {{/class_type_methods}} From 4e487ade84db39a9f3fe3dec13bc08e77fac6e61 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 10:21:16 +0200 Subject: [PATCH 014/218] Link against liblinphone. --- tools/python/setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/python/setup.py b/tools/python/setup.py index cf88c1b28..b3c36fc22 100644 --- a/tools/python/setup.py +++ b/tools/python/setup.py @@ -4,6 +4,8 @@ from distutils.core import setup, Extension m = Extension('linphone', include_dirs = ['/home/ghislain/linphone-install/include'], + libraries = ['linphone'], + library_dirs = ['/home/ghislain/linphone-install/lib'], sources = ['linphone.c'] ) setup(name = 'Linphone', version = '1.0', description = 'Linphone package', ext_modules = [m]) From 9213ca8417d04fad3f44af43a7126d7a5e9ec533 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 10:22:43 +0200 Subject: [PATCH 015/218] Define class methods correctly. --- .../apixml2python/linphone_module.mustache | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index d788485af..7e4303a80 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -58,20 +58,12 @@ static void pylinphone_{{class_name}}_dealloc(PyObject *self) { {{#class_type_methods}} -static PyObject * pylinphone_{{class_name}}_type_method_{{method_name}}(PyObject *self, PyObject *args) { +static PyObject * pylinphone_{{class_name}}_class_method_{{method_name}}(PyObject *cls, PyObject *args) { {{{method_body}}} } {{/class_type_methods}} -static PyMethodDef pylinphone_{{class_name}}_type_methods[] = { - // TODO: Handle doc -{{#class_type_methods}} - { "{{method_name}}", pylinphone_{{class_name}}_type_method_{{method_name}}, METH_VARARGS, "" }, -{{/class_type_methods}} - { NULL, NULL, 0, NULL } /* Sentinel */ -}; - {{#class_instance_methods}} static PyObject * pylinphone_{{class_name}}_instance_method_{{method_name}}(PyObject *self, PyObject *args) { @@ -83,6 +75,11 @@ static PyObject * pylinphone_{{class_name}}_instance_method_{{method_name}}(PyOb static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { // TODO: Handle doc + /* Class methods */ +{{#class_type_methods}} + { "{{method_name}}", pylinphone_{{class_name}}_class_method_{{method_name}}, METH_VARARGS | METH_CLASS, "" }, +{{/class_type_methods}} + /* Instance methods */ {{#class_instance_methods}} { "{{method_name}}", pylinphone_{{class_name}}_instance_method_{{method_name}}, METH_VARARGS, "" }, {{/class_instance_methods}} @@ -167,7 +164,6 @@ static PyMethodDef pylinphone_NoMethods[] = { PyMODINIT_FUNC initlinphone(void) { PyObject *m; PyObject *menum; - PyMethodDef *def; init_logging(); @@ -190,12 +186,5 @@ PyMODINIT_FUNC initlinphone(void) { {{#classes}} Py_INCREF(&pylinphone_{{class_name}}Type); PyModule_AddObject(m, "{{class_name}}", (PyObject *)&pylinphone_{{class_name}}Type); - for (def = pylinphone_{{class_name}}_type_methods; def->ml_name != NULL; def++) { - PyObject *func = PyCFunction_New(def, NULL); - PyObject *method = PyMethod_New(func, NULL, (PyObject *)&pylinphone_{{class_name}}Type); - PyDict_SetItemString(pylinphone_{{class_name}}Type.tp_dict, def->ml_name, method); - Py_DECREF(method); - Py_DECREF(func); - } {{/classes}} } From a8565b049bd8ec82d5a44f804466099f25ac899b Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 10:23:28 +0200 Subject: [PATCH 016/218] Handle creation of Python object from native object pointer. --- tools/python/apixml2python/linphone.py | 33 +++++++++++++++---- .../apixml2python/linphone_module.mustache | 16 +++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 63534f60f..7c5146da4 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -25,7 +25,7 @@ class LinphoneModule(object): for xml_type_method in xml_type_methods: m = {} m['method_name'] = xml_type_method.get('name').replace(c['class_c_function_prefix'], '') - m['method_body'] = self.__format_method_body(xml_type_method) + m['method_body'] = self.__format_method_body(xml_type_method, c['class_name']) c['class_type_methods'].append(m) c['class_instance_methods'] = [] xml_instance_methods = xml_class.findall("./instancemethods/instancemethod") @@ -47,7 +47,7 @@ class LinphoneModule(object): c['class_properties'].append(p) self.classes.append(c) - def __ctype_to_parse_tuple_format(self, ctype): + def __ctype_to_python_format(self, ctype): keywords = ['const', 'struct', 'enum', 'signed', 'unsigned', 'short', 'long', '*'] splitted_type = ctype.split(' ') for s in splitted_type: @@ -87,21 +87,42 @@ class LinphoneModule(object): else: return 'O' - def __format_method_body(self, method_node): + def __format_method_body(self, method_node, class_name): body = '' parse_tuple_format = '' + xml_method_return = method_node.find('./return') + return_type = xml_method_return.get('type') + if return_type != 'void': + body += "\t" + return_type + " cresult;\n" + build_value_format = self.__ctype_to_python_format(return_type) + if build_value_format == 'O': + body += "\tPyObject * pyresult;\n" + body += "\tPyObject * pyret;\n" xml_method_args = method_node.findall('./arguments/argument') arg_names = [] for xml_method_arg in xml_method_args: - parse_tuple_format += self.__ctype_to_parse_tuple_format(xml_method_arg.get('type')) + parse_tuple_format += self.__ctype_to_python_format(xml_method_arg.get('type')) body += "\t" + xml_method_arg.get('type') + " " + xml_method_arg.get('name') + ";\n" arg_names.append(xml_method_arg.get('name')) - body += "\tpylinphone_trace(__FUNCTION__);\n" if len(xml_method_args) > 0: body += "\n\tif (!PyArg_ParseTuple(args, \"" + parse_tuple_format + "\"" body += ', ' + ', '.join(map(lambda a: '&' + a, arg_names)) body += ")) {\n\t\treturn NULL;\n\t}\n\n" - body += "\tPy_RETURN_NONE;" + body += "\tpylinphone_trace(__FUNCTION__);\n\n" + body += "\t" + if return_type != 'void': + body += "cresult = " + body += method_node.get('name') + "(" + ', '.join(arg_names) + ");\n" + if return_type != 'void': + if build_value_format == 'O': + body += "\tpyresult = pylinphone_" + class_name + "_new_from_native_ptr(&pylinphone_" + class_name + "Type, cresult);\n" + body += "\tpyret = Py_BuildValue(\"" + build_value_format + "\", pyresult);\n" + body += "\tPy_DECREF(pyresult);\n" + body += "\treturn pyret;" + else: + body += "\treturn Py_BuildValue(\"" + build_value_format + "\", cresult);" + else: + body += "\tPy_RETURN_NONE;" return body def __format_doc_node(self, node): diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 7e4303a80..8b5a5b6eb 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -38,6 +38,12 @@ static void pylinphone_log(const char *level, const char *fmt) { #define pylinphone_trace pylinphone_debug +{{#classes}} + +static PyTypeObject pylinphone_{{class_name}}Type; + +{{/classes}} + {{#classes}} typedef struct { @@ -45,6 +51,16 @@ typedef struct { {{class_name}} *native_ptr; } pylinphone_{{class_name}}Object; +static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, {{class_name}} *native_ptr) { + pylinphone_{{class_name}}Object *self; + pylinphone_trace(__FUNCTION__); + if (native_ptr == NULL) Py_RETURN_NONE; + self = (pylinphone_{{class_name}}Object *)PyObject_New(pylinphone_{{class_name}}Object, type); + if (self == NULL) Py_RETURN_NONE; + self->native_ptr = native_ptr; + return (PyObject *)self; +} + static PyObject * pylinphone_{{class_name}}_new(PyTypeObject *type, PyObject *args, PyObject *kw) { pylinphone_{{class_name}}Object *self = (pylinphone_{{class_name}}Object *)type->tp_alloc(type, 0); pylinphone_trace(__FUNCTION__); From 793f77e928d842d3c10c75402e8f5d6cdc9160e2 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 14:19:41 +0200 Subject: [PATCH 017/218] Handle setters and getters. --- tools/python/apixml2python/linphone.py | 271 ++++++++++++------ .../apixml2python/linphone_module.mustache | 10 +- 2 files changed, 194 insertions(+), 87 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 7c5146da4..e1ed69f8e 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -1,59 +1,92 @@ -class LinphoneModule(object): - def __init__(self, tree): - self.enums = [] - xml_enums = tree.findall("./enums/enum") - for xml_enum in xml_enums: - e = {} - e['enum_name'] = xml_enum.get('name') - e['enum_doc'] = self.__format_doc(xml_enum.find('briefdescription'), xml_enum.find('detaileddescription')) - e['enum_values'] = [] - xml_enum_values = xml_enum.findall("./values/value") - for xml_enum_value in xml_enum_values: - v = {} - v['enum_value_name'] = xml_enum_value.get('name') - e['enum_values'].append(v) - self.enums.append(e) - self.classes = [] - xml_classes = tree.findall("./classes/class") - for xml_class in xml_classes: - c = {} - c['class_name'] = xml_class.get('name') - c['class_c_function_prefix'] = xml_class.get('cfunctionprefix') - c['class_doc'] = self.__format_doc(xml_class.find('briefdescription'), xml_class.find('detaileddescription')) - c['class_type_methods'] = [] - xml_type_methods = xml_class.findall("./classmethods/classmethod") - for xml_type_method in xml_type_methods: - m = {} - m['method_name'] = xml_type_method.get('name').replace(c['class_c_function_prefix'], '') - m['method_body'] = self.__format_method_body(xml_type_method, c['class_name']) - c['class_type_methods'].append(m) - c['class_instance_methods'] = [] - xml_instance_methods = xml_class.findall("./instancemethods/instancemethod") - for xml_instance_method in xml_instance_methods: - m = {} - m['method_name'] = xml_instance_method.get('name').replace(c['class_c_function_prefix'], '') - c['class_instance_methods'].append(m) - c['class_properties'] = [] - xml_properties = xml_class.findall("./properties/property") - for xml_property in xml_properties: - p = {} - p['property_name'] = xml_property.get('name') - xml_property_getter = xml_property.find("./getter") - xml_property_setter = xml_property.find("./setter") - if xml_property_getter is not None: - p['getter_name'] = xml_property_getter.get('name').replace(c['class_c_function_prefix'], '') - if xml_property_setter is not None: - p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '') - c['class_properties'].append(p) - self.classes.append(c) +class MethodDefinition: + def __init__(self, method_node, class_): + self.body = '' + self.arg_names = [] + self.parse_tuple_format = '' + self.build_value_format = '' + self.return_type = 'void' + self.method_node = method_node + self.class_ = class_ + self.xml_method_return = self.method_node.find('./return') + self.xml_method_args = self.method_node.findall('./arguments/argument') + self.method_type = self.method_node.tag + if self.method_type != 'classmethod': + self.xml_method_args = self.xml_method_args[1:] - def __ctype_to_python_format(self, ctype): + def format_local_variables_definition(self): + self.return_type = self.xml_method_return.get('type') + if self.return_type != 'void': + self.body += "\t" + self.return_type + " cresult;\n" + self.build_value_format = self.__ctype_to_python_format(self.return_type) + if self.build_value_format == 'O': + self.body += "\tPyObject * pyresult;\n" + self.body += "\tPyObject * pyret;\n" + for xml_method_arg in self.xml_method_args: + self.parse_tuple_format += self.__ctype_to_python_format(xml_method_arg.get('type')) + self.body += "\t" + xml_method_arg.get('type') + " " + xml_method_arg.get('name') + ";\n" + self.arg_names.append(xml_method_arg.get('name')) + + def format_arguments_parsing(self): + if len(self.arg_names) > 0: + self.body += "\n\tif (!PyArg_ParseTuple(args, \"" + self.parse_tuple_format + "\"" + self.body += ', ' + ', '.join(map(lambda a: '&' + a, self.arg_names)) + self.body += ")) {\n\t\treturn NULL;\n\t}\n\n" + + def format_setter_value_checking_and_c_function_call(self): + self.body += "\n\tif (value == NULL) {\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Cannot delete this attribute\");\n" + self.body += "\t\treturn -1;\n" + self.body += "\t}\n" + basic_type, checkfunc, convertfunc = self.__ctype_to_python_type(self.xml_method_args[0].get('type')) + self.body += "\n\tif (!" + checkfunc + "(value)) {\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"This attribute value must be a " + basic_type + "\");\n" + self.body += "\t\treturn -1;\n" + self.body += "\t}\n\n" + if convertfunc is None: + pass # TODO + else: + self.body += "\t" + self.arg_names[0] + " = (" + self.xml_method_args[0].get('type') + ")" + convertfunc + "(value);\n" + self.body += "\t" + self.method_node.get('name') + "(" + self.body += "pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self), " + self.arg_names[0] + ");\n" + + def format_tracing(self): + self.body += "\tpylinphone_trace(__FUNCTION__);\n\n" + + def format_c_function_call(self): + self.body += "\t" + if self.return_type != 'void': + self.body += "cresult = " + self.body += self.method_node.get('name') + "(" + if self.method_type != 'classmethod': + self.body += "pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self)" + if len(self.arg_names) > 0: + self.body += ', ' + self.body += ', '.join(self.arg_names) + ");\n" + + def format_method_result(self): + if self.return_type != 'void': + if self.build_value_format == 'O': + self.body += "\tpyresult = pylinphone_" + self.class_['class_name'] + "_new_from_native_ptr(&pylinphone_" + self.class_['class_name'] + "Type, cresult);\n" + self.body += "\tpyret = Py_BuildValue(\"" + self.build_value_format + "\", pyresult);\n" + self.body += "\tPy_DECREF(pyresult);\n" + self.body += "\treturn pyret;" + else: + self.body += "\treturn Py_BuildValue(\"" + self.build_value_format + "\", cresult);" + else: + self.body += "\tPy_RETURN_NONE;" + + def __get_basic_type_from_c_type(self, ctype): + basic_type = 'int' keywords = ['const', 'struct', 'enum', 'signed', 'unsigned', 'short', 'long', '*'] splitted_type = ctype.split(' ') for s in splitted_type: if s not in keywords: basic_type = s break + return (basic_type, splitted_type) + + def __ctype_to_python_format(self, ctype): + basic_type, splitted_type = self.__get_basic_type_from_c_type(ctype) if basic_type == 'char': if '*' in splitted_type: return 's' @@ -84,46 +117,116 @@ class LinphoneModule(object): return 'f' elif basic_type == 'double': return 'd' + elif basic_type == 'bool_t': + return 'i' else: return 'O' - def __format_method_body(self, method_node, class_name): - body = '' - parse_tuple_format = '' - xml_method_return = method_node.find('./return') - return_type = xml_method_return.get('type') - if return_type != 'void': - body += "\t" + return_type + " cresult;\n" - build_value_format = self.__ctype_to_python_format(return_type) - if build_value_format == 'O': - body += "\tPyObject * pyresult;\n" - body += "\tPyObject * pyret;\n" - xml_method_args = method_node.findall('./arguments/argument') - arg_names = [] - for xml_method_arg in xml_method_args: - parse_tuple_format += self.__ctype_to_python_format(xml_method_arg.get('type')) - body += "\t" + xml_method_arg.get('type') + " " + xml_method_arg.get('name') + ";\n" - arg_names.append(xml_method_arg.get('name')) - if len(xml_method_args) > 0: - body += "\n\tif (!PyArg_ParseTuple(args, \"" + parse_tuple_format + "\"" - body += ', ' + ', '.join(map(lambda a: '&' + a, arg_names)) - body += ")) {\n\t\treturn NULL;\n\t}\n\n" - body += "\tpylinphone_trace(__FUNCTION__);\n\n" - body += "\t" - if return_type != 'void': - body += "cresult = " - body += method_node.get('name') + "(" + ', '.join(arg_names) + ");\n" - if return_type != 'void': - if build_value_format == 'O': - body += "\tpyresult = pylinphone_" + class_name + "_new_from_native_ptr(&pylinphone_" + class_name + "Type, cresult);\n" - body += "\tpyret = Py_BuildValue(\"" + build_value_format + "\", pyresult);\n" - body += "\tPy_DECREF(pyresult);\n" - body += "\treturn pyret;" + def __ctype_to_python_type(self, ctype): + basic_type, splitted_type = self.__get_basic_type_from_c_type(ctype) + if basic_type == 'char': + if '*' in splitted_type: + return ('string', 'PyString_Check', 'PyString_AsString') else: - body += "\treturn Py_BuildValue(\"" + build_value_format + "\", cresult);" + return ('int', 'PyInt_Check', 'PyInt_AsLong') + elif basic_type == 'int': + if 'unsigned' in splitted_type: + return ('unsigned int', 'PyLong_Check', 'PyLong_AsUnsignedLong') + else: + return ('int', 'PyLong_Check', 'PyLong_AsLong') + elif basic_type in ['int8_t', 'int16_t' 'int32_t']: + return ('int', 'PyLong_Check', 'PyLong_AsLong') + elif basic_type in ['uint8_t', 'uin16_t', 'uint32_t']: + return ('unsigned int', 'PyLong_Check', 'PyLong_AsUnsignedLong') + elif basic_type == 'int64_t': + return ('64bits int', 'PyLong_Check', 'PyLong_AsLongLong') + elif basic_type == 'uint64_t': + return ('64bits unsigned int', 'PyLong_Check', 'PyLong_AsUnsignedLongLong') + elif basic_type == 'size_t': + return ('size_t', 'PyLong_Check', 'PyLong_AsSsize_t') + elif basic_type in ['float', 'double']: + return ('float', 'PyFloat_Check', 'PyFloat_AsDouble') + elif basic_type == 'bool_t': + return ('bool', 'PyBool_Check', 'PyInt_AsLong') else: - body += "\tPy_RETURN_NONE;" - return body + return ('class instance', 'PyInstance_Check', None) + + +class LinphoneModule(object): + def __init__(self, tree): + self.enums = [] + xml_enums = tree.findall("./enums/enum") + for xml_enum in xml_enums: + e = {} + e['enum_name'] = xml_enum.get('name') + e['enum_doc'] = self.__format_doc(xml_enum.find('briefdescription'), xml_enum.find('detaileddescription')) + e['enum_values'] = [] + xml_enum_values = xml_enum.findall("./values/value") + for xml_enum_value in xml_enum_values: + v = {} + v['enum_value_name'] = xml_enum_value.get('name') + e['enum_values'].append(v) + self.enums.append(e) + self.classes = [] + xml_classes = tree.findall("./classes/class") + for xml_class in xml_classes: + c = {} + c['class_name'] = xml_class.get('name') + c['class_c_function_prefix'] = xml_class.get('cfunctionprefix') + c['class_doc'] = self.__format_doc(xml_class.find('briefdescription'), xml_class.find('detaileddescription')) + c['class_type_methods'] = [] + xml_type_methods = xml_class.findall("./classmethods/classmethod") + for xml_type_method in xml_type_methods: + m = {} + m['method_name'] = xml_type_method.get('name').replace(c['class_c_function_prefix'], '') + m['method_body'] = self.__format_method_body(xml_type_method, c) + c['class_type_methods'].append(m) + c['class_instance_methods'] = [] + xml_instance_methods = xml_class.findall("./instancemethods/instancemethod") + for xml_instance_method in xml_instance_methods: + m = {} + m['method_name'] = xml_instance_method.get('name').replace(c['class_c_function_prefix'], '') + c['class_instance_methods'].append(m) + c['class_properties'] = [] + xml_properties = xml_class.findall("./properties/property") + for xml_property in xml_properties: + p = {} + p['property_name'] = xml_property.get('name') + xml_property_getter = xml_property.find("./getter") + xml_property_setter = xml_property.find("./setter") + if xml_property_getter is not None: + p['getter_name'] = xml_property_getter.get('name').replace(c['class_c_function_prefix'], '') + p['getter_body'] = self.__format_getter_body(xml_property_getter, c) + if xml_property_setter is not None: + p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '') + p['setter_body'] = self.__format_setter_body(xml_property_setter, c) + c['class_properties'].append(p) + self.classes.append(c) + + def __format_method_body(self, method_node, class_): + method = MethodDefinition(method_node, class_) + method.format_local_variables_definition() + method.format_arguments_parsing() + method.format_tracing() + method.format_c_function_call() + method.format_method_result() + return method.body + + def __format_getter_body(self, getter_node, class_): + method = MethodDefinition(getter_node, class_) + method.format_local_variables_definition() + method.format_arguments_parsing() + method.format_tracing() + method.format_c_function_call() + method.format_method_result() + return method.body + + def __format_setter_body(self, setter_node, class_): + method = MethodDefinition(setter_node, class_) + method.format_local_variables_definition() + method.format_tracing() + method.format_setter_value_checking_and_c_function_call() + return method.body def __format_doc_node(self, node): desc = '' diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 8b5a5b6eb..cb6bb27e9 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -51,6 +51,10 @@ typedef struct { {{class_name}} *native_ptr; } pylinphone_{{class_name}}Object; +static {{class_name}} * pylinphone_{{class_name}}_get_native_ptr(pylinphone_{{class_name}}Object *self) { + return self->native_ptr; +} + static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, {{class_name}} *native_ptr) { pylinphone_{{class_name}}Object *self; pylinphone_trace(__FUNCTION__); @@ -64,6 +68,7 @@ static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *ty static PyObject * pylinphone_{{class_name}}_new(PyTypeObject *type, PyObject *args, PyObject *kw) { pylinphone_{{class_name}}Object *self = (pylinphone_{{class_name}}Object *)type->tp_alloc(type, 0); pylinphone_trace(__FUNCTION__); + self->native_ptr = NULL; return (PyObject *)self; } @@ -105,12 +110,11 @@ static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { {{#class_properties}} static PyObject * pylinphone_{{class_name}}_{{getter_name}}(pylinphone_{{class_name}}Object *self, void *closure) { - // TODO: Fill implementation - Py_RETURN_NONE; +{{{getter_body}}} } static int pylinphone_{{class_name}}_{{setter_name}}(pylinphone_{{class_name}}Object *self, PyObject *value, void *closure) { - // TODO: Fill implementation +{{{setter_body}}} return 0; } From 1661ea383a657eecfa6c67c0c3f9c70acb7e39b5 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 14:29:48 +0200 Subject: [PATCH 018/218] Add copyright notice. --- tools/genapixml.py | 16 ++++++++++++++++ tools/python/apixml2python.py | 16 ++++++++++++++++ tools/python/apixml2python/linphone.py | 16 ++++++++++++++++ .../apixml2python/linphone_module.mustache | 18 ++++++++++++++++++ tools/python/setup.py | 16 ++++++++++++++++ 5 files changed, 82 insertions(+) diff --git a/tools/genapixml.py b/tools/genapixml.py index 32f1d0043..85804785c 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -1,5 +1,21 @@ #!/usr/bin/python +# Copyright (C) 2014 Belledonne Communications SARL +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + import argparse import string import sys diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 63f7ed8db..87497ec8e 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -1,5 +1,21 @@ #!/usr/bin/python +# Copyright (C) 2014 Belledonne Communications SARL +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + import argparse import os import pystache diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index e1ed69f8e..d0b748993 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -1,3 +1,19 @@ +# Copyright (C) 2014 Belledonne Communications SARL +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + class MethodDefinition: def __init__(self, method_node, class_): self.body = '' diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index cb6bb27e9..90cc78b80 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -1,3 +1,21 @@ +/* +Copyright (C) 2014 Belledonne Communications SARL + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + #include #include diff --git a/tools/python/setup.py b/tools/python/setup.py index b3c36fc22..bcd5dc419 100644 --- a/tools/python/setup.py +++ b/tools/python/setup.py @@ -1,5 +1,21 @@ #!/usr/bin/python +# Copyright (C) 2014 Belledonne Communications SARL +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + from distutils.core import setup, Extension m = Extension('linphone', From c8d65fc4ecded76c8a5d6a6c9f79b81c2d066074 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 15:28:13 +0200 Subject: [PATCH 019/218] Fix LINPHONE_PLUGIN_DIRS for Windows Phone 8. --- build/wp8/LibLinphone.vcxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/wp8/LibLinphone.vcxproj b/build/wp8/LibLinphone.vcxproj index ed1e7e847..797b2517d 100644 --- a/build/wp8/LibLinphone.vcxproj +++ b/build/wp8/LibLinphone.vcxproj @@ -54,7 +54,7 @@ Level4 $(ProjectDir)..\..\..\belle-sip\include;$(ProjectDir)..\..\oRTP\include;$(ProjectDir)..\..\mediastreamer2\include;$(ProjectDIr)..\..\..\tunnel\include;$(ProjectDir)..\..\coreapi;$(ProjectDir)..\..\include;$(SolutionDir)$(Platform)\$(Configuration)\include;%(AdditionalIncludeDirectories) - __STDC_CONSTANT_MACROS;_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;_USRDLL;WINDOW_NATIVE;_TRUE_TIME;IN_LINPHONE;USE_BELLESIP;TUNNEL_ENABLED;VIDEO_ENABLED;LINPHONE_PACKAGE_NAME="linphone";LINPHONE_VERSION="Devel";LIBLINPHONE_EXPORTS;LINPHONE_PLUGINS_DIR="";UNICODE;_XKEYCHECK_H;%(PreprocessorDefinitions) + __STDC_CONSTANT_MACROS;_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;_USRDLL;WINDOW_NATIVE;_TRUE_TIME;IN_LINPHONE;USE_BELLESIP;TUNNEL_ENABLED;VIDEO_ENABLED;LINPHONE_PACKAGE_NAME="linphone";LINPHONE_VERSION="Devel";LIBLINPHONE_EXPORTS;LINPHONE_PLUGINS_DIR=".";UNICODE;_XKEYCHECK_H;%(PreprocessorDefinitions) Default NotUsing false From 7ec3173462d4738192a22b18dc0cdb6f48c25da7 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Wed, 9 Jul 2014 15:49:49 +0200 Subject: [PATCH 020/218] Detect invalid URI's for remote provisioning --- coreapi/remote_provisioning.c | 31 +++++++++++----------- tester/rcfiles/marie_remote_invalid_uri_rc | 3 +++ tester/remote_provisioning_tester.c | 9 ++++++- 3 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 tester/rcfiles/marie_remote_invalid_uri_rc diff --git a/coreapi/remote_provisioning.c b/coreapi/remote_provisioning.c index 8c57b47ef..abf884e95 100644 --- a/coreapi/remote_provisioning.c +++ b/coreapi/remote_provisioning.c @@ -110,33 +110,32 @@ static void belle_request_process_auth_requested(void *ctx, belle_sip_auth_event } int linphone_remote_provisioning_download_and_apply(LinphoneCore *lc, const char *remote_provisioning_uri) { - const char* file_path = strstr(remote_provisioning_uri, "file://"); - if( file_path == remote_provisioning_uri ){ - // We allow for 'local remote-provisioning' in case the file is to be opened from the hard drive - file_path += strlen("file://"); + belle_generic_uri_t *uri=belle_generic_uri_parse(remote_provisioning_uri); + const char* scheme = uri ? belle_generic_uri_get_scheme(uri) : NULL; + + if( scheme && (strcmp(scheme,"file") == 0) ){ + // We allow for 'local remote-provisioning' in case the file is to be opened from the hard drive. + const char* file_path = remote_provisioning_uri + strlen("file://"); // skip scheme return linphone_remote_provisioning_load_file(lc, file_path); - } else { - belle_generic_uri_t *uri=belle_generic_uri_parse(remote_provisioning_uri); + + } else if( scheme && strncmp(scheme, "http", 4) == 0 ) { belle_http_request_listener_callbacks_t belle_request_listener={0}; belle_http_request_listener_t *listener; belle_http_request_t *request; - + belle_request_listener.process_response=belle_request_process_response_event; belle_request_listener.process_auth_requested=belle_request_process_auth_requested; belle_request_listener.process_io_error=belle_request_process_io_error; belle_request_listener.process_timeout=belle_request_process_timeout; - - listener = belle_http_request_listener_create_from_callbacks(&belle_request_listener, lc); - - if (uri==NULL) { - belle_sip_error("Invalid provisioning URI [%s]",remote_provisioning_uri); - return -1; - } + listener = belle_http_request_listener_create_from_callbacks(&belle_request_listener, lc); + request=belle_http_request_create("GET",uri, NULL); - belle_http_provider_send_request(lc->http_provider, request, listener); - return 0; + return belle_http_provider_send_request(lc->http_provider, request, listener); + } else { + ms_error("Invalid provisioning URI [%s] (missing scheme?)",remote_provisioning_uri); + return -1; } } diff --git a/tester/rcfiles/marie_remote_invalid_uri_rc b/tester/rcfiles/marie_remote_invalid_uri_rc new file mode 100644 index 000000000..972c0a0c3 --- /dev/null +++ b/tester/rcfiles/marie_remote_invalid_uri_rc @@ -0,0 +1,3 @@ +[misc] +config-uri=/tmp/lol + diff --git a/tester/remote_provisioning_tester.c b/tester/remote_provisioning_tester.c index 21e9a3f6a..6864b573a 100644 --- a/tester/remote_provisioning_tester.c +++ b/tester/remote_provisioning_tester.c @@ -77,6 +77,12 @@ static void remote_provisioning_invalid(void) { linphone_core_manager_destroy(marie); } +static void remote_provisioning_invalid_uri(void) { + LinphoneCoreManager* marie = linphone_core_manager_new2("marie_remote_invalid_uri_rc", FALSE); + CU_ASSERT_TRUE(wait_for(marie->lc,NULL,&marie->stat.number_of_LinphoneConfiguringFailed,1)); + linphone_core_manager_destroy(marie); +} + static void remote_provisioning_default_values(void) { LinphoneProxyConfig *lpc; LinphoneCoreManager* marie = linphone_core_manager_new2("marie_remote_default_values_rc", FALSE); @@ -120,7 +126,8 @@ test_t remote_provisioning_tests[] = { { "Remote provisioning invalid", remote_provisioning_invalid }, { "Remote provisioning transient successful", remote_provisioning_transient }, { "Remote provisioning default values", remote_provisioning_default_values }, - { "Remote provisioning from file", remote_provisioning_file } + { "Remote provisioning from file", remote_provisioning_file }, + { "Remote provisioning invalid URI", remote_provisioning_invalid_uri } }; test_suite_t remote_provisioning_test_suite = { From a200ccbc766328bfdf78dd20ea4b7ac6c04e30fa Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 16:02:01 +0200 Subject: [PATCH 021/218] Check validity of the native pointer in the python wrapper. --- tools/python/apixml2python/linphone.py | 41 +++++++++++++++++++++----- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index d0b748993..1de8e97a2 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -23,10 +23,12 @@ class MethodDefinition: self.return_type = 'void' self.method_node = method_node self.class_ = class_ + self.self_arg = None self.xml_method_return = self.method_node.find('./return') self.xml_method_args = self.method_node.findall('./arguments/argument') self.method_type = self.method_node.tag if self.method_type != 'classmethod': + self.self_arg = self.xml_method_args[0] self.xml_method_args = self.xml_method_args[1:] def format_local_variables_definition(self): @@ -37,36 +39,59 @@ class MethodDefinition: if self.build_value_format == 'O': self.body += "\tPyObject * pyresult;\n" self.body += "\tPyObject * pyret;\n" + if self.self_arg is not None: + self.body += "\t" + self.self_arg.get('type') + "native_ptr;\n" for xml_method_arg in self.xml_method_args: self.parse_tuple_format += self.__ctype_to_python_format(xml_method_arg.get('type')) self.body += "\t" + xml_method_arg.get('type') + " " + xml_method_arg.get('name') + ";\n" self.arg_names.append(xml_method_arg.get('name')) + def format_native_pointer_checking(self, return_int): + self.body += "\tnative_ptr = pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self);\n" + self.body += "\tif(native_ptr == NULL) {\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid " + self.class_['class_name'] + " instance\");\n" + if return_int: + self.body += "\t\treturn -1;\n" + else: + self.body += "\t\treturn NULL;\n" + self.body += "\t}\n" + def format_arguments_parsing(self): + if self.self_arg is not None: + self.format_native_pointer_checking(False) if len(self.arg_names) > 0: - self.body += "\n\tif (!PyArg_ParseTuple(args, \"" + self.parse_tuple_format + "\"" + self.body += "\tif (!PyArg_ParseTuple(args, \"" + self.parse_tuple_format + "\"" self.body += ', ' + ', '.join(map(lambda a: '&' + a, self.arg_names)) - self.body += ")) {\n\t\treturn NULL;\n\t}\n\n" + self.body += ")) {\n\t\treturn NULL;\n\t}\n" def format_setter_value_checking_and_c_function_call(self): - self.body += "\n\tif (value == NULL) {\n" + # Check the native pointer + self.format_native_pointer_checking(True) + self.body += "\tnative_ptr = pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self);\n" + self.body += "\tif(native_ptr == NULL) {\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid " + self.class_['class_name'] + " instance\");\n" + self.body += "\t\treturn -1;\n" + self.body += "\t}\n" + # Check that the value exists + self.body += "\tif (value == NULL) {\n" self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Cannot delete this attribute\");\n" self.body += "\t\treturn -1;\n" self.body += "\t}\n" + # Check the value basic_type, checkfunc, convertfunc = self.__ctype_to_python_type(self.xml_method_args[0].get('type')) - self.body += "\n\tif (!" + checkfunc + "(value)) {\n" + self.body += "\tif (!" + checkfunc + "(value)) {\n" self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"This attribute value must be a " + basic_type + "\");\n" self.body += "\t\treturn -1;\n" - self.body += "\t}\n\n" + self.body += "\t}\n" + # Call the C function if convertfunc is None: pass # TODO else: self.body += "\t" + self.arg_names[0] + " = (" + self.xml_method_args[0].get('type') + ")" + convertfunc + "(value);\n" - self.body += "\t" + self.method_node.get('name') + "(" - self.body += "pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self), " + self.arg_names[0] + ");\n" + self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + self.arg_names[0] + ");" def format_tracing(self): - self.body += "\tpylinphone_trace(__FUNCTION__);\n\n" + self.body += "\tpylinphone_trace(__FUNCTION__);\n" def format_c_function_call(self): self.body += "\t" From 6c0efc3ebc2e259b6abbe56cf381ae7981f8cd9c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 16:15:07 +0200 Subject: [PATCH 022/218] Strip useless 'Linphone' prefix in class and enum names in the Python wrapper. --- tools/python/apixml2python/linphone.py | 14 +++++++++++--- .../python/apixml2python/linphone_module.mustache | 8 ++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 1de8e97a2..57127d815 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -199,20 +199,22 @@ class LinphoneModule(object): xml_enums = tree.findall("./enums/enum") for xml_enum in xml_enums: e = {} - e['enum_name'] = xml_enum.get('name') + e['enum_name'] = self.__strip_leading_linphone(xml_enum.get('name')) e['enum_doc'] = self.__format_doc(xml_enum.find('briefdescription'), xml_enum.find('detaileddescription')) e['enum_values'] = [] xml_enum_values = xml_enum.findall("./values/value") for xml_enum_value in xml_enum_values: v = {} - v['enum_value_name'] = xml_enum_value.get('name') + v['enum_value_cname'] = xml_enum_value.get('name') + v['enum_value_name'] = self.__strip_leading_linphone(v['enum_value_cname']) e['enum_values'].append(v) self.enums.append(e) self.classes = [] xml_classes = tree.findall("./classes/class") for xml_class in xml_classes: c = {} - c['class_name'] = xml_class.get('name') + c['class_cname'] = xml_class.get('name') + c['class_name'] = self.__strip_leading_linphone(c['class_cname']) c['class_c_function_prefix'] = xml_class.get('cfunctionprefix') c['class_doc'] = self.__format_doc(xml_class.find('briefdescription'), xml_class.find('detaileddescription')) c['class_type_methods'] = [] @@ -244,6 +246,12 @@ class LinphoneModule(object): c['class_properties'].append(p) self.classes.append(c) + def __strip_leading_linphone(self, s): + if s.lower().startswith('linphone'): + return s[8:] + else: + return s + def __format_method_body(self, method_node, class_): method = MethodDefinition(method_node, class_) method.format_local_variables_definition() diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 90cc78b80..682934031 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -66,14 +66,14 @@ static PyTypeObject pylinphone_{{class_name}}Type; typedef struct { PyObject_HEAD - {{class_name}} *native_ptr; + {{class_cname}} *native_ptr; } pylinphone_{{class_name}}Object; -static {{class_name}} * pylinphone_{{class_name}}_get_native_ptr(pylinphone_{{class_name}}Object *self) { +static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(pylinphone_{{class_name}}Object *self) { return self->native_ptr; } -static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, {{class_name}} *native_ptr) { +static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, {{class_cname}} *native_ptr) { pylinphone_{{class_name}}Object *self; pylinphone_trace(__FUNCTION__); if (native_ptr == NULL) Py_RETURN_NONE; @@ -217,7 +217,7 @@ PyMODINIT_FUNC initlinphone(void) { if (menum == NULL) return; if (PyModule_AddObject(m, "{{enum_name}}", menum) < 0) return; {{#enum_values}} - if (PyModule_AddIntConstant(menum, "{{enum_value_name}}", {{enum_value_name}}) < 0) return; + if (PyModule_AddIntConstant(menum, "{{enum_value_name}}", {{enum_value_cname}}) < 0) return; {{/enum_values}} {{/enums}} From 62d58437ea902afc13d17aa198d24428b286bb14 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 17:26:05 +0200 Subject: [PATCH 023/218] Implement instance methods and add a blacklist of C functions that must not be wrapped. --- tools/python/apixml2python.py | 11 ++++++++++- tools/python/apixml2python/linphone.py | 17 ++++++++++++----- .../apixml2python/linphone_module.mustache | 13 +++++-------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 87497ec8e..f83084f54 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -26,10 +26,19 @@ sys.path.append(os.path.realpath(__file__)) from apixml2python.linphone import LinphoneModule +blacklisted_functions = [ + 'linphone_auth_info_write_config', + 'lp_config_for_each_entry', + 'lp_config_for_each_section', + 'lp_config_get_range', + 'lp_config_load_dict_to_section', + 'lp_config_section_to_dict' +] + def generate(apixmlfile): tree = ET.parse(apixmlfile) renderer = pystache.Renderer() - m = LinphoneModule(tree) + m = LinphoneModule(tree, blacklisted_functions) f = open("linphone.c", "w") f.write(renderer.render(m)) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 57127d815..eca991ff8 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -98,8 +98,8 @@ class MethodDefinition: if self.return_type != 'void': self.body += "cresult = " self.body += self.method_node.get('name') + "(" - if self.method_type != 'classmethod': - self.body += "pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self)" + if self.self_arg is not None: + self.body += "native_ptr" if len(self.arg_names) > 0: self.body += ', ' self.body += ', '.join(self.arg_names) + ");\n" @@ -194,7 +194,7 @@ class MethodDefinition: class LinphoneModule(object): - def __init__(self, tree): + def __init__(self, tree, blacklisted_functions): self.enums = [] xml_enums = tree.findall("./enums/enum") for xml_enum in xml_enums: @@ -220,15 +220,22 @@ class LinphoneModule(object): c['class_type_methods'] = [] xml_type_methods = xml_class.findall("./classmethods/classmethod") for xml_type_method in xml_type_methods: + method_name = xml_type_method.get('name') + if method_name in blacklisted_functions: + continue m = {} - m['method_name'] = xml_type_method.get('name').replace(c['class_c_function_prefix'], '') + m['method_name'] = method_name.replace(c['class_c_function_prefix'], '') m['method_body'] = self.__format_method_body(xml_type_method, c) c['class_type_methods'].append(m) c['class_instance_methods'] = [] xml_instance_methods = xml_class.findall("./instancemethods/instancemethod") for xml_instance_method in xml_instance_methods: + method_name = xml_instance_method.get('name') + if method_name in blacklisted_functions: + continue m = {} - m['method_name'] = xml_instance_method.get('name').replace(c['class_c_function_prefix'], '') + m['method_name'] = method_name.replace(c['class_c_function_prefix'], '') + m['method_body'] = self.__format_method_body(xml_instance_method, c) c['class_instance_methods'].append(m) c['class_properties'] = [] xml_properties = xml_class.findall("./properties/property") diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 682934031..915a2759b 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -57,9 +57,7 @@ static void pylinphone_log(const char *level, const char *fmt) { {{#classes}} - static PyTypeObject pylinphone_{{class_name}}Type; - {{/classes}} {{#classes}} @@ -69,8 +67,8 @@ typedef struct { {{class_cname}} *native_ptr; } pylinphone_{{class_name}}Object; -static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(pylinphone_{{class_name}}Object *self) { - return self->native_ptr; +static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(PyObject *self) { + return ((pylinphone_{{class_name}}Object *)self)->native_ptr; } static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, {{class_cname}} *native_ptr) { @@ -106,8 +104,7 @@ static PyObject * pylinphone_{{class_name}}_class_method_{{method_name}}(PyObjec {{#class_instance_methods}} static PyObject * pylinphone_{{class_name}}_instance_method_{{method_name}}(PyObject *self, PyObject *args) { - // TODO: Fill implementation - Py_RETURN_NONE; +{{{method_body}}} } {{/class_instance_methods}} @@ -127,11 +124,11 @@ static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { {{#class_properties}} -static PyObject * pylinphone_{{class_name}}_{{getter_name}}(pylinphone_{{class_name}}Object *self, void *closure) { +static PyObject * pylinphone_{{class_name}}_{{getter_name}}(PyObject *self, void *closure) { {{{getter_body}}} } -static int pylinphone_{{class_name}}_{{setter_name}}(pylinphone_{{class_name}}Object *self, PyObject *value, void *closure) { +static int pylinphone_{{class_name}}_{{setter_name}}(PyObject *self, PyObject *value, void *closure) { {{{setter_body}}} return 0; } From 19e056b1913b1abb24f865f526c7e49fcc1a7f05 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 9 Jul 2014 17:28:50 +0200 Subject: [PATCH 024/218] Add .gitignore in python directory. --- tools/python/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tools/python/.gitignore diff --git a/tools/python/.gitignore b/tools/python/.gitignore new file mode 100644 index 000000000..701f19514 --- /dev/null +++ b/tools/python/.gitignore @@ -0,0 +1,4 @@ +*.pyc +build +linphone.c +.kdev* From 6ef708d85fe7c552e5b0e5de31b3b2362f2e6822 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 10:55:54 +0200 Subject: [PATCH 025/218] Do not include non-documented functions to the API. --- tools/genapixml.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/genapixml.py b/tools/genapixml.py index 85804785c..e60ac3a5d 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -503,6 +503,8 @@ class Project: f.deprecated = True f.briefDescription = ''.join(node.find('./briefdescription').itertext()).strip() f.detailedDescription = self.__cleanDescription(node.find('./detaileddescription')) + if f.briefDescription == '' and ''.join(f.detailedDescription.itertext()).strip() == '': + return None locationNode = node.find('./location') if locationNode is not None: f.location = locationNode.get('file') From 867a5a90f632994e4bcaa57cb09afe7eb4e43162 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 11:02:05 +0200 Subject: [PATCH 026/218] Always include the deprecated attribute in the XML file generated by the genapixml tool. --- tools/genapixml.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tools/genapixml.py b/tools/genapixml.py index e60ac3a5d..9780e6957 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -554,11 +554,9 @@ class Generator: self.__outputfile = outputfile def __generateEnum(self, cenum, enumsNode): - enumNodeAttributes = { 'name' : cenum.name } + enumNodeAttributes = { 'name' : cenum.name, 'deprecated' : str(cenum.deprecated).lower() } if cenum.associatedTypedef is not None: enumNodeAttributes['name'] = cenum.associatedTypedef.name - if cenum.deprecated: - enumNodeAttributes['deprecated'] = 'true' enumNode = ET.SubElement(enumsNode, 'enum', enumNodeAttributes) if cenum.briefDescription != '': enumBriefDescriptionNode = ET.SubElement(enumNode, 'briefdescription') @@ -567,9 +565,7 @@ class Generator: if len(cenum.values) > 0: enumValuesNode = ET.SubElement(enumNode, 'values') for value in cenum.values: - enumValuesNodeAttributes = { 'name' : value.name } - if value.deprecated: - enumValuesNodeAttributes['deprecated'] = 'true' + enumValuesNodeAttributes = { 'name' : value.name, 'deprecated' : str(value.deprecated).lower() } valueNode = ET.SubElement(enumValuesNode, 'value', enumValuesNodeAttributes) if value.briefDescription != '': valueBriefDescriptionNode = ET.SubElement(valueNode, 'briefdescription') @@ -577,11 +573,9 @@ class Generator: valueNode.append(value.detailedDescription) def __generateFunction(self, parentNode, nodeName, f): - functionAttributes = { 'name' : f.name } + functionAttributes = { 'name' : f.name, 'deprecated' : str(f.deprecated).lower() } if f.location is not None: functionAttributes['location'] = f.location - if f.deprecated: - functionAttributes['deprecated'] = 'true' functionNode = ET.SubElement(parentNode, nodeName, functionAttributes) returnValueAttributes = { 'type' : f.returnArgument.completeType } returnValueNode = ET.SubElement(functionNode, 'return', returnValueAttributes) @@ -599,9 +593,7 @@ class Generator: functionNode.append(f.detailedDescription) def __generateClass(self, cclass, classesNode): - classNodeAttributes = { 'name' : cclass.name, 'cfunctionprefix' : cclass.cFunctionPrefix } - if cclass.deprecated: - classNodeAttributes['deprecated'] = 'true' + classNodeAttributes = { 'name' : cclass.name, 'cfunctionprefix' : cclass.cFunctionPrefix, 'deprecated' : str(cclass.deprecated).lower() } classNode = ET.SubElement(classesNode, 'class', classNodeAttributes) if len(cclass.events) > 0: eventsNode = ET.SubElement(classNode, 'events') From 03a7fe29fd5dddef5417aed55b4ce51ab1d6aa4f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 11:11:52 +0200 Subject: [PATCH 027/218] Add attributes for classes in the XML file generated by the genapixml tool to tell whether a class is refcountable and/or destroyable. --- tools/genapixml.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tools/genapixml.py b/tools/genapixml.py index 9780e6957..92b503739 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -593,7 +593,30 @@ class Generator: functionNode.append(f.detailedDescription) def __generateClass(self, cclass, classesNode): - classNodeAttributes = { 'name' : cclass.name, 'cfunctionprefix' : cclass.cFunctionPrefix, 'deprecated' : str(cclass.deprecated).lower() } + has_ref_method = False + has_unref_method = False + has_destroy_method = False + for methodname in cclass.instanceMethods: + methodname_without_prefix = methodname.replace(cclass.cFunctionPrefix, '') + if methodname_without_prefix == 'ref': + has_ref_method = True + elif methodname_without_prefix == 'unref': + has_unref_method = True + elif methodname_without_prefix == 'destroy': + has_destroy_method = True + refcountable = False + destroyable = False + if has_ref_method and has_unref_method: + refcountable = True + if has_destroy_method: + destroyable = True + classNodeAttributes = { + 'name' : cclass.name, + 'cfunctionprefix' : cclass.cFunctionPrefix, + 'deprecated' : str(cclass.deprecated).lower(), + 'refcountable' : str(refcountable).lower(), + 'destroyable' : str(destroyable).lower() + } classNode = ET.SubElement(classesNode, 'class', classNodeAttributes) if len(cclass.events) > 0: eventsNode = ET.SubElement(classNode, 'events') From c228387471313c39f85c873dac3f89e8c01fcc5a Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 11:16:23 +0200 Subject: [PATCH 028/218] Do not include empty classes to the XML file generated by the genapixml tool. --- tools/genapixml.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/genapixml.py b/tools/genapixml.py index 92b503739..c63ccd28c 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -593,6 +593,11 @@ class Generator: functionNode.append(f.detailedDescription) def __generateClass(self, cclass, classesNode): + # Do not include classes that contain nothing + if len(cclass.events) == 0 and len(cclass.classMethods) == 0 and \ + len(cclass.instanceMethods) == 0 and len(cclass.properties) == 0: + return + # Check the capabilities of the class has_ref_method = False has_unref_method = False has_destroy_method = False @@ -617,6 +622,7 @@ class Generator: 'refcountable' : str(refcountable).lower(), 'destroyable' : str(destroyable).lower() } + # Generate the XML node for the class classNode = ET.SubElement(classesNode, 'class', classNodeAttributes) if len(cclass.events) > 0: eventsNode = ET.SubElement(classNode, 'events') From eabe6cabb7a90a3ead11980f74532f0b6ab3ed1b Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 11:34:11 +0200 Subject: [PATCH 029/218] Complete arguments type checking in the genapixml tool. --- tools/genapixml.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/genapixml.py b/tools/genapixml.py index c63ccd28c..49ac079da 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -71,7 +71,7 @@ class CArgument(CObject): def __init__(self, t, name = '', enums = [], structs = []): CObject.__init__(self, name) self.description = None - keywords = [ 'const', 'struct', 'enum', 'signed', 'unsigned', '*' ] + keywords = [ 'const', 'struct', 'enum', 'signed', 'unsigned', 'short', 'long', '*' ] fullySplittedType = [] splittedType = t.strip().split(' ') for s in splittedType: @@ -84,9 +84,9 @@ class CArgument(CObject): fullySplittedType.append('*') else: fullySplittedType.append(s) - self.completeType = ' '.join(fullySplittedType) isStruct = False isEnum = False + self.ctype = 'int' # Default to int so that the result is correct eg. for 'unsigned short' for s in fullySplittedType: if not s in keywords: self.ctype = s @@ -102,6 +102,12 @@ class CArgument(CObject): for e in enums: if e.associatedTypedef is not None: self.ctype = e.associatedTypedef.name + if self.ctype == 'int' and 'int' not in fullySplittedType: + if fullySplittedType[-1] == '*': + fullySplittedType.insert(-1, 'int') + else: + fullySplittedType.append('int') + self.completeType = ' '.join(fullySplittedType) def __str__(self): return self.completeType + " " + self.name From 76650542353e82d18fa74e8a52f93f7514c855b2 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 11:34:54 +0200 Subject: [PATCH 030/218] Remove function from blacklist. --- tools/python/apixml2python.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index f83084f54..53ce49119 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -27,7 +27,6 @@ from apixml2python.linphone import LinphoneModule blacklisted_functions = [ - 'linphone_auth_info_write_config', 'lp_config_for_each_entry', 'lp_config_for_each_section', 'lp_config_get_range', From 22e7418bf4cd664e0ec8a2aca719a29982abda8e Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 11:48:07 +0200 Subject: [PATCH 031/218] Do not generate Python wrapper for deprecated enums, classes and methods. --- tools/python/apixml2python/linphone.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index eca991ff8..b3a3bff77 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -198,12 +198,16 @@ class LinphoneModule(object): self.enums = [] xml_enums = tree.findall("./enums/enum") for xml_enum in xml_enums: + if xml_enum.get('deprecated') == 'true': + continue e = {} e['enum_name'] = self.__strip_leading_linphone(xml_enum.get('name')) e['enum_doc'] = self.__format_doc(xml_enum.find('briefdescription'), xml_enum.find('detaileddescription')) e['enum_values'] = [] xml_enum_values = xml_enum.findall("./values/value") for xml_enum_value in xml_enum_values: + if xml_enum_value.get('deprecated') == 'true': + continue v = {} v['enum_value_cname'] = xml_enum_value.get('name') v['enum_value_name'] = self.__strip_leading_linphone(v['enum_value_cname']) @@ -212,6 +216,8 @@ class LinphoneModule(object): self.classes = [] xml_classes = tree.findall("./classes/class") for xml_class in xml_classes: + if xml_class.get('deprecated') == 'true': + continue c = {} c['class_cname'] = xml_class.get('name') c['class_name'] = self.__strip_leading_linphone(c['class_cname']) @@ -220,6 +226,8 @@ class LinphoneModule(object): c['class_type_methods'] = [] xml_type_methods = xml_class.findall("./classmethods/classmethod") for xml_type_method in xml_type_methods: + if xml_type_method.get('deprecated') == 'true': + continue method_name = xml_type_method.get('name') if method_name in blacklisted_functions: continue @@ -230,6 +238,8 @@ class LinphoneModule(object): c['class_instance_methods'] = [] xml_instance_methods = xml_class.findall("./instancemethods/instancemethod") for xml_instance_method in xml_instance_methods: + if xml_instance_method.get('deprecated') == 'true': + continue method_name = xml_instance_method.get('name') if method_name in blacklisted_functions: continue From 7ead063d7f59872fbe9ccbc508ecd2f916e76984 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 12:02:35 +0200 Subject: [PATCH 032/218] Do not generate Python wrapper for *_destroy(), *_ref() and *_unref() functions. --- tools/python/apixml2python/linphone.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index b3a3bff77..6bdf1cdaf 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -195,6 +195,7 @@ class MethodDefinition: class LinphoneModule(object): def __init__(self, tree, blacklisted_functions): + self.internal_instance_method_names = ['destroy', 'ref', 'unref'] self.enums = [] xml_enums = tree.findall("./enums/enum") for xml_enum in xml_enums: @@ -243,8 +244,11 @@ class LinphoneModule(object): method_name = xml_instance_method.get('name') if method_name in blacklisted_functions: continue + method_name = method_name.replace(c['class_c_function_prefix'], '') + if method_name in self.internal_instance_method_names: + continue m = {} - m['method_name'] = method_name.replace(c['class_c_function_prefix'], '') + m['method_name'] = method_name m['method_body'] = self.__format_method_body(xml_instance_method, c) c['class_instance_methods'].append(m) c['class_properties'] = [] From 1953750ec66f10ced9c24db4c27b0bf4b4b8b17c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 12:19:07 +0200 Subject: [PATCH 033/218] Improve documentation of LinphoneAuthInfo. --- coreapi/authentication.c | 39 -------------- coreapi/linphonecore.h | 109 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 44 deletions(-) diff --git a/coreapi/authentication.c b/coreapi/authentication.c index 4e32d5be3..d763f8902 100644 --- a/coreapi/authentication.c +++ b/coreapi/authentication.c @@ -31,20 +31,6 @@ * @{ **/ -/** - * Create a LinphoneAuthInfo object with supplied information. - * The object can be created empty, that is with all arguments set to NULL. - * Username, userid, password, realm and domain can be set later using specific methods. - * At the end, username and passwd (or ha1) are required. - * @param username the username that needs to be authenticated - * @param userid the userid used for authenticating (use NULL if you don't know what it is) - * @param passwd the password in clear text - * @param ha1 the ha1-encrypted password if password is not given in clear text. - * @param realm the authentication domain (which can be larger than the sip domain. Unfortunately many SIP servers don't use this parameter. - * @param domain the SIP domain for which this authentication information is valid, if it has to be restricted for a single SIP domain. - * @return a #LinphoneAuthInfo. linphone_auth_info_destroy() must be used to destroy it when no longer needed. The LinphoneCore makes a copy of LinphoneAuthInfo - * passed through linphone_core_add_auth_info(). -**/ LinphoneAuthInfo *linphone_auth_info_new(const char *username, const char *userid, const char *passwd, const char *ha1, const char *realm, const char *domain){ LinphoneAuthInfo *obj=ms_new0(LinphoneAuthInfo,1); if (username!=NULL && (strlen(username)>0) ) obj->username=ms_strdup(username); @@ -67,16 +53,10 @@ LinphoneAuthInfo *linphone_auth_info_clone(const LinphoneAuthInfo *ai){ return obj; } -/** - * Returns username. -**/ const char *linphone_auth_info_get_username(const LinphoneAuthInfo *i){ return i->username; } -/** - * Returns password. -**/ const char *linphone_auth_info_get_passwd(const LinphoneAuthInfo *i){ return i->passwd; } @@ -97,9 +77,6 @@ const char *linphone_auth_info_get_ha1(const LinphoneAuthInfo *i){ return i->ha1; } -/** - * Sets the password. -**/ void linphone_auth_info_set_passwd(LinphoneAuthInfo *info, const char *passwd){ if (info->passwd!=NULL) { ms_free(info->passwd); @@ -108,9 +85,6 @@ void linphone_auth_info_set_passwd(LinphoneAuthInfo *info, const char *passwd){ if (passwd!=NULL && (strlen(passwd)>0)) info->passwd=ms_strdup(passwd); } -/** - * Sets the username. -**/ void linphone_auth_info_set_username(LinphoneAuthInfo *info, const char *username){ if (info->username){ ms_free(info->username); @@ -119,9 +93,6 @@ void linphone_auth_info_set_username(LinphoneAuthInfo *info, const char *usernam if (username && strlen(username)>0) info->username=ms_strdup(username); } -/** - * Sets userid. -**/ void linphone_auth_info_set_userid(LinphoneAuthInfo *info, const char *userid){ if (info->userid){ ms_free(info->userid); @@ -130,9 +101,6 @@ void linphone_auth_info_set_userid(LinphoneAuthInfo *info, const char *userid){ if (userid && strlen(userid)>0) info->userid=ms_strdup(userid); } -/** - * Set realm. -**/ void linphone_auth_info_set_realm(LinphoneAuthInfo *info, const char *realm){ if (info->realm){ ms_free(info->realm); @@ -141,10 +109,6 @@ void linphone_auth_info_set_realm(LinphoneAuthInfo *info, const char *realm){ if (realm && strlen(realm)>0) info->realm=ms_strdup(realm); } -/** - * Set domain for which this authentication is valid. This should not be necessary because realm is supposed to be unique and sufficient. - * However, many SIP servers don't set realm correctly, then domain has to be used to distinguish between several SIP account bearing the same username. -**/ void linphone_auth_info_set_domain(LinphoneAuthInfo *info, const char *domain){ if (info->domain){ ms_free(info->domain); @@ -153,9 +117,6 @@ void linphone_auth_info_set_domain(LinphoneAuthInfo *info, const char *domain){ if (domain && strlen(domain)>0) info->domain=ms_strdup(domain); } -/** - * Sets ha1. -**/ void linphone_auth_info_set_ha1(LinphoneAuthInfo *info, const char *ha1){ if (info->ha1){ ms_free(info->ha1); diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 1db2b29c0..e70104405 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1067,7 +1067,8 @@ void linphone_account_creator_destroy(LinphoneAccountCreator *obj); struct _LinphoneAuthInfo; /** - * @ingroup authentication + * @addtogroup authentication + * @{ * Object holding authentication information. * * @note The object's fields should not be accessed directly. Prefer using @@ -1092,33 +1093,131 @@ struct _LinphoneAuthInfo; **/ typedef struct _LinphoneAuthInfo LinphoneAuthInfo; +/** + * Creates a #LinphoneAuthInfo object with supplied information. + * The object can be created empty, that is with all arguments set to NULL. + * Username, userid, password, realm and domain can be set later using specific methods. + * At the end, username and passwd (or ha1) are required. + * @param username The username that needs to be authenticated + * @param userid The userid used for authenticating (use NULL if you don't know what it is) + * @param passwd The password in clear text + * @param ha1 The ha1-encrypted password if password is not given in clear text. + * @param realm The authentication domain (which can be larger than the sip domain. Unfortunately many SIP servers don't use this parameter. + * @param domain The SIP domain for which this authentication information is valid, if it has to be restricted for a single SIP domain. + * @return A #LinphoneAuthInfo object. linphone_auth_info_destroy() must be used to destroy it when no longer needed. The LinphoneCore makes a copy of LinphoneAuthInfo + * passed through linphone_core_add_auth_info(). +**/ LINPHONE_PUBLIC LinphoneAuthInfo *linphone_auth_info_new(const char *username, const char *userid, - const char *passwd, const char *ha1,const char *realm, const char *domain); + const char *passwd, const char *ha1,const char *realm, const char *domain); + /** * @addtogroup authentication - * Instanciate a new auth info with values from source - * @param source auth info object to be cloned - * @return newly created auth info + * Instantiates a new auth info with values from source. + * @param[in] source The #LinphoneAuthInfo object to be cloned + * @return The newly created #LinphoneAuthInfo object. */ LINPHONE_PUBLIC LinphoneAuthInfo *linphone_auth_info_clone(const LinphoneAuthInfo* source); + +/** + * Sets the password. + * @param[in] info The #LinphoneAuthInfo object + * @param[in] passwd The password. +**/ LINPHONE_PUBLIC void linphone_auth_info_set_passwd(LinphoneAuthInfo *info, const char *passwd); + +/** + * Sets the username. + * @param[in] info The #LinphoneAuthInfo object + * @param[in] username The username. +**/ LINPHONE_PUBLIC void linphone_auth_info_set_username(LinphoneAuthInfo *info, const char *username); + +/** + * Sets the userid. + * @param[in] info The #LinphoneAuthInfo object + * @param[in] userid The userid. +**/ LINPHONE_PUBLIC void linphone_auth_info_set_userid(LinphoneAuthInfo *info, const char *userid); + +/** + * Sets the realm. + * @param[in] info The #LinphoneAuthInfo object + * @param[in] realm The realm. +**/ LINPHONE_PUBLIC void linphone_auth_info_set_realm(LinphoneAuthInfo *info, const char *realm); + +/** + * Sets the domain for which this authentication is valid. + * @param[in] info The #LinphoneAuthInfo object + * @param[in] domain The domain. + * This should not be necessary because realm is supposed to be unique and sufficient. + * However, many SIP servers don't set realm correctly, then domain has to be used to distinguish between several SIP account bearing the same username. +**/ LINPHONE_PUBLIC void linphone_auth_info_set_domain(LinphoneAuthInfo *info, const char *domain); + +/** + * Sets the ha1. + * @param[in] info The #LinphoneAuthInfo object + * @param[in] ha1 The ha1. +**/ LINPHONE_PUBLIC void linphone_auth_info_set_ha1(LinphoneAuthInfo *info, const char *ha1); +/** + * Gets the username. + * + * @params[in] i The #LinphoneAuthInfo object + * @return The username. + */ LINPHONE_PUBLIC const char *linphone_auth_info_get_username(const LinphoneAuthInfo *i); + +/** + * Gets the password. + * + * @params[in] i The #LinphoneAuthInfo object + * @return The password. + */ LINPHONE_PUBLIC const char *linphone_auth_info_get_passwd(const LinphoneAuthInfo *i); + +/** + * Gets the userid. + * + * @params[in] i The #LinphoneAuthInfo object + * @return The userid. + */ LINPHONE_PUBLIC const char *linphone_auth_info_get_userid(const LinphoneAuthInfo *i); + +/** + * Gets the realm. + * + * @params[in] i The #LinphoneAuthInfo object + * @return The realm. + */ LINPHONE_PUBLIC const char *linphone_auth_info_get_realm(const LinphoneAuthInfo *i); + +/** + * Gets the domain. + * + * @params[in] i The #LinphoneAuthInfo object + * @return The domain. + */ LINPHONE_PUBLIC const char *linphone_auth_info_get_domain(const LinphoneAuthInfo *i); + +/** + * Gets the ha1. + * + * @params[in] i The #LinphoneAuthInfo object + * @return The ha1. + */ LINPHONE_PUBLIC const char *linphone_auth_info_get_ha1(const LinphoneAuthInfo *i); /* you don't need those function*/ LINPHONE_PUBLIC void linphone_auth_info_destroy(LinphoneAuthInfo *info); LINPHONE_PUBLIC LinphoneAuthInfo * linphone_auth_info_new_from_config_file(LpConfig *config, int pos); +/** + * @} + */ + struct _LinphoneChatRoom; /** From 214521c5a87048b81bb7f6ae278782db5ab4e124 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 12:22:31 +0200 Subject: [PATCH 034/218] Fix typos in previous commit. --- coreapi/linphonecore.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index e70104405..fda0ccc7c 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1165,50 +1165,50 @@ LINPHONE_PUBLIC void linphone_auth_info_set_ha1(LinphoneAuthInfo *info, const ch /** * Gets the username. * - * @params[in] i The #LinphoneAuthInfo object + * @param[in] info The #LinphoneAuthInfo object * @return The username. */ -LINPHONE_PUBLIC const char *linphone_auth_info_get_username(const LinphoneAuthInfo *i); +LINPHONE_PUBLIC const char *linphone_auth_info_get_username(const LinphoneAuthInfo *info); /** * Gets the password. * - * @params[in] i The #LinphoneAuthInfo object + * @param[in] info The #LinphoneAuthInfo object * @return The password. */ -LINPHONE_PUBLIC const char *linphone_auth_info_get_passwd(const LinphoneAuthInfo *i); +LINPHONE_PUBLIC const char *linphone_auth_info_get_passwd(const LinphoneAuthInfo *info); /** * Gets the userid. * - * @params[in] i The #LinphoneAuthInfo object + * @param[in] info The #LinphoneAuthInfo object * @return The userid. */ -LINPHONE_PUBLIC const char *linphone_auth_info_get_userid(const LinphoneAuthInfo *i); +LINPHONE_PUBLIC const char *linphone_auth_info_get_userid(const LinphoneAuthInfo *info); /** * Gets the realm. * - * @params[in] i The #LinphoneAuthInfo object + * @param[in] info The #LinphoneAuthInfo object * @return The realm. */ -LINPHONE_PUBLIC const char *linphone_auth_info_get_realm(const LinphoneAuthInfo *i); +LINPHONE_PUBLIC const char *linphone_auth_info_get_realm(const LinphoneAuthInfo *info); /** * Gets the domain. * - * @params[in] i The #LinphoneAuthInfo object + * @param[in] info The #LinphoneAuthInfo object * @return The domain. */ -LINPHONE_PUBLIC const char *linphone_auth_info_get_domain(const LinphoneAuthInfo *i); +LINPHONE_PUBLIC const char *linphone_auth_info_get_domain(const LinphoneAuthInfo *info); /** * Gets the ha1. * - * @params[in] i The #LinphoneAuthInfo object + * @param[in] info The #LinphoneAuthInfo object * @return The ha1. */ -LINPHONE_PUBLIC const char *linphone_auth_info_get_ha1(const LinphoneAuthInfo *i); +LINPHONE_PUBLIC const char *linphone_auth_info_get_ha1(const LinphoneAuthInfo *info); /* you don't need those function*/ LINPHONE_PUBLIC void linphone_auth_info_destroy(LinphoneAuthInfo *info); From 96674441f8d5645386aebc5958f7d35448f5b400 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 10 Jul 2014 14:12:11 +0200 Subject: [PATCH 035/218] Updated ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 6155d6437..1f4c694e1 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 6155d6437712ac049be34b73ddc51a85d62c9f9b +Subproject commit 1f4c694e186f38575e4017ede2badd1f30f80e7e From eb2f1cf3bea7d43cb4f7435bc669dc417259792e Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 14:13:58 +0200 Subject: [PATCH 036/218] Call linphone *_destroy() or *_unref() functions when destroying a Python object. --- tools/python/apixml2python/linphone.py | 34 +++++++++++++++++-- .../apixml2python/linphone_module.mustache | 15 ++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 6bdf1cdaf..11694a6b5 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -27,7 +27,7 @@ class MethodDefinition: self.xml_method_return = self.method_node.find('./return') self.xml_method_args = self.method_node.findall('./arguments/argument') self.method_type = self.method_node.tag - if self.method_type != 'classmethod': + if self.method_type != 'classmethod' and len(self.xml_method_args) > 0: self.self_arg = self.xml_method_args[0] self.xml_method_args = self.xml_method_args[1:] @@ -46,8 +46,11 @@ class MethodDefinition: self.body += "\t" + xml_method_arg.get('type') + " " + xml_method_arg.get('name') + ";\n" self.arg_names.append(xml_method_arg.get('name')) - def format_native_pointer_checking(self, return_int): + def format_native_pointer_get(self): self.body += "\tnative_ptr = pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self);\n" + + def format_native_pointer_checking(self, return_int): + self.format_native_pointer_get() self.body += "\tif(native_ptr == NULL) {\n" self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid " + self.class_['class_name'] + " instance\");\n" if return_int: @@ -116,6 +119,17 @@ class MethodDefinition: else: self.body += "\tPy_RETURN_NONE;" + def format_dealloc_c_function_call(self): + if self.class_['class_refcountable']: + self.body += "\tif (native_ptr != NULL) {\n" + self.body += "\t\t" + self.class_['class_c_function_prefix'] + "unref(native_ptr);\n" + self.body += "\t}\n" + elif self.class_['class_destroyable']: + self.body += "\tif (native_ptr != NULL) {\n" + self.body += "\t\t" + self.class_['class_c_function_prefix'] + "destroy(native_ptr);\n" + self.body += "\t}\n" + self.body += "\tself->ob_type->tp_free(self);" + def __get_basic_type_from_c_type(self, ctype): basic_type = 'int' keywords = ['const', 'struct', 'enum', 'signed', 'unsigned', 'short', 'long', '*'] @@ -224,6 +238,8 @@ class LinphoneModule(object): c['class_name'] = self.__strip_leading_linphone(c['class_cname']) c['class_c_function_prefix'] = xml_class.get('cfunctionprefix') c['class_doc'] = self.__format_doc(xml_class.find('briefdescription'), xml_class.find('detaileddescription')) + c['class_refcountable'] = (xml_class.get('refcountable') == 'true') + c['class_destroyable'] = (xml_class.get('destroyable') == 'true') c['class_type_methods'] = [] xml_type_methods = xml_class.findall("./classmethods/classmethod") for xml_type_method in xml_type_methods: @@ -265,6 +281,12 @@ class LinphoneModule(object): p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '') p['setter_body'] = self.__format_setter_body(xml_property_setter, c) c['class_properties'].append(p) + if c['class_refcountable']: + xml_instance_method = xml_class.find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") + c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) + elif c['class_destroyable']: + xml_instance_method = xml_class.find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']") + c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) self.classes.append(c) def __strip_leading_linphone(self, s): @@ -298,6 +320,14 @@ class LinphoneModule(object): method.format_setter_value_checking_and_c_function_call() return method.body + def __format_dealloc_body(self, method_node, class_): + method = MethodDefinition(method_node, class_) + method.format_local_variables_definition() + method.format_native_pointer_get() + method.format_tracing() + method.format_dealloc_c_function_call() + return method.body + def __format_doc_node(self, node): desc = '' if node.tag == 'para': diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 915a2759b..37547302c 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -89,8 +89,7 @@ static PyObject * pylinphone_{{class_name}}_new(PyTypeObject *type, PyObject *ar } static void pylinphone_{{class_name}}_dealloc(PyObject *self) { - pylinphone_trace(__FUNCTION__); - self->ob_type->tp_free(self); +{{{dealloc_body}}} } {{#class_type_methods}} @@ -119,7 +118,8 @@ static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { {{#class_instance_methods}} { "{{method_name}}", pylinphone_{{class_name}}_instance_method_{{method_name}}, METH_VARARGS, "" }, {{/class_instance_methods}} - { NULL, NULL, 0, NULL } /* Sentinel */ + /* Sentinel */ + { NULL, NULL, 0, NULL } }; {{#class_properties}} @@ -140,7 +140,8 @@ static PyGetSetDef pylinphone_{{class_name}}_getseters[] = { {{#class_properties}} { "{{property_name}}", (getter)pylinphone_{{class_name}}_{{getter_name}}, (setter)pylinphone_{{class_name}}_{{setter_name}}, "" }, {{/class_properties}} - { NULL, NULL, NULL, NULL, NULL } /* Sentinel */ + /* Sentinel */ + { NULL, NULL, NULL, NULL, NULL } }; static PyTypeObject pylinphone_{{class_name}}Type = { @@ -189,11 +190,13 @@ static PyTypeObject pylinphone_{{class_name}}Type = { {{/classes}} static PyMethodDef pylinphone_ModuleMethods[] = { - { NULL, NULL, 0, NULL } /* Sentinel */ + /* Sentinel */ + { NULL, NULL, 0, NULL } }; static PyMethodDef pylinphone_NoMethods[] = { - { NULL, NULL, 0, NULL } /* Sentinel */ + /* Sentinel */ + { NULL, NULL, 0, NULL } }; PyMODINIT_FUNC initlinphone(void) { From b4687141227948a321867dddbc815522710b4f3f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 10 Jul 2014 14:33:46 +0200 Subject: [PATCH 037/218] Fixed linphonecore_jni method needsEchoCalibration --- coreapi/linphonecore_jni.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 78eb797c3..acd2d1969 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -1482,12 +1482,11 @@ extern "C" jboolean Java_org_linphone_core_LinphoneCoreImpl_needsEchoCalibration const char *card=linphone_core_get_capture_device((LinphoneCore*)lc); sndcard=ms_snd_card_manager_get_card(m,card); if (sndcard == NULL){ - ms_error("Could not get soundcard."); + ms_error("Could not get soundcard %s", card); return TRUE; } if (ms_snd_card_get_capabilities(sndcard) & MS_SND_CARD_CAP_BUILTIN_ECHO_CANCELLER) return FALSE; - if (ms_snd_card_get_minimal_latency(sndcard)==0) return TRUE; - return FALSE; + return TRUE; } extern "C" jint Java_org_linphone_core_LinphoneCoreImpl_getMediaEncryption(JNIEnv* env From f3cfd4e0d5a1be838a54ca65a8e4b003af304a7d Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Thu, 10 Jul 2014 14:48:31 +0200 Subject: [PATCH 038/218] add JNI getters/setters for quality reporting --- coreapi/linphonecore_jni.cc | 31 ++++++++++++ .../linphone/core/LinphoneProxyConfig.java | 47 +++++++++++++++++-- .../core/LinphoneProxyConfigImpl.java | 40 ++++++++++++++++ 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index acd2d1969..c43b6e3c3 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -2993,6 +2993,37 @@ JNIEXPORT jint JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_getAvpfRRI return (jint)linphone_proxy_config_get_avpf_rr_interval((LinphoneProxyConfig *)ptr); } + + +JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_enableQualityReporting(JNIEnv *env, jobject thiz, jlong ptr, jboolean enable) { + linphone_proxy_config_enable_quality_reporting((LinphoneProxyConfig *)ptr, (bool)enable); +} + +JNIEXPORT jboolean JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_quality_reportingEnabled(JNIEnv *env, jobject thiz, jlong ptr) { + return linphone_proxy_config_quality_reporting_enabled((LinphoneProxyConfig *)ptr); +} + +JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_setQualityReportingInterval(JNIEnv *env, jobject thiz, jlong ptr, jint interval) { + linphone_proxy_config_set_quality_reporting_interval((LinphoneProxyConfig *)ptr, (uint8_t)interval); +} + +JNIEXPORT jint JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_getQualityReportingInterval(JNIEnv *env, jobject thiz, jlong ptr) { + return (jint)linphone_proxy_config_get_quality_reporting_interval((LinphoneProxyConfig *)ptr); +} + +JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_setQualityReportingCollector(JNIEnv *env, jobject thiz, jlong ptr, jstring jcollector) { + if (jcollector){ + const char *collector=env->GetStringUTFChars(jcollector, NULL); + linphone_proxy_config_set_quality_reporting_collector((LinphoneProxyConfig *)ptr, collector); + env->ReleaseStringUTFChars(jcollector,collector); + } +} + +JNIEXPORT jstring JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_getQualityReportingCollector(JNIEnv *env, jobject thiz, jlong ptr) { + jstring jvalue = env->NewStringUTF(linphone_proxy_config_get_quality_reporting_collector((LinphoneProxyConfig *)ptr)); + return jvalue; +} + extern "C" jint Java_org_linphone_core_LinphoneCallImpl_getDuration(JNIEnv* env,jobject thiz,jlong ptr) { return (jint)linphone_call_get_duration((LinphoneCall *) ptr); } diff --git a/java/common/org/linphone/core/LinphoneProxyConfig.java b/java/common/org/linphone/core/LinphoneProxyConfig.java index b34363e5f..c2c742bd4 100644 --- a/java/common/org/linphone/core/LinphoneProxyConfig.java +++ b/java/common/org/linphone/core/LinphoneProxyConfig.java @@ -172,6 +172,12 @@ public interface LinphoneProxyConfig { */ void enableAvpf(boolean enable); + /** + * Whether AVPF is used for calls through this proxy. + * @return + */ + boolean avpfEnabled(); + /** * Set the interval between regular RTCP reports when using AVPF/SAVPF. * @param interval The interval in seconds (between 0 and 5 seconds). @@ -185,14 +191,45 @@ public interface LinphoneProxyConfig { int getAvpfRRInterval(); /** - * Whether AVPF is used for calls through this proxy. + * Indicates whether quality reporting must be used for calls using this proxy config. + * @param enable True to enable quality reporting, false to disable it. + */ + void enableQualityReporting(boolean enable); + + + /** + * Whether quality reporting is used for calls through this proxy. * @return */ - boolean avpfEnabled(); + boolean qualityReportingEnabled(); + + /** + * Set the interval between quality interval reports during a call when using quality reporting. + * @param interval The interval in seconds (should be greater than 120 seconds to avoid too much). + */ + void setQualityReportingInterval(int interval); + + /** + * Get the interval between quality interval reports during a call when using quality reporting. + * @return The interval in seconds. + */ + int getQualityReportingInterval(); + + /** + * Set the collector SIP URI to collect reports when using quality reporting. + * @param collector The collector SIP URI which should be configured server side too. + */ + void setQualityReportingCollector(String collector); + + /** + * Get the collector SIP URI collecting reports when using quality reporting. + * @return The SIP URI collector address. + */ + String getQualityReportingCollector(); /** * Set optional contact parameters that will be added to the contact information sent in the registration. - * @param contact_params a string contaning the additional parameters in text form, like "myparam=something;myparam2=something_else" + * @param contact_params a string containing the additional parameters in text form, like "myparam=something;myparam2=something_else" * * The main use case for this function is provide the proxy additional information regarding the user agent, like for example unique identifier or android push id. * As an example, the contact address in the SIP register sent will look like ;android-push-id=43143-DFE23F-2323-FA2232. @@ -207,7 +244,7 @@ public interface LinphoneProxyConfig { /** * Set optional contact parameters that will be added to the contact information sent in the registration, inside the URI. - * @param params a string contaning the additional parameters in text form, like "myparam=something;myparam2=something_else" + * @param params a string containing the additional parameters in text form, like "myparam=something;myparam2=something_else" * * The main use case for this function is provide the proxy additional information regarding the user agent, like for example unique identifier or apple push id. * As an example, the contact address in the SIP register sent will look like . @@ -215,7 +252,7 @@ public interface LinphoneProxyConfig { public void setContactUriParameters(String params); /** - * Get the contact's uri parameters. + * Get the contact's URI parameters. * @return */ public String getContactUriParameters(); diff --git a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java index 1288ac39f..475fe352d 100644 --- a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java +++ b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java @@ -304,4 +304,44 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { public ErrorInfo getErrorInfo() { return new ErrorInfoImpl(getErrorInfo(nativePtr)); } + + private native void enableQualityReporting(long nativePtr, boolean enable); + @Override + public void enableQualityReporting(boolean enable) { + isValid(); + enableQualityReporting(nativePtr, enable); + } + + private native boolean qualityReportingEnabled(long nativePtr); + @Override + public boolean qualityReportingEnabled() { + isValid(); + return avpfEnabled(nativePtr); + } + + private native void setQualityReportingInterval(long nativePtr, int interval); + @Override + public void setQualityReportingInterval(int interval) { + isValid(); + setQualityReportingInterval(nativePtr, interval); + } + private native int getQualityReportingInterval(long nativePtr); + @Override + public int getQualityReportingInterval() { + isValid(); + return getQualityReportingInterval(nativePtr); + } + private native void setQualityReportingCollector(long nativePtr, String collector); + @Override + public void setQualityReportingCollector(String collector) { + isValid(); + setQualityReportingCollector(nativePtr, collector); + } + private native String getQualityReportingCollector(long nativePtr); + @Override + public String getQualityReportingCollector() { + + isValid(); + return getQualityReportingCollector(nativePtr); + } } From 7675668137d235dd471295b5d0002f7b330bd353 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 15:01:37 +0200 Subject: [PATCH 039/218] Force return value type of deallocate and setter functions to void in the Python wrapper to prevent useless variables definitions. --- tools/python/apixml2python/linphone.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 11694a6b5..8f9a0d02c 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -315,6 +315,9 @@ class LinphoneModule(object): def __format_setter_body(self, setter_node, class_): method = MethodDefinition(setter_node, class_) + # Force return value type of dealloc function to prevent declaring useless local variables + # TODO: Investigate. Maybe we should decide that setters must always return an int value. + method.xml_method_return.set('type', 'void') method.format_local_variables_definition() method.format_tracing() method.format_setter_value_checking_and_c_function_call() @@ -322,6 +325,8 @@ class LinphoneModule(object): def __format_dealloc_body(self, method_node, class_): method = MethodDefinition(method_node, class_) + # Force return value type of dealloc function to prevent declaring useless local variables + method.xml_method_return.set('type', 'void') method.format_local_variables_definition() method.format_native_pointer_get() method.format_tracing() From e74afa797946959cff86ab874a6370dee4e7b69c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 15:03:32 +0200 Subject: [PATCH 040/218] Prevent double native pointer checking + Improve error messages + Do not generate user_data attributes in the Python wrapper. --- tools/python/apixml2python/linphone.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 8f9a0d02c..81d5436b0 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -68,22 +68,17 @@ class MethodDefinition: self.body += ")) {\n\t\treturn NULL;\n\t}\n" def format_setter_value_checking_and_c_function_call(self): - # Check the native pointer + attribute_name = self.method_node.get('property_name') self.format_native_pointer_checking(True) - self.body += "\tnative_ptr = pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self);\n" - self.body += "\tif(native_ptr == NULL) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid " + self.class_['class_name'] + " instance\");\n" - self.body += "\t\treturn -1;\n" - self.body += "\t}\n" # Check that the value exists self.body += "\tif (value == NULL) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Cannot delete this attribute\");\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Cannot delete the " + attribute_name + " attribute\");\n" self.body += "\t\treturn -1;\n" self.body += "\t}\n" # Check the value basic_type, checkfunc, convertfunc = self.__ctype_to_python_type(self.xml_method_args[0].get('type')) self.body += "\tif (!" + checkfunc + "(value)) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"This attribute value must be a " + basic_type + "\");\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"The " + attribute_name + " attribute value must be a " + basic_type + "\");\n" self.body += "\t\treturn -1;\n" self.body += "\t}\n" # Call the C function @@ -210,6 +205,7 @@ class MethodDefinition: class LinphoneModule(object): def __init__(self, tree, blacklisted_functions): self.internal_instance_method_names = ['destroy', 'ref', 'unref'] + self.internal_property_names = ['user_data'] self.enums = [] xml_enums = tree.findall("./enums/enum") for xml_enum in xml_enums: @@ -270,14 +266,19 @@ class LinphoneModule(object): c['class_properties'] = [] xml_properties = xml_class.findall("./properties/property") for xml_property in xml_properties: + property_name = xml_property.get('name') + if property_name in self.internal_property_names: + continue p = {} - p['property_name'] = xml_property.get('name') + p['property_name'] = property_name xml_property_getter = xml_property.find("./getter") xml_property_setter = xml_property.find("./setter") if xml_property_getter is not None: + xml_property_getter.set('property_name', property_name) p['getter_name'] = xml_property_getter.get('name').replace(c['class_c_function_prefix'], '') p['getter_body'] = self.__format_getter_body(xml_property_getter, c) if xml_property_setter is not None: + xml_property_setter.set('property_name', property_name) p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '') p['setter_body'] = self.__format_setter_body(xml_property_setter, c) c['class_properties'].append(p) From ce1911fe5ae1878bdbd475a92ac4aec7b75c1e42 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 15:04:21 +0200 Subject: [PATCH 041/218] Allow None for string parameters in the Python wrapper (value passed as NULL to the C code). --- tools/python/apixml2python/linphone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 81d5436b0..35d8f215d 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -139,7 +139,7 @@ class MethodDefinition: basic_type, splitted_type = self.__get_basic_type_from_c_type(ctype) if basic_type == 'char': if '*' in splitted_type: - return 's' + return 'z' elif 'unsigned' in splitted_type: return 'b' elif basic_type == 'int': From 117c833d57dc65f03e20a20ac0b1611990a7d9ce Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Thu, 10 Jul 2014 15:42:28 +0200 Subject: [PATCH 042/218] fix crash when proxy config done is call first --- coreapi/proxy.c | 4 ++-- tester/setup_tester.c | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/coreapi/proxy.c b/coreapi/proxy.c index fb8519c32..409ddb4a3 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -275,13 +275,13 @@ int linphone_proxy_config_set_route(LinphoneProxyConfig *obj, const char *route) bool_t linphone_proxy_config_check(LinphoneCore *lc, LinphoneProxyConfig *obj){ if (obj->reg_proxy==NULL){ - if (lc->vtable.display_warning) + if (lc && lc->vtable.display_warning) lc->vtable.display_warning(lc,_("The sip proxy address you entered is invalid, it must start with \"sip:\"" " followed by a hostname.")); return FALSE; } if (obj->reg_identity==NULL){ - if (lc->vtable.display_warning) + if (lc && lc->vtable.display_warning) lc->vtable.display_warning(lc,_("The sip identity you entered is invalid.\nIt should look like " "sip:username@proxydomain, such as sip:alice@example.net")); return FALSE; diff --git a/tester/setup_tester.c b/tester/setup_tester.c index 760cdf6bd..546042714 100644 --- a/tester/setup_tester.c +++ b/tester/setup_tester.c @@ -127,6 +127,8 @@ void linphone_proxy_config_address_equal_test() { void linphone_proxy_config_is_server_config_changed_test() { LinphoneProxyConfig* proxy_config = linphone_proxy_config_new(); + linphone_proxy_config_done(proxy_config); /*test done without edit*/ + linphone_proxy_config_set_identity(proxy_config,"sip:toto@titi"); linphone_proxy_config_edit(proxy_config); linphone_proxy_config_set_identity(proxy_config,"sips:toto@titi"); From 41a2152b030fc4b6aab8bbe363f61db160f1b46c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 15:54:01 +0200 Subject: [PATCH 043/218] Fix tp_name of objects in Python wrapper. --- tools/python/apixml2python/linphone_module.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 37547302c..ca64847a9 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -147,7 +147,7 @@ static PyGetSetDef pylinphone_{{class_name}}_getseters[] = { static PyTypeObject pylinphone_{{class_name}}Type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ - "linphone.{{name}}", /* tp_name */ + "linphone.{{class_name}}", /* tp_name */ sizeof(pylinphone_{{class_name}}Object), /* tp_basicsize */ 0, /* tp_itemsize */ pylinphone_{{class_name}}_dealloc, /* tp_dealloc */ From 2d020da4f0f61734e3bd56aa0818bcc3a4500384 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 15:54:45 +0200 Subject: [PATCH 044/218] Allow definition of properties without setter or without getter in the Python wrapper. --- tools/python/apixml2python/linphone.py | 19 +++++++++++++++++-- .../apixml2python/linphone_module.mustache | 11 +++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 35d8f215d..3a263df3b 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -51,7 +51,7 @@ class MethodDefinition: def format_native_pointer_checking(self, return_int): self.format_native_pointer_get() - self.body += "\tif(native_ptr == NULL) {\n" + self.body += "\tif (native_ptr == NULL) {\n" self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid " + self.class_['class_name'] + " instance\");\n" if return_int: self.body += "\t\treturn -1;\n" @@ -86,7 +86,8 @@ class MethodDefinition: pass # TODO else: self.body += "\t" + self.arg_names[0] + " = (" + self.xml_method_args[0].get('type') + ")" + convertfunc + "(value);\n" - self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + self.arg_names[0] + ");" + self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + self.arg_names[0] + ");\n" + self.body += "\treturn 0;" def format_tracing(self): self.body += "\tpylinphone_trace(__FUNCTION__);\n" @@ -273,14 +274,28 @@ class LinphoneModule(object): p['property_name'] = property_name xml_property_getter = xml_property.find("./getter") xml_property_setter = xml_property.find("./setter") + if xml_property_getter is not None and xml_property_getter.get('name') in blacklisted_functions: + continue + if xml_property_setter is not None and xml_property_setter.get('name') in blacklisted_functions: + continue if xml_property_getter is not None: xml_property_getter.set('property_name', property_name) p['getter_name'] = xml_property_getter.get('name').replace(c['class_c_function_prefix'], '') p['getter_body'] = self.__format_getter_body(xml_property_getter, c) + p['getter_reference'] = "(getter)pylinphone_" + c['class_name'] + "_" + p['getter_name'] + p['getter_definition_begin'] = "static PyObject * pylinphone_" + c['class_name'] + "_" + p['getter_name'] + "(PyObject *self, void *closure) {" + p['getter_definition_end'] = "}" + else: + p['getter_reference'] = "NULL" if xml_property_setter is not None: xml_property_setter.set('property_name', property_name) p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '') p['setter_body'] = self.__format_setter_body(xml_property_setter, c) + p['setter_reference'] = "(setter)pylinphone_" + c['class_name'] + "_" + p['setter_name'] + p['setter_definition_begin'] = "static int pylinphone_" + c['class_name'] + "_" + p['setter_name'] + "(PyObject *self, PyObject *value, void *closure) {" + p['setter_definition_end'] = "}" + else: + p['setter_reference'] = "NULL" c['class_properties'].append(p) if c['class_refcountable']: xml_instance_method = xml_class.find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index ca64847a9..451b785a4 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -124,21 +124,20 @@ static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { {{#class_properties}} -static PyObject * pylinphone_{{class_name}}_{{getter_name}}(PyObject *self, void *closure) { +{{{getter_definition_begin}}} {{{getter_body}}} -} +{{{getter_definition_end}}} -static int pylinphone_{{class_name}}_{{setter_name}}(PyObject *self, PyObject *value, void *closure) { +{{{setter_definition_begin}}} {{{setter_body}}} - return 0; -} +{{{setter_definition_end}}} {{/class_properties}} static PyGetSetDef pylinphone_{{class_name}}_getseters[] = { // TODO: Handle doc {{#class_properties}} - { "{{property_name}}", (getter)pylinphone_{{class_name}}_{{getter_name}}, (setter)pylinphone_{{class_name}}_{{setter_name}}, "" }, + { "{{property_name}}", {{getter_reference}}, {{setter_reference}}, "" }, {{/class_properties}} /* Sentinel */ { NULL, NULL, NULL, NULL, NULL } From 575c9a20d2845daa8d5609888e1e30de26e1d76c Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Thu, 10 Jul 2014 17:44:19 +0200 Subject: [PATCH 045/218] Fix crash when launching file download from message after linphone reboot. We now launch the download from the external_body_url and create a file_transfer_information when the response headers are received. --- coreapi/chat.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index 7617f0e55..7eafc5644 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -959,10 +959,33 @@ static void on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t lc->vtable.file_transfer_received(lc, chatMsg, chatMsg->file_transfer_information, (char *)buffer, size); } return; - - /* feed the callback with the received data */ +} +static LinphoneContent* linphone_chat_create_file_transfer_information_from_headers(const belle_sip_message_t* message ){ + LinphoneContent *content = ms_malloc0(sizeof(LinphoneContent)); + + belle_sip_header_content_length_t* content_length_hdr = BELLE_SIP_HEADER_CONTENT_LENGTH(belle_sip_message_get_header(message, "Content-Length")); + belle_sip_header_content_type_t* content_type_hdr = BELLE_SIP_HEADER_CONTENT_TYPE(belle_sip_message_get_header(message, "Content-Type")); + const char* type = NULL,*subtype = NULL; + + content->name = ms_strdup(""); + + if( content_type_hdr ){ + type = belle_sip_header_content_type_get_type(content_type_hdr); + subtype = belle_sip_header_content_type_get_subtype(content_type_hdr); + ms_message("Extracted content type %s / %s from header", type?type:"", subtype?subtype:""); + if( type ) content->type = ms_strdup(type); + if( subtype ) content->type = ms_strdup(subtype); + } + + if( content_length_hdr ){ + content->size = belle_sip_header_content_length_get_content_length(content_length_hdr); + ms_message("Extracted content length %i from header", (int)content->size); + } + + + return content; } static void linphone_chat_process_response_headers_from_get_file(void *data, const belle_http_response_event_t *event){ @@ -970,9 +993,21 @@ static void linphone_chat_process_response_headers_from_get_file(void *data, con /*we are receiving a response, set a specific body handler to acquire the response. * if not done, belle-sip will create a memory body handler, the default*/ LinphoneChatMessage *message=(LinphoneChatMessage *)belle_sip_object_data_get(BELLE_SIP_OBJECT(event->request),"message"); + belle_sip_message_t* response = event->response; + size_t body_size = 0; + + if( message->file_transfer_information == NULL ){ + ms_warning("No file transfer information for message %p: creating...", message); + message->file_transfer_information = linphone_chat_create_file_transfer_information_from_headers(response); + } + + if( message->file_transfer_information ){ + body_size = message->file_transfer_information->size; + } + belle_sip_message_set_body_handler( (belle_sip_message_t*)event->response, - (belle_sip_body_handler_t*)belle_sip_user_body_handler_new(message->file_transfer_information->size, linphone_chat_message_file_transfer_on_progress,on_recv_body,NULL,message) + (belle_sip_body_handler_t*)belle_sip_user_body_handler_new(body_size, linphone_chat_message_file_transfer_on_progress,on_recv_body,NULL,message) ); } } From 38ea4ee8a341f58f26406dfb94b7a021980f4872 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Thu, 10 Jul 2014 17:44:49 +0200 Subject: [PATCH 046/218] Made the code more readable --- coreapi/chat.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index 7eafc5644..7b6792107 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -132,7 +132,9 @@ static void linphone_chat_message_process_response_from_post_file(void *data, co char *content_type=belle_sip_strdup_printf("%s/%s", msg->file_transfer_information->type, msg->file_transfer_information->subtype); /* create a user body handler to take care of the file */ - belle_sip_user_body_handler_t *first_part_bh=belle_sip_user_body_handler_new(msg->file_transfer_information->size+linphone_chat_message_compute_filepart_header_size(msg->file_transfer_information->name, content_type), NULL, NULL, linphone_chat_message_file_transfer_on_send_body, msg); + size_t body_size = msg->file_transfer_information->size+linphone_chat_message_compute_filepart_header_size(msg->file_transfer_information->name, content_type); + + belle_sip_user_body_handler_t *first_part_bh=belle_sip_user_body_handler_new(body_size,NULL,NULL,linphone_chat_message_file_transfer_on_send_body,msg); /* insert it in a multipart body handler which will manage the boundaries of multipart message */ belle_sip_multipart_body_handler_t *bh=belle_sip_multipart_body_handler_new(linphone_chat_message_file_transfer_on_progress, msg, (belle_sip_body_handler_t *)first_part_bh); From 6b290a01ea4fc4817aea4e53d9f623d0c4dbe952 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 11 Jul 2014 11:04:08 +0200 Subject: [PATCH 047/218] Fix compilation. --- coreapi/chat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index 7b6792107..e445d2bfa 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -995,7 +995,7 @@ static void linphone_chat_process_response_headers_from_get_file(void *data, con /*we are receiving a response, set a specific body handler to acquire the response. * if not done, belle-sip will create a memory body handler, the default*/ LinphoneChatMessage *message=(LinphoneChatMessage *)belle_sip_object_data_get(BELLE_SIP_OBJECT(event->request),"message"); - belle_sip_message_t* response = event->response; + belle_sip_message_t* response = BELLE_SIP_MESSAGE(event->response); size_t body_size = 0; if( message->file_transfer_information == NULL ){ From e3257c88a19b8ebd473e682fe994c82b47988aa4 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 11 Jul 2014 11:04:22 +0200 Subject: [PATCH 048/218] Add API and linphonerc parameter to configure SIP transport timeout. --- coreapi/linphonecore.c | 11 +++++++++++ coreapi/linphonecore.h | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 276dd82f7..d12a92bdf 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -756,6 +756,7 @@ static void sip_config_read(LinphoneCore *lc) sal_use_dates(lc->sal,lp_config_get_int(lc->config,"sip","put_date",0)); sal_enable_sip_update_method(lc->sal,lp_config_get_int(lc->config,"sip","sip_update",1)); lc->sip_conf.vfu_with_info=lp_config_get_int(lc->config,"sip","vfu_with_info",1); + linphone_core_set_sip_transport_timeout(lc, lp_config_get_int(lc->config, "sip", "transport_timeout", 63000)); } static void rtp_config_read(LinphoneCore *lc) @@ -1115,6 +1116,16 @@ void linphone_core_set_upload_bandwidth(LinphoneCore *lc, int bw){ if (linphone_core_ready(lc)) lp_config_set_int(lc->config,"net","upload_bw",bw); } +void linphone_core_set_sip_transport_timeout(LinphoneCore *lc, int timeout_ms) { + sal_set_transport_timeout(lc->sal, timeout_ms); + if (linphone_core_ready(lc)) + lp_config_set_int(lc->config, "sip", "transport_timeout", timeout_ms); +} + +int linphone_core_get_sip_transport_timeout(LinphoneCore *lc) { + return sal_get_transport_timeout(lc->sal); +} + void linphone_core_enable_dns_srv(LinphoneCore *lc, bool_t enable) { sal_enable_dns_srv(lc->sal, enable); if (linphone_core_ready(lc)) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index fda0ccc7c..6e0d88807 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1811,6 +1811,22 @@ LINPHONE_PUBLIC void linphone_core_set_upload_ptime(LinphoneCore *lc, int ptime) LINPHONE_PUBLIC int linphone_core_get_upload_ptime(LinphoneCore *lc); +/** + * Set the SIP transport timeout. + * @param[in] lc #LinphoneCore object. + * @param[in] timeout_ms The SIP transport timeout in milliseconds. + * @ingroup media_parameters + */ +void linphone_core_set_sip_transport_timeout(LinphoneCore *lc, int timeout_ms); + +/** + * Get the SIP transport timeout. + * @param[in] lc #LinphoneCore object. + * @return The SIP transport timeout in milliseconds. + * @ingroup media_parameters + */ +int linphone_core_get_sip_transport_timeout(LinphoneCore *lc); + /** * Enable or disable DNS SRV resolution. * @param[in] lc #LinphoneCore object. From 95476e63a63b88d58d967c18604158ee71e7ee7d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 11 Jul 2014 11:10:15 +0200 Subject: [PATCH 049/218] Updated ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 1f4c694e1..b40af312e 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 1f4c694e186f38575e4017ede2badd1f30f80e7e +Subproject commit b40af312e90b6c91bbee360f430ed87fa26119e9 From 4abe3b3cc845b5308858b94b5e16877e9abaaae4 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 10 Jul 2014 16:20:35 +0200 Subject: [PATCH 050/218] Correctly handle passing of enum arguments to methods in the Python wrapper. --- tools/python/apixml2python/linphone.py | 53 +++++++++++++++++--------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 3a263df3b..02034fcdc 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -14,8 +14,14 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +def strip_leading_linphone(s): + if s.lower().startswith('linphone'): + return s[8:] + else: + return s + class MethodDefinition: - def __init__(self, method_node, class_): + def __init__(self, method_node, class_, enum_names): self.body = '' self.arg_names = [] self.parse_tuple_format = '' @@ -23,6 +29,7 @@ class MethodDefinition: self.return_type = 'void' self.method_node = method_node self.class_ = class_ + self.enum_names = enum_names self.self_arg = None self.xml_method_return = self.method_node.find('./return') self.xml_method_args = self.method_node.findall('./arguments/argument') @@ -42,8 +49,16 @@ class MethodDefinition: if self.self_arg is not None: self.body += "\t" + self.self_arg.get('type') + "native_ptr;\n" for xml_method_arg in self.xml_method_args: - self.parse_tuple_format += self.__ctype_to_python_format(xml_method_arg.get('type')) - self.body += "\t" + xml_method_arg.get('type') + " " + xml_method_arg.get('name') + ";\n" + method_type = xml_method_arg.get('type') + fmt = self.__ctype_to_python_format(method_type) + self.parse_tuple_format += fmt + if fmt == 'O': + # TODO + pass + elif strip_leading_linphone(method_type) in self.enum_names: + self.body += "\tint " + xml_method_arg.get('name') + ";\n" + else: + self.body += "\t" + xml_method_arg.get('type') + " " + xml_method_arg.get('name') + ";\n" self.arg_names.append(xml_method_arg.get('name')) def format_native_pointer_get(self): @@ -171,7 +186,10 @@ class MethodDefinition: elif basic_type == 'bool_t': return 'i' else: - return 'O' + if strip_leading_linphone(basic_type) in self.enum_names: + return 'i' + else: + return 'O' def __ctype_to_python_type(self, ctype): basic_type, splitted_type = self.__get_basic_type_from_c_type(ctype) @@ -200,7 +218,10 @@ class MethodDefinition: elif basic_type == 'bool_t': return ('bool', 'PyBool_Check', 'PyInt_AsLong') else: - return ('class instance', 'PyInstance_Check', None) + if strip_leading_linphone(basic_type) in self.enum_names: + return ('int', 'PyInt_Check', 'PyInt_AsLong') + else: + return ('class instance', 'PyInstance_Check', None) class LinphoneModule(object): @@ -208,12 +229,13 @@ class LinphoneModule(object): self.internal_instance_method_names = ['destroy', 'ref', 'unref'] self.internal_property_names = ['user_data'] self.enums = [] + self.enum_names = [] xml_enums = tree.findall("./enums/enum") for xml_enum in xml_enums: if xml_enum.get('deprecated') == 'true': continue e = {} - e['enum_name'] = self.__strip_leading_linphone(xml_enum.get('name')) + e['enum_name'] = strip_leading_linphone(xml_enum.get('name')) e['enum_doc'] = self.__format_doc(xml_enum.find('briefdescription'), xml_enum.find('detaileddescription')) e['enum_values'] = [] xml_enum_values = xml_enum.findall("./values/value") @@ -222,9 +244,10 @@ class LinphoneModule(object): continue v = {} v['enum_value_cname'] = xml_enum_value.get('name') - v['enum_value_name'] = self.__strip_leading_linphone(v['enum_value_cname']) + v['enum_value_name'] = strip_leading_linphone(v['enum_value_cname']) e['enum_values'].append(v) self.enums.append(e) + self.enum_names.append(e['enum_name']) self.classes = [] xml_classes = tree.findall("./classes/class") for xml_class in xml_classes: @@ -232,7 +255,7 @@ class LinphoneModule(object): continue c = {} c['class_cname'] = xml_class.get('name') - c['class_name'] = self.__strip_leading_linphone(c['class_cname']) + c['class_name'] = strip_leading_linphone(c['class_cname']) c['class_c_function_prefix'] = xml_class.get('cfunctionprefix') c['class_doc'] = self.__format_doc(xml_class.find('briefdescription'), xml_class.find('detaileddescription')) c['class_refcountable'] = (xml_class.get('refcountable') == 'true') @@ -305,14 +328,8 @@ class LinphoneModule(object): c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) self.classes.append(c) - def __strip_leading_linphone(self, s): - if s.lower().startswith('linphone'): - return s[8:] - else: - return s - def __format_method_body(self, method_node, class_): - method = MethodDefinition(method_node, class_) + method = MethodDefinition(method_node, class_, self.enum_names) method.format_local_variables_definition() method.format_arguments_parsing() method.format_tracing() @@ -321,7 +338,7 @@ class LinphoneModule(object): return method.body def __format_getter_body(self, getter_node, class_): - method = MethodDefinition(getter_node, class_) + method = MethodDefinition(getter_node, class_, self.enum_names) method.format_local_variables_definition() method.format_arguments_parsing() method.format_tracing() @@ -330,7 +347,7 @@ class LinphoneModule(object): return method.body def __format_setter_body(self, setter_node, class_): - method = MethodDefinition(setter_node, class_) + method = MethodDefinition(setter_node, class_, self.enum_names) # Force return value type of dealloc function to prevent declaring useless local variables # TODO: Investigate. Maybe we should decide that setters must always return an int value. method.xml_method_return.set('type', 'void') @@ -340,7 +357,7 @@ class LinphoneModule(object): return method.body def __format_dealloc_body(self, method_node, class_): - method = MethodDefinition(method_node, class_) + method = MethodDefinition(method_node, class_, self.enum_names) # Force return value type of dealloc function to prevent declaring useless local variables method.xml_method_return.set('type', 'void') method.format_local_variables_definition() From affdb49e12b09f7e179294f02684ec0f64440700 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 11 Jul 2014 14:11:53 +0200 Subject: [PATCH 051/218] Include type and complete type for arguments in the generated XML file of the API. --- tools/genapixml.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/genapixml.py b/tools/genapixml.py index 49ac079da..11a3cf072 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -583,13 +583,13 @@ class Generator: if f.location is not None: functionAttributes['location'] = f.location functionNode = ET.SubElement(parentNode, nodeName, functionAttributes) - returnValueAttributes = { 'type' : f.returnArgument.completeType } + returnValueAttributes = { 'type' : f.returnArgument.ctype, 'completetype' : f.returnArgument.completeType } returnValueNode = ET.SubElement(functionNode, 'return', returnValueAttributes) if f.returnArgument.description is not None: returnValueNode.append(f.returnArgument.description) argumentsNode = ET.SubElement(functionNode, 'arguments') for arg in f.arguments: - argumentNodeAttributes = { 'name' : arg.name, 'type' : arg.completeType } + argumentNodeAttributes = { 'name' : arg.name, 'type' : arg.ctype, 'completetype' : arg.completeType } argumentNode = ET.SubElement(argumentsNode, 'argument', argumentNodeAttributes) if arg.description is not None: argumentNode.append(arg.description) From 0c37b9e59eb60df51790c79c1eeec9b18ede4e57 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 11 Jul 2014 14:12:52 +0200 Subject: [PATCH 052/218] Correctly handle passing of objects as method arguments in the Python wrapper. --- tools/python/apixml2python/linphone.py | 87 ++++++++++++------- .../apixml2python/linphone_module.mustache | 4 + 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 02034fcdc..4bfccdd6b 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -40,26 +40,29 @@ class MethodDefinition: def format_local_variables_definition(self): self.return_type = self.xml_method_return.get('type') - if self.return_type != 'void': - self.body += "\t" + self.return_type + " cresult;\n" - self.build_value_format = self.__ctype_to_python_format(self.return_type) + self.return_complete_type = self.xml_method_return.get('completetype') + if self.return_complete_type != 'void': + self.body += "\t" + self.return_complete_type + " cresult;\n" + self.build_value_format = self.__ctype_to_python_format(self.return_type, self.return_complete_type) if self.build_value_format == 'O': self.body += "\tPyObject * pyresult;\n" self.body += "\tPyObject * pyret;\n" if self.self_arg is not None: - self.body += "\t" + self.self_arg.get('type') + "native_ptr;\n" + self.body += "\t" + self.self_arg.get('completetype') + "native_ptr;\n" for xml_method_arg in self.xml_method_args: - method_type = xml_method_arg.get('type') - fmt = self.__ctype_to_python_format(method_type) + arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + fmt = self.__ctype_to_python_format(arg_type, arg_complete_type) self.parse_tuple_format += fmt if fmt == 'O': - # TODO - pass - elif strip_leading_linphone(method_type) in self.enum_names: - self.body += "\tint " + xml_method_arg.get('name') + ";\n" + self.body += "\tPyObject * " + arg_name + ";\n" + self.body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr;\n" + elif strip_leading_linphone(arg_complete_type) in self.enum_names: + self.body += "\tint " + arg_name + ";\n" else: - self.body += "\t" + xml_method_arg.get('type') + " " + xml_method_arg.get('name') + ";\n" - self.arg_names.append(xml_method_arg.get('name')) + self.body += "\t" + arg_complete_type + " " + arg_name + ";\n" + self.arg_names.append(arg_name) def format_native_pointer_get(self): self.body += "\tnative_ptr = pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self);\n" @@ -74,6 +77,17 @@ class MethodDefinition: self.body += "\t\treturn NULL;\n" self.body += "\t}\n" + def format_object_args_native_pointers_checking(self): + for xml_method_arg in self.xml_method_args: + arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + fmt = self.__ctype_to_python_format(arg_type, arg_complete_type) + if fmt == 'O': + self.body += "\tif ((" + arg_name + "_native_ptr = pylinphone_" + strip_leading_linphone(arg_type) + "_get_native_ptr(" + arg_name + ")) == NULL) {\n" + self.body += "\t\treturn NULL;\n" + self.body += "\t}\n" + def format_arguments_parsing(self): if self.self_arg is not None: self.format_native_pointer_checking(False) @@ -81,9 +95,13 @@ class MethodDefinition: self.body += "\tif (!PyArg_ParseTuple(args, \"" + self.parse_tuple_format + "\"" self.body += ', ' + ', '.join(map(lambda a: '&' + a, self.arg_names)) self.body += ")) {\n\t\treturn NULL;\n\t}\n" + self.format_object_args_native_pointers_checking() def format_setter_value_checking_and_c_function_call(self): attribute_name = self.method_node.get('property_name') + first_arg_type = self.xml_method_args[0].get('type') + first_arg_complete_type = self.xml_method_args[0].get('completetype') + first_arg_name = self.xml_method_args[0].get('name') self.format_native_pointer_checking(True) # Check that the value exists self.body += "\tif (value == NULL) {\n" @@ -91,32 +109,45 @@ class MethodDefinition: self.body += "\t\treturn -1;\n" self.body += "\t}\n" # Check the value - basic_type, checkfunc, convertfunc = self.__ctype_to_python_type(self.xml_method_args[0].get('type')) + type_str, checkfunc, convertfunc = self.__ctype_to_python_type(first_arg_type, first_arg_complete_type) self.body += "\tif (!" + checkfunc + "(value)) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"The " + attribute_name + " attribute value must be a " + basic_type + "\");\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"The " + attribute_name + " attribute value must be a " + type_str + "\");\n" self.body += "\t\treturn -1;\n" self.body += "\t}\n" # Call the C function if convertfunc is None: pass # TODO else: - self.body += "\t" + self.arg_names[0] + " = (" + self.xml_method_args[0].get('type') + ")" + convertfunc + "(value);\n" - self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + self.arg_names[0] + ");\n" + self.body += "\t" + first_arg_name + " = (" + first_arg_complete_type + ")" + convertfunc + "(value);\n" + if self.__ctype_to_python_format(first_arg_type, first_arg_complete_type) == 'O': + pass # TODO + else: + self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + self.arg_names[0] + ");\n" self.body += "\treturn 0;" def format_tracing(self): self.body += "\tpylinphone_trace(__FUNCTION__);\n" def format_c_function_call(self): + arg_names = [] + for xml_method_arg in self.xml_method_args: + arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + type_str, checkfunc, convertfunc = self.__ctype_to_python_type(arg_type, arg_complete_type) + if convertfunc is None: + arg_names.append(arg_name + "_native_ptr") + else: + arg_names.append(arg_name) self.body += "\t" if self.return_type != 'void': self.body += "cresult = " self.body += self.method_node.get('name') + "(" if self.self_arg is not None: self.body += "native_ptr" - if len(self.arg_names) > 0: + if len(arg_names) > 0: self.body += ', ' - self.body += ', '.join(self.arg_names) + ");\n" + self.body += ', '.join(arg_names) + ");\n" def format_method_result(self): if self.return_type != 'void': @@ -141,18 +172,8 @@ class MethodDefinition: self.body += "\t}\n" self.body += "\tself->ob_type->tp_free(self);" - def __get_basic_type_from_c_type(self, ctype): - basic_type = 'int' - keywords = ['const', 'struct', 'enum', 'signed', 'unsigned', 'short', 'long', '*'] - splitted_type = ctype.split(' ') - for s in splitted_type: - if s not in keywords: - basic_type = s - break - return (basic_type, splitted_type) - - def __ctype_to_python_format(self, ctype): - basic_type, splitted_type = self.__get_basic_type_from_c_type(ctype) + def __ctype_to_python_format(self, basic_type, complete_type): + splitted_type = complete_type.split(' ') if basic_type == 'char': if '*' in splitted_type: return 'z' @@ -191,8 +212,8 @@ class MethodDefinition: else: return 'O' - def __ctype_to_python_type(self, ctype): - basic_type, splitted_type = self.__get_basic_type_from_c_type(ctype) + def __ctype_to_python_type(self, basic_type, complete_type): + splitted_type = complete_type.split(' ') if basic_type == 'char': if '*' in splitted_type: return ('string', 'PyString_Check', 'PyString_AsString') @@ -351,6 +372,7 @@ class LinphoneModule(object): # Force return value type of dealloc function to prevent declaring useless local variables # TODO: Investigate. Maybe we should decide that setters must always return an int value. method.xml_method_return.set('type', 'void') + method.xml_method_return.set('completetype', 'void') method.format_local_variables_definition() method.format_tracing() method.format_setter_value_checking_and_c_function_call() @@ -360,6 +382,7 @@ class LinphoneModule(object): method = MethodDefinition(method_node, class_, self.enum_names) # Force return value type of dealloc function to prevent declaring useless local variables method.xml_method_return.set('type', 'void') + method.xml_method_return.set('completetype', 'void') method.format_local_variables_definition() method.format_native_pointer_get() method.format_tracing() diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 451b785a4..0c5cb00aa 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -67,6 +67,10 @@ typedef struct { {{class_cname}} *native_ptr; } pylinphone_{{class_name}}Object; +{{/classes}} + +{{#classes}} + static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(PyObject *self) { return ((pylinphone_{{class_name}}Object *)self)->native_ptr; } From c4adb92093ea172a3a420ef2aa7a0407f3238318 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 11 Jul 2014 16:55:05 +0200 Subject: [PATCH 053/218] Correctly handle return of object types from methods. --- tools/python/apixml2python/linphone.py | 91 ++++++++++++++----- .../apixml2python/linphone_module.mustache | 8 +- 2 files changed, 71 insertions(+), 28 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 4bfccdd6b..fab665461 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -21,7 +21,7 @@ def strip_leading_linphone(s): return s class MethodDefinition: - def __init__(self, method_node, class_, enum_names): + def __init__(self, method_node, class_, linphone_module): self.body = '' self.arg_names = [] self.parse_tuple_format = '' @@ -29,7 +29,7 @@ class MethodDefinition: self.return_type = 'void' self.method_node = method_node self.class_ = class_ - self.enum_names = enum_names + self.linphone_module = linphone_module self.self_arg = None self.xml_method_return = self.method_node.find('./return') self.xml_method_args = self.method_node.findall('./arguments/argument') @@ -58,7 +58,7 @@ class MethodDefinition: if fmt == 'O': self.body += "\tPyObject * " + arg_name + ";\n" self.body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr;\n" - elif strip_leading_linphone(arg_complete_type) in self.enum_names: + elif strip_leading_linphone(arg_complete_type) in self.linphone_module.enum_names: self.body += "\tint " + arg_name + ";\n" else: self.body += "\t" + arg_complete_type + " " + arg_name + ";\n" @@ -150,9 +150,15 @@ class MethodDefinition: self.body += ', '.join(arg_names) + ");\n" def format_method_result(self): - if self.return_type != 'void': + if self.return_complete_type != 'void': if self.build_value_format == 'O': - self.body += "\tpyresult = pylinphone_" + self.class_['class_name'] + "_new_from_native_ptr(&pylinphone_" + self.class_['class_name'] + "Type, cresult);\n" + stripped_return_type = strip_leading_linphone(self.return_type) + if self.class_['class_has_user_data']: + get_user_data_function = self.__find_class_definition(self.return_type)['class_c_function_prefix'] + "get_user_data" + self.body += "\tif (" + get_user_data_function + "(cresult) != NULL) {\n" + self.body += "\t\treturn (PyObject *)" + get_user_data_function + "(cresult);\n" + self.body += "\t}\n" + self.body += "\tpyresult = pylinphone_" + stripped_return_type + "_new_from_native_ptr(&pylinphone_" + stripped_return_type + "Type, cresult);\n" self.body += "\tpyret = Py_BuildValue(\"" + self.build_value_format + "\", pyresult);\n" self.body += "\tPy_DECREF(pyresult);\n" self.body += "\treturn pyret;" @@ -161,6 +167,17 @@ class MethodDefinition: else: self.body += "\tPy_RETURN_NONE;" + def format_new_from_native_pointer_body(self): + self.body += "\tpylinphone_" + self.class_['class_name'] + "Object *self;\n" + self.format_tracing() + self.body += "\tif (native_ptr == NULL) Py_RETURN_NONE;\n" + self.body += "\tself = (pylinphone_" + self.class_['class_name'] + "Object *)PyObject_New(pylinphone_" + self.class_['class_name'] + "Object, type);\n" + self.body += "\tif (self == NULL) Py_RETURN_NONE;\n" + self.body += "\tself->native_ptr = native_ptr;\n" + if self.class_['class_has_user_data']: + self.body += "\t" + self.class_['class_c_function_prefix'] + "set_user_data(native_ptr, self);\n" + self.body += "\treturn (PyObject *)self;" + def format_dealloc_c_function_call(self): if self.class_['class_refcountable']: self.body += "\tif (native_ptr != NULL) {\n" @@ -207,7 +224,7 @@ class MethodDefinition: elif basic_type == 'bool_t': return 'i' else: - if strip_leading_linphone(basic_type) in self.enum_names: + if strip_leading_linphone(basic_type) in self.linphone_module.enum_names: return 'i' else: return 'O' @@ -239,11 +256,18 @@ class MethodDefinition: elif basic_type == 'bool_t': return ('bool', 'PyBool_Check', 'PyInt_AsLong') else: - if strip_leading_linphone(basic_type) in self.enum_names: + if strip_leading_linphone(basic_type) in self.linphone_module.enum_names: return ('int', 'PyInt_Check', 'PyInt_AsLong') else: return ('class instance', 'PyInstance_Check', None) + def __find_class_definition(self, basic_type): + basic_type = strip_leading_linphone(basic_type) + for c in self.linphone_module.classes: + if c['class_name'] == basic_type: + return c + return None + class LinphoneModule(object): def __init__(self, tree, blacklisted_functions): @@ -275,12 +299,14 @@ class LinphoneModule(object): if xml_class.get('deprecated') == 'true': continue c = {} + c['class_xml_node'] = xml_class c['class_cname'] = xml_class.get('name') c['class_name'] = strip_leading_linphone(c['class_cname']) c['class_c_function_prefix'] = xml_class.get('cfunctionprefix') c['class_doc'] = self.__format_doc(xml_class.find('briefdescription'), xml_class.find('detaileddescription')) c['class_refcountable'] = (xml_class.get('refcountable') == 'true') c['class_destroyable'] = (xml_class.get('destroyable') == 'true') + c['class_has_user_data'] = False c['class_type_methods'] = [] xml_type_methods = xml_class.findall("./classmethods/classmethod") for xml_type_method in xml_type_methods: @@ -291,7 +317,7 @@ class LinphoneModule(object): continue m = {} m['method_name'] = method_name.replace(c['class_c_function_prefix'], '') - m['method_body'] = self.__format_method_body(xml_type_method, c) + m['method_xml_node'] = xml_type_method c['class_type_methods'].append(m) c['class_instance_methods'] = [] xml_instance_methods = xml_class.findall("./instancemethods/instancemethod") @@ -306,12 +332,14 @@ class LinphoneModule(object): continue m = {} m['method_name'] = method_name - m['method_body'] = self.__format_method_body(xml_instance_method, c) + m['method_xml_node'] = xml_instance_method c['class_instance_methods'].append(m) c['class_properties'] = [] xml_properties = xml_class.findall("./properties/property") for xml_property in xml_properties: property_name = xml_property.get('name') + if property_name == 'user_data': + c['class_has_user_data'] = True if property_name in self.internal_property_names: continue p = {} @@ -325,7 +353,7 @@ class LinphoneModule(object): if xml_property_getter is not None: xml_property_getter.set('property_name', property_name) p['getter_name'] = xml_property_getter.get('name').replace(c['class_c_function_prefix'], '') - p['getter_body'] = self.__format_getter_body(xml_property_getter, c) + p['getter_xml_node'] = xml_property_getter p['getter_reference'] = "(getter)pylinphone_" + c['class_name'] + "_" + p['getter_name'] p['getter_definition_begin'] = "static PyObject * pylinphone_" + c['class_name'] + "_" + p['getter_name'] + "(PyObject *self, void *closure) {" p['getter_definition_end'] = "}" @@ -334,23 +362,36 @@ class LinphoneModule(object): if xml_property_setter is not None: xml_property_setter.set('property_name', property_name) p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '') - p['setter_body'] = self.__format_setter_body(xml_property_setter, c) + p['setter_xml_node'] = xml_property_setter p['setter_reference'] = "(setter)pylinphone_" + c['class_name'] + "_" + p['setter_name'] p['setter_definition_begin'] = "static int pylinphone_" + c['class_name'] + "_" + p['setter_name'] + "(PyObject *self, PyObject *value, void *closure) {" p['setter_definition_end'] = "}" else: p['setter_reference'] = "NULL" c['class_properties'].append(p) - if c['class_refcountable']: - xml_instance_method = xml_class.find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") - c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) - elif c['class_destroyable']: - xml_instance_method = xml_class.find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']") - c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) self.classes.append(c) + # Format methods' bodies + for c in self.classes: + for m in c['class_type_methods']: + m['method_body'] = self.__format_method_body(m['method_xml_node'], c) + for m in c['class_instance_methods']: + m['method_body'] = self.__format_method_body(m['method_xml_node'], c) + for p in c['class_properties']: + if p.has_key('getter_xml_node'): + p['getter_body'] = self.__format_getter_body(p['getter_xml_node'], c) + if p.has_key('setter_xml_node'): + p['setter_body'] = self.__format_setter_body(p['setter_xml_node'], c) + if c['class_refcountable']: + xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") + c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(xml_instance_method, c) + c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) + elif c['class_destroyable']: + xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']") + c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(xml_instance_method, c) + c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) def __format_method_body(self, method_node, class_): - method = MethodDefinition(method_node, class_, self.enum_names) + method = MethodDefinition(method_node, class_, self) method.format_local_variables_definition() method.format_arguments_parsing() method.format_tracing() @@ -359,7 +400,7 @@ class LinphoneModule(object): return method.body def __format_getter_body(self, getter_node, class_): - method = MethodDefinition(getter_node, class_, self.enum_names) + method = MethodDefinition(getter_node, class_, self) method.format_local_variables_definition() method.format_arguments_parsing() method.format_tracing() @@ -368,7 +409,7 @@ class LinphoneModule(object): return method.body def __format_setter_body(self, setter_node, class_): - method = MethodDefinition(setter_node, class_, self.enum_names) + method = MethodDefinition(setter_node, class_, self) # Force return value type of dealloc function to prevent declaring useless local variables # TODO: Investigate. Maybe we should decide that setters must always return an int value. method.xml_method_return.set('type', 'void') @@ -378,8 +419,16 @@ class LinphoneModule(object): method.format_setter_value_checking_and_c_function_call() return method.body + def __format_new_from_native_pointer_body(self, method_node, class_): + method = MethodDefinition(method_node, class_, self) + # Force return value type of dealloc function to prevent declaring useless local variables + method.xml_method_return.set('type', 'void') + method.xml_method_return.set('completetype', 'void') + method.format_new_from_native_pointer_body() + return method.body + def __format_dealloc_body(self, method_node, class_): - method = MethodDefinition(method_node, class_, self.enum_names) + method = MethodDefinition(method_node, class_, self) # Force return value type of dealloc function to prevent declaring useless local variables method.xml_method_return.set('type', 'void') method.xml_method_return.set('completetype', 'void') diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 0c5cb00aa..fc8ed7a96 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -76,13 +76,7 @@ static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(PyObject *self } static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, {{class_cname}} *native_ptr) { - pylinphone_{{class_name}}Object *self; - pylinphone_trace(__FUNCTION__); - if (native_ptr == NULL) Py_RETURN_NONE; - self = (pylinphone_{{class_name}}Object *)PyObject_New(pylinphone_{{class_name}}Object, type); - if (self == NULL) Py_RETURN_NONE; - self->native_ptr = native_ptr; - return (PyObject *)self; +{{{new_from_native_pointer_body}}} } static PyObject * pylinphone_{{class_name}}_new(PyTypeObject *type, PyObject *args, PyObject *kw) { From d139c681f13d3b0d0d2316ad73e76b6b2be0f1c3 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 11 Jul 2014 17:03:27 +0200 Subject: [PATCH 054/218] Add check on NULL pointer before calling *_get_user_data in the Python wrapper. --- tools/python/apixml2python/linphone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index fab665461..c9821d34b 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -155,7 +155,7 @@ class MethodDefinition: stripped_return_type = strip_leading_linphone(self.return_type) if self.class_['class_has_user_data']: get_user_data_function = self.__find_class_definition(self.return_type)['class_c_function_prefix'] + "get_user_data" - self.body += "\tif (" + get_user_data_function + "(cresult) != NULL) {\n" + self.body += "\tif ((cresult != NULL) && (" + get_user_data_function + "(cresult) != NULL)) {\n" self.body += "\t\treturn (PyObject *)" + get_user_data_function + "(cresult);\n" self.body += "\t}\n" self.body += "\tpyresult = pylinphone_" + stripped_return_type + "_new_from_native_ptr(&pylinphone_" + stripped_return_type + "Type, cresult);\n" From f92d32e5cf6dda37c213ddec87d397a2b3fc2494 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 11 Jul 2014 17:34:04 +0200 Subject: [PATCH 055/218] Fix indentation bug that was causing errors in Python wrapper generation. --- tools/python/apixml2python/linphone.py | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index c9821d34b..f3e5a03a7 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -370,25 +370,25 @@ class LinphoneModule(object): p['setter_reference'] = "NULL" c['class_properties'].append(p) self.classes.append(c) - # Format methods' bodies - for c in self.classes: - for m in c['class_type_methods']: - m['method_body'] = self.__format_method_body(m['method_xml_node'], c) - for m in c['class_instance_methods']: - m['method_body'] = self.__format_method_body(m['method_xml_node'], c) - for p in c['class_properties']: - if p.has_key('getter_xml_node'): - p['getter_body'] = self.__format_getter_body(p['getter_xml_node'], c) - if p.has_key('setter_xml_node'): - p['setter_body'] = self.__format_setter_body(p['setter_xml_node'], c) - if c['class_refcountable']: - xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") - c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(xml_instance_method, c) - c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) - elif c['class_destroyable']: - xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']") - c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(xml_instance_method, c) - c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) + # Format methods' bodies + for c in self.classes: + for m in c['class_type_methods']: + m['method_body'] = self.__format_method_body(m['method_xml_node'], c) + for m in c['class_instance_methods']: + m['method_body'] = self.__format_method_body(m['method_xml_node'], c) + for p in c['class_properties']: + if p.has_key('getter_xml_node'): + p['getter_body'] = self.__format_getter_body(p['getter_xml_node'], c) + if p.has_key('setter_xml_node'): + p['setter_body'] = self.__format_setter_body(p['setter_xml_node'], c) + if c['class_refcountable']: + xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") + c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(xml_instance_method, c) + c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) + elif c['class_destroyable']: + xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']") + c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(xml_instance_method, c) + c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) def __format_method_body(self, method_node, class_): method = MethodDefinition(method_node, class_, self) From 3c692e3a36d7e55819c1693ac5d2b4a23e098e3e Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 11 Jul 2014 17:34:29 +0200 Subject: [PATCH 056/218] Add some function declarations at the beginning of the generation of the Python wrapper to prevent compilation warnings. --- tools/python/apixml2python/linphone_module.mustache | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index fc8ed7a96..a92149c1e 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -69,6 +69,11 @@ typedef struct { {{/classes}} +{{#classes}} +static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(PyObject *self); +static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, {{class_cname}} *native_ptr); +{{/classes}} + {{#classes}} static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(PyObject *self) { From 684a869a31133c133ec978259cfef1c0171b9481 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 11 Jul 2014 17:57:58 +0200 Subject: [PATCH 057/218] Do not include deprecated properties in the Python wrapper. --- tools/python/apixml2python/linphone.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index f3e5a03a7..b9334fc82 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -346,9 +346,11 @@ class LinphoneModule(object): p['property_name'] = property_name xml_property_getter = xml_property.find("./getter") xml_property_setter = xml_property.find("./setter") - if xml_property_getter is not None and xml_property_getter.get('name') in blacklisted_functions: + if xml_property_getter is not None and ( + xml_property_getter.get('name') in blacklisted_functions or xml_property_getter.get('deprecated') == 'true'): continue - if xml_property_setter is not None and xml_property_setter.get('name') in blacklisted_functions: + if xml_property_setter is not None and ( + xml_property_setter.get('name') in blacklisted_functions or xml_property_setter.get('deprecated') == 'true'): continue if xml_property_getter is not None: xml_property_getter.set('property_name', property_name) From b36e61414a27b884058f99a94b0f2f7d5507dd09 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 15 Jul 2014 14:15:11 +0200 Subject: [PATCH 058/218] Fix const warnings during Python wrapper compilation. --- tools/python/apixml2python/linphone.py | 4 ++-- tools/python/apixml2python/linphone_module.mustache | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index b9334fc82..86ec90e14 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -173,9 +173,9 @@ class MethodDefinition: self.body += "\tif (native_ptr == NULL) Py_RETURN_NONE;\n" self.body += "\tself = (pylinphone_" + self.class_['class_name'] + "Object *)PyObject_New(pylinphone_" + self.class_['class_name'] + "Object, type);\n" self.body += "\tif (self == NULL) Py_RETURN_NONE;\n" - self.body += "\tself->native_ptr = native_ptr;\n" + self.body += "\tself->native_ptr = (" + self.class_['class_cname'] + " *)native_ptr;\n" if self.class_['class_has_user_data']: - self.body += "\t" + self.class_['class_c_function_prefix'] + "set_user_data(native_ptr, self);\n" + self.body += "\t" + self.class_['class_c_function_prefix'] + "set_user_data(self->native_ptr, self);\n" self.body += "\treturn (PyObject *)self;" def format_dealloc_c_function_call(self): diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index a92149c1e..3e8e00096 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -71,7 +71,7 @@ typedef struct { {{#classes}} static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(PyObject *self); -static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, {{class_cname}} *native_ptr); +static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, const {{class_cname}} *native_ptr); {{/classes}} {{#classes}} @@ -80,7 +80,7 @@ static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(PyObject *self return ((pylinphone_{{class_name}}Object *)self)->native_ptr; } -static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, {{class_cname}} *native_ptr) { +static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, const {{class_cname}} *native_ptr) { {{{new_from_native_pointer_body}}} } From b7cf077e3f44677cc889589036e8b0fccf87294f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 15 Jul 2014 14:15:44 +0200 Subject: [PATCH 059/218] Fix search of *_get_user_data function in the Python wrapper. --- tools/python/apixml2python/linphone.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 86ec90e14..bcba50fc9 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -153,8 +153,9 @@ class MethodDefinition: if self.return_complete_type != 'void': if self.build_value_format == 'O': stripped_return_type = strip_leading_linphone(self.return_type) - if self.class_['class_has_user_data']: - get_user_data_function = self.__find_class_definition(self.return_type)['class_c_function_prefix'] + "get_user_data" + return_type_class = self.__find_class_definition(self.return_type) + if return_type_class['class_has_user_data']: + get_user_data_function = return_type_class['class_c_function_prefix'] + "get_user_data" self.body += "\tif ((cresult != NULL) && (" + get_user_data_function + "(cresult) != NULL)) {\n" self.body += "\t\treturn (PyObject *)" + get_user_data_function + "(cresult);\n" self.body += "\t}\n" From fb3b97bdea7e8af51d06b0e0c726b79a4c1c408c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 15 Jul 2014 14:21:28 +0200 Subject: [PATCH 060/218] Add missing const for getters' parameter. --- coreapi/linphonepresence.h | 10 +++++----- coreapi/presence.c | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/coreapi/linphonepresence.h b/coreapi/linphonepresence.h index 780ae22b6..014821328 100644 --- a/coreapi/linphonepresence.h +++ b/coreapi/linphonepresence.h @@ -752,7 +752,7 @@ LINPHONE_PUBLIC void linphone_presence_model_set_user_data(LinphonePresenceModel * @param[in] model The #LinphonePresenceModel object for which to get the user data. * @return A pointer to the user data. */ -LINPHONE_PUBLIC void * linphone_presence_model_get_user_data(LinphonePresenceModel *model); +LINPHONE_PUBLIC void * linphone_presence_model_get_user_data(const LinphonePresenceModel *model); /** * Increase the reference count of the #LinphonePresenceService object. @@ -780,7 +780,7 @@ LINPHONE_PUBLIC void linphone_presence_service_set_user_data(LinphonePresenceSer * @param[in] service The #LinphonePresenceService object for which to get the user data. * @return A pointer to the user data. */ -LINPHONE_PUBLIC void * linphone_presence_service_get_user_data(LinphonePresenceService *service); +LINPHONE_PUBLIC void * linphone_presence_service_get_user_data(const LinphonePresenceService *service); /** * Increase the reference count of the #LinphonePresencePerson object. @@ -808,7 +808,7 @@ LINPHONE_PUBLIC void linphone_presence_person_set_user_data(LinphonePresencePers * @param[in] person The #LinphonePresencePerson object for which to get the user data. * @return A pointer to the user data. */ -LINPHONE_PUBLIC void * linphone_presence_person_get_user_data(LinphonePresencePerson *person); +LINPHONE_PUBLIC void * linphone_presence_person_get_user_data(const LinphonePresencePerson *person); /** * Increase the reference count of the #LinphonePresenceActivity object. @@ -836,7 +836,7 @@ LINPHONE_PUBLIC void linphone_presence_activity_set_user_data(LinphonePresenceAc * @param[in] activity The #LinphonePresenceActivity object for which to get the user data. * @return A pointer to the user data. */ -LINPHONE_PUBLIC void * linphone_presence_activity_get_user_data(LinphonePresenceActivity *activity); +LINPHONE_PUBLIC void * linphone_presence_activity_get_user_data(const LinphonePresenceActivity *activity); /** * Increase the reference count of the #LinphonePresenceNote object. @@ -864,7 +864,7 @@ LINPHONE_PUBLIC void linphone_presence_note_set_user_data(LinphonePresenceNote * * @param[in] note The #LinphonePresenceNote object for which to get the user data. * @return A pointer to the user data. */ -LINPHONE_PUBLIC void * linphone_presence_note_get_user_data(LinphonePresenceNote *note); +LINPHONE_PUBLIC void * linphone_presence_note_get_user_data(const LinphonePresenceNote *note); /***************************************************************************** diff --git a/coreapi/presence.c b/coreapi/presence.c index 183002b86..97aca82b4 100644 --- a/coreapi/presence.c +++ b/coreapi/presence.c @@ -1043,7 +1043,7 @@ void linphone_presence_model_set_user_data(LinphonePresenceModel *model, void *u model->user_data = user_data; } -void * linphone_presence_model_get_user_data(LinphonePresenceModel *model) { +void * linphone_presence_model_get_user_data(const LinphonePresenceModel *model) { return model->user_data; } @@ -1065,7 +1065,7 @@ void linphone_presence_service_set_user_data(LinphonePresenceService *service, v service->user_data = user_data; } -void * linphone_presence_service_get_user_data(LinphonePresenceService *service) { +void * linphone_presence_service_get_user_data(const LinphonePresenceService *service) { return service->user_data; } @@ -1087,7 +1087,7 @@ void linphone_presence_person_set_user_data(LinphonePresencePerson *person, void person->user_data = user_data; } -void * linphone_presence_person_get_user_data(LinphonePresencePerson *person) { +void * linphone_presence_person_get_user_data(const LinphonePresencePerson *person) { return person->user_data; } @@ -1109,7 +1109,7 @@ void linphone_presence_activity_set_user_data(LinphonePresenceActivity *activity activity->user_data = user_data; } -void * linphone_presence_activity_get_user_data(LinphonePresenceActivity *activity) { +void * linphone_presence_activity_get_user_data(const LinphonePresenceActivity *activity) { return activity->user_data; } @@ -1131,7 +1131,7 @@ void linphone_presence_note_set_user_data(LinphonePresenceNote *note, void *user note->user_data = user_data; } -void * linphone_presence_note_get_user_data(LinphonePresenceNote *note) { +void * linphone_presence_note_get_user_data(const LinphonePresenceNote *note) { return note->user_data; } From 975f434f9c29ad7f663150fd674b993657a1c207 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 15 Jul 2014 16:28:44 +0200 Subject: [PATCH 061/218] Handle object arguments correctly in the setters of the Python wrapper. --- tools/python/apixml2python/linphone.py | 30 ++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index bcba50fc9..fa4227b6c 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -70,7 +70,7 @@ class MethodDefinition: def format_native_pointer_checking(self, return_int): self.format_native_pointer_get() self.body += "\tif (native_ptr == NULL) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid " + self.class_['class_name'] + " instance\");\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid linphone." + self.class_['class_name'] + " instance\");\n" if return_int: self.body += "\t\treturn -1;\n" else: @@ -110,19 +110,31 @@ class MethodDefinition: self.body += "\t}\n" # Check the value type_str, checkfunc, convertfunc = self.__ctype_to_python_type(first_arg_type, first_arg_complete_type) - self.body += "\tif (!" + checkfunc + "(value)) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"The " + attribute_name + " attribute value must be a " + type_str + "\");\n" - self.body += "\t\treturn -1;\n" - self.body += "\t}\n" + first_arg_class = strip_leading_linphone(first_arg_type) + if checkfunc is None: + self.body += "\tif (!PyObject_IsInstance(value, (PyObject *)&pylinphone_" + first_arg_class + "Type)) {\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"The " + attribute_name + " attribute value must be a linphone." + first_arg_class + " instance\");\n" + self.body += "\t\treturn -1;\n" + self.body += "\t}\n" + else: + self.body += "\tif (!" + checkfunc + "(value)) {\n" + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"The " + attribute_name + " attribute value must be a " + type_str + "\");\n" + self.body += "\t\treturn -1;\n" + self.body += "\t}\n" # Call the C function if convertfunc is None: - pass # TODO + self.body += "\t" + first_arg_name + " = value;\n" else: self.body += "\t" + first_arg_name + " = (" + first_arg_complete_type + ")" + convertfunc + "(value);\n" if self.__ctype_to_python_format(first_arg_type, first_arg_complete_type) == 'O': - pass # TODO + self.body += "\t" + first_arg_name + "_native_ptr = pylinphone_" + first_arg_class + "_get_native_ptr(" + first_arg_name + ");\n" + self.body += "\tif (%s_native_ptr == NULL) {\n" % (first_arg_name) + self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid linphone." + first_arg_class + " instance\");\n" + self.body += "\t\treturn -1;\n" + self.body += "\t}\n" + self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + first_arg_name + "_native_ptr);\n" else: - self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + self.arg_names[0] + ");\n" + self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + first_arg_name + ");\n" self.body += "\treturn 0;" def format_tracing(self): @@ -260,7 +272,7 @@ class MethodDefinition: if strip_leading_linphone(basic_type) in self.linphone_module.enum_names: return ('int', 'PyInt_Check', 'PyInt_AsLong') else: - return ('class instance', 'PyInstance_Check', None) + return (None, None, None) def __find_class_definition(self, basic_type): basic_type = strip_leading_linphone(basic_type) From cfc9bdd5502d1b5754caf2005b663c988e1cb927 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 15 Jul 2014 16:57:51 +0200 Subject: [PATCH 062/218] Improve readability of code snippets in the Python wrapper generator. --- tools/python/apixml2python/linphone.py | 161 ++++++++++++++++--------- 1 file changed, 102 insertions(+), 59 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index fa4227b6c..243cc2e6a 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -65,17 +65,21 @@ class MethodDefinition: self.arg_names.append(arg_name) def format_native_pointer_get(self): - self.body += "\tnative_ptr = pylinphone_" + self.class_['class_name'] + "_get_native_ptr(self);\n" + self.body += \ +""" native_ptr = pylinphone_{class_name}_get_native_ptr(self); +""".format(class_name=self.class_['class_name']) def format_native_pointer_checking(self, return_int): self.format_native_pointer_get() - self.body += "\tif (native_ptr == NULL) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid linphone." + self.class_['class_name'] + " instance\");\n" + return_value = "NULL" if return_int: - self.body += "\t\treturn -1;\n" - else: - self.body += "\t\treturn NULL;\n" - self.body += "\t}\n" + return_value = "-1" + self.body += \ +""" if (native_ptr == NULL) {{ + PyErr_SetString(PyExc_TypeError, "Invalid linphone.{class_name} instance"); + return {return_value}; + }} +""".format(class_name=self.class_['class_name'], return_value=return_value) def format_object_args_native_pointers_checking(self): for xml_method_arg in self.xml_method_args: @@ -84,17 +88,21 @@ class MethodDefinition: arg_complete_type = xml_method_arg.get('completetype') fmt = self.__ctype_to_python_format(arg_type, arg_complete_type) if fmt == 'O': - self.body += "\tif ((" + arg_name + "_native_ptr = pylinphone_" + strip_leading_linphone(arg_type) + "_get_native_ptr(" + arg_name + ")) == NULL) {\n" - self.body += "\t\treturn NULL;\n" - self.body += "\t}\n" + self.body += \ +""" if (({arg_name}_native_ptr = pylinphone_{arg_type}_get_native_ptr({arg_name})) == NULL) {{ + return NULL; + }} +""".format(arg_name=arg_name, arg_type=strip_leading_linphone(arg_type)) def format_arguments_parsing(self): if self.self_arg is not None: self.format_native_pointer_checking(False) if len(self.arg_names) > 0: - self.body += "\tif (!PyArg_ParseTuple(args, \"" + self.parse_tuple_format + "\"" - self.body += ', ' + ', '.join(map(lambda a: '&' + a, self.arg_names)) - self.body += ")) {\n\t\treturn NULL;\n\t}\n" + self.body += \ +""" if (!PyArg_ParseTuple(args, "{fmt}", {args})) {{ + return NULL; + }} +""".format(fmt=self.parse_tuple_format, args=', '.join(map(lambda a: '&' + a, self.arg_names))) self.format_object_args_native_pointers_checking() def format_setter_value_checking_and_c_function_call(self): @@ -104,41 +112,58 @@ class MethodDefinition: first_arg_name = self.xml_method_args[0].get('name') self.format_native_pointer_checking(True) # Check that the value exists - self.body += "\tif (value == NULL) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Cannot delete the " + attribute_name + " attribute\");\n" - self.body += "\t\treturn -1;\n" - self.body += "\t}\n" + self.body += \ +""" if (value == NULL) {{ + PyErr_SetString(PyExc_TypeError, "Cannot delete the {attribute_name} attribute"); + return -1; + }} +""".format(attribute_name=attribute_name) # Check the value type_str, checkfunc, convertfunc = self.__ctype_to_python_type(first_arg_type, first_arg_complete_type) first_arg_class = strip_leading_linphone(first_arg_type) if checkfunc is None: - self.body += "\tif (!PyObject_IsInstance(value, (PyObject *)&pylinphone_" + first_arg_class + "Type)) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"The " + attribute_name + " attribute value must be a linphone." + first_arg_class + " instance\");\n" - self.body += "\t\treturn -1;\n" - self.body += "\t}\n" + self.body += \ +""" if (!PyObject_IsInstance(value, (PyObject *)&pylinphone_{class_name}Type)) {{ + PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a linphone.{class_name} instance"); + return -1; + }} +""".format(class_name=first_arg_class, attribute_name=attribute_name) else: - self.body += "\tif (!" + checkfunc + "(value)) {\n" - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"The " + attribute_name + " attribute value must be a " + type_str + "\");\n" - self.body += "\t\treturn -1;\n" - self.body += "\t}\n" + self.body += \ +""" if (!{checkfunc}(value)) {{ + PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a {type_str}"); + return -1; + }} +""".format(checkfunc=checkfunc, attribute_name=attribute_name, type_str=type_str) # Call the C function if convertfunc is None: - self.body += "\t" + first_arg_name + " = value;\n" + self.body += \ +""" {arg_name} = value; +""".format(arg_name=first_arg_name) else: - self.body += "\t" + first_arg_name + " = (" + first_arg_complete_type + ")" + convertfunc + "(value);\n" + self.body += \ +""" {arg_name} = ({arg_type}){convertfunc}(value); +""".format(arg_name=first_arg_name, arg_type=first_arg_complete_type, convertfunc=convertfunc) if self.__ctype_to_python_format(first_arg_type, first_arg_complete_type) == 'O': - self.body += "\t" + first_arg_name + "_native_ptr = pylinphone_" + first_arg_class + "_get_native_ptr(" + first_arg_name + ");\n" - self.body += "\tif (%s_native_ptr == NULL) {\n" % (first_arg_name) - self.body += "\t\tPyErr_SetString(PyExc_TypeError, \"Invalid linphone." + first_arg_class + " instance\");\n" - self.body += "\t\treturn -1;\n" - self.body += "\t}\n" - self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + first_arg_name + "_native_ptr);\n" + self.body += \ +""" {arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name}); + if ({arg_name}_native_ptr == NULL) {{ + PyErr_SetString(PyExc_TypeError, "Invalid linphone.{arg_class} instance"); + return -1; + }} + {method_name}(native_ptr, {arg_name}_native_ptr); +""".format(arg_name=first_arg_name, arg_class=first_arg_class, method_name=self.method_node.get('name')) else: - self.body += "\t" + self.method_node.get('name') + "(native_ptr, " + first_arg_name + ");\n" - self.body += "\treturn 0;" + self.body += \ +""" {method_name}(native_ptr, {arg_name}); +""".format(method_name=self.method_node.get('name'), arg_name=first_arg_name) + self.body += \ +""" return 0;""" def format_tracing(self): - self.body += "\tpylinphone_trace(__FUNCTION__);\n" + self.body += \ +""" pylinphone_trace(__FUNCTION__); +""" def format_c_function_call(self): arg_names = [] @@ -168,39 +193,57 @@ class MethodDefinition: return_type_class = self.__find_class_definition(self.return_type) if return_type_class['class_has_user_data']: get_user_data_function = return_type_class['class_c_function_prefix'] + "get_user_data" - self.body += "\tif ((cresult != NULL) && (" + get_user_data_function + "(cresult) != NULL)) {\n" - self.body += "\t\treturn (PyObject *)" + get_user_data_function + "(cresult);\n" - self.body += "\t}\n" - self.body += "\tpyresult = pylinphone_" + stripped_return_type + "_new_from_native_ptr(&pylinphone_" + stripped_return_type + "Type, cresult);\n" - self.body += "\tpyret = Py_BuildValue(\"" + self.build_value_format + "\", pyresult);\n" - self.body += "\tPy_DECREF(pyresult);\n" - self.body += "\treturn pyret;" + self.body += \ +""" if ((cresult != NULL) && ({func}(cresult) != NULL)) {{ + return (PyObject *){func}(cresult); + }} +""".format(func=get_user_data_function) + self.body += \ +""" pyresult = pylinphone_{return_type}_new_from_native_ptr(&pylinphone_{return_type}Type, cresult); + pyret = Py_BuildValue("{fmt}", pyresult); + Py_DECREF(pyresult); + return pyret; +""".format(return_type=stripped_return_type, fmt=self.build_value_format) else: - self.body += "\treturn Py_BuildValue(\"" + self.build_value_format + "\", cresult);" + self.body += \ +""" return Py_BuildValue("{fmt}", cresult);""".format(fmt=self.build_value_format) else: - self.body += "\tPy_RETURN_NONE;" + self.body += \ +""" Py_RETURN_NONE;""" def format_new_from_native_pointer_body(self): - self.body += "\tpylinphone_" + self.class_['class_name'] + "Object *self;\n" + self.body += \ +""" pylinphone_{class_name}Object *self; +""".format(class_name=self.class_['class_name']) self.format_tracing() - self.body += "\tif (native_ptr == NULL) Py_RETURN_NONE;\n" - self.body += "\tself = (pylinphone_" + self.class_['class_name'] + "Object *)PyObject_New(pylinphone_" + self.class_['class_name'] + "Object, type);\n" - self.body += "\tif (self == NULL) Py_RETURN_NONE;\n" - self.body += "\tself->native_ptr = (" + self.class_['class_cname'] + " *)native_ptr;\n" + self.body += \ +""" if (native_ptr == NULL) Py_RETURN_NONE; + self = (pylinphone_{class_name}Object *)PyObject_New(pylinphone_{class_name}Object, type); + if (self == NULL) Py_RETURN_NONE; + self->native_ptr = ({class_cname} *)native_ptr; +""".format(class_name=self.class_['class_name'], class_cname=self.class_['class_cname']) if self.class_['class_has_user_data']: - self.body += "\t" + self.class_['class_c_function_prefix'] + "set_user_data(self->native_ptr, self);\n" - self.body += "\treturn (PyObject *)self;" + self.body += \ +""" {function_prefix}set_user_data(self->native_ptr, self); +""".format(function_prefix=self.class_['class_c_function_prefix']) + self.body += \ +""" return (PyObject *)self;""" def format_dealloc_c_function_call(self): if self.class_['class_refcountable']: - self.body += "\tif (native_ptr != NULL) {\n" - self.body += "\t\t" + self.class_['class_c_function_prefix'] + "unref(native_ptr);\n" - self.body += "\t}\n" + self.body += \ +""" if (native_ptr != NULL) {{ + {function_prefix}unref(native_ptr); + }} +""".format(function_prefix=self.class_['class_c_function_prefix']) elif self.class_['class_destroyable']: - self.body += "\tif (native_ptr != NULL) {\n" - self.body += "\t\t" + self.class_['class_c_function_prefix'] + "destroy(native_ptr);\n" - self.body += "\t}\n" - self.body += "\tself->ob_type->tp_free(self);" + self.body += \ +""" if (native_ptr != NULL) {{ + {function_prefix}destroy(native_ptr); + }} +""".format(function_prefix=self.class_['class_c_function_prefix']) + self.body += \ +""" self->ob_type->tp_free(self);""" def __ctype_to_python_format(self, basic_type, complete_type): splitted_type = complete_type.split(' ') From c3024aa772d1c4705c144a2d43fc3f3c02383a51 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 15 Jul 2014 18:21:28 +0200 Subject: [PATCH 063/218] Improve logging of the Python wrapper. --- tools/python/apixml2python/linphone.py | 2 +- .../apixml2python/linphone_module.mustache | 39 +++++++++++++++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 243cc2e6a..904fd820a 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -162,7 +162,7 @@ class MethodDefinition: def format_tracing(self): self.body += \ -""" pylinphone_trace(__FUNCTION__); +""" pylinphone_debug("[PYLINPHONE] %s", __FUNCTION__); """ def format_c_function_call(self): diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 3e8e00096..9400dab74 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -18,6 +18,14 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include +#include + + +#ifdef _MSC_VER +#define PYLINPHONE_INLINE __inline +#else +#define PYLINPHONE_INLINE inline +#endif static PyObject *logging_module = NULL; @@ -44,16 +52,33 @@ static void init_logging(void) { } } -static void pylinphone_log(const char *level, const char *fmt) { +static void pylinphone_log(const char *level, const char *fmt, va_list args) { if (logging_module != NULL) { - PyEval_CallMethod(logging_module, level, "(s)", fmt); + char logstr[4096]; + if (vsnprintf(logstr, sizeof(logstr), fmt, args) > 0) { + PyEval_CallMethod(logging_module, level, "(s)", logstr); + } } } -#define pylinphone_debug(fmt) pylinphone_log("debug", fmt) -#define pylinphone_info(fmt) pylinphone_log("info", fmt) -#define pylinphone_warning(fmt) pylinphone_log("warning", fmt) -#define pylinphone_trace pylinphone_debug +static PYLINPHONE_INLINE void pylinphone_debug(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + pylinphone_log("debug", fmt, args); + va_end(args); +} +static PYLINPHONE_INLINE void pylinphone_info(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + pylinphone_log("info", fmt, args); + va_end(args); +} +static PYLINPHONE_INLINE void pylinphone_warning(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + pylinphone_log("warning", fmt, args); + va_end(args); +} {{#classes}} @@ -86,7 +111,7 @@ static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *ty static PyObject * pylinphone_{{class_name}}_new(PyTypeObject *type, PyObject *args, PyObject *kw) { pylinphone_{{class_name}}Object *self = (pylinphone_{{class_name}}Object *)type->tp_alloc(type, 0); - pylinphone_trace(__FUNCTION__); + pylinphone_debug("[PYLINPHONE] %s", __FUNCTION__); self->native_ptr = NULL; return (PyObject *)self; } From c5d92aed0ad0ac135c6dda5a43089a7c81971355 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Tue, 15 Jul 2014 22:58:24 +0200 Subject: [PATCH 064/218] add test for chat room object --- tester/setup_tester.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tester/setup_tester.c b/tester/setup_tester.c index 546042714..cc72e2e95 100644 --- a/tester/setup_tester.c +++ b/tester/setup_tester.c @@ -166,6 +166,16 @@ void linphone_proxy_config_is_server_config_changed_test() { linphone_proxy_config_destroy(proxy_config); } +static void chat_root_test(void) { + LinphoneCoreVTable v_table; + LinphoneCore* lc; + memset (&v_table,0,sizeof(v_table)); + lc = linphone_core_new(&v_table,NULL,NULL,NULL); + CU_ASSERT_PTR_NOT_NULL_FATAL(lc); + linphone_core_create_chat_room(lc,"sip:toto@titi.com"); + linphone_core_destroy(lc); +} + test_t setup_tests[] = { { "Linphone Address", linphone_address_test }, { "Linphone proxy config address equal (internal api)", linphone_proxy_config_address_equal_test}, @@ -173,7 +183,8 @@ test_t setup_tests[] = { { "Linphone core init/uninit", core_init_test }, { "Linphone random transport port",core_sip_transport_test}, { "Linphone interpret url", linphone_interpret_url_test }, - { "LPConfig from buffer", linphone_lpconfig_from_buffer } + { "LPConfig from buffer", linphone_lpconfig_from_buffer }, + { "Chat room", chat_root_test } }; test_suite_t setup_test_suite = { From 2ec33e6518fd0f422b4c9ad956ad340ed0777aad Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 16 Jul 2014 14:24:48 +0200 Subject: [PATCH 065/218] Update ms2 submodule. --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index b40af312e..a921798e2 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit b40af312e90b6c91bbee360f430ed87fa26119e9 +Subproject commit a921798e217000c6c286942acc140e58b14fbcf5 From 2b1f7c50aa946e10053836c0e41657342b401d24 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 16 Jul 2014 14:51:05 +0200 Subject: [PATCH 066/218] Generate new method body by code instead of a simple template in the Python wrapper. --- tools/python/apixml2python/linphone.py | 17 +++++++++++++++++ .../apixml2python/linphone_module.mustache | 5 +---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 904fd820a..8704c0671 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -211,6 +211,16 @@ class MethodDefinition: self.body += \ """ Py_RETURN_NONE;""" + def format_new_body(self): + self.body += \ +""" pylinphone_{class_name}Object *self = (pylinphone_{class_name}Object *)type->tp_alloc(type, 0); +""".format(class_name=self.class_['class_name']) + self.format_tracing() + self.body += \ +""" self->native_ptr = NULL; + return (PyObject *)self; +""" + def format_new_from_native_pointer_body(self): self.body += \ """ pylinphone_{class_name}Object *self; @@ -430,6 +440,8 @@ class LinphoneModule(object): self.classes.append(c) # Format methods' bodies for c in self.classes: + xml_new_method = c['class_xml_node'].find("./classmethods/classmethod[@name='" + c['class_c_function_prefix'] + "new']") + c['new_body'] = self.__format_new_body(xml_new_method, c) for m in c['class_type_methods']: m['method_body'] = self.__format_method_body(m['method_xml_node'], c) for m in c['class_instance_methods']: @@ -477,6 +489,11 @@ class LinphoneModule(object): method.format_setter_value_checking_and_c_function_call() return method.body + def __format_new_body(self, method_node, class_): + method = MethodDefinition(method_node, class_, self) + method.format_new_body() + return method.body + def __format_new_from_native_pointer_body(self, method_node, class_): method = MethodDefinition(method_node, class_, self) # Force return value type of dealloc function to prevent declaring useless local variables diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 9400dab74..85889b4ae 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -110,10 +110,7 @@ static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *ty } static PyObject * pylinphone_{{class_name}}_new(PyTypeObject *type, PyObject *args, PyObject *kw) { - pylinphone_{{class_name}}Object *self = (pylinphone_{{class_name}}Object *)type->tp_alloc(type, 0); - pylinphone_debug("[PYLINPHONE] %s", __FUNCTION__); - self->native_ptr = NULL; - return (PyObject *)self; +{{{new_body}}} } static void pylinphone_{{class_name}}_dealloc(PyObject *self) { From 302e97147478311f9df6084f0b03a2810cbf3116 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 16 Jul 2014 16:23:08 +0200 Subject: [PATCH 067/218] Improve tracing to show arguments and return values in the Python wrapper. --- tools/python/apixml2python/linphone.py | 144 +++++++++++++++--- .../apixml2python/linphone_module.mustache | 29 +++- 2 files changed, 150 insertions(+), 23 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 8704c0671..a8eb2e198 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -46,7 +46,7 @@ class MethodDefinition: self.build_value_format = self.__ctype_to_python_format(self.return_type, self.return_complete_type) if self.build_value_format == 'O': self.body += "\tPyObject * pyresult;\n" - self.body += "\tPyObject * pyret;\n" + self.body += "\tPyObject * pyret;\n" if self.self_arg is not None: self.body += "\t" + self.self_arg.get('completetype') + "native_ptr;\n" for xml_method_arg in self.xml_method_args: @@ -151,18 +151,69 @@ class MethodDefinition: PyErr_SetString(PyExc_TypeError, "Invalid linphone.{arg_class} instance"); return -1; }} - {method_name}(native_ptr, {arg_name}_native_ptr); -""".format(arg_name=first_arg_name, arg_class=first_arg_class, method_name=self.method_node.get('name')) +""".format(arg_name=first_arg_name, arg_class=first_arg_class) + self.format_enter_function_trace() + self.body += \ +""" {method_name}(native_ptr, {arg_name}_native_ptr); +""".format(arg_name=first_arg_name, method_name=self.method_node.get('name')) else: + self.format_enter_function_trace() self.body += \ """ {method_name}(native_ptr, {arg_name}); """.format(method_name=self.method_node.get('name'), arg_name=first_arg_name) self.body += \ """ return 0;""" - def format_tracing(self): + def format_enter_constructor_trace(self): self.body += \ -""" pylinphone_debug("[PYLINPHONE] %s", __FUNCTION__); +""" pylinphone_trace(1, "[PYLINPHONE] %s()", __FUNCTION__); +""" + + def format_return_constructor_trace(self): + self.body += \ +""" pylinphone_trace(-1, "[PYLINPHONE] %s -> %p", __FUNCTION__, self); +""" + + def format_enter_constructor_from_native_ptr_trace(self): + self.body += \ +""" pylinphone_trace(1, "[PYLINPHONE] %s(%p)", __FUNCTION__, native_ptr); +""" + + def format_return_constructor_from_native_ptr_trace(self): + self.body += \ +""" pylinphone_trace(-1, "[PYLINPHONE] %s -> %p", __FUNCTION__, self); +""" + + def format_dealloc_trace(self): + self.body += \ +""" pylinphone_trace(0, "[PYLINPHONE] %s(%p [%p])", __FUNCTION__, self, native_ptr); +""" + + def format_enter_function_trace(self): + fmt = '' + args = [] + if self.self_arg is not None: + fmt += "%p [%p]" + args += ["self", "native_ptr"] + for xml_method_arg in self.xml_method_args: + arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + if fmt != '': + fmt += ', ' + f, a = self.__ctype_to_str_format(arg_name, arg_type, arg_complete_type) + fmt += f + args += a + args=', '.join(args) + if args != '': + args = ', ' + args + self.body += \ +""" pylinphone_trace(1, "[PYLINPHONE] %s({fmt})", __FUNCTION__{args}); +""".format(fmt=fmt, args=args) + + def format_return_function_trace(self): + self.body += \ +""" pylinphone_trace(-1, "[PYLINPHONE] %s -> %p", __FUNCTION__, pyret); """ def format_c_function_call(self): @@ -201,12 +252,17 @@ class MethodDefinition: self.body += \ """ pyresult = pylinphone_{return_type}_new_from_native_ptr(&pylinphone_{return_type}Type, cresult); pyret = Py_BuildValue("{fmt}", pyresult); - Py_DECREF(pyresult); - return pyret; """.format(return_type=stripped_return_type, fmt=self.build_value_format) + self.format_return_function_trace() + self.body += \ +""" Py_DECREF(pyresult); + return pyret;""" else: self.body += \ -""" return Py_BuildValue("{fmt}", cresult);""".format(fmt=self.build_value_format) +""" pyret = Py_BuildValue("{fmt}", cresult); +""".format(fmt=self.build_value_format) + self.format_return_function_trace() + self.body += """ return pyret;""" else: self.body += \ """ Py_RETURN_NONE;""" @@ -215,17 +271,20 @@ class MethodDefinition: self.body += \ """ pylinphone_{class_name}Object *self = (pylinphone_{class_name}Object *)type->tp_alloc(type, 0); """.format(class_name=self.class_['class_name']) - self.format_tracing() + self.format_enter_constructor_trace() self.body += \ """ self->native_ptr = NULL; - return (PyObject *)self; +""" + self.format_return_constructor_trace() + self.body += \ +""" return (PyObject *)self; """ def format_new_from_native_pointer_body(self): self.body += \ """ pylinphone_{class_name}Object *self; """.format(class_name=self.class_['class_name']) - self.format_tracing() + self.format_enter_constructor_from_native_ptr_trace() self.body += \ """ if (native_ptr == NULL) Py_RETURN_NONE; self = (pylinphone_{class_name}Object *)PyObject_New(pylinphone_{class_name}Object, type); @@ -236,6 +295,7 @@ class MethodDefinition: self.body += \ """ {function_prefix}set_user_data(self->native_ptr, self); """.format(function_prefix=self.class_['class_c_function_prefix']) + self.format_return_constructor_from_native_ptr_trace() self.body += \ """ return (PyObject *)self;""" @@ -255,6 +315,46 @@ class MethodDefinition: self.body += \ """ self->ob_type->tp_free(self);""" + def __ctype_to_str_format(self, name, basic_type, complete_type): + splitted_type = complete_type.split(' ') + if basic_type == 'char': + if '*' in splitted_type: + return ('\\"%s\\"', [name]) + elif 'unsigned' in splitted_type: + return ('%08x', [name]) + elif basic_type == 'int': + # TODO: + return ('%d', [name]) + elif basic_type == 'int8_t': + return ('%d', [name]) + elif basic_type == 'uint8_t': + return ('%u', [name]) + elif basic_type == 'int16_t': + return ('%d', [name]) + elif basic_type == 'uint16_t': + return ('%u', [name]) + elif basic_type == 'int32_t': + return ('%d', [name]) + elif basic_type == 'uint32_t': + return ('%u', [name]) + elif basic_type == 'int64_t': + return ('%ld', [name]) + elif basic_type == 'uint64_t': + return ('%lu', [name]) + elif basic_type == 'size_t': + return ('%lu', [name]) + elif basic_type == 'float': + return ('%f', [name]) + elif basic_type == 'double': + return ('%f', [name]) + elif basic_type == 'bool_t': + return ('%d', [name]) + else: + if strip_leading_linphone(basic_type) in self.linphone_module.enum_names: + return ('%d', [name]) + else: + return ('%p [%p]', [name, name + "_native_ptr"]) + def __ctype_to_python_format(self, basic_type, complete_type): splitted_type = complete_type.split(' ') if basic_type == 'char': @@ -443,9 +543,9 @@ class LinphoneModule(object): xml_new_method = c['class_xml_node'].find("./classmethods/classmethod[@name='" + c['class_c_function_prefix'] + "new']") c['new_body'] = self.__format_new_body(xml_new_method, c) for m in c['class_type_methods']: - m['method_body'] = self.__format_method_body(m['method_xml_node'], c) + m['method_body'] = self.__format_class_method_body(m['method_xml_node'], c) for m in c['class_instance_methods']: - m['method_body'] = self.__format_method_body(m['method_xml_node'], c) + m['method_body'] = self.__format_instance_method_body(m['method_xml_node'], c) for p in c['class_properties']: if p.has_key('getter_xml_node'): p['getter_body'] = self.__format_getter_body(p['getter_xml_node'], c) @@ -460,11 +560,20 @@ class LinphoneModule(object): c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(xml_instance_method, c) c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) - def __format_method_body(self, method_node, class_): + def __format_class_method_body(self, method_node, class_): method = MethodDefinition(method_node, class_, self) method.format_local_variables_definition() method.format_arguments_parsing() - method.format_tracing() + method.format_enter_function_trace() + method.format_c_function_call() + method.format_method_result() + return method.body + + def __format_instance_method_body(self, method_node, class_): + method = MethodDefinition(method_node, class_, self) + method.format_local_variables_definition() + method.format_arguments_parsing() + method.format_enter_function_trace() method.format_c_function_call() method.format_method_result() return method.body @@ -473,7 +582,7 @@ class LinphoneModule(object): method = MethodDefinition(getter_node, class_, self) method.format_local_variables_definition() method.format_arguments_parsing() - method.format_tracing() + method.format_enter_function_trace() method.format_c_function_call() method.format_method_result() return method.body @@ -485,7 +594,6 @@ class LinphoneModule(object): method.xml_method_return.set('type', 'void') method.xml_method_return.set('completetype', 'void') method.format_local_variables_definition() - method.format_tracing() method.format_setter_value_checking_and_c_function_call() return method.body @@ -509,7 +617,7 @@ class LinphoneModule(object): method.xml_method_return.set('completetype', 'void') method.format_local_variables_definition() method.format_native_pointer_get() - method.format_tracing() + method.format_dealloc_trace() method.format_dealloc_c_function_call() return method.body diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 85889b4ae..e880c68bc 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -29,6 +29,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. static PyObject *logging_module = NULL; +static int current_indent = 1; static void init_logging(void) { @@ -52,10 +53,19 @@ static void init_logging(void) { } } -static void pylinphone_log(const char *level, const char *fmt, va_list args) { +static void pylinphone_log(const char *level, int indent, const char *fmt, va_list args) { if (logging_module != NULL) { char logstr[4096]; - if (vsnprintf(logstr, sizeof(logstr), fmt, args) > 0) { + int i = 0; + if (indent == -1) current_indent--; + if (current_indent < 1) current_indent = 1; + if ((indent >= -1) && (indent <= 1)) { + for (i = 0; i < current_indent; i++) { + logstr[i] = '\t'; + } + } + if (indent == 1) current_indent++; + if (vsnprintf(logstr + i, sizeof(logstr) - i, fmt, args) > 0) { PyEval_CallMethod(logging_module, level, "(s)", logstr); } } @@ -64,19 +74,28 @@ static void pylinphone_log(const char *level, const char *fmt, va_list args) { static PYLINPHONE_INLINE void pylinphone_debug(const char *fmt, ...) { va_list args; va_start(args, fmt); - pylinphone_log("debug", fmt, args); + pylinphone_log("debug", 0xff, fmt, args); va_end(args); } + static PYLINPHONE_INLINE void pylinphone_info(const char *fmt, ...) { va_list args; va_start(args, fmt); - pylinphone_log("info", fmt, args); + pylinphone_log("info", 0xff, fmt, args); va_end(args); } + static PYLINPHONE_INLINE void pylinphone_warning(const char *fmt, ...) { va_list args; va_start(args, fmt); - pylinphone_log("warning", fmt, args); + pylinphone_log("warning", 0xff, fmt, args); + va_end(args); +} + +static PYLINPHONE_INLINE void pylinphone_trace(int indent, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + pylinphone_log("debug", indent, fmt, args); va_end(args); } From 4b3a1fb01637aeee9440f53f65095656e4fec6f1 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 16 Jul 2014 17:58:41 +0200 Subject: [PATCH 068/218] Improve Python wrapper tracing a little more + fix a refcounting issue. --- tools/python/apixml2python/linphone.py | 46 +++++++++++++++++++++----- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index a8eb2e198..646d144b4 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -161,6 +161,7 @@ class MethodDefinition: self.body += \ """ {method_name}(native_ptr, {arg_name}); """.format(method_name=self.method_node.get('name'), arg_name=first_arg_name) + self.format_return_setter_trace() self.body += \ """ return 0;""" @@ -214,6 +215,16 @@ class MethodDefinition: def format_return_function_trace(self): self.body += \ """ pylinphone_trace(-1, "[PYLINPHONE] %s -> %p", __FUNCTION__, pyret); +""" + + def format_return_setter_trace(self): + self.body += \ +""" pylinphone_trace(-1, "[PYLINPHONE] %s -> 0", __FUNCTION__); +""" + + def format_return_none_trace(self): + self.body += \ +""" pylinphone_trace(-1, "[PYLINPHONE] %s -> None", __FUNCTION__); """ def format_c_function_call(self): @@ -251,8 +262,15 @@ class MethodDefinition: """.format(func=get_user_data_function) self.body += \ """ pyresult = pylinphone_{return_type}_new_from_native_ptr(&pylinphone_{return_type}Type, cresult); - pyret = Py_BuildValue("{fmt}", pyresult); -""".format(return_type=stripped_return_type, fmt=self.build_value_format) +""".format(return_type=stripped_return_type) + if self.self_arg is not None and return_type_class['class_refcountable']: + ref_function = return_type_class['class_c_function_prefix'] + "ref" + self.body += \ +""" {func}(cresult); +""".format(func=ref_function) + self.body += \ +""" pyret = Py_BuildValue("{fmt}", pyresult); +""".format(fmt=self.build_value_format) self.format_return_function_trace() self.body += \ """ Py_DECREF(pyresult); @@ -286,11 +304,21 @@ class MethodDefinition: """.format(class_name=self.class_['class_name']) self.format_enter_constructor_from_native_ptr_trace() self.body += \ -""" if (native_ptr == NULL) Py_RETURN_NONE; +""" if (native_ptr == NULL) { + """ + self.format_return_none_trace() + self.body += \ +""" Py_RETURN_NONE; + }} self = (pylinphone_{class_name}Object *)PyObject_New(pylinphone_{class_name}Object, type); - if (self == NULL) Py_RETURN_NONE; + if (self == NULL) {{ + """.format(class_name=self.class_['class_name']) + self.format_return_none_trace() + self.body += \ +""" Py_RETURN_NONE; + }} self->native_ptr = ({class_cname} *)native_ptr; -""".format(class_name=self.class_['class_name'], class_cname=self.class_['class_cname']) +""".format(class_cname=self.class_['class_cname']) if self.class_['class_has_user_data']: self.body += \ """ {function_prefix}set_user_data(self->native_ptr, self); @@ -598,9 +626,11 @@ class LinphoneModule(object): return method.body def __format_new_body(self, method_node, class_): - method = MethodDefinition(method_node, class_, self) - method.format_new_body() - return method.body + if method_node is not None: + method = MethodDefinition(method_node, class_, self) + method.format_new_body() + return method.body + return '' def __format_new_from_native_pointer_body(self, method_node, class_): method = MethodDefinition(method_node, class_, self) From fa38f30896b873bd4d235631a44fc944e54bad2c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 16 Jul 2014 18:09:15 +0200 Subject: [PATCH 069/218] Fix compilation warnings in the Python wrapper. --- tools/python/apixml2python/linphone.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 646d144b4..80f6733ce 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -266,8 +266,8 @@ class MethodDefinition: if self.self_arg is not None and return_type_class['class_refcountable']: ref_function = return_type_class['class_c_function_prefix'] + "ref" self.body += \ -""" {func}(cresult); -""".format(func=ref_function) +""" {func}(({cast_type})cresult); +""".format(func=ref_function, cast_type=self.__remove_const_from_complete_type(self.return_complete_type)) self.body += \ """ pyret = Py_BuildValue("{fmt}", pyresult); """.format(fmt=self.build_value_format) @@ -343,6 +343,12 @@ class MethodDefinition: self.body += \ """ self->ob_type->tp_free(self);""" + def __remove_const_from_complete_type(self, complete_type): + splitted_type = complete_type.split(' ') + while 'const' in splitted_type: + splitted_type.remove('const') + return ' '.join(splitted_type) + def __ctype_to_str_format(self, name, basic_type, complete_type): splitted_type = complete_type.split(' ') if basic_type == 'char': From 7b47e83310abd1d8e84965102b25528cd95ab66e Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 16 Jul 2014 18:09:32 +0200 Subject: [PATCH 070/218] Include linphone_tunnel.h in the Python wrapper. --- tools/python/apixml2python/linphone_module.mustache | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index e880c68bc..3529904c1 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -18,6 +18,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include +#include #include From 1f65d8d709d3dae83221fa21de475dec846b4d2e Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 09:52:08 +0200 Subject: [PATCH 071/218] Fill *_new(), *_new_from_native_pointer() and *_dealloc() functions for class with no public constructors in the Python wrapper. --- tools/python/apixml2python/linphone.py | 41 +++++++++++++++----------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 80f6733ce..cbb0908a8 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -31,9 +31,14 @@ class MethodDefinition: self.class_ = class_ self.linphone_module = linphone_module self.self_arg = None - self.xml_method_return = self.method_node.find('./return') - self.xml_method_args = self.method_node.findall('./arguments/argument') - self.method_type = self.method_node.tag + if self.method_node is None: + self.xml_method_return = None + self.xml_method_args = [] + self.method_type = 'instancemethod' + else: + self.xml_method_return = self.method_node.find('./return') + self.xml_method_args = self.method_node.findall('./arguments/argument') + self.method_type = self.method_node.tag if self.method_type != 'classmethod' and len(self.xml_method_args) > 0: self.self_arg = self.xml_method_args[0] self.xml_method_args = self.xml_method_args[1:] @@ -64,6 +69,11 @@ class MethodDefinition: self.body += "\t" + arg_complete_type + " " + arg_name + ";\n" self.arg_names.append(arg_name) + def format_dealloc_local_variables_definition(self): + self.body += \ +""" {arg_type} * native_ptr; +""".format(arg_type=self.class_['class_cname']) + def format_native_pointer_get(self): self.body += \ """ native_ptr = pylinphone_{class_name}_get_native_ptr(self); @@ -295,8 +305,7 @@ class MethodDefinition: """ self.format_return_constructor_trace() self.body += \ -""" return (PyObject *)self; -""" +""" return (PyObject *)self;""" def format_new_from_native_pointer_body(self): self.body += \ @@ -576,6 +585,7 @@ class LinphoneModule(object): for c in self.classes: xml_new_method = c['class_xml_node'].find("./classmethods/classmethod[@name='" + c['class_c_function_prefix'] + "new']") c['new_body'] = self.__format_new_body(xml_new_method, c) + c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(None, c) for m in c['class_type_methods']: m['method_body'] = self.__format_class_method_body(m['method_xml_node'], c) for m in c['class_instance_methods']: @@ -587,12 +597,12 @@ class LinphoneModule(object): p['setter_body'] = self.__format_setter_body(p['setter_xml_node'], c) if c['class_refcountable']: xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") - c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(xml_instance_method, c) c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) elif c['class_destroyable']: xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']") - c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(xml_instance_method, c) c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) + else: + c['dealloc_body'] = self.__format_dealloc_body(None, c) def __format_class_method_body(self, method_node, class_): method = MethodDefinition(method_node, class_, self) @@ -632,26 +642,21 @@ class LinphoneModule(object): return method.body def __format_new_body(self, method_node, class_): - if method_node is not None: - method = MethodDefinition(method_node, class_, self) - method.format_new_body() - return method.body - return '' + method = MethodDefinition(method_node, class_, self) + method.format_new_body() + return method.body def __format_new_from_native_pointer_body(self, method_node, class_): method = MethodDefinition(method_node, class_, self) - # Force return value type of dealloc function to prevent declaring useless local variables - method.xml_method_return.set('type', 'void') - method.xml_method_return.set('completetype', 'void') method.format_new_from_native_pointer_body() return method.body def __format_dealloc_body(self, method_node, class_): method = MethodDefinition(method_node, class_, self) # Force return value type of dealloc function to prevent declaring useless local variables - method.xml_method_return.set('type', 'void') - method.xml_method_return.set('completetype', 'void') - method.format_local_variables_definition() + #method.xml_method_return.set('type', 'void') + #method.xml_method_return.set('completetype', 'void') + method.format_dealloc_local_variables_definition() method.format_native_pointer_get() method.format_dealloc_trace() method.format_dealloc_c_function_call() From 8fa788510dd960e49b6f5cbe6b2b812383527328 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 10:30:44 +0200 Subject: [PATCH 072/218] =?UTF-8?q?Fix=20compilation=20warning:=20"functio?= =?UTF-8?q?n=20declaration=20isn=E2=80=99t=20a=20prototype".?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- coreapi/linphone_tunnel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreapi/linphone_tunnel.h b/coreapi/linphone_tunnel.h index 9b33e32f8..336351267 100644 --- a/coreapi/linphone_tunnel.h +++ b/coreapi/linphone_tunnel.h @@ -53,7 +53,7 @@ typedef struct _LinphoneTunnelConfig LinphoneTunnelConfig; /** * Create a new tunnel configuration */ -LINPHONE_PUBLIC LinphoneTunnelConfig *linphone_tunnel_config_new(); +LINPHONE_PUBLIC LinphoneTunnelConfig *linphone_tunnel_config_new(void); /** * Set address of server. From 546200b61e9299540d34277557577ef41b8b9b0d Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 10:30:54 +0200 Subject: [PATCH 073/218] Deprecate linphone_address_destroy(). --- coreapi/address.c | 1 + 1 file changed, 1 insertion(+) diff --git a/coreapi/address.c b/coreapi/address.c index ec37255c1..250026911 100644 --- a/coreapi/address.c +++ b/coreapi/address.c @@ -184,6 +184,7 @@ bool_t linphone_address_weak_equal(const LinphoneAddress *a1, const LinphoneAddr /** * Destroys a LinphoneAddress object (actually calls linphone_address_unref()). + * @deprecated Use linphone_address_unref() instead **/ void linphone_address_destroy(LinphoneAddress *u){ sal_address_unref(u); From 7861f9a1277f3d88f457eae0f53c1e5cb98359f3 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 10:49:19 +0200 Subject: [PATCH 074/218] Update ms2 submodule. --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index a921798e2..aa511d1ba 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit a921798e217000c6c286942acc140e58b14fbcf5 +Subproject commit aa511d1ba37bea8e65983374f2968856bf109a6a From de812274e7087049aac7d7f5387625f9b54b97cc Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Thu, 17 Jul 2014 11:08:19 +0200 Subject: [PATCH 075/218] add JNI wrapper for publish expires getter/setter --- coreapi/linphonecore_jni.cc | 9 ++ .../linphone/core/LinphoneProxyConfig.java | 88 +++++++++++-------- .../core/LinphoneProxyConfigImpl.java | 21 ++++- 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index c43b6e3c3..609493b3e 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -1729,6 +1729,15 @@ extern "C" jlong Java_org_linphone_core_LinphoneProxyConfigImpl_getErrorInfo(JNI return (jlong)linphone_proxy_config_get_error_info((LinphoneProxyConfig *) ptr); } +extern "C" jint Java_org_linphone_core_LinphoneProxyConfigImpl_getPublishExpires(JNIEnv* env,jobject thiz,jlong ptr) { + return (jint)linphone_proxy_config_get_publish_expires((LinphoneProxyConfig *) ptr); +} +extern "C" void Java_org_linphone_core_LinphoneProxyConfigImpl_setPublishExpires(JNIEnv* env + ,jobject thiz + ,jlong ptr + ,jint jval) { + linphone_proxy_config_set_publish_expires((LinphoneProxyConfig *) ptr, jval); +} //Auth Info extern "C" jlong Java_org_linphone_core_LinphoneAuthInfoImpl_newLinphoneAuthInfo(JNIEnv* env diff --git a/java/common/org/linphone/core/LinphoneProxyConfig.java b/java/common/org/linphone/core/LinphoneProxyConfig.java index c2c742bd4..0f8db591b 100644 --- a/java/common/org/linphone/core/LinphoneProxyConfig.java +++ b/java/common/org/linphone/core/LinphoneProxyConfig.java @@ -18,19 +18,19 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.linphone.core; /** - * The LinphoneProxyConfig object represents a proxy configuration to be used by the LinphoneCore object. Its fields must not be used directly in favour of the accessors methods. + * The LinphoneProxyConfig object represents a proxy configuration to be used by the LinphoneCore object. Its fields must not be used directly in favour of the accessors methods. * Once created and filled properly the LinphoneProxyConfig can be given to LinphoneCore with {@link LinphoneCore#addProxyConfig(LinphoneProxyConfig)}. This will automatically triggers the registration, if enabled. *
The proxy configuration are persistent to restarts because they are saved in the configuration file. As a consequence, after {@link LinphoneCoreFactory#createLinphoneCore(LinphoneCoreListener, String, String, Object)} there might already be a default proxy that can be examined with {@link LinphoneCore#getDefaultProxyConfig()} . * */ public interface LinphoneProxyConfig { - + public void setIsDeleted(boolean b); public boolean getIsDeleted(); - + /** *Starts editing a proxy configuration. - *Because proxy configuration must be consistent, applications MUST call {@link #edit()} before doing any attempts to modify proxy configuration (such as identity, proxy address and so on). + *Because proxy configuration must be consistent, applications MUST call {@link #edit()} before doing any attempts to modify proxy configuration (such as identity, proxy address and so on). *Once the modifications are done, then the application must call {@link #done()} to commit the changes. */ public LinphoneProxyConfig edit(); @@ -61,20 +61,20 @@ public interface LinphoneProxyConfig { public void setProxy(String proxyUri) throws LinphoneCoreException; /** * get the proxy's SIP address. - * + * */ public String getProxy(); /** * Enable register for this proxy config. * Register message is issued after call to {@link #done()} * @param value - */ + */ public LinphoneProxyConfig enableRegister(boolean value); /** * @return true if registration to the proxy is enabled. */ public boolean registerEnabled(); - + /** * normalize a human readable phone number into a basic string. 888-444-222 becomes 888444222 * @param number @@ -86,32 +86,32 @@ public interface LinphoneProxyConfig { * @param prefix */ public void setDialPrefix(String prefix); - + /** * Returns the automatically added international prefix to e164 phone numbers */ public String getDialPrefix(); - + /** * * Sets whether liblinphone should replace "+" by "00" in dialed numbers (passed to * {@link LinphoneCore#invite(String)}). * @param value default value is false */ public void setDialEscapePlus(boolean value); - + /** * Whether liblinphone should replace "+" by "00" in dialed numbers (passed to * {@link LinphoneCore#invite(String)}). */ public boolean getDialEscapePlus(); - + /** * get domain host name or ip * @return may be null */ public String getDomain(); /** - * + * * @return a boolean indicating that the user is successfully registered on the proxy. */ public boolean isRegistered(); @@ -122,7 +122,7 @@ public interface LinphoneProxyConfig { */ public void setRoute(String routeUri) throws LinphoneCoreException; /** - * + * * @return the route set for this proxy configuration. */ public String getRoute(); @@ -138,95 +138,95 @@ public interface LinphoneProxyConfig { * returns publish state for this proxy config (see {@link #enablePublish(boolean)} ) */ public boolean publishEnabled(); - - + + LinphoneCore.RegistrationState getState(); - + /** * Sets the registration expiration time. * @param delay expiration time in seconds */ void setExpires(int delay); - + /** * Gets the registration expiration time. * @return delay expiration time in seconds. */ int getExpires(); - + /** * Set the privacy for all calls or chat sessions using the identity exposed by this LinphoneProxyConfig * @param privacy_mask a or'd int of values defined in interface {@link org.linphone.core.Privacy} */ void setPrivacy(int privacy_mask); - + /** * Get the privacy mask requested for this proxy config. * @return the privacy mask as defined in interface {@link org.linphone.core.Privacy} */ int getPrivacy(); - + /** * Indicates whether AVPF/SAVPF must be used for calls using this proxy config. * @param enable True to enable AVPF/SAVF, false to disable it. */ void enableAvpf(boolean enable); - + /** * Whether AVPF is used for calls through this proxy. - * @return + * @return */ boolean avpfEnabled(); - + /** * Set the interval between regular RTCP reports when using AVPF/SAVPF. * @param interval The interval in seconds (between 0 and 5 seconds). */ void setAvpfRRInterval(int interval); - + /** * Get the interval between regular RTCP reports when using AVPF/SAVPF. * @return The interval in seconds. */ int getAvpfRRInterval(); - + /** * Indicates whether quality reporting must be used for calls using this proxy config. * @param enable True to enable quality reporting, false to disable it. */ void enableQualityReporting(boolean enable); - + /** * Whether quality reporting is used for calls through this proxy. - * @return + * @return */ boolean qualityReportingEnabled(); - + /** * Set the interval between quality interval reports during a call when using quality reporting. * @param interval The interval in seconds (should be greater than 120 seconds to avoid too much). */ void setQualityReportingInterval(int interval); - + /** * Get the interval between quality interval reports during a call when using quality reporting. * @return The interval in seconds. */ int getQualityReportingInterval(); - + /** * Set the collector SIP URI to collect reports when using quality reporting. * @param collector The collector SIP URI which should be configured server side too. */ void setQualityReportingCollector(String collector); - + /** * Get the collector SIP URI collecting reports when using quality reporting. * @return The SIP URI collector address. */ String getQualityReportingCollector(); - + /** * Set optional contact parameters that will be added to the contact information sent in the registration. * @param contact_params a string containing the additional parameters in text form, like "myparam=something;myparam2=something_else" @@ -235,13 +235,13 @@ public interface LinphoneProxyConfig { * As an example, the contact address in the SIP register sent will look like ;android-push-id=43143-DFE23F-2323-FA2232. **/ public void setContactParameters(String contact_params); - + /** * Get the contact's parameters. * @return */ public String getContactParameters(); - + /** * Set optional contact parameters that will be added to the contact information sent in the registration, inside the URI. * @param params a string containing the additional parameters in text form, like "myparam=something;myparam2=something_else" @@ -250,34 +250,44 @@ public interface LinphoneProxyConfig { * As an example, the contact address in the SIP register sent will look like . **/ public void setContactUriParameters(String params); - + /** * Get the contact's URI parameters. * @return */ public String getContactUriParameters(); - + /** * Return the international prefix for the given country * @param country iso code */ public int lookupCCCFromIso(String iso); - + /** * Return the international prefix for the given country * @param e164 phone number */ public int lookupCCCFromE164(String e164); - + /** * Return reason error code. * @return reason code. */ public Reason getError(); - + /** * Get full error information about last error occured on the proxy config. * @return an ErrorInfo. */ public ErrorInfo getErrorInfo(); + + /** + * Set the publish expiration time in second. + * @param expires in second + */ + public void setPublishExpires(int expires); + /** + * @return the publish expiration time in second. Default value is the registration expiration value. + */ + public int getPublishExpires(); } diff --git a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java index 475fe352d..68d444332 100644 --- a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java +++ b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java @@ -212,7 +212,7 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { } public boolean publishEnabled() { isValid(); - return publishEnabled(nativePtr); + return publishEnabled(nativePtr); } @Override public void setContactParameters(String params) { @@ -304,21 +304,21 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { public ErrorInfo getErrorInfo() { return new ErrorInfoImpl(getErrorInfo(nativePtr)); } - + private native void enableQualityReporting(long nativePtr, boolean enable); @Override public void enableQualityReporting(boolean enable) { isValid(); enableQualityReporting(nativePtr, enable); } - + private native boolean qualityReportingEnabled(long nativePtr); @Override public boolean qualityReportingEnabled() { isValid(); return avpfEnabled(nativePtr); } - + private native void setQualityReportingInterval(long nativePtr, int interval); @Override public void setQualityReportingInterval(int interval) { @@ -344,4 +344,17 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { isValid(); return getQualityReportingCollector(nativePtr); } + private native void setPublishExpires(long nativePtr, int expires); + @Override + public void setPublishExpires(int expires) { + isValid(); + setPublishExpires(nativePtr, expires); + } + private native int getPublishExpires(long nativePtr); + @Override + public int getPublishExpires() { + + isValid(); + return getPublishExpires(nativePtr); + } } From e73d6676d104da9692564e2e0df3dac507d36724 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 11:44:51 +0200 Subject: [PATCH 076/218] Handle MS_VIDEO_DECODER_RECOVERED_FROM_ERRORS events. --- coreapi/linphonecall.c | 8 +++++++- mediastreamer2 | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index d78867bfd..15df7990b 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1578,12 +1578,18 @@ static void video_stream_event_cb(void *user_pointer, const MSFilter *f, const u LinphoneCall* call = (LinphoneCall*) user_pointer; switch (event_id) { case MS_VIDEO_DECODER_DECODING_ERRORS: - ms_warning("Case is MS_VIDEO_DECODER_DECODING_ERRORS"); + ms_warning("MS_VIDEO_DECODER_DECODING_ERRORS"); if (call->videostream && (video_stream_is_decoding_error_to_be_reported(call->videostream, 5000) == TRUE)) { video_stream_decoding_error_reported(call->videostream); linphone_call_send_vfu_request(call); } break; + case MS_VIDEO_DECODER_RECOVERED_FROM_ERRORS: + ms_message("MS_VIDEO_DECODER_RECOVERED_FROM_ERRORS"); + if (call->videostream) { + video_stream_decoding_error_recovered(call->videostream); + } + break; case MS_VIDEO_DECODER_FIRST_IMAGE_DECODED: ms_message("First video frame decoded successfully"); if (call->nextVideoFrameDecoded._func != NULL) diff --git a/mediastreamer2 b/mediastreamer2 index aa511d1ba..8a55c6ea6 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit aa511d1ba37bea8e65983374f2968856bf109a6a +Subproject commit 8a55c6ea69456f876fedd4b28594fe0c5c413860 From 19570fc16f3a866d30a0e4597b75a3bd7791bd00 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 12:42:17 +0200 Subject: [PATCH 077/218] Update ms2 submodule for compilation fix with MinGW. --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 8a55c6ea6..003add3c5 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 8a55c6ea69456f876fedd4b28594fe0c5c413860 +Subproject commit 003add3c50881ad8c16cdd38202376afea2f424a From 5546137ed544e22726b35eb6fa17ace22908f75f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 16:36:40 +0200 Subject: [PATCH 078/218] Refactor Python wrapper generator of method bodies for better maintainability. --- tools/python/apixml2python/linphone.py | 638 ++++++++++++------------- 1 file changed, 317 insertions(+), 321 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index cbb0908a8..4ef8a28ba 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -20,187 +20,72 @@ def strip_leading_linphone(s): else: return s + class MethodDefinition: - def __init__(self, method_node, class_, linphone_module): + def __init__(self, linphone_module, class_, method_node = None): self.body = '' self.arg_names = [] self.parse_tuple_format = '' self.build_value_format = '' self.return_type = 'void' + self.return_complete_type = 'void' self.method_node = method_node self.class_ = class_ self.linphone_module = linphone_module self.self_arg = None - if self.method_node is None: - self.xml_method_return = None - self.xml_method_args = [] - self.method_type = 'instancemethod' - else: - self.xml_method_return = self.method_node.find('./return') - self.xml_method_args = self.method_node.findall('./arguments/argument') - self.method_type = self.method_node.tag - if self.method_type != 'classmethod' and len(self.xml_method_args) > 0: - self.self_arg = self.xml_method_args[0] - self.xml_method_args = self.xml_method_args[1:] + self.xml_method_return = None + self.xml_method_args = [] + self.method_type = 'instancemethod' def format_local_variables_definition(self): - self.return_type = self.xml_method_return.get('type') - self.return_complete_type = self.xml_method_return.get('completetype') + body = '' + if self.xml_method_return is not None: + self.return_type = self.xml_method_return.get('type') + self.return_complete_type = self.xml_method_return.get('completetype') if self.return_complete_type != 'void': - self.body += "\t" + self.return_complete_type + " cresult;\n" - self.build_value_format = self.__ctype_to_python_format(self.return_type, self.return_complete_type) + body += "\t" + self.return_complete_type + " cresult;\n" + self.build_value_format = self.ctype_to_python_format(self.return_type, self.return_complete_type) if self.build_value_format == 'O': - self.body += "\tPyObject * pyresult;\n" - self.body += "\tPyObject * pyret;\n" + body += "\tPyObject * pyresult;\n" + body += "\tPyObject * pyret;\n" if self.self_arg is not None: - self.body += "\t" + self.self_arg.get('completetype') + "native_ptr;\n" + body += "\t" + self.self_arg.get('completetype') + "native_ptr;\n" for xml_method_arg in self.xml_method_args: arg_name = xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - fmt = self.__ctype_to_python_format(arg_type, arg_complete_type) + fmt = self.ctype_to_python_format(arg_type, arg_complete_type) self.parse_tuple_format += fmt if fmt == 'O': - self.body += "\tPyObject * " + arg_name + ";\n" - self.body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr;\n" + body += "\tPyObject * " + arg_name + ";\n" + body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr;\n" elif strip_leading_linphone(arg_complete_type) in self.linphone_module.enum_names: - self.body += "\tint " + arg_name + ";\n" + body += "\tint " + arg_name + ";\n" else: - self.body += "\t" + arg_complete_type + " " + arg_name + ";\n" + body += "\t" + arg_complete_type + " " + arg_name + ";\n" self.arg_names.append(arg_name) - - def format_dealloc_local_variables_definition(self): - self.body += \ -""" {arg_type} * native_ptr; -""".format(arg_type=self.class_['class_cname']) - - def format_native_pointer_get(self): - self.body += \ -""" native_ptr = pylinphone_{class_name}_get_native_ptr(self); -""".format(class_name=self.class_['class_name']) - - def format_native_pointer_checking(self, return_int): - self.format_native_pointer_get() - return_value = "NULL" - if return_int: - return_value = "-1" - self.body += \ -""" if (native_ptr == NULL) {{ - PyErr_SetString(PyExc_TypeError, "Invalid linphone.{class_name} instance"); - return {return_value}; - }} -""".format(class_name=self.class_['class_name'], return_value=return_value) - - def format_object_args_native_pointers_checking(self): - for xml_method_arg in self.xml_method_args: - arg_name = xml_method_arg.get('name') - arg_type = xml_method_arg.get('type') - arg_complete_type = xml_method_arg.get('completetype') - fmt = self.__ctype_to_python_format(arg_type, arg_complete_type) - if fmt == 'O': - self.body += \ -""" if (({arg_name}_native_ptr = pylinphone_{arg_type}_get_native_ptr({arg_name})) == NULL) {{ - return NULL; - }} -""".format(arg_name=arg_name, arg_type=strip_leading_linphone(arg_type)) + return body def format_arguments_parsing(self): + class_native_ptr_check_code = '' if self.self_arg is not None: - self.format_native_pointer_checking(False) + class_native_ptr_check_code = self.format_class_native_pointer_check(False) + parse_tuple_code = '' if len(self.arg_names) > 0: - self.body += \ + parse_tuple_code = \ """ if (!PyArg_ParseTuple(args, "{fmt}", {args})) {{ return NULL; }} """.format(fmt=self.parse_tuple_format, args=', '.join(map(lambda a: '&' + a, self.arg_names))) - self.format_object_args_native_pointers_checking() + return \ +""" {class_native_ptr_check_code} + {parse_tuple_code} + {args_native_ptr_check_code} +""".format(class_native_ptr_check_code=class_native_ptr_check_code, + parse_tuple_code=parse_tuple_code, + args_native_ptr_check_code=self.format_args_native_pointer_check()) - def format_setter_value_checking_and_c_function_call(self): - attribute_name = self.method_node.get('property_name') - first_arg_type = self.xml_method_args[0].get('type') - first_arg_complete_type = self.xml_method_args[0].get('completetype') - first_arg_name = self.xml_method_args[0].get('name') - self.format_native_pointer_checking(True) - # Check that the value exists - self.body += \ -""" if (value == NULL) {{ - PyErr_SetString(PyExc_TypeError, "Cannot delete the {attribute_name} attribute"); - return -1; - }} -""".format(attribute_name=attribute_name) - # Check the value - type_str, checkfunc, convertfunc = self.__ctype_to_python_type(first_arg_type, first_arg_complete_type) - first_arg_class = strip_leading_linphone(first_arg_type) - if checkfunc is None: - self.body += \ -""" if (!PyObject_IsInstance(value, (PyObject *)&pylinphone_{class_name}Type)) {{ - PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a linphone.{class_name} instance"); - return -1; - }} -""".format(class_name=first_arg_class, attribute_name=attribute_name) - else: - self.body += \ -""" if (!{checkfunc}(value)) {{ - PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a {type_str}"); - return -1; - }} -""".format(checkfunc=checkfunc, attribute_name=attribute_name, type_str=type_str) - # Call the C function - if convertfunc is None: - self.body += \ -""" {arg_name} = value; -""".format(arg_name=first_arg_name) - else: - self.body += \ -""" {arg_name} = ({arg_type}){convertfunc}(value); -""".format(arg_name=first_arg_name, arg_type=first_arg_complete_type, convertfunc=convertfunc) - if self.__ctype_to_python_format(first_arg_type, first_arg_complete_type) == 'O': - self.body += \ -""" {arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name}); - if ({arg_name}_native_ptr == NULL) {{ - PyErr_SetString(PyExc_TypeError, "Invalid linphone.{arg_class} instance"); - return -1; - }} -""".format(arg_name=first_arg_name, arg_class=first_arg_class) - self.format_enter_function_trace() - self.body += \ -""" {method_name}(native_ptr, {arg_name}_native_ptr); -""".format(arg_name=first_arg_name, method_name=self.method_node.get('name')) - else: - self.format_enter_function_trace() - self.body += \ -""" {method_name}(native_ptr, {arg_name}); -""".format(method_name=self.method_node.get('name'), arg_name=first_arg_name) - self.format_return_setter_trace() - self.body += \ -""" return 0;""" - - def format_enter_constructor_trace(self): - self.body += \ -""" pylinphone_trace(1, "[PYLINPHONE] %s()", __FUNCTION__); -""" - - def format_return_constructor_trace(self): - self.body += \ -""" pylinphone_trace(-1, "[PYLINPHONE] %s -> %p", __FUNCTION__, self); -""" - - def format_enter_constructor_from_native_ptr_trace(self): - self.body += \ -""" pylinphone_trace(1, "[PYLINPHONE] %s(%p)", __FUNCTION__, native_ptr); -""" - - def format_return_constructor_from_native_ptr_trace(self): - self.body += \ -""" pylinphone_trace(-1, "[PYLINPHONE] %s -> %p", __FUNCTION__, self); -""" - - def format_dealloc_trace(self): - self.body += \ -""" pylinphone_trace(0, "[PYLINPHONE] %s(%p [%p])", __FUNCTION__, self, native_ptr); -""" - - def format_enter_function_trace(self): + def format_enter_trace(self): fmt = '' args = [] if self.self_arg is not None: @@ -212,30 +97,13 @@ class MethodDefinition: arg_complete_type = xml_method_arg.get('completetype') if fmt != '': fmt += ', ' - f, a = self.__ctype_to_str_format(arg_name, arg_type, arg_complete_type) + f, a = self.ctype_to_str_format(arg_name, arg_type, arg_complete_type) fmt += f args += a args=', '.join(args) if args != '': args = ', ' + args - self.body += \ -""" pylinphone_trace(1, "[PYLINPHONE] %s({fmt})", __FUNCTION__{args}); -""".format(fmt=fmt, args=args) - - def format_return_function_trace(self): - self.body += \ -""" pylinphone_trace(-1, "[PYLINPHONE] %s -> %p", __FUNCTION__, pyret); -""" - - def format_return_setter_trace(self): - self.body += \ -""" pylinphone_trace(-1, "[PYLINPHONE] %s -> 0", __FUNCTION__); -""" - - def format_return_none_trace(self): - self.body += \ -""" pylinphone_trace(-1, "[PYLINPHONE] %s -> None", __FUNCTION__); -""" + return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s({fmt})\", __FUNCTION__{args});\n".format(fmt=fmt, args=args) def format_c_function_call(self): arg_names = [] @@ -243,122 +111,118 @@ class MethodDefinition: arg_name = xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - type_str, checkfunc, convertfunc = self.__ctype_to_python_type(arg_type, arg_complete_type) + type_str, checkfunc, convertfunc = self.ctype_to_python_type(arg_type, arg_complete_type) if convertfunc is None: arg_names.append(arg_name + "_native_ptr") else: arg_names.append(arg_name) - self.body += "\t" + body = "\t" if self.return_type != 'void': - self.body += "cresult = " - self.body += self.method_node.get('name') + "(" + body += "cresult = " + body += self.method_node.get('name') + "(" if self.self_arg is not None: - self.body += "native_ptr" + body += "native_ptr" if len(arg_names) > 0: - self.body += ', ' - self.body += ', '.join(arg_names) + ");\n" - - def format_method_result(self): + body += ', ' + body += ', '.join(arg_names) + ");\n" + return_from_user_data_code = '' + new_from_native_pointer_code = '' + ref_native_pointer_code = '' + build_value_code = '' + result_variable = '' if self.return_complete_type != 'void': if self.build_value_format == 'O': stripped_return_type = strip_leading_linphone(self.return_type) - return_type_class = self.__find_class_definition(self.return_type) + return_type_class = self.find_class_definition(self.return_type) if return_type_class['class_has_user_data']: get_user_data_function = return_type_class['class_c_function_prefix'] + "get_user_data" - self.body += \ + return_from_user_data_code = \ """ if ((cresult != NULL) && ({func}(cresult) != NULL)) {{ return (PyObject *){func}(cresult); }} """.format(func=get_user_data_function) - self.body += \ -""" pyresult = pylinphone_{return_type}_new_from_native_ptr(&pylinphone_{return_type}Type, cresult); -""".format(return_type=stripped_return_type) + new_from_native_pointer_code = "\tpyresult = pylinphone_{return_type}_new_from_native_ptr(&pylinphone_{return_type}Type, cresult);\n".format(return_type=stripped_return_type) if self.self_arg is not None and return_type_class['class_refcountable']: ref_function = return_type_class['class_c_function_prefix'] + "ref" - self.body += \ -""" {func}(({cast_type})cresult); -""".format(func=ref_function, cast_type=self.__remove_const_from_complete_type(self.return_complete_type)) - self.body += \ -""" pyret = Py_BuildValue("{fmt}", pyresult); -""".format(fmt=self.build_value_format) - self.format_return_function_trace() - self.body += \ + ref_native_pointer_code = "\t{func}(({cast_type})cresult);\n".format(func=ref_function, cast_type=self.remove_const_from_complete_type(self.return_complete_type)) + result_variable = 'pyresult' + else: + result_variable = 'cresult' + if result_variable != '': + build_value_code = "pyret = Py_BuildValue(\"{fmt}\", {result_variable});\n".format(fmt=self.build_value_format, result_variable=result_variable) + body += \ +""" {return_from_user_data_code} + {new_from_native_pointer_code} + {ref_native_pointer_code} + {build_value_code} +""".format(return_from_user_data_code=return_from_user_data_code, + new_from_native_pointer_code=new_from_native_pointer_code, + ref_native_pointer_code=ref_native_pointer_code, + build_value_code=build_value_code) + return body + + def format_return_trace(self): + if self.return_complete_type != 'void': + return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> %p\", __FUNCTION__, pyret);\n" + else: + return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> None\", __FUNCTION__);\n" + + def format_return_result(self): + if self.return_complete_type != 'void': + if self.build_value_format == 'O': + return \ """ Py_DECREF(pyresult); return pyret;""" else: - self.body += \ -""" pyret = Py_BuildValue("{fmt}", cresult); -""".format(fmt=self.build_value_format) - self.format_return_function_trace() - self.body += """ return pyret;""" - else: - self.body += \ -""" Py_RETURN_NONE;""" + return "\treturn pyret;" + return "\tPy_RETURN_NONE;" - def format_new_body(self): - self.body += \ -""" pylinphone_{class_name}Object *self = (pylinphone_{class_name}Object *)type->tp_alloc(type, 0); -""".format(class_name=self.class_['class_name']) - self.format_enter_constructor_trace() - self.body += \ -""" self->native_ptr = NULL; -""" - self.format_return_constructor_trace() - self.body += \ -""" return (PyObject *)self;""" + def format_return_none_trace(self): + return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> None\", __FUNCTION__);\n" - def format_new_from_native_pointer_body(self): - self.body += \ -""" pylinphone_{class_name}Object *self; -""".format(class_name=self.class_['class_name']) - self.format_enter_constructor_from_native_ptr_trace() - self.body += \ -""" if (native_ptr == NULL) { - """ - self.format_return_none_trace() - self.body += \ -""" Py_RETURN_NONE; + def format_class_native_pointer_check(self, return_int): + return_value = "NULL" + if return_int: + return_value = "-1" + return \ +""" native_ptr = pylinphone_{class_name}_get_native_ptr(self); + if (native_ptr == NULL) {{ + PyErr_SetString(PyExc_TypeError, "Invalid linphone.{class_name} instance"); + return {return_value}; }} - self = (pylinphone_{class_name}Object *)PyObject_New(pylinphone_{class_name}Object, type); - if (self == NULL) {{ - """.format(class_name=self.class_['class_name']) - self.format_return_none_trace() - self.body += \ -""" Py_RETURN_NONE; - }} - self->native_ptr = ({class_cname} *)native_ptr; -""".format(class_cname=self.class_['class_cname']) - if self.class_['class_has_user_data']: - self.body += \ -""" {function_prefix}set_user_data(self->native_ptr, self); -""".format(function_prefix=self.class_['class_c_function_prefix']) - self.format_return_constructor_from_native_ptr_trace() - self.body += \ -""" return (PyObject *)self;""" +""".format(class_name=self.class_['class_name'], return_value=return_value) - def format_dealloc_c_function_call(self): - if self.class_['class_refcountable']: - self.body += \ -""" if (native_ptr != NULL) {{ - {function_prefix}unref(native_ptr); + def format_args_native_pointer_check(self): + body = '' + for xml_method_arg in self.xml_method_args: + arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + fmt = self.ctype_to_python_format(arg_type, arg_complete_type) + if fmt == 'O': + body += \ +""" if (({arg_name}_native_ptr = pylinphone_{arg_type}_get_native_ptr({arg_name})) == NULL) {{ + return NULL; }} -""".format(function_prefix=self.class_['class_c_function_prefix']) - elif self.class_['class_destroyable']: - self.body += \ -""" if (native_ptr != NULL) {{ - {function_prefix}destroy(native_ptr); - }} -""".format(function_prefix=self.class_['class_c_function_prefix']) - self.body += \ -""" self->ob_type->tp_free(self);""" +""".format(arg_name=arg_name, arg_type=strip_leading_linphone(arg_type)) + return body - def __remove_const_from_complete_type(self, complete_type): + def parse_method_node(self): + if self.method_node is not None: + self.xml_method_return = self.method_node.find('./return') + self.xml_method_args = self.method_node.findall('./arguments/argument') + self.method_type = self.method_node.tag + if self.method_type != 'classmethod' and len(self.xml_method_args) > 0: + self.self_arg = self.xml_method_args[0] + self.xml_method_args = self.xml_method_args[1:] + + def remove_const_from_complete_type(self, complete_type): splitted_type = complete_type.split(' ') while 'const' in splitted_type: splitted_type.remove('const') return ' '.join(splitted_type) - def __ctype_to_str_format(self, name, basic_type, complete_type): + def ctype_to_str_format(self, name, basic_type, complete_type): splitted_type = complete_type.split(' ') if basic_type == 'char': if '*' in splitted_type: @@ -398,7 +262,7 @@ class MethodDefinition: else: return ('%p [%p]', [name, name + "_native_ptr"]) - def __ctype_to_python_format(self, basic_type, complete_type): + def ctype_to_python_format(self, basic_type, complete_type): splitted_type = complete_type.split(' ') if basic_type == 'char': if '*' in splitted_type: @@ -438,7 +302,7 @@ class MethodDefinition: else: return 'O' - def __ctype_to_python_type(self, basic_type, complete_type): + def ctype_to_python_type(self, basic_type, complete_type): splitted_type = complete_type.split(' ') if basic_type == 'char': if '*' in splitted_type: @@ -470,13 +334,203 @@ class MethodDefinition: else: return (None, None, None) - def __find_class_definition(self, basic_type): + def find_class_definition(self, basic_type): basic_type = strip_leading_linphone(basic_type) for c in self.linphone_module.classes: if c['class_name'] == basic_type: return c return None + def format(self): + self.parse_method_node() + body = self.format_local_variables_definition() + body += self.format_arguments_parsing() + body += self.format_enter_trace() + body += self.format_c_function_call() + body += self.format_return_trace() + body += self.format_return_result() + return body + +class NewMethodDefinition(MethodDefinition): + def __init__(self, linphone_module, class_, method_node = None): + MethodDefinition.__init__(self, linphone_module, class_, method_node) + + def format_local_variables_definition(self): + return "\tpylinphone_{class_name}Object *self = (pylinphone_{class_name}Object *)type->tp_alloc(type, 0);\n".format(class_name=self.class_['class_name']) + + def format_arguments_parsing(self): + return '' + + def format_enter_trace(self): + return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s()\", __FUNCTION__);\n" + + def format_c_function_call(self): + return "\tself->native_ptr = NULL;\n" + + def format_return_trace(self): + return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> %p\", __FUNCTION__, self);\n" + + def format_return_result(self): + return "\treturn (PyObject *)self;" + +class NewFromNativePointerMethodDefinition(MethodDefinition): + def __init__(self, linphone_module, class_): + MethodDefinition.__init__(self, linphone_module, class_, None) + + def format_local_variables_definition(self): + return "\tpylinphone_{class_name}Object *self;\n".format(class_name=self.class_['class_name']) + + def format_arguments_parsing(self): + return '' + + def format_enter_trace(self): + return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s(%p)\", __FUNCTION__, native_ptr);\n" + + def format_c_function_call(self): + set_user_data_func_call = '' + if self.class_['class_has_user_data']: + set_user_data_func_call = "\t{function_prefix}set_user_data(self->native_ptr, self);\n".format(function_prefix=self.class_['class_c_function_prefix']) + return \ +""" if (native_ptr == NULL) {{ + {none_trace} + Py_RETURN_NONE; + }} + self = (pylinphone_{class_name}Object *)PyObject_New(pylinphone_{class_name}Object, type); + if (self == NULL) {{ + {none_trace} + Py_RETURN_NONE; + }} + self->native_ptr = ({class_cname} *)native_ptr; + {set_user_data_func_call} +""".format(class_name=self.class_['class_name'], class_cname=self.class_['class_cname'], + none_trace=self.format_return_none_trace(), set_user_data_func_call=set_user_data_func_call) + + def format_return_trace(self): + return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> %p\", __FUNCTION__, self);\n" + + def format_return_result(self): + return "\treturn (PyObject *)self;" + +class DeallocMethodDefinition(MethodDefinition): + def __init__(self, linphone_module, class_, method_node = None): + MethodDefinition.__init__(self, linphone_module, class_, method_node) + + def format_local_variables_definition(self): + func = "pylinphone_{class_name}_get_native_ptr".format(class_name=self.class_['class_name']) + return \ +""" {arg_type} * native_ptr = {func}(self); +""".format(arg_type=self.class_['class_cname'], func=func) + + def format_arguments_parsing(self): + return '' + + def format_enter_trace(self): + return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s(%p [%p])\", __FUNCTION__, self, native_ptr);\n" + + def format_c_function_call(self): + native_ptr_dealloc_code = '' + if self.class_['class_refcountable']: + native_ptr_dealloc_code = \ +""" if (native_ptr != NULL) {{ + {function_prefix}unref(native_ptr); + }} +""".format(function_prefix=self.class_['class_c_function_prefix']) + elif self.class_['class_destroyable']: + native_ptr_dealloc_code = \ +""" if (native_ptr != NULL) {{ + {function_prefix}destroy(native_ptr); + }} +""".format(function_prefix=self.class_['class_c_function_prefix']) + return \ +"""{native_ptr_dealloc_code} + self->ob_type->tp_free(self); +""".format(native_ptr_dealloc_code=native_ptr_dealloc_code) + + def format_return_trace(self): + return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s\", __FUNCTION__);" + + def format_return_result(self): + return '' + +class GetterMethodDefinition(MethodDefinition): + def __init__(self, linphone_module, class_, method_node = None): + MethodDefinition.__init__(self, linphone_module, class_, method_node) + +class SetterMethodDefinition(MethodDefinition): + def __init__(self, linphone_module, class_, method_node = None): + MethodDefinition.__init__(self, linphone_module, class_, method_node) + + def format_arguments_parsing(self): + if self.checkfunc is None: + attribute_type_check_code = \ +""" if (!PyObject_IsInstance(value, (PyObject *)&pylinphone_{class_name}Type)) {{ + PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a linphone.{class_name} instance"); + return -1; + }} +""".format(class_name=self.first_arg_class, attribute_name=self.attribute_name) + else: + attribute_type_check_code = \ +""" if (!{checkfunc}(value)) {{ + PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a {type_str}"); + return -1; + }} +""".format(checkfunc=self.checkfunc, attribute_name=self.attribute_name, type_str=self.type_str) + if self.convertfunc is None: + attribute_conversion_code = "\t{arg_name} = value;\n".format(arg_name=self.first_arg_name) + else: + attribute_conversion_code = "\t{arg_name} = ({arg_type}){convertfunc}(value);\n".format( + arg_name=self.first_arg_name, arg_type=self.first_arg_complete_type, convertfunc=self.convertfunc) + attribute_native_ptr_check_code = '' + if self.python_fmt == 'O': + attribute_native_ptr_check_code = \ +""" {arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name}); + if ({arg_name}_native_ptr == NULL) {{ + PyErr_SetString(PyExc_TypeError, "Invalid linphone.{arg_class} instance"); + return -1; + }} +""".format(arg_name=self.first_arg_name, arg_class=self.first_arg_class) + return \ +""" {native_ptr_check_code} + if (value == NULL) {{ + PyErr_SetString(PyExc_TypeError, "Cannot delete the {attribute_name} attribute"); + return -1; + }} + {attribute_type_check_code} + {attribute_conversion_code} + {attribute_native_ptr_check_code} +""".format(attribute_name=self.attribute_name, + native_ptr_check_code=self.format_class_native_pointer_check(True), + attribute_type_check_code=attribute_type_check_code, + attribute_conversion_code=attribute_conversion_code, + attribute_native_ptr_check_code=attribute_native_ptr_check_code) + + def format_c_function_call(self): + use_native_ptr = '' + if self.python_fmt == 'O': + use_native_ptr = '_native_ptr' + return "\t{method_name}(native_ptr, {arg_name}{use_native_ptr});\n".format( + arg_name=self.first_arg_name, method_name=self.method_node.get('name'), use_native_ptr=use_native_ptr) + + def format_return_trace(self): + return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> 0\", __FUNCTION__);\n" + + def format_return_result(self): + return "\treturn 0;" + + def parse_method_node(self): + MethodDefinition.parse_method_node(self) + # Force return value type of setter function to prevent declaring useless local variables + # TODO: Investigate. Maybe we should decide that setters must always return an int value. + self.xml_method_return = None + self.attribute_name = self.method_node.get('property_name') + self.first_arg_type = self.xml_method_args[0].get('type') + self.first_arg_complete_type = self.xml_method_args[0].get('completetype') + self.first_arg_name = self.xml_method_args[0].get('name') + self.type_str, self.checkfunc, self.convertfunc = self.ctype_to_python_type(self.first_arg_type, self.first_arg_complete_type) + self.first_arg_class = strip_leading_linphone(self.first_arg_type) + self.python_fmt = self.ctype_to_python_format(self.first_arg_type, self.first_arg_complete_type) + + class LinphoneModule(object): def __init__(self, tree, blacklisted_functions): @@ -584,83 +638,25 @@ class LinphoneModule(object): # Format methods' bodies for c in self.classes: xml_new_method = c['class_xml_node'].find("./classmethods/classmethod[@name='" + c['class_c_function_prefix'] + "new']") - c['new_body'] = self.__format_new_body(xml_new_method, c) - c['new_from_native_pointer_body'] = self.__format_new_from_native_pointer_body(None, c) + c['new_body'] = NewMethodDefinition(self, c, xml_new_method).format() + c['new_from_native_pointer_body'] = NewFromNativePointerMethodDefinition(self, c).format() for m in c['class_type_methods']: - m['method_body'] = self.__format_class_method_body(m['method_xml_node'], c) + m['method_body'] = MethodDefinition(self, c, m['method_xml_node']).format() for m in c['class_instance_methods']: - m['method_body'] = self.__format_instance_method_body(m['method_xml_node'], c) + m['method_body'] = MethodDefinition(self, c, m['method_xml_node']).format() for p in c['class_properties']: if p.has_key('getter_xml_node'): - p['getter_body'] = self.__format_getter_body(p['getter_xml_node'], c) + p['getter_body'] = GetterMethodDefinition(self, c, p['getter_xml_node']).format() if p.has_key('setter_xml_node'): - p['setter_body'] = self.__format_setter_body(p['setter_xml_node'], c) + p['setter_body'] = SetterMethodDefinition(self, c, p['setter_xml_node']).format() if c['class_refcountable']: xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") - c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) + c['dealloc_body'] = DeallocMethodDefinition(self, c, xml_instance_method).format() elif c['class_destroyable']: xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']") - c['dealloc_body'] = self.__format_dealloc_body(xml_instance_method, c) + c['dealloc_body'] = DeallocMethodDefinition(self, c, xml_instance_method).format() else: - c['dealloc_body'] = self.__format_dealloc_body(None, c) - - def __format_class_method_body(self, method_node, class_): - method = MethodDefinition(method_node, class_, self) - method.format_local_variables_definition() - method.format_arguments_parsing() - method.format_enter_function_trace() - method.format_c_function_call() - method.format_method_result() - return method.body - - def __format_instance_method_body(self, method_node, class_): - method = MethodDefinition(method_node, class_, self) - method.format_local_variables_definition() - method.format_arguments_parsing() - method.format_enter_function_trace() - method.format_c_function_call() - method.format_method_result() - return method.body - - def __format_getter_body(self, getter_node, class_): - method = MethodDefinition(getter_node, class_, self) - method.format_local_variables_definition() - method.format_arguments_parsing() - method.format_enter_function_trace() - method.format_c_function_call() - method.format_method_result() - return method.body - - def __format_setter_body(self, setter_node, class_): - method = MethodDefinition(setter_node, class_, self) - # Force return value type of dealloc function to prevent declaring useless local variables - # TODO: Investigate. Maybe we should decide that setters must always return an int value. - method.xml_method_return.set('type', 'void') - method.xml_method_return.set('completetype', 'void') - method.format_local_variables_definition() - method.format_setter_value_checking_and_c_function_call() - return method.body - - def __format_new_body(self, method_node, class_): - method = MethodDefinition(method_node, class_, self) - method.format_new_body() - return method.body - - def __format_new_from_native_pointer_body(self, method_node, class_): - method = MethodDefinition(method_node, class_, self) - method.format_new_from_native_pointer_body() - return method.body - - def __format_dealloc_body(self, method_node, class_): - method = MethodDefinition(method_node, class_, self) - # Force return value type of dealloc function to prevent declaring useless local variables - #method.xml_method_return.set('type', 'void') - #method.xml_method_return.set('completetype', 'void') - method.format_dealloc_local_variables_definition() - method.format_native_pointer_get() - method.format_dealloc_trace() - method.format_dealloc_c_function_call() - return method.body + c['dealloc_body'] = DeallocMethodDefinition(self, c).format() def __format_doc_node(self, node): desc = '' From 317a69f967832310dc3195b636b411f2be1b0ec5 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 18:03:11 +0200 Subject: [PATCH 079/218] Add exception handling in the Python wrapper generator to know which method of which class is pausing problem. --- tools/python/apixml2python/linphone.py | 62 ++++++++++++++++++-------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 4ef8a28ba..409ec5a25 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -14,6 +14,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys + + def strip_leading_linphone(s): if s.lower().startswith('linphone'): return s[8:] @@ -638,25 +642,45 @@ class LinphoneModule(object): # Format methods' bodies for c in self.classes: xml_new_method = c['class_xml_node'].find("./classmethods/classmethod[@name='" + c['class_c_function_prefix'] + "new']") - c['new_body'] = NewMethodDefinition(self, c, xml_new_method).format() - c['new_from_native_pointer_body'] = NewFromNativePointerMethodDefinition(self, c).format() - for m in c['class_type_methods']: - m['method_body'] = MethodDefinition(self, c, m['method_xml_node']).format() - for m in c['class_instance_methods']: - m['method_body'] = MethodDefinition(self, c, m['method_xml_node']).format() - for p in c['class_properties']: - if p.has_key('getter_xml_node'): - p['getter_body'] = GetterMethodDefinition(self, c, p['getter_xml_node']).format() - if p.has_key('setter_xml_node'): - p['setter_body'] = SetterMethodDefinition(self, c, p['setter_xml_node']).format() - if c['class_refcountable']: - xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") - c['dealloc_body'] = DeallocMethodDefinition(self, c, xml_instance_method).format() - elif c['class_destroyable']: - xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']") - c['dealloc_body'] = DeallocMethodDefinition(self, c, xml_instance_method).format() - else: - c['dealloc_body'] = DeallocMethodDefinition(self, c).format() + try: + c['new_body'] = NewMethodDefinition(self, c, xml_new_method).format() + except Exception, e: + e.args += (c['class_name'], 'new_body') + raise + try: + c['new_from_native_pointer_body'] = NewFromNativePointerMethodDefinition(self, c).format() + except Exception, e: + e.args += (c['class_name'], 'new_from_native_pointer_body') + raise + try: + for m in c['class_type_methods']: + m['method_body'] = MethodDefinition(self, c, m['method_xml_node']).format() + for m in c['class_instance_methods']: + m['method_body'] = MethodDefinition(self, c, m['method_xml_node']).format() + except Exception, e: + e.args += (c['class_name'], m['method_name']) + raise + try: + for p in c['class_properties']: + if p.has_key('getter_xml_node'): + p['getter_body'] = GetterMethodDefinition(self, c, p['getter_xml_node']).format() + if p.has_key('setter_xml_node'): + p['setter_body'] = SetterMethodDefinition(self, c, p['setter_xml_node']).format() + except Exception, e: + e.args += (c['class_name'], p['property_name']) + raise + try: + if c['class_refcountable']: + xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "unref']") + c['dealloc_body'] = DeallocMethodDefinition(self, c, xml_instance_method).format() + elif c['class_destroyable']: + xml_instance_method = c['class_xml_node'].find("./instancemethods/instancemethod[@name='" + c['class_c_function_prefix'] + "destroy']") + c['dealloc_body'] = DeallocMethodDefinition(self, c, xml_instance_method).format() + else: + c['dealloc_body'] = DeallocMethodDefinition(self, c).format() + except Exception, e: + e.args += (c['class_name'], 'dealloc_body') + raise def __format_doc_node(self, node): desc = '' From 2bc530054ab615af642f1da31e41ca269c4b833c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 18:03:42 +0200 Subject: [PATCH 080/218] Include linphonecore_utils.h in the Python wrapper. --- tools/python/apixml2python/linphone_module.mustache | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 3529904c1..2fc006def 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include #include +#include #include From ae5158f8c0dee1764e0038547611051fb0471d54 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 18:23:45 +0200 Subject: [PATCH 081/218] Prefix local variables in the Python wrapper to prevent name clashes. --- tools/python/apixml2python/linphone.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 409ec5a25..bfc0a27bc 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -55,7 +55,7 @@ class MethodDefinition: if self.self_arg is not None: body += "\t" + self.self_arg.get('completetype') + "native_ptr;\n" for xml_method_arg in self.xml_method_args: - arg_name = xml_method_arg.get('name') + arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') fmt = self.ctype_to_python_format(arg_type, arg_complete_type) @@ -96,7 +96,7 @@ class MethodDefinition: fmt += "%p [%p]" args += ["self", "native_ptr"] for xml_method_arg in self.xml_method_args: - arg_name = xml_method_arg.get('name') + arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') if fmt != '': @@ -112,7 +112,7 @@ class MethodDefinition: def format_c_function_call(self): arg_names = [] for xml_method_arg in self.xml_method_args: - arg_name = xml_method_arg.get('name') + arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') type_str, checkfunc, convertfunc = self.ctype_to_python_type(arg_type, arg_complete_type) @@ -199,7 +199,7 @@ class MethodDefinition: def format_args_native_pointer_check(self): body = '' for xml_method_arg in self.xml_method_args: - arg_name = xml_method_arg.get('name') + arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') fmt = self.ctype_to_python_format(arg_type, arg_complete_type) @@ -480,10 +480,10 @@ class SetterMethodDefinition(MethodDefinition): }} """.format(checkfunc=self.checkfunc, attribute_name=self.attribute_name, type_str=self.type_str) if self.convertfunc is None: - attribute_conversion_code = "\t{arg_name} = value;\n".format(arg_name=self.first_arg_name) + attribute_conversion_code = "\t{arg_name} = value;\n".format(arg_name="_" + self.first_arg_name) else: attribute_conversion_code = "\t{arg_name} = ({arg_type}){convertfunc}(value);\n".format( - arg_name=self.first_arg_name, arg_type=self.first_arg_complete_type, convertfunc=self.convertfunc) + arg_name="_" + self.first_arg_name, arg_type=self.first_arg_complete_type, convertfunc=self.convertfunc) attribute_native_ptr_check_code = '' if self.python_fmt == 'O': attribute_native_ptr_check_code = \ @@ -492,7 +492,7 @@ class SetterMethodDefinition(MethodDefinition): PyErr_SetString(PyExc_TypeError, "Invalid linphone.{arg_class} instance"); return -1; }} -""".format(arg_name=self.first_arg_name, arg_class=self.first_arg_class) +""".format(arg_name="_" + self.first_arg_name, arg_class=self.first_arg_class) return \ """ {native_ptr_check_code} if (value == NULL) {{ @@ -513,7 +513,7 @@ class SetterMethodDefinition(MethodDefinition): if self.python_fmt == 'O': use_native_ptr = '_native_ptr' return "\t{method_name}(native_ptr, {arg_name}{use_native_ptr});\n".format( - arg_name=self.first_arg_name, method_name=self.method_node.get('name'), use_native_ptr=use_native_ptr) + arg_name="_" + self.first_arg_name, method_name=self.method_node.get('name'), use_native_ptr=use_native_ptr) def format_return_trace(self): return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> 0\", __FUNCTION__);\n" From 941d57811be28b53cf9bd03402d3db89aad51f3d Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 17 Jul 2014 18:24:53 +0200 Subject: [PATCH 082/218] Blacklist some functions in the Python wrapper. --- tools/python/apixml2python.py | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 53ce49119..7242ff43d 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -27,6 +27,76 @@ from apixml2python.linphone import LinphoneModule blacklisted_functions = [ + 'linphone_call_get_user_pointer', + 'linphone_call_set_user_pointer', + 'linphone_call_log_get_local_stats', + 'linphone_call_log_get_remote_stats', + 'linphone_call_log_get_start_date', + 'linphone_call_log_get_user_pointer', + 'linphone_call_log_set_user_pointer', + 'linphone_call_params_get_received_video_size', + 'linphone_call_params_get_privacy', + 'linphone_call_params_get_sent_video_size', + 'linphone_call_params_get_used_audio_codec', + 'linphone_call_params_get_used_video_codec', + 'linphone_call_params_set_privacy', + 'linphone_call_stats_get_late_packets_cumulative_number', + 'linphone_call_stats_get_receiver_interarrival_jitter', + 'linphone_call_stats_get_sender_interarrival_jitter', + 'linphone_chat_message_get_chat_room', + 'linphone_chat_message_get_file_transfer_information', + 'linphone_chat_message_get_time', + 'linphone_chat_message_state_to_string', + 'linphone_chat_room_create_file_transfer_message', + 'linphone_chat_room_create_message_2', + 'linphone_chat_room_send_message2', + 'linphone_core_can_we_add_call', + 'linphone_core_enable_payload_type', + 'linphone_core_find_payload_type', + 'linphone_core_get_audio_codecs', + 'linphone_core_get_auth_info_list', + 'linphone_core_get_call_logs', + 'linphone_core_get_calls', + 'linphone_core_get_chat_rooms', + 'linphone_core_get_default_proxy', + 'linphone_core_get_payload_type_bitrate', + 'linphone_core_get_preferred_video_size', + 'linphone_core_get_friend_list', + 'linphone_core_get_proxy_config_list', + 'linphone_core_get_sip_transports', + 'linphone_core_get_sip_transports_used', + 'linphone_core_get_supported_video_sizes', + 'linphone_core_get_video_codecs', + 'linphone_core_get_video_policy', + 'linphone_core_new', + 'linphone_core_new_with_config', + 'linphone_core_payload_type_enabled', + 'linphone_core_payload_type_is_vbr', + 'linphone_core_publish', + 'linphone_core_set_log_file', + 'linphone_core_set_log_handler', + 'linphone_core_set_log_level', + 'linphone_core_set_payload_type_bitrate', + 'linphone_core_set_preferred_video_size', + 'linphone_core_set_video_policy', + 'linphone_core_play_dtmf', + 'linphone_core_send_dtmf', + 'linphone_core_set_audio_codecs', + 'linphone_core_set_preview_video_size', + 'linphone_core_set_sip_transports', + 'linphone_core_subscribe', + 'linphone_event_notify', + 'linphone_event_send_publish', + 'linphone_event_send_subscribe', + 'linphone_event_update_publish', + 'linphone_event_update_subscribe', + 'linphone_presence_model_get_timestamp', + 'linphone_presence_model_set_timestamp', + 'linphone_proxy_config_get_privacy', + 'linphone_proxy_config_normalize_number', + 'linphone_proxy_config_set_file_transfer_server', + 'linphone_proxy_config_set_privacy', + 'linphone_tunnel_get_http_proxy', 'lp_config_for_each_entry', 'lp_config_for_each_section', 'lp_config_get_range', From a00ba8db311ccf8fe512813998f0e96c110ce28d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 18 Jul 2014 11:17:04 +0200 Subject: [PATCH 083/218] Updated ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 003add3c5..805f4d33d 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 003add3c50881ad8c16cdd38202376afea2f424a +Subproject commit 805f4d33d01208692565b2fa43ced64b2c821389 From bc4060f5f7829e7687985a761190cf6cfa70902d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 18 Jul 2014 11:34:40 +0200 Subject: [PATCH 084/218] Updated ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 805f4d33d..29ac9d32d 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 805f4d33d01208692565b2fa43ced64b2c821389 +Subproject commit 29ac9d32dab63a68c8743c6981a55da98e5ebf21 From 066c01470218234f0c815e6431214715c633b14a Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Fri, 18 Jul 2014 14:45:35 +0200 Subject: [PATCH 085/218] When applying a remote provisioning config, if it is setting a proxy config and none is currently the default one, this proxy config will be the default one --- coreapi/linphonecore.c | 6 +++--- coreapi/remote_provisioning.c | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index d12a92bdf..c9b2dd4f0 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -999,10 +999,10 @@ static void video_config_read(LinphoneCore *lc){ linphone_core_set_preferred_video_size_by_name(lc, lp_config_get_string(lc->config,"video","size","cif")); - + linphone_core_set_preview_video_size_by_name(lc, lp_config_get_string(lc->config,"video","preview_size",NULL)); - + linphone_core_set_preferred_framerate(lc,lp_config_get_float(lc->config,"video","framerate",0)); #ifdef VIDEO_ENABLED @@ -1417,7 +1417,7 @@ static void linphone_core_init(LinphoneCore * lc, const LinphoneCoreVTable *vtab remote_provisioning_uri = linphone_core_get_provisioning_uri(lc); if (remote_provisioning_uri == NULL) { linphone_configuring_terminated(lc, LinphoneConfiguringSkipped, NULL); - } // else linphone_core_start will be called after the remote provisioining (see linphone_core_iterate) + } // else linphone_core_start will be called after the remote provisioning (see linphone_core_iterate) } /** diff --git a/coreapi/remote_provisioning.c b/coreapi/remote_provisioning.c index abf884e95..1b081a354 100644 --- a/coreapi/remote_provisioning.c +++ b/coreapi/remote_provisioning.c @@ -36,20 +36,28 @@ static void xml2lpc_callback(void *ctx, xml2lpc_log_level level, const char *fmt static void linphone_remote_provisioning_apply(LinphoneCore *lc, const char *xml) { xml2lpc_context *context = xml2lpc_context_new(xml2lpc_callback, lc); int result = xml2lpc_set_xml_string(context, xml); + char * error_msg = NULL; if (result == 0) { - result = xml2lpc_convert(context, linphone_core_get_config(lc)); + LpConfig * lpc = linphone_core_get_config(lc); + result = xml2lpc_convert(context, lpc); if (result == 0) { - lp_config_sync(linphone_core_get_config(lc)); - xml2lpc_context_destroy(context); - linphone_configuring_terminated(lc, LinphoneConfiguringSuccessful, NULL); + // if the remote provisioning added a proxy config and none was set before, set it + if (lp_config_has_section(lpc, "proxy_0") && lp_config_get_int(lpc, "sip", "default_proxy", -1) == -1){ + lp_config_set_int(lpc, "sip", "default_proxy", 0); + } + lp_config_sync(lpc); + } else { - xml2lpc_context_destroy(context); - linphone_configuring_terminated(lc, LinphoneConfiguringFailed, "xml to lpc failed"); + error_msg = "xml to lpc failed"; } } else { - xml2lpc_context_destroy(context); - linphone_configuring_terminated(lc, LinphoneConfiguringFailed, "invalid xml"); + error_msg = "invalid xml"; } + + xml2lpc_context_destroy(context); + linphone_configuring_terminated(lc + ,error_msg ? LinphoneConfiguringFailed : LinphoneConfiguringSuccessful + , error_msg); } static int linphone_remote_provisioning_load_file( LinphoneCore* lc, const char* file_path){ From a4c2f0ef36168954908cdde7d94b106fd668b669 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 18 Jul 2014 15:53:43 +0200 Subject: [PATCH 086/218] Blacklist some classes in the Python wrapper. --- tools/python/apixml2python.py | 6 +++++- tools/python/apixml2python/linphone.py | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 7242ff43d..56aeb275a 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -26,6 +26,10 @@ sys.path.append(os.path.realpath(__file__)) from apixml2python.linphone import LinphoneModule +blacklisted_classes = [ + 'LinphoneTunnel', + 'LinphoneTunnelConfig' +] blacklisted_functions = [ 'linphone_call_get_user_pointer', 'linphone_call_set_user_pointer', @@ -107,7 +111,7 @@ blacklisted_functions = [ def generate(apixmlfile): tree = ET.parse(apixmlfile) renderer = pystache.Renderer() - m = LinphoneModule(tree, blacklisted_functions) + m = LinphoneModule(tree, blacklisted_classes, blacklisted_functions) f = open("linphone.c", "w") f.write(renderer.render(m)) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index bfc0a27bc..bdffdba13 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -537,7 +537,7 @@ class SetterMethodDefinition(MethodDefinition): class LinphoneModule(object): - def __init__(self, tree, blacklisted_functions): + def __init__(self, tree, blacklisted_classes, blacklisted_functions): self.internal_instance_method_names = ['destroy', 'ref', 'unref'] self.internal_property_names = ['user_data'] self.enums = [] @@ -565,6 +565,8 @@ class LinphoneModule(object): for xml_class in xml_classes: if xml_class.get('deprecated') == 'true': continue + if xml_class.get('name') in blacklisted_classes: + continue c = {} c['class_xml_node'] = xml_class c['class_cname'] = xml_class.get('name') From 4d2900ca322a4fbc5f0e75e973efb0dc5b33e948 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Mon, 21 Jul 2014 14:10:22 +0200 Subject: [PATCH 087/218] MS2:make sure jpeg writter open file in binary mode for windows --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 29ac9d32d..4d8d2e4f0 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 29ac9d32dab63a68c8743c6981a55da98e5ebf21 +Subproject commit 4d8d2e4f01c6b301e67eb7f89dc7968b65f5fa50 From 0f96e56963521c26da09555e7907d25cbbcd1020 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Tue, 22 Jul 2014 13:05:59 +0200 Subject: [PATCH 088/218] Update ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 4d8d2e4f0..f5f9d66ff 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 4d8d2e4f01c6b301e67eb7f89dc7968b65f5fa50 +Subproject commit f5f9d66ffd11449782ccc8ebecde1f117433979d From 8951a8ebe0435afee850093f9f69e6a2aa63bb6f Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Wed, 23 Jul 2014 12:53:46 +0200 Subject: [PATCH 089/218] Fix (hopefully) the git check --- coreapi/Makefile.am | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/coreapi/Makefile.am b/coreapi/Makefile.am index ce7dc56a0..03d42f08d 100644 --- a/coreapi/Makefile.am +++ b/coreapi/Makefile.am @@ -5,6 +5,13 @@ GITDESCRIBE=`cd $(top_srcdir) && git describe --always` GIT_TAG=`cd $(top_srcdir) && git describe --abbrev=0` GITREVISION=`cd $(top_srcdir) && git rev-parse HEAD` +## This command is used to check if the sources are cloned in a git repo. +## We can't only depend on the presence of the .git/ directory anymore, +## because of gits submodule handling. +## We now simply issue a git status and if there's an error, the $(GITSTATUS) +## variable won't contain "GITOK" +GITSTATUS=`cd $(top_srcdir) && git status > /dev/null && echo GITOK` + ECHO=/bin/echo SUBDIRS=. help @@ -161,7 +168,7 @@ AM_CXXFLAGS=$(AM_CFLAGS) #the PACKAGE_VERSION given in configure.ac make_gitversion_h: - if test -d $(top_srcdir)/.git ; then \ + if test "$(GITSTATUS)" == "GITOK" ; then \ if test "$(GITDESCRIBE)" != "" ; then \ if test "$(GIT_TAG)" != "$(PACKAGE_VERSION)" ; then \ echo "*** PACKAGE_VERSION and git tag differ. Please put them identical."; \ From b2132d4d8a4621fa714dd6118e02502cfb29fdbc Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Wed, 23 Jul 2014 12:56:15 +0200 Subject: [PATCH 090/218] Update ms2 for git fix --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index f5f9d66ff..b84ad6280 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit f5f9d66ffd11449782ccc8ebecde1f117433979d +Subproject commit b84ad6280a29fb55ff0de3b26c710c2c31f83055 From 8b99a4c074157c284119303b1f84bb9f5f6c2deb Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Wed, 23 Jul 2014 14:58:12 +0200 Subject: [PATCH 091/218] Add JNI method for user data in call and proxy config --- .../org/linphone/core/LinphoneCall.java | 10 +++++++++ .../linphone/core/LinphoneProxyConfig.java | 14 ++++++++++--- .../org/linphone/core/LinphoneCallImpl.java | 9 ++++++++ .../core/LinphoneProxyConfigImpl.java | 21 +++++++++---------- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/java/common/org/linphone/core/LinphoneCall.java b/java/common/org/linphone/core/LinphoneCall.java index 90fe60ded..d75f1bbdb 100644 --- a/java/common/org/linphone/core/LinphoneCall.java +++ b/java/common/org/linphone/core/LinphoneCall.java @@ -323,4 +323,14 @@ public interface LinphoneCall { */ ErrorInfo getErrorInfo(); + /** + * attached a user data to a call + **/ + void setUserData(Object obj); + + /** + * Returns user data from a call. return null if any + * @return an Object. + */ + Object getUserData(); } diff --git a/java/common/org/linphone/core/LinphoneProxyConfig.java b/java/common/org/linphone/core/LinphoneProxyConfig.java index 0f8db591b..711a53668 100644 --- a/java/common/org/linphone/core/LinphoneProxyConfig.java +++ b/java/common/org/linphone/core/LinphoneProxyConfig.java @@ -25,9 +25,6 @@ package org.linphone.core; */ public interface LinphoneProxyConfig { - public void setIsDeleted(boolean b); - public boolean getIsDeleted(); - /** *Starts editing a proxy configuration. *Because proxy configuration must be consistent, applications MUST call {@link #edit()} before doing any attempts to modify proxy configuration (such as identity, proxy address and so on). @@ -290,4 +287,15 @@ public interface LinphoneProxyConfig { * @return the publish expiration time in second. Default value is the registration expiration value. */ public int getPublishExpires(); + + /** + * attached a user data to a proxy config + **/ + void setUserData(Object obj); + + /** + * Returns user data from a proxy config. return null if any + * @return an Object. + */ + Object getUserData(); } diff --git a/java/impl/org/linphone/core/LinphoneCallImpl.java b/java/impl/org/linphone/core/LinphoneCallImpl.java index 66a9bf1d9..ee78e9d89 100644 --- a/java/impl/org/linphone/core/LinphoneCallImpl.java +++ b/java/impl/org/linphone/core/LinphoneCallImpl.java @@ -22,6 +22,7 @@ class LinphoneCallImpl implements LinphoneCall { protected final long nativePtr; boolean ownPtr = false; + Object userData; private LinphoneCallStats audioStats; private LinphoneCallStats videoStats; @@ -236,4 +237,12 @@ class LinphoneCallImpl implements LinphoneCall { public ErrorInfo getErrorInfo() { return new ErrorInfoImpl(getErrorInfo(nativePtr)); } + @Override + public void setUserData(Object obj) { + userData = obj; + } + @Override + public Object getUserData() { + return userData; + } } diff --git a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java index 68d444332..b7c301297 100644 --- a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java +++ b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java @@ -24,8 +24,7 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { protected long nativePtr; protected LinphoneCoreImpl mCore; - protected boolean isDeleting; - + Object userData; private native int getState(long nativePtr); private native void setExpires(long nativePtr, int delay); private native int getExpires(long nativePtr); @@ -36,7 +35,6 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { setIdentity(identity); setProxy(proxy); setRoute(route); - setIsDeleted(false); enableRegister(enableRegister); ownPtr=true; } @@ -52,14 +50,6 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { ownPtr=false; } - public boolean getIsDeleted() { - return isDeleting; - } - - public void setIsDeleted(boolean b) { - isDeleting = b; - } - private void isValid() { if (nativePtr == 0) { throw new RuntimeException("proxy config removed"); @@ -357,4 +347,13 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { isValid(); return getPublishExpires(nativePtr); } + + @Override + public void setUserData(Object obj) { + userData = obj; + } + @Override + public Object getUserData() { + return userData; + } } From 012dc476e1fadcd6822ce2cf878e79bf94e70056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Wed, 23 Jul 2014 16:11:48 +0200 Subject: [PATCH 092/218] Destroy the "server_addresses" line in the configuration file while the save of the tunnel config when the tunnel host is empty --- coreapi/linphone_tunnel.cc | 44 +++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/coreapi/linphone_tunnel.cc b/coreapi/linphone_tunnel.cc index 151755078..2e77bbd95 100644 --- a/coreapi/linphone_tunnel.cc +++ b/coreapi/linphone_tunnel.cc @@ -60,16 +60,19 @@ void linphone_tunnel_destroy(LinphoneTunnel *tunnel){ static char *linphone_tunnel_config_to_string(const LinphoneTunnelConfig *tunnel_config) { char *str = NULL; - if(linphone_tunnel_config_get_remote_udp_mirror_port(tunnel_config) != -1) { - str = ms_strdup_printf("%s:%d:%d:%d", - linphone_tunnel_config_get_host(tunnel_config), - linphone_tunnel_config_get_port(tunnel_config), - linphone_tunnel_config_get_remote_udp_mirror_port(tunnel_config), - linphone_tunnel_config_get_delay(tunnel_config)); - } else { - str = ms_strdup_printf("%s:%d", - linphone_tunnel_config_get_host(tunnel_config), - linphone_tunnel_config_get_port(tunnel_config)); + const char *host = linphone_tunnel_config_get_host(tunnel_config); + if(host != NULL) { + if(linphone_tunnel_config_get_remote_udp_mirror_port(tunnel_config) != -1) { + str = ms_strdup_printf("%s:%d:%d:%d", + linphone_tunnel_config_get_host(tunnel_config), + linphone_tunnel_config_get_port(tunnel_config), + linphone_tunnel_config_get_remote_udp_mirror_port(tunnel_config), + linphone_tunnel_config_get_delay(tunnel_config)); + } else { + str = ms_strdup_printf("%s:%d", + linphone_tunnel_config_get_host(tunnel_config), + linphone_tunnel_config_get_port(tunnel_config)); + } } return str; } @@ -124,20 +127,21 @@ static LinphoneTunnelConfig *linphone_tunnel_config_from_string(const char *str) static void linphone_tunnel_save_config(LinphoneTunnel *tunnel) { - MSList *elem = tunnel->config_list; + MSList *elem = NULL; char *tmp = NULL, *old_tmp = NULL, *tc_str = NULL; - while(elem != NULL) { + for(elem = tunnel->config_list; elem != NULL; elem = elem->next) { LinphoneTunnelConfig *tunnel_config = (LinphoneTunnelConfig *)elem->data; tc_str = linphone_tunnel_config_to_string(tunnel_config); - if(tmp != NULL) { - old_tmp = tmp; - tmp = ms_strdup_printf("%s %s", old_tmp, tc_str); - ms_free(old_tmp); - ms_free(tc_str); - } else { - tmp = tc_str; + if(tc_str != NULL) { + if(tmp != NULL) { + old_tmp = tmp; + tmp = ms_strdup_printf("%s %s", old_tmp, tc_str); + ms_free(old_tmp); + ms_free(tc_str); + } else { + tmp = tc_str; + } } - elem = elem->next; } lp_config_set_string(config(tunnel), "tunnel", "server_addresses", tmp); if(tmp != NULL) { From 82ec76a4e1c1b63c028955303d954c430b9a5954 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Thu, 24 Jul 2014 16:19:19 +0200 Subject: [PATCH 093/218] Reuse previous nonce if outbound proxy realm is set to avoid reauthentication --- coreapi/authentication.c | 23 ++++++++++---------- coreapi/bellesip_sal/sal_impl.c | 2 +- coreapi/bellesip_sal/sal_op_impl.c | 11 +++++----- coreapi/bellesip_sal/sal_op_presence.c | 15 ++++++------- coreapi/linphonecall.c | 3 ++- coreapi/linphonecore.c | 1 + coreapi/linphonecore.h | 14 +++++++++++++ coreapi/private.h | 1 + coreapi/proxy.c | 29 ++++++++++++++++++-------- coreapi/sal.c | 11 ++++++++++ include/sal/sal.h | 2 ++ 11 files changed, 77 insertions(+), 35 deletions(-) diff --git a/coreapi/authentication.c b/coreapi/authentication.c index d763f8902..59b3ea1b5 100644 --- a/coreapi/authentication.c +++ b/coreapi/authentication.c @@ -21,7 +21,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ - + #include "linphonecore.h" #include "private.h" #include "lpconfig.h" @@ -143,7 +143,7 @@ void linphone_auth_info_write_config(LpConfig *config, LinphoneAuthInfo *obj, in char key[50]; sprintf(key,"auth_info_%i",pos); lp_config_clean_section(config,key); - + if (obj==NULL || lp_config_get_int(config, "sip", "store_auth_info", 1) == 0){ return; } @@ -176,12 +176,12 @@ LinphoneAuthInfo *linphone_auth_info_new_from_config_file(LpConfig * config, int char key[50]; const char *username,*userid,*passwd,*ha1,*realm,*domain; LinphoneAuthInfo *ret; - + sprintf(key,"auth_info_%i",pos); if (!lp_config_has_section(config,key)){ return NULL; } - + username=lp_config_get_string(config,key,"username",NULL); userid=lp_config_get_string(config,key,"userid",NULL); passwd=lp_config_get_string(config,key,"passwd",NULL); @@ -221,7 +221,7 @@ static int realm_match(const char *realm1, const char *realm2){ static const LinphoneAuthInfo *find_auth_info(LinphoneCore *lc, const char *username, const char *realm, const char *domain){ MSList *elem; const LinphoneAuthInfo *ret=NULL; - + for (elem=lc->auth_info;elem!=NULL;elem=elem->next) { LinphoneAuthInfo *pinfo = (LinphoneAuthInfo*)elem->data; if (username && pinfo->username && strcmp(username,pinfo->username)==0) { @@ -240,7 +240,7 @@ static const LinphoneAuthInfo *find_auth_info(LinphoneCore *lc, const char *user } } else if (domain && pinfo->domain && strcmp(domain,pinfo->domain)==0) { return pinfo; - } else if (!domain) { + } else if (!domain) { return pinfo; } } @@ -249,7 +249,7 @@ static const LinphoneAuthInfo *find_auth_info(LinphoneCore *lc, const char *user } /** - * Find authentication info matching realm, username, domain criterias. + * Find authentication info matching realm, username, domain criteria. * First of all, (realm,username) pair are searched. If multiple results (which should not happen because realm are supposed to be unique), then domain is added to the search. * @param lc the LinphoneCore * @param realm the authentication 'realm' (optional) @@ -264,7 +264,7 @@ const LinphoneAuthInfo *linphone_core_find_auth_info(LinphoneCore *lc, const cha if (ai==NULL && domain){ ai=find_auth_info(lc,username,realm,domain); } - } + } if (ai == NULL && domain != NULL) { ai=find_auth_info(lc,username,NULL,domain); } @@ -292,8 +292,8 @@ LinphoneAuthInfo * linphone_core_create_auth_info(LinphoneCore *lc, const char * /** * Adds authentication information to the LinphoneCore. - * - * This information will be used during all SIP transacations that require authentication. + * + * This information will be used during all SIP transactions that require authentication. **/ void linphone_core_add_auth_info(LinphoneCore *lc, const LinphoneAuthInfo *info){ LinphoneAuthInfo *ai; @@ -301,7 +301,7 @@ void linphone_core_add_auth_info(LinphoneCore *lc, const LinphoneAuthInfo *info) MSList *l; int restarted_op_count=0; bool_t updating=FALSE; - + if (info->ha1==NULL && info->passwd==NULL){ ms_error("linphone_core_add_auth_info(): info supplied with empty password or ha1."); return; @@ -371,7 +371,6 @@ void linphone_core_remove_auth_info(LinphoneCore *lc, const LinphoneAuthInfo *in r=(LinphoneAuthInfo*)linphone_core_find_auth_info(lc,info->realm,info->username,info->domain); if (r){ lc->auth_info=ms_list_remove(lc->auth_info,r); - /*printf("len=%i newlen=%i\n",len,newlen);*/ linphone_auth_info_destroy(r); write_auth_infos(lc); } diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index 9693226dd..ab7714fa9 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -137,7 +137,7 @@ void sal_process_authentication(SalOp *op) { return; } - if (belle_sip_provider_add_authorization(op->base.root->prov,new_request,response,from_uri,&auth_list)) { + if (belle_sip_provider_add_authorization(op->base.root->prov,new_request,response,from_uri,&auth_list,op->base.realm)) { if (is_within_dialog) { sal_op_send_request(op,new_request); } else { diff --git a/coreapi/bellesip_sal/sal_op_impl.c b/coreapi/bellesip_sal/sal_op_impl.c index 649a4e240..e548cec43 100644 --- a/coreapi/bellesip_sal/sal_op_impl.c +++ b/coreapi/bellesip_sal/sal_op_impl.c @@ -137,7 +137,7 @@ static void add_initial_route_set(belle_sip_request_t *request, const MSList *li continue; } } - + route=belle_sip_header_route_create((belle_sip_header_address_t*)addr); uri=belle_sip_header_address_get_uri((belle_sip_header_address_t*)route); belle_sip_uri_set_lr_param(uri,1); @@ -180,11 +180,11 @@ belle_sip_request_t* sal_op_build_request(SalOp *op,const char* method) { belle_sip_header_p_preferred_identity_t* p_preferred_identity=belle_sip_header_p_preferred_identity_create(BELLE_SIP_HEADER_ADDRESS(sal_op_get_from_address(op))); belle_sip_message_add_header(BELLE_SIP_MESSAGE(req),BELLE_SIP_HEADER(p_preferred_identity)); } - + if (elem && strcmp(method,"REGISTER")!=0 && !op->base.root->no_initial_route){ add_initial_route_set(req,elem); } - + if (strcmp("REGISTER",method)!=0 && op->privacy!=SalPrivacyNone ){ belle_sip_header_privacy_t* privacy_header=belle_sip_header_privacy_new(); if (op->privacy&SalPrivacyCritical) @@ -332,7 +332,7 @@ static int _sal_op_send_request_with_contact(SalOp* op, belle_sip_request_t* req if (!belle_sip_message_get_header(BELLE_SIP_MESSAGE(request),BELLE_SIP_AUTHORIZATION) && !belle_sip_message_get_header(BELLE_SIP_MESSAGE(request),BELLE_SIP_PROXY_AUTHORIZATION)) { /*hmm just in case we already have authentication param in cache*/ - belle_sip_provider_add_authorization(op->base.root->prov,request,NULL,NULL,NULL); + belle_sip_provider_add_authorization(op->base.root->prov,request,NULL,NULL,NULL,op->base.realm); } result = belle_sip_client_transaction_send_request_to(client_transaction,next_hop_uri/*might be null*/); @@ -608,7 +608,7 @@ int sal_op_send_and_create_refresher(SalOp* op,belle_sip_request_t* req, int exp belle_sip_object_unref(op->refresher); } if ((op->refresher = belle_sip_client_transaction_create_refresher(op->pending_client_trans))) { - /*since refresher acquires the transaction, we should remove our context from the transaction, because we won't be notified + /*since refresher acquires the transaction, we should remove our context from the transaction, because we won't be notified * that it is terminated anymore.*/ sal_op_unref(op);/*loose the reference that was given to the transaction when creating it*/ /* Note that the refresher will replace our data with belle_sip_transaction_set_application_data(). @@ -617,6 +617,7 @@ int sal_op_send_and_create_refresher(SalOp* op,belle_sip_request_t* req, int exp notify the user as a normal transaction*/ belle_sip_refresher_set_listener(op->refresher,listener,op); belle_sip_refresher_set_retry_after(op->refresher,op->base.root->refresher_retry_after); + belle_sip_refresher_set_realm(op->refresher,op->base.realm); belle_sip_refresher_enable_manual_mode(op->refresher,op->manual_refresher); return 0; } else { diff --git a/coreapi/bellesip_sal/sal_op_presence.c b/coreapi/bellesip_sal/sal_op_presence.c index e1ab054aa..23db49e6f 100644 --- a/coreapi/bellesip_sal/sal_op_presence.c +++ b/coreapi/bellesip_sal/sal_op_presence.c @@ -35,7 +35,7 @@ void sal_add_presence_info(SalOp *op, belle_sip_message_t *notify, SalPresenceMo belle_sip_message_remove_header(BELLE_SIP_MESSAGE(notify),BELLE_SIP_CONTENT_TYPE); belle_sip_message_remove_header(BELLE_SIP_MESSAGE(notify),BELLE_SIP_CONTENT_LENGTH); belle_sip_message_set_body(BELLE_SIP_MESSAGE(notify),NULL,0); - + if (content){ belle_sip_message_add_header(BELLE_SIP_MESSAGE(notify) ,BELLE_SIP_HEADER(belle_sip_header_content_type_create("application","pidf+xml"))); @@ -95,7 +95,7 @@ static void presence_response_event(void *op_base, const belle_sip_response_even belle_sip_request_t* request=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(client_transaction)); int code = belle_sip_response_get_status_code(response); belle_sip_header_expires_t* expires; - + sal_op_set_error_info_from_response(op,response); if (code>=300) { @@ -127,6 +127,7 @@ static void presence_response_event(void *op_base, const belle_sip_response_even if (expires>0){ op->refresher=belle_sip_client_transaction_create_refresher(client_transaction); belle_sip_refresher_set_listener(op->refresher,presence_refresher_listener,op); + belle_sip_refresher_set_realm(op->refresher,op->base.realm); } } break; @@ -164,7 +165,7 @@ static SalPresenceModel * process_presence_notification(SalOp *op, belle_sip_req return NULL; if (belle_sip_header_content_length_get_content_length(content_length) == 0) return NULL; - + if (body==NULL) return NULL; op->base.root->callbacks.parse_presence_requested(op, @@ -181,7 +182,7 @@ static void handle_notify(SalOp *op, belle_sip_request_t *req){ belle_sip_server_transaction_t* server_transaction=op->pending_server_trans; belle_sip_header_subscription_state_t* subscription_state_header=belle_sip_message_get_header_by_type(req,belle_sip_header_subscription_state_t); SalSubscribeStatus sub_state; - + if (strcmp("NOTIFY",belle_sip_request_get_method(req))==0) { SalPresenceModel *presence_model = NULL; const char* body = belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)); @@ -194,7 +195,7 @@ static void handle_notify(SalOp *op, belle_sip_request_t *req){ presence_model = process_presence_notification(op, req); if (presence_model != NULL || body==NULL) { /* Presence notification body parsed successfully. */ - + resp = sal_op_create_response_from_request(op, req, 200); /*create first because the op may be destroyed by notify_presence */ op->base.root->callbacks.notify_presence(op, sub_state, presence_model, NULL); } else if (body){ @@ -214,7 +215,7 @@ static void presence_process_request_event(void *op_base, const belle_sip_reques belle_sip_header_expires_t* expires = belle_sip_message_get_header_by_type(req,belle_sip_header_expires_t); belle_sip_response_t* resp; const char *method=belle_sip_request_get_method(req); - + belle_sip_object_ref(server_transaction); if (op->pending_server_trans) belle_sip_object_unref(op->pending_server_trans); op->pending_server_trans=server_transaction; @@ -256,7 +257,7 @@ static void presence_process_request_event(void *op_base, const belle_sip_reques } } break; - default: + default: ms_error("unexpected dialog state [%s]",belle_sip_dialog_state_to_string(dialog_state)); break; } diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 15df7990b..edfd973d2 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -724,6 +724,7 @@ LinphoneCall * linphone_call_new_incoming(LinphoneCore *lc, LinphoneAddress *fro from_str=linphone_address_as_string_uri_only(from); sal_op_set_route(call->ping_op,sal_op_get_network_origin(op)); sal_op_set_user_pointer(call->ping_op,call); + sal_op_set_realm(call->ping_op,linphone_proxy_config_get_realm(linphone_core_lookup_known_proxy(call->core, to))); sal_ping(call->ping_op,linphone_core_find_best_identity(lc,from),from_str); ms_free(from_str); } @@ -1313,7 +1314,7 @@ int linphone_call_take_video_snapshot(LinphoneCall *call, const char *file){ * Note that the snapshot is asynchronous, an application shall not assume that the file is created when the function returns. * @param call a LinphoneCall * @param file a path where to write the jpeg content. - * @return 0 if successfull, -1 otherwise (typically if jpeg format is not supported). + * @return 0 if successfull, -1 otherwise (typically if jpeg format is not supported). **/ int linphone_call_take_preview_snapshot(LinphoneCall *call, const char *file){ #ifdef VIDEO_ENABLED diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index c9b2dd4f0..35d91976d 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2824,6 +2824,7 @@ void linphone_configure_op(LinphoneCore *lc, SalOp *op, const LinphoneAddress *d sal_op_set_to_address(op,dest); sal_op_set_from(op,identity); sal_op_set_sent_custom_header(op,headers); + sal_op_set_realm(op,linphone_proxy_config_get_realm(proxy)); if (with_contact && proxy && proxy->op){ const SalAddress *contact; if ((contact=sal_op_get_contact_address(proxy->op))){ diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 6e0d88807..518ad7c4d 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -917,6 +917,20 @@ LINPHONE_PUBLIC bool_t linphone_proxy_config_is_registered(const LinphoneProxyCo **/ LINPHONE_PUBLIC const char *linphone_proxy_config_get_domain(const LinphoneProxyConfig *cfg); +/** + * Get the realm of the given proxy config. + * @param[in] cfg #LinphoneProxyConfig object. + * @returns The realm of the proxy config. +**/ +LINPHONE_PUBLIC const char *linphone_proxy_config_get_realm(const LinphoneProxyConfig *cfg); +/** + * Set the realm of the given proxy config. + * @param[in] cfg #LinphoneProxyConfig object. + * @param[in] realm New realm value. + * @returns The realm of the proxy config. +**/ +LINPHONE_PUBLIC void linphone_proxy_config_set_realm(LinphoneProxyConfig *cfg, const char * realm); + LINPHONE_PUBLIC const char *linphone_proxy_config_get_route(const LinphoneProxyConfig *obj); LINPHONE_PUBLIC const char *linphone_proxy_config_get_identity(const LinphoneProxyConfig *obj); LINPHONE_PUBLIC bool_t linphone_proxy_config_publish_enabled(const LinphoneProxyConfig *obj); diff --git a/coreapi/private.h b/coreapi/private.h index 32e013c94..f1f6fdf38 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -423,6 +423,7 @@ struct _LinphoneProxyConfig char *reg_identity; char *reg_route; char *quality_reporting_collector; + char *domain; char *realm; char *contact_params; char *contact_uri_params; diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 409ddb4a3..619c98814 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -94,6 +94,7 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *ob const char *identity = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_identity", NULL) : NULL; const char *proxy = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_proxy", NULL) : NULL; const char *route = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_route", NULL) : NULL; + const char *realm = lc ? lp_config_get_default_string(lc->config, "proxy", "realm", NULL) : NULL; const char *quality_reporting_collector = lc ? lp_config_get_default_string(lc->config, "proxy", "quality_reporting_collector", NULL) : NULL; const char *contact_params = lc ? lp_config_get_default_string(lc->config, "proxy", "contact_parameters", NULL) : NULL; const char *contact_uri_params = lc ? lp_config_get_default_string(lc->config, "proxy", "contact_uri_parameters", NULL) : NULL; @@ -108,6 +109,7 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *ob obj->reg_identity = identity ? ms_strdup(identity) : NULL; obj->reg_proxy = proxy ? ms_strdup(proxy) : NULL; obj->reg_route = route ? ms_strdup(route) : NULL; + obj->realm = realm ? ms_strdup(realm) : NULL; obj->quality_reporting_enabled = lc ? lp_config_get_default_int(lc->config, "proxy", "quality_reporting_enabled", 0) : 0; obj->quality_reporting_collector = quality_reporting_collector ? ms_strdup(quality_reporting_collector) : NULL; obj->quality_reporting_interval = lc ? lp_config_get_default_int(lc->config, "proxy", "quality_reporting_interval", 0) : 0; @@ -150,6 +152,7 @@ void linphone_proxy_config_destroy(LinphoneProxyConfig *obj){ if (obj->reg_route!=NULL) ms_free(obj->reg_route); if (obj->quality_reporting_collector!=NULL) ms_free(obj->quality_reporting_collector); if (obj->ssctx!=NULL) sip_setup_context_free(obj->ssctx); + if (obj->domain!=NULL) ms_free(obj->domain); if (obj->realm!=NULL) ms_free(obj->realm); if (obj->type!=NULL) ms_free(obj->type); if (obj->dial_prefix!=NULL) ms_free(obj->dial_prefix); @@ -228,10 +231,10 @@ int linphone_proxy_config_set_identity(LinphoneProxyConfig *obj, const char *ide obj->reg_identity=NULL; } obj->reg_identity=ms_strdup(identity); - if (obj->realm){ - ms_free(obj->realm); + if (obj->domain){ + ms_free(obj->domain); } - obj->realm=ms_strdup(linphone_address_get_domain(addr)); + obj->domain=ms_strdup(linphone_address_get_domain(addr)); linphone_address_destroy(addr); return 0; } @@ -240,7 +243,7 @@ int linphone_proxy_config_set_identity(LinphoneProxyConfig *obj, const char *ide } const char *linphone_proxy_config_get_domain(const LinphoneProxyConfig *cfg){ - return cfg->realm; + return cfg->domain; } /** @@ -310,7 +313,7 @@ void linphone_proxy_config_enable_publish(LinphoneProxyConfig *obj, bool_t val){ /** * Prevent a proxy config from refreshing its registration. - * This is useful to let registrations to expire naturally (or) when the application wants to keep control on when + * This is useful to let registrations to expire naturally (or) when the application wants to keep control on when * refreshes are sent. * However, linphone_core_set_network_reachable(lc,TRUE) will always request the proxy configs to refresh their registrations. * The refreshing operations can be resumed with linphone_proxy_config_refresh_register(). @@ -422,6 +425,7 @@ static void linphone_proxy_config_register(LinphoneProxyConfig *obj){ linphone_address_destroy(contact); } sal_op_set_user_pointer(obj->op,obj); + sal_op_set_realm(obj->op, obj->realm); if (sal_register(obj->op,proxy_string,obj->reg_identity,obj->expires)==0) { linphone_proxy_config_set_state(obj,LinphoneRegistrationProgress,"Registration in progress"); } else { @@ -946,13 +950,16 @@ int linphone_proxy_config_done(LinphoneProxyConfig *obj) return 0; } +const char* linphone_proxy_config_get_realm(const LinphoneProxyConfig *cfg) +{ + return cfg?cfg->realm:NULL; +} void linphone_proxy_config_set_realm(LinphoneProxyConfig *cfg, const char *realm) { if (cfg->realm!=NULL) { ms_free(cfg->realm); - cfg->realm=NULL; } - if (realm!=NULL) cfg->realm=ms_strdup(realm); + cfg->realm=ms_strdup(realm); } int linphone_proxy_config_send_publish(LinphoneProxyConfig *proxy, LinphonePresenceModel *presence){ @@ -964,6 +971,7 @@ int linphone_proxy_config_send_publish(LinphoneProxyConfig *proxy, LinphonePrese sal_op_set_route(proxy->publish_op,proxy->reg_proxy); sal_op_set_from(proxy->publish_op,linphone_proxy_config_get_identity(proxy)); sal_op_set_to(proxy->publish_op,linphone_proxy_config_get_identity(proxy)); + sal_op_set_realm(proxy->publish_op,linphone_proxy_config_get_realm(proxy)); if (lp_config_get_int(proxy->lc->config,"sip","publish_msg_with_contact",0)){ SalAddress *addr=sal_address_new(linphone_proxy_config_get_identity(proxy)); sal_op_set_contact_address(proxy->publish_op,addr); @@ -1205,6 +1213,9 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC if (obj->reg_identity!=NULL){ lp_config_set_string(config,key,"reg_identity",obj->reg_identity); } + if (obj->realm!=NULL){ + lp_config_set_string(config,key,"realm",obj->realm); + } if (obj->contact_params!=NULL){ lp_config_set_string(config,key,"contact_parameters",obj->contact_params); } @@ -1259,10 +1270,10 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LinphoneCore* lc CONFIGURE_STRING_VALUE(cfg,config,key,server_addr,"reg_proxy") CONFIGURE_STRING_VALUE(cfg,config,key,route,"reg_route") + CONFIGURE_STRING_VALUE(cfg,config,key,realm,"realm") + CONFIGURE_BOOL_VALUE(cfg,config,key,quality_reporting,"quality_reporting_enabled") - CONFIGURE_STRING_VALUE(cfg,config,key,quality_reporting_collector,"quality_reporting_collector") - CONFIGURE_INT_VALUE(cfg,config,key,quality_reporting_interval,"quality_reporting_interval") CONFIGURE_STRING_VALUE(cfg,config,key,contact_parameters,"contact_parameters") diff --git a/coreapi/sal.c b/coreapi/sal.c index 24da751b6..8a48c79d0 100644 --- a/coreapi/sal.c +++ b/coreapi/sal.c @@ -386,6 +386,13 @@ void sal_op_add_route_address(SalOp *op, const SalAddress *address){ sal_op_set_route_address(op,address); } } +void sal_op_set_realm(SalOp *op, const char *realm){ + SalOpBase* op_base = (SalOpBase*)op; + if (op_base->realm != NULL){ + ms_free(op_base->realm); + } + op_base->realm = ms_strdup(realm); +} void sal_op_set_from(SalOp *op, const char *from){ SET_PARAM(op,from); } @@ -511,6 +518,10 @@ void __sal_op_free(SalOp *op){ ms_free(b->route); b->route=NULL; } + if (b->realm) { + ms_free(b->realm); + b->realm=NULL; + } if (b->contact_address) { sal_address_destroy(b->contact_address); } diff --git a/include/sal/sal.h b/include/sal/sal.h index 5533182ed..ffca87c5f 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -285,6 +285,7 @@ typedef struct SalOpBase{ SalMediaDescription *remote_media; void *user_pointer; const char* call_id; + char* realm; SalAddress* service_route; /*as defined by rfc3608, might be a list*/ SalCustomHeader *sent_custom_headers; SalCustomHeader *recv_custom_headers; @@ -560,6 +561,7 @@ void sal_op_set_contact_address(SalOp *op, const SalAddress* address); void sal_op_set_route(SalOp *op, const char *route); void sal_op_set_route_address(SalOp *op, const SalAddress* address); void sal_op_add_route_address(SalOp *op, const SalAddress* address); +void sal_op_set_realm(SalOp *op, const char *realm); void sal_op_set_from(SalOp *op, const char *from); void sal_op_set_from_address(SalOp *op, const SalAddress *from); void sal_op_set_to(SalOp *op, const char *to); From b8c9c8093408d7ef1e9091877e4ff5e16cbd241a Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Fri, 25 Jul 2014 14:48:31 +0200 Subject: [PATCH 094/218] enable no-rtp timeout for early media --- coreapi/linphonecore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 35d91976d..299bdc27f 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2351,7 +2351,7 @@ void linphone_core_iterate(LinphoneCore *lc){ #endif //BUILD_UPNP linphone_core_start_invite(lc,call, NULL); } - if (call->state==LinphoneCallIncomingReceived){ + if (call->state==LinphoneCallIncomingReceived || call->state==LinphoneCallIncomingEarlyMedia){ if (one_second_elapsed) ms_message("incoming call ringing for %i seconds",elapsed); if (elapsed>lc->sip_conf.inc_timeout){ LinphoneReason decline_reason; From 66044e773d7951957f61ad9c29483212b1285ba9 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Mon, 28 Jul 2014 15:15:35 +0200 Subject: [PATCH 095/218] Factorization of linphone_configure_op for every linphone core requests --- coreapi/linphonecall.c | 10 +++++----- coreapi/proxy.c | 21 ++++++++++++++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index edfd973d2..f8c6d1d72 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -701,7 +701,6 @@ void linphone_call_set_compatible_incoming_call_parameters(LinphoneCall *call, c LinphoneCall * linphone_call_new_incoming(LinphoneCore *lc, LinphoneAddress *from, LinphoneAddress *to, SalOp *op){ LinphoneCall *call=ms_new0(LinphoneCall,1); - char *from_str; const SalMediaDescription *md; LinphoneFirewallPolicy fpol; @@ -721,12 +720,13 @@ LinphoneCall * linphone_call_new_incoming(LinphoneCore *lc, LinphoneAddress *fro /*the following sends an option request back to the caller so that we get a chance to discover our nat'd address before answering.*/ call->ping_op=sal_op_new(lc->sal); - from_str=linphone_address_as_string_uri_only(from); + + linphone_configure_op(lc, call->ping_op, from, NULL, FALSE); + sal_op_set_route(call->ping_op,sal_op_get_network_origin(op)); sal_op_set_user_pointer(call->ping_op,call); - sal_op_set_realm(call->ping_op,linphone_proxy_config_get_realm(linphone_core_lookup_known_proxy(call->core, to))); - sal_ping(call->ping_op,linphone_core_find_best_identity(lc,from),from_str); - ms_free(from_str); + + sal_ping(call->ping_op,sal_op_get_from(call->ping_op), sal_op_get_to(call->ping_op)); } } diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 619c98814..e6a33d556 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -412,6 +412,7 @@ void _linphone_proxy_config_unregister(LinphoneProxyConfig *obj) { static void linphone_proxy_config_register(LinphoneProxyConfig *obj){ if (obj->reg_sendregister){ LinphoneAddress* proxy=linphone_address_new(obj->reg_proxy); + LinphoneAddress* to=linphone_address_new(obj->reg_identity); char* proxy_string; LinphoneAddress *contact; ms_message("LinphoneProxyConfig [%p] about to register (LinphoneCore version: %s)",obj,linphone_core_get_version()); @@ -420,12 +421,18 @@ static void linphone_proxy_config_register(LinphoneProxyConfig *obj){ if (obj->op) sal_op_release(obj->op); obj->op=sal_op_new(obj->lc->sal); + + linphone_configure_op(obj->lc, obj->op, to, NULL, FALSE); + linphone_address_destroy(to); + if ((contact=guess_contact_for_register(obj))) { sal_op_set_contact_address(obj->op,contact); linphone_address_destroy(contact); } + sal_op_set_user_pointer(obj->op,obj); - sal_op_set_realm(obj->op, obj->realm); + + if (sal_register(obj->op,proxy_string,obj->reg_identity,obj->expires)==0) { linphone_proxy_config_set_state(obj,LinphoneRegistrationProgress,"Registration in progress"); } else { @@ -967,11 +974,15 @@ int linphone_proxy_config_send_publish(LinphoneProxyConfig *proxy, LinphonePrese if (proxy->state==LinphoneRegistrationOk || proxy->state==LinphoneRegistrationCleared){ if (proxy->publish_op==NULL){ + LinphoneAddress *to=linphone_address_new(linphone_proxy_config_get_identity(proxy)); proxy->publish_op=sal_op_new(proxy->lc->sal); - sal_op_set_route(proxy->publish_op,proxy->reg_proxy); - sal_op_set_from(proxy->publish_op,linphone_proxy_config_get_identity(proxy)); - sal_op_set_to(proxy->publish_op,linphone_proxy_config_get_identity(proxy)); - sal_op_set_realm(proxy->publish_op,linphone_proxy_config_get_realm(proxy)); + + linphone_configure_op(proxy->lc, proxy->publish_op, + to, NULL, FALSE); + + if (to!=NULL){ + linphone_address_destroy(to); + } if (lp_config_get_int(proxy->lc->config,"sip","publish_msg_with_contact",0)){ SalAddress *addr=sal_address_new(linphone_proxy_config_get_identity(proxy)); sal_op_set_contact_address(proxy->publish_op,addr); From d8cc5b600f3771b101f52c2da0761b78a3b4e947 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Tue, 29 Jul 2014 14:11:21 +0200 Subject: [PATCH 096/218] Add JNI wrapper for linphone proxy config realm parameter --- coreapi/linphonecore_jni.cc | 13 +++++++++++++ .../org/linphone/core/LinphoneProxyConfig.java | 17 +++++++++++++++-- .../linphone/core/LinphoneProxyConfigImpl.java | 16 ++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 609493b3e..1f01ccadf 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -3033,6 +3033,19 @@ JNIEXPORT jstring JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_getQual return jvalue; } +JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_setRealm(JNIEnv *env, jobject thiz, jlong ptr, jstring jrealm) { + if (jrealm){ + const char *realm=env->GetStringUTFChars(jrealm, NULL); + linphone_proxy_config_set_realm((LinphoneProxyConfig *)ptr, realm); + env->ReleaseStringUTFChars(jrealm,realm); + } +} + +JNIEXPORT jstring JNICALL Java_org_linphone_core_LinphoneProxyConfigImpl_getRealm(JNIEnv *env, jobject thiz, jlong ptr) { + jstring jvalue = env->NewStringUTF(linphone_proxy_config_get_realm((LinphoneProxyConfig *)ptr)); + return jvalue; +} + extern "C" jint Java_org_linphone_core_LinphoneCallImpl_getDuration(JNIEnv* env,jobject thiz,jlong ptr) { return (jint)linphone_call_get_duration((LinphoneCall *) ptr); } diff --git a/java/common/org/linphone/core/LinphoneProxyConfig.java b/java/common/org/linphone/core/LinphoneProxyConfig.java index 711a53668..8b668da2e 100644 --- a/java/common/org/linphone/core/LinphoneProxyConfig.java +++ b/java/common/org/linphone/core/LinphoneProxyConfig.java @@ -224,6 +224,19 @@ public interface LinphoneProxyConfig { */ String getQualityReportingCollector(); + /** + * Set the outbound proxy realm. It is used in digest authentication to avoid + * re-authentication if a previous token has already been provided. + * @param The new outbound proxy realm. + */ + void setRealm(String realm); + + /** + * Get the outbound proxy realm. + * @return The outbound proxy realm. + */ + String getRealm(); + /** * Set optional contact parameters that will be added to the contact information sent in the registration. * @param contact_params a string containing the additional parameters in text form, like "myparam=something;myparam2=something_else" @@ -287,12 +300,12 @@ public interface LinphoneProxyConfig { * @return the publish expiration time in second. Default value is the registration expiration value. */ public int getPublishExpires(); - + /** * attached a user data to a proxy config **/ void setUserData(Object obj); - + /** * Returns user data from a proxy config. return null if any * @return an Object. diff --git a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java index b7c301297..5113f958c 100644 --- a/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java +++ b/java/impl/org/linphone/core/LinphoneProxyConfigImpl.java @@ -321,6 +321,7 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { isValid(); return getQualityReportingInterval(nativePtr); } + private native void setQualityReportingCollector(long nativePtr, String collector); @Override public void setQualityReportingCollector(String collector) { @@ -334,6 +335,21 @@ class LinphoneProxyConfigImpl implements LinphoneProxyConfig { isValid(); return getQualityReportingCollector(nativePtr); } + + private native void setRealm(long nativePtr, String realm); + @Override + public void setRealm(String realm) { + isValid(); + setRealm(nativePtr, realm); + } + private native String getRealm(long nativePtr); + @Override + public String getRealm() { + + isValid(); + return getRealm(nativePtr); + } + private native void setPublishExpires(long nativePtr, int expires); @Override public void setPublishExpires(int expires) { From 1a2990a8b65389578320c9a289fdd9c6bd7d2e79 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 29 Jul 2014 18:11:14 +0200 Subject: [PATCH 097/218] Beginning of the implementation of linphone_core_new() wrapper + Allow setting log handler from python. --- tools/python/apixml2python.py | 9 +- .../python/apixml2python/handwritten.mustache | 108 ++++++++++++++++++ tools/python/apixml2python/linphone.py | 10 +- .../apixml2python/linphone_module.mustache | 84 +++----------- 4 files changed, 134 insertions(+), 77 deletions(-) create mode 100644 tools/python/apixml2python/handwritten.mustache diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 56aeb275a..fda7a9262 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -72,13 +72,12 @@ blacklisted_functions = [ 'linphone_core_get_supported_video_sizes', 'linphone_core_get_video_codecs', 'linphone_core_get_video_policy', - 'linphone_core_new', 'linphone_core_new_with_config', 'linphone_core_payload_type_enabled', 'linphone_core_payload_type_is_vbr', 'linphone_core_publish', 'linphone_core_set_log_file', - 'linphone_core_set_log_handler', + 'linphone_core_set_log_handler', # Hand-written but put directly in the linphone module 'linphone_core_set_log_level', 'linphone_core_set_payload_type_bitrate', 'linphone_core_set_preferred_video_size', @@ -107,12 +106,16 @@ blacklisted_functions = [ 'lp_config_load_dict_to_section', 'lp_config_section_to_dict' ] +hand_written_functions = [ + 'linphone_core_new' +] def generate(apixmlfile): tree = ET.parse(apixmlfile) renderer = pystache.Renderer() - m = LinphoneModule(tree, blacklisted_classes, blacklisted_functions) + m = LinphoneModule(tree, blacklisted_classes, blacklisted_functions, hand_written_functions) f = open("linphone.c", "w") + os.chdir('apixml2python') f.write(renderer.render(m)) diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache new file mode 100644 index 000000000..fd25b0a72 --- /dev/null +++ b/tools/python/apixml2python/handwritten.mustache @@ -0,0 +1,108 @@ +static void pylinphone_log(const char *level, int indent, const char *fmt, va_list args) { + static int current_indent = 1; + PyObject *linphone_module = PyImport_ImportModule("linphone"); + if ((linphone_module != NULL) && PyObject_HasAttrString(linphone_module, "__log_handler")) { + PyObject *log_handler = PyObject_GetAttrString(linphone_module, "__log_handler"); + if ((log_handler != NULL) && PyFunction_Check(log_handler)) { + char logstr[4096]; + int i = 0; + if (indent == -1) current_indent--; + if (current_indent < 1) current_indent = 1; + if ((indent >= -1) && (indent <= 1)) { + for (i = 0; i < current_indent; i++) { + logstr[i] = '\t'; + } + } + if (indent == 1) current_indent++; + if (vsnprintf(logstr + i, sizeof(logstr) - i, fmt, args) > 0) { + PyEval_CallFunction(log_handler, "ss", level, logstr); + } + } + } +} + +static PYLINPHONE_INLINE void pylinphone_trace(int indent, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + pylinphone_log("debug", indent, fmt, args); + va_end(args); +} + +static const char * pylinphone_ortp_log_level_to_string(OrtpLogLevel lev) { + switch (lev) { + default: + case ORTP_DEBUG: + return "debug"; + case ORTP_MESSAGE: + return "info"; + case ORTP_WARNING: + return "warning"; + case ORTP_ERROR: + return "error"; + case ORTP_FATAL: + return "critical"; + case ORTP_TRACE: + return "debug"; + } +} + +static void pylinphone_module_log_handler(OrtpLogLevel lev, const char *fmt, va_list args) { + PyObject *linphone_module = PyImport_ImportModule("linphone"); + const char *level = pylinphone_ortp_log_level_to_string(lev); + if ((linphone_module != NULL) && PyObject_HasAttrString(linphone_module, "__log_handler")) { + PyObject *log_handler = PyObject_GetAttrString(linphone_module, "__log_handler"); + if ((log_handler != NULL) && PyFunction_Check(log_handler)) { + char logstr[4096]; + if (vsnprintf(logstr, sizeof(logstr), fmt, args) > 0) { + PyEval_CallFunction(log_handler, "ss", level, logstr); + } + } + } +} + +static void pylinphone_init_logging(void) { + linphone_core_set_log_handler(pylinphone_module_log_handler); + linphone_core_set_log_level(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR|ORTP_FATAL); +} + + +static PyObject * pylinphone_module_method_set_log_handler(PyObject *self, PyObject *args) { + PyObject *linphone_module = PyImport_ImportModule("linphone"); + PyObject *callback; + if (!PyArg_ParseTuple(args, "O", &callback)) { + return NULL; + } + if (linphone_module != NULL) { + Py_INCREF(callback); + PyObject_SetAttrString(linphone_module, "__log_handler", callback); + } + Py_RETURN_NONE; +} + +static PyObject * pylinphone_Core_class_method_new(PyObject *cls, PyObject *args) { + LinphoneCore * cresult; + pylinphone_CoreObject *self; + PyObject * pyret; + LinphoneCoreVTable _vtable = { 0 }; + const char * _config_path; + const char * _factory_config_path; + + if (!PyArg_ParseTuple(args, "zz", &_config_path, &_factory_config_path)) { + return NULL; + } + + self = (pylinphone_CoreObject *)PyObject_New(pylinphone_CoreObject, &pylinphone_CoreType); + if (self == NULL) { + return NULL; + } + + pylinphone_trace(1, "[PYLINPHONE] >>> %s(\"%s\", \"%s\")", __FUNCTION__, _config_path, _factory_config_path); + cresult = linphone_core_new(&_vtable, _config_path, _factory_config_path, self); + self->native_ptr = cresult; + + pyret = Py_BuildValue("O", self); + + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> %p", __FUNCTION__, pyret); + Py_DECREF(self); + return pyret; +} diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index bdffdba13..7e3816410 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -537,7 +537,7 @@ class SetterMethodDefinition(MethodDefinition): class LinphoneModule(object): - def __init__(self, tree, blacklisted_classes, blacklisted_functions): + def __init__(self, tree, blacklisted_classes, blacklisted_functions, hand_written_functions): self.internal_instance_method_names = ['destroy', 'ref', 'unref'] self.internal_property_names = ['user_data'] self.enums = [] @@ -577,6 +577,7 @@ class LinphoneModule(object): c['class_destroyable'] = (xml_class.get('destroyable') == 'true') c['class_has_user_data'] = False c['class_type_methods'] = [] + c['class_type_hand_written_methods'] = [] xml_type_methods = xml_class.findall("./classmethods/classmethod") for xml_type_method in xml_type_methods: if xml_type_method.get('deprecated') == 'true': @@ -586,8 +587,11 @@ class LinphoneModule(object): continue m = {} m['method_name'] = method_name.replace(c['class_c_function_prefix'], '') - m['method_xml_node'] = xml_type_method - c['class_type_methods'].append(m) + if method_name in hand_written_functions: + c['class_type_hand_written_methods'].append(m) + else: + m['method_xml_node'] = xml_type_method + c['class_type_methods'].append(m) c['class_instance_methods'] = [] xml_instance_methods = xml_class.findall("./instancemethods/instancemethod") for xml_instance_method in xml_instance_methods: diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 2fc006def..ce222015f 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -30,76 +30,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif -static PyObject *logging_module = NULL; -static int current_indent = 1; - - -static void init_logging(void) { - logging_module = PyImport_ImportModule("logging"); - if (logging_module != NULL) { - PyObject *constant; - PyObject *func; - PyObject *kws; - long level = 0; - - constant = PyObject_GetAttrString(logging_module, "DEBUG"); - if (PyInt_Check(constant)) { - level = PyInt_AsLong(constant); - } - Py_DECREF(constant); - func = PyObject_GetAttrString(logging_module, "basicConfig"); - kws = Py_BuildValue("{s:i,s:s}", "level", level, "format", "%(levelname)s: %(message)s"); - PyEval_CallObjectWithKeywords(func, NULL, kws); - Py_DECREF(kws); - Py_DECREF(func); - } -} - -static void pylinphone_log(const char *level, int indent, const char *fmt, va_list args) { - if (logging_module != NULL) { - char logstr[4096]; - int i = 0; - if (indent == -1) current_indent--; - if (current_indent < 1) current_indent = 1; - if ((indent >= -1) && (indent <= 1)) { - for (i = 0; i < current_indent; i++) { - logstr[i] = '\t'; - } - } - if (indent == 1) current_indent++; - if (vsnprintf(logstr + i, sizeof(logstr) - i, fmt, args) > 0) { - PyEval_CallMethod(logging_module, level, "(s)", logstr); - } - } -} - -static PYLINPHONE_INLINE void pylinphone_debug(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - pylinphone_log("debug", 0xff, fmt, args); - va_end(args); -} - -static PYLINPHONE_INLINE void pylinphone_info(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - pylinphone_log("info", 0xff, fmt, args); - va_end(args); -} - -static PYLINPHONE_INLINE void pylinphone_warning(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - pylinphone_log("warning", 0xff, fmt, args); - va_end(args); -} - -static PYLINPHONE_INLINE void pylinphone_trace(int indent, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - pylinphone_log("debug", indent, fmt, args); - va_end(args); -} +static PYLINPHONE_INLINE void pylinphone_trace(int indent, const char *fmt, ...); {{#classes}} @@ -118,6 +49,9 @@ typedef struct { {{#classes}} static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(PyObject *self); static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *type, const {{class_cname}} *native_ptr); +{{#class_type_hand_written_methods}} +static PyObject * pylinphone_{{class_name}}_class_method_{{method_name}}(PyObject *cls, PyObject *args); +{{/class_type_hand_written_methods}} {{/classes}} {{#classes}} @@ -157,6 +91,9 @@ static PyObject * pylinphone_{{class_name}}_instance_method_{{method_name}}(PyOb static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { // TODO: Handle doc /* Class methods */ +{{#class_type_hand_written_methods}} + { "{{method_name}}", pylinphone_{{class_name}}_class_method_{{method_name}}, METH_VARARGS | METH_CLASS, "" }, +{{/class_type_hand_written_methods}} {{#class_type_methods}} { "{{method_name}}", pylinphone_{{class_name}}_class_method_{{method_name}}, METH_VARARGS | METH_CLASS, "" }, {{/class_type_methods}} @@ -234,8 +171,13 @@ static PyTypeObject pylinphone_{{class_name}}Type = { {{/classes}} + +{{> handwritten}} + + static PyMethodDef pylinphone_ModuleMethods[] = { /* Sentinel */ + { "set_log_handler", pylinphone_module_method_set_log_handler, METH_VARARGS, "" }, { NULL, NULL, 0, NULL } }; @@ -248,7 +190,7 @@ PyMODINIT_FUNC initlinphone(void) { PyObject *m; PyObject *menum; - init_logging(); + pylinphone_init_logging(); {{#classes}} if (PyType_Ready(&pylinphone_{{class_name}}Type) < 0) return; From 67c8c4df731620c5fd83520bc8c19b864c66e0b9 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 29 Jul 2014 18:12:31 +0200 Subject: [PATCH 098/218] Add test program in python. --- tools/python/test.py | 79 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tools/python/test.py diff --git a/tools/python/test.py b/tools/python/test.py new file mode 100644 index 000000000..d291d1f65 --- /dev/null +++ b/tools/python/test.py @@ -0,0 +1,79 @@ +import linphone +import logging +import signal +import sys +import threading +import time + + +class StoppableThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.stop_event = threading.Event() + + def stop(self): + if self.isAlive() == True: + # Set an event to signal the thread to terminate + self.stop_event.set() + # Block the calling thread until the thread really has terminated + self.join() + +class IntervalTimer(StoppableThread): + def __init__(self, interval, worker_func, kwargs={}): + StoppableThread.__init__(self) + self._interval = interval + self._worker_func = worker_func + self._kwargs = kwargs + + def run(self): + while not self.stop_event.is_set(): + self._worker_func(self._kwargs) + time.sleep(self._interval) + + +# Configure logging module +logging.addLevelName(logging.DEBUG, "\033[1;37m%s\033[1;0m" % logging.getLevelName(logging.DEBUG)) +logging.addLevelName(logging.INFO, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.INFO)) +logging.addLevelName(logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) +logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) +logging.basicConfig(level=logging.DEBUG, format="%(asctime)s.%(msecs)-3d %(levelname)s: %(message)s", datefmt="%H:%M:%S") + +# Define the linphone module log handler +def log_handler(level, msg): + method = getattr(logging, level) + method(msg) + + +def test_friend(): + f = linphone.Friend.new() + print(f.address) + a1 = linphone.Address.new("sip:cotcot@sip.linphone.org") + print(a1.username) + print(a1.domain) + a1.domain = "sip2.linphone.org" + print(a1.domain) + f.address = a1 + a2 = f.address + + +def signal_handler(signal, frame): + cont = False + raise KeyError("Ctrl+C") + +# Define the iteration function +def iterate(kwargs): + core = kwargs['core'] + core.iterate() + +# Create a linphone core and iterate every 20 ms +linphone.set_log_handler(log_handler) +core = linphone.Core.new(None, None) +interval = IntervalTimer(0.02, iterate, kwargs={'core':core}) +signal.signal(signal.SIGINT, signal_handler) +try: + interval.start() + signal.pause() +except KeyError: + interval.stop() + del interval +del core From ce24877b9d1fa7443de62c5a2f795d4542507775 Mon Sep 17 00:00:00 2001 From: Johan Pascal Date: Tue, 29 Jul 2014 21:16:01 +0200 Subject: [PATCH 099/218] File transfer: add a cancel function to the core - allow cancellation during upload or download + fix bug in file transfer message tester --- coreapi/chat.c | 48 +++++++++++++--- coreapi/linphonecore.h | 3 +- coreapi/private.h | 5 +- tester/message_tester.c | 121 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 158 insertions(+), 19 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index e445d2bfa..28c1741ba 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -48,10 +48,13 @@ static size_t linphone_chat_message_compute_filepart_header_size(const char *fil } static void process_io_error(void *data, const belle_sip_io_error_event_t *event){ LinphoneChatMessage* msg=(LinphoneChatMessage *)data; + ms_error("I/O Error during file upload or download to/from %s - msg [%p] chat room[%p]", msg->chat_room->lc->file_transfer_server, msg, msg->chat_room); msg->cb(msg, LinphoneChatMessageStateNotDelivered, msg->chat_room->lc); } static void process_auth_requested(void *data, belle_sip_auth_event_t *event){ - printf("We have a auth requested!\n"); + LinphoneChatMessage* msg=(LinphoneChatMessage *)data; + ms_error("Error during file upload or download : auth requested to connect %s - msg [%p] chat room[%p]", msg->chat_room->lc->file_transfer_server, msg, msg->chat_room); + msg->cb(msg, LinphoneChatMessageStateNotDelivered, msg->chat_room->lc); } /** @@ -157,9 +160,14 @@ static void linphone_chat_message_process_response_from_post_file(void *data, co cbs.process_io_error=process_io_error; cbs.process_auth_requested=process_auth_requested; l=belle_http_request_listener_create_from_callbacks(&cbs,msg); + msg->http_request=req; /* update the reference to the http request to be able to cancel it during upload */ belle_http_provider_send_request(msg->chat_room->lc->http_provider,req,l); } - if (code == 200 ) { /* file has been uplaoded correctly, get server reply and send it */ + if (code == 200 ) { /* file has been uploaded correctly, get server reply and send it */ + /* TODO Check that the transfer has not been cancelled, note this shall be removed once the belle sip API will provide a cancel request as we shall never reach this part if the transfer is actually cancelled */ + if (msg->http_request == NULL) { + return; + } const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response); msg->message = ms_strdup(body); linphone_content_uninit(msg->file_transfer_information); @@ -338,6 +346,7 @@ static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatM cbs.process_io_error=process_io_error; cbs.process_auth_requested=process_auth_requested; l=belle_http_request_listener_create_from_callbacks(&cbs,msg); /* give msg to listener to be able to start the actual file upload when server answer a 204 No content */ + msg->http_request = req; /* keep a reference on the request to be able to cancel it */ belle_http_provider_send_request(cr->lc->http_provider,req,l); linphone_chat_message_unref(msg); return; @@ -371,8 +380,8 @@ static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatM content_type=ms_strdup_printf("message/external-body; access-type=URL; URL=\"%s\"",msg->external_body_url); sal_message_send(op,identity,cr->peer,content_type, NULL); ms_free(content_type); - } else { - if (msg->content_type == NULL) { + } else { /* the message is either text or have a file transfer using RCS recommendation */ + if (msg->content_type == NULL) { /* if no content type is specified, it is a text message */ sal_text_send(op, identity, cr->peer,msg->message); } else { sal_message_send(op, identity, cr->peer, msg->content_type, msg->message); @@ -450,7 +459,7 @@ void linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessag /* create a new chat room */ cr=linphone_core_create_chat_room(lc,cleanfrom); } - if (sal_msg->content_type != NULL) { /* content_type field is, for now, used only for rcs file transfer bu twe shall strcmp it with "application/vnd.gsma.rcs-ft-http+xml" */ + if (sal_msg->content_type != NULL) { /* content_type field is, for now, used only for rcs file transfer but we shall strcmp it with "application/vnd.gsma.rcs-ft-http+xml" */ xmlChar *file_url = NULL; xmlDocPtr xmlMessageBody; xmlNodePtr cur; @@ -668,6 +677,7 @@ LinphoneChatMessage* linphone_chat_room_create_message(LinphoneChatRoom *cr, con msg->is_read=TRUE; msg->content_type = NULL; /* this property is used only when transfering file */ msg->file_transfer_information = NULL; /* this property is used only when transfering file */ + msg->http_request = NULL; return msg; } @@ -953,9 +963,12 @@ const LinphoneContent *linphone_chat_message_get_file_transfer_information(const } static void on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *msg, void *data, size_t offset, const uint8_t *buffer, size_t size){ - //printf("Receive %ld bytes\n\n%s\n\n", size, (char *)buffer); LinphoneChatMessage* chatMsg=(LinphoneChatMessage *)data; LinphoneCore *lc = chatMsg->chat_room->lc; + /* TODO: while belle sip doesn't implement the cancel http request method, test if a request is still linked to the message before forwarding the data to callback */ + if (chatMsg->http_request == NULL) { + return; + } /* call back given by application level */ if (lc->vtable.file_transfer_received != NULL) { lc->vtable.file_transfer_received(lc, chatMsg, chatMsg->file_transfer_information, (char *)buffer, size); @@ -1036,7 +1049,7 @@ static void linphone_chat_process_response_from_get_file(void *data, const belle * * @param message #LinphoneChatMessage */ -void linphone_chat_message_start_file_download(const LinphoneChatMessage *message) { +void linphone_chat_message_start_file_download(LinphoneChatMessage *message) { belle_http_request_listener_callbacks_t cbs={0}; belle_http_request_listener_t *l; belle_generic_uri_t *uri; @@ -1059,8 +1072,25 @@ void linphone_chat_message_start_file_download(const LinphoneChatMessage *messag cbs.process_auth_requested=process_auth_requested; l=belle_http_request_listener_create_from_callbacks(&cbs, (void *)message); belle_sip_object_data_set(BELLE_SIP_OBJECT(req),"message",(void *)message,NULL); + message->http_request = req; /* keep a reference on the request to be able to cancel the download */ belle_http_provider_send_request(message->chat_room->lc->http_provider,req,l); } + +/** + * Cancel an ongoing file transfer attached to this message.(upload or download) + * @param msg #LinphoneChatMessage + */ +void linphone_chat_room_cancel_file_transfer(LinphoneChatMessage *msg) { + ms_message("Cancelled file transfer %s - msg [%p] chat room[%p]", (msg->external_body_url==NULL)?msg->chat_room->lc->file_transfer_server:msg->external_body_url, msg, msg->chat_room); + /* TODO: here we shall call the cancel http request from bellesip API when it is available passing msg->http_request */ + /* waiting for this API, just set to NULL the reference to the request in the message and any request */ + msg->http_request = NULL; + if (msg->cb) { + msg->cb(msg, LinphoneChatMessageStateNotDelivered, msg->chat_room->lc); + } +} + + /** * Set origin of the message *@param message #LinphoneChatMessage obj @@ -1288,11 +1318,11 @@ LinphoneChatMessage* linphone_chat_room_create_file_transfer_message(LinphoneCha linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr)); linphone_chat_message_set_from(msg, linphone_address_new(linphone_core_get_identity(cr->lc))); msg->content_type=NULL; /* this will be set to application/vnd.gsma.rcs-ft-http+xml when we will transfer the xml reply from server to the peers */ + msg->http_request=NULL; /* this will store the http request during file upload to the server */ return msg; } + /** * @} */ - - diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 518ad7c4d..280ca7358 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1331,7 +1331,8 @@ LINPHONE_PUBLIC const LinphoneAddress* linphone_chat_message_get_to(const Linpho LINPHONE_PUBLIC const char* linphone_chat_message_get_external_body_url(const LinphoneChatMessage* message); LINPHONE_PUBLIC void linphone_chat_message_set_external_body_url(LinphoneChatMessage* message,const char* url); LINPHONE_PUBLIC const LinphoneContent* linphone_chat_message_get_file_transfer_information(const LinphoneChatMessage* message); -LINPHONE_PUBLIC void linphone_chat_message_start_file_download(const LinphoneChatMessage*message); +LINPHONE_PUBLIC void linphone_chat_message_start_file_download(LinphoneChatMessage* message); +LINPHONE_PUBLIC void linphone_chat_room_cancel_file_transfer(LinphoneChatMessage* msg); LINPHONE_PUBLIC const char* linphone_chat_message_get_appdata(const LinphoneChatMessage* message); LINPHONE_PUBLIC void linphone_chat_message_set_appdata(LinphoneChatMessage* message, const char* data); LINPHONE_PUBLIC const char* linphone_chat_message_get_text(const LinphoneChatMessage* message); diff --git a/coreapi/private.h b/coreapi/private.h index f1f6fdf38..2df416fba 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -163,8 +163,9 @@ struct _LinphoneChatMessage { bool_t is_read; unsigned int storage_id; SalOp *op; - LinphoneContent *file_transfer_information; - char *content_type; + LinphoneContent *file_transfer_information; /**< used to store file transfer information when the message is of file transfer type */ + char *content_type; /**< is used to specified the type of message to be sent, used only for file transfer message */ + belle_http_request_t *http_request; /**< keep a reference to the http_request in case of file transfer in order to be able to cancel the transfer */ }; BELLE_SIP_DECLARE_VPTR(LinphoneChatMessage); diff --git a/tester/message_tester.c b/tester/message_tester.c index daafac426..102f4832e 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -46,11 +46,11 @@ void message_received(LinphoneCore *lc, LinphoneChatRoom *room, LinphoneChatMess ms_free(from); counters = get_stats(lc); counters->number_of_LinphoneMessageReceived++; - if (linphone_chat_message_get_file_transfer_information(message)) - counters->number_of_LinphoneMessageReceivedWithFile++; if (counters->last_received_chat_message) linphone_chat_message_unref(counters->last_received_chat_message); linphone_chat_message_ref(counters->last_received_chat_message=message); - if (linphone_chat_message_get_external_body_url(message)) { + if (linphone_chat_message_get_file_transfer_information(message)) { + counters->number_of_LinphoneMessageReceivedWithFile++; + } else if (linphone_chat_message_get_external_body_url(message)) { counters->number_of_LinphoneMessageExtBodyReceived++; if (message_external_body_url) { CU_ASSERT_STRING_EQUAL(linphone_chat_message_get_external_body_url(message),message_external_body_url); @@ -75,7 +75,7 @@ void file_transfer_received(LinphoneCore *lc, LinphoneChatMessage *message, cons /*next chunk*/ file = (FILE*)linphone_chat_message_get_user_data(message); - if (size==0) { /* tranfer complerte */ + if (size==0) { /* tranfer complete */ stats* counters = get_stats(lc); linphone_chat_room_destroy(linphone_chat_message_get_chat_room(message)); linphone_chat_message_destroy(message); @@ -377,10 +377,10 @@ static void file_transfer_message(void) { linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,pauline->lc); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceivedWithFile,1)); - if (marie->stat.last_received_info_message ) { - linphone_chat_message_start_file_download((const LinphoneChatMessage*)marie->stat.last_received_info_message); + if (marie->stat.last_received_chat_message ) { + linphone_chat_message_start_file_download(marie->stat.last_received_chat_message); } - CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDelivered,1)); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageExtBodyReceived,1)); CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1); CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,1); @@ -439,6 +439,111 @@ static void file_transfer_message_io_error(void) { linphone_core_manager_destroy(pauline); } +static void file_transfer_message_upload_cancelled(void) { + int i; + char* to; + LinphoneChatRoom* chat_room; + LinphoneChatMessage* message; + LinphoneContent content; + const char* big_file_content="big file"; /* setting dummy file content to something */ + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + reset_counters(&marie->stat); + reset_counters(&pauline->stat); + + /* setting dummy file content to something */ + for (i=0;ilc,"https://www.linphone.org:444/lft.php"); + + /* create a chatroom on pauline's side */ + to = linphone_address_as_string(marie->identity); + chat_room = linphone_core_create_chat_room(pauline->lc,to); + + /* create a file transfer message */ + memset(&content,0,sizeof(content)); + content.type="text"; + content.subtype="plain"; + content.size=sizeof(big_file); /*total size to be transfered*/ + content.name = "bigfile.txt"; + message = linphone_chat_room_create_file_transfer_message(chat_room, &content); + + linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,pauline->lc); + + /*wait for file to be 50% uploaded and cancel the transfer */ + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.progress_of_LinphoneFileTransfer, 50)); + linphone_chat_room_cancel_file_transfer(message); + + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageNotDelivered,1)); + + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageNotDelivered,1); + CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,0); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void file_transfer_message_download_cancelled(void) { + int i; + char* to; + LinphoneChatRoom* chat_room; + LinphoneChatMessage* message; + LinphoneContent content; + const char* big_file_content="big file"; /* setting dummy file content to something */ + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + reset_counters(&marie->stat); + reset_counters(&pauline->stat); + + /* setting dummy file content to something */ + for (i=0;ilc,"https://www.linphone.org:444/lft.php"); + + /* create a chatroom on pauline's side */ + to = linphone_address_as_string(marie->identity); + chat_room = linphone_core_create_chat_room(pauline->lc,to); + + /* create a file transfer message */ + memset(&content,0,sizeof(content)); + content.type="text"; + content.subtype="plain"; + content.size=sizeof(big_file); /*total size to be transfered*/ + content.name = "bigfile.txt"; + message = linphone_chat_room_create_file_transfer_message(chat_room, &content); + + linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,pauline->lc); + + /* wait for marie to receive pauline's message */ + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceivedWithFile,1)); + + + if (marie->stat.last_received_chat_message ) { /* get last message and use it to download file */ + linphone_chat_message_start_file_download(marie->stat.last_received_chat_message); + /* wait for file to be 50% downloaded */ + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.progress_of_LinphoneFileTransfer, 50)); + /* and cancel the transfer */ + linphone_chat_room_cancel_file_transfer(marie->stat.last_received_chat_message); + } + //CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDelivered,1)); + + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1); + CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,0); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + static void text_message_with_send_error(void) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); @@ -672,6 +777,8 @@ test_t message_tests[] = { { "Text message with external body", text_message_with_external_body }, { "File transfer message", file_transfer_message }, { "File transfer message with io error", file_transfer_message_io_error }, + { "File transfer message upload cancelled", file_transfer_message_upload_cancelled }, + { "File transfer message download cancelled", file_transfer_message_download_cancelled }, { "Text message denied", text_message_denied }, { "Info message", info_message }, { "Info message with body", info_message_with_body }, From 97050ac654d47bf8da87388904defd8ca19d8259 Mon Sep 17 00:00:00 2001 From: Johan Pascal Date: Wed, 30 Jul 2014 17:14:56 +0200 Subject: [PATCH 100/218] File Transfer: manage i/o error during file download from server - note : not functional, test is bugged and commented. To be fixed. --- coreapi/chat.c | 50 +++++++++++++++++------ coreapi/help/filetransfer.c | 20 +++++----- coreapi/linphonecore.h | 5 ++- tester/message_tester.c | 79 ++++++++++++++++++++++++++++++++++--- 4 files changed, 123 insertions(+), 31 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index 28c1741ba..b115343e0 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -46,15 +46,35 @@ const char *multipart_boundary=MULTIPART_BOUNDARY; static size_t linphone_chat_message_compute_filepart_header_size(const char *filename, const char *content_type) { return strlen(FILEPART_HEADER_1)+strlen(filename)+strlen(FILEPART_HEADER_2)+strlen(content_type)+strlen(FILEPART_HEADER_3); } -static void process_io_error(void *data, const belle_sip_io_error_event_t *event){ + +static void process_io_error_upload(void *data, const belle_sip_io_error_event_t *event){ LinphoneChatMessage* msg=(LinphoneChatMessage *)data; - ms_error("I/O Error during file upload or download to/from %s - msg [%p] chat room[%p]", msg->chat_room->lc->file_transfer_server, msg, msg->chat_room); - msg->cb(msg, LinphoneChatMessageStateNotDelivered, msg->chat_room->lc); + ms_error("I/O Error during file upload to %s - msg [%p] chat room[%p]", msg->chat_room->lc->file_transfer_server, msg, msg->chat_room); + if (msg->cb) { + msg->cb(msg, LinphoneChatMessageStateNotDelivered, msg->chat_room->lc); + } } -static void process_auth_requested(void *data, belle_sip_auth_event_t *event){ +static void process_auth_requested_upload(void *data, belle_sip_auth_event_t *event){ LinphoneChatMessage* msg=(LinphoneChatMessage *)data; - ms_error("Error during file upload or download : auth requested to connect %s - msg [%p] chat room[%p]", msg->chat_room->lc->file_transfer_server, msg, msg->chat_room); - msg->cb(msg, LinphoneChatMessageStateNotDelivered, msg->chat_room->lc); + ms_error("Error during file upload : auth requested to connect %s - msg [%p] chat room[%p]", msg->chat_room->lc->file_transfer_server, msg, msg->chat_room); + if (msg->cb) { + msg->cb(msg, LinphoneChatMessageStateNotDelivered, msg->chat_room->lc); + } +} + +static void process_io_error_download(void *data, const belle_sip_io_error_event_t *event){ + LinphoneChatMessage* msg=(LinphoneChatMessage *)data; + ms_error("I/O Error during file download %s - msg [%p] chat room[%p]", msg->external_body_url, msg, msg->chat_room); + if (msg->cb) { + msg->cb(msg, LinphoneChatMessageStateFileTransferError, msg->chat_room->lc); + } +} +static void process_auth_requested_download(void *data, belle_sip_auth_event_t *event){ + LinphoneChatMessage* msg=(LinphoneChatMessage *)data; + ms_error("Error during file download : auth requested to get %s - msg [%p] chat room[%p]", msg->external_body_url, msg, msg->chat_room); + if (msg->cb) { + msg->cb(msg, LinphoneChatMessageStateFileTransferError, msg->chat_room->lc); + } } /** @@ -157,8 +177,8 @@ static void linphone_chat_message_process_response_from_post_file(void *data, co belle_sip_free(content_type); belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req),BELLE_SIP_BODY_HANDLER(bh)); cbs.process_response=linphone_chat_message_process_response_from_post_file; - cbs.process_io_error=process_io_error; - cbs.process_auth_requested=process_auth_requested; + cbs.process_io_error=process_io_error_upload; + cbs.process_auth_requested=process_auth_requested_upload; l=belle_http_request_listener_create_from_callbacks(&cbs,msg); msg->http_request=req; /* update the reference to the http request to be able to cancel it during upload */ belle_http_provider_send_request(msg->chat_room->lc->http_provider,req,l); @@ -343,8 +363,8 @@ static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatM NULL, NULL); cbs.process_response=linphone_chat_message_process_response_from_post_file; - cbs.process_io_error=process_io_error; - cbs.process_auth_requested=process_auth_requested; + cbs.process_io_error=process_io_error_upload; + cbs.process_auth_requested=process_auth_requested_upload; l=belle_http_request_listener_create_from_callbacks(&cbs,msg); /* give msg to listener to be able to start the actual file upload when server answer a 204 No content */ msg->http_request = req; /* keep a reference on the request to be able to cancel it */ belle_http_provider_send_request(cr->lc->http_provider,req,l); @@ -867,6 +887,7 @@ const char* linphone_chat_message_state_to_string(const LinphoneChatMessageState case LinphoneChatMessageStateInProgress:return "LinphoneChatMessageStateInProgress"; case LinphoneChatMessageStateDelivered:return "LinphoneChatMessageStateDelivered"; case LinphoneChatMessageStateNotDelivered:return "LinphoneChatMessageStateNotDelivered"; + case LinphoneChatMessageStateFileTransferError:return "LinphoneChatMessageStateFileTransferError"; default: return "Unknown state"; } @@ -1048,8 +1069,9 @@ static void linphone_chat_process_response_from_get_file(void *data, const belle * Start the download of the file from remote server * * @param message #LinphoneChatMessage + * @param status_cb LinphoneChatMessageStateChangeCb status callback invoked when file is downloaded or could not be downloaded */ -void linphone_chat_message_start_file_download(LinphoneChatMessage *message) { +void linphone_chat_message_start_file_download(LinphoneChatMessage *message, LinphoneChatMessageStateChangedCb status_cb) { belle_http_request_listener_callbacks_t cbs={0}; belle_http_request_listener_t *l; belle_generic_uri_t *uri; @@ -1068,11 +1090,13 @@ void linphone_chat_message_start_file_download(LinphoneChatMessage *message) { cbs.process_response_headers=linphone_chat_process_response_headers_from_get_file; cbs.process_response=linphone_chat_process_response_from_get_file; - cbs.process_io_error=process_io_error; - cbs.process_auth_requested=process_auth_requested; + cbs.process_io_error=process_io_error_download; + cbs.process_auth_requested=process_auth_requested_download; l=belle_http_request_listener_create_from_callbacks(&cbs, (void *)message); belle_sip_object_data_set(BELLE_SIP_OBJECT(req),"message",(void *)message,NULL); message->http_request = req; /* keep a reference on the request to be able to cancel the download */ + message->cb = status_cb; + message->state = LinphoneChatMessageStateInProgress; /* start the download, status is In Progress */ belle_http_provider_send_request(message->chat_room->lc->http_provider,req,l); } diff --git a/coreapi/help/filetransfer.c b/coreapi/help/filetransfer.c index 819330f57..5e8c2054b 100644 --- a/coreapi/help/filetransfer.c +++ b/coreapi/help/filetransfer.c @@ -115,16 +115,6 @@ static void file_transfer_send(LinphoneCore *lc, LinphoneChatMessage *message, } -/* - * Call back called when a message is received - */ -static void message_received(LinphoneCore *lc, LinphoneChatRoom *cr, LinphoneChatMessage *msg) { - const LinphoneContent *file_transfer_info = linphone_chat_message_get_file_transfer_information(msg); - printf ("Do you really want to download %s (size %ld)?[Y/n]\nOk, let's go\n", file_transfer_info->name, (long int)file_transfer_info->size); - - linphone_chat_message_start_file_download(msg); - -} /* * Call back to get delivery status of a message * */ @@ -136,6 +126,16 @@ static void linphone_file_transfer_state_changed(LinphoneChatMessage* msg,Linpho free(to); } +/* + * Call back called when a message is received + */ +static void message_received(LinphoneCore *lc, LinphoneChatRoom *cr, LinphoneChatMessage *msg) { + const LinphoneContent *file_transfer_info = linphone_chat_message_get_file_transfer_information(msg); + printf ("Do you really want to download %s (size %ld)?[Y/n]\nOk, let's go\n", file_transfer_info->name, (long int)file_transfer_info->size); + + linphone_chat_message_start_file_download(msg, linphone_file_transfer_state_changed); + +} LinphoneCore *lc; int main(int argc, char *argv[]){ diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 280ca7358..0c9358094 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1258,7 +1258,8 @@ typedef enum _LinphoneChatMessageState { LinphoneChatMessageStateIdle, /**< Initial state */ LinphoneChatMessageStateInProgress, /**< Delivery in progress */ LinphoneChatMessageStateDelivered, /**< Message succesffully delivered an acknoleged by remote end point */ - LinphoneChatMessageStateNotDelivered /**< Message was not delivered */ + LinphoneChatMessageStateNotDelivered, /**< Message was not delivered */ + LinphoneChatMessageStateFileTransferError /**< Message was received(and acknowledged) but cannot get file from server */ } LinphoneChatMessageState; /** @@ -1331,7 +1332,7 @@ LINPHONE_PUBLIC const LinphoneAddress* linphone_chat_message_get_to(const Linpho LINPHONE_PUBLIC const char* linphone_chat_message_get_external_body_url(const LinphoneChatMessage* message); LINPHONE_PUBLIC void linphone_chat_message_set_external_body_url(LinphoneChatMessage* message,const char* url); LINPHONE_PUBLIC const LinphoneContent* linphone_chat_message_get_file_transfer_information(const LinphoneChatMessage* message); -LINPHONE_PUBLIC void linphone_chat_message_start_file_download(LinphoneChatMessage* message); +LINPHONE_PUBLIC void linphone_chat_message_start_file_download(LinphoneChatMessage* message, LinphoneChatMessageStateChangedCb status_cb); LINPHONE_PUBLIC void linphone_chat_room_cancel_file_transfer(LinphoneChatMessage* msg); LINPHONE_PUBLIC const char* linphone_chat_message_get_appdata(const LinphoneChatMessage* message); LINPHONE_PUBLIC void linphone_chat_message_set_appdata(LinphoneChatMessage* message, const char* data); diff --git a/tester/message_tester.c b/tester/message_tester.c index 102f4832e..d71f2db53 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -124,7 +124,7 @@ void file_transfer_progress_indication(LinphoneCore *lc, LinphoneChatMessage *me const LinphoneAddress* to_address = linphone_chat_message_get_to(message); char *address = linphone_chat_message_is_outgoing(message)?linphone_address_as_string(to_address):linphone_address_as_string(from_address); stats* counters = get_stats(lc); - printf(" File transfer [%d%%] %s of type [%s/%s] %s [%s] \n", (int)progress + ms_message(" File transfer [%d%%] %s of type [%s/%s] %s [%s] \n", (int)progress ,(linphone_chat_message_is_outgoing(message)?"sent":"received") , content->type , content->subtype @@ -157,6 +157,9 @@ void liblinphone_tester_chat_message_state_change(LinphoneChatMessage* msg,Linph case LinphoneChatMessageStateInProgress: counters->number_of_LinphoneMessageInProgress++; break; + case LinphoneChatMessageStateFileTransferError: + counters->number_of_LinphoneMessageNotDelivered++; + break; default: ms_error("Unexpected state [%s] for message [%p]",linphone_chat_message_state_to_string(state),msg); } @@ -378,18 +381,19 @@ static void file_transfer_message(void) { linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,pauline->lc); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceivedWithFile,1)); if (marie->stat.last_received_chat_message ) { - linphone_chat_message_start_file_download(marie->stat.last_received_chat_message); + linphone_chat_message_start_file_download(marie->stat.last_received_chat_message, liblinphone_tester_chat_message_state_change); } CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageExtBodyReceived,1)); CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1); + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageDelivered,1); CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,1); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); } -static void file_transfer_message_io_error(void) { +static void file_transfer_message_io_error_upload(void) { int i; char* to; LinphoneChatRoom* chat_room; @@ -439,6 +443,67 @@ static void file_transfer_message_io_error(void) { linphone_core_manager_destroy(pauline); } + +#ifdef TEST_IS_BUGGED_NO_CALL_TO_IO_ERROR_CALLBACK +static void file_transfer_message_io_error_download(void) { + int i; + char* to; + LinphoneChatRoom* chat_room; + LinphoneChatMessage* message; + LinphoneContent content; + const char* big_file_content="big file"; /* setting dummy file content to something */ + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + reset_counters(&marie->stat); + reset_counters(&pauline->stat); + + /* setting dummy file content to something */ + for (i=0;ilc,"https://www.linphone.org:444/lft.php"); + + /* create a chatroom on pauline's side */ + to = linphone_address_as_string(marie->identity); + chat_room = linphone_core_create_chat_room(pauline->lc,to); + + /* create a file transfer message */ + memset(&content,0,sizeof(content)); + content.type="text"; + content.subtype="plain"; + content.size=sizeof(big_file); /*total size to be transfered*/ + content.name = "bigfile.txt"; + message = linphone_chat_room_create_file_transfer_message(chat_room, &content); + + linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,pauline->lc); + + /* wait for marie to receive pauline's message */ + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceivedWithFile,1)); + + + if (marie->stat.last_received_chat_message ) { /* get last message and use it to download file */ + linphone_chat_message_start_file_download(marie->stat.last_received_chat_message, liblinphone_tester_chat_message_state_change); + /* wait for file to be 50% downloaded */ + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.progress_of_LinphoneFileTransfer, 50)); + /* and simulate network error */ + sal_set_recv_error(marie->lc->sal, -1); + } + + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1); + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageDelivered,1); + CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageNotDelivered,1); + CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,0); + + sal_set_recv_error(marie->lc->sal, 0); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} +#endif + static void file_transfer_message_upload_cancelled(void) { int i; char* to; @@ -529,16 +594,17 @@ static void file_transfer_message_download_cancelled(void) { if (marie->stat.last_received_chat_message ) { /* get last message and use it to download file */ - linphone_chat_message_start_file_download(marie->stat.last_received_chat_message); + linphone_chat_message_start_file_download(marie->stat.last_received_chat_message, liblinphone_tester_chat_message_state_change); /* wait for file to be 50% downloaded */ CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.progress_of_LinphoneFileTransfer, 50)); /* and cancel the transfer */ linphone_chat_room_cancel_file_transfer(marie->stat.last_received_chat_message); } - //CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDelivered,1)); CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1); + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageDelivered,1); CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,0); + CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageNotDelivered,1); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); @@ -776,7 +842,8 @@ test_t message_tests[] = { { "Text message with send error", text_message_with_send_error }, { "Text message with external body", text_message_with_external_body }, { "File transfer message", file_transfer_message }, - { "File transfer message with io error", file_transfer_message_io_error }, + { "File transfer message with io error at upload", file_transfer_message_io_error_upload }, +/* { "File transfer message with io error at download", file_transfer_message_io_error_download },*/ { "File transfer message upload cancelled", file_transfer_message_upload_cancelled }, { "File transfer message download cancelled", file_transfer_message_download_cancelled }, { "Text message denied", text_message_denied }, From bef17939019267e054db0e3924df10d15f6eb930 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Wed, 30 Jul 2014 23:13:38 +0200 Subject: [PATCH 101/218] update MS2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index b84ad6280..12a820ca4 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit b84ad6280a29fb55ff0de3b26c710c2c31f83055 +Subproject commit 12a820ca46211cdae6b8ab502d12c4064ba6c518 From b45a3767275f2a5ae7146ef318527396f98b6645 Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Thu, 31 Jul 2014 11:15:29 +0200 Subject: [PATCH 102/218] Fix crash in chatroom destroy --- coreapi/chat.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index b115343e0..6679d9034 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -300,7 +300,8 @@ LinphoneChatRoom* linphone_core_get_or_create_chat_room(LinphoneCore* lc, const static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) { if (cr->composing_idle_timer) { - sal_cancel_timer(cr->lc->sal, cr->composing_idle_timer); + if(cr->lc->sal) + sal_cancel_timer(cr->lc->sal, cr->composing_idle_timer); belle_sip_object_unref(cr->composing_idle_timer); cr->composing_idle_timer = NULL; } @@ -308,7 +309,8 @@ static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr) { if (cr->composing_refresh_timer) { - sal_cancel_timer(cr->lc->sal, cr->composing_refresh_timer); + if(cr->lc->sal) + sal_cancel_timer(cr->lc->sal, cr->composing_refresh_timer); belle_sip_object_unref(cr->composing_refresh_timer); cr->composing_refresh_timer = NULL; } @@ -316,7 +318,8 @@ static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom * static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr) { if (cr->remote_composing_refresh_timer) { - sal_cancel_timer(cr->lc->sal, cr->remote_composing_refresh_timer); + if(cr->lc->sal) + sal_cancel_timer(cr->lc->sal, cr->remote_composing_refresh_timer); belle_sip_object_unref(cr->remote_composing_refresh_timer); cr->remote_composing_refresh_timer = NULL; } From 3519575570adaa67d737ea8a1a89ca79237b3137 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 31 Jul 2014 14:44:23 +0200 Subject: [PATCH 103/218] Manual handling of global_state_changed callback in the Python wrapper. --- .../python/apixml2python/handwritten.mustache | 20 +++++++- tools/python/apixml2python/linphone.py | 3 ++ .../apixml2python/linphone_module.mustache | 1 + tools/python/test.py | 51 ++++++++----------- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index fd25b0a72..6f826a913 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -79,15 +79,30 @@ static PyObject * pylinphone_module_method_set_log_handler(PyObject *self, PyObj Py_RETURN_NONE; } +static void pylinphone_Core_callback_global_state_changed(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message) { + pylinphone_CoreObject *pylc = (pylinphone_CoreObject *)linphone_core_get_user_data(lc); + PyObject *func = PyDict_GetItemString(pylc->vtable_dict, "global_state_changed"); + if ((func != NULL) && PyFunction_Check(func)) { + if (PyEval_CallFunction(func, "Ois", pylc, gstate, message) == NULL) { + PyErr_Print(); + } + } +} + static PyObject * pylinphone_Core_class_method_new(PyObject *cls, PyObject *args) { LinphoneCore * cresult; pylinphone_CoreObject *self; PyObject * pyret; LinphoneCoreVTable _vtable = { 0 }; + PyObject * _vtable_dict; const char * _config_path; const char * _factory_config_path; - if (!PyArg_ParseTuple(args, "zz", &_config_path, &_factory_config_path)) { + if (!PyArg_ParseTuple(args, "Ozz", &_vtable_dict, &_config_path, &_factory_config_path)) { + return NULL; + } + if (!PyDict_Check(_vtable_dict)) { + PyErr_SetString(PyExc_TypeError, "The first argument must be a dictionary"); return NULL; } @@ -95,6 +110,9 @@ static PyObject * pylinphone_Core_class_method_new(PyObject *cls, PyObject *args if (self == NULL) { return NULL; } + Py_INCREF(_vtable_dict); + self->vtable_dict = _vtable_dict; + _vtable.global_state_changed = pylinphone_Core_callback_global_state_changed; pylinphone_trace(1, "[PYLINPHONE] >>> %s(\"%s\", \"%s\")", __FUNCTION__, _config_path, _factory_config_path); cresult = linphone_core_new(&_vtable, _config_path, _factory_config_path, self); diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 7e3816410..561c840ec 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -578,6 +578,9 @@ class LinphoneModule(object): c['class_has_user_data'] = False c['class_type_methods'] = [] c['class_type_hand_written_methods'] = [] + c['class_object_members'] = '' + if c['class_name'] == 'Core': + c['class_object_members'] = "\tPyObject *vtable_dict;" xml_type_methods = xml_class.findall("./classmethods/classmethod") for xml_type_method in xml_type_methods: if xml_type_method.get('deprecated') == 'true': diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index ce222015f..3879d1194 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -42,6 +42,7 @@ static PyTypeObject pylinphone_{{class_name}}Type; typedef struct { PyObject_HEAD {{class_cname}} *native_ptr; +{{{class_object_members}}} } pylinphone_{{class_name}}Object; {{/classes}} diff --git a/tools/python/test.py b/tools/python/test.py index d291d1f65..627f8cad9 100644 --- a/tools/python/test.py +++ b/tools/python/test.py @@ -1,7 +1,5 @@ import linphone import logging -import signal -import sys import threading import time @@ -36,44 +34,39 @@ logging.addLevelName(logging.DEBUG, "\033[1;37m%s\033[1;0m" % logging.getLevelNa logging.addLevelName(logging.INFO, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.INFO)) logging.addLevelName(logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) -logging.basicConfig(level=logging.DEBUG, format="%(asctime)s.%(msecs)-3d %(levelname)s: %(message)s", datefmt="%H:%M:%S") +logging.basicConfig(level=logging.INFO, format="%(asctime)s.%(msecs)03d %(levelname)s: %(message)s", datefmt="%H:%M:%S") # Define the linphone module log handler def log_handler(level, msg): method = getattr(logging, level) + if not msg.strip().startswith('[PYLINPHONE]'): + msg = '[CORE] ' + msg method(msg) - -def test_friend(): - f = linphone.Friend.new() - print(f.address) - a1 = linphone.Address.new("sip:cotcot@sip.linphone.org") - print(a1.username) - print(a1.domain) - a1.domain = "sip2.linphone.org" - print(a1.domain) - f.address = a1 - a2 = f.address - - -def signal_handler(signal, frame): - cont = False - raise KeyError("Ctrl+C") - # Define the iteration function def iterate(kwargs): core = kwargs['core'] core.iterate() +def interact(): + choice = raw_input('> ') + if choice == "quit": + return False + return True + +def global_state_changed(core, state, message): + logging.warning("[PYTHON] global_state_changed: " + str(state) + ", " + message) + if state == linphone.GlobalState.GlobalOn: + logging.warning("[PYTHON] core version: " + str(core.version)) + # Create a linphone core and iterate every 20 ms linphone.set_log_handler(log_handler) -core = linphone.Core.new(None, None) +callbacks = { + 'global_state_changed':global_state_changed +} +core = linphone.Core.new(callbacks, None, None) interval = IntervalTimer(0.02, iterate, kwargs={'core':core}) -signal.signal(signal.SIGINT, signal_handler) -try: - interval.start() - signal.pause() -except KeyError: - interval.stop() - del interval -del core +interval.start() +while interact(): + pass +interval.stop() From c15b02f2711def8a90249cc11d6cdbeabdadce4f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 31 Jul 2014 14:53:09 +0200 Subject: [PATCH 104/218] Add Python refcount increment in dealloc method to prevent reentrancy. --- tools/python/apixml2python/linphone.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 561c840ec..b8520d353 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -432,15 +432,16 @@ class DeallocMethodDefinition(MethodDefinition): return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s(%p [%p])\", __FUNCTION__, self, native_ptr);\n" def format_c_function_call(self): - native_ptr_dealloc_code = '' + # Increment the refcount on self to prevent reentrancy in the dealloc method. + native_ptr_dealloc_code = "\tPy_INCREF(self);\n" if self.class_['class_refcountable']: - native_ptr_dealloc_code = \ + native_ptr_dealloc_code += \ """ if (native_ptr != NULL) {{ {function_prefix}unref(native_ptr); }} """.format(function_prefix=self.class_['class_c_function_prefix']) elif self.class_['class_destroyable']: - native_ptr_dealloc_code = \ + native_ptr_dealloc_code += \ """ if (native_ptr != NULL) {{ {function_prefix}destroy(native_ptr); }} From 68c06536a10f7ee7495bd2fed533d47c2e178fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Thu, 31 Jul 2014 18:31:42 +0200 Subject: [PATCH 105/218] Update mediastreamer2 and oRTP submodules --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index b84ad6280..47df383ea 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit b84ad6280a29fb55ff0de3b26c710c2c31f83055 +Subproject commit 47df383ea1631ef1171e10ecf4d6d8333979c3fd diff --git a/oRTP b/oRTP index 99f33a0f5..c41c4d538 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 99f33a0f510310389c22bf88a39582450be38425 +Subproject commit c41c4d53835927ef34269290960f211765f98f11 From 682b00678602ac0a0f6c8ace07857bf6eecb7b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Fri, 1 Aug 2014 12:32:26 +0200 Subject: [PATCH 106/218] Fix bad commit reference --- oRTP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oRTP b/oRTP index c41c4d538..1fc29ba46 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit c41c4d53835927ef34269290960f211765f98f11 +Subproject commit 1fc29ba469c5bb1dbfc19c64e8dac382913d2fa7 From 3dc58012831d75d6622d2992aeeecdcdc269f639 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 1 Aug 2014 14:40:38 +0200 Subject: [PATCH 107/218] Follow naming conventions of linphone core callbacks. --- coreapi/chat.c | 8 ++++---- coreapi/help/filetransfer.c | 2 +- coreapi/linphonecore.h | 2 +- tester/tester.c | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index 6679d9034..165d71ac3 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -994,8 +994,8 @@ static void on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t return; } /* call back given by application level */ - if (lc->vtable.file_transfer_received != NULL) { - lc->vtable.file_transfer_received(lc, chatMsg, chatMsg->file_transfer_information, (char *)buffer, size); + if (lc->vtable.file_transfer_recv != NULL) { + lc->vtable.file_transfer_recv(lc, chatMsg, chatMsg->file_transfer_information, (char *)buffer, size); } return; } @@ -1061,8 +1061,8 @@ static void linphone_chat_process_response_from_get_file(void *data, const belle LinphoneChatMessage* chatMsg=(LinphoneChatMessage *)data; LinphoneCore *lc = chatMsg->chat_room->lc; /* file downloaded succesfully, call again the callback with size at zero */ - if (lc->vtable.file_transfer_received != NULL) { - lc->vtable.file_transfer_received(lc, chatMsg, chatMsg->file_transfer_information, NULL, 0); + if (lc->vtable.file_transfer_recv != NULL) { + lc->vtable.file_transfer_recv(lc, chatMsg, chatMsg->file_transfer_information, NULL, 0); } } } diff --git a/coreapi/help/filetransfer.c b/coreapi/help/filetransfer.c index 5e8c2054b..25edc2d39 100644 --- a/coreapi/help/filetransfer.c +++ b/coreapi/help/filetransfer.c @@ -162,7 +162,7 @@ int main(int argc, char *argv[]){ in order to get notifications about incoming file receive, file_transfer_send to feed file to be transfered and file_transfer_progress_indication to print progress. */ - vtable.file_transfer_received=file_transfer_received; + vtable.file_transfer_recv=file_transfer_received; vtable.file_transfer_send=file_transfer_send; vtable.file_transfer_progress_indication=file_transfer_progress_indication; vtable.message_received=message_received; diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 0c9358094..5e842ac59 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1604,7 +1604,7 @@ typedef struct _LinphoneCoreVTable{ DisplayUrlCb display_url; /**< @deprecated */ ShowInterfaceCb show; /**< @deprecated Notifies the application that it should show up*/ LinphoneCoreTextMessageReceivedCb text_received; /** @deprecated, use #message_received instead
A text message has been received */ - LinphoneCoreFileTransferRecvCb file_transfer_received; /** Callback to store file received attached to a #LinphoneChatMessage */ + LinphoneCoreFileTransferRecvCb file_transfer_recv; /** Callback to store file received attached to a #LinphoneChatMessage */ LinphoneCoreFileTransferSendCb file_transfer_send; /** Callback to collect file chunk to be sent for a #LinphoneChatMessage */ LinphoneCoreFileTransferProgressIndicationCb file_transfer_progress_indication; /**Callback to indicate file transfer progress*/ } LinphoneCoreVTable; diff --git a/tester/tester.c b/tester/tester.c index 475d2c90f..d0faca038 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -199,7 +199,7 @@ LinphoneCoreManager* linphone_core_manager_new2(const char* rc_file, int check_f mgr->v_table.call_state_changed=call_state_changed; mgr->v_table.text_received=text_message_received; mgr->v_table.message_received=message_received; - mgr->v_table.file_transfer_received=file_transfer_received; + mgr->v_table.file_transfer_recv=file_transfer_received; mgr->v_table.file_transfer_send=file_transfer_send; mgr->v_table.file_transfer_progress_indication=file_transfer_progress_indication; mgr->v_table.is_composing_received=is_composing_received; From 2fe6c114b61462a13da4ebc057cf74627876d0dc Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 1 Aug 2014 15:57:25 +0200 Subject: [PATCH 108/218] Generation of linphone core events callbacks in the Python wrapper. --- tools/python/apixml2python.py | 10 +- .../python/apixml2python/handwritten.mustache | 14 +- tools/python/apixml2python/linphone.py | 145 +++++++++++++++++- .../apixml2python/linphone_module.mustache | 4 + 4 files changed, 155 insertions(+), 18 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index fda7a9262..6368908fd 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -30,6 +30,13 @@ blacklisted_classes = [ 'LinphoneTunnel', 'LinphoneTunnelConfig' ] +blacklisted_events = [ + 'LinphoneCoreInfoReceivedCb', + 'LinphoneCoreNotifyReceivedCb', + 'LinphoneCoreFileTransferProgressIndicationCb', + 'LinphoneCoreFileTransferRecvCb', + 'LinphoneCoreFileTransferSendCb' +] blacklisted_functions = [ 'linphone_call_get_user_pointer', 'linphone_call_set_user_pointer', @@ -50,6 +57,7 @@ blacklisted_functions = [ 'linphone_chat_message_get_chat_room', 'linphone_chat_message_get_file_transfer_information', 'linphone_chat_message_get_time', + 'linphone_chat_message_start_file_download', 'linphone_chat_message_state_to_string', 'linphone_chat_room_create_file_transfer_message', 'linphone_chat_room_create_message_2', @@ -113,7 +121,7 @@ hand_written_functions = [ def generate(apixmlfile): tree = ET.parse(apixmlfile) renderer = pystache.Renderer() - m = LinphoneModule(tree, blacklisted_classes, blacklisted_functions, hand_written_functions) + m = LinphoneModule(tree, blacklisted_classes, blacklisted_events, blacklisted_functions, hand_written_functions) f = open("linphone.c", "w") os.chdir('apixml2python') f.write(renderer.render(m)) diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index 6f826a913..4717e6081 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -79,16 +79,6 @@ static PyObject * pylinphone_module_method_set_log_handler(PyObject *self, PyObj Py_RETURN_NONE; } -static void pylinphone_Core_callback_global_state_changed(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message) { - pylinphone_CoreObject *pylc = (pylinphone_CoreObject *)linphone_core_get_user_data(lc); - PyObject *func = PyDict_GetItemString(pylc->vtable_dict, "global_state_changed"); - if ((func != NULL) && PyFunction_Check(func)) { - if (PyEval_CallFunction(func, "Ois", pylc, gstate, message) == NULL) { - PyErr_Print(); - } - } -} - static PyObject * pylinphone_Core_class_method_new(PyObject *cls, PyObject *args) { LinphoneCore * cresult; pylinphone_CoreObject *self; @@ -112,7 +102,9 @@ static PyObject * pylinphone_Core_class_method_new(PyObject *cls, PyObject *args } Py_INCREF(_vtable_dict); self->vtable_dict = _vtable_dict; - _vtable.global_state_changed = pylinphone_Core_callback_global_state_changed; +{{#events}} + {{{event_vtable_reference}}} +{{/events}} pylinphone_trace(1, "[PYLINPHONE] >>> %s(\"%s\", \"%s\")", __FUNCTION__, _config_path, _factory_config_path); cresult = linphone_core_new(&_vtable, _config_path, _factory_config_path, self); diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index b8520d353..e0a0a050c 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -24,6 +24,18 @@ def strip_leading_linphone(s): else: return s +def compute_event_name(s): + s = strip_leading_linphone(s) + s = s[4:-2] # Remove leading 'Core' and tailing 'Cb' + event_name = '' + first = True + for l in s: + if l.isupper() and not first: + event_name += '_' + event_name += l.lower() + first = False + return event_name + class MethodDefinition: def __init__(self, linphone_module, class_, method_node = None): @@ -226,7 +238,7 @@ class MethodDefinition: splitted_type.remove('const') return ' '.join(splitted_type) - def ctype_to_str_format(self, name, basic_type, complete_type): + def ctype_to_str_format(self, name, basic_type, complete_type, with_native_ptr=True): splitted_type = complete_type.split(' ') if basic_type == 'char': if '*' in splitted_type: @@ -263,8 +275,10 @@ class MethodDefinition: else: if strip_leading_linphone(basic_type) in self.linphone_module.enum_names: return ('%d', [name]) - else: + elif with_native_ptr: return ('%p [%p]', [name, name + "_native_ptr"]) + else: + return ('%p', [name]) def ctype_to_python_format(self, basic_type, complete_type): splitted_type = complete_type.split(' ') @@ -535,10 +549,109 @@ class SetterMethodDefinition(MethodDefinition): self.first_arg_class = strip_leading_linphone(self.first_arg_type) self.python_fmt = self.ctype_to_python_format(self.first_arg_type, self.first_arg_complete_type) +class EventCallbackMethodDefinition(MethodDefinition): + def __init__(self, linphone_module, class_, method_node = None): + MethodDefinition.__init__(self, linphone_module, class_, method_node) + + def format_local_variables_definition(self): + common = \ +""" pylinphone_CoreObject *pylc = (pylinphone_CoreObject *)linphone_core_get_user_data(lc); + PyObject *func = PyDict_GetItemString(pylc->vtable_dict, "{name}");""".format(name=self.class_['event_name']) + specific = '' + for xml_method_arg in self.xml_method_args: + arg_name = 'py' + xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + fmt = self.ctype_to_python_format(arg_type, arg_complete_type) + if fmt == 'O': + specific += "\tPyObject * " + arg_name + " = NULL;\n" + return "{common}\n{specific}".format(common=common, specific=specific) + + def format_arguments_parsing(self): + body = '' + for xml_method_arg in self.xml_method_args: + arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + fmt = self.ctype_to_python_format(arg_type, arg_complete_type) + if fmt == 'O': + type_class = self.find_class_definition(arg_type) + get_user_data_code = '' + new_from_native_pointer_code = "py{name} = pylinphone_{arg_type}_new_from_native_ptr(&pylinphone_{arg_type}Type, {name});".format(name=arg_name, arg_type=strip_leading_linphone(arg_type)) + if type_class is not None and type_class['class_has_user_data']: + get_user_data_function = type_class['class_c_function_prefix'] + "get_user_data" + get_user_data_code = "py{name} = {get_user_data_function}({name});".format(name=arg_name, get_user_data_function=get_user_data_function) + body += \ +""" {get_user_data_code} + if (py{name} == NULL) {{ + {new_from_native_pointer_code} + }} +""".format(name=arg_name, get_user_data_code=get_user_data_code, new_from_native_pointer_code=new_from_native_pointer_code) + return body + + def format_enter_trace(self): + fmt = '%p' + args = ['lc'] + for xml_method_arg in self.xml_method_args: + arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + if fmt != '': + fmt += ', ' + f, a = self.ctype_to_str_format(arg_name, arg_type, arg_complete_type, with_native_ptr=False) + fmt += f + args += a + args=', '.join(args) + if args != '': + args = ', ' + args + return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s({fmt})\", __FUNCTION__{args});\n".format(fmt=fmt, args=args) + + def format_c_function_call(self): + fmt = 'O' + args = ['pylc'] + for xml_method_arg in self.xml_method_args: + arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + f = self.ctype_to_python_format(arg_type, arg_complete_type) + fmt += f + if f == 'O': + args.append('py' + arg_name) + else: + args.append(arg_name) + args=', '.join(args) + return \ +""" if ((func != NULL) && PyFunction_Check(func)) {{ + if (PyEval_CallFunction(func, "{fmt}", {args}) == NULL) {{ + PyErr_Print(); + }} + }} +""".format(fmt=fmt, args=args) + + def format_return_trace(self): + return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s\", __FUNCTION__);" + + def format_return_result(self): + return '' + + def format(self): + body = MethodDefinition.format(self) + arguments = ['LinphoneCore * lc'] + for xml_method_arg in self.xml_method_args: + arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + arguments.append(arg_complete_type + ' ' + arg_name) + definition = \ +"""static void pylinphone_Core_callback_{event_name}({arguments}) {{ +{body} +}} +""".format(event_name=self.class_['event_name'], arguments=', '.join(arguments), body=body) + return definition class LinphoneModule(object): - def __init__(self, tree, blacklisted_classes, blacklisted_functions, hand_written_functions): + def __init__(self, tree, blacklisted_classes, blacklisted_events, blacklisted_functions, hand_written_functions): self.internal_instance_method_names = ['destroy', 'ref', 'unref'] self.internal_property_names = ['user_data'] self.enums = [] @@ -561,6 +674,7 @@ class LinphoneModule(object): e['enum_values'].append(v) self.enums.append(e) self.enum_names.append(e['enum_name']) + self.events = [] self.classes = [] xml_classes = tree.findall("./classes/class") for xml_class in xml_classes: @@ -582,6 +696,18 @@ class LinphoneModule(object): c['class_object_members'] = '' if c['class_name'] == 'Core': c['class_object_members'] = "\tPyObject *vtable_dict;" + xml_events = xml_class.findall("./events/event") + for xml_event in xml_events: + if xml_event.get('deprecated') == 'true': + continue + if xml_event.get('name') in blacklisted_events: + continue + ev = {} + ev['event_xml_node'] = xml_event + ev['event_cname'] = xml_event.get('name') + ev['event_name'] = compute_event_name(ev['event_cname']) + ev['event_doc'] = self.__format_doc(xml_event.find('briefdescription'), xml_event.find('detaileddescription')) + self.events.append(ev) xml_type_methods = xml_class.findall("./classmethods/classmethod") for xml_type_method in xml_type_methods: if xml_type_method.get('deprecated') == 'true': @@ -649,6 +775,10 @@ class LinphoneModule(object): p['setter_reference'] = "NULL" c['class_properties'].append(p) self.classes.append(c) + # Format events definitions + for ev in self.events: + ev['event_callback_definition'] = EventCallbackMethodDefinition(self, ev, ev['event_xml_node']).format() + ev['event_vtable_reference'] = "_vtable.{name} = pylinphone_Core_callback_{name};".format(name=ev['event_name']) # Format methods' bodies for c in self.classes: xml_new_method = c['class_xml_node'].find("./classmethods/classmethod[@name='" + c['class_c_function_prefix'] + "new']") @@ -695,15 +825,18 @@ class LinphoneModule(object): def __format_doc_node(self, node): desc = '' if node.tag == 'para': - desc += node.text.strip() + if node.text is not None: + desc += node.text.strip() for n in list(node): desc += self.__format_doc_node(n) elif node.tag == 'note': - desc += node.text.strip() + if node.text is not None: + desc += node.text.strip() for n in list(node): desc += self.__format_doc_node(n) elif node.tag == 'ref': - desc += ' ' + node.text.strip() + ' ' + if node.text is not None: + desc += ' ' + node.text.strip() + ' ' tail = node.tail.strip() if tail != '': if node.tag != 'ref': diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 3879d1194..9b54a2073 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -55,6 +55,10 @@ static PyObject * pylinphone_{{class_name}}_class_method_{{method_name}}(PyObjec {{/class_type_hand_written_methods}} {{/classes}} +{{#events}} +{{{event_callback_definition}}} +{{/events}} + {{#classes}} static {{class_cname}} * pylinphone_{{class_name}}_get_native_ptr(PyObject *self) { From ee446299b8e429243b605f4ec082a8b0bd5cf387 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 1 Aug 2014 16:29:07 +0200 Subject: [PATCH 109/218] Real Python program to test the Python wrapper. --- tools/python/test.py | 66 ++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/tools/python/test.py b/tools/python/test.py index 627f8cad9..53856e7d4 100644 --- a/tools/python/test.py +++ b/tools/python/test.py @@ -1,5 +1,7 @@ +import argparse import linphone import logging +import sys import threading import time @@ -29,12 +31,22 @@ class IntervalTimer(StoppableThread): time.sleep(self._interval) -# Configure logging module -logging.addLevelName(logging.DEBUG, "\033[1;37m%s\033[1;0m" % logging.getLevelName(logging.DEBUG)) -logging.addLevelName(logging.INFO, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.INFO)) -logging.addLevelName(logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) -logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) -logging.basicConfig(level=logging.INFO, format="%(asctime)s.%(msecs)03d %(levelname)s: %(message)s", datefmt="%H:%M:%S") +def setup_log_colors(): + logging.addLevelName(logging.DEBUG, "\033[1;37m%s\033[1;0m" % logging.getLevelName(logging.DEBUG)) + logging.addLevelName(logging.INFO, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.INFO)) + logging.addLevelName(logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) + logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) + +def setup_log(log, trace): + if log is None: + setup_log_colors() + format = "%(asctime)s.%(msecs)03d %(levelname)s: %(message)s" + datefmt = "%H:%M:%S" + if trace: + level = logging.DEBUG + else: + level = logging.INFO + logging.basicConfig(filename=log, level=level, format=format, datefmt=datefmt) # Define the linphone module log handler def log_handler(level, msg): @@ -59,14 +71,34 @@ def global_state_changed(core, state, message): if state == linphone.GlobalState.GlobalOn: logging.warning("[PYTHON] core version: " + str(core.version)) -# Create a linphone core and iterate every 20 ms -linphone.set_log_handler(log_handler) -callbacks = { - 'global_state_changed':global_state_changed -} -core = linphone.Core.new(callbacks, None, None) -interval = IntervalTimer(0.02, iterate, kwargs={'core':core}) -interval.start() -while interact(): - pass -interval.stop() +def registration_state_changed(core, proxy_cfg, state, message): + logging.warning("[PYTHON] registration_state_changed: " + str(state) + ", " + message) + +def run(args): + # Create a linphone core and iterate every 20 ms + setup_log(args.log, args.trace) + linphone.set_log_handler(log_handler) + callbacks = { + 'global_state_changed':global_state_changed, + 'registration_state_changed':registration_state_changed + } + core = linphone.Core.new(callbacks, args.config, args.factory_config) + interval = IntervalTimer(0.02, iterate, kwargs={'core':core}) + interval.start() + while interact(): + pass + interval.stop() + +def main(argv = None): + if argv is None: + argv = sys.argv + argparser = argparse.ArgumentParser(description="Linphone console interface in Python.") + argparser.add_argument('--config', default=None, help="Path to the linphonerc configuration file to use.") + argparser.add_argument('--factory_config', default=None, help="Path to the linphonerc factory configuration file to use.") + argparser.add_argument('--log', default=None, help="Path to the file used for logging (default is the standard output).") + argparser.add_argument('--trace', action='store_true', help="Output linphone Python module tracing logs (for debug purposes).") + args = argparser.parse_args() + run(args) + +if __name__ == "__main__": + sys.exit(main()) From 7d7c860c973cbe205a33530ac7854bfcd0534dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Fri, 1 Aug 2014 17:28:14 +0200 Subject: [PATCH 110/218] Update mediastreamer2 submodule --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 47df383ea..c68891876 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 47df383ea1631ef1171e10ecf4d6d8333979c3fd +Subproject commit c68891876f9b29b41302c0b53106dde64842b7b0 From 6d53248011f036efa0a477fc94d061b7d25427cd Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 1 Aug 2014 17:46:05 +0200 Subject: [PATCH 111/218] Fix some Python reference count + Handle thread concurrency in Python code called from C. --- .../python/apixml2python/handwritten.mustache | 25 ++++++++++++++++--- tools/python/apixml2python/linphone.py | 9 ++++--- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index 4717e6081..69f73533b 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -1,6 +1,10 @@ static void pylinphone_log(const char *level, int indent, const char *fmt, va_list args) { static int current_indent = 1; - PyObject *linphone_module = PyImport_ImportModule("linphone"); + PyObject *linphone_module; + PyGILState_STATE gstate; + + gstate = PyGILState_Ensure(); + linphone_module = PyImport_ImportModule("linphone"); if ((linphone_module != NULL) && PyObject_HasAttrString(linphone_module, "__log_handler")) { PyObject *log_handler = PyObject_GetAttrString(linphone_module, "__log_handler"); if ((log_handler != NULL) && PyFunction_Check(log_handler)) { @@ -17,8 +21,11 @@ static void pylinphone_log(const char *level, int indent, const char *fmt, va_li if (vsnprintf(logstr + i, sizeof(logstr) - i, fmt, args) > 0) { PyEval_CallFunction(log_handler, "ss", level, logstr); } + Py_DECREF(log_handler); } + Py_DECREF(linphone_module); } + PyGILState_Release(gstate); } static PYLINPHONE_INLINE void pylinphone_trace(int indent, const char *fmt, ...) { @@ -47,17 +54,27 @@ static const char * pylinphone_ortp_log_level_to_string(OrtpLogLevel lev) { } static void pylinphone_module_log_handler(OrtpLogLevel lev, const char *fmt, va_list args) { - PyObject *linphone_module = PyImport_ImportModule("linphone"); - const char *level = pylinphone_ortp_log_level_to_string(lev); + PyGILState_STATE gstate; + PyObject *linphone_module; + const char *level; + + gstate = PyGILState_Ensure(); + linphone_module = PyImport_ImportModule("linphone"); + level = pylinphone_ortp_log_level_to_string(lev); if ((linphone_module != NULL) && PyObject_HasAttrString(linphone_module, "__log_handler")) { PyObject *log_handler = PyObject_GetAttrString(linphone_module, "__log_handler"); if ((log_handler != NULL) && PyFunction_Check(log_handler)) { char logstr[4096]; if (vsnprintf(logstr, sizeof(logstr), fmt, args) > 0) { - PyEval_CallFunction(log_handler, "ss", level, logstr); + if (PyEval_CallFunction(log_handler, "ss", level, logstr) == NULL) { + PyErr_Print(); + } } + Py_DECREF(log_handler); } + Py_DECREF(linphone_module); } + PyGILState_Release(gstate); } static void pylinphone_init_logging(void) { diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index e0a0a050c..42e6c5786 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -556,7 +556,8 @@ class EventCallbackMethodDefinition(MethodDefinition): def format_local_variables_definition(self): common = \ """ pylinphone_CoreObject *pylc = (pylinphone_CoreObject *)linphone_core_get_user_data(lc); - PyObject *func = PyDict_GetItemString(pylc->vtable_dict, "{name}");""".format(name=self.class_['event_name']) + PyObject *func = PyDict_GetItemString(pylc->vtable_dict, "{name}"); + PyGILState_STATE pygil_state;""".format(name=self.class_['event_name']) specific = '' for xml_method_arg in self.xml_method_args: arg_name = 'py' + xml_method_arg.get('name') @@ -568,7 +569,7 @@ class EventCallbackMethodDefinition(MethodDefinition): return "{common}\n{specific}".format(common=common, specific=specific) def format_arguments_parsing(self): - body = '' + body = "\tpygil_state = PyGILState_Ensure();\n" for xml_method_arg in self.xml_method_args: arg_name = xml_method_arg.get('name') arg_type = xml_method_arg.get('type') @@ -629,10 +630,10 @@ class EventCallbackMethodDefinition(MethodDefinition): """.format(fmt=fmt, args=args) def format_return_trace(self): - return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s\", __FUNCTION__);" + return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s\", __FUNCTION__);\n" def format_return_result(self): - return '' + return '\tPyGILState_Release(pygil_state);' def format(self): body = MethodDefinition.format(self) From 09b56423dc50d974808cd15dc937aaf71258940b Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Sun, 3 Aug 2014 21:04:09 +0200 Subject: [PATCH 112/218] Fix compilation with Visual Studio. --- coreapi/chat.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index 165d71ac3..c9ec8b6d5 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -184,11 +184,12 @@ static void linphone_chat_message_process_response_from_post_file(void *data, co belle_http_provider_send_request(msg->chat_room->lc->http_provider,req,l); } if (code == 200 ) { /* file has been uploaded correctly, get server reply and send it */ + const char *body; /* TODO Check that the transfer has not been cancelled, note this shall be removed once the belle sip API will provide a cancel request as we shall never reach this part if the transfer is actually cancelled */ if (msg->http_request == NULL) { return; } - const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response); + body = belle_sip_message_get_body((belle_sip_message_t *)event->response); msg->message = ms_strdup(body); linphone_content_uninit(msg->file_transfer_information); ms_free(msg->file_transfer_information); From 48a2e6622c24e13dd2edd625d31a72a54a5e0692 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Sun, 3 Aug 2014 22:46:25 +0200 Subject: [PATCH 113/218] Improve compilation with CMake by using CMake modules to find the dependencies. --- CMakeLists.txt | 51 ++++++++++++++++++++++++++++++++++---- coreapi/CMakeLists.txt | 45 ++++++++++++++++++++++++--------- share/CMakeLists.txt | 29 ++++++++++++++++------ share/rings/CMakeLists.txt | 22 ++++++++++++++++ 4 files changed, 123 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae3952338..725126296 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,59 @@ +############################################################################ +# CMakeLists.txt +# Copyright (C) 2014 Belledonne Communications, Grenoble France +# +############################################################################ +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################ + cmake_minimum_required(VERSION 2.6) project(LINPHONE C) -option(LINPHONE_ENABLE_VIDEO "Build linphone with video support." ON) + +option(ENABLE_STATIC "Build static library (default is shared library)." NO) +option(ENABLE_VIDEO "Build with video support." YES) + + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_PREFIX_PATH}/share/cmake/Modules) + +include(CheckIncludeFile) + +if(MSVC) + list(APPEND CMAKE_REQUIRED_INCLUDES ${CMAKE_PREFIX_PATH}/include/MSVC) +endif() + +find_package(BelleSIP REQUIRED) +find_package(MS2 REQUIRED) +find_package(XML2 REQUIRED) + include_directories( include/ coreapi/ ${CMAKE_CURRENT_BINARY_DIR}/coreapi/ + ${BELLESIP_INCLUDE_DIRS} + ${MS2_INCLUDE_DIRS} + ${XML2_INCLUDE_DIRS} ) -include_directories( - ${CMAKE_INSTALL_PREFIX}/include - ${CMAKE_INSTALL_PREFIX}/include/libxml2 -) +if(MSVC) + include_directories(${CMAKE_PREFIX_PATH}/include/MSVC) +endif() + add_subdirectory(coreapi) add_subdirectory(share) diff --git a/coreapi/CMakeLists.txt b/coreapi/CMakeLists.txt index 1fb5f2add..e6f530ee9 100644 --- a/coreapi/CMakeLists.txt +++ b/coreapi/CMakeLists.txt @@ -1,12 +1,29 @@ +############################################################################ +# CMakeLists.txt +# Copyright (C) 2014 Belledonne Communications, Grenoble France +# +############################################################################ +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################ + if(MSVC) find_library(LIBGCC NAMES gcc) find_library(LIBMINGWEX NAMES mingwex) endif() -find_library(LIBORTP NAMES ortp) -find_library(LIBMEDIASTREAMER_BASE NAMES mediastreamer_base) -find_library(LIBMEDIASTREAMER_VOIP NAMES mediastreamer_voip) -find_library(LIBBELLESIP NAMES bellesip) -find_library(LIBXML2 NAMES xml2) find_program(GIT git) @@ -111,19 +128,23 @@ add_definitions( -DLINPHONE_PLUGINS_DIR="" ) -if(LINPHONE_ENABLE_VIDEO) +if(ENABLE_VIDEO) add_definitions(-DVIDEO_ENABLED) -endif(LINPHONE_ENABLE_VIDEO) +endif() +set(LIBS + ${LIBGCC} + ${LIBMINGWEX} + ${BELLESIP_LIBRARIES} + ${MS2_LIBRARIES} + ${XML2_LIBRARIES} +) if(WIN32) add_definitions( -DWINDOW_NATIVE /FIliblinphone_gitversion.h ) - -set(LIBS ws2_32) -endif(WIN32) -set(LIBS ${LIBS} ${LIBGCC} ${LIBMINGWEX} ${LIBORTP} ${LIBMEDIASTREAMER_BASE} ${LIBMEDIASTREAMER_VOIP} ${LIBBELLESIP} ${LIBXML2}) +endif() add_library(linphone SHARED ${SOURCE_FILES}) set_target_properties(linphone PROPERTIES VERSION 3.7.0 SOVERSION 5) @@ -150,4 +171,4 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/Debug/linphone.pdb PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) endif() -endif(WIN32) +endif() diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index 8d15a6f5c..03b36a74d 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -1,17 +1,32 @@ +############################################################################ +# CMakeLists.txt +# Copyright (C) 2014 Belledonne Communications, Grenoble France +# +############################################################################ +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################ + install(FILES archived-rootca.pem RENAME rootca.pem - COMPONENT COMP_liblinphone DESTINATION share/linphone PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) install(FILES ringback.wav - COMPONENT COMP_liblinphone DESTINATION share/sounds/linphone PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) add_subdirectory(rings) - -install(FILES ../mediastreamer2/src/voip/nowebcamCIF.jpg - COMPONENT COMP_liblinphone - DESTINATION share/images - PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) diff --git a/share/rings/CMakeLists.txt b/share/rings/CMakeLists.txt index cf359bb4e..63f2c47ac 100644 --- a/share/rings/CMakeLists.txt +++ b/share/rings/CMakeLists.txt @@ -1,3 +1,25 @@ +############################################################################ +# CMakeLists.txt +# Copyright (C) 2014 Belledonne Communications, Grenoble France +# +############################################################################ +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################ + install(FILES oldphone.wav toy-mono.wav COMPONENT COMP_liblinphone DESTINATION share/sounds/linphone/rings From 113dece9cc4474a3d13954d5a56842e5c6d5e4f4 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Mon, 4 Aug 2014 09:07:28 +0200 Subject: [PATCH 114/218] fix liblinphone android tester --- console/commands.c | 2 +- tester/liblinphone_tester.c | 8 +++----- tester/liblinphone_tester.h | 2 ++ tester/tester.c | 21 +++++++++++++++++++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/console/commands.c b/console/commands.c index 009d8724b..f0b44bdd5 100644 --- a/console/commands.c +++ b/console/commands.c @@ -515,7 +515,7 @@ lpc_cmd_help(LinphoneCore *lc, char *arg) i=0; while (advanced_commands[i].help) { - linphonec_out("%10.10s\t%s\n", advanced_commands[i].name, + linphonec_out("%20.20s\t%s\n", advanced_commands[i].name, advanced_commands[i].help); i++; } diff --git a/tester/liblinphone_tester.c b/tester/liblinphone_tester.c index b11cbefaf..5fb91605c 100644 --- a/tester/liblinphone_tester.c +++ b/tester/liblinphone_tester.c @@ -27,9 +27,6 @@ #ifdef ANDROID -extern void AndroidPrintf(FILE *stream, const char *fmt, ...); -#define fprintf(file, fmt, ...) AndroidPrintf(file, fmt, ##__VA_ARGS__) - #include #include #include @@ -118,8 +115,9 @@ static void liblinphone_tester_qnx_log_handler(OrtpLogLevel lev, const char *fmt #endif /* __QNX__ */ + void helper(const char *name) { - fprintf(stderr,"%s --help\n" + liblinphone_tester_fprintf(stderr,"%s --help\n" "\t\t\t--verbose\n" "\t\t\t--silent\n" "\t\t\t--list-suites\n" @@ -197,7 +195,7 @@ int main (int argc, char *argv[]) liblinphone_tester_list_suite_tests(suite_name); return 0; } else { - fprintf(stderr, "Unknown option \"%s\"\n", argv[i]); \ + liblinphone_tester_fprintf(stderr, "Unknown option \"%s\"\n", argv[i]); \ helper(argv[0]); return -1; } diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 8fbeafc31..7eb7ab456 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -248,5 +248,7 @@ void liblinphone_tester_check_rtcp(LinphoneCoreManager* caller, LinphoneCoreMana void liblinphone_tester_clock_start(MSTimeSpec *start); bool_t liblinphone_tester_clock_elapsed(const MSTimeSpec *start, int value_ms); +int liblinphone_tester_fprintf(FILE * restrict stream, const char * restrict format, ...); + #endif /* LIBLINPHONE_TESTER_H_ */ diff --git a/tester/tester.c b/tester/tester.c index d0faca038..a5a74a8af 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -294,7 +294,7 @@ int liblinphone_tester_test_suite_index(const char *suite_name) { void liblinphone_tester_list_suites() { int j; for(j=0;j Date: Mon, 4 Aug 2014 09:35:16 +0200 Subject: [PATCH 115/218] Update oRTP and ms2 submodules. --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index c68891876..44675f02a 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit c68891876f9b29b41302c0b53106dde64842b7b0 +Subproject commit 44675f02ad01445478e2be8ad1ab533f74737bf0 diff --git a/oRTP b/oRTP index 1fc29ba46..7aaba994d 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 1fc29ba469c5bb1dbfc19c64e8dac382913d2fa7 +Subproject commit 7aaba994d4ec860a0773679dee9baa018b8801ee From 528f2e1582fd669c4685623f4b93aa292b30411e Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Mon, 4 Aug 2014 09:42:09 +0200 Subject: [PATCH 116/218] fix android liblinphone_tester_fprintf --- tester/liblinphone_tester.h | 6 ++++-- tester/tester.c | 10 +++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 7eb7ab456..9b2eeeb60 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -247,8 +247,10 @@ void liblinphone_tester_chat_message_state_change(LinphoneChatMessage* msg,Linph void liblinphone_tester_check_rtcp(LinphoneCoreManager* caller, LinphoneCoreManager* callee); void liblinphone_tester_clock_start(MSTimeSpec *start); bool_t liblinphone_tester_clock_elapsed(const MSTimeSpec *start, int value_ms); - -int liblinphone_tester_fprintf(FILE * restrict stream, const char * restrict format, ...); +#ifdef ANDROID +void cunit_android_trace_handler(int level, const char *fmt, va_list args) ; +#endif +int liblinphone_tester_fprintf(FILE * stream, const char * format, ...); #endif /* LIBLINPHONE_TESTER_H_ */ diff --git a/tester/tester.c b/tester/tester.c index a5a74a8af..12dd60a4a 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -443,12 +443,7 @@ int liblinphone_tester_run_tests(const char *suite_name, const char *test_name) CU_cleanup_registry(); return ret; } -#ifdef ANDROID -/*implemented in cunit*/ -extern void AndroidPrintf(FILE *stream, const char *fmt, ...); -#endif - -int liblinphone_tester_fprintf(FILE * restrict stream, const char * restrict format, ...) { +int liblinphone_tester_fprintf(FILE * stream, const char * format, ...) { va_list args; va_start(args, format); int result; @@ -456,7 +451,8 @@ int liblinphone_tester_fprintf(FILE * restrict stream, const char * restrict fo result = vfprintf(stream,format,args); #else /*used by liblinphone tester to retrieve suite list*/ - result = AndroidPrintf(stream, format, args); + result = 0; + cunit_android_trace_handler(stream, format, args); #endif va_end(args); return result; From 11dab7cfb43eb6fe08ade1ecf82057f4358094a5 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 4 Aug 2014 12:56:51 +0200 Subject: [PATCH 117/218] Allow None is string parameter in the Python wrapper. --- tools/python/apixml2python/linphone.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 42e6c5786..784bec1f6 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -482,27 +482,30 @@ class SetterMethodDefinition(MethodDefinition): def format_arguments_parsing(self): if self.checkfunc is None: attribute_type_check_code = \ -""" if (!PyObject_IsInstance(value, (PyObject *)&pylinphone_{class_name}Type)) {{ +"""if (!PyObject_IsInstance(value, (PyObject *)&pylinphone_{class_name}Type)) {{ PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a linphone.{class_name} instance"); return -1; }} """.format(class_name=self.first_arg_class, attribute_name=self.attribute_name) else: + checknotnone = '' + if self.type_str == 'string': + checknotnone = "(value != Py_None) && " attribute_type_check_code = \ -""" if (!{checkfunc}(value)) {{ +"""if ({checknotnone}!{checkfunc}(value)) {{ PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a {type_str}"); return -1; }} -""".format(checkfunc=self.checkfunc, attribute_name=self.attribute_name, type_str=self.type_str) +""".format(checknotnone=checknotnone, checkfunc=self.checkfunc, attribute_name=self.attribute_name, type_str=self.type_str) if self.convertfunc is None: - attribute_conversion_code = "\t{arg_name} = value;\n".format(arg_name="_" + self.first_arg_name) + attribute_conversion_code = "{arg_name} = value;\n".format(arg_name="_" + self.first_arg_name) else: - attribute_conversion_code = "\t{arg_name} = ({arg_type}){convertfunc}(value);\n".format( + attribute_conversion_code = "{arg_name} = ({arg_type}){convertfunc}(value);\n".format( arg_name="_" + self.first_arg_name, arg_type=self.first_arg_complete_type, convertfunc=self.convertfunc) attribute_native_ptr_check_code = '' if self.python_fmt == 'O': attribute_native_ptr_check_code = \ -""" {arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name}); +"""{arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name}); if ({arg_name}_native_ptr == NULL) {{ PyErr_SetString(PyExc_TypeError, "Invalid linphone.{arg_class} instance"); return -1; From e5db6dabd593d69447182453328829427adbffb6 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 4 Aug 2014 12:57:45 +0200 Subject: [PATCH 118/218] Begin handling of commands in Python command-line interface to Linphone. --- tools/python/linphone-daemon.py | 269 ++++++++++++++++++++++++++++++++ tools/python/test.py | 104 ------------ 2 files changed, 269 insertions(+), 104 deletions(-) create mode 100644 tools/python/linphone-daemon.py delete mode 100644 tools/python/test.py diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py new file mode 100644 index 000000000..8cdc43eac --- /dev/null +++ b/tools/python/linphone-daemon.py @@ -0,0 +1,269 @@ +import argparse +import linphone +import logging +import sys +import threading +import time + + +class StoppableThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.stop_event = threading.Event() + + def stop(self): + if self.isAlive() == True: + # Set an event to signal the thread to terminate + self.stop_event.set() + # Block the calling thread until the thread really has terminated + self.join() + +class IntervalTimer(StoppableThread): + def __init__(self, interval, worker_func, kwargs={}): + StoppableThread.__init__(self) + self._interval = interval + self._worker_func = worker_func + self._kwargs = kwargs + + def run(self): + while not self.stop_event.is_set(): + self._worker_func(self._kwargs) + time.sleep(self._interval) + + +class Response: + Ok = 0 + Error = 1 + + def __init__(self, status, msg = ''): + self.status = status + if status == Response.Ok: + self.body = msg + self.reason = None + else: + self.body = None + self.reason = msg + + def __str__(self): + status_str = ["Ok", "Error"][self.status] + body = '' + if self.reason: + body += "Reason: {reason}\n".format(reason=self.reason) + if self.body: + body += '\n' + self.body + '\n' + return \ +"""Status: {status} +{body}""".format(status=status_str, body=body) + +class Command: + def __init__(self, name, proto): + self.name = name + self.proto = proto + + def exec_command(self, app, args): + pass + + def help(self): + return \ +"""{proto} + +Description: +{description} +""".format(proto=self.proto, description=self.__doc__) + +class CallCommand(Command): + """Place a call.""" + def __init__(self): + Command.__init__(self, "call", "call ") + + def exec_command(self, app, args): + if len(args) >= 1: + call = app.core.invite(args[0]) + if call is None: + app.send_response(Response(Response.Error, "Call creation failed.")) + else: + id = app.update_call_id(call) + app.send_response(Response(Response.Ok, "Id: " + str(id))) + else: + app.send_response(Response(Response.Error, "Missing parameter.")) + +class HelpCommand(Command): + """Show help notice, if command is unspecified or inexistent show all commands.""" + def __init__(self): + Command.__init__(self, "help", "help ") + + def exec_command(self, app, args): + body = '' + if args: + command = [item for item in app.commands if item.name == args[0]] + if command: + body = command[0].help() + else: + app.send_response(Response(Response.Error, "Unknown command '{command}'.".format(command=args[0]))) + return + else: + for command in app.commands: + body += command.proto + '\n' + app.send_response(Response(Response.Ok, body)) + +class QuitCommand(Command): + """Quit the application.""" + def __init__(self): + Command.__init__(self, "quit", "quit") + + def exec_command(self, app, args): + app.quit() + app.send_response(Response(Response.Ok)) + +class RegisterCommand(Command): + """Register the daemon to a SIP proxy. If one of the parameters , and is not needed, send the string "NULL".""" + def __init__(self): + Command.__init__(self, "register", "register [password] [userid] [realm] [contact-parameters]") + + def exec_command(self, app, args): + if len(args) >= 2: + password = None + userid = None + realm = None + contact_parameters = None + identity = args[0] + proxy = args[1] + if len(args) > 2 and args[2] != "NULL": + password = args[2] + if len(args) > 3 and args[3] != "NULL": + userid = args[3] + if len(args) > 4 and args[4] != "NULL": + realm = args[4] + if len(args) > 5 and args[5] != "NULL": + contact_parameters = args[5] + proxy_cfg = app.core.create_proxy_config() + if password is not None: + addr = linphone.Address.new(identity) + if addr is not None: + info = linphone.AuthInfo.new(addr.username, userid, password, None, realm, None) + app.core.add_auth_info(info) + print(info) + proxy_cfg.identity = identity + proxy_cfg.server_addr = proxy + proxy_cfg.register_enabled = True + proxy_cfg.contact_parameters = contact_parameters + app.core.add_proxy_config(proxy_cfg) + id = app.update_proxy_id(proxy_cfg) + app.send_response(Response(Response.Ok, "Id: " + str(id))) + else: + app.send_response(Response(Response.Error, "Missing/Incorrect parameter(s).")) + +class Daemon: + def __init__(self): + self._quit = False + self._next_proxy_id = 1 + self._proxy_ids_map = {} + self._next_call_id = 1 + self._call_ids_map = {} + self.commands = [ + CallCommand(), + HelpCommand(), + QuitCommand(), + RegisterCommand() + ] + + def send_response(self, response): + print(response) + + def exec_command(self, command_line): + splitted_command_line = command_line.split() + name = splitted_command_line[0] + args = splitted_command_line[1:] + command = [item for item in self.commands if item.name == name] + if command: + command[0].exec_command(self, args) + else: + self.send_response(Response(Response.Error, "Unknown command.")) + + def interact(self): + command_line = raw_input('> ').strip() + if command_line != '': + self.exec_command(command_line) + + def run(self, args): + # Define the iteration function + def iterate(kwargs): + core = kwargs['core'] + core.iterate() + + def global_state_changed(core, state, message): + logging.warning("[PYTHON] global_state_changed: " + str(state) + ", " + message) + if state == linphone.GlobalState.GlobalOn: + logging.warning("[PYTHON] core version: " + str(core.version)) + + def registration_state_changed(core, proxy_cfg, state, message): + logging.warning("[PYTHON] registration_state_changed: " + str(state) + ", " + message) + + callbacks = { + 'global_state_changed':global_state_changed, + 'registration_state_changed':registration_state_changed + } + + # Create a linphone core and iterate every 20 ms + self.core = linphone.Core.new(callbacks, args.config, args.factory_config) + interval = IntervalTimer(0.02, iterate, kwargs={'core':self.core}) + interval.start() + while not self._quit: + self.interact() + interval.stop() + + def quit(self): + self._quit = True + + def update_proxy_id(self, proxy): + id = self._next_proxy_id + self._proxy_ids_map[id] = proxy + self._next_proxy_id = self._next_proxy_id + 1 + return id + + def update_call_id(self, call): + id = self._next_call_id + self._call_ids_map[id] = call + self._next_call_id = self._next_call_id + 1 + return id + +def setup_log_colors(): + logging.addLevelName(logging.DEBUG, "\033[1;37m%s\033[1;0m" % logging.getLevelName(logging.DEBUG)) + logging.addLevelName(logging.INFO, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.INFO)) + logging.addLevelName(logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) + logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) + +def setup_log(log, trace): + if log is None: + setup_log_colors() + format = "%(asctime)s.%(msecs)03d %(levelname)s: %(message)s" + datefmt = "%H:%M:%S" + if trace: + level = logging.DEBUG + else: + level = logging.INFO + logging.basicConfig(filename=log, level=level, format=format, datefmt=datefmt) + +# Define the linphone module log handler +def log_handler(level, msg): + method = getattr(logging, level) + if not msg.strip().startswith('[PYLINPHONE]'): + msg = '[CORE] ' + msg + method(msg) + +def main(argv = None): + if argv is None: + argv = sys.argv + argparser = argparse.ArgumentParser(description="Linphone console interface in Python.") + argparser.add_argument('--config', default=None, help="Path to the linphonerc configuration file to use.") + argparser.add_argument('--factory_config', default=None, help="Path to the linphonerc factory configuration file to use.") + argparser.add_argument('--log', default=None, help="Path to the file used for logging (default is the standard output).") + argparser.add_argument('--trace', action='store_true', help="Output linphone Python module tracing logs (for debug purposes).") + args = argparser.parse_args() + setup_log(args.log, args.trace) + linphone.set_log_handler(log_handler) + d = Daemon() + d.run(args) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/python/test.py b/tools/python/test.py deleted file mode 100644 index 53856e7d4..000000000 --- a/tools/python/test.py +++ /dev/null @@ -1,104 +0,0 @@ -import argparse -import linphone -import logging -import sys -import threading -import time - - -class StoppableThread(threading.Thread): - def __init__(self): - threading.Thread.__init__(self) - self.stop_event = threading.Event() - - def stop(self): - if self.isAlive() == True: - # Set an event to signal the thread to terminate - self.stop_event.set() - # Block the calling thread until the thread really has terminated - self.join() - -class IntervalTimer(StoppableThread): - def __init__(self, interval, worker_func, kwargs={}): - StoppableThread.__init__(self) - self._interval = interval - self._worker_func = worker_func - self._kwargs = kwargs - - def run(self): - while not self.stop_event.is_set(): - self._worker_func(self._kwargs) - time.sleep(self._interval) - - -def setup_log_colors(): - logging.addLevelName(logging.DEBUG, "\033[1;37m%s\033[1;0m" % logging.getLevelName(logging.DEBUG)) - logging.addLevelName(logging.INFO, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.INFO)) - logging.addLevelName(logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) - logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) - -def setup_log(log, trace): - if log is None: - setup_log_colors() - format = "%(asctime)s.%(msecs)03d %(levelname)s: %(message)s" - datefmt = "%H:%M:%S" - if trace: - level = logging.DEBUG - else: - level = logging.INFO - logging.basicConfig(filename=log, level=level, format=format, datefmt=datefmt) - -# Define the linphone module log handler -def log_handler(level, msg): - method = getattr(logging, level) - if not msg.strip().startswith('[PYLINPHONE]'): - msg = '[CORE] ' + msg - method(msg) - -# Define the iteration function -def iterate(kwargs): - core = kwargs['core'] - core.iterate() - -def interact(): - choice = raw_input('> ') - if choice == "quit": - return False - return True - -def global_state_changed(core, state, message): - logging.warning("[PYTHON] global_state_changed: " + str(state) + ", " + message) - if state == linphone.GlobalState.GlobalOn: - logging.warning("[PYTHON] core version: " + str(core.version)) - -def registration_state_changed(core, proxy_cfg, state, message): - logging.warning("[PYTHON] registration_state_changed: " + str(state) + ", " + message) - -def run(args): - # Create a linphone core and iterate every 20 ms - setup_log(args.log, args.trace) - linphone.set_log_handler(log_handler) - callbacks = { - 'global_state_changed':global_state_changed, - 'registration_state_changed':registration_state_changed - } - core = linphone.Core.new(callbacks, args.config, args.factory_config) - interval = IntervalTimer(0.02, iterate, kwargs={'core':core}) - interval.start() - while interact(): - pass - interval.stop() - -def main(argv = None): - if argv is None: - argv = sys.argv - argparser = argparse.ArgumentParser(description="Linphone console interface in Python.") - argparser.add_argument('--config', default=None, help="Path to the linphonerc configuration file to use.") - argparser.add_argument('--factory_config', default=None, help="Path to the linphonerc factory configuration file to use.") - argparser.add_argument('--log', default=None, help="Path to the file used for logging (default is the standard output).") - argparser.add_argument('--trace', action='store_true', help="Output linphone Python module tracing logs (for debug purposes).") - args = argparser.parse_args() - run(args) - -if __name__ == "__main__": - sys.exit(main()) From 5547298f2527cb04a6fcef5b8092184b735a5981 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 4 Aug 2014 14:26:03 +0200 Subject: [PATCH 119/218] Update ms2 submodule. --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 44675f02a..37ca88370 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 44675f02ad01445478e2be8ad1ab533f74737bf0 +Subproject commit 37ca88370e57fda9488d4523db46197beb36df94 From 9d547808651d3a4f1272da69a3e11afd4c4fd171 Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Mon, 4 Aug 2014 14:28:40 +0200 Subject: [PATCH 120/218] Add jni for adaptive rate control --- coreapi/linphonecore_jni.cc | 14 ++++++++++++++ java/common/org/linphone/core/LinphoneCore.java | 12 ++++++++++++ java/impl/org/linphone/core/LinphoneCoreImpl.java | 11 +++++++++++ 3 files changed, 37 insertions(+) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 1f01ccadf..fc6b76f1f 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -1287,6 +1287,20 @@ extern "C" jint Java_org_linphone_core_LinphoneCoreImpl_getPayloadTypeBitrate(JN return (jint)linphone_core_get_payload_type_bitrate((LinphoneCore*)lc,(PayloadType*)pt); } +extern "C" void Java_org_linphone_core_LinphoneCoreImpl_enableAdaptiveRateControl(JNIEnv* env + ,jobject thiz + ,jlong lc + ,jboolean enable) { + linphone_core_enable_adaptive_rate_control((LinphoneCore*)lc, enable); +} + +extern "C" jboolean Java_org_linphone_core_LinphoneCoreImpl_isAdaptiveRateControlEnabled(JNIEnv* env + ,jobject thiz + ,jlong lc + ) { + return (jboolean)linphone_core_adaptive_rate_control_enabled((LinphoneCore*)lc); +} + extern "C" void Java_org_linphone_core_LinphoneCoreImpl_enableEchoCancellation(JNIEnv* env ,jobject thiz ,jlong lc diff --git a/java/common/org/linphone/core/LinphoneCore.java b/java/common/org/linphone/core/LinphoneCore.java index 332a85be0..a74ebc125 100644 --- a/java/common/org/linphone/core/LinphoneCore.java +++ b/java/common/org/linphone/core/LinphoneCore.java @@ -707,6 +707,18 @@ public interface LinphoneCore { */ int getPayloadTypeBitrate(PayloadType pt); + /** + * Enable adaptive rate control. + * @param enable + */ + void enableAdaptiveRateControl(boolean enable); + + /** + * Enables or disable adaptive rate control. + * @return true if adaptive rate control is enabled. + */ + boolean isAdaptiveRateControlEnabled(); + /** * Enables or disable echo cancellation. * @param enable diff --git a/java/impl/org/linphone/core/LinphoneCoreImpl.java b/java/impl/org/linphone/core/LinphoneCoreImpl.java index eeebbd328..04a679f1a 100644 --- a/java/impl/org/linphone/core/LinphoneCoreImpl.java +++ b/java/impl/org/linphone/core/LinphoneCoreImpl.java @@ -73,6 +73,8 @@ class LinphoneCoreImpl implements LinphoneCore { private native long findPayloadType(long nativePtr, String mime, int clockRate, int channels); private native int enablePayloadType(long nativePtr, long payloadType, boolean enable); private native boolean isPayloadTypeEnabled(long nativePtr, long payloadType); + private native void enableAdaptiveRateControl(long nativePtr,boolean enable); + private native boolean isAdaptiveRateControlEnabled(long nativePtr); private native void enableEchoCancellation(long nativePtr,boolean enable); private native boolean isEchoCancellationEnabled(long nativePtr); private native Object getCurrentCall(long nativePtr) ; @@ -1190,5 +1192,14 @@ class LinphoneCoreImpl implements LinphoneCore { public synchronized int getPayloadTypeBitrate(PayloadType pt) { return getPayloadTypeBitrate(nativePtr, ((PayloadTypeImpl)pt).nativePtr); } + @Override + public void enableAdaptiveRateControl(boolean enable) { + enableAdaptiveRateControl(nativePtr,enable); + + } + @Override + public boolean isAdaptiveRateControlEnabled() { + return isAdaptiveRateControlEnabled(nativePtr); + } } From c420236597178e9e41ccbfd05f6953cde42034c6 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 4 Aug 2014 14:57:45 +0200 Subject: [PATCH 121/218] Handle command examples + add register-status command. --- tools/python/linphone-daemon.py | 93 +++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py index 8cdc43eac..1d2243c33 100644 --- a/tools/python/linphone-daemon.py +++ b/tools/python/linphone-daemon.py @@ -55,26 +55,67 @@ class Response: """Status: {status} {body}""".format(status=status_str, body=body) +class RegisterStatusResponse(Response): + def __init__(self): + Response.__init__(self, Response.Ok) + + def append(self, id, proxy_cfg): + self.body += \ +"""Id: {id} +State: {state} +""".format(id=id, state=str(proxy_cfg.state)) + +class CommandExample: + def __init__(self, command, output): + self.command = command + self.output = output + + def __str__(self): + return \ +"""> {command} +{output}""".format(command=self.command, output=self.output) + class Command: def __init__(self, name, proto): self.name = name self.proto = proto + self.examples = [] def exec_command(self, app, args): pass + def add_example(self, example): + self.examples.append(example) + def help(self): - return \ + body = \ """{proto} Description: {description} """.format(proto=self.proto, description=self.__doc__) + idx = 0 + for example in self.examples: + idx += 1 + body += \ +""" +Example {idx}: +{example} +""".format(idx=idx, example=str(example)) + return body class CallCommand(Command): """Place a call.""" def __init__(self): Command.__init__(self, "call", "call ") + self.add_example(CommandExample( + "call daemon-test@sip.linphone.org", + "Status: Ok\n\nId: 1" + )) + self.add_example(CommandExample( + "call daemon-test@sip.linphone.org", + "Status: Error\nReason: Call creation failed." + )) def exec_command(self, app, args): if len(args) >= 1: @@ -119,6 +160,10 @@ class RegisterCommand(Command): """Register the daemon to a SIP proxy. If one of the parameters , and is not needed, send the string "NULL".""" def __init__(self): Command.__init__(self, "register", "register [password] [userid] [realm] [contact-parameters]") + self.add_example(CommandExample( + "register sip:daemon-test@sip.linphone.org sip.linphone.org password bob linphone.org", + "Status: Ok\n\nId: 1" + )) def exec_command(self, app, args): if len(args) >= 2: @@ -153,18 +198,53 @@ class RegisterCommand(Command): else: app.send_response(Response(Response.Error, "Missing/Incorrect parameter(s).")) +class RegisterStatusCommand(Command): + """Return status of a registration or of all registrations.""" + def __init__(self): + Command.__init__(self, "register-status", "register-status ") + self.add_example(CommandExample( + "register-status 1", + "Status: Ok\n\nId: 1\nState: LinphoneRegistrationOk" + )) + self.add_example(CommandExample( + "register-status ALL", + "Status: Ok\n\nId: 1\nState: LinphoneRegistrationOk\n\nId: 2\nState: LinphoneRegistrationFailed" + )) + self.add_example(CommandExample( + "register-status 3", + "Status: Error\nReason: No register with such id." + )) + + def exec_command(self, app, args): + if len(args) == 0: + app.send_response(Response(Response.Error, "Missing parameter.")) + else: + id = args[0] + if id == "ALL": + response = RegisterStatusResponse() + for id in app.proxy_ids_map: + response.append(id, app.proxy_ids_map[id]) + app.send_response(response) + else: + proxy_cfg = app.find_proxy(id) + if proxy_cfg is None: + app.send_response(Response(Response.Error, "No register with such id.")) + else: + app.send_response(RegisterStatusResponse().append(id, proxy_cfg)) + class Daemon: def __init__(self): self._quit = False self._next_proxy_id = 1 - self._proxy_ids_map = {} + self.proxy_ids_map = {} self._next_call_id = 1 self._call_ids_map = {} self.commands = [ CallCommand(), HelpCommand(), QuitCommand(), - RegisterCommand() + RegisterCommand(), + RegisterStatusCommand() ] def send_response(self, response): @@ -217,10 +297,15 @@ class Daemon: def update_proxy_id(self, proxy): id = self._next_proxy_id - self._proxy_ids_map[id] = proxy + self.proxy_ids_map[id] = proxy self._next_proxy_id = self._next_proxy_id + 1 return id + def find_proxy(self, id): + if self.proxy_ids_map.has_key(id): + return self.proxy_ids_map[id] + return None + def update_call_id(self, call): id = self._next_call_id self._call_ids_map[id] = call From 123e4ccdfe5c515ad4e751b91e45e3f8a9fb8482 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 4 Aug 2014 16:24:32 +0200 Subject: [PATCH 122/218] Define methods to get string representation of enum values in the Python wrapper. --- .../apixml2python/linphone_module.mustache | 32 +++++++++++++++++-- tools/python/linphone-daemon.py | 2 +- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 9b54a2073..b85253611 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -181,16 +181,42 @@ static PyTypeObject pylinphone_{{class_name}}Type = { static PyMethodDef pylinphone_ModuleMethods[] = { - /* Sentinel */ { "set_log_handler", pylinphone_module_method_set_log_handler, METH_VARARGS, "" }, + /* Sentinel */ { NULL, NULL, 0, NULL } }; -static PyMethodDef pylinphone_NoMethods[] = { +{{#enums}} +static PyObject * pylinphone_{{enum_name}}_module_method_string(PyObject *self, PyObject *args) { + const char *value_str = "[invalid]"; + int value; + PyObject *pyret; + if (!PyArg_ParseTuple(args, "i", &value)) { + return NULL; + } + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%d)", __FUNCTION__, value); + switch (value) { +{{#enum_values}} + case {{enum_value_cname}}: + value_str = "{{enum_value_name}}"; + break; +{{/enum_values}} + default: + break; + } + pyret = Py_BuildValue("z", value_str); + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> %p", __FUNCTION__, pyret); + return pyret; +} + +static PyMethodDef pylinphone_{{enum_name}}_ModuleMethods[] = { + { "string", pylinphone_{{enum_name}}_module_method_string, METH_VARARGS, "" }, /* Sentinel */ { NULL, NULL, 0, NULL } }; +{{/enums}} + PyMODINIT_FUNC initlinphone(void) { PyObject *m; PyObject *menum; @@ -205,7 +231,7 @@ PyMODINIT_FUNC initlinphone(void) { if (m == NULL) return; {{#enums}} - menum = Py_InitModule3("{{enum_name}}", pylinphone_NoMethods, {{{enum_doc}}}); + menum = Py_InitModule3("{{enum_name}}", pylinphone_{{enum_name}}_ModuleMethods, {{{enum_doc}}}); if (menum == NULL) return; if (PyModule_AddObject(m, "{{enum_name}}", menum) < 0) return; {{#enum_values}} diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py index 1d2243c33..ce24fca61 100644 --- a/tools/python/linphone-daemon.py +++ b/tools/python/linphone-daemon.py @@ -63,7 +63,7 @@ class RegisterStatusResponse(Response): self.body += \ """Id: {id} State: {state} -""".format(id=id, state=str(proxy_cfg.state)) +""".format(id=id, state=str(linphone.RegistrationState.string(proxy_cfg.state))) class CommandExample: def __init__(self, command, output): From b79cc5738dc74370ddf60b03e29ce092e532b0d6 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 4 Aug 2014 16:25:13 +0200 Subject: [PATCH 123/218] Add missing reference decrement in the Python wrapper. --- tools/python/apixml2python/handwritten.mustache | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index 69f73533b..6971b7cb6 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -92,6 +92,7 @@ static PyObject * pylinphone_module_method_set_log_handler(PyObject *self, PyObj if (linphone_module != NULL) { Py_INCREF(callback); PyObject_SetAttrString(linphone_module, "__log_handler", callback); + Py_DECREF(linphone_module); } Py_RETURN_NONE; } From 6a191c2fb584b3c99b575650489cce1e60a0ed10 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 4 Aug 2014 17:52:04 +0200 Subject: [PATCH 124/218] Add call_state_changed callback in the Python application. --- tools/python/linphone-daemon.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py index ce24fca61..fbde38a79 100644 --- a/tools/python/linphone-daemon.py +++ b/tools/python/linphone-daemon.py @@ -279,9 +279,13 @@ class Daemon: def registration_state_changed(core, proxy_cfg, state, message): logging.warning("[PYTHON] registration_state_changed: " + str(state) + ", " + message) + def call_state_changed(core, call, state, message): + logging.warning("[PYTHON] call_state_changed: " + str(state) + ", " + message) + callbacks = { 'global_state_changed':global_state_changed, - 'registration_state_changed':registration_state_changed + 'registration_state_changed':registration_state_changed, + 'call_state_changed':call_state_changed } # Create a linphone core and iterate every 20 ms From 0aca2d72fc96408577cdb2b5348f51304c716c41 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 4 Aug 2014 17:52:30 +0200 Subject: [PATCH 125/218] Check for NULL pointer before calling *_ref() function in Python wrapper. --- tools/python/apixml2python/linphone.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 784bec1f6..0a8c1f550 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -160,7 +160,11 @@ class MethodDefinition: new_from_native_pointer_code = "\tpyresult = pylinphone_{return_type}_new_from_native_ptr(&pylinphone_{return_type}Type, cresult);\n".format(return_type=stripped_return_type) if self.self_arg is not None and return_type_class['class_refcountable']: ref_function = return_type_class['class_c_function_prefix'] + "ref" - ref_native_pointer_code = "\t{func}(({cast_type})cresult);\n".format(func=ref_function, cast_type=self.remove_const_from_complete_type(self.return_complete_type)) + ref_native_pointer_code = \ +""" if (cresult != NULL) {{ + {func}(({cast_type})cresult); + }} +""".format(func=ref_function, cast_type=self.remove_const_from_complete_type(self.return_complete_type)) result_variable = 'pyresult' else: result_variable = 'cresult' From 9582124ed567f82a127d2062da0e2176feb49390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Mon, 4 Aug 2014 18:17:41 +0200 Subject: [PATCH 126/218] Update mediastreamer2 submodule --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 37ca88370..cc94bbd37 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 37ca88370e57fda9488d4523db46197beb36df94 +Subproject commit cc94bbd3703acf2794093e948be08e39bed3d58c From d4665fde599479ae199c45a4a48281592cce9715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Mon, 4 Aug 2014 18:18:03 +0200 Subject: [PATCH 127/218] Add "Call recording" test to the Call suite --- tester/call_tester.c | 47 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tester/call_tester.c b/tester/call_tester.c index b507b5855..c07e29e86 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -18,6 +18,8 @@ #include +#include +#include #include "CUnit/Basic.h" #include "linphonecore.h" #include "lpconfig.h" @@ -2647,6 +2649,48 @@ static void savpf_to_savpf_call(void) { profile_call(TRUE, TRUE, TRUE, TRUE, "RTP/SAVPF"); } +static void call_recording() { + LinphoneCoreManager *marie = NULL, *pauline = NULL; + LinphoneCallParams *params = NULL; + const MSList *calls = NULL; + LinphoneCall *callInst = NULL; + const char filename[] = "recording.mkv"; + const char dirname[] = ".test"; + char *filepath = NULL; + + filepath = ms_new0(char, strlen(dirname) + strlen(filename) + 2); + strcpy(filepath, dirname); + strcat(filepath, "/"); + strcat(filepath, filename); + if(access(dirname, F_OK) != 0) { + CU_ASSERT_EQUAL(mkdir(dirname, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH), 0); + } + CU_ASSERT_EQUAL(access(dirname, W_OK), 0); + if(access(filepath, F_OK) == 0) { + CU_ASSERT_EQUAL(remove(filepath), 0); + } + + marie = linphone_core_manager_new("marie_rc"); + pauline = linphone_core_manager_new("pauline_rc"); + params = linphone_core_create_default_call_parameters(marie->lc); + linphone_call_params_set_record_file(params, filepath); + + CU_ASSERT_TRUE(call_with_caller_params(marie, pauline, params)); + calls = linphone_core_get_calls(marie->lc); + CU_ASSERT_PTR_NOT_NULL(calls); + callInst = (LinphoneCall *)calls->data; + linphone_call_start_recording(callInst); + sleep(2); + linphone_call_stop_recording(callInst); + + CU_ASSERT_EQUAL(access(filepath, F_OK), 0); + end_call(marie, pauline); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + ms_free(filepath); +} + test_t call_tests[] = { { "Early declined call", early_declined_call }, { "Call declined", call_declined }, @@ -2735,7 +2779,8 @@ test_t call_tests[] = { { "SAVPF to AVP call", savpf_to_avp_call }, { "SAVPF to AVPF call", savpf_to_avpf_call }, { "SAVPF to SAVP call", savpf_to_savp_call }, - { "SAVPF to SAVPF call", savpf_to_savpf_call } + { "SAVPF to SAVPF call", savpf_to_savpf_call }, + { "Call recording", call_recording } }; test_suite_t call_test_suite = { From 9725f1596b61096dfdc2126c0122c0f8247a764b Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 4 Aug 2014 22:11:49 +0200 Subject: [PATCH 128/218] Improve compilation with CMake. --- CMakeLists.txt | 35 ++++++++++++++- config.h.cmake | 28 ++++++++++++ coreapi/CMakeLists.txt | 95 ++++++++++++++-------------------------- coreapi/gitversion.cmake | 38 ++++++++++++++++ 4 files changed, 134 insertions(+), 62 deletions(-) create mode 100644 config.h.cmake create mode 100644 coreapi/gitversion.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 725126296..94f49e8f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,11 +21,33 @@ ############################################################################ cmake_minimum_required(VERSION 2.6) -project(LINPHONE C) +project(LINPHONE C CXX) +set(LINPHONE_MAJOR_VERSION "3") +set(LINPHONE_MINOR_VERSION "7") +set(LINPHONE_MICRO_VERSION "0") +set(LINPHONE_VERSION "${LINPHONE_MAJOR_VERSION}.${LINPHONE_MINOR_VERSION}.${LINPHONE_MICRO_VERSION}") +set(LINPHONE_SO_VERSION "6") + + +include(CMakeDependentOption) + option(ENABLE_STATIC "Build static library (default is shared library)." NO) +option(ENABLE_CONSOLE_UI "Turn on or off compilation of console interface." YES) +option(ENABLE_DATE "Use build date in internal version number." NO) +option(ENABLE_GTK_UI "Turn on or off compilation of gtk interface." YES) +option(ENABLE_LDAP "Enable LDAP support." NO) +option(ENABLE_MSG_STORAGE "Turn on compilation of message storage." YES) +option(ENABLE_NOTIFY "Enable libnotify support." YES) +option(ENABLE_RELATIVE_PREFIX "Find resources relatively to the installation directory." NO) +option(ENABLE_TOOLS "Turn on or off compilation of console interface" YES) +option(ENABLE_TUNNEL "Turn on compilation of tunnel support." NO) +option(ENABLE_TUTORIALS "Enable compilation of tutorials." YES) +option(ENABLE_UNIT_TESTS "Enable compilation of unit tests." YES) +option(ENABLE_UPNP "Build with uPnP support." YES) option(ENABLE_VIDEO "Build with video support." YES) +cmake_dependent_option(ENABLE_ASSISTANT "Turn on assistant compiling." YES "ENABLE_GTK_UI" NO) list(APPEND CMAKE_MODULE_PATH ${CMAKE_PREFIX_PATH}/share/cmake/Modules) @@ -44,6 +66,7 @@ find_package(XML2 REQUIRED) include_directories( include/ coreapi/ + ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/coreapi/ ${BELLESIP_INCLUDE_DIRS} ${MS2_INCLUDE_DIRS} @@ -55,5 +78,15 @@ if(MSVC) endif() +if(ENABLE_RELATIVE_PREFIX) + set(LINPHONE_PLUGINS_DIR "./lib/liblinphone/plugins") +else() + set(LINPHONE_PLUGINS_DIR "${CMAKE_INSTALL_PREFIX}/lib/liblinphone/plugins") +endif() +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/config.h PROPERTIES GENERATED ON) +add_definitions(-DHAVE_CONFIG_H) + + add_subdirectory(coreapi) add_subdirectory(share) diff --git a/config.h.cmake b/config.h.cmake new file mode 100644 index 000000000..6f8db1a86 --- /dev/null +++ b/config.h.cmake @@ -0,0 +1,28 @@ +/*************************************************************************** +* config.h.cmake +* Copyright (C) 2014 Belledonne Communications, Grenoble France +* +**************************************************************************** +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +* +****************************************************************************/ + +#define LINPHONE_MAJOR_VERSION ${LINPHONE_MAJOR_VERSION} +#define LINPHONE_MINOR_VERSION ${LINPHONE_MINOR_VERSION} +#define LINPHONE_MICRO_VERSION ${LINPHONE_MICRO_VERSION} +#define LINPHONE_VERSION "${LINPHONE_VERSION}" + +#cmakedefine LINPHONE_PLUGINS_DIR "${LINPHONE_PLUGINS_DIR}" diff --git a/coreapi/CMakeLists.txt b/coreapi/CMakeLists.txt index e6f530ee9..ff39dfb41 100644 --- a/coreapi/CMakeLists.txt +++ b/coreapi/CMakeLists.txt @@ -25,40 +25,6 @@ if(MSVC) find_library(LIBMINGWEX NAMES mingwex) endif() -find_program(GIT git) - -set(GIT_VERSION "unknown") -if(GIT) - execute_process( - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${GIT} describe --always - OUTPUT_VARIABLE GIT_DESCRIBE - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - execute_process( - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${GIT} describe --abbrev=0 - OUTPUT_VARIABLE GIT_TAG - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - execute_process( - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${GIT} rev-parse HEAD - OUTPUT_VARIABLE GIT_REVISION - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - if(GIT_DESCRIBE) - set(GIT_VERSION ${GIT_DESCRIBE}) - else(GIT_DESCRIBE) - if(GIT_REVISION) - set(GIT_VERSION ${GIT_REVISION}) - endif(GIT_REVISION) - endif(GIT_DESCRIBE) -endif(GIT) -execute_process( - COMMAND ${CMAKE_COMMAND} -E echo "#define LIBLINPHONE_GIT_VERSION \"${GIT_VERSION}\"" - OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/liblinphone_gitversion.h -) set(SOURCE_FILES address.c @@ -85,7 +51,6 @@ set(SOURCE_FILES info.c linphonecall.c linphonecore.c - #linphone_tunnel.cc linphone_tunnel_stubs.c linphone_tunnel_config.c lpconfig.c @@ -100,7 +65,6 @@ set(SOURCE_FILES sal.c siplogin.c sipsetup.c - #TunnelManager.cc xml.c xml2lpc.c bellesip_sal/sal_impl.h @@ -116,16 +80,26 @@ set(SOURCE_FILES sipsetup.h xml2lpc.h ) +if(ENABLE_TUNNEL) + list(APPEND SOURCE_FILES + linphone_tunnel.cc + TunnelManager.cc + ) + add_definitions(-DTUNNEL_ENABLED) +endif() + +set(GENERATED_SOURCE_FILES + ${CMAKE_CURRENT_BINARY_DIR}/liblinphone_gitversion.h +) +set_source_files_properties(${GENERATED_SOURCE_FILES} PROPERTIES GENERATED TRUE) +find_package(Git) +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/liblinphone_gitversion.h + COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DOUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/gitversion.cmake) add_definitions( - -D_TRUE_TIME -DIN_LINPHONE -DUSE_BELLESIP - #-DTUNNEL_ENABLED - -DLINPHONE_PACKAGE_NAME="linphone" - -DLINPHONE_VERSION="Devel" -DLIBLINPHONE_EXPORTS - -DLINPHONE_PLUGINS_DIR="" ) if(ENABLE_VIDEO) @@ -139,23 +113,30 @@ set(LIBS ${MS2_LIBRARIES} ${XML2_LIBRARIES} ) -if(WIN32) -add_definitions( - -DWINDOW_NATIVE - /FIliblinphone_gitversion.h -) + +if(ENABLE_STATIC) + add_library(linphone STATIC ${SOURCE_FILES} ${GENERATED_SOURCE_FILES}) + target_link_libraries(linphone ${LIBS}) +else() + add_library(linphone SHARED ${SOURCE_FILES} ${GENERATED_SOURCE_FILES}) + set_target_properties(linphone PROPERTIES VERSION ${LINPHONE_VERSION} SOVERSION ${LINPHONE_SO_VERSION} LINKER_LANGUAGE CXX) + target_link_libraries(linphone ${LIBS}) + if(MSVC) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/Debug/linphone.pdb + DESTINATION bin + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + ) + endif() + endif() endif() -add_library(linphone SHARED ${SOURCE_FILES}) -set_target_properties(linphone PROPERTIES VERSION 3.7.0 SOVERSION 5) - -target_link_libraries(linphone ${LIBS}) - install(TARGETS linphone - RUNTIME DESTINATION bin + RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib - PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE +) file(GLOB HEADER_FILES "*.h") @@ -164,11 +145,3 @@ install(FILES ${HEADER_FILES} DESTINATION include/linphone PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ ) -if(WIN32) -if(CMAKE_BUILD_TYPE STREQUAL "Debug") -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/Debug/linphone.pdb - DESTINATION bin - PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE -) -endif() -endif() diff --git a/coreapi/gitversion.cmake b/coreapi/gitversion.cmake new file mode 100644 index 000000000..1a6ec406c --- /dev/null +++ b/coreapi/gitversion.cmake @@ -0,0 +1,38 @@ +############################################################################ +# CMakeLists.txt +# Copyright (C) 2014 Belledonne Communications, Grenoble France +# +############################################################################ +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################ + +if(GIT_EXECUTABLE) + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --always + OUTPUT_VARIABLE GIT_REVISION + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + execute_process( + COMMAND ${CMAKE_COMMAND} -E echo "#define GIT_VERSION \"${GIT_REVISION}\"" + OUTPUT_FILE ${OUTPUT_DIR}/liblinphone_gitversion.h + ) +else() + execute_process( + COMMAND ${CMAKE_COMMAND} -E echo "#define GIT_VERSION \"unknown\"" + OUTPUT_FILE ${OUTPUT_DIR}/liblinphone_gitversion.h + ) +endif() From 0046baac1bfa3e027b901646c7315b2cec8ecce1 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 5 Aug 2014 12:02:22 +0200 Subject: [PATCH 129/218] Updated ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index cc94bbd37..2af3b2c25 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit cc94bbd3703acf2794093e948be08e39bed3d58c +Subproject commit 2af3b2c257c21278aa1b08fbb0f8a15da14f5471 From 7ec17111b0113481083334fc3bb0509542dd2e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Tue, 5 Aug 2014 15:07:18 +0200 Subject: [PATCH 130/218] Add video support to the "Call recording" test --- mediastreamer2 | 2 +- tester/call_tester.c | 67 ++++++++++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index 2af3b2c25..e3fe44b86 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 2af3b2c257c21278aa1b08fbb0f8a15da14f5471 +Subproject commit e3fe44b86513914192078d2096d83592cfeb41ff diff --git a/tester/call_tester.c b/tester/call_tester.c index c07e29e86..095df01c9 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -27,7 +27,7 @@ #include "liblinphone_tester.h" static void call_base(LinphoneMediaEncryption mode, bool_t enable_video,bool_t enable_relay,LinphoneFirewallPolicy policy); -static void disable_all_codecs_except_one(LinphoneCore *lc, const char *mime); +static void disable_all_audio_codecs_except_one(LinphoneCore *lc, const char *mime); void call_state_changed(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate, const char *msg){ char* to=linphone_address_as_string(linphone_call_get_call_log(call)->to); @@ -427,8 +427,8 @@ static void call_with_specified_codec_bitrate(void) { goto end; } - disable_all_codecs_except_one(marie->lc,"opus"); - disable_all_codecs_except_one(pauline->lc,"opus"); + disable_all_audio_codecs_except_one(marie->lc,"opus"); + disable_all_audio_codecs_except_one(pauline->lc,"opus"); linphone_core_set_payload_type_bitrate(marie->lc, linphone_core_find_payload_type(marie->lc,"opus",48000,-1), @@ -537,7 +537,7 @@ static void cancelled_call(void) { linphone_core_manager_destroy(pauline); } -static void disable_all_codecs_except_one(LinphoneCore *lc, const char *mime){ +static void disable_all_audio_codecs_except_one(LinphoneCore *lc, const char *mime){ const MSList *elem=linphone_core_get_audio_codecs(lc); PayloadType *pt; @@ -550,13 +550,25 @@ static void disable_all_codecs_except_one(LinphoneCore *lc, const char *mime){ linphone_core_enable_payload_type(lc,pt,TRUE); } +static void disable_all_video_codecs_except_one(LinphoneCore *lc, const char *mime) { + const MSList *codecs = linphone_core_get_video_codecs(lc); + const MSList *it = NULL; + PayloadType *pt = NULL; + + for(it = codecs; it != NULL; it = it->next) { + linphone_core_enable_payload_type(lc, (PayloadType *)it->data, FALSE); + } + CU_ASSERT_PTR_NOT_NULL_FATAL(pt = linphone_core_find_payload_type(lc, mime, -1, -1)); + linphone_core_enable_payload_type(lc, pt, TRUE); +} + static void call_failed_because_of_codecs(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); LinphoneCall* out_call; - disable_all_codecs_except_one(marie->lc,"pcmu"); - disable_all_codecs_except_one(pauline->lc,"pcma"); + disable_all_audio_codecs_except_one(marie->lc,"pcmu"); + disable_all_audio_codecs_except_one(pauline->lc,"pcma"); out_call = linphone_core_invite(pauline->lc,"marie"); linphone_call_ref(out_call); CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallOutgoingInit,1)); @@ -1913,8 +1925,8 @@ static void early_media_call_with_update_base(bool_t media_change){ lcs = ms_list_append(lcs,marie->lc); lcs = ms_list_append(lcs,pauline->lc); if (media_change) { - disable_all_codecs_except_one(marie->lc,"pcmu"); - disable_all_codecs_except_one(pauline->lc,"pcmu"); + disable_all_audio_codecs_except_one(marie->lc,"pcmu"); + disable_all_audio_codecs_except_one(pauline->lc,"pcmu"); } /* @@ -1939,8 +1951,8 @@ static void early_media_call_with_update_base(bool_t media_change){ pauline_params = linphone_call_params_copy(linphone_call_get_current_params(pauline_call)); if (media_change) { - disable_all_codecs_except_one(marie->lc,"pcma"); - disable_all_codecs_except_one(pauline->lc,"pcma"); + disable_all_audio_codecs_except_one(marie->lc,"pcma"); + disable_all_audio_codecs_except_one(pauline->lc,"pcma"); } #define UPDATED_SESSION_NAME "nouveau nom de session" @@ -2649,10 +2661,11 @@ static void savpf_to_savpf_call(void) { profile_call(TRUE, TRUE, TRUE, TRUE, "RTP/SAVPF"); } -static void call_recording() { - LinphoneCoreManager *marie = NULL, *pauline = NULL; - LinphoneCallParams *params = NULL; - const MSList *calls = NULL; +static void recording_call() { + LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_rc"); + LinphoneCallParams *marieParams = linphone_core_create_default_call_parameters(marie->lc); + LinphoneCallParams *paulineParams = linphone_core_create_default_call_parameters(pauline->lc); LinphoneCall *callInst = NULL; const char filename[] = "recording.mkv"; const char dirname[] = ".test"; @@ -2670,17 +2683,23 @@ static void call_recording() { CU_ASSERT_EQUAL(remove(filepath), 0); } - marie = linphone_core_manager_new("marie_rc"); - pauline = linphone_core_manager_new("pauline_rc"); - params = linphone_core_create_default_call_parameters(marie->lc); - linphone_call_params_set_record_file(params, filepath); + linphone_core_enable_video_display(marie->lc, TRUE); + linphone_core_enable_video_display(pauline->lc, FALSE); + linphone_core_enable_video_capture(marie->lc, TRUE); + linphone_core_enable_video_capture(pauline->lc, TRUE); + + linphone_call_params_enable_video(marieParams, TRUE); + linphone_call_params_set_record_file(marieParams, filepath); + linphone_call_params_enable_video(paulineParams, TRUE); + + disable_all_video_codecs_except_one(marie->lc, "H264"); + disable_all_video_codecs_except_one(pauline->lc, "H264"); + + CU_ASSERT_TRUE(call_with_params(marie, pauline, marieParams, paulineParams)); + CU_ASSERT_PTR_NOT_NULL(callInst = linphone_core_get_current_call(marie->lc)); - CU_ASSERT_TRUE(call_with_caller_params(marie, pauline, params)); - calls = linphone_core_get_calls(marie->lc); - CU_ASSERT_PTR_NOT_NULL(calls); - callInst = (LinphoneCall *)calls->data; linphone_call_start_recording(callInst); - sleep(2); + sleep(20); linphone_call_stop_recording(callInst); CU_ASSERT_EQUAL(access(filepath, F_OK), 0); @@ -2780,7 +2799,7 @@ test_t call_tests[] = { { "SAVPF to AVPF call", savpf_to_avpf_call }, { "SAVPF to SAVP call", savpf_to_savp_call }, { "SAVPF to SAVPF call", savpf_to_savpf_call }, - { "Call recording", call_recording } + { "Call recording", recording_call } }; test_suite_t call_test_suite = { From f056bb1085b23b34e8d35a1f6c7f68c3e46186d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Tue, 5 Aug 2014 15:54:00 +0200 Subject: [PATCH 131/218] Make "Call recording" tester writes in a wav file when video support is disabled. --- configure.ac | 2 +- mediastreamer2 | 2 +- tester/call_tester.c | 13 ++++++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 092b41129..8d4c80155 100644 --- a/configure.ac +++ b/configure.ac @@ -537,7 +537,7 @@ fi dnl conditionnal build of video support AC_ARG_ENABLE(video, - [AS_HELP_STRING([--enable-video], [Turn on video support compiling])], + [AS_HELP_STRING([--enable-video], [Turn on video support compiling (default=yes)])], [case "${enableval}" in yes) video=true ;; no) video=false ;; diff --git a/mediastreamer2 b/mediastreamer2 index e3fe44b86..a1746a013 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit e3fe44b86513914192078d2096d83592cfeb41ff +Subproject commit a1746a0139adcd656f29019c8b251d3947fe5580 diff --git a/tester/call_tester.c b/tester/call_tester.c index 095df01c9..510988a13 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -550,6 +550,7 @@ static void disable_all_audio_codecs_except_one(LinphoneCore *lc, const char *mi linphone_core_enable_payload_type(lc,pt,TRUE); } +#ifdef VIDEO_ENABLED static void disable_all_video_codecs_except_one(LinphoneCore *lc, const char *mime) { const MSList *codecs = linphone_core_get_video_codecs(lc); const MSList *it = NULL; @@ -561,6 +562,7 @@ static void disable_all_video_codecs_except_one(LinphoneCore *lc, const char *mi CU_ASSERT_PTR_NOT_NULL_FATAL(pt = linphone_core_find_payload_type(lc, mime, -1, -1)); linphone_core_enable_payload_type(lc, pt, TRUE); } +#endif static void call_failed_because_of_codecs(void) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); @@ -2667,7 +2669,13 @@ static void recording_call() { LinphoneCallParams *marieParams = linphone_core_create_default_call_parameters(marie->lc); LinphoneCallParams *paulineParams = linphone_core_create_default_call_parameters(pauline->lc); LinphoneCall *callInst = NULL; + +#ifdef VIDEO_ENABLED const char filename[] = "recording.mkv"; +#else + const char filename[] = "recording.wav"; +#endif + const char dirname[] = ".test"; char *filepath = NULL; @@ -2683,17 +2691,20 @@ static void recording_call() { CU_ASSERT_EQUAL(remove(filepath), 0); } + linphone_call_params_set_record_file(marieParams, filepath); + +#ifdef VIDEO_ENABLED linphone_core_enable_video_display(marie->lc, TRUE); linphone_core_enable_video_display(pauline->lc, FALSE); linphone_core_enable_video_capture(marie->lc, TRUE); linphone_core_enable_video_capture(pauline->lc, TRUE); linphone_call_params_enable_video(marieParams, TRUE); - linphone_call_params_set_record_file(marieParams, filepath); linphone_call_params_enable_video(paulineParams, TRUE); disable_all_video_codecs_except_one(marie->lc, "H264"); disable_all_video_codecs_except_one(pauline->lc, "H264"); +#endif CU_ASSERT_TRUE(call_with_params(marie, pauline, marieParams, paulineParams)); CU_ASSERT_PTR_NOT_NULL(callInst = linphone_core_get_current_call(marie->lc)); From 4525fe436c2be26dfd4ff3aa28baef762ecc3621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Tue, 5 Aug 2014 16:08:37 +0200 Subject: [PATCH 132/218] Do not set video session wether the H264 payload connot be found --- tester/call_tester.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tester/call_tester.c b/tester/call_tester.c index 510988a13..3269c6fe0 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2694,16 +2694,20 @@ static void recording_call() { linphone_call_params_set_record_file(marieParams, filepath); #ifdef VIDEO_ENABLED - linphone_core_enable_video_display(marie->lc, TRUE); - linphone_core_enable_video_display(pauline->lc, FALSE); - linphone_core_enable_video_capture(marie->lc, TRUE); - linphone_core_enable_video_capture(pauline->lc, TRUE); + if((linphone_core_find_payload_type(marie->lc, "H264", -1, -1) != NULL) && (linphone_core_find_payload_type(pauline->lc, "H264", -1, -1) != NULL)) { + linphone_core_enable_video_display(marie->lc, TRUE); + linphone_core_enable_video_display(pauline->lc, FALSE); + linphone_core_enable_video_capture(marie->lc, TRUE); + linphone_core_enable_video_capture(pauline->lc, TRUE); - linphone_call_params_enable_video(marieParams, TRUE); - linphone_call_params_enable_video(paulineParams, TRUE); + linphone_call_params_enable_video(marieParams, TRUE); + linphone_call_params_enable_video(paulineParams, TRUE); - disable_all_video_codecs_except_one(marie->lc, "H264"); - disable_all_video_codecs_except_one(pauline->lc, "H264"); + disable_all_video_codecs_except_one(marie->lc, "H264"); + disable_all_video_codecs_except_one(pauline->lc, "H264"); + } else { + ms_warning("call_recording(): the H264 payload has not been found. Only sound will be recorded"); + } #endif CU_ASSERT_TRUE(call_with_params(marie, pauline, marieParams, paulineParams)); From d798b9686918a80e2e984be675552f056c5e229b Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 5 Aug 2014 15:41:33 +0200 Subject: [PATCH 133/218] Update README.mingw file. --- README.mingw | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.mingw b/README.mingw index 79e8ce547..8b97ea1ef 100644 --- a/README.mingw +++ b/README.mingw @@ -4,20 +4,20 @@ Software to install Download lastest mingw-get-setup.exe from http://www.mingw.org Run mingw-get-setup.exe. In the package list, select and install: -* mingw32-developer-tool +* mingw-developer-toolkit * mingw32-base * mingw32-gcc-g++ +* mingw32-pthreads-w32 * msys-base +* msys-zip +* msys-unzip +* msys-wget For more information: http://www.mingw.org/wiki/Getting_Started In mingw shell (also refered as msys), run -mingw-get install msys-zip -mingw-get install msys-unzip -mingw-get install msys-wget - mkdir -p /opt/perl/bin cp /bin/perl /opt/perl/bin/. @@ -48,7 +48,7 @@ libintl.a libintl.la libintl.dll.a * Download and install Inno Setup Compiler (required only if you run 'make setup.exe'). Add it to your windows Path environment variable. -* Install msys-git from (http://code.google.com/p/msysgit/). During installation you are asked to make a choice about how line endings are treated by git. Choose "Checkout line endings as they are, commit as they are". THIS CHOICE IS VERY IMPORTANT. OTHERS BREAK AUTOMAKE. +* Install msys-git from (http://msysgit.github.io/). During installation you are asked to make a choice about how line endings are treated by git. Choose "Checkout line endings as they are, commit as they are". THIS CHOICE IS VERY IMPORTANT. OTHERS BREAK AUTOMAKE. General rules for compilation @@ -98,7 +98,8 @@ Building plugins (optional) *************************** This the example for msx264 (H264 plugin), the same applies for other linphone plugins. - $ cd mediastreamer2/plugins/msx264 + $ git clone git://git.linphone.org/msx264.git + $ cd msx264 $ ./autogen.sh $ PKG_CONFIG_PATH=/usr/lib/pkgconfig ./configure --prefix=/usr --enable-shared --disable-static #make a binary zip of this plugin From 62bb9e0b8c3f523e342c2310432487c7f0699cf3 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 5 Aug 2014 16:00:53 +0200 Subject: [PATCH 134/218] Updated ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index a1746a013..70a029a00 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit a1746a0139adcd656f29019c8b251d3947fe5580 +Subproject commit 70a029a00f2b7272d8c790557e71ff8b73004b85 From 549c6f024aa0e2bde2f6467a81763bcaadc3c36d Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 5 Aug 2014 16:18:21 +0200 Subject: [PATCH 135/218] Update oRTP submodule. --- oRTP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oRTP b/oRTP index 7aaba994d..fc8d8457e 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 7aaba994d4ec860a0773679dee9baa018b8801ee +Subproject commit fc8d8457eb630907eff50333ddf5243b448fe733 From b17cab312acdd0efe81b76fb8570bcb50fb00d47 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 5 Aug 2014 16:18:43 +0200 Subject: [PATCH 136/218] Output the logs from a single thread. --- coreapi/linphonecore.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 299bdc27f..a3d618e96 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -1331,6 +1331,7 @@ static void linphone_core_init(LinphoneCore * lc, const LinphoneCoreVTable *vtab linphone_core_set_state(lc,LinphoneGlobalStartup,"Starting up"); ortp_init(); + ortp_set_log_thread_id(ortp_thread_self()); lc->dyn_pt=96; lc->default_profile=rtp_profile_new("default profile"); linphone_core_assign_payload_type(lc,&payload_type_pcmu8000,0,NULL); @@ -2395,6 +2396,8 @@ void linphone_core_iterate(LinphoneCore *lc){ lp_config_sync(lc->config); } } + + ortp_logv_flush(); } /** @@ -6035,6 +6038,7 @@ static void linphone_core_uninit(LinphoneCore *lc) linphone_core_message_storage_close(lc); ms_exit(); linphone_core_set_state(lc,LinphoneGlobalOff,"Off"); + ortp_set_log_thread_id(0); } static void set_network_reachable(LinphoneCore* lc,bool_t isReachable, time_t curtime){ From 89dd6fbaa16f5ff6f6fc02a3c269c229489b9270 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 5 Aug 2014 16:35:27 +0200 Subject: [PATCH 137/218] Add Python wrapper for linphone_core_new_with_config(). --- tools/python/apixml2python.py | 4 +- .../python/apixml2python/handwritten.mustache | 44 ++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 6368908fd..af06a3371 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -80,7 +80,6 @@ blacklisted_functions = [ 'linphone_core_get_supported_video_sizes', 'linphone_core_get_video_codecs', 'linphone_core_get_video_policy', - 'linphone_core_new_with_config', 'linphone_core_payload_type_enabled', 'linphone_core_payload_type_is_vbr', 'linphone_core_publish', @@ -115,7 +114,8 @@ blacklisted_functions = [ 'lp_config_section_to_dict' ] hand_written_functions = [ - 'linphone_core_new' + 'linphone_core_new', + 'linphone_core_new_with_config' ] def generate(apixmlfile): diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index 6971b7cb6..5f02432ee 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -124,7 +124,7 @@ static PyObject * pylinphone_Core_class_method_new(PyObject *cls, PyObject *args {{{event_vtable_reference}}} {{/events}} - pylinphone_trace(1, "[PYLINPHONE] >>> %s(\"%s\", \"%s\")", __FUNCTION__, _config_path, _factory_config_path); + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p, \"%s\", \"%s\")", __FUNCTION__, _vtable_dict, _config_path, _factory_config_path); cresult = linphone_core_new(&_vtable, _config_path, _factory_config_path, self); self->native_ptr = cresult; @@ -134,3 +134,45 @@ static PyObject * pylinphone_Core_class_method_new(PyObject *cls, PyObject *args Py_DECREF(self); return pyret; } + +static PyObject * pylinphone_Core_class_method_new_with_config(PyObject *cls, PyObject *args) { + LinphoneCore * cresult; + pylinphone_CoreObject *self; + PyObject * pyret; + LinphoneCoreVTable _vtable = { 0 }; + PyObject * _vtable_dict; + PyObject * _config; + LpConfig * _config_native_ptr; + + if (!PyArg_ParseTuple(args, "OO", &_vtable_dict, &_config)) { + return NULL; + } + if (!PyDict_Check(_vtable_dict)) { + PyErr_SetString(PyExc_TypeError, "The first argument must be a dictionary"); + return NULL; + } + + if ((_config_native_ptr = pylinphone_LpConfig_get_native_ptr(_config)) == NULL) { + return NULL; + } + + self = (pylinphone_CoreObject *)PyObject_New(pylinphone_CoreObject, &pylinphone_CoreType); + if (self == NULL) { + return NULL; + } + Py_INCREF(_vtable_dict); + self->vtable_dict = _vtable_dict; +{{#events}} + {{{event_vtable_reference}}} +{{/events}} + + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p [%p])", __FUNCTION__, _config, _config_native_ptr); + cresult = linphone_core_new_with_config(&_vtable, _config_native_ptr, self); + self->native_ptr = cresult; + + pyret = Py_BuildValue("O", self); + + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> %p", __FUNCTION__, pyret); + Py_DECREF(self); + return pyret; +} From 94df8abd09962394e5aff5503a71f234a2a6034e Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 5 Aug 2014 17:35:45 +0200 Subject: [PATCH 138/218] Add some commands to handle call in the Python example program. --- tools/python/apixml2python.py | 4 +- tools/python/linphone-daemon.py | 185 ++++++++++++++++++++++++++++++-- 2 files changed, 178 insertions(+), 11 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index af06a3371..5459c1ac8 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -83,9 +83,9 @@ blacklisted_functions = [ 'linphone_core_payload_type_enabled', 'linphone_core_payload_type_is_vbr', 'linphone_core_publish', - 'linphone_core_set_log_file', + 'linphone_core_set_log_file', # There is no use to wrap this function 'linphone_core_set_log_handler', # Hand-written but put directly in the linphone module - 'linphone_core_set_log_level', + 'linphone_core_set_log_level', # There is no use to wrap this function 'linphone_core_set_payload_type_bitrate', 'linphone_core_set_preferred_video_size', 'linphone_core_set_video_policy', diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py index fbde38a79..69c7b8d89 100644 --- a/tools/python/linphone-daemon.py +++ b/tools/python/linphone-daemon.py @@ -107,7 +107,7 @@ Example {idx}: class CallCommand(Command): """Place a call.""" def __init__(self): - Command.__init__(self, "call", "call ") + Command.__init__(self, "call", "call ") self.add_example(CommandExample( "call daemon-test@sip.linphone.org", "Status: Ok\n\nId: 1" @@ -128,6 +128,128 @@ class CallCommand(Command): else: app.send_response(Response(Response.Error, "Missing parameter.")) +class CallPauseCommand(Command): + """Pause a call (pause current if no id is specified).""" + def __init__(self): + Command.__init__(self, "call-pause", "call-pause [call-id]") + self.add_example(CommandExample( + "call-pause 1", + "Status: Ok\n\nCall was paused" + )) + self.add_example(CommandExample( + "call-pause 2", + "Status: Error\nReason: No call with such id." + )) + self.add_example(CommandExample( + "call-pause", + "Status: Error\nReason: No current call available." + )) + + def exec_command(self, app, args): + current = False + if len(args) >= 1: + call = app.find_call(args[0]) + if call is None: + app.send_response(Response(Response.Error, "No call with such id.")) + return + else: + current = True + call = app.core.current_call + if call is None: + app.send_response(Response(Response.Error, "No current call available.")) + return + if app.core.pause_call(call) == 0: + msg = "Call was paused." + if current: + msg = "Current call was paused." + app.send_response(Response(Response.Ok, msg)) + else: + app.send_response(Response(Response.Error, "Error pausing call.")) + +class CallResumeCommand(Command): + """Resume a call (resume current if no id is specified).""" + def __init__(self): + Command.__init__(self, "call-resume", "call-resume [call-id]") + self.add_example(CommandExample( + "call-resume 1", + "Status: Ok\n\nCall was resumed" + )) + self.add_example(CommandExample( + "call-resume 2", + "Status: Error\nReason: No call with such id." + )) + self.add_example(CommandExample( + "call-resume", + "Status: Error\nReason: No current call available." + )) + + def exec_command(self, app, args): + current = False + if len(args) >= 1: + call = app.find_call(args[0]) + if call is None: + app.send_response(Response(Response.Error, "No call with such id.")) + return + else: + current = True + call = app.core.current_call + if call is None: + app.send_response(Response(Response.Error, "No current call available.")) + return + if app.core.resume_call(call) == 0: + msg = "Call was resumed." + if current: + msg = "Current call was resumed." + app.send_response(Response(Response.Ok, msg)) + else: + app.send_response(Response(Response.Error, "Error resuming call.")) + +class CallStatusCommand(Command): + """Return status of the specified call or of the current call if no id is given.""" + def __init__(self): + Command.__init__(self, "call-status", "call-status [call-id]") + self.add_example(CommandExample( + "call-status 1", + "Status: Ok\n\nState: LinphoneCallStreamsRunning\nFrom: \nDirection: out\nDuration: 6" + )) + self.add_example(CommandExample( + "call-status 2", + "Status: Error\nReason: No call with such id." + )) + self.add_example(CommandExample( + "call-status", + "Status: Error\nReason: No current call available." + )) + + def exec_command(self, app, args): + if len(args) >= 1: + call = app.find_call(args[0]) + if call is None: + app.send_response(Response(Response.Error, "No call with such id.")) + return + else: + call = app.core.current_call + if call is None: + app.send_response(Response(Response.Error, "No current call available.")) + return + state = call.state + body = "State: {state}".format(state=linphone.CallState.string(state)) + if state == linphone.CallState.CallOutgoingInit \ + or state == linphone.CallState.CallOutgoingProgress \ + or state == linphone.CallState.CallOutgoingRinging \ + or state == linphone.CallState.CallPaused \ + or state == linphone.CallState.CallStreamsRunning \ + or state == linphone.CallState.CallConnected \ + or state == linphone.CallState.CallIncomingReceived: + body += "\nFrom: {address}".format(address=call.remote_address.as_string()) + if state == linphone.CallState.CallStreamsRunning \ + or state == linphone.CallState.CallConnected: + direction_str = 'in' + if call.dir == linphone.CallDir.CallOutgoing: + direction_str = 'out' + body += "\nDirection: {direction}\nDuration: {duration}".format(direction=direction_str, duration=call.duration) + app.send_response(Response(Response.Ok, body)) + class HelpCommand(Command): """Show help notice, if command is unspecified or inexistent show all commands.""" def __init__(self): @@ -232,19 +354,59 @@ class RegisterStatusCommand(Command): else: app.send_response(RegisterStatusResponse().append(id, proxy_cfg)) +class TerminateCommand(Command): + """Terminate the specified call or the current call if no id is given.""" + def __init__(self): + Command.__init__(self, "terminate", "terminate [call id]") + self.add_example(CommandExample( + "terminate 2", + "Status: Error\nReason: No call with such id." + )) + self.add_example(CommandExample( + "terminate 1", + "Status: Ok\n" + )) + self.add_example(CommandExample( + "terminate", + "Status: Ok\n" + )) + self.add_example(CommandExample( + "terminate", + "Status: Error\nReason: No active call." + )) + + def exec_command(self, app, args): + if len(args) >= 1: + call = app.find_call(args[0]) + if call is None: + app.send_response(Response(Response.Error, "No call with such id.")) + return + else: + call = app.core.current_call + if call is None: + app.send_response(Response(Response.Error, "No active call.")) + return + app.core.terminate_call(call) + app.send_response(Response(Response.Ok)) + + class Daemon: def __init__(self): self._quit = False self._next_proxy_id = 1 self.proxy_ids_map = {} self._next_call_id = 1 - self._call_ids_map = {} + self.call_ids_map = {} self.commands = [ CallCommand(), + CallPauseCommand(), + CallResumeCommand(), + CallStatusCommand(), HelpCommand(), QuitCommand(), RegisterCommand(), - RegisterStatusCommand() + RegisterStatusCommand(), + TerminateCommand() ] def send_response(self, response): @@ -284,8 +446,8 @@ class Daemon: callbacks = { 'global_state_changed':global_state_changed, - 'registration_state_changed':registration_state_changed, - 'call_state_changed':call_state_changed + 'registration_state_changed':registration_state_changed#, + #'call_state_changed':call_state_changed } # Create a linphone core and iterate every 20 ms @@ -301,8 +463,8 @@ class Daemon: def update_proxy_id(self, proxy): id = self._next_proxy_id - self.proxy_ids_map[id] = proxy - self._next_proxy_id = self._next_proxy_id + 1 + self.proxy_ids_map[str(id)] = proxy + self._next_proxy_id += 1 return id def find_proxy(self, id): @@ -312,10 +474,15 @@ class Daemon: def update_call_id(self, call): id = self._next_call_id - self._call_ids_map[id] = call - self._next_call_id = self._next_call_id + 1 + self.call_ids_map[str(id)] = call + self._next_call_id += 1 return id + def find_call(self, id): + if self.call_ids_map.has_key(id): + return self.call_ids_map[id] + return None + def setup_log_colors(): logging.addLevelName(logging.DEBUG, "\033[1;37m%s\033[1;0m" % logging.getLevelName(logging.DEBUG)) logging.addLevelName(logging.INFO, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.INFO)) From 44fa58fe8c4122f81a3979cdb991990ff7c4c769 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 5 Aug 2014 17:49:02 +0200 Subject: [PATCH 139/218] Restore commented code. --- tools/python/linphone-daemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py index 69c7b8d89..17ad85373 100644 --- a/tools/python/linphone-daemon.py +++ b/tools/python/linphone-daemon.py @@ -446,8 +446,8 @@ class Daemon: callbacks = { 'global_state_changed':global_state_changed, - 'registration_state_changed':registration_state_changed#, - #'call_state_changed':call_state_changed + 'registration_state_changed':registration_state_changed, + 'call_state_changed':call_state_changed } # Create a linphone core and iterate every 20 ms From 2b045325181621d9ac6acba50dc10eb2a0cbc626 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 6 Aug 2014 17:26:50 +0200 Subject: [PATCH 140/218] fix compilation error and fix bug when updating preview in linphone_core_set_preferred_video_size() --- coreapi/linphonecore.c | 6 ++++-- coreapi/upnp.c | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index e2c21e90a..1775580a8 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -900,7 +900,7 @@ static int codec_compare(const PayloadType *a, const PayloadType *b){ rb=find_codec_rank(b->mime_type,b->clock_rate); if (ra>rb) return 1; if (ravideo_conf.preview_vsize; + if (oldvsize.width==0){ oldvsize=lc->video_conf.vsize; - update_preview_size(lc,oldvsize,vsize); } lc->video_conf.vsize=vsize; + update_preview_size(lc,oldvsize,vsize); + if (linphone_core_ready(lc)) lp_config_set_string(lc->config,"video","size",video_size_get_name(vsize)); } diff --git a/coreapi/upnp.c b/coreapi/upnp.c index 5fcbbe065..562078570 100644 --- a/coreapi/upnp.c +++ b/coreapi/upnp.c @@ -607,7 +607,7 @@ int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBind mapping.remote_port = port->external_port; mapping.remote_host = ""; snprintf(description, 128, "%s %s at %s:%d", - PACKAGE_NAME, + "Linphone", (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP", port->local_addr, port->local_port); mapping.description = description; From dd07464872205ebb908e5864d203e365ac76bf01 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 6 Aug 2014 18:28:29 +0200 Subject: [PATCH 141/218] fix windows build --- tester/call_tester.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tester/call_tester.c b/tester/call_tester.c index 3269c6fe0..09ae65ded 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2669,7 +2669,7 @@ static void recording_call() { LinphoneCallParams *marieParams = linphone_core_create_default_call_parameters(marie->lc); LinphoneCallParams *paulineParams = linphone_core_create_default_call_parameters(pauline->lc); LinphoneCall *callInst = NULL; - + int dummy=0; #ifdef VIDEO_ENABLED const char filename[] = "recording.mkv"; #else @@ -2684,7 +2684,11 @@ static void recording_call() { strcat(filepath, "/"); strcat(filepath, filename); if(access(dirname, F_OK) != 0) { +#ifdef WIN32 + CU_ASSERT_EQUAL(mkdir(dirname),0); +#else CU_ASSERT_EQUAL(mkdir(dirname, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH), 0); +#endif } CU_ASSERT_EQUAL(access(dirname, W_OK), 0); if(access(filepath, F_OK) == 0) { @@ -2714,7 +2718,7 @@ static void recording_call() { CU_ASSERT_PTR_NOT_NULL(callInst = linphone_core_get_current_call(marie->lc)); linphone_call_start_recording(callInst); - sleep(20); + wait_for_until(marie->lc,pauline->lc,&dummy,1,10000); linphone_call_stop_recording(callInst); CU_ASSERT_EQUAL(access(filepath, F_OK), 0); From ad65819eb14b3177646e60ae6bbcb649ef84e21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Tue, 5 Aug 2014 17:19:52 +0200 Subject: [PATCH 142/218] Make the "Call recording" tester to save the recording file in another place for Android --- tester/call_tester.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tester/call_tester.c b/tester/call_tester.c index 09ae65ded..31129e4b9 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2670,15 +2670,20 @@ static void recording_call() { LinphoneCallParams *paulineParams = linphone_core_create_default_call_parameters(pauline->lc); LinphoneCall *callInst = NULL; int dummy=0; + char *filepath = NULL; + +#ifdef ANDROID + const char dirname[] = "/data/data/org.linphone.tester/files/.test"; +#else + const char dirname[] = ".test"; +#endif + #ifdef VIDEO_ENABLED const char filename[] = "recording.mkv"; #else const char filename[] = "recording.wav"; #endif - const char dirname[] = ".test"; - char *filepath = NULL; - filepath = ms_new0(char, strlen(dirname) + strlen(filename) + 2); strcpy(filepath, dirname); strcat(filepath, "/"); From 1168afe9efd1fd49240a42c44804fc36a1880897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Wed, 6 Aug 2014 02:41:23 +0200 Subject: [PATCH 143/218] Update mediastreamer submodule --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 932964c57..8dae6e326 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 932964c57f0fc8d7690334dae59ab70b455bf466 +Subproject commit 8dae6e326a4bb933d0563af744c24d161b3148ef From 574ed8e52bad94235e6531f9e3deefed0febafa1 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 6 Aug 2014 10:53:05 +0200 Subject: [PATCH 144/218] Add API to activate the serialization of logs. --- coreapi/linphonecore.c | 19 +++++++++++++++---- coreapi/linphonecore.h | 8 ++++++++ oRTP | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index a3d618e96..e2c21e90a 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -64,6 +64,7 @@ static const char *liblinphone_version= LIBLINPHONE_VERSION #endif ; +static bool_t liblinphone_serialize_logs = FALSE; static void set_network_reachable(LinphoneCore* lc,bool_t isReachable, time_t curtime); static void linphone_core_run_hooks(LinphoneCore *lc); static void linphone_core_free_hooks(LinphoneCore *lc); @@ -480,11 +481,15 @@ void linphone_core_enable_logs_with_cb(OrtpLogFunc logfunc){ * @ingroup misc * @deprecated Use #linphone_core_set_log_level instead. **/ -void linphone_core_disable_logs(){ +void linphone_core_disable_logs(void){ ortp_set_log_level_mask(ORTP_ERROR|ORTP_FATAL); sal_disable_logs(); } +void linphone_core_serialize_logs(void) { + liblinphone_serialize_logs = TRUE; +} + static void net_config_read (LinphoneCore *lc) { @@ -1331,7 +1336,9 @@ static void linphone_core_init(LinphoneCore * lc, const LinphoneCoreVTable *vtab linphone_core_set_state(lc,LinphoneGlobalStartup,"Starting up"); ortp_init(); - ortp_set_log_thread_id(ortp_thread_self()); + if (liblinphone_serialize_logs == TRUE) { + ortp_set_log_thread_id(ortp_thread_self()); + } lc->dyn_pt=96; lc->default_profile=rtp_profile_new("default profile"); linphone_core_assign_payload_type(lc,&payload_type_pcmu8000,0,NULL); @@ -2397,7 +2404,9 @@ void linphone_core_iterate(LinphoneCore *lc){ } } - ortp_logv_flush(); + if (liblinphone_serialize_logs == TRUE) { + ortp_logv_flush(); + } } /** @@ -6038,7 +6047,9 @@ static void linphone_core_uninit(LinphoneCore *lc) linphone_core_message_storage_close(lc); ms_exit(); linphone_core_set_state(lc,LinphoneGlobalOff,"Off"); - ortp_set_log_thread_id(0); + if (liblinphone_serialize_logs == TRUE) { + ortp_set_log_thread_id(0); + } } static void set_network_reachable(LinphoneCore* lc,bool_t isReachable, time_t curtime){ diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 5e842ac59..9fdb864df 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1674,6 +1674,14 @@ LINPHONE_PUBLIC void linphone_core_set_log_level(OrtpLogLevel loglevel); LINPHONE_PUBLIC void linphone_core_enable_logs(FILE *file); LINPHONE_PUBLIC void linphone_core_enable_logs_with_cb(OrtpLogFunc logfunc); LINPHONE_PUBLIC void linphone_core_disable_logs(void); + +/** + * Enable logs serialization (output logs from either the thread that creates the linphone core or the thread that calls linphone_core_iterate()). + * Must be called before creating the linphone core. + * @ingroup misc + */ +LINPHONE_PUBLIC void linphone_core_serialize_logs(void); + LINPHONE_PUBLIC const char *linphone_core_get_version(void); LINPHONE_PUBLIC const char *linphone_core_get_user_agent(LinphoneCore *lc); /** diff --git a/oRTP b/oRTP index fc8d8457e..dfb505d71 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit fc8d8457eb630907eff50333ddf5243b448fe733 +Subproject commit dfb505d7198dc8be59919c8c6d68add302a98fd3 From 31c895b5217cf643dff8d91aaff5bb5b3cbff8f2 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 6 Aug 2014 11:22:46 +0200 Subject: [PATCH 145/218] really check that we are in a linphone git repo before taking the "git describe". --- console/linphonec.c | 2 +- coreapi/Makefile.am | 7 +++---- mediastreamer2 | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/console/linphonec.c b/console/linphonec.c index b8072e325..ff16e1387 100644 --- a/console/linphonec.c +++ b/console/linphonec.c @@ -747,7 +747,7 @@ linphonec_init(int argc, char **argv) linphone_core_enable_video_display(linphonec, display_enabled); if (display_enabled && window_id != 0) { - printf ("Setting window_id: 0x%x\n", window_id); + printf("Setting window_id: 0x%x\n", window_id); linphone_core_set_native_video_window_id(linphonec,window_id); } diff --git a/coreapi/Makefile.am b/coreapi/Makefile.am index 03d42f08d..5b4460c79 100644 --- a/coreapi/Makefile.am +++ b/coreapi/Makefile.am @@ -8,9 +8,8 @@ GITREVISION=`cd $(top_srcdir) && git rev-parse HEAD` ## This command is used to check if the sources are cloned in a git repo. ## We can't only depend on the presence of the .git/ directory anymore, ## because of gits submodule handling. -## We now simply issue a git status and if there's an error, the $(GITSTATUS) -## variable won't contain "GITOK" -GITSTATUS=`cd $(top_srcdir) && git status > /dev/null && echo GITOK` +## We now simply issue a git log on configure.ac and if the output is empty (error or file not tracked), then we are not in git. +GITLOG=$(shell git log -1 $(top_srcdir)/configure.ac) ECHO=/bin/echo @@ -168,7 +167,7 @@ AM_CXXFLAGS=$(AM_CFLAGS) #the PACKAGE_VERSION given in configure.ac make_gitversion_h: - if test "$(GITSTATUS)" == "GITOK" ; then \ + if test -n "$(GITLOG)" ; then \ if test "$(GITDESCRIBE)" != "" ; then \ if test "$(GIT_TAG)" != "$(PACKAGE_VERSION)" ; then \ echo "*** PACKAGE_VERSION and git tag differ. Please put them identical."; \ diff --git a/mediastreamer2 b/mediastreamer2 index 70a029a00..932964c57 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 70a029a00f2b7272d8c790557e71ff8b73004b85 +Subproject commit 932964c57f0fc8d7690334dae59ab70b455bf466 From 45bc8f1fa7e7b8a5337f10c9a3a9b7b4582a37bc Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 6 Aug 2014 12:05:44 +0200 Subject: [PATCH 146/218] Generate documentation when compiling with CMake. --- coreapi/CMakeLists.txt | 2 ++ coreapi/help/CMakeLists.txt | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 coreapi/help/CMakeLists.txt diff --git a/coreapi/CMakeLists.txt b/coreapi/CMakeLists.txt index ff39dfb41..2be82be73 100644 --- a/coreapi/CMakeLists.txt +++ b/coreapi/CMakeLists.txt @@ -145,3 +145,5 @@ install(FILES ${HEADER_FILES} DESTINATION include/linphone PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ ) + +add_subdirectory(help) diff --git a/coreapi/help/CMakeLists.txt b/coreapi/help/CMakeLists.txt new file mode 100644 index 000000000..8f7b9b125 --- /dev/null +++ b/coreapi/help/CMakeLists.txt @@ -0,0 +1,37 @@ +############################################################################ +# CMakeLists.txt +# Copyright (C) 2014 Belledonne Communications, Grenoble France +# +############################################################################ +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################ + +find_package(Doxygen) + +if(DOXYGEN_FOUND) + if(DOXYGEN_DOT_FOUND) + set(top_srcdir ${CMAKE_SOURCE_DIR}) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + add_custom_target(doc ALL + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doc/html" "${CMAKE_CURRENT_BINARY_DIR}/doc/xml" + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/doc/linphone-${LINPHONE_VERSION}") + else() + message(WARNING "The dot program is needed to generate the linphone documentation. You can get it from http://www.graphviz.org/.") + endif() +endif() From fe76b2f4c09bdd23c6fb710cf67a6c0497e2bf52 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 6 Aug 2014 14:50:27 +0200 Subject: [PATCH 147/218] Add an option to define the output file when generating the Python wrapper code. --- tools/python/apixml2python.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 5459c1ac8..ee31c09c3 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -118,11 +118,10 @@ hand_written_functions = [ 'linphone_core_new_with_config' ] -def generate(apixmlfile): +def generate(apixmlfile, f): tree = ET.parse(apixmlfile) renderer = pystache.Renderer() m = LinphoneModule(tree, blacklisted_classes, blacklisted_events, blacklisted_functions, hand_written_functions) - f = open("linphone.c", "w") os.chdir('apixml2python') f.write(renderer.render(m)) @@ -131,9 +130,12 @@ def main(argv = None): if argv is None: argv = sys.argv argparser = argparse.ArgumentParser(description="Generate a Python wrapper of the Linphone API.") + argparser.add_argument('-o', '--outputfile', metavar='outputfile', type=argparse.FileType('w'), help="Output C file containing the code of the Python wrapper.") argparser.add_argument('apixmlfile', help="XML file of the Linphone API generated by genapixml.py.") args = argparser.parse_args() - generate(args.apixmlfile) + if args.outputfile == None: + args.outputfile = open('linphone.c', 'w') + generate(args.apixmlfile, args.outputfile) if __name__ == "__main__": sys.exit(main()) From de9538cf8dda254cff3cf00f5cd78d53f43d039f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 6 Aug 2014 17:38:27 +0200 Subject: [PATCH 148/218] Take the XML directory instead of all the XML files as parameter for the genapixml.py script. --- tools/genapixml.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/genapixml.py b/tools/genapixml.py index 11a3cf072..388870e2c 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -17,6 +17,7 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import argparse +import os import string import sys import xml.etree.ElementTree as ET @@ -548,6 +549,10 @@ class Project: self.__findCFunction(tree) self.__discoverClasses() + def initFromDir(self, xmldir): + files = [ os.path.join(xmldir, f) for f in os.listdir(xmldir) if (os.path.isfile(os.path.join(xmldir, f)) and f.endswith('.xml')) ] + self.initFromFiles(files) + def check(self): for c in self.classes: for name, p in c.properties.iteritems(): @@ -700,7 +705,7 @@ def main(argv = None): argparser.add_argument('-o', '--outputfile', metavar='outputfile', type=argparse.FileType('w'), help="Output XML file describing the Linphone API.") argparser.add_argument('--verbose', help="Increase output verbosity", action='store_true') argparser.add_argument('--pretty', help="XML pretty print", action='store_true') - argparser.add_argument('xmlfile', metavar='xmlfile', type=argparse.FileType('r'), nargs='+', help="XML file generated by doxygen.") + argparser.add_argument('xmldir', help="XML directory generated by doxygen.") args = argparser.parse_args() if args.outputfile == None: args.outputfile = open('api.xml', 'w') @@ -709,7 +714,7 @@ def main(argv = None): project.verbose = True if args.pretty: project.prettyPrint = True - project.initFromFiles(args.xmlfile) + project.initFromDir(args.xmldir) project.check() gen = Generator(args.outputfile) gen.generate(project) From a75b2d306a24747185e1b3bc7328043d4dec8cfb Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 6 Aug 2014 17:39:07 +0200 Subject: [PATCH 149/218] Add trick to ensure line endings are correct on Windows in the apixml2python.py script. --- tools/python/apixml2python.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index ee31c09c3..6dc59c95b 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -118,12 +118,20 @@ hand_written_functions = [ 'linphone_core_new_with_config' ] -def generate(apixmlfile, f): +def generate(apixmlfile, outputfile): tree = ET.parse(apixmlfile) renderer = pystache.Renderer() m = LinphoneModule(tree, blacklisted_classes, blacklisted_events, blacklisted_functions, hand_written_functions) os.chdir('apixml2python') + tmpfilename = outputfile.name + '.tmp' + f = open(tmpfilename, 'w') f.write(renderer.render(m)) + f.close() + f = open(tmpfilename, 'rU') + for line in f: + outputfile.write(line) + f.close() + os.unlink(tmpfilename) def main(argv = None): From 16fa9f86937718003affc671103376049acf994a Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 6 Aug 2014 18:11:31 +0200 Subject: [PATCH 150/218] Add CMake script to find linphone library. --- CMakeLists.txt | 6 ++++ FindLinphone.cmake | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 FindLinphone.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 94f49e8f0..7440ba633 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,3 +90,9 @@ add_definitions(-DHAVE_CONFIG_H) add_subdirectory(coreapi) add_subdirectory(share) + + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/FindLinphone.cmake + DESTINATION share/cmake/Modules + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ +) diff --git a/FindLinphone.cmake b/FindLinphone.cmake new file mode 100644 index 000000000..fdb582c72 --- /dev/null +++ b/FindLinphone.cmake @@ -0,0 +1,69 @@ +############################################################################ +# FindLinphone.txt +# Copyright (C) 2014 Belledonne Communications, Grenoble France +# +############################################################################ +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################ +# +# - Find the linphone include file and library +# +# LINPHONE_FOUND - system has linphone +# LINPHONE_INCLUDE_DIRS - the linphone include directory +# LINPHONE_LIBRARIES - The libraries needed to use linphone +# LINPHONE_CPPFLAGS - The compilation flags needed to use linphone + +find_package(ORTP REQUIRED) +find_package(MS2 REQUIRED) +find_package(XML2 REQUIRED) +find_package(BelleSIP REQUIRED) + +set(_LINPHONEROOT_PATHS + ${WITH_LINPHONE} + ${CMAKE_INSTALL_PREFIX} +) + +find_path(LINPHONE_INCLUDE_DIRS + NAMES linphone/linphonecore.h + HINTS _LINPHONE_ROOT_PATHS + PATH_SUFFIXES include +) + +if(LINPHONE_INCLUDE_DIRS) + set(HAVE_LINPHONE_LINPHONECORE_H 1) +endif() + +find_library(LINPHONE_LIBRARIES + NAMES linphone + HINTS ${_LINPHONE_ROOT_PATHS} + PATH_SUFFIXES bin lib +) + +list(APPEND LINPHONE_INCLUDE_DIRS ${ORTP_INCLUDE_DIRS} ${MS2_INCLUDE_DIRS} ${XML2_INCLUDE_DIRS} ${BELLESIP_INCLUDE_DIRS}) +list(APPEND LINPHONE_LIBRARIES ${ORTP_LIBRARIES} ${MS2_LIBRARIES} ${XML2_LIBRARIES} ${BELLESIP_LIBRARIES}) + +list(REMOVE_DUPLICATES LINPHONE_INCLUDE_DIRS) +list(REMOVE_DUPLICATES LINPHONE_LIBRARIES) +set(LINPHONE_CPPFLAGS ${MS2_CPPFLAGS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Linphone + DEFAULT_MSG + LINPHONE_INCLUDE_DIRS LINPHONE_LIBRARIES LINPHONE_CPPFLAGS +) + +mark_as_advanced(LINPHONE_INCLUDE_DIRS LINPHONE_LIBRARIES LINPHONE_CPPFLAGS) From ddf89c8241539301378d4a48337ac6a25654fefb Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 7 Aug 2014 14:09:01 +0200 Subject: [PATCH 151/218] The linphone Python module is put in a linphone package. --- tools/python/linphone-daemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py index 17ad85373..ab27bcb55 100644 --- a/tools/python/linphone-daemon.py +++ b/tools/python/linphone-daemon.py @@ -1,9 +1,9 @@ import argparse -import linphone import logging import sys import threading import time +from linphone import linphone class StoppableThread(threading.Thread): From 925733429580739468db722a715ad2c35686fc17 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 7 Aug 2014 15:09:10 +0200 Subject: [PATCH 152/218] Correctly define package data directories when compiling with CMake. --- CMakeLists.txt | 8 ++++++-- config.h.cmake | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7440ba633..470df4585 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,10 +79,14 @@ endif() if(ENABLE_RELATIVE_PREFIX) - set(LINPHONE_PLUGINS_DIR "./lib/liblinphone/plugins") + set(LINPHONE_DATA_DIR ".") else() - set(LINPHONE_PLUGINS_DIR "${CMAKE_INSTALL_PREFIX}/lib/liblinphone/plugins") + set(LINPHONE_DATA_DIR "${CMAKE_INSTALL_PREFIX}") endif() +set(LINPHONE_PLUGINS_DIR "${LINPHONE_DATA_DIR}/lib/liblinphone/plugins") +set(PACKAGE_LOCALE_DIR "${LINPHONE_DATA_DIR}/share/locale") +set(PACKAGE_DATA_DIR "${LINPHONE_DATA_DIR}/share") +set(PACKAGE_SOUND_DIR "${LINPHONE_DATA_DIR}/share/sounds/linphone") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/config.h PROPERTIES GENERATED ON) add_definitions(-DHAVE_CONFIG_H) diff --git a/config.h.cmake b/config.h.cmake index 6f8db1a86..aa49b3b98 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -25,4 +25,7 @@ #define LINPHONE_MICRO_VERSION ${LINPHONE_MICRO_VERSION} #define LINPHONE_VERSION "${LINPHONE_VERSION}" -#cmakedefine LINPHONE_PLUGINS_DIR "${LINPHONE_PLUGINS_DIR}" +#define LINPHONE_PLUGINS_DIR "${LINPHONE_PLUGINS_DIR}" +#define PACKAGE_LOCALE_DIR "${PACKAGE_LOCALE_DIR}" +#define PACKAGE_DATA_DIR "${PACKAGE_DATA_DIR}" +#define PACKAGE_SOUND_DIR "${PACKAGE_SOUND_DIR}" From 367a02b95a9ff49ac4197045ebc75deae2501004 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 7 Aug 2014 16:10:08 +0200 Subject: [PATCH 153/218] Correct handling of WINAPI families. --- coreapi/linphonecore.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index e2c21e90a..8da07887e 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -76,10 +76,10 @@ static void linphone_core_free_hooks(LinphoneCore *lc); const char *linphone_core_get_nat_address_resolved(LinphoneCore *lc); static void toggle_video_preview(LinphoneCore *lc, bool_t val); -#ifdef WINAPI_FAMILY_PHONE_APP -#define SOUNDS_PREFIX "Assets/Sounds/" -#else +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) #define SOUNDS_PREFIX +#else +#define SOUNDS_PREFIX "Assets/Sounds/" #endif /* relative path where is stored local ring*/ #define LOCAL_RING SOUNDS_PREFIX "rings/oldphone.wav" From 62efa148c25e3055756e9bc0665c363eca8c6886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Wed, 6 Aug 2014 10:53:22 +0200 Subject: [PATCH 154/218] Rename recording_call() into call_recording() Save test results into sdcard --- build/android/liblinphone_tester.mk | 4 ++++ mediastreamer2 | 2 +- oRTP | 2 +- tester/call_tester.c | 7 ++++--- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/build/android/liblinphone_tester.mk b/build/android/liblinphone_tester.mk index e9a2c8718..cada1aba4 100644 --- a/build/android/liblinphone_tester.mk +++ b/build/android/liblinphone_tester.mk @@ -32,6 +32,10 @@ LOCAL_C_INCLUDES = $(common_C_INCLUDES) LOCAL_CFLAGS = -DIN_LINPHONE LOCAL_LDLIBS := -llog +ifeq ($(BUILD_MATROSKA), 1) +LOCAL_CFLAGS += -DHAVE_MATROSKA +endif + LOCAL_SHARED_LIBRARIES := cunit liblinphone include $(BUILD_SHARED_LIBRARY) diff --git a/mediastreamer2 b/mediastreamer2 index 8dae6e326..4064390b4 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 8dae6e326a4bb933d0563af744c24d161b3148ef +Subproject commit 4064390b48c957dcf3077b0e30e0c082395bbf05 diff --git a/oRTP b/oRTP index dfb505d71..fc8d8457e 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit dfb505d7198dc8be59919c8c6d68add302a98fd3 +Subproject commit fc8d8457eb630907eff50333ddf5243b448fe733 diff --git a/tester/call_tester.c b/tester/call_tester.c index 31129e4b9..2b72ce088 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2663,7 +2663,7 @@ static void savpf_to_savpf_call(void) { profile_call(TRUE, TRUE, TRUE, TRUE, "RTP/SAVPF"); } -static void recording_call() { +static void call_recording() { LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_rc"); LinphoneCallParams *marieParams = linphone_core_create_default_call_parameters(marie->lc); @@ -2673,7 +2673,7 @@ static void recording_call() { char *filepath = NULL; #ifdef ANDROID - const char dirname[] = "/data/data/org.linphone.tester/files/.test"; + const char dirname[] = "/sdcard/Movies/liblinphone_tester"; #else const char dirname[] = ".test"; #endif @@ -2722,6 +2722,7 @@ static void recording_call() { CU_ASSERT_TRUE(call_with_params(marie, pauline, marieParams, paulineParams)); CU_ASSERT_PTR_NOT_NULL(callInst = linphone_core_get_current_call(marie->lc)); + ms_message("call_recording(): the call will be recorded into %s", filepath); linphone_call_start_recording(callInst); wait_for_until(marie->lc,pauline->lc,&dummy,1,10000); linphone_call_stop_recording(callInst); @@ -2823,7 +2824,7 @@ test_t call_tests[] = { { "SAVPF to AVPF call", savpf_to_avpf_call }, { "SAVPF to SAVP call", savpf_to_savp_call }, { "SAVPF to SAVPF call", savpf_to_savpf_call }, - { "Call recording", recording_call } + { "Call recording", call_recording } }; test_suite_t call_test_suite = { From 211b56c29fba45ca95c8c15ff657e1ffdfb6a767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Wed, 6 Aug 2014 13:43:21 +0200 Subject: [PATCH 155/218] Add specific config files for the "call recording" tester --- tester/call_tester.c | 10 ++----- tester/rcfiles/marie_h264_rc | 55 ++++++++++++++++++++++++++++++++++ tester/rcfiles/pauline_h264_rc | 52 ++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 tester/rcfiles/marie_h264_rc create mode 100644 tester/rcfiles/pauline_h264_rc diff --git a/tester/call_tester.c b/tester/call_tester.c index 2b72ce088..51c57252b 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2664,8 +2664,8 @@ static void savpf_to_savpf_call(void) { } static void call_recording() { - LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); - LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_rc"); + LinphoneCoreManager *marie = linphone_core_manager_new("marie_h264_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_h264_rc"); LinphoneCallParams *marieParams = linphone_core_create_default_call_parameters(marie->lc); LinphoneCallParams *paulineParams = linphone_core_create_default_call_parameters(pauline->lc); LinphoneCall *callInst = NULL; @@ -2704,14 +2704,8 @@ static void call_recording() { #ifdef VIDEO_ENABLED if((linphone_core_find_payload_type(marie->lc, "H264", -1, -1) != NULL) && (linphone_core_find_payload_type(pauline->lc, "H264", -1, -1) != NULL)) { - linphone_core_enable_video_display(marie->lc, TRUE); - linphone_core_enable_video_display(pauline->lc, FALSE); - linphone_core_enable_video_capture(marie->lc, TRUE); - linphone_core_enable_video_capture(pauline->lc, TRUE); - linphone_call_params_enable_video(marieParams, TRUE); linphone_call_params_enable_video(paulineParams, TRUE); - disable_all_video_codecs_except_one(marie->lc, "H264"); disable_all_video_codecs_except_one(pauline->lc, "H264"); } else { diff --git a/tester/rcfiles/marie_h264_rc b/tester/rcfiles/marie_h264_rc new file mode 100644 index 000000000..f2ab26190 --- /dev/null +++ b/tester/rcfiles/marie_h264_rc @@ -0,0 +1,55 @@ +[sip] +sip_port=-1 +sip_tcp_port=-1 +sip_tls_port=-1 +default_proxy=0 +ping_with_options=0 +register_only_when_network_is_up=0 +composing_idle_timeout=1 + +[auth_info_0] +username=marie +userid=marie +passwd=secret +realm=sip.example.org + + +[proxy_0] +reg_proxy=sip.example.org;transport=tcp +reg_route=sip.example.org;transport=tcp;lr +reg_identity=sip:marie@sip.example.org +reg_expires=3600 +reg_sendregister=1 +publish=0 +dial_escape_plus=0 +quality_reporting_collector=sip:collector@sip.example.org +quality_reporting_enabled=1 + +[friend_0] +url="Paupoche" +pol=accept +subscribe=0 + + +[rtp] +audio_rtp_port=8070 +video_rtp_port=9072 + +[video] +display=1 +capture=1 +show_local=0 +size=vga +enabled=0 +self_view=0 +automatically_initiate=0 +automatically_accept=0 +device=StaticImage: Static picture + +[sound] +echocancellation=0 #to not overload cpu in case of VG + +[video_codec_0] +mime=H264 +rate=90000 +enabled=1 diff --git a/tester/rcfiles/pauline_h264_rc b/tester/rcfiles/pauline_h264_rc new file mode 100644 index 000000000..d11c7d071 --- /dev/null +++ b/tester/rcfiles/pauline_h264_rc @@ -0,0 +1,52 @@ +[sip] +sip_port=-1 +sip_tcp_port=-1 +sip_tls_port=-1 +default_proxy=0 +ping_with_options=0 +register_only_when_network_is_up=0 +composing_idle_timeout=1 + +[auth_info_0] +username=pauline +userid=pauline +passwd=secret +realm=sip.example.org + + +[proxy_0] +reg_proxy=sip2.linphone.org;transport=tls +reg_route=sip2.linphone.org;transport=tls +reg_identity=sip:pauline@sip.example.org +reg_expires=3600 +reg_sendregister=1 +publish=0 +dial_escape_plus=0 + +#[friend_0] +#url="Mariette" +#pol=accept +#subscribe=0 + +[rtp] +audio_rtp_port=8090 +video_rtp_port=9092 + +[video] +display=0 +capture=1 +show_local=0 +size=vga +enabled=0 +self_view=0 +automatically_initiate=0 +automatically_accept=0 +device=StaticImage: Static picture + +[sound] +echocancellation=0 #to not overload cpu in case of VG + +[video_codec_0] +mime=H264 +rate=90000 +enabled=1 From 9c3f4771a9fa58111b24efb931f315b9cba06ee7 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 8 Aug 2014 16:23:24 +0200 Subject: [PATCH 156/218] Serialize logs in the Python wrapper. --- tools/python/apixml2python.py | 1 + tools/python/apixml2python/handwritten.mustache | 1 + 2 files changed, 2 insertions(+) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 6dc59c95b..c93b21eb1 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -83,6 +83,7 @@ blacklisted_functions = [ 'linphone_core_payload_type_enabled', 'linphone_core_payload_type_is_vbr', 'linphone_core_publish', + 'linphone_core_serialize_logs', # There is no use to wrap this function 'linphone_core_set_log_file', # There is no use to wrap this function 'linphone_core_set_log_handler', # Hand-written but put directly in the linphone module 'linphone_core_set_log_level', # There is no use to wrap this function diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index 5f02432ee..101cabe8c 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -78,6 +78,7 @@ static void pylinphone_module_log_handler(OrtpLogLevel lev, const char *fmt, va_ } static void pylinphone_init_logging(void) { + linphone_core_serialize_logs(); linphone_core_set_log_handler(pylinphone_module_log_handler); linphone_core_set_log_level(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR|ORTP_FATAL); } From 102cab7620cca5afec2468fe569beabe6340a98b Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 8 Aug 2014 16:24:13 +0200 Subject: [PATCH 157/218] Hand-written version of linphone_core_iterate() in the Python wrapper. It adds a loop to peek Windows messages to prevent a deadlock in the MoveWindow function. --- tools/python/apixml2python.py | 1 + .../python/apixml2python/handwritten.mustache | 23 +++++++++++++++++++ tools/python/apixml2python/linphone.py | 11 +++++---- .../apixml2python/linphone_module.mustache | 9 ++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index c93b21eb1..5d8e18d57 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -115,6 +115,7 @@ blacklisted_functions = [ 'lp_config_section_to_dict' ] hand_written_functions = [ + 'linphone_core_iterate', 'linphone_core_new', 'linphone_core_new_with_config' ] diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index 101cabe8c..2033799e7 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -177,3 +177,26 @@ static PyObject * pylinphone_Core_class_method_new_with_config(PyObject *cls, Py Py_DECREF(self); return pyret; } + +static PyObject * pylinphone_Core_instance_method_iterate(PyObject *self, PyObject *args) { + LinphoneCore *native_ptr = pylinphone_Core_get_native_ptr(self); + if (native_ptr == NULL) { + PyErr_SetString(PyExc_TypeError, "Invalid linphone.Core instance"); + return NULL; + } + + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p [%p])", __FUNCTION__, self, native_ptr); + linphone_core_iterate(native_ptr); +#ifdef WIN32 + { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, 1)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +#endif + + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> None", __FUNCTION__); + Py_RETURN_NONE; +} diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 0a8c1f550..5e7a0873d 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -701,6 +701,7 @@ class LinphoneModule(object): c['class_has_user_data'] = False c['class_type_methods'] = [] c['class_type_hand_written_methods'] = [] + c['class_instance_hand_written_methods'] = [] c['class_object_members'] = '' if c['class_name'] == 'Core': c['class_object_members'] = "\tPyObject *vtable_dict;" @@ -738,13 +739,15 @@ class LinphoneModule(object): method_name = xml_instance_method.get('name') if method_name in blacklisted_functions: continue - method_name = method_name.replace(c['class_c_function_prefix'], '') if method_name in self.internal_instance_method_names: continue m = {} - m['method_name'] = method_name - m['method_xml_node'] = xml_instance_method - c['class_instance_methods'].append(m) + m['method_name'] = method_name.replace(c['class_c_function_prefix'], '') + if method_name in hand_written_functions: + c['class_instance_hand_written_methods'].append(m) + else: + m['method_xml_node'] = xml_instance_method + c['class_instance_methods'].append(m) c['class_properties'] = [] xml_properties = xml_class.findall("./properties/property") for xml_property in xml_properties: diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index b85253611..2a9c903b0 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -22,6 +22,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include +#ifdef WIN32 +#include +#endif #ifdef _MSC_VER #define PYLINPHONE_INLINE __inline @@ -53,6 +56,9 @@ static PyObject * pylinphone_{{class_name}}_new_from_native_ptr(PyTypeObject *ty {{#class_type_hand_written_methods}} static PyObject * pylinphone_{{class_name}}_class_method_{{method_name}}(PyObject *cls, PyObject *args); {{/class_type_hand_written_methods}} +{{#class_instance_hand_written_methods}} +static PyObject * pylinphone_{{class_name}}_instance_method_{{method_name}}(PyObject *self, PyObject *args); +{{/class_instance_hand_written_methods}} {{/classes}} {{#events}} @@ -103,6 +109,9 @@ static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { { "{{method_name}}", pylinphone_{{class_name}}_class_method_{{method_name}}, METH_VARARGS | METH_CLASS, "" }, {{/class_type_methods}} /* Instance methods */ +{{#class_instance_hand_written_methods}} + { "{{method_name}}", pylinphone_{{class_name}}_instance_method_{{method_name}}, METH_VARARGS, "" }, +{{/class_instance_hand_written_methods}} {{#class_instance_methods}} { "{{method_name}}", pylinphone_{{class_name}}_instance_method_{{method_name}}, METH_VARARGS, "" }, {{/class_instance_methods}} From 18ed8688b144630819e5009f85b6ca8f38d0eef2 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Sun, 10 Aug 2014 22:39:50 +0200 Subject: [PATCH 158/218] Add linphone_core_set_preferred_framerate() to the media_parameters documentation group. --- coreapi/linphonecore.c | 1 + 1 file changed, 1 insertion(+) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index edf63d848..4a5a11554 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -5451,6 +5451,7 @@ void linphone_core_set_preferred_framerate(LinphoneCore *lc, float fps){ } /** * Returns the preferred video framerate, previously set by linphone_core_set_preferred_framerate(). + * @ingroup media_parameters * @param lc the linphone core * @return frame rate in number of frames per seconds. **/ From d6945537fb2cb7934c15ad2ea796d36ba7225717 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Sun, 10 Aug 2014 22:40:15 +0200 Subject: [PATCH 159/218] Document the reasons why some function are blacklisted in the Python wrapper. --- tools/python/apixml2python.py | 147 ++++++++++++++++------------------ 1 file changed, 71 insertions(+), 76 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 5d8e18d57..bdf8f2c09 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -31,88 +31,83 @@ blacklisted_classes = [ 'LinphoneTunnelConfig' ] blacklisted_events = [ - 'LinphoneCoreInfoReceivedCb', - 'LinphoneCoreNotifyReceivedCb', - 'LinphoneCoreFileTransferProgressIndicationCb', - 'LinphoneCoreFileTransferRecvCb', - 'LinphoneCoreFileTransferSendCb' + 'LinphoneCoreInfoReceivedCb', # missing LinphoneInfoMessage + 'LinphoneCoreNotifyReceivedCb', # missing LinphoneContent + 'LinphoneCoreFileTransferProgressIndicationCb', # missing LinphoneContent + 'LinphoneCoreFileTransferRecvCb', # missing LinphoneContent + 'LinphoneCoreFileTransferSendCb' # missing LinphoneContent ] blacklisted_functions = [ - 'linphone_call_get_user_pointer', - 'linphone_call_set_user_pointer', - 'linphone_call_log_get_local_stats', - 'linphone_call_log_get_remote_stats', - 'linphone_call_log_get_start_date', - 'linphone_call_log_get_user_pointer', - 'linphone_call_log_set_user_pointer', - 'linphone_call_params_get_received_video_size', - 'linphone_call_params_get_privacy', - 'linphone_call_params_get_sent_video_size', - 'linphone_call_params_get_used_audio_codec', - 'linphone_call_params_get_used_video_codec', - 'linphone_call_params_set_privacy', - 'linphone_call_stats_get_late_packets_cumulative_number', - 'linphone_call_stats_get_receiver_interarrival_jitter', - 'linphone_call_stats_get_sender_interarrival_jitter', - 'linphone_chat_message_get_chat_room', - 'linphone_chat_message_get_file_transfer_information', - 'linphone_chat_message_get_time', - 'linphone_chat_message_start_file_download', - 'linphone_chat_message_state_to_string', - 'linphone_chat_room_create_file_transfer_message', - 'linphone_chat_room_create_message_2', - 'linphone_chat_room_send_message2', - 'linphone_core_can_we_add_call', - 'linphone_core_enable_payload_type', - 'linphone_core_find_payload_type', - 'linphone_core_get_audio_codecs', - 'linphone_core_get_auth_info_list', - 'linphone_core_get_call_logs', - 'linphone_core_get_calls', - 'linphone_core_get_chat_rooms', - 'linphone_core_get_default_proxy', - 'linphone_core_get_payload_type_bitrate', - 'linphone_core_get_preferred_video_size', - 'linphone_core_get_friend_list', - 'linphone_core_get_proxy_config_list', - 'linphone_core_get_sip_transports', - 'linphone_core_get_sip_transports_used', - 'linphone_core_get_supported_video_sizes', - 'linphone_core_get_video_codecs', - 'linphone_core_get_video_policy', - 'linphone_core_payload_type_enabled', - 'linphone_core_payload_type_is_vbr', - 'linphone_core_publish', + 'linphone_call_get_user_pointer', # rename to linphone_call_get_user_data + 'linphone_call_set_user_pointer', # rename to linphone_call_set_user_data + 'linphone_call_log_get_local_stats', # missing rtp_stats_t + 'linphone_call_log_get_remote_stats', # missing rtp_stats_t + 'linphone_call_log_get_start_date', # missing time_t + 'linphone_call_log_get_user_pointer', # rename to linphone_call_log_get_user_data + 'linphone_call_log_set_user_pointer', # rename to linphone_call_log_set_user_data + 'linphone_call_params_get_received_video_size', # missing MSVideoSize + 'linphone_call_params_get_privacy', # missing LinphonePrivacyMask + 'linphone_call_params_get_sent_video_size', # missing MSVideoSize + 'linphone_call_params_get_used_audio_codec', # missing PayloadType + 'linphone_call_params_get_used_video_codec', # missing PayloadType + 'linphone_call_params_set_privacy', # missing LinphonePrivacyMask + 'linphone_chat_message_get_file_transfer_information', # missing LinphoneContent + 'linphone_chat_message_get_time', # missing time_t + 'linphone_chat_message_start_file_download', # to be handwritten because of callback + 'linphone_chat_message_state_to_string', # There is no use to wrap this function + 'linphone_chat_room_create_file_transfer_message', # missing LinphoneContent + 'linphone_chat_room_create_message_2', # missing time_t + 'linphone_chat_room_send_message2', # to be handwritten because of callback + 'linphone_core_can_we_add_call', # private function + 'linphone_core_enable_payload_type', # missing PayloadType + 'linphone_core_find_payload_type', # missing PayloadType + 'linphone_core_get_audio_codecs', # missing PayloadType and MSList + 'linphone_core_get_auth_info_list', # missing MSList + 'linphone_core_get_call_logs', # missing MSList + 'linphone_core_get_calls', # missing MSList + 'linphone_core_get_chat_rooms', # missing MSList + 'linphone_core_get_default_proxy', # to be handwritten because of double pointer indirection + 'linphone_core_get_payload_type_bitrate', # missing PayloadType + 'linphone_core_get_preferred_video_size', # missing MSVideoSize + 'linphone_core_get_friend_list', # missing MSList + 'linphone_core_get_proxy_config_list', # missing MSList + 'linphone_core_get_sip_transports', # missing LCSipTransports + 'linphone_core_get_sip_transports_used', # missing LCSipTransports + 'linphone_core_get_supported_video_sizes', # missing MSVideoSizeDef + 'linphone_core_get_video_codecs', # missing PayloadType and MSList + 'linphone_core_get_video_policy', # missing LinphoneVideoPolicy + 'linphone_core_payload_type_enabled', # missing PayloadType + 'linphone_core_payload_type_is_vbr', # missing PayloadType + 'linphone_core_publish', # missing LinphoneContent 'linphone_core_serialize_logs', # There is no use to wrap this function 'linphone_core_set_log_file', # There is no use to wrap this function 'linphone_core_set_log_handler', # Hand-written but put directly in the linphone module 'linphone_core_set_log_level', # There is no use to wrap this function - 'linphone_core_set_payload_type_bitrate', - 'linphone_core_set_preferred_video_size', - 'linphone_core_set_video_policy', - 'linphone_core_play_dtmf', - 'linphone_core_send_dtmf', - 'linphone_core_set_audio_codecs', - 'linphone_core_set_preview_video_size', - 'linphone_core_set_sip_transports', - 'linphone_core_subscribe', - 'linphone_event_notify', - 'linphone_event_send_publish', - 'linphone_event_send_subscribe', - 'linphone_event_update_publish', - 'linphone_event_update_subscribe', - 'linphone_presence_model_get_timestamp', - 'linphone_presence_model_set_timestamp', - 'linphone_proxy_config_get_privacy', - 'linphone_proxy_config_normalize_number', - 'linphone_proxy_config_set_file_transfer_server', - 'linphone_proxy_config_set_privacy', - 'linphone_tunnel_get_http_proxy', - 'lp_config_for_each_entry', - 'lp_config_for_each_section', - 'lp_config_get_range', - 'lp_config_load_dict_to_section', - 'lp_config_section_to_dict' + 'linphone_core_set_payload_type_bitrate', # missing PayloadType + 'linphone_core_set_preferred_video_size', # missing MSVideoSize + 'linphone_core_set_video_policy', # missing LinphoneVideoPolicy + 'linphone_core_play_dtmf', # handling of char + 'linphone_core_send_dtmf', # handling of char + 'linphone_core_set_audio_codecs', # missing PayloadType and MSList + 'linphone_core_set_preview_video_size', # missing MSVideoSize + 'linphone_core_set_sip_transports', # missing LCSipTransports + 'linphone_core_subscribe', # missing LinphoneContent + 'linphone_event_notify', # missing LinphoneContent + 'linphone_event_send_publish', # missing LinphoneContent + 'linphone_event_send_subscribe', # missing LinphoneContent + 'linphone_event_update_publish', # missing LinphoneContent + 'linphone_event_update_subscribe', # missing LinphoneContent + 'linphone_presence_model_get_timestamp', # missing time_t + 'linphone_proxy_config_get_privacy', # missing LinphonePrivacyMask + 'linphone_proxy_config_normalize_number', # to be handwritten because of result via arguments + 'linphone_proxy_config_set_file_transfer_server', # defined but not implemented in linphone core + 'linphone_proxy_config_set_privacy', # missing LinphonePrivacyMask + 'linphone_tunnel_get_http_proxy', # to be handwritten because of double pointer indirection + 'lp_config_for_each_entry', # to be handwritten because of callback + 'lp_config_for_each_section', # to be handwritten because of callback + 'lp_config_get_range', # to be handwritten because of result via arguments + 'lp_config_load_dict_to_section', # missing LinphoneDictionary + 'lp_config_section_to_dict' # missing LinphoneDictionary ] hand_written_functions = [ 'linphone_core_iterate', From f4dc674dc233c2064c27184fe7bb1a0b56159a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Wed, 6 Aug 2014 15:13:00 +0200 Subject: [PATCH 160/218] Enable openh264 plugin for "Call recording" test --- coreapi/linphonecore.c | 6 ++++++ coreapi/linphonecore.h | 2 ++ tester/call_tester.c | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 4a5a11554..15491765e 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -6685,3 +6685,9 @@ bool_t linphone_core_sdp_200_ack_enabled(const LinphoneCore *lc) { void linphone_core_set_file_transfer_server(LinphoneCore *core, const char * server_url) { core->file_transfer_server=ms_strdup(server_url); } + +void linphone_core_init_openh264(void) { +#ifdef HAVE_OPENH264 + libmsopenh264_init(); +#endif +} diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 9fdb864df..4f3f6f210 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -2782,6 +2782,8 @@ LINPHONE_PUBLIC void linphone_core_set_file_transfer_server(LinphoneCore *core, **/ LINPHONE_PUBLIC const char ** linphone_core_get_supported_file_formats(LinphoneCore *core); +LINPHONE_PUBLIC void linphone_core_init_openh264(void); + #ifdef __cplusplus } #endif diff --git a/tester/call_tester.c b/tester/call_tester.c index 51c57252b..e9e155a8e 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2663,7 +2663,16 @@ static void savpf_to_savpf_call(void) { profile_call(TRUE, TRUE, TRUE, TRUE, "RTP/SAVPF"); } +#ifdef ANDROID +#ifdef HAVE_OPENH264 +extern void libmsopenh264_init(void); +#endif +#endif + static void call_recording() { +#ifdef ANDROID + linphone_core_init_openh264(); +#endif LinphoneCoreManager *marie = linphone_core_manager_new("marie_h264_rc"); LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_h264_rc"); LinphoneCallParams *marieParams = linphone_core_create_default_call_parameters(marie->lc); From 7d94757ac0eb846ae1897bb0dae2e82dc68af13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Mon, 11 Aug 2014 13:28:58 +0200 Subject: [PATCH 161/218] Enable openh264 plugins for the "Call recording tester" --- coreapi/linphonecore.c | 6 ------ coreapi/linphonecore.h | 2 -- tester/call_tester.c | 35 ++++++++++++++++++++++------------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 15491765e..4a5a11554 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -6685,9 +6685,3 @@ bool_t linphone_core_sdp_200_ack_enabled(const LinphoneCore *lc) { void linphone_core_set_file_transfer_server(LinphoneCore *core, const char * server_url) { core->file_transfer_server=ms_strdup(server_url); } - -void linphone_core_init_openh264(void) { -#ifdef HAVE_OPENH264 - libmsopenh264_init(); -#endif -} diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 4f3f6f210..9fdb864df 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -2782,8 +2782,6 @@ LINPHONE_PUBLIC void linphone_core_set_file_transfer_server(LinphoneCore *core, **/ LINPHONE_PUBLIC const char ** linphone_core_get_supported_file_formats(LinphoneCore *core); -LINPHONE_PUBLIC void linphone_core_init_openh264(void); - #ifdef __cplusplus } #endif diff --git a/tester/call_tester.c b/tester/call_tester.c index e9e155a8e..a64193a3d 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -29,6 +29,13 @@ static void call_base(LinphoneMediaEncryption mode, bool_t enable_video,bool_t enable_relay,LinphoneFirewallPolicy policy); static void disable_all_audio_codecs_except_one(LinphoneCore *lc, const char *mime); +// prototype definition for call_recording() +#ifdef ANDROID +#ifdef HAVE_OPENH264 +extern void libmsopenh264_init(void); +#endif +#endif + void call_state_changed(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate, const char *msg){ char* to=linphone_address_as_string(linphone_call_get_call_log(call)->to); char* from=linphone_address_as_string(linphone_call_get_call_log(call)->from); @@ -2663,24 +2670,26 @@ static void savpf_to_savpf_call(void) { profile_call(TRUE, TRUE, TRUE, TRUE, "RTP/SAVPF"); } -#ifdef ANDROID -#ifdef HAVE_OPENH264 -extern void libmsopenh264_init(void); -#endif -#endif - static void call_recording() { -#ifdef ANDROID - linphone_core_init_openh264(); -#endif - LinphoneCoreManager *marie = linphone_core_manager_new("marie_h264_rc"); - LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_h264_rc"); - LinphoneCallParams *marieParams = linphone_core_create_default_call_parameters(marie->lc); - LinphoneCallParams *paulineParams = linphone_core_create_default_call_parameters(pauline->lc); + LinphoneCoreManager *marie = NULL; + LinphoneCoreManager *pauline = NULL; + LinphoneCallParams *marieParams = NULL; + LinphoneCallParams *paulineParams = NULL; LinphoneCall *callInst = NULL; int dummy=0; char *filepath = NULL; +#ifdef ANDROID +#ifdef HAVE_OPENH264 + libmsopenh264_init(); +#endif +#endif + + marie = linphone_core_manager_new("marie_h264_rc"); + pauline = linphone_core_manager_new("pauline_h264_rc"); + marieParams = linphone_core_create_default_call_parameters(marie->lc); + paulineParams = linphone_core_create_default_call_parameters(pauline->lc); + #ifdef ANDROID const char dirname[] = "/sdcard/Movies/liblinphone_tester"; #else From 6689ab019e9437618b84c534613e24fd824c60a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Mon, 11 Aug 2014 15:10:32 +0200 Subject: [PATCH 162/218] Update desciption comment of linphone_call_params_set_record_file() function. --- coreapi/linphonecall.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index f8c6d1d72..c650894b3 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -2771,7 +2771,9 @@ uint64_t linphone_call_stats_get_late_packets_cumulative_number(const LinphoneCa * The call recording can be started and paused after the call is established with * linphone_call_start_recording() and linphone_call_pause_recording(). * @param cp the call parameters - * @param path path and filename of the file where audio is written. + * @param path path and filename of the file where audio/video streams are written. + * The filename must have either .mkv or .wav extention. The video stream will be written + * only if a MKV file is given. **/ void linphone_call_params_set_record_file(LinphoneCallParams *cp, const char *path){ if (cp->record_file){ From 4a0ec4a571abb55b59e43d9f3c9c5db17950607b Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 11 Aug 2014 14:37:26 +0200 Subject: [PATCH 163/218] Dispatch Windows messages regularly in the Python wrapper. --- .../python/apixml2python/handwritten.mustache | 20 +++++++------ tools/python/apixml2python/linphone.py | 28 +++++++++++-------- .../apixml2python/linphone_module.mustache | 1 + 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index 2033799e7..dff7a574b 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -1,3 +1,13 @@ +static void pylinphone_dispatch_messages(void) { +#ifdef WIN32 + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, 1)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#endif +} + static void pylinphone_log(const char *level, int indent, const char *fmt, va_list args) { static int current_indent = 1; PyObject *linphone_module; @@ -187,15 +197,7 @@ static PyObject * pylinphone_Core_instance_method_iterate(PyObject *self, PyObje pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p [%p])", __FUNCTION__, self, native_ptr); linphone_core_iterate(native_ptr); -#ifdef WIN32 - { - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, 1)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } -#endif + pylinphone_dispatch_messages(); pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> None", __FUNCTION__); Py_RETURN_NONE; diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 5e7a0873d..75ef733ad 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -123,6 +123,7 @@ class MethodDefinition: def format_c_function_call(self): arg_names = [] + c_function_call_code = '' for xml_method_arg in self.xml_method_args: arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') @@ -132,15 +133,14 @@ class MethodDefinition: arg_names.append(arg_name + "_native_ptr") else: arg_names.append(arg_name) - body = "\t" if self.return_type != 'void': - body += "cresult = " - body += self.method_node.get('name') + "(" + c_function_call_code += "cresult = " + c_function_call_code += self.method_node.get('name') + "(" if self.self_arg is not None: - body += "native_ptr" + c_function_call_code += "native_ptr" if len(arg_names) > 0: - body += ', ' - body += ', '.join(arg_names) + ");\n" + c_function_call_code += ', ' + c_function_call_code += ', '.join(arg_names) + ");" return_from_user_data_code = '' new_from_native_pointer_code = '' ref_native_pointer_code = '' @@ -170,12 +170,15 @@ class MethodDefinition: result_variable = 'cresult' if result_variable != '': build_value_code = "pyret = Py_BuildValue(\"{fmt}\", {result_variable});\n".format(fmt=self.build_value_format, result_variable=result_variable) - body += \ -""" {return_from_user_data_code} + body = \ +""" {c_function_call_code} + pylinphone_dispatch_messages(); + {return_from_user_data_code} {new_from_native_pointer_code} {ref_native_pointer_code} {build_value_code} -""".format(return_from_user_data_code=return_from_user_data_code, +""".format(c_function_call_code=c_function_call_code, + return_from_user_data_code=return_from_user_data_code, new_from_native_pointer_code=new_from_native_pointer_code, ref_native_pointer_code=ref_native_pointer_code, build_value_code=build_value_code) @@ -466,6 +469,7 @@ class DeallocMethodDefinition(MethodDefinition): """.format(function_prefix=self.class_['class_c_function_prefix']) return \ """{native_ptr_dealloc_code} + pylinphone_dispatch_messages(); self->ob_type->tp_free(self); """.format(native_ptr_dealloc_code=native_ptr_dealloc_code) @@ -534,8 +538,10 @@ class SetterMethodDefinition(MethodDefinition): use_native_ptr = '' if self.python_fmt == 'O': use_native_ptr = '_native_ptr' - return "\t{method_name}(native_ptr, {arg_name}{use_native_ptr});\n".format( - arg_name="_" + self.first_arg_name, method_name=self.method_node.get('name'), use_native_ptr=use_native_ptr) + return \ +""" {method_name}(native_ptr, {arg_name}{use_native_ptr}); + pylinphone_dispatch_messages(); +""".format(arg_name="_" + self.first_arg_name, method_name=self.method_node.get('name'), use_native_ptr=use_native_ptr) def format_return_trace(self): return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> 0\", __FUNCTION__);\n" diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 2a9c903b0..aad4ff777 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -33,6 +33,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif +static void pylinphone_dispatch_messages(void); static PYLINPHONE_INLINE void pylinphone_trace(int indent, const char *fmt, ...); From 92ee0383c26a0eeece9710ded9e26733d5f227f1 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 11 Aug 2014 14:38:23 +0200 Subject: [PATCH 164/218] Execute the commands and the linphone_core_iterate() in the same thread in the Python example. --- tools/python/linphone-daemon.py | 63 ++++++++++++++------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py index ab27bcb55..8f4d0eab7 100644 --- a/tools/python/linphone-daemon.py +++ b/tools/python/linphone-daemon.py @@ -6,31 +6,6 @@ import time from linphone import linphone -class StoppableThread(threading.Thread): - def __init__(self): - threading.Thread.__init__(self) - self.stop_event = threading.Event() - - def stop(self): - if self.isAlive() == True: - # Set an event to signal the thread to terminate - self.stop_event.set() - # Block the calling thread until the thread really has terminated - self.join() - -class IntervalTimer(StoppableThread): - def __init__(self, interval, worker_func, kwargs={}): - StoppableThread.__init__(self) - self._interval = interval - self._worker_func = worker_func - self._kwargs = kwargs - - def run(self): - while not self.stop_event.is_set(): - self._worker_func(self._kwargs) - time.sleep(self._interval) - - class Response: Ok = 0 Error = 1 @@ -392,11 +367,14 @@ class TerminateCommand(Command): class Daemon: def __init__(self): - self._quit = False + self.quitting = False self._next_proxy_id = 1 self.proxy_ids_map = {} self._next_call_id = 1 self.call_ids_map = {} + self.command_mutex = threading.Lock() + self.command_executed_event = threading.Event() + self.command_to_execute = None self.commands = [ CallCommand(), CallPauseCommand(), @@ -425,13 +403,16 @@ class Daemon: def interact(self): command_line = raw_input('> ').strip() if command_line != '': - self.exec_command(command_line) + self.command_mutex.acquire() + self.command_to_execute = command_line + self.command_mutex.release() + self.command_executed_event.wait() + self.command_executed_event.clear() def run(self, args): - # Define the iteration function - def iterate(kwargs): - core = kwargs['core'] - core.iterate() + def command_read(daemon): + while not daemon.quitting: + daemon.interact() def global_state_changed(core, state, message): logging.warning("[PYTHON] global_state_changed: " + str(state) + ", " + message) @@ -452,14 +433,22 @@ class Daemon: # Create a linphone core and iterate every 20 ms self.core = linphone.Core.new(callbacks, args.config, args.factory_config) - interval = IntervalTimer(0.02, iterate, kwargs={'core':self.core}) - interval.start() - while not self._quit: - self.interact() - interval.stop() + t = threading.Thread(target=command_read, kwargs={'daemon':self}) + t.start() + while not self.quitting: + self.command_mutex.acquire() + command_line = self.command_to_execute + if command_line is not None: + self.exec_command(command_line) + self.command_to_execute = None + self.command_executed_event.set() + self.command_mutex.release() + self.core.iterate() + time.sleep(0.02) + t.join() def quit(self): - self._quit = True + self.quitting = True def update_proxy_id(self, proxy): id = self._next_proxy_id From 87eb75d3794147620ffb43e39105e63a9b583438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Mon, 11 Aug 2014 15:20:10 +0200 Subject: [PATCH 165/218] Update description comment of the LinphoneCallParams.setRecordFile() method. --- java/common/org/linphone/core/LinphoneCallParams.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/common/org/linphone/core/LinphoneCallParams.java b/java/common/org/linphone/core/LinphoneCallParams.java index b226077cd..7086a56fb 100644 --- a/java/common/org/linphone/core/LinphoneCallParams.java +++ b/java/common/org/linphone/core/LinphoneCallParams.java @@ -76,6 +76,9 @@ public interface LinphoneCallParams { /** * Set a path to file where the call will be recorded. * Actual start of the recording is controlled by LinphoneCall.startRecording(). + * @param path Path to the file where the call will be recorded. If it is a WAV + * file, only audio will be written whereas if it is a MKV file, audio and video + * will be written. **/ void setRecordFile(String path); From 860b23448c30532d86160514287c0c16ec1f6dff Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 11 Aug 2014 15:33:29 +0200 Subject: [PATCH 166/218] Allow any callable as linphone core callbacks and log handler in the Python wrapper. --- tools/python/apixml2python/handwritten.mustache | 14 ++++++++++---- tools/python/apixml2python/linphone.py | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index dff7a574b..54ad63314 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -17,7 +17,7 @@ static void pylinphone_log(const char *level, int indent, const char *fmt, va_li linphone_module = PyImport_ImportModule("linphone"); if ((linphone_module != NULL) && PyObject_HasAttrString(linphone_module, "__log_handler")) { PyObject *log_handler = PyObject_GetAttrString(linphone_module, "__log_handler"); - if ((log_handler != NULL) && PyFunction_Check(log_handler)) { + if ((log_handler != NULL) && PyCallable_Check(log_handler)) { char logstr[4096]; int i = 0; if (indent == -1) current_indent--; @@ -29,7 +29,9 @@ static void pylinphone_log(const char *level, int indent, const char *fmt, va_li } if (indent == 1) current_indent++; if (vsnprintf(logstr + i, sizeof(logstr) - i, fmt, args) > 0) { - PyEval_CallFunction(log_handler, "ss", level, logstr); + if (PyEval_CallObject(log_handler, Py_BuildValue("ss", level, logstr)) == NULL) { + PyErr_Print(); + } } Py_DECREF(log_handler); } @@ -73,10 +75,10 @@ static void pylinphone_module_log_handler(OrtpLogLevel lev, const char *fmt, va_ level = pylinphone_ortp_log_level_to_string(lev); if ((linphone_module != NULL) && PyObject_HasAttrString(linphone_module, "__log_handler")) { PyObject *log_handler = PyObject_GetAttrString(linphone_module, "__log_handler"); - if ((log_handler != NULL) && PyFunction_Check(log_handler)) { + if ((log_handler != NULL) && PyCallable_Check(log_handler)) { char logstr[4096]; if (vsnprintf(logstr, sizeof(logstr), fmt, args) > 0) { - if (PyEval_CallFunction(log_handler, "ss", level, logstr) == NULL) { + if (PyEval_CallObject(log_handler, Py_BuildValue("ss", level, logstr)) == NULL) { PyErr_Print(); } } @@ -100,6 +102,10 @@ static PyObject * pylinphone_module_method_set_log_handler(PyObject *self, PyObj if (!PyArg_ParseTuple(args, "O", &callback)) { return NULL; } + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "The argument must be a callable"); + return NULL; + } if (linphone_module != NULL) { Py_INCREF(callback); PyObject_SetAttrString(linphone_module, "__log_handler", callback); diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 75ef733ad..1e3b44492 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -635,8 +635,8 @@ class EventCallbackMethodDefinition(MethodDefinition): args.append(arg_name) args=', '.join(args) return \ -""" if ((func != NULL) && PyFunction_Check(func)) {{ - if (PyEval_CallFunction(func, "{fmt}", {args}) == NULL) {{ +""" if ((func != NULL) && PyCallable_Check(func)) {{ + if (PyEval_CallObject(func, Py_BuildValue("{fmt}", {args})) == NULL) {{ PyErr_Print(); }} }} From 7a4458c2e27479520294740ce38f1293ff37355e Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 11 Aug 2014 15:34:12 +0200 Subject: [PATCH 167/218] Set linphone core callbacks as instance methods in the Python example. --- tools/python/linphone-daemon.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py index 8f4d0eab7..547aba918 100644 --- a/tools/python/linphone-daemon.py +++ b/tools/python/linphone-daemon.py @@ -387,6 +387,17 @@ class Daemon: TerminateCommand() ] + def global_state_changed(self, core, state, message): + logging.warning("[PYTHON] global_state_changed: " + str(state) + ", " + message) + if state == linphone.GlobalState.GlobalOn: + logging.warning("[PYTHON] core version: " + str(core.version)) + + def registration_state_changed(self, core, proxy_cfg, state, message): + logging.warning("[PYTHON] registration_state_changed: " + str(state) + ", " + message) + + def call_state_changed(self, core, call, state, message): + logging.warning("[PYTHON] call_state_changed: " + str(state) + ", " + message) + def send_response(self, response): print(response) @@ -414,21 +425,10 @@ class Daemon: while not daemon.quitting: daemon.interact() - def global_state_changed(core, state, message): - logging.warning("[PYTHON] global_state_changed: " + str(state) + ", " + message) - if state == linphone.GlobalState.GlobalOn: - logging.warning("[PYTHON] core version: " + str(core.version)) - - def registration_state_changed(core, proxy_cfg, state, message): - logging.warning("[PYTHON] registration_state_changed: " + str(state) + ", " + message) - - def call_state_changed(core, call, state, message): - logging.warning("[PYTHON] call_state_changed: " + str(state) + ", " + message) - callbacks = { - 'global_state_changed':global_state_changed, - 'registration_state_changed':registration_state_changed, - 'call_state_changed':call_state_changed + 'global_state_changed':self.global_state_changed, + 'registration_state_changed':self.registration_state_changed, + 'call_state_changed':self.call_state_changed } # Create a linphone core and iterate every 20 ms From 6e2861e544eb53239d004b3642d631923c440539 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 11 Aug 2014 15:38:39 +0200 Subject: [PATCH 168/218] linphone_core_iterate() Python wrapper no longer needs to be handwritten. --- tools/python/apixml2python.py | 1 - tools/python/apixml2python/handwritten.mustache | 15 --------------- 2 files changed, 16 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index bdf8f2c09..29a2904c2 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -110,7 +110,6 @@ blacklisted_functions = [ 'lp_config_section_to_dict' # missing LinphoneDictionary ] hand_written_functions = [ - 'linphone_core_iterate', 'linphone_core_new', 'linphone_core_new_with_config' ] diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten.mustache index 54ad63314..df58453dc 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten.mustache @@ -193,18 +193,3 @@ static PyObject * pylinphone_Core_class_method_new_with_config(PyObject *cls, Py Py_DECREF(self); return pyret; } - -static PyObject * pylinphone_Core_instance_method_iterate(PyObject *self, PyObject *args) { - LinphoneCore *native_ptr = pylinphone_Core_get_native_ptr(self); - if (native_ptr == NULL) { - PyErr_SetString(PyExc_TypeError, "Invalid linphone.Core instance"); - return NULL; - } - - pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p [%p])", __FUNCTION__, self, native_ptr); - linphone_core_iterate(native_ptr); - pylinphone_dispatch_messages(); - - pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> None", __FUNCTION__); - Py_RETURN_NONE; -} From b9a6e50746c45be2a1f98ae777bc0cd8b7a1be50 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 11 Aug 2014 16:53:16 +0200 Subject: [PATCH 169/218] fix problems with authinfo wrappers, update ms2 for opensles fixes --- java/impl/org/linphone/core/LinphoneAuthInfoImpl.java | 3 ++- java/impl/org/linphone/core/LinphoneChatMessageImpl.java | 3 ++- mediastreamer2 | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/java/impl/org/linphone/core/LinphoneAuthInfoImpl.java b/java/impl/org/linphone/core/LinphoneAuthInfoImpl.java index 7d2b3dde2..8efdcc144 100644 --- a/java/impl/org/linphone/core/LinphoneAuthInfoImpl.java +++ b/java/impl/org/linphone/core/LinphoneAuthInfoImpl.java @@ -37,7 +37,7 @@ class LinphoneAuthInfoImpl implements LinphoneAuthInfo { boolean ownPtr = false; protected LinphoneAuthInfoImpl(String username,String password, String realm, String domain) { - this(username, null, password, null, null, domain); + this(username, null, password, null, realm, domain); } protected LinphoneAuthInfoImpl(String username, String userid, String passwd, String ha1, String realm, String domain) { nativePtr = newLinphoneAuthInfo(); @@ -46,6 +46,7 @@ class LinphoneAuthInfoImpl implements LinphoneAuthInfo { this.setPassword(passwd); this.setHa1(ha1); this.setDomain(domain); + this.setRealm(realm); ownPtr = true; } protected LinphoneAuthInfoImpl(long aNativePtr) { diff --git a/java/impl/org/linphone/core/LinphoneChatMessageImpl.java b/java/impl/org/linphone/core/LinphoneChatMessageImpl.java index c48c8a5cc..bcf7d6e14 100644 --- a/java/impl/org/linphone/core/LinphoneChatMessageImpl.java +++ b/java/impl/org/linphone/core/LinphoneChatMessageImpl.java @@ -93,7 +93,8 @@ public class LinphoneChatMessageImpl implements LinphoneChatMessage { public ErrorInfo getErrorInfo() { return new ErrorInfoImpl(getErrorInfo(nativePtr)); } - protected void finalize(){ + protected void finalize() throws Throwable{ unref(nativePtr); + super.finalize(); } } diff --git a/mediastreamer2 b/mediastreamer2 index 4064390b4..cc4a59f37 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 4064390b48c957dcf3077b0e30e0c082395bbf05 +Subproject commit cc4a59f373ba3899815be662e7f3b63ee677b01e From 989505d3ee7256ff1697b9e09dc88676bbcbc513 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 11 Aug 2014 17:13:37 +0200 Subject: [PATCH 170/218] Rework argument type handling in the Python wrapper generator. --- tools/python/apixml2python/linphone.py | 239 ++++++++++++++----------- 1 file changed, 137 insertions(+), 102 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 1e3b44492..2581ac342 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -37,6 +37,110 @@ def compute_event_name(s): return event_name +class ArgumentType: + def __init__(self, basic_type, complete_type, linphone_module): + self.basic_type = basic_type + self.complete_type = complete_type + self.linphone_module = linphone_module + self.type_str = None + self.check_func = None + self.convert_func = None + self.fmt_str = 'O' + self.cfmt_str = '%p' + self.__compute() + + def __compute(self): + splitted_type = self.complete_type.split(' ') + if self.basic_type == 'char': + if '*' in splitted_type: + self.type_str = 'string' + self.check_func = 'PyString_Check' + self.convert_func = 'PyString_AsString' + self.fmt_str = 'z' + self.cfmt_str = '\\"%s\\"' + else: + self.type_str = 'int' + self.check_func = 'PyInt_Check' + self.convert_func = 'PyInt_AsLong' + self.fmt_str = 'b' + self.cfmt_str = '%08x' + elif self.basic_type == 'int': + if 'unsigned' in splitted_type: + self.type_str = 'unsigned int' + self.check_func = 'PyLong_Check' + self.convert_func = 'PyLong_AsUnsignedLong' + self.fmt_str = 'I' + self.cfmt_str = '%u' + else: + self.type_str = 'int' + self.check_func = 'PyLong_Check' + self.convert_func = 'PyLong_AsLong' + self.fmt_str = 'i' + self.cfmt_str = '%d' + elif self.basic_type in ['int8_t', 'int16_t' 'int32_t']: + self.type_str = 'int' + self.check_func = 'PyLong_Check' + self.convert_func = 'PyLong_AsLong' + if self.basic_type == 'int8_t': + self.fmt_str = 'c' + elif self.basic_type == 'int16_t': + self.fmt_str = 'h' + elif self.basic_type == 'int32_t': + self.fmt_str = 'l' + self.cfmt_str = '%d' + elif self.basic_type in ['uint8_t', 'uint16_t', 'uint32_t']: + self.type_str = 'unsigned int' + self.check_func = 'PyLong_Check' + self.convert_func = 'PyLong_AsUnsignedLong' + if self.basic_type == 'uint8_t': + self.fmt_str = 'b' + elif self.basic_type == 'uint16_t': + self.fmt_str = 'H' + elif self.basic_type == 'uint32_t': + self.fmt_str = 'k' + self.cfmt_str = '%u' + elif self.basic_type == 'int64_t': + self.type_str = '64bits int' + self.check_func = 'PyLong_Check' + self.convert_func = 'PyLong_AsLongLong' + self.fmt_str = 'L' + self.cfmt_str = '%ld' + elif self.basic_type == 'uint64_t': + self.type_str = '64bits unsigned int' + self.check_func = 'PyLong_Check' + self.convert_func = 'PyLong_AsUnsignedLongLong' + self.fmt_str = 'K' + self.cfmt_str = '%lu' + elif self.basic_type == 'size_t': + self.type_str = 'size_t' + self.check_func = 'PyLong_Check' + self.convert_func = 'PyLong_AsSsize_t' + self.fmt_str = 'n' + self.cfmt_str = '%lu' + elif self.basic_type in ['float', 'double']: + self.type_str = 'float' + self.check_func = 'PyFloat_Check' + self.convert_func = 'PyFloat_AsDouble' + if self.basic_type == 'float': + self.fmt_str = 'f' + elif self.basic_type == 'double': + self.fmt_str = 'd' + self.cfmt_str = '%f' + elif self.basic_type == 'bool_t': + self.type_str = 'bool' + self.check_func = 'PyBool_Check' + self.convert_func = 'PyInt_AsLong' + self.fmt_str = 'i' + self.cfmt_str = '%d' + else: + if strip_leading_linphone(self.basic_type) in self.linphone_module.enum_names: + self.type_str = 'int' + self.check_func = 'PyInt_Check' + self.convert_func = 'PyInt_AsLong' + self.fmt_str = 'i' + self.cfmt_str = '%d' + + class MethodDefinition: def __init__(self, linphone_module, class_, method_node = None): self.body = '' @@ -60,7 +164,8 @@ class MethodDefinition: self.return_complete_type = self.xml_method_return.get('completetype') if self.return_complete_type != 'void': body += "\t" + self.return_complete_type + " cresult;\n" - self.build_value_format = self.ctype_to_python_format(self.return_type, self.return_complete_type) + argument_type = ArgumentType(self.return_type, self.return_complete_type, self.linphone_module) + self.build_value_format = argument_type.fmt_str if self.build_value_format == 'O': body += "\tPyObject * pyresult;\n" body += "\tPyObject * pyret;\n" @@ -70,9 +175,9 @@ class MethodDefinition: arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - fmt = self.ctype_to_python_format(arg_type, arg_complete_type) - self.parse_tuple_format += fmt - if fmt == 'O': + argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + self.parse_tuple_format += argument_type.fmt_str + if argument_type.fmt_str == 'O': body += "\tPyObject * " + arg_name + ";\n" body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr;\n" elif strip_leading_linphone(arg_complete_type) in self.linphone_module.enum_names: @@ -113,9 +218,12 @@ class MethodDefinition: arg_complete_type = xml_method_arg.get('completetype') if fmt != '': fmt += ', ' - f, a = self.ctype_to_str_format(arg_name, arg_type, arg_complete_type) - fmt += f - args += a + argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + fmt += argument_type.cfmt_str + args.append(arg_name) + if argument_type.fmt_str == 'O': + fmt += ' [' + argument_type.cfmt_str + ']' + args.append(arg_name) args=', '.join(args) if args != '': args = ', ' + args @@ -128,8 +236,8 @@ class MethodDefinition: arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - type_str, checkfunc, convertfunc = self.ctype_to_python_type(arg_type, arg_complete_type) - if convertfunc is None: + argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + if argument_type.convert_func is None: arg_names.append(arg_name + "_native_ptr") else: arg_names.append(arg_name) @@ -221,8 +329,8 @@ class MethodDefinition: arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - fmt = self.ctype_to_python_format(arg_type, arg_complete_type) - if fmt == 'O': + argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + if argument_type.fmt_str == 'O': body += \ """ if (({arg_name}_native_ptr = pylinphone_{arg_type}_get_native_ptr({arg_name})) == NULL) {{ return NULL; @@ -287,78 +395,6 @@ class MethodDefinition: else: return ('%p', [name]) - def ctype_to_python_format(self, basic_type, complete_type): - splitted_type = complete_type.split(' ') - if basic_type == 'char': - if '*' in splitted_type: - return 'z' - elif 'unsigned' in splitted_type: - return 'b' - elif basic_type == 'int': - # TODO: - return 'i' - elif basic_type == 'int8_t': - return 'c' - elif basic_type == 'uint8_t': - return 'b' - elif basic_type == 'int16_t': - return 'h' - elif basic_type == 'uint16_t': - return 'H' - elif basic_type == 'int32_t': - return 'l' - elif basic_type == 'uint32_t': - return 'k' - elif basic_type == 'int64_t': - return 'L' - elif basic_type == 'uint64_t': - return 'K' - elif basic_type == 'size_t': - return 'n' - elif basic_type == 'float': - return 'f' - elif basic_type == 'double': - return 'd' - elif basic_type == 'bool_t': - return 'i' - else: - if strip_leading_linphone(basic_type) in self.linphone_module.enum_names: - return 'i' - else: - return 'O' - - def ctype_to_python_type(self, basic_type, complete_type): - splitted_type = complete_type.split(' ') - if basic_type == 'char': - if '*' in splitted_type: - return ('string', 'PyString_Check', 'PyString_AsString') - else: - return ('int', 'PyInt_Check', 'PyInt_AsLong') - elif basic_type == 'int': - if 'unsigned' in splitted_type: - return ('unsigned int', 'PyLong_Check', 'PyLong_AsUnsignedLong') - else: - return ('int', 'PyLong_Check', 'PyLong_AsLong') - elif basic_type in ['int8_t', 'int16_t' 'int32_t']: - return ('int', 'PyLong_Check', 'PyLong_AsLong') - elif basic_type in ['uint8_t', 'uin16_t', 'uint32_t']: - return ('unsigned int', 'PyLong_Check', 'PyLong_AsUnsignedLong') - elif basic_type == 'int64_t': - return ('64bits int', 'PyLong_Check', 'PyLong_AsLongLong') - elif basic_type == 'uint64_t': - return ('64bits unsigned int', 'PyLong_Check', 'PyLong_AsUnsignedLongLong') - elif basic_type == 'size_t': - return ('size_t', 'PyLong_Check', 'PyLong_AsSsize_t') - elif basic_type in ['float', 'double']: - return ('float', 'PyFloat_Check', 'PyFloat_AsDouble') - elif basic_type == 'bool_t': - return ('bool', 'PyBool_Check', 'PyInt_AsLong') - else: - if strip_leading_linphone(basic_type) in self.linphone_module.enum_names: - return ('int', 'PyInt_Check', 'PyInt_AsLong') - else: - return (None, None, None) - def find_class_definition(self, basic_type): basic_type = strip_leading_linphone(basic_type) for c in self.linphone_module.classes: @@ -488,7 +524,7 @@ class SetterMethodDefinition(MethodDefinition): MethodDefinition.__init__(self, linphone_module, class_, method_node) def format_arguments_parsing(self): - if self.checkfunc is None: + if self.first_argument_type.check_func is None: attribute_type_check_code = \ """if (!PyObject_IsInstance(value, (PyObject *)&pylinphone_{class_name}Type)) {{ PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a linphone.{class_name} instance"); @@ -497,21 +533,21 @@ class SetterMethodDefinition(MethodDefinition): """.format(class_name=self.first_arg_class, attribute_name=self.attribute_name) else: checknotnone = '' - if self.type_str == 'string': + if self.first_argument_type.type_str == 'string': checknotnone = "(value != Py_None) && " attribute_type_check_code = \ """if ({checknotnone}!{checkfunc}(value)) {{ PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a {type_str}"); return -1; }} -""".format(checknotnone=checknotnone, checkfunc=self.checkfunc, attribute_name=self.attribute_name, type_str=self.type_str) - if self.convertfunc is None: +""".format(checknotnone=checknotnone, checkfunc=self.first_argument_type.check_func, attribute_name=self.attribute_name, type_str=self.first_argument_type.type_str) + if self.first_argument_type.convert_func is None: attribute_conversion_code = "{arg_name} = value;\n".format(arg_name="_" + self.first_arg_name) else: attribute_conversion_code = "{arg_name} = ({arg_type}){convertfunc}(value);\n".format( - arg_name="_" + self.first_arg_name, arg_type=self.first_arg_complete_type, convertfunc=self.convertfunc) + arg_name="_" + self.first_arg_name, arg_type=self.first_arg_complete_type, convertfunc=self.first_argument_type.convert_func) attribute_native_ptr_check_code = '' - if self.python_fmt == 'O': + if self.first_argument_type.fmt_str == 'O': attribute_native_ptr_check_code = \ """{arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name}); if ({arg_name}_native_ptr == NULL) {{ @@ -536,7 +572,7 @@ class SetterMethodDefinition(MethodDefinition): def format_c_function_call(self): use_native_ptr = '' - if self.python_fmt == 'O': + if self.first_argument_type.fmt_str == 'O': use_native_ptr = '_native_ptr' return \ """ {method_name}(native_ptr, {arg_name}{use_native_ptr}); @@ -558,9 +594,8 @@ class SetterMethodDefinition(MethodDefinition): self.first_arg_type = self.xml_method_args[0].get('type') self.first_arg_complete_type = self.xml_method_args[0].get('completetype') self.first_arg_name = self.xml_method_args[0].get('name') - self.type_str, self.checkfunc, self.convertfunc = self.ctype_to_python_type(self.first_arg_type, self.first_arg_complete_type) + self.first_argument_type = ArgumentType(self.first_arg_type, self.first_arg_complete_type, self.linphone_module) self.first_arg_class = strip_leading_linphone(self.first_arg_type) - self.python_fmt = self.ctype_to_python_format(self.first_arg_type, self.first_arg_complete_type) class EventCallbackMethodDefinition(MethodDefinition): def __init__(self, linphone_module, class_, method_node = None): @@ -576,8 +611,8 @@ class EventCallbackMethodDefinition(MethodDefinition): arg_name = 'py' + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - fmt = self.ctype_to_python_format(arg_type, arg_complete_type) - if fmt == 'O': + argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + if argument_type.fmt_str == 'O': specific += "\tPyObject * " + arg_name + " = NULL;\n" return "{common}\n{specific}".format(common=common, specific=specific) @@ -587,8 +622,8 @@ class EventCallbackMethodDefinition(MethodDefinition): arg_name = xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - fmt = self.ctype_to_python_format(arg_type, arg_complete_type) - if fmt == 'O': + argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + if argument_type.fmt_str == 'O': type_class = self.find_class_definition(arg_type) get_user_data_code = '' new_from_native_pointer_code = "py{name} = pylinphone_{arg_type}_new_from_native_ptr(&pylinphone_{arg_type}Type, {name});".format(name=arg_name, arg_type=strip_leading_linphone(arg_type)) @@ -612,9 +647,9 @@ class EventCallbackMethodDefinition(MethodDefinition): arg_complete_type = xml_method_arg.get('completetype') if fmt != '': fmt += ', ' - f, a = self.ctype_to_str_format(arg_name, arg_type, arg_complete_type, with_native_ptr=False) - fmt += f - args += a + argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + fmt += argument_type.cfmt_str + args.append(arg_name) args=', '.join(args) if args != '': args = ', ' + args @@ -627,9 +662,9 @@ class EventCallbackMethodDefinition(MethodDefinition): arg_name = xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - f = self.ctype_to_python_format(arg_type, arg_complete_type) - fmt += f - if f == 'O': + argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + fmt += argument_type.fmt_str + if argument_type.fmt_str == 'O': args.append('py' + arg_name) else: args.append(arg_name) From 61c5b4c285da2aa1633158cd2f63e499449e6e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Mon, 11 Aug 2014 18:12:23 +0200 Subject: [PATCH 171/218] Fix timestamp bug in the video recorder --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index cc4a59f37..3b9fe1b8c 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit cc4a59f373ba3899815be662e7f3b63ee677b01e +Subproject commit 3b9fe1b8c405f1a148ec7b58f7d540c8946a9a05 From 6b88923d4c44042b5aa87402c52f23ece18730c4 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 11 Aug 2014 22:17:13 +0200 Subject: [PATCH 172/218] update ms2 for bugfixes, implement jitter buffer disablement --- coreapi/linphonecore.c | 29 +++++++++++++++++++++++++++++ mediastreamer2 | 2 +- oRTP | 2 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 4a5a11554..6818b1415 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -1763,24 +1763,53 @@ bool_t linphone_core_get_rtp_no_xmit_on_audio_mute(const LinphoneCore *lc){ return lc->rtp_conf.rtp_no_xmit_on_audio_mute; } +static void apply_jitter_value(LinphoneCore *lc, int value, MSFormatType stype){ + LinphoneCall *call; + MSList *it; + for (it=lc->calls;it!=NULL;it=it->next){ + MediaStream *ms; + call=(LinphoneCall*)it->data; + ms = stype==MSAudio ? (MediaStream*)call->audiostream : (MediaStream*)call->videostream; + if (ms){ + RtpSession *s=ms->sessions.rtp_session; + if (s){ + if (value>0){ + ms_message("Jitter buffer size set to [%i] ms on call [%p]",value,call); + rtp_session_set_jitter_compensation(s,value); + rtp_session_enable_jitter_buffer(s,TRUE); + }else if (value==0){ + ms_warning("Jitter buffer is disabled per application request on call [%p]",call); + rtp_session_enable_jitter_buffer(s,FALSE); + } + } + } + } +} + /** * Sets the nominal audio jitter buffer size in milliseconds. + * The value takes effect immediately for all running and pending calls, if any. + * A value of 0 disables the jitter buffer. * * @ingroup media_parameters **/ void linphone_core_set_audio_jittcomp(LinphoneCore *lc, int value) { lc->rtp_conf.audio_jitt_comp=value; + apply_jitter_value(lc, value, MSAudio); } /** * Sets the nominal video jitter buffer size in milliseconds. + * The value takes effect immediately for all running and pending calls, if any. + * A value of 0 disables the jitter buffer. * * @ingroup media_parameters **/ void linphone_core_set_video_jittcomp(LinphoneCore *lc, int value) { lc->rtp_conf.video_jitt_comp=value; + apply_jitter_value(lc, value, MSVideo); } void linphone_core_set_rtp_no_xmit_on_audio_mute(LinphoneCore *lc,bool_t rtp_no_xmit_on_audio_mute){ diff --git a/mediastreamer2 b/mediastreamer2 index 3b9fe1b8c..c82cc74f7 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 3b9fe1b8c405f1a148ec7b58f7d540c8946a9a05 +Subproject commit c82cc74f7341378ea3d21257448c427e4e7b91a1 diff --git a/oRTP b/oRTP index fc8d8457e..9d158c2da 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit fc8d8457eb630907eff50333ddf5243b448fe733 +Subproject commit 9d158c2daf289bd826b855903e913786f90d5bad From f0e0f1a9d59d58520e26db5fbc2e5ed473d104de Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 09:36:54 +0200 Subject: [PATCH 173/218] Document enums in the Python wrapper. --- tools/python/apixml2python/linphone.py | 25 +++++++++++++------ .../apixml2python/linphone_module.mustache | 6 ++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 2581ac342..281451973 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -224,7 +224,7 @@ class MethodDefinition: if argument_type.fmt_str == 'O': fmt += ' [' + argument_type.cfmt_str + ']' args.append(arg_name) - args=', '.join(args) + args = ', '.join(args) if args != '': args = ', ' + args return "\tpylinphone_trace(1, \"[PYLINPHONE] >>> %s({fmt})\", __FUNCTION__{args});\n".format(fmt=fmt, args=args) @@ -711,7 +711,8 @@ class LinphoneModule(object): continue e = {} e['enum_name'] = strip_leading_linphone(xml_enum.get('name')) - e['enum_doc'] = self.__format_doc(xml_enum.find('briefdescription'), xml_enum.find('detaileddescription')) + e['enum_doc'] = self.__format_doc_content(xml_enum.find('briefdescription'), xml_enum.find('detaileddescription')) + e['enum_doc'] += "\n\nValues:\n" e['enum_values'] = [] xml_enum_values = xml_enum.findall("./values/value") for xml_enum_value in xml_enum_values: @@ -720,7 +721,10 @@ class LinphoneModule(object): v = {} v['enum_value_cname'] = xml_enum_value.get('name') v['enum_value_name'] = strip_leading_linphone(v['enum_value_cname']) + v['enum_value_doc'] = self.__format_doc(xml_enum_value.find('briefdescription'), xml_enum_value.find('detaileddescription')) + e['enum_doc'] += '\t' + v['enum_value_name'] + ': ' + v['enum_value_doc'] + '\n' e['enum_values'].append(v) + e['enum_doc'] = self.__replace_doc_special_chars(e['enum_doc']) self.enums.append(e) self.enum_names.append(e['enum_name']) self.events = [] @@ -898,7 +902,7 @@ class LinphoneModule(object): desc += '\n' return desc - def __format_doc(self, brief_description, detailed_description): + def __format_doc_content(self, brief_description, detailed_description): doc = '' if brief_description is None: brief_description = '' @@ -908,12 +912,19 @@ class LinphoneModule(object): desc = '' for node in list(detailed_description): desc += self.__format_doc_node(node) + '\n' - detailed_description = desc.strip().replace('\n', '\\n') + detailed_description = desc.strip() brief_description = brief_description.strip() doc += brief_description if detailed_description != '': if doc != '': - doc += '\\n\\n' - doc+= detailed_description - doc = '\"' + doc + '\"' + doc += '\n\n' + doc += detailed_description + return doc + + def __replace_doc_special_chars(self, doc): + return doc.replace('"', '').encode('string-escape') + + def __format_doc(self, brief_description, detailed_description): + doc = self.__format_doc_content(brief_description, detailed_description) + doc = self.__replace_doc_special_chars(doc) return doc diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index aad4ff777..3fca3bd9e 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -163,7 +163,7 @@ static PyTypeObject pylinphone_{{class_name}}Type = { 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ - {{{class_doc}}}, /* tp_doc */ + "{{{class_doc}}}", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ @@ -220,7 +220,7 @@ static PyObject * pylinphone_{{enum_name}}_module_method_string(PyObject *self, } static PyMethodDef pylinphone_{{enum_name}}_ModuleMethods[] = { - { "string", pylinphone_{{enum_name}}_module_method_string, METH_VARARGS, "" }, + { "string", pylinphone_{{enum_name}}_module_method_string, METH_VARARGS, "Get a string representation of a linphone.{{enum_name}} value." }, /* Sentinel */ { NULL, NULL, 0, NULL } }; @@ -241,7 +241,7 @@ PyMODINIT_FUNC initlinphone(void) { if (m == NULL) return; {{#enums}} - menum = Py_InitModule3("{{enum_name}}", pylinphone_{{enum_name}}_ModuleMethods, {{{enum_doc}}}); + menum = Py_InitModule3("{{enum_name}}", pylinphone_{{enum_name}}_ModuleMethods, "{{{enum_doc}}}"); if (menum == NULL) return; if (PyModule_AddObject(m, "{{enum_name}}", menum) < 0) return; {{#enum_values}} From 0cc70b04ab27038fcc56c316575b97c2e3b44437 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 10:53:08 +0200 Subject: [PATCH 174/218] Fix *_ref(), *_unref(), *_destroy() appearing in the Python wrapper. --- tools/python/apixml2python/linphone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 281451973..e2b3141d9 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -784,7 +784,7 @@ class LinphoneModule(object): method_name = xml_instance_method.get('name') if method_name in blacklisted_functions: continue - if method_name in self.internal_instance_method_names: + if method_name.replace(c['class_c_function_prefix'], '') in self.internal_instance_method_names: continue m = {} m['method_name'] = method_name.replace(c['class_c_function_prefix'], '') From 51c72605fb202e339ca32ed586d85d763822d434 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 11:05:51 +0200 Subject: [PATCH 175/218] Add methods documentation in the Python wrapper. --- tools/python/apixml2python/linphone.py | 25 +++++++++++++++++++ .../apixml2python/linphone_module.mustache | 5 ++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index e2b3141d9..90b054ba0 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -851,8 +851,10 @@ class LinphoneModule(object): try: for m in c['class_type_methods']: m['method_body'] = MethodDefinition(self, c, m['method_xml_node']).format() + m['method_doc'] = self.__format_method_doc(m['method_xml_node']) for m in c['class_instance_methods']: m['method_body'] = MethodDefinition(self, c, m['method_xml_node']).format() + m['method_doc'] = self.__format_method_doc(m['method_xml_node']) except Exception, e: e.args += (c['class_name'], m['method_name']) raise @@ -928,3 +930,26 @@ class LinphoneModule(object): doc = self.__format_doc_content(brief_description, detailed_description) doc = self.__replace_doc_special_chars(doc) return doc + + def __format_method_doc(self, xml_node): + doc = self.__format_doc_content(xml_node.find('briefdescription'), xml_node.find('detaileddescription')) + xml_method_return = xml_node.find('./return') + xml_method_args = xml_node.findall('./arguments/argument') + method_type = xml_node.tag + if method_type != 'classmethod' and len(xml_method_args) > 0: + xml_method_args = xml_method_args[1:] + if len(xml_method_args) > 0: + doc += "\n\nArguments:" + for xml_method_arg in xml_method_args: + arg_name = xml_method_arg.get('name') + arg_doc = self.__format_doc_content(None, xml_method_arg.find('description')) + doc += '\n\t' + arg_name + if arg_doc != '': + doc += ': ' + arg_doc + if xml_method_return is not None: + return_complete_type = xml_method_return.get('completetype') + if return_complete_type != 'void': + return_doc = self.__format_doc_content(None, xml_method_return.find('description')) + doc += '\n\nReturns:\n\t' + return_doc + doc = self.__replace_doc_special_chars(doc) + return doc diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 3fca3bd9e..4a8888ff3 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -101,20 +101,19 @@ static PyObject * pylinphone_{{class_name}}_instance_method_{{method_name}}(PyOb {{/class_instance_methods}} static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { - // TODO: Handle doc /* Class methods */ {{#class_type_hand_written_methods}} { "{{method_name}}", pylinphone_{{class_name}}_class_method_{{method_name}}, METH_VARARGS | METH_CLASS, "" }, {{/class_type_hand_written_methods}} {{#class_type_methods}} - { "{{method_name}}", pylinphone_{{class_name}}_class_method_{{method_name}}, METH_VARARGS | METH_CLASS, "" }, + { "{{method_name}}", pylinphone_{{class_name}}_class_method_{{method_name}}, METH_VARARGS | METH_CLASS, "{{{method_doc}}}" }, {{/class_type_methods}} /* Instance methods */ {{#class_instance_hand_written_methods}} { "{{method_name}}", pylinphone_{{class_name}}_instance_method_{{method_name}}, METH_VARARGS, "" }, {{/class_instance_hand_written_methods}} {{#class_instance_methods}} - { "{{method_name}}", pylinphone_{{class_name}}_instance_method_{{method_name}}, METH_VARARGS, "" }, + { "{{method_name}}", pylinphone_{{class_name}}_instance_method_{{method_name}}, METH_VARARGS, "{{{method_doc}}}" }, {{/class_instance_methods}} /* Sentinel */ { NULL, NULL, 0, NULL } From 1208a71955166636396e7b3f8c44305eb359bb81 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 12:11:11 +0200 Subject: [PATCH 176/218] Implement VideoSize class in the Python wrapper. --- tools/python/apixml2python.py | 5 - .../handwritten_declarations.mustache | 10 ++ ...tache => handwritten_definitions.mustache} | 116 +++++++++++++++++- tools/python/apixml2python/linphone.py | 100 +++++++-------- .../apixml2python/linphone_module.mustache | 14 ++- 5 files changed, 178 insertions(+), 67 deletions(-) create mode 100644 tools/python/apixml2python/handwritten_declarations.mustache rename tools/python/apixml2python/{handwritten.mustache => handwritten_definitions.mustache} (61%) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 29a2904c2..a5b9d46e6 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -45,9 +45,7 @@ blacklisted_functions = [ 'linphone_call_log_get_start_date', # missing time_t 'linphone_call_log_get_user_pointer', # rename to linphone_call_log_get_user_data 'linphone_call_log_set_user_pointer', # rename to linphone_call_log_set_user_data - 'linphone_call_params_get_received_video_size', # missing MSVideoSize 'linphone_call_params_get_privacy', # missing LinphonePrivacyMask - 'linphone_call_params_get_sent_video_size', # missing MSVideoSize 'linphone_call_params_get_used_audio_codec', # missing PayloadType 'linphone_call_params_get_used_video_codec', # missing PayloadType 'linphone_call_params_set_privacy', # missing LinphonePrivacyMask @@ -68,7 +66,6 @@ blacklisted_functions = [ 'linphone_core_get_chat_rooms', # missing MSList 'linphone_core_get_default_proxy', # to be handwritten because of double pointer indirection 'linphone_core_get_payload_type_bitrate', # missing PayloadType - 'linphone_core_get_preferred_video_size', # missing MSVideoSize 'linphone_core_get_friend_list', # missing MSList 'linphone_core_get_proxy_config_list', # missing MSList 'linphone_core_get_sip_transports', # missing LCSipTransports @@ -84,12 +81,10 @@ blacklisted_functions = [ 'linphone_core_set_log_handler', # Hand-written but put directly in the linphone module 'linphone_core_set_log_level', # There is no use to wrap this function 'linphone_core_set_payload_type_bitrate', # missing PayloadType - 'linphone_core_set_preferred_video_size', # missing MSVideoSize 'linphone_core_set_video_policy', # missing LinphoneVideoPolicy 'linphone_core_play_dtmf', # handling of char 'linphone_core_send_dtmf', # handling of char 'linphone_core_set_audio_codecs', # missing PayloadType and MSList - 'linphone_core_set_preview_video_size', # missing MSVideoSize 'linphone_core_set_sip_transports', # missing LCSipTransports 'linphone_core_subscribe', # missing LinphoneContent 'linphone_event_notify', # missing LinphoneContent diff --git a/tools/python/apixml2python/handwritten_declarations.mustache b/tools/python/apixml2python/handwritten_declarations.mustache new file mode 100644 index 000000000..1d27b9963 --- /dev/null +++ b/tools/python/apixml2python/handwritten_declarations.mustache @@ -0,0 +1,10 @@ +static PyTypeObject pylinphone_VideoSizeType; + +typedef struct { + PyObject_HEAD + MSVideoSize vs; +} pylinphone_VideoSizeObject; + +int PyLinphoneVideoSize_Check(PyObject *p); +MSVideoSize PyLinphoneVideoSize_AsMSVideoSize(PyObject *obj); +PyObject * PyLinphoneVideoSize_FromMSVideoSize(MSVideoSize vs); diff --git a/tools/python/apixml2python/handwritten.mustache b/tools/python/apixml2python/handwritten_definitions.mustache similarity index 61% rename from tools/python/apixml2python/handwritten.mustache rename to tools/python/apixml2python/handwritten_definitions.mustache index df58453dc..f6697a606 100644 --- a/tools/python/apixml2python/handwritten.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -14,7 +14,7 @@ static void pylinphone_log(const char *level, int indent, const char *fmt, va_li PyGILState_STATE gstate; gstate = PyGILState_Ensure(); - linphone_module = PyImport_ImportModule("linphone"); + linphone_module = PyImport_ImportModule("linphone.linphone"); if ((linphone_module != NULL) && PyObject_HasAttrString(linphone_module, "__log_handler")) { PyObject *log_handler = PyObject_GetAttrString(linphone_module, "__log_handler"); if ((log_handler != NULL) && PyCallable_Check(log_handler)) { @@ -71,7 +71,7 @@ static void pylinphone_module_log_handler(OrtpLogLevel lev, const char *fmt, va_ const char *level; gstate = PyGILState_Ensure(); - linphone_module = PyImport_ImportModule("linphone"); + linphone_module = PyImport_ImportModule("linphone.linphone"); level = pylinphone_ortp_log_level_to_string(lev); if ((linphone_module != NULL) && PyObject_HasAttrString(linphone_module, "__log_handler")) { PyObject *log_handler = PyObject_GetAttrString(linphone_module, "__log_handler"); @@ -97,7 +97,7 @@ static void pylinphone_init_logging(void) { static PyObject * pylinphone_module_method_set_log_handler(PyObject *self, PyObject *args) { - PyObject *linphone_module = PyImport_ImportModule("linphone"); + PyObject *linphone_module = PyImport_ImportModule("linphone.linphone"); PyObject *callback; if (!PyArg_ParseTuple(args, "O", &callback)) { return NULL; @@ -193,3 +193,113 @@ static PyObject * pylinphone_Core_class_method_new_with_config(PyObject *cls, Py Py_DECREF(self); return pyret; } + + + +static void pylinphone_VideoSize_dealloc(PyObject *self) { + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p)", __FUNCTION__, self); + self->ob_type->tp_free(self); + pylinphone_trace(-1, "[PYLINPHONE] <<< %s", __FUNCTION__); +} + +static PyObject * pylinphone_VideoSize_new(PyTypeObject *type, PyObject *args, PyObject *kw) { + pylinphone_VideoSizeObject *self = (pylinphone_VideoSizeObject *)type->tp_alloc(type, 0); + pylinphone_trace(1, "[PYLINPHONE] >>> %s()", __FUNCTION__); + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> %p", __FUNCTION__, self); + return (PyObject *)self; +} + +static int pylinphone_VideoSize_init(PyObject *self, PyObject *args, PyObject *kwds) { + pylinphone_VideoSizeObject *vso = (pylinphone_VideoSizeObject *)self; + int width; + int height; + if (!PyArg_ParseTuple(args, "ii", &width, &height)) { + return -1; + } + vso->vs.width = width; + vso->vs.height = height; + return 0; +} + +static PyMemberDef pylinphone_VideoSize_members[] = { + { "width", T_INT, offsetof(pylinphone_VideoSizeObject, vs) + offsetof(MSVideoSize, width), 0, "The width of the video" }, + { "height", T_INT, offsetof(pylinphone_VideoSizeObject, vs) + offsetof(MSVideoSize, height), 0, "The height of the video" }, + { NULL } /* Sentinel */ +}; + +static PyTypeObject pylinphone_VideoSizeType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "linphone.VideoSize", /* tp_name */ + sizeof(pylinphone_VideoSizeObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + pylinphone_VideoSize_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Object representing the size of a video: its width and its height in pixels.", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + pylinphone_VideoSize_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + pylinphone_VideoSize_init, /* tp_init */ + 0, /* tp_alloc */ + pylinphone_VideoSize_new, /* tp_new */ + 0, /* tp_free */ +}; + +int PyLinphoneVideoSize_Check(PyObject *p) { + return PyObject_IsInstance(p, (PyObject *)&pylinphone_VideoSizeType); +} + +MSVideoSize PyLinphoneVideoSize_AsMSVideoSize(PyObject *obj) { + return ((pylinphone_VideoSizeObject *)obj)->vs; +} + +PyObject * PyLinphoneVideoSize_FromMSVideoSize(MSVideoSize vs) { + PyObject *linphone_module; + PyObject *pyret = NULL; + PyGILState_STATE gstate; + + gstate = PyGILState_Ensure(); + linphone_module = PyImport_ImportModule("linphone.linphone"); + if (linphone_module != NULL) { + PyObject *cls = PyObject_GetAttrString(linphone_module, "VideoSize"); + if (cls != NULL) { + pyret = PyEval_CallObject(cls, Py_BuildValue("ii", vs.width, vs.height)); + if (pyret == NULL) { + PyErr_Print(); + } + Py_DECREF(cls); + } + Py_DECREF(linphone_module); + } + PyGILState_Release(gstate); + + if (pyret == NULL) { + Py_RETURN_NONE; + } + return pyret; +} diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 90b054ba0..4a73cb820 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -45,8 +45,11 @@ class ArgumentType: self.type_str = None self.check_func = None self.convert_func = None + self.convert_from_func = None self.fmt_str = 'O' self.cfmt_str = '%p' + self.use_native_pointer = False + self.cast_convert_func_result = True self.__compute() def __compute(self): @@ -132,6 +135,14 @@ class ArgumentType: self.convert_func = 'PyInt_AsLong' self.fmt_str = 'i' self.cfmt_str = '%d' + elif self.basic_type == 'MSVideoSize': + self.type_str = 'linphone.VideoSize' + self.check_func = 'PyLinphoneVideoSize_Check' + self.convert_func = 'PyLinphoneVideoSize_AsMSVideoSize' + self.convert_from_func = 'PyLinphoneVideoSize_FromMSVideoSize' + self.fmt_str = 'O' + self.cfmt_str = '%p' + self.cast_convert_func_result = False else: if strip_leading_linphone(self.basic_type) in self.linphone_module.enum_names: self.type_str = 'int' @@ -139,6 +150,8 @@ class ArgumentType: self.convert_func = 'PyInt_AsLong' self.fmt_str = 'i' self.cfmt_str = '%d' + elif '*' in splitted_type: + self.use_native_pointer = True class MethodDefinition: @@ -177,7 +190,7 @@ class MethodDefinition: arg_complete_type = xml_method_arg.get('completetype') argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) self.parse_tuple_format += argument_type.fmt_str - if argument_type.fmt_str == 'O': + if argument_type.use_native_pointer: body += "\tPyObject * " + arg_name + ";\n" body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr;\n" elif strip_leading_linphone(arg_complete_type) in self.linphone_module.enum_names: @@ -194,7 +207,7 @@ class MethodDefinition: parse_tuple_code = '' if len(self.arg_names) > 0: parse_tuple_code = \ -""" if (!PyArg_ParseTuple(args, "{fmt}", {args})) {{ +"""if (!PyArg_ParseTuple(args, "{fmt}", {args})) {{ return NULL; }} """.format(fmt=self.parse_tuple_format, args=', '.join(map(lambda a: '&' + a, self.arg_names))) @@ -252,27 +265,35 @@ class MethodDefinition: return_from_user_data_code = '' new_from_native_pointer_code = '' ref_native_pointer_code = '' + convert_from_code = '' build_value_code = '' result_variable = '' if self.return_complete_type != 'void': if self.build_value_format == 'O': stripped_return_type = strip_leading_linphone(self.return_type) return_type_class = self.find_class_definition(self.return_type) - if return_type_class['class_has_user_data']: - get_user_data_function = return_type_class['class_c_function_prefix'] + "get_user_data" - return_from_user_data_code = \ -""" if ((cresult != NULL) && ({func}(cresult) != NULL)) {{ + if return_type_class is not None: + if return_type_class['class_has_user_data']: + get_user_data_function = return_type_class['class_c_function_prefix'] + "get_user_data" + return_from_user_data_code = \ +"""if ((cresult != NULL) && ({func}(cresult) != NULL)) {{ return (PyObject *){func}(cresult); }} """.format(func=get_user_data_function) - new_from_native_pointer_code = "\tpyresult = pylinphone_{return_type}_new_from_native_ptr(&pylinphone_{return_type}Type, cresult);\n".format(return_type=stripped_return_type) - if self.self_arg is not None and return_type_class['class_refcountable']: - ref_function = return_type_class['class_c_function_prefix'] + "ref" - ref_native_pointer_code = \ -""" if (cresult != NULL) {{ + new_from_native_pointer_code = "pyresult = pylinphone_{return_type}_new_from_native_ptr(&pylinphone_{return_type}Type, cresult);\n".format(return_type=stripped_return_type) + if self.self_arg is not None and return_type_class['class_refcountable']: + ref_function = return_type_class['class_c_function_prefix'] + "ref" + ref_native_pointer_code = \ +"""if (cresult != NULL) {{ {func}(({cast_type})cresult); }} """.format(func=ref_function, cast_type=self.remove_const_from_complete_type(self.return_complete_type)) + else: + return_argument_type = ArgumentType(self.return_type, self.return_complete_type, self.linphone_module) + if return_argument_type.convert_from_func is not None: + convert_from_code = \ +"""pyresult = {convert_func}(cresult); +""".format(convert_func=return_argument_type.convert_from_func) result_variable = 'pyresult' else: result_variable = 'cresult' @@ -284,11 +305,13 @@ class MethodDefinition: {return_from_user_data_code} {new_from_native_pointer_code} {ref_native_pointer_code} + {convert_from_code} {build_value_code} """.format(c_function_call_code=c_function_call_code, return_from_user_data_code=return_from_user_data_code, new_from_native_pointer_code=new_from_native_pointer_code, ref_native_pointer_code=ref_native_pointer_code, + convert_from_code=convert_from_code, build_value_code=build_value_code) return body @@ -316,7 +339,7 @@ class MethodDefinition: if return_int: return_value = "-1" return \ -""" native_ptr = pylinphone_{class_name}_get_native_ptr(self); +"""native_ptr = pylinphone_{class_name}_get_native_ptr(self); if (native_ptr == NULL) {{ PyErr_SetString(PyExc_TypeError, "Invalid linphone.{class_name} instance"); return {return_value}; @@ -353,48 +376,6 @@ class MethodDefinition: splitted_type.remove('const') return ' '.join(splitted_type) - def ctype_to_str_format(self, name, basic_type, complete_type, with_native_ptr=True): - splitted_type = complete_type.split(' ') - if basic_type == 'char': - if '*' in splitted_type: - return ('\\"%s\\"', [name]) - elif 'unsigned' in splitted_type: - return ('%08x', [name]) - elif basic_type == 'int': - # TODO: - return ('%d', [name]) - elif basic_type == 'int8_t': - return ('%d', [name]) - elif basic_type == 'uint8_t': - return ('%u', [name]) - elif basic_type == 'int16_t': - return ('%d', [name]) - elif basic_type == 'uint16_t': - return ('%u', [name]) - elif basic_type == 'int32_t': - return ('%d', [name]) - elif basic_type == 'uint32_t': - return ('%u', [name]) - elif basic_type == 'int64_t': - return ('%ld', [name]) - elif basic_type == 'uint64_t': - return ('%lu', [name]) - elif basic_type == 'size_t': - return ('%lu', [name]) - elif basic_type == 'float': - return ('%f', [name]) - elif basic_type == 'double': - return ('%f', [name]) - elif basic_type == 'bool_t': - return ('%d', [name]) - else: - if strip_leading_linphone(basic_type) in self.linphone_module.enum_names: - return ('%d', [name]) - elif with_native_ptr: - return ('%p [%p]', [name, name + "_native_ptr"]) - else: - return ('%p', [name]) - def find_class_definition(self, basic_type): basic_type = strip_leading_linphone(basic_type) for c in self.linphone_module.classes: @@ -544,10 +525,13 @@ class SetterMethodDefinition(MethodDefinition): if self.first_argument_type.convert_func is None: attribute_conversion_code = "{arg_name} = value;\n".format(arg_name="_" + self.first_arg_name) else: - attribute_conversion_code = "{arg_name} = ({arg_type}){convertfunc}(value);\n".format( - arg_name="_" + self.first_arg_name, arg_type=self.first_arg_complete_type, convertfunc=self.first_argument_type.convert_func) + cast_code = '' + if self.first_argument_type.cast_convert_func_result: + cast_code = "({arg_type})".format(arg_type=self.first_arg_complete_type) + attribute_conversion_code = "{arg_name} = {cast_code}{convertfunc}(value);\n".format( + arg_name="_" + self.first_arg_name, cast_code=cast_code, convertfunc=self.first_argument_type.convert_func) attribute_native_ptr_check_code = '' - if self.first_argument_type.fmt_str == 'O': + if self.first_argument_type.use_native_pointer: attribute_native_ptr_check_code = \ """{arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name}); if ({arg_name}_native_ptr == NULL) {{ @@ -572,7 +556,7 @@ class SetterMethodDefinition(MethodDefinition): def format_c_function_call(self): use_native_ptr = '' - if self.first_argument_type.fmt_str == 'O': + if self.first_argument_type.use_native_pointer: use_native_ptr = '_native_ptr' return \ """ {method_name}(native_ptr, {arg_name}{use_native_ptr}); diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 4a8888ff3..9c1b197f6 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -17,6 +17,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include +#include #include #include #include @@ -37,6 +38,10 @@ static void pylinphone_dispatch_messages(void); static PYLINPHONE_INLINE void pylinphone_trace(int indent, const char *fmt, ...); +{{> handwritten_declarations}} + + + {{#classes}} static PyTypeObject pylinphone_{{class_name}}Type; {{/classes}} @@ -186,7 +191,7 @@ static PyTypeObject pylinphone_{{class_name}}Type = { {{/classes}} -{{> handwritten}} +{{> handwritten_definitions}} static PyMethodDef pylinphone_ModuleMethods[] = { @@ -236,6 +241,9 @@ PyMODINIT_FUNC initlinphone(void) { if (PyType_Ready(&pylinphone_{{class_name}}Type) < 0) return; {{/classes}} + /* Hand-written classes. */ + if (PyType_Ready(&pylinphone_VideoSizeType) < 0) return; + m = Py_InitModule3("linphone", pylinphone_ModuleMethods, "Python module giving access to the Linphone library."); if (m == NULL) return; @@ -252,4 +260,8 @@ PyMODINIT_FUNC initlinphone(void) { Py_INCREF(&pylinphone_{{class_name}}Type); PyModule_AddObject(m, "{{class_name}}", (PyObject *)&pylinphone_{{class_name}}Type); {{/classes}} + + /* Hand-written classes. */ + Py_INCREF(&pylinphone_VideoSizeType); + PyModule_AddObject(m, "VideoSize", (PyObject *)&pylinphone_VideoSizeType); } From 581c9b29bcd29bd01c6b6478378a388fc94d7f28 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 15:08:35 +0200 Subject: [PATCH 177/218] Generate Python wrapper for linphone_core_send_dtmf() and linphone_core_play_dtmf(). --- tools/python/apixml2python.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index a5b9d46e6..bd3826871 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -82,8 +82,6 @@ blacklisted_functions = [ 'linphone_core_set_log_level', # There is no use to wrap this function 'linphone_core_set_payload_type_bitrate', # missing PayloadType 'linphone_core_set_video_policy', # missing LinphoneVideoPolicy - 'linphone_core_play_dtmf', # handling of char - 'linphone_core_send_dtmf', # handling of char 'linphone_core_set_audio_codecs', # missing PayloadType and MSList 'linphone_core_set_sip_transports', # missing LCSipTransports 'linphone_core_subscribe', # missing LinphoneContent From fc7c3963f63c62b38fc234f9438a320a8f378524 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 15:36:22 +0200 Subject: [PATCH 178/218] Fix git revision when compiling with CMake. --- config.h.cmake | 1 + coreapi/CMakeLists.txt | 2 +- coreapi/gitversion.cmake | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config.h.cmake b/config.h.cmake index aa49b3b98..f348cf3f9 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -24,6 +24,7 @@ #define LINPHONE_MINOR_VERSION ${LINPHONE_MINOR_VERSION} #define LINPHONE_MICRO_VERSION ${LINPHONE_MICRO_VERSION} #define LINPHONE_VERSION "${LINPHONE_VERSION}" +#define LIBLINPHONE_VERSION "${LINPHONE_VERSION}" #define LINPHONE_PLUGINS_DIR "${LINPHONE_PLUGINS_DIR}" #define PACKAGE_LOCALE_DIR "${PACKAGE_LOCALE_DIR}" diff --git a/coreapi/CMakeLists.txt b/coreapi/CMakeLists.txt index 2be82be73..ab707ce69 100644 --- a/coreapi/CMakeLists.txt +++ b/coreapi/CMakeLists.txt @@ -94,7 +94,7 @@ set(GENERATED_SOURCE_FILES set_source_files_properties(${GENERATED_SOURCE_FILES} PROPERTIES GENERATED TRUE) find_package(Git) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/liblinphone_gitversion.h - COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DOUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/gitversion.cmake) + COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DWORK_DIR=${CMAKE_CURRENT_SOURCE_DIR} -DOUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/gitversion.cmake) add_definitions( -DIN_LINPHONE diff --git a/coreapi/gitversion.cmake b/coreapi/gitversion.cmake index 1a6ec406c..5c6facdab 100644 --- a/coreapi/gitversion.cmake +++ b/coreapi/gitversion.cmake @@ -23,16 +23,17 @@ if(GIT_EXECUTABLE) execute_process( COMMAND ${GIT_EXECUTABLE} describe --always + WORKING_DIRECTORY ${WORK_DIR} OUTPUT_VARIABLE GIT_REVISION OUTPUT_STRIP_TRAILING_WHITESPACE ) execute_process( - COMMAND ${CMAKE_COMMAND} -E echo "#define GIT_VERSION \"${GIT_REVISION}\"" + COMMAND ${CMAKE_COMMAND} -E echo "#define LIBLINPHONE_GIT_VERSION \"${GIT_REVISION}\"" OUTPUT_FILE ${OUTPUT_DIR}/liblinphone_gitversion.h ) else() execute_process( - COMMAND ${CMAKE_COMMAND} -E echo "#define GIT_VERSION \"unknown\"" + COMMAND ${CMAKE_COMMAND} -E echo "#define LIBLINPHONE_GIT_VERSION \"unknown\"" OUTPUT_FILE ${OUTPUT_DIR}/liblinphone_gitversion.h ) endif() From 0778de48aa6e1171ae316b953cfc6b2d9513f727 Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Thu, 7 Aug 2014 17:46:41 +0200 Subject: [PATCH 179/218] Fix synchronize in chatroom Fix getProxyConfig --- coreapi/linphonecore_jni.cc | 2 +- .../linphone/core/LinphoneChatRoomImpl.java | 30 +++++++++---------- .../org/linphone/core/LinphoneEventImpl.java | 12 ++++---- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index fc6b76f1f..115f4a6fb 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -910,7 +910,7 @@ static jobject getOrCreateProxy(JNIEnv* env,LinphoneProxyConfig* proxy){ extern "C" jobjectArray Java_org_linphone_core_LinphoneCoreImpl_getProxyConfigList(JNIEnv* env, jobject thiz, jlong lc) { const MSList* proxies = linphone_core_get_proxy_config_list((LinphoneCore*)lc); int proxyCount = ms_list_size(proxies); - jclass cls = env->FindClass("java/lang/Object"); + jclass cls = env->FindClass("org/linphone/core/LinphoneProxyConfigImpl"); jobjectArray jProxies = env->NewObjectArray(proxyCount,cls,NULL); for (int i = 0; i < proxyCount; i++ ) { diff --git a/java/impl/org/linphone/core/LinphoneChatRoomImpl.java b/java/impl/org/linphone/core/LinphoneChatRoomImpl.java index 8aa9c258d..b33f5db4b 100644 --- a/java/impl/org/linphone/core/LinphoneChatRoomImpl.java +++ b/java/impl/org/linphone/core/LinphoneChatRoomImpl.java @@ -44,37 +44,37 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { nativePtr = aNativePtr; } - public synchronized LinphoneAddress getPeerAddress() { + public LinphoneAddress getPeerAddress() { return new LinphoneAddressImpl(getPeerAddress(nativePtr),LinphoneAddressImpl.WrapMode.FromConst); } - public synchronized void sendMessage(String message) { + public void sendMessage(String message) { synchronized(getCore()){ sendMessage(nativePtr,message); } } @Override - public synchronized void sendMessage(LinphoneChatMessage message, StateListener listener) { + public void sendMessage(LinphoneChatMessage message, StateListener listener) { synchronized(getCore()){ sendMessage2(nativePtr, message, ((LinphoneChatMessageImpl)message).getNativePtr(), listener); } } @Override - public synchronized LinphoneChatMessage createLinphoneChatMessage(String message) { + public LinphoneChatMessage createLinphoneChatMessage(String message) { synchronized(getCore()){ return new LinphoneChatMessageImpl(createLinphoneChatMessage(nativePtr, message)); } } - public synchronized LinphoneChatMessage[] getHistory() { + public LinphoneChatMessage[] getHistory() { synchronized(getCore()){ return getHistory(0); } } - public synchronized LinphoneChatMessage[] getHistory(int limit) { + public LinphoneChatMessage[] getHistory(int limit) { synchronized(getCore()){ long[] typesPtr = getHistory(nativePtr, limit); if (typesPtr == null) return null; @@ -88,48 +88,48 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { } } - public synchronized void destroy() { + public void destroy() { destroy(nativePtr); } - public synchronized int getUnreadMessagesCount() { + public int getUnreadMessagesCount() { synchronized(getCore()){ return getUnreadMessagesCount(nativePtr); } } - public synchronized void deleteHistory() { + public void deleteHistory() { synchronized(getCore()){ deleteHistory(nativePtr); } } - public synchronized void compose() { + public void compose() { synchronized(getCore()){ compose(nativePtr); } } - public synchronized boolean isRemoteComposing() { + public boolean isRemoteComposing() { synchronized(getCore()){ return isRemoteComposing(nativePtr); } } - public synchronized void markAsRead() { + public void markAsRead() { synchronized(getCore()){ markAsRead(nativePtr); } } - public synchronized void deleteMessage(LinphoneChatMessage message) { + public void deleteMessage(LinphoneChatMessage message) { synchronized(getCore()){ if (message != null) deleteMessage(nativePtr, ((LinphoneChatMessageImpl)message).getNativePtr()); } } - public synchronized void updateUrl(LinphoneChatMessage message) { + public void updateUrl(LinphoneChatMessage message) { synchronized(getCore()){ if (message != null) updateUrl(nativePtr, ((LinphoneChatMessageImpl)message).getNativePtr()); @@ -137,7 +137,7 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { } @Override - public synchronized LinphoneChatMessage createLinphoneChatMessage(String message, + public LinphoneChatMessage createLinphoneChatMessage(String message, String url, State state, long timestamp, boolean isRead, boolean isIncoming) { synchronized(getCore()){ diff --git a/java/impl/org/linphone/core/LinphoneEventImpl.java b/java/impl/org/linphone/core/LinphoneEventImpl.java index ca9c2151c..c78b5da40 100644 --- a/java/impl/org/linphone/core/LinphoneEventImpl.java +++ b/java/impl/org/linphone/core/LinphoneEventImpl.java @@ -33,7 +33,7 @@ public class LinphoneEventImpl implements LinphoneEvent { private native int notify(long nativeptr, String type, String subtype, byte data[], String encoding); @Override - public synchronized void notify(LinphoneContent content) { + public void notify(LinphoneContent content) { synchronized(getCore()){ notify(mNativePtr,content.getType(),content.getSubtype(),content.getData(),content.getEncoding()); } @@ -41,7 +41,7 @@ public class LinphoneEventImpl implements LinphoneEvent { private native int updateSubscribe(long nativePtr, String type, String subtype, byte data[], String encoding); @Override - public synchronized void updateSubscribe(LinphoneContent content) { + public void updateSubscribe(LinphoneContent content) { synchronized(getCore()){ updateSubscribe(mNativePtr,content.getType(), content.getSubtype(),content.getData(),content.getEncoding()); } @@ -49,7 +49,7 @@ public class LinphoneEventImpl implements LinphoneEvent { private native int updatePublish(long nativePtr, String type, String subtype, byte data[], String encoding); @Override - public synchronized void updatePublish(LinphoneContent content) { + public void updatePublish(LinphoneContent content) { synchronized(getCore()){ updatePublish(mNativePtr,content.getType(), content.getSubtype(),content.getData(),content.getEncoding()); } @@ -57,7 +57,7 @@ public class LinphoneEventImpl implements LinphoneEvent { private native int terminate(long nativePtr); @Override - public synchronized void terminate() { + public void terminate() { synchronized(getCore()){ terminate(mNativePtr); } @@ -115,7 +115,7 @@ public class LinphoneEventImpl implements LinphoneEvent { private native void sendSubscribe(long ptr, String type, String subtype, byte data [], String encoding); @Override - public synchronized void sendSubscribe(LinphoneContent body) { + public void sendSubscribe(LinphoneContent body) { synchronized(getCore()){ if (body != null) sendSubscribe(mNativePtr, body.getType(), body.getSubtype(), body.getData(), body.getEncoding()); @@ -126,7 +126,7 @@ public class LinphoneEventImpl implements LinphoneEvent { private native void sendPublish(long ptr, String type, String subtype, byte data [], String encoding); @Override - public synchronized void sendPublish(LinphoneContent body) { + public void sendPublish(LinphoneContent body) { synchronized(getCore()){ if (body != null) sendPublish(mNativePtr, body.getType(), body.getSubtype(), body.getData(), body.getEncoding()); From c7f6a5a4f828074eee318c5f7e9c28116569c750 Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Tue, 12 Aug 2014 16:13:36 +0200 Subject: [PATCH 180/218] Add JNI for detect VBR codec --- coreapi/linphonecore_jni.cc | 7 +++++++ java/common/org/linphone/core/LinphoneCore.java | 9 ++++++++- java/impl/org/linphone/core/LinphoneCoreImpl.java | 7 +++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 115f4a6fb..1b00f795d 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -1272,6 +1272,13 @@ extern "C" jboolean Java_org_linphone_core_LinphoneCoreImpl_isPayloadTypeEnabled return (jboolean) linphone_core_payload_type_enabled((LinphoneCore*)lc, (PayloadType*)pt); } +extern "C" jboolean Java_org_linphone_core_LinphoneCoreImpl_payloadTypeIsVbr(JNIEnv* env + ,jobject thiz + ,jlong lc + ,jlong pt) { + return (jboolean) linphone_core_payload_type_is_vbr((LinphoneCore*)lc, (PayloadType*)pt); +} + extern "C" void Java_org_linphone_core_LinphoneCoreImpl_setPayloadTypeBitrate(JNIEnv* env ,jobject thiz ,jlong lc diff --git a/java/common/org/linphone/core/LinphoneCore.java b/java/common/org/linphone/core/LinphoneCore.java index a74ebc125..5328442fd 100644 --- a/java/common/org/linphone/core/LinphoneCore.java +++ b/java/common/org/linphone/core/LinphoneCore.java @@ -689,10 +689,17 @@ public interface LinphoneCore { void enablePayloadType(PayloadType pt, boolean enable) throws LinphoneCoreException; /** - * Returns whether or not the payload is enabled in linphonecore. + * @param pt the payload type + * @return whether or not the payload is enabled in linphonecore. */ boolean isPayloadTypeEnabled(PayloadType pt); + /** + * @param pt the payload type + * @return whether or not the payload epresents a VBR codec + */ + boolean payloadTypeIsVbr(PayloadType pt); + /** * Set an explicit bitrate (IP bitrate, not codec bitrate) for a given codec, in kbit/s. * @param pt the payload type diff --git a/java/impl/org/linphone/core/LinphoneCoreImpl.java b/java/impl/org/linphone/core/LinphoneCoreImpl.java index 04a679f1a..dc2a9188b 100644 --- a/java/impl/org/linphone/core/LinphoneCoreImpl.java +++ b/java/impl/org/linphone/core/LinphoneCoreImpl.java @@ -73,6 +73,7 @@ class LinphoneCoreImpl implements LinphoneCore { private native long findPayloadType(long nativePtr, String mime, int clockRate, int channels); private native int enablePayloadType(long nativePtr, long payloadType, boolean enable); private native boolean isPayloadTypeEnabled(long nativePtr, long payloadType); + private native boolean payloadTypeIsVbr(long nativePtr, long payloadType); private native void enableAdaptiveRateControl(long nativePtr,boolean enable); private native boolean isAdaptiveRateControlEnabled(long nativePtr); private native void enableEchoCancellation(long nativePtr,boolean enable); @@ -341,6 +342,12 @@ class LinphoneCoreImpl implements LinphoneCore { isValid(); return isPayloadTypeEnabled(nativePtr, ((PayloadTypeImpl)pt).nativePtr); } + + public synchronized boolean payloadTypeIsVbr(PayloadType pt) { + isValid(); + return payloadTypeIsVbr(nativePtr, ((PayloadTypeImpl)pt).nativePtr); + } + public synchronized void enableEchoCancellation(boolean enable) { isValid(); enableEchoCancellation(nativePtr, enable); From 032d83c830e659f0fb5b89d8c8872912751a0964 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 16:59:53 +0200 Subject: [PATCH 181/218] Add Python wrapper for linphone_chat_room_send_message2(). --- tools/python/apixml2python.py | 2 +- .../handwritten_definitions.mustache | 58 +++++++++++++++++++ tools/python/apixml2python/linphone.py | 2 + 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index bd3826871..810dc3edc 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -55,7 +55,6 @@ blacklisted_functions = [ 'linphone_chat_message_state_to_string', # There is no use to wrap this function 'linphone_chat_room_create_file_transfer_message', # missing LinphoneContent 'linphone_chat_room_create_message_2', # missing time_t - 'linphone_chat_room_send_message2', # to be handwritten because of callback 'linphone_core_can_we_add_call', # private function 'linphone_core_enable_payload_type', # missing PayloadType 'linphone_core_find_payload_type', # missing PayloadType @@ -103,6 +102,7 @@ blacklisted_functions = [ 'lp_config_section_to_dict' # missing LinphoneDictionary ] hand_written_functions = [ + 'linphone_chat_room_send_message2', 'linphone_core_new', 'linphone_core_new_with_config' ] diff --git a/tools/python/apixml2python/handwritten_definitions.mustache b/tools/python/apixml2python/handwritten_definitions.mustache index f6697a606..dd19be207 100644 --- a/tools/python/apixml2python/handwritten_definitions.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -195,6 +195,64 @@ 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 * func = NULL; + pylinphone_ChatRoomObject *pycr = (pylinphone_ChatRoomObject *)ud; + + pygil_state = PyGILState_Ensure(); + pycm = linphone_chat_message_get_user_data(msg); + if (pycm == NULL) { + pycm = pylinphone_ChatMessage_new_from_native_ptr(&pylinphone_ChatMessageType, msg); + } + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p, %p [%p], %d, %p)", __FUNCTION__, pycm, msg, state, ud); + if ((pycr->send_message_cb != NULL) && PyCallable_Check(pycr->send_message_cb)) { + if (PyEval_CallObject(pycr->send_message_cb, Py_BuildValue("OiO", pycm, state, pycr->send_message_ud)) == NULL) { + PyErr_Print(); + } + } + 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 *_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 (!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; + } + + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p [%p], %p [%p], %p, %p)", __FUNCTION__, self, native_ptr, _chat_message, _chat_message_native_ptr, _cb, _ud); + ((pylinphone_ChatRoomObject *)self)->send_message_cb = _cb; + ((pylinphone_ChatRoomObject *)self)->send_message_ud = _ud; + linphone_chat_room_send_message2(native_ptr, _chat_message_native_ptr, pylinphone_ChatRoom_callback_chat_message_state_changed, self); + 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); diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 4a73cb820..dd818ccb1 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -746,6 +746,8 @@ class LinphoneModule(object): ev['event_name'] = compute_event_name(ev['event_cname']) ev['event_doc'] = self.__format_doc(xml_event.find('briefdescription'), xml_event.find('detaileddescription')) self.events.append(ev) + elif c['class_name'] == 'ChatRoom': + c['class_object_members'] = "\tPyObject *send_message_cb;\n\tPyObject *send_message_ud;" xml_type_methods = xml_class.findall("./classmethods/classmethod") for xml_type_method in xml_type_methods: if xml_type_method.get('deprecated') == 'true': From a357f2fc865e5dda729006dde7987377892871d5 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Tue, 12 Aug 2014 17:09:01 +0200 Subject: [PATCH 182/218] add LinphoneChatMessage.getTo() java wrapper --- coreapi/linphonecore_jni.cc | 6 ++++++ java/common/org/linphone/core/LinphoneChatMessage.java | 7 +++++++ java/impl/org/linphone/core/LinphoneChatMessageImpl.java | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 1b00f795d..5e28c52ad 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -2589,6 +2589,12 @@ extern "C" jlong Java_org_linphone_core_LinphoneChatMessageImpl_getFrom(JNIEnv* return (jlong) linphone_chat_message_get_from((LinphoneChatMessage*)ptr); } +extern "C" jlong Java_org_linphone_core_LinphoneChatMessageImpl_getTo(JNIEnv* env + ,jobject thiz + ,jlong ptr) { + return (jlong) linphone_chat_message_get_to((LinphoneChatMessage*)ptr); +} + extern "C" jlong Java_org_linphone_core_LinphoneChatMessageImpl_getPeerAddress(JNIEnv* env ,jobject thiz ,jlong ptr) { diff --git a/java/common/org/linphone/core/LinphoneChatMessage.java b/java/common/org/linphone/core/LinphoneChatMessage.java index d51a19251..d103ba26d 100644 --- a/java/common/org/linphone/core/LinphoneChatMessage.java +++ b/java/common/org/linphone/core/LinphoneChatMessage.java @@ -74,6 +74,12 @@ public interface LinphoneChatMessage { */ LinphoneAddress getFrom(); + /** + * Get destination address of the LinphoneChatMessage. + * @return the LinphoneAddress in the To field of the message. + */ + LinphoneAddress getTo(); + /** * Linphone message can carry external body as defined by rfc2017 * @param message #LinphoneChatMessage @@ -147,4 +153,5 @@ public interface LinphoneChatMessage { * @return an ErrorInfo. */ ErrorInfo getErrorInfo(); + } diff --git a/java/impl/org/linphone/core/LinphoneChatMessageImpl.java b/java/impl/org/linphone/core/LinphoneChatMessageImpl.java index bcf7d6e14..e44027aeb 100644 --- a/java/impl/org/linphone/core/LinphoneChatMessageImpl.java +++ b/java/impl/org/linphone/core/LinphoneChatMessageImpl.java @@ -48,6 +48,12 @@ public class LinphoneChatMessageImpl implements LinphoneChatMessage { return new LinphoneAddressImpl(getFrom(nativePtr),LinphoneAddressImpl.WrapMode.FromConst); } + private native long getTo(long ptr); + @Override + public LinphoneAddress getTo() { + return new LinphoneAddressImpl(getTo(nativePtr),LinphoneAddressImpl.WrapMode.FromConst); + } + private native void addCustomHeader(long nativePtr, String name, String value); @Override public void addCustomHeader(String name, String value) { From b0badc3759066663c3f50f3691c9a692b78f4e40 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 17:19:47 +0200 Subject: [PATCH 183/218] Add __version__ attribute to the linphone Python module. --- tools/python/apixml2python/linphone_module.mustache | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 9c1b197f6..488d239b1 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -23,6 +23,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include +#include "gitversion.h" + #ifdef WIN32 #include #endif @@ -246,6 +248,7 @@ PyMODINIT_FUNC initlinphone(void) { m = Py_InitModule3("linphone", pylinphone_ModuleMethods, "Python module giving access to the Linphone library."); if (m == NULL) return; + if (PyModule_AddStringConstant(m, "__version__", LINPHONE_GIT_REVISION) < 0) return; {{#enums}} menum = Py_InitModule3("{{enum_name}}", pylinphone_{{enum_name}}_ModuleMethods, "{{{enum_doc}}}"); From 5ada6d74443fc6e2aca7964e89661d6a8a6d122c Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 17:22:44 +0200 Subject: [PATCH 184/218] Don't call instance_methods an array that list all the methods including the class ones. --- tools/python/apixml2python/linphone_module.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 488d239b1..3680fdcd4 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -107,7 +107,7 @@ static PyObject * pylinphone_{{class_name}}_instance_method_{{method_name}}(PyOb {{/class_instance_methods}} -static PyMethodDef pylinphone_{{class_name}}_instance_methods[] = { +static PyMethodDef pylinphone_{{class_name}}_methods[] = { /* Class methods */ {{#class_type_hand_written_methods}} { "{{method_name}}", pylinphone_{{class_name}}_class_method_{{method_name}}, METH_VARARGS | METH_CLASS, "" }, @@ -176,7 +176,7 @@ static PyTypeObject pylinphone_{{class_name}}Type = { 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - pylinphone_{{class_name}}_instance_methods, /* tp_methods */ + pylinphone_{{class_name}}_methods, /* tp_methods */ 0, /* tp_members */ pylinphone_{{class_name}}_getseters, /* tp_getset */ 0, /* tp_base */ From 3603a92ce401548e69f6fefdef874bbe9dd1616a Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Tue, 12 Aug 2014 17:24:28 +0200 Subject: [PATCH 185/218] add jitter buffer settings --- coreapi/linphonecore_jni.cc | 8 ++++++++ java/common/org/linphone/core/LinphoneCore.java | 17 +++++++++++++++++ .../org/linphone/core/LinphoneCoreImpl.java | 15 +++++++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 5e28c52ad..92c253288 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -3412,6 +3412,14 @@ extern "C" void Java_org_linphone_core_LinphoneCoreImpl_setCpuCountNative(JNIEnv ms_set_cpu_count(count); } +extern "C" void Java_org_linphone_core_LinphoneCoreImpl_setAudioJittcomp(JNIEnv *env, jobject thiz, jlong lc, jint value) { + linphone_core_set_audio_jittcomp((LinphoneCore *)lc, value); +} + +extern "C" void Java_org_linphone_core_LinphoneCoreImpl_setVideoJittcomp(JNIEnv *env, jobject thiz, jlong lc, jint value) { + linphone_core_set_video_jittcomp((LinphoneCore *)lc, value); +} + extern "C" void Java_org_linphone_core_LinphoneCoreImpl_setAudioPort(JNIEnv *env, jobject thiz, jlong lc, jint port) { linphone_core_set_audio_port((LinphoneCore *)lc, port); } diff --git a/java/common/org/linphone/core/LinphoneCore.java b/java/common/org/linphone/core/LinphoneCore.java index 5328442fd..54f923dd6 100644 --- a/java/common/org/linphone/core/LinphoneCore.java +++ b/java/common/org/linphone/core/LinphoneCore.java @@ -1623,4 +1623,21 @@ public interface LinphoneCore { * Typical use is to stop ringing when the user requests to ignore the call. **/ public void stopRinging(); + + /** + * Set audio jitter buffer size in milliseconds. + * A value of zero disables the jitter buffer. + * The new value is taken into account immediately for all running or pending calls. + * @param value the jitter buffer size in milliseconds. + */ + public void setAudioJittcomp(int value); + + /** + * Set video jitter buffer size in milliseconds. + * A value of zero disables the jitter buffer. + * The new value is taken into account immediately for all running or pending calls. + * @param value the jitter buffer size in milliseconds. + */ + public void setVideoJittcomp(int value); + } diff --git a/java/impl/org/linphone/core/LinphoneCoreImpl.java b/java/impl/org/linphone/core/LinphoneCoreImpl.java index dc2a9188b..c7dd9f0fa 100644 --- a/java/impl/org/linphone/core/LinphoneCoreImpl.java +++ b/java/impl/org/linphone/core/LinphoneCoreImpl.java @@ -1200,13 +1200,24 @@ class LinphoneCoreImpl implements LinphoneCore { return getPayloadTypeBitrate(nativePtr, ((PayloadTypeImpl)pt).nativePtr); } @Override - public void enableAdaptiveRateControl(boolean enable) { + public synchronized void enableAdaptiveRateControl(boolean enable) { enableAdaptiveRateControl(nativePtr,enable); } @Override - public boolean isAdaptiveRateControlEnabled() { + public synchronized boolean isAdaptiveRateControlEnabled() { return isAdaptiveRateControlEnabled(nativePtr); } + private native void setAudioJittcomp(long ptr, int value); + @Override + public synchronized void setAudioJittcomp(int value) { + setAudioJittcomp(nativePtr,value); + } + private native void setVideoJittcomp(long ptr, int value); + @Override + public synchronized void setVideoJittcomp(int value) { + setVideoJittcomp(nativePtr,value); + } + } From f4423b93bd19438c21c33caafc5cdd43a9164f96 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 12 Aug 2014 17:37:55 +0200 Subject: [PATCH 186/218] Add documentation for the properties in the Python wrapper. --- tools/python/apixml2python/linphone.py | 8 ++++++-- tools/python/apixml2python/linphone_module.mustache | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index dd818ccb1..7d3fe6280 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -846,10 +846,14 @@ class LinphoneModule(object): raise try: for p in c['class_properties']: - if p.has_key('getter_xml_node'): - p['getter_body'] = GetterMethodDefinition(self, c, p['getter_xml_node']).format() + p['property_doc'] = '' if p.has_key('setter_xml_node'): p['setter_body'] = SetterMethodDefinition(self, c, p['setter_xml_node']).format() + p['property_doc'] = self.__format_doc(p['setter_xml_node'].find('briefdescription'), p['setter_xml_node'].find('detaileddescription')) + if p.has_key('getter_xml_node'): + p['getter_body'] = GetterMethodDefinition(self, c, p['getter_xml_node']).format() + if p['property_doc'] == '': + p['property_doc'] = self.__format_doc(p['getter_xml_node'].find('briefdescription'), p['getter_xml_node'].find('detaileddescription')) except Exception, e: e.args += (c['class_name'], p['property_name']) raise diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 3680fdcd4..ee072aeae 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -139,9 +139,8 @@ static PyMethodDef pylinphone_{{class_name}}_methods[] = { {{/class_properties}} static PyGetSetDef pylinphone_{{class_name}}_getseters[] = { - // TODO: Handle doc {{#class_properties}} - { "{{property_name}}", {{getter_reference}}, {{setter_reference}}, "" }, + { "{{property_name}}", {{getter_reference}}, {{setter_reference}}, "{{{property_doc}}}" }, {{/class_properties}} /* Sentinel */ { NULL, NULL, NULL, NULL, NULL } From ab30b93098eb13596c37e06e355bff7bbc1f83f4 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 13 Aug 2014 13:38:20 +0200 Subject: [PATCH 187/218] Blacklist linphone_core_get_video_devices() in the Python wrapper. --- tools/python/apixml2python.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 810dc3edc..dd66dc1ec 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -71,6 +71,7 @@ blacklisted_functions = [ 'linphone_core_get_sip_transports_used', # missing LCSipTransports 'linphone_core_get_supported_video_sizes', # missing MSVideoSizeDef 'linphone_core_get_video_codecs', # missing PayloadType and MSList + 'linphone_core_get_video_devices', # returns a list of strings 'linphone_core_get_video_policy', # missing LinphoneVideoPolicy 'linphone_core_payload_type_enabled', # missing PayloadType 'linphone_core_payload_type_is_vbr', # missing PayloadType From d9f02dd6325974eb06ab589c22878c8df4497955 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 13 Aug 2014 13:38:39 +0200 Subject: [PATCH 188/218] Do not rely on the ChatRoom object to set the send message callback in the Python wrapper. --- .../handwritten_definitions.mustache | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tools/python/apixml2python/handwritten_definitions.mustache b/tools/python/apixml2python/handwritten_definitions.mustache index dd19be207..73baddea6 100644 --- a/tools/python/apixml2python/handwritten_definitions.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -197,9 +197,11 @@ 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 * func = NULL; - pylinphone_ChatRoomObject *pycr = (pylinphone_ChatRoomObject *)ud; + PyObject *pycm = NULL; + PyObject *func = NULL; + PyObject *_dict = (PyObject *)ud; + PyObject *_cb = PyDict_GetItemString(_dict, "callback"); + PyObject *_ud = PyDict_GetItemString(_dict, "user_data"); pygil_state = PyGILState_Ensure(); pycm = linphone_chat_message_get_user_data(msg); @@ -207,8 +209,8 @@ static void pylinphone_ChatRoom_callback_chat_message_state_changed(LinphoneChat pycm = pylinphone_ChatMessage_new_from_native_ptr(&pylinphone_ChatMessageType, msg); } pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p, %p [%p], %d, %p)", __FUNCTION__, pycm, msg, state, ud); - if ((pycr->send_message_cb != NULL) && PyCallable_Check(pycr->send_message_cb)) { - if (PyEval_CallObject(pycr->send_message_cb, Py_BuildValue("OiO", pycm, state, pycr->send_message_ud)) == NULL) { + if ((_cb != NULL) && PyCallable_Check(_cb)) { + if (PyEval_CallObject(_cb, Py_BuildValue("OiO", pycm, state, _ud)) == NULL) { PyErr_Print(); } } @@ -218,6 +220,7 @@ static void pylinphone_ChatRoom_callback_chat_message_state_changed(LinphoneChat 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; @@ -234,18 +237,19 @@ static PyObject * pylinphone_ChatRoom_instance_method_send_message2(PyObject *se PyErr_SetString(PyExc_TypeError, "The msg argument must be a linphone.ChatMessage"); return NULL; } - if (!PyCallable_Check(_cb)) { + 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); - ((pylinphone_ChatRoomObject *)self)->send_message_cb = _cb; - ((pylinphone_ChatRoomObject *)self)->send_message_ud = _ud; - linphone_chat_room_send_message2(native_ptr, _chat_message_native_ptr, pylinphone_ChatRoom_callback_chat_message_state_changed, self); + 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__); From 551fa6bc930ca830bf7459a5aaaf21900d336f03 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 13 Aug 2014 14:10:09 +0200 Subject: [PATCH 189/218] Blacklist linphone_core_get_audio_port_range() and linphone_core_get_video_port_range() functions in the Python wrapper. --- tools/python/apixml2python.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index dd66dc1ec..f71cbb7eb 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -59,6 +59,7 @@ blacklisted_functions = [ 'linphone_core_enable_payload_type', # missing PayloadType 'linphone_core_find_payload_type', # missing PayloadType 'linphone_core_get_audio_codecs', # missing PayloadType and MSList + 'linphone_core_get_audio_port_range', # to be handwritten because of result via arguments 'linphone_core_get_auth_info_list', # missing MSList 'linphone_core_get_call_logs', # missing MSList 'linphone_core_get_calls', # missing MSList @@ -73,6 +74,7 @@ blacklisted_functions = [ 'linphone_core_get_video_codecs', # missing PayloadType and MSList 'linphone_core_get_video_devices', # returns a list of strings 'linphone_core_get_video_policy', # missing LinphoneVideoPolicy + 'linphone_core_get_video_port_range', # to be handwritten because of result via arguments 'linphone_core_payload_type_enabled', # missing PayloadType 'linphone_core_payload_type_is_vbr', # missing PayloadType 'linphone_core_publish', # missing LinphoneContent From d5332aa80a66711e4c97fcf2430c0c3a1abd37b9 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 13 Aug 2014 14:11:27 +0200 Subject: [PATCH 190/218] Document arguments type in the Python wrapper. --- .../handwritten_definitions.mustache | 4 +-- tools/python/apixml2python/linphone.py | 36 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/tools/python/apixml2python/handwritten_definitions.mustache b/tools/python/apixml2python/handwritten_definitions.mustache index 73baddea6..6b6a4eecf 100644 --- a/tools/python/apixml2python/handwritten_definitions.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -284,8 +284,8 @@ static int pylinphone_VideoSize_init(PyObject *self, PyObject *args, PyObject *k } static PyMemberDef pylinphone_VideoSize_members[] = { - { "width", T_INT, offsetof(pylinphone_VideoSizeObject, vs) + offsetof(MSVideoSize, width), 0, "The width of the video" }, - { "height", T_INT, offsetof(pylinphone_VideoSizeObject, vs) + offsetof(MSVideoSize, height), 0, "The height of the video" }, + { "width", T_INT, offsetof(pylinphone_VideoSizeObject, vs) + offsetof(MSVideoSize, width), 0, "[int] The width of the video" }, + { "height", T_INT, offsetof(pylinphone_VideoSizeObject, vs) + offsetof(MSVideoSize, height), 0, "[int] The height of the video" }, { NULL } /* Sentinel */ }; diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 7d3fe6280..4a6fc98fd 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -151,7 +151,10 @@ class ArgumentType: self.fmt_str = 'i' self.cfmt_str = '%d' elif '*' in splitted_type: + self.type_str = 'linphone.' + strip_leading_linphone(self.basic_type) self.use_native_pointer = True + else: + self.type_str = 'linphone.' + strip_leading_linphone(self.basic_type) class MethodDefinition: @@ -849,11 +852,11 @@ class LinphoneModule(object): p['property_doc'] = '' if p.has_key('setter_xml_node'): p['setter_body'] = SetterMethodDefinition(self, c, p['setter_xml_node']).format() - p['property_doc'] = self.__format_doc(p['setter_xml_node'].find('briefdescription'), p['setter_xml_node'].find('detaileddescription')) + p['property_doc'] = self.__format_setter_doc(p['setter_xml_node']) if p.has_key('getter_xml_node'): p['getter_body'] = GetterMethodDefinition(self, c, p['getter_xml_node']).format() if p['property_doc'] == '': - p['property_doc'] = self.__format_doc(p['getter_xml_node'].find('briefdescription'), p['getter_xml_node'].find('detaileddescription')) + p['property_doc'] = self.__format_getter_doc(p['getter_xml_node']) except Exception, e: e.args += (c['class_name'], p['property_name']) raise @@ -932,14 +935,39 @@ class LinphoneModule(object): doc += "\n\nArguments:" for xml_method_arg in xml_method_args: arg_name = xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + argument_type = ArgumentType(arg_type, arg_complete_type, self) arg_doc = self.__format_doc_content(None, xml_method_arg.find('description')) - doc += '\n\t' + arg_name + doc += '\n\t' + arg_name + ' [' + argument_type.type_str + ']' if arg_doc != '': doc += ': ' + arg_doc if xml_method_return is not None: + return_type = xml_method_return.get('type') return_complete_type = xml_method_return.get('completetype') if return_complete_type != 'void': return_doc = self.__format_doc_content(None, xml_method_return.find('description')) - doc += '\n\nReturns:\n\t' + return_doc + return_argument_type = ArgumentType(return_type, return_complete_type, self) + doc += '\n\nReturns:\n\t[' + return_argument_type.type_str + '] ' + return_doc + doc = self.__replace_doc_special_chars(doc) + return doc + + def __format_setter_doc(self, xml_node): + xml_method_arg = xml_node.findall('./arguments/argument')[1] + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + argument_type = ArgumentType(arg_type, arg_complete_type, self) + doc = self.__format_doc_content(xml_node.find('briefdescription'), xml_node.find('detaileddescription')) + doc = '[' + argument_type.type_str + '] ' + doc + doc = self.__replace_doc_special_chars(doc) + return doc + + def __format_getter_doc(self, xml_node): + xml_method_return = xml_node.find('./return') + return_type = xml_method_return.get('type') + return_complete_type = xml_method_return.get('completetype') + return_argument_type = ArgumentType(return_type, return_complete_type, self) + doc = self.__format_doc_content(xml_node.find('briefdescription'), xml_node.find('detaileddescription')) + doc = '[' + return_argument_type.type_str + '] ' + doc doc = self.__replace_doc_special_chars(doc) return doc From efacfee3a3aa855c099210a7e0abb20d2f979b34 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 13 Aug 2014 14:28:48 +0200 Subject: [PATCH 191/218] Standardize user_data/user_pointer naming. --- coreapi/linphonecall.c | 4 ++-- coreapi/linphonecore.c | 4 ++-- coreapi/linphonecore.h | 16 ++++++++++++---- tools/python/apixml2python.py | 4 ---- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index c650894b3..459c8c92f 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1146,7 +1146,7 @@ const LinphoneErrorInfo *linphone_call_get_error_info(const LinphoneCall *call){ * * return user_pointer an opaque user pointer that can be retrieved at any time **/ -void *linphone_call_get_user_pointer(LinphoneCall *call) +void *linphone_call_get_user_data(LinphoneCall *call) { return call->user_pointer; } @@ -1158,7 +1158,7 @@ void *linphone_call_get_user_pointer(LinphoneCall *call) * * the user_pointer is an opaque user pointer that can be retrieved at any time in the LinphoneCall **/ -void linphone_call_set_user_pointer(LinphoneCall *call, void *user_pointer) +void linphone_call_set_user_data(LinphoneCall *call, void *user_pointer) { call->user_pointer = user_pointer; } diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 6818b1415..93f30606e 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -288,14 +288,14 @@ const char *linphone_call_log_get_call_id(const LinphoneCallLog *cl){ /** * Assign a user pointer to the call log. **/ -void linphone_call_log_set_user_pointer(LinphoneCallLog *cl, void *up){ +void linphone_call_log_set_user_data(LinphoneCallLog *cl, void *up){ cl->user_pointer=up; } /** * Returns the user pointer associated with the call log. **/ -void *linphone_call_log_get_user_pointer(const LinphoneCallLog *cl){ +void *linphone_call_log_get_user_data(const LinphoneCallLog *cl){ return cl->user_pointer; } diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 9fdb864df..b08c75395 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -356,8 +356,12 @@ LINPHONE_PUBLIC bool_t linphone_call_log_video_enabled(LinphoneCallLog *cl); LINPHONE_PUBLIC time_t linphone_call_log_get_start_date(LinphoneCallLog *cl); LINPHONE_PUBLIC int linphone_call_log_get_duration(LinphoneCallLog *cl); LINPHONE_PUBLIC float linphone_call_log_get_quality(LinphoneCallLog *cl); -LINPHONE_PUBLIC void linphone_call_log_set_user_pointer(LinphoneCallLog *cl, void *up); -LINPHONE_PUBLIC void *linphone_call_log_get_user_pointer(const LinphoneCallLog *cl); +/** @deprecated Use linphone_call_log_set_user_data() instead. */ +#define linphone_call_log_set_user_pointer(cl, ud) linphone_call_log_set_user_data(cl, ud) +LINPHONE_PUBLIC void linphone_call_log_set_user_data(LinphoneCallLog *cl, void *up); +/** @deprecated Use linphone_call_log_get_user_data() instead. */ +#define linphone_call_log_get_user_pointer(cl) linphone_call_log_get_user_data(cl) +LINPHONE_PUBLIC void *linphone_call_log_get_user_data(const LinphoneCallLog *cl); void linphone_call_log_set_ref_key(LinphoneCallLog *cl, const char *refkey); const char *linphone_call_log_get_ref_key(const LinphoneCallLog *cl); LINPHONE_PUBLIC const rtp_stats_t *linphone_call_log_get_local_stats(const LinphoneCallLog *cl); @@ -717,8 +721,12 @@ LINPHONE_PUBLIC const char* linphone_call_get_authentication_token(LinphoneCall LINPHONE_PUBLIC bool_t linphone_call_get_authentication_token_verified(LinphoneCall *call); LINPHONE_PUBLIC void linphone_call_set_authentication_token_verified(LinphoneCall *call, bool_t verified); LINPHONE_PUBLIC void linphone_call_send_vfu_request(LinphoneCall *call); -LINPHONE_PUBLIC void *linphone_call_get_user_pointer(LinphoneCall *call); -LINPHONE_PUBLIC void linphone_call_set_user_pointer(LinphoneCall *call, void *user_pointer); +/** @deprecated Use linphone_call_get_user_data() instead. */ +#define linphone_call_get_user_pointer(call) linphone_call_get_user_data(call) +LINPHONE_PUBLIC void *linphone_call_get_user_data(LinphoneCall *call); +/** @deprecated Use linphone_call_set_user_data() instead. */ +#define linphone_call_set_user_pointer(call, ud) linphone_call_set_user_data(call, ud) +LINPHONE_PUBLIC void linphone_call_set_user_data(LinphoneCall *call, void *user_data); LINPHONE_PUBLIC void linphone_call_set_next_video_frame_decoded_callback(LinphoneCall *call, LinphoneCallCbFunc cb, void* user_data); LINPHONE_PUBLIC LinphoneCallState linphone_call_get_transfer_state(LinphoneCall *call); LINPHONE_PUBLIC void linphone_call_zoom_video(LinphoneCall* call, float zoom_factor, float* cx, float* cy); diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index f71cbb7eb..34770b328 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -38,13 +38,9 @@ blacklisted_events = [ 'LinphoneCoreFileTransferSendCb' # missing LinphoneContent ] blacklisted_functions = [ - 'linphone_call_get_user_pointer', # rename to linphone_call_get_user_data - 'linphone_call_set_user_pointer', # rename to linphone_call_set_user_data 'linphone_call_log_get_local_stats', # missing rtp_stats_t 'linphone_call_log_get_remote_stats', # missing rtp_stats_t 'linphone_call_log_get_start_date', # missing time_t - 'linphone_call_log_get_user_pointer', # rename to linphone_call_log_get_user_data - 'linphone_call_log_set_user_pointer', # rename to linphone_call_log_set_user_data 'linphone_call_params_get_privacy', # missing LinphonePrivacyMask 'linphone_call_params_get_used_audio_codec', # missing PayloadType 'linphone_call_params_get_used_video_codec', # missing PayloadType From 0cc60a9d312ab603561cd6b42a7594175af3e0a6 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 13 Aug 2014 15:07:53 +0200 Subject: [PATCH 192/218] Remove useless variable definitions in the Python wrapper. --- tools/python/apixml2python/linphone.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 4a6fc98fd..884576a93 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -749,8 +749,6 @@ class LinphoneModule(object): ev['event_name'] = compute_event_name(ev['event_cname']) ev['event_doc'] = self.__format_doc(xml_event.find('briefdescription'), xml_event.find('detaileddescription')) self.events.append(ev) - elif c['class_name'] == 'ChatRoom': - c['class_object_members'] = "\tPyObject *send_message_cb;\n\tPyObject *send_message_ud;" xml_type_methods = xml_class.findall("./classmethods/classmethod") for xml_type_method in xml_type_methods: if xml_type_method.get('deprecated') == 'true': From 31ab25d815e2fa4237d9be074c388bc292cdcbcc Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 13 Aug 2014 16:10:48 +0200 Subject: [PATCH 193/218] Handle PayloadType objects in the Python wrapper. --- coreapi/linphonecall.c | 8 ++--- coreapi/linphonecore.c | 21 +++++++++-- coreapi/linphonecore.h | 68 ++++++++++++++++++++++++++++------- coreapi/misc.c | 10 +++--- tools/genapixml.py | 5 +++ tools/python/apixml2python.py | 8 ----- 6 files changed, 88 insertions(+), 32 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 459c8c92f..9d8b6060c 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1342,17 +1342,17 @@ void linphone_call_params_enable_video(LinphoneCallParams *cp, bool_t enabled){ } /** - * Returns the audio codec used in the call, described as a PayloadType structure. + * Returns the audio codec used in the call, described as a LinphonePayloadType structure. **/ -const PayloadType* linphone_call_params_get_used_audio_codec(const LinphoneCallParams *cp) { +const LinphonePayloadType* linphone_call_params_get_used_audio_codec(const LinphoneCallParams *cp) { return cp->audio_codec; } /** - * Returns the video codec used in the call, described as a PayloadType structure. + * Returns the video codec used in the call, described as a LinphonePayloadType structure. **/ -const PayloadType* linphone_call_params_get_used_video_codec(const LinphoneCallParams *cp) { +const LinphonePayloadType* linphone_call_params_get_used_video_codec(const LinphoneCallParams *cp) { return cp->video_codec; } diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 93f30606e..dbed18003 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -6260,8 +6260,8 @@ static PayloadType* find_payload_type_from_list(const char* type, int rate, int } -PayloadType* linphone_core_find_payload_type(LinphoneCore* lc, const char* type, int rate, int channels) { - PayloadType* result = find_payload_type_from_list(type, rate, channels, linphone_core_get_audio_codecs(lc)); +LinphonePayloadType* linphone_core_find_payload_type(LinphoneCore* lc, const char* type, int rate, int channels) { + LinphonePayloadType* result = find_payload_type_from_list(type, rate, channels, linphone_core_get_audio_codecs(lc)); if (result) { return result; } else { @@ -6714,3 +6714,20 @@ bool_t linphone_core_sdp_200_ack_enabled(const LinphoneCore *lc) { void linphone_core_set_file_transfer_server(LinphoneCore *core, const char * server_url) { core->file_transfer_server=ms_strdup(server_url); } + + +int linphone_payload_type_get_type(const LinphonePayloadType *pt) { + return pt->type; +} + +int linphone_payload_type_get_normal_bitrate(const LinphonePayloadType *pt) { + return pt->normal_bitrate; +} + +char * linphone_payload_type_get_mime_type(const LinphonePayloadType *pt) { + return pt->mime_type; +} + +int linphone_payload_type_get_channels(const LinphonePayloadType *pt) { + return pt->channels; +} diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index b08c75395..7147f30e7 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -247,6 +247,48 @@ LinphoneDictionary* lp_config_section_to_dict( const LpConfig* lpconfig, const c void lp_config_load_dict_to_section( LpConfig* lpconfig, const char* section, const LinphoneDictionary* dict); +/** + * @addtogroup media_parameters + * @{ +**/ + +/** + * Object representing an RTP payload type. + */ +typedef PayloadType LinphonePayloadType; + +/** + * Get the type of payload. + * @param[in] pt LinphonePayloadType object + * @return The type of payload. + */ +LINPHONE_PUBLIC int linphone_payload_type_get_type(const LinphonePayloadType *pt); + +/** + * Get the normal bitrate in bits/s. + * @param[in] pt LinphonePayloadType object + * @return The normal bitrate in bits/s. + */ +LINPHONE_PUBLIC int linphone_payload_type_get_normal_bitrate(const LinphonePayloadType *pt); + +/** + * Get the mime type. + * @param[in] pt LinphonePayloadType object + * @return The mime type. + */ +LINPHONE_PUBLIC char * linphone_payload_type_get_mime_type(const LinphonePayloadType *pt); + +/** + * Get the number of channels. + * @param[in] pt LinphonePayloadType object + * @return The number of channels. + */ +LINPHONE_PUBLIC int linphone_payload_type_get_channels(const LinphonePayloadType *pt); + +/** + * @} +**/ + #ifdef IN_LINPHONE #include "linphonefriend.h" #include "event.h" @@ -383,8 +425,8 @@ struct _LinphoneCallParams; **/ typedef struct _LinphoneCallParams LinphoneCallParams; -LINPHONE_PUBLIC const PayloadType* linphone_call_params_get_used_audio_codec(const LinphoneCallParams *cp); -LINPHONE_PUBLIC const PayloadType* linphone_call_params_get_used_video_codec(const LinphoneCallParams *cp); +LINPHONE_PUBLIC const LinphonePayloadType* linphone_call_params_get_used_audio_codec(const LinphoneCallParams *cp); +LINPHONE_PUBLIC const LinphonePayloadType* linphone_call_params_get_used_video_codec(const LinphoneCallParams *cp); LINPHONE_PUBLIC LinphoneCallParams * linphone_call_params_copy(const LinphoneCallParams *cp); LINPHONE_PUBLIC void linphone_call_params_enable_video(LinphoneCallParams *cp, bool_t enabled); LINPHONE_PUBLIC bool_t linphone_call_params_video_enabled(const LinphoneCallParams *cp); @@ -1887,48 +1929,48 @@ LINPHONE_PUBLIC int linphone_core_set_video_codecs(LinphoneCore *lc, MSList *cod /** * Tells whether the specified payload type is enabled. * @param[in] lc #LinphoneCore object. - * @param[in] pt The #PayloadType we want to know is enabled or not. + * @param[in] pt The #LinphonePayloadType we want to know is enabled or not. * @returns TRUE if the payload type is enabled, FALSE if disabled. * @ingroup media_parameters */ -LINPHONE_PUBLIC bool_t linphone_core_payload_type_enabled(LinphoneCore *lc, const PayloadType *pt); +LINPHONE_PUBLIC bool_t linphone_core_payload_type_enabled(LinphoneCore *lc, const LinphonePayloadType *pt); /** * Tells whether the specified payload type represents a variable bitrate codec. * @param[in] lc #LinphoneCore object. - * @param[in] pt The #PayloadType we want to know + * @param[in] pt The #LinphonePayloadType we want to know * @returns TRUE if the payload type represents a VBR codec, FALSE if disabled. * @ingroup media_parameters */ -LINPHONE_PUBLIC bool_t linphone_core_payload_type_is_vbr(LinphoneCore *lc, const PayloadType *pt); +LINPHONE_PUBLIC bool_t linphone_core_payload_type_is_vbr(LinphoneCore *lc, const LinphonePayloadType *pt); /** * Set an explicit bitrate (IP bitrate, not codec bitrate) for a given codec, in kbit/s. * @param[in] lc the #LinphoneCore object - * @param[in] pt the #PayloadType to modify. + * @param[in] pt the #LinphonePayloadType to modify. * @param[in] bitrate the IP bitrate in kbit/s. * @ingroup media_parameters **/ -LINPHONE_PUBLIC void linphone_core_set_payload_type_bitrate(LinphoneCore *lc, PayloadType *pt, int bitrate); +LINPHONE_PUBLIC void linphone_core_set_payload_type_bitrate(LinphoneCore *lc, LinphonePayloadType *pt, int bitrate); /** * Get the bitrate explicitely set with linphone_core_set_payload_type_bitrate(). * @param[in] lc the #LinphoneCore object - * @param[in] pt the #PayloadType to modify. + * @param[in] pt the #LinphonePayloadType to modify. * @return bitrate the IP bitrate in kbit/s, or -1 if an error occured. * @ingroup media_parameters **/ -LINPHONE_PUBLIC int linphone_core_get_payload_type_bitrate(LinphoneCore *lc, const PayloadType *pt); +LINPHONE_PUBLIC int linphone_core_get_payload_type_bitrate(LinphoneCore *lc, const LinphonePayloadType *pt); /** * Enable or disable the use of the specified payload type. * @param[in] lc #LinphoneCore object. - * @param[in] pt The #PayloadType to enable or disable. It can be retrieved using #linphone_core_find_payload_type + * @param[in] pt The #LinphonePayloadType to enable or disable. It can be retrieved using #linphone_core_find_payload_type * @param[in] enable TRUE to enable the payload type, FALSE to disable it. * @return 0 if successful, any other value otherwise. * @ingroup media_parameters */ -LINPHONE_PUBLIC int linphone_core_enable_payload_type(LinphoneCore *lc, PayloadType *pt, bool_t enable); +LINPHONE_PUBLIC int linphone_core_enable_payload_type(LinphoneCore *lc, LinphonePayloadType *pt, bool_t enable); /** * Wildcard value used by #linphone_core_find_payload_type to ignore rate in search algorithm @@ -1950,7 +1992,7 @@ LINPHONE_PUBLIC int linphone_core_enable_payload_type(LinphoneCore *lc, PayloadT * @param channels number of channels, can be #LINPHONE_FIND_PAYLOAD_IGNORE_CHANNELS * @return Returns NULL if not found. */ -LINPHONE_PUBLIC PayloadType* linphone_core_find_payload_type(LinphoneCore* lc, const char* type, int rate, int channels) ; +LINPHONE_PUBLIC LinphonePayloadType* linphone_core_find_payload_type(LinphoneCore* lc, const char* type, int rate, int channels) ; LINPHONE_PUBLIC int linphone_core_get_payload_type_number(LinphoneCore *lc, const PayloadType *pt); diff --git a/coreapi/misc.c b/coreapi/misc.c index 38b0efa5d..d4bf48cd5 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -69,7 +69,7 @@ static bool_t payload_type_enabled(const PayloadType *pt) { return (((pt)->flags & PAYLOAD_TYPE_ENABLED)!=0); } -bool_t linphone_core_payload_type_enabled(LinphoneCore *lc, const PayloadType *pt){ +bool_t linphone_core_payload_type_enabled(LinphoneCore *lc, const LinphonePayloadType *pt){ if (ms_list_find(lc->codecs_conf.audio_codecs, (PayloadType*) pt) || ms_list_find(lc->codecs_conf.video_codecs, (PayloadType*)pt)){ return payload_type_enabled(pt); } @@ -77,12 +77,12 @@ bool_t linphone_core_payload_type_enabled(LinphoneCore *lc, const PayloadType *p return FALSE; } -bool_t linphone_core_payload_type_is_vbr(LinphoneCore *lc, const PayloadType *pt){ +bool_t linphone_core_payload_type_is_vbr(LinphoneCore *lc, const LinphonePayloadType *pt){ if (pt->type==PAYLOAD_VIDEO) return TRUE; return !!(pt->flags & PAYLOAD_TYPE_IS_VBR); } -int linphone_core_enable_payload_type(LinphoneCore *lc, PayloadType *pt, bool_t enabled){ +int linphone_core_enable_payload_type(LinphoneCore *lc, LinphonePayloadType *pt, bool_t enabled){ if (ms_list_find(lc->codecs_conf.audio_codecs,pt) || ms_list_find(lc->codecs_conf.video_codecs,pt)){ payload_type_set_enable(pt,enabled); _linphone_core_codec_config_write(lc); @@ -108,7 +108,7 @@ const char *linphone_core_get_payload_type_description(LinphoneCore *lc, Payload return NULL; } -void linphone_core_set_payload_type_bitrate(LinphoneCore *lc, PayloadType *pt, int bitrate){ +void linphone_core_set_payload_type_bitrate(LinphoneCore *lc, LinphonePayloadType *pt, int bitrate){ if (ms_list_find(lc->codecs_conf.audio_codecs, (PayloadType*) pt) || ms_list_find(lc->codecs_conf.video_codecs, (PayloadType*)pt)){ if (pt->type==PAYLOAD_VIDEO || pt->flags & PAYLOAD_TYPE_IS_VBR){ pt->normal_bitrate=bitrate*1000; @@ -181,7 +181,7 @@ static int get_audio_payload_bandwidth(LinphoneCore *lc, const PayloadType *pt, }else return (int)ceil(get_audio_payload_bandwidth_from_codec_bitrate(pt)/1000.0);/*rounding codec bandwidth should be avoid, specially for AMR*/ } -int linphone_core_get_payload_type_bitrate(LinphoneCore *lc, const PayloadType *pt){ +int linphone_core_get_payload_type_bitrate(LinphoneCore *lc, const LinphonePayloadType *pt){ int maxbw=get_min_bandwidth(linphone_core_get_download_bandwidth(lc), linphone_core_get_upload_bandwidth(lc)); if (pt->type==PAYLOAD_AUDIO_CONTINUOUS || pt->type==PAYLOAD_AUDIO_PACKETIZED){ diff --git a/tools/genapixml.py b/tools/genapixml.py index 388870e2c..2c9286792 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -335,6 +335,11 @@ class Project: if st.associatedTypedef == td: self.add(CClass(st)) break + elif ('Linphone' + td.definition) == td.name: + st = CStruct(td.name) + st.associatedTypedef = td + self.add(st) + self.add(CClass(st)) # Sort classes by length of name (longest first), so that methods are put in the right class self.classes.sort(key = lambda c: len(c.name), reverse = True) for e in self.__events: diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 34770b328..5b6876a34 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -42,8 +42,6 @@ blacklisted_functions = [ 'linphone_call_log_get_remote_stats', # missing rtp_stats_t 'linphone_call_log_get_start_date', # missing time_t 'linphone_call_params_get_privacy', # missing LinphonePrivacyMask - 'linphone_call_params_get_used_audio_codec', # missing PayloadType - 'linphone_call_params_get_used_video_codec', # missing PayloadType 'linphone_call_params_set_privacy', # missing LinphonePrivacyMask 'linphone_chat_message_get_file_transfer_information', # missing LinphoneContent 'linphone_chat_message_get_time', # missing time_t @@ -52,8 +50,6 @@ blacklisted_functions = [ 'linphone_chat_room_create_file_transfer_message', # missing LinphoneContent 'linphone_chat_room_create_message_2', # missing time_t 'linphone_core_can_we_add_call', # private function - 'linphone_core_enable_payload_type', # missing PayloadType - 'linphone_core_find_payload_type', # missing PayloadType 'linphone_core_get_audio_codecs', # missing PayloadType and MSList 'linphone_core_get_audio_port_range', # to be handwritten because of result via arguments 'linphone_core_get_auth_info_list', # missing MSList @@ -61,7 +57,6 @@ blacklisted_functions = [ 'linphone_core_get_calls', # missing MSList 'linphone_core_get_chat_rooms', # missing MSList 'linphone_core_get_default_proxy', # to be handwritten because of double pointer indirection - 'linphone_core_get_payload_type_bitrate', # missing PayloadType 'linphone_core_get_friend_list', # missing MSList 'linphone_core_get_proxy_config_list', # missing MSList 'linphone_core_get_sip_transports', # missing LCSipTransports @@ -71,14 +66,11 @@ blacklisted_functions = [ 'linphone_core_get_video_devices', # returns a list of strings 'linphone_core_get_video_policy', # missing LinphoneVideoPolicy 'linphone_core_get_video_port_range', # to be handwritten because of result via arguments - 'linphone_core_payload_type_enabled', # missing PayloadType - 'linphone_core_payload_type_is_vbr', # missing PayloadType 'linphone_core_publish', # missing LinphoneContent 'linphone_core_serialize_logs', # There is no use to wrap this function 'linphone_core_set_log_file', # There is no use to wrap this function 'linphone_core_set_log_handler', # Hand-written but put directly in the linphone module 'linphone_core_set_log_level', # There is no use to wrap this function - 'linphone_core_set_payload_type_bitrate', # missing PayloadType 'linphone_core_set_video_policy', # missing LinphoneVideoPolicy 'linphone_core_set_audio_codecs', # missing PayloadType and MSList 'linphone_core_set_sip_transports', # missing LCSipTransports From 8e7a9cbd744e753ce5e0ff0b9f82ab73a2636c6f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 13 Aug 2014 16:32:22 +0200 Subject: [PATCH 194/218] Add definitions of the types of PayloadType in the Python wrapper. --- .../handwritten_definitions.mustache | 39 +++++++++++++++++++ .../apixml2python/linphone_module.mustache | 9 +++++ 2 files changed, 48 insertions(+) diff --git a/tools/python/apixml2python/handwritten_definitions.mustache b/tools/python/apixml2python/handwritten_definitions.mustache index 6b6a4eecf..30ceb33c7 100644 --- a/tools/python/apixml2python/handwritten_definitions.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -365,3 +365,42 @@ PyObject * PyLinphoneVideoSize_FromMSVideoSize(MSVideoSize vs) { } return pyret; } + + +static PyObject * pylinphone_PayloadTypeType_module_method_string(PyObject *self, PyObject *args) { + const char *value_str = "[invalid]"; + int value; + PyObject *pyret; + if (!PyArg_ParseTuple(args, "i", &value)) { + return NULL; + } + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%d)", __FUNCTION__, value); + switch (value) { + case PAYLOAD_AUDIO_CONTINUOUS: + value_str = "PAYLOAD_AUDIO_CONTINUOUS"; + break; + case PAYLOAD_AUDIO_PACKETIZED: + value_str = "PAYLOAD_AUDIO_PACKETIZED"; + break; + case PAYLOAD_VIDEO: + value_str = "PAYLOAD_VIDEO"; + break; + case PAYLOAD_TEXT: + value_str = "PAYLOAD_TEXT"; + break; + case PAYLOAD_OTHER: + value_str = "PAYLOAD_OTHER"; + break; + default: + break; + } + pyret = Py_BuildValue("z", value_str); + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> %p", __FUNCTION__, pyret); + return pyret; +} + +static PyMethodDef pylinphone_PayloadTypeType_ModuleMethods[] = { + { "string", pylinphone_PayloadTypeType_module_method_string, METH_VARARGS, "Get a string representation of a linphone.PayloadTypeType value." }, + /* Sentinel */ + { NULL, NULL, 0, NULL } +}; diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index ee072aeae..02f1920ac 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -258,6 +258,15 @@ PyMODINIT_FUNC initlinphone(void) { {{/enum_values}} {{/enums}} + menum = Py_InitModule3("PayloadTypeType", pylinphone_PayloadTypeType_ModuleMethods, "Type of linphone.PayloadType."); + if (menum == NULL) return; + if (PyModule_AddObject(m, "PayloadTypeType", menum) < 0) return; + if (PyModule_AddIntConstant(menum, "PAYLOAD_AUDIO_CONTINUOUS", PAYLOAD_AUDIO_CONTINUOUS) < 0) return; + if (PyModule_AddIntConstant(menum, "PAYLOAD_AUDIO_PACKETIZED", PAYLOAD_AUDIO_PACKETIZED) < 0) return; + if (PyModule_AddIntConstant(menum, "PAYLOAD_VIDEO", PAYLOAD_VIDEO) < 0) return; + if (PyModule_AddIntConstant(menum, "PAYLOAD_TEXT", PAYLOAD_TEXT) < 0) return; + if (PyModule_AddIntConstant(menum, "PAYLOAD_OTHER", PAYLOAD_OTHER) < 0) return; + {{#classes}} Py_INCREF(&pylinphone_{{class_name}}Type); PyModule_AddObject(m, "{{class_name}}", (PyObject *)&pylinphone_{{class_name}}Type); From ebad6bca11dc9449163794bdfc954bb21c3eac10 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 13 Aug 2014 18:11:08 +0200 Subject: [PATCH 195/218] Handwritten implementation of linphone_core_get_video_devices() in the Python wrapper. --- tools/python/apixml2python.py | 2 +- .../handwritten_declarations.mustache | 2 + .../handwritten_definitions.mustache | 27 +++++++++ tools/python/apixml2python/linphone.py | 59 ++++++++++++------- .../apixml2python/linphone_module.mustache | 3 + 5 files changed, 70 insertions(+), 23 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 5b6876a34..dd37e2027 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -63,7 +63,6 @@ blacklisted_functions = [ 'linphone_core_get_sip_transports_used', # missing LCSipTransports 'linphone_core_get_supported_video_sizes', # missing MSVideoSizeDef 'linphone_core_get_video_codecs', # missing PayloadType and MSList - 'linphone_core_get_video_devices', # returns a list of strings 'linphone_core_get_video_policy', # missing LinphoneVideoPolicy 'linphone_core_get_video_port_range', # to be handwritten because of result via arguments 'linphone_core_publish', # missing LinphoneContent @@ -94,6 +93,7 @@ blacklisted_functions = [ ] hand_written_functions = [ 'linphone_chat_room_send_message2', + 'linphone_core_get_video_devices', 'linphone_core_new', 'linphone_core_new_with_config' ] diff --git a/tools/python/apixml2python/handwritten_declarations.mustache b/tools/python/apixml2python/handwritten_declarations.mustache index 1d27b9963..4a50a848b 100644 --- a/tools/python/apixml2python/handwritten_declarations.mustache +++ b/tools/python/apixml2python/handwritten_declarations.mustache @@ -1,3 +1,5 @@ +static PyObject * pylinphone_Core_get_video_devices(PyObject *self, void *closure); + static PyTypeObject pylinphone_VideoSizeType; typedef struct { diff --git a/tools/python/apixml2python/handwritten_definitions.mustache b/tools/python/apixml2python/handwritten_definitions.mustache index 30ceb33c7..b3d043c32 100644 --- a/tools/python/apixml2python/handwritten_definitions.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -114,6 +114,33 @@ static PyObject * pylinphone_module_method_set_log_handler(PyObject *self, PyObj Py_RETURN_NONE; } + +static PyObject * pylinphone_Core_get_video_devices(PyObject *self, void *closure) { + PyObject *_list; + char **_devices; + char *_device; + LinphoneCore *native_ptr = pylinphone_Core_get_native_ptr(self); + + if (native_ptr == NULL) { + PyErr_SetString(PyExc_TypeError, "Invalid linphone.Core instance"); + return NULL; + } + + pylinphone_trace(1, "[PYLINPHONE] >>> %s(%p [%p])", __FUNCTION__, self, native_ptr); + _devices = linphone_core_get_video_devices(native_ptr); + pylinphone_dispatch_messages(); + + _list = PyList_New(0); + while (*_devices != NULL) { + PyObject *_item = PyString_FromString(*_devices); + PyList_Append(_list, _item); + _devices++; + } + + pylinphone_trace(-1, "[PYLINPHONE] <<< %s -> %p", __FUNCTION__, _list); + return _list; +} + static PyObject * pylinphone_Core_class_method_new(PyObject *cls, PyObject *args) { LinphoneCore * cresult; pylinphone_CoreObject *self; diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 884576a93..38253fd94 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -734,6 +734,7 @@ class LinphoneModule(object): c['class_type_methods'] = [] c['class_type_hand_written_methods'] = [] c['class_instance_hand_written_methods'] = [] + c['class_hand_written_properties'] = [] c['class_object_members'] = '' if c['class_name'] == 'Core': c['class_object_members'] = "\tPyObject *vtable_dict;" @@ -792,31 +793,45 @@ class LinphoneModule(object): p['property_name'] = property_name xml_property_getter = xml_property.find("./getter") xml_property_setter = xml_property.find("./setter") - if xml_property_getter is not None and ( - xml_property_getter.get('name') in blacklisted_functions or xml_property_getter.get('deprecated') == 'true'): - continue - if xml_property_setter is not None and ( - xml_property_setter.get('name') in blacklisted_functions or xml_property_setter.get('deprecated') == 'true'): - continue + handwritten_property = False if xml_property_getter is not None: - xml_property_getter.set('property_name', property_name) - p['getter_name'] = xml_property_getter.get('name').replace(c['class_c_function_prefix'], '') - p['getter_xml_node'] = xml_property_getter - p['getter_reference'] = "(getter)pylinphone_" + c['class_name'] + "_" + p['getter_name'] - p['getter_definition_begin'] = "static PyObject * pylinphone_" + c['class_name'] + "_" + p['getter_name'] + "(PyObject *self, void *closure) {" - p['getter_definition_end'] = "}" - else: - p['getter_reference'] = "NULL" + if xml_property_getter.get('name') in blacklisted_functions or xml_property_getter.get('deprecated') == 'true': + continue + elif xml_property_getter.get('name') in hand_written_functions: + handwritten_property = True if xml_property_setter is not None: - xml_property_setter.set('property_name', property_name) - p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '') - p['setter_xml_node'] = xml_property_setter - p['setter_reference'] = "(setter)pylinphone_" + c['class_name'] + "_" + p['setter_name'] - p['setter_definition_begin'] = "static int pylinphone_" + c['class_name'] + "_" + p['setter_name'] + "(PyObject *self, PyObject *value, void *closure) {" - p['setter_definition_end'] = "}" + if xml_property_setter.get('name') in blacklisted_functions or xml_property_setter.get('deprecated') == 'true': + continue + elif xml_property_setter.get('name') in hand_written_functions: + handwritten_property = True + if handwritten_property: + p['getter_reference'] = 'NULL' + p['setter_reference'] = 'NULL' + if xml_property_getter is not None: + p['getter_reference'] = '(getter)pylinphone_' + c['class_name'] + '_get_' + p['property_name'] + if xml_property_setter is not None: + p['setter_reference'] = '(setter)pylinphone_' + c['class_name'] + '_set_' + p['property_name'] + c['class_hand_written_properties'].append(p) else: - p['setter_reference'] = "NULL" - c['class_properties'].append(p) + if xml_property_getter is not None: + xml_property_getter.set('property_name', property_name) + p['getter_name'] = xml_property_getter.get('name').replace(c['class_c_function_prefix'], '') + p['getter_xml_node'] = xml_property_getter + p['getter_reference'] = "(getter)pylinphone_" + c['class_name'] + "_" + p['getter_name'] + p['getter_definition_begin'] = "static PyObject * pylinphone_" + c['class_name'] + "_" + p['getter_name'] + "(PyObject *self, void *closure) {" + p['getter_definition_end'] = "}" + else: + p['getter_reference'] = "NULL" + if xml_property_setter is not None: + xml_property_setter.set('property_name', property_name) + p['setter_name'] = xml_property_setter.get('name').replace(c['class_c_function_prefix'], '') + p['setter_xml_node'] = xml_property_setter + p['setter_reference'] = "(setter)pylinphone_" + c['class_name'] + "_" + p['setter_name'] + p['setter_definition_begin'] = "static int pylinphone_" + c['class_name'] + "_" + p['setter_name'] + "(PyObject *self, PyObject *value, void *closure) {" + p['setter_definition_end'] = "}" + else: + p['setter_reference'] = "NULL" + c['class_properties'].append(p) self.classes.append(c) # Format events definitions for ev in self.events: diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 02f1920ac..005f05edb 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -139,6 +139,9 @@ static PyMethodDef pylinphone_{{class_name}}_methods[] = { {{/class_properties}} static PyGetSetDef pylinphone_{{class_name}}_getseters[] = { +{{#class_hand_written_properties}} + { "{{property_name}}", {{getter_reference}}, {{setter_reference}}, "" }, +{{/class_hand_written_properties}} {{#class_properties}} { "{{property_name}}", {{getter_reference}}, {{setter_reference}}, "{{{property_doc}}}" }, {{/class_properties}} From 61d1f7b5de76f2208943bcbd877a3ceb830bbcc5 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 13 Aug 2014 19:24:01 +0200 Subject: [PATCH 196/218] allow fps change in linphone_core_update_call() --- coreapi/linphonecore.c | 1 + coreapi/message_storage.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index dbed18003..59c46fee2 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -3279,6 +3279,7 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho #ifdef VIDEO_ENABLED if ((call->videostream != NULL) && (call->state == LinphoneCallStreamsRunning)) { video_stream_set_sent_video_size(call->videostream,linphone_core_get_preferred_video_size(lc)); + video_stream_set_fps(call->videostream, linphone_core_get_preferred_framerate(lc)); if (call->camera_enabled && call->videostream->cam!=lc->video_conf.device){ video_stream_change_camera(call->videostream,lc->video_conf.device); }else video_stream_update_video_params(call->videostream); diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 7caf4536b..d2f83b12a 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -54,6 +54,7 @@ static inline LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, u static void create_chat_message(char **argv, void *data){ LinphoneChatRoom *cr = (LinphoneChatRoom *)data; LinphoneAddress *from; + LinphoneAddress *to; unsigned int storage_id = atoi(argv[0]); @@ -65,12 +66,18 @@ static void create_chat_message(char **argv, void *data){ if(atoi(argv[3])==LinphoneChatMessageIncoming){ new_message->dir=LinphoneChatMessageIncoming; from=linphone_address_new(argv[2]); + to=linphone_address_new(argv[1]); } else { new_message->dir=LinphoneChatMessageOutgoing; from=linphone_address_new(argv[1]); + to=linphone_address_new(argv[2]); } linphone_chat_message_set_from(new_message,from); linphone_address_destroy(from); + if (to){ + linphone_chat_message_set_to(new_message,to); + linphone_address_destroy(to); + } if( argv[9] != NULL ){ new_message->time = (time_t)atol(argv[9]); From 82e1a90ba69ecfc99a47bf83b62edd66ceba46d6 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 14 Aug 2014 11:21:33 +0200 Subject: [PATCH 197/218] Improve int types checking in the Python wrapper. --- tools/python/apixml2python/linphone.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 38253fd94..2b57bf35f 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -70,20 +70,20 @@ class ArgumentType: elif self.basic_type == 'int': if 'unsigned' in splitted_type: self.type_str = 'unsigned int' - self.check_func = 'PyLong_Check' - self.convert_func = 'PyLong_AsUnsignedLong' + self.check_func = 'PyInt_Check' + self.convert_func = 'PyInt_AsUnsignedLongMask' self.fmt_str = 'I' self.cfmt_str = '%u' else: self.type_str = 'int' - self.check_func = 'PyLong_Check' - self.convert_func = 'PyLong_AsLong' + self.check_func = 'PyInt_Check' + self.convert_func = 'PyInt_AS_LONG' self.fmt_str = 'i' self.cfmt_str = '%d' elif self.basic_type in ['int8_t', 'int16_t' 'int32_t']: self.type_str = 'int' - self.check_func = 'PyLong_Check' - self.convert_func = 'PyLong_AsLong' + self.check_func = 'PyInt_Check' + self.convert_func = 'PyInt_AS_LONG' if self.basic_type == 'int8_t': self.fmt_str = 'c' elif self.basic_type == 'int16_t': @@ -93,8 +93,8 @@ class ArgumentType: self.cfmt_str = '%d' elif self.basic_type in ['uint8_t', 'uint16_t', 'uint32_t']: self.type_str = 'unsigned int' - self.check_func = 'PyLong_Check' - self.convert_func = 'PyLong_AsUnsignedLong' + self.check_func = 'PyInt_Check' + self.convert_func = 'PyInt_AsUnsignedLongMask' if self.basic_type == 'uint8_t': self.fmt_str = 'b' elif self.basic_type == 'uint16_t': @@ -116,8 +116,8 @@ class ArgumentType: self.cfmt_str = '%lu' elif self.basic_type == 'size_t': self.type_str = 'size_t' - self.check_func = 'PyLong_Check' - self.convert_func = 'PyLong_AsSsize_t' + self.check_func = 'PyInt_Check' + self.convert_func = 'PyInt_AsSsize_t' self.fmt_str = 'n' self.cfmt_str = '%lu' elif self.basic_type in ['float', 'double']: From e5d15ca06a73f8d80345accf6dd752dd963aab6f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 14 Aug 2014 12:51:11 +0200 Subject: [PATCH 198/218] Improve argument checking in the Python wrapper. --- tools/python/apixml2python/linphone.py | 45 +++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 2b57bf35f..e2e13b94b 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -195,7 +195,7 @@ class MethodDefinition: self.parse_tuple_format += argument_type.fmt_str if argument_type.use_native_pointer: body += "\tPyObject * " + arg_name + ";\n" - body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr;\n" + body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr = NULL;\n" elif strip_leading_linphone(arg_complete_type) in self.linphone_module.enum_names: body += "\tint " + arg_name + ";\n" else: @@ -217,9 +217,11 @@ class MethodDefinition: return \ """ {class_native_ptr_check_code} {parse_tuple_code} + {args_type_check_code} {args_native_ptr_check_code} """.format(class_native_ptr_check_code=class_native_ptr_check_code, parse_tuple_code=parse_tuple_code, + args_type_check_code=self.format_args_type_check(), args_native_ptr_check_code=self.format_args_native_pointer_check()) def format_enter_trace(self): @@ -349,6 +351,23 @@ class MethodDefinition: }} """.format(class_name=self.class_['class_name'], return_value=return_value) + def format_args_type_check(self): + body = '' + for xml_method_arg in self.xml_method_args: + arg_name = "_" + xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + if argument_type.fmt_str == 'O': + body += \ +""" if (({arg_name} != Py_None) && !PyObject_IsInstance({arg_name}, (PyObject *)&pylinphone_{arg_type}Type)) {{ + PyErr_SetString(PyExc_TypeError, "The '{arg_name}' arguments must be a {type_str} instance."); + return NULL; + }} +""".format(arg_name=arg_name, arg_type=strip_leading_linphone(arg_type), type_str=argument_type.type_str) + body = body[1:] # Remove leading '\t' + return body + def format_args_native_pointer_check(self): body = '' for xml_method_arg in self.xml_method_args: @@ -358,10 +377,13 @@ class MethodDefinition: argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) if argument_type.fmt_str == 'O': body += \ -""" if (({arg_name}_native_ptr = pylinphone_{arg_type}_get_native_ptr({arg_name})) == NULL) {{ - return NULL; +""" if (({arg_name} != NULL) && ({arg_name} != Py_None)) {{ + if (({arg_name}_native_ptr = pylinphone_{arg_type}_get_native_ptr({arg_name})) == NULL) {{ + return NULL; + }} }} """.format(arg_name=arg_name, arg_type=strip_leading_linphone(arg_type)) + body = body[1:] # Remove leading '\t' return body def parse_method_node(self): @@ -510,8 +532,8 @@ class SetterMethodDefinition(MethodDefinition): def format_arguments_parsing(self): if self.first_argument_type.check_func is None: attribute_type_check_code = \ -"""if (!PyObject_IsInstance(value, (PyObject *)&pylinphone_{class_name}Type)) {{ - PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a linphone.{class_name} instance"); +"""if ((value != Py_None) && !PyObject_IsInstance(value, (PyObject *)&pylinphone_{class_name}Type)) {{ + PyErr_SetString(PyExc_TypeError, "The '{attribute_name}' attribute value must be a linphone.{class_name} instance."); return -1; }} """.format(class_name=self.first_arg_class, attribute_name=self.attribute_name) @@ -521,7 +543,7 @@ class SetterMethodDefinition(MethodDefinition): checknotnone = "(value != Py_None) && " attribute_type_check_code = \ """if ({checknotnone}!{checkfunc}(value)) {{ - PyErr_SetString(PyExc_TypeError, "The {attribute_name} attribute value must be a {type_str}"); + PyErr_SetString(PyExc_TypeError, "The '{attribute_name}' attribute value must be a {type_str}."); return -1; }} """.format(checknotnone=checknotnone, checkfunc=self.first_argument_type.check_func, attribute_name=self.attribute_name, type_str=self.first_argument_type.type_str) @@ -536,16 +558,17 @@ class SetterMethodDefinition(MethodDefinition): attribute_native_ptr_check_code = '' if self.first_argument_type.use_native_pointer: attribute_native_ptr_check_code = \ -"""{arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name}); - if ({arg_name}_native_ptr == NULL) {{ - PyErr_SetString(PyExc_TypeError, "Invalid linphone.{arg_class} instance"); - return -1; +"""if ({arg_name} != Py_None) {{ + if (({arg_name}_native_ptr = pylinphone_{arg_class}_get_native_ptr({arg_name})) == NULL) {{ + PyErr_SetString(PyExc_TypeError, "Invalid linphone.{arg_class} instance."); + return -1; + }} }} """.format(arg_name="_" + self.first_arg_name, arg_class=self.first_arg_class) return \ """ {native_ptr_check_code} if (value == NULL) {{ - PyErr_SetString(PyExc_TypeError, "Cannot delete the {attribute_name} attribute"); + PyErr_SetString(PyExc_TypeError, "Cannot delete the {attribute_name} attribute."); return -1; }} {attribute_type_check_code} From 067c8a9527be15688db798aa3736129a31241a82 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 14 Aug 2014 14:44:59 +0200 Subject: [PATCH 199/218] For functions returning or taking an MSList as an argument, specify what the MSList contains in the documentation. --- coreapi/authentication.c | 2 ++ coreapi/chat.c | 4 ++-- coreapi/help/Doxyfile.in | 2 +- coreapi/linphonecore.c | 14 ++++++++++++++ coreapi/linphonefriend.h | 3 ++- coreapi/proxy.c | 2 ++ tools/genapixml.py | 15 +++++++++++++++ tools/python/apixml2python.py | 3 ++- 8 files changed, 40 insertions(+), 5 deletions(-) diff --git a/coreapi/authentication.c b/coreapi/authentication.c index 59b3ea1b5..de7248316 100644 --- a/coreapi/authentication.c +++ b/coreapi/authentication.c @@ -378,6 +378,8 @@ void linphone_core_remove_auth_info(LinphoneCore *lc, const LinphoneAuthInfo *in /** * Returns an unmodifiable list of currently entered LinphoneAuthInfo. + * @param[in] lc The LinphoneCore object + * @return \mslist{LinphoneAuthInfo} **/ const MSList *linphone_core_get_auth_info_list(const LinphoneCore *lc){ return lc->auth_info; diff --git a/coreapi/chat.c b/coreapi/chat.c index c9ec8b6d5..66bde2968 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -246,8 +246,8 @@ bool_t linphone_core_chat_enabled(const LinphoneCore *lc){ /** * Returns an list of chat rooms - * @param lc #LinphoneCore object - * @return A list of #LinphoneChatRoom + * @param[in] lc #LinphoneCore object + * @return \mslist{LinphoneChatRoom} **/ MSList* linphone_core_get_chat_rooms(LinphoneCore *lc) { return lc->chatrooms; diff --git a/coreapi/help/Doxyfile.in b/coreapi/help/Doxyfile.in index 27068c53d..12facea9b 100644 --- a/coreapi/help/Doxyfile.in +++ b/coreapi/help/Doxyfile.in @@ -34,7 +34,7 @@ DETAILS_AT_TOP = NO INHERIT_DOCS = YES DISTRIBUTE_GROUP_DOC = NO TAB_SIZE = 8 -ALIASES = +ALIASES = mslist{1}="A list of \ref \1 objects. \xmlonly \1 \endxmlonly" OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO SUBGROUPING = YES diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 59c46fee2..34add6e0f 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -1466,6 +1466,8 @@ LinphoneCore *linphone_core_new_with_config(const LinphoneCoreVTable *vtable, st /** * Returns the list of available audio codecs. + * @param[in] lc The LinphoneCore object + * @return \mslist{PayloadType} * * This list is unmodifiable. The ->data field of the MSList points a PayloadType * structure holding the codec information. @@ -1480,6 +1482,8 @@ const MSList *linphone_core_get_audio_codecs(const LinphoneCore *lc) /** * Returns the list of available video codecs. + * @param[in] lc The LinphoneCore object + * @return \mslist{PayloadType} * * This list is unmodifiable. The ->data field of the MSList points a PayloadType * structure holding the codec information. @@ -1631,6 +1635,9 @@ LinphoneAddress *linphone_core_get_primary_contact_parsed(LinphoneCore *lc){ /** * Sets the list of audio codecs. + * @param[in] lc The LinphoneCore object + * @param[in] codecs \mslist{PayloadType} + * @return 0 * * @ingroup media_parameters * The list is taken by the LinphoneCore thus the application should not free it. @@ -1646,6 +1653,9 @@ int linphone_core_set_audio_codecs(LinphoneCore *lc, MSList *codecs) /** * Sets the list of video codecs. + * @param[in] lc The LinphoneCore object + * @param[in] codecs \mslist{PayloadType} + * @return 0 * * @ingroup media_parameters * The list is taken by the LinphoneCore thus the application should not free it. @@ -3672,6 +3682,8 @@ int linphone_core_terminate_all_calls(LinphoneCore *lc){ /** * Returns the current list of calls. + * @param[in] lc The LinphoneCore object + * @return \mslist{LinphoneCall} * * Note that this list is read-only and might be changed by the core after a function call to linphone_core_iterate(). * Similarly the LinphoneCall objects inside it might be destroyed without prior notice. @@ -4814,6 +4826,8 @@ LinphoneFirewallPolicy linphone_core_get_firewall_policy(const LinphoneCore *lc) /** * Get the list of call logs (past calls). + * @param[in] lc The LinphoneCore object + * @return \mslist{LinphoneCallLog} * * @ingroup call_logs **/ diff --git a/coreapi/linphonefriend.h b/coreapi/linphonefriend.h index 891bf7631..f64286c0e 100644 --- a/coreapi/linphonefriend.h +++ b/coreapi/linphonefriend.h @@ -356,7 +356,8 @@ LINPHONE_PUBLIC void linphone_core_reject_subscriber(LinphoneCore *lc, LinphoneF /** * Get Buddy list of LinphoneFriend - * @param lc #LinphoneCore object + * @param[in] lc #LinphoneCore object + * @return \mslist{LinphoneFriend} */ LINPHONE_PUBLIC const MSList * linphone_core_get_friend_list(const LinphoneCore *lc); diff --git a/coreapi/proxy.c b/coreapi/proxy.c index e6a33d556..d66dd30fd 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -1198,6 +1198,8 @@ int linphone_core_get_default_proxy(LinphoneCore *lc, LinphoneProxyConfig **conf /** * Returns an unmodifiable list of entered proxy configurations. + * @param[in] lc The LinphoneCore object + * @return \mslist{LinphoneProxyConfig} **/ const MSList *linphone_core_get_proxy_config_list(const LinphoneCore *lc){ return lc->sip_conf.proxies; diff --git a/tools/genapixml.py b/tools/genapixml.py index 2c9286792..4f834fb0c 100755 --- a/tools/genapixml.py +++ b/tools/genapixml.py @@ -72,6 +72,7 @@ class CArgument(CObject): def __init__(self, t, name = '', enums = [], structs = []): CObject.__init__(self, name) self.description = None + self.containedType = None keywords = [ 'const', 'struct', 'enum', 'signed', 'unsigned', 'short', 'long', '*' ] fullySplittedType = [] splittedType = t.strip().split(' ') @@ -302,6 +303,8 @@ class Project: para.remove(n) for n in para.findall('.//ref'): n.attrib = {} + for n in para.findall(".//mslist"): + para.remove(n) if descriptionNode.tag == 'parameterdescription': descriptionNode.tag = 'description' if descriptionNode.tag == 'simplesect': @@ -485,6 +488,10 @@ class Project: returnarg = CArgument(t, enums = self.enums, structs = self.__structs) returndesc = node.find("./detaileddescription/para/simplesect[@kind='return']") if returndesc is not None: + if returnarg.ctype == 'MSList': + n = returndesc.find('.//mslist') + if n is not None: + returnarg.containedType = n.text returnarg.description = self.__cleanDescription(returndesc) elif returnarg.completeType != 'void': missingDocWarning += "\tReturn value is not documented\n" @@ -504,6 +511,10 @@ class Project: for arg in argslist.arguments: for paramdesc in paramdescs: if arg.name == paramdesc.find('./parameternamelist').find('./parametername').text: + if arg.ctype == 'MSList': + n = paramdesc.find('.//mslist') + if n is not None: + arg.containedType = n.text arg.description = self.__cleanDescription(paramdesc.find('./parameterdescription')) missingDocWarning = '' for arg in argslist.arguments: @@ -594,12 +605,16 @@ class Generator: functionAttributes['location'] = f.location functionNode = ET.SubElement(parentNode, nodeName, functionAttributes) returnValueAttributes = { 'type' : f.returnArgument.ctype, 'completetype' : f.returnArgument.completeType } + if f.returnArgument.containedType is not None: + returnValueAttributes['containedtype'] = f.returnArgument.containedType returnValueNode = ET.SubElement(functionNode, 'return', returnValueAttributes) if f.returnArgument.description is not None: returnValueNode.append(f.returnArgument.description) argumentsNode = ET.SubElement(functionNode, 'arguments') for arg in f.arguments: argumentNodeAttributes = { 'name' : arg.name, 'type' : arg.ctype, 'completetype' : arg.completeType } + if arg.containedType is not None: + argumentNodeAttributes['containedtype'] = arg.containedType argumentNode = ET.SubElement(argumentsNode, 'argument', argumentNodeAttributes) if arg.description is not None: argumentNode.append(arg.description) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index dd37e2027..79b2bae0b 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -67,11 +67,12 @@ blacklisted_functions = [ 'linphone_core_get_video_port_range', # to be handwritten because of result via arguments 'linphone_core_publish', # missing LinphoneContent 'linphone_core_serialize_logs', # There is no use to wrap this function + 'linphone_core_set_audio_codecs', # missing PayloadType and MSList 'linphone_core_set_log_file', # There is no use to wrap this function 'linphone_core_set_log_handler', # Hand-written but put directly in the linphone module 'linphone_core_set_log_level', # There is no use to wrap this function + 'linphone_core_set_video_codecs', # missing PayloadType and MSList 'linphone_core_set_video_policy', # missing LinphoneVideoPolicy - 'linphone_core_set_audio_codecs', # missing PayloadType and MSList 'linphone_core_set_sip_transports', # missing LCSipTransports 'linphone_core_subscribe', # missing LinphoneContent 'linphone_event_notify', # missing LinphoneContent From 448ff25b546486a06ae1bb70efcbaeed45964c1b Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 14 Aug 2014 15:50:13 +0200 Subject: [PATCH 200/218] Handle MSList type in the Python wrapper. --- tools/python/apixml2python/linphone.py | 72 ++++++++++++++----- .../apixml2python/linphone_module.mustache | 26 +++++++ 2 files changed, 80 insertions(+), 18 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index e2e13b94b..109f6f5b2 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -15,6 +15,7 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +from sets import Set import sys @@ -38,9 +39,10 @@ def compute_event_name(s): class ArgumentType: - def __init__(self, basic_type, complete_type, linphone_module): + def __init__(self, basic_type, complete_type, contained_type, linphone_module): self.basic_type = basic_type self.complete_type = complete_type + self.contained_type = contained_type self.linphone_module = linphone_module self.type_str = None self.check_func = None @@ -51,6 +53,8 @@ class ArgumentType: self.use_native_pointer = False self.cast_convert_func_result = True self.__compute() + if self.basic_type == 'MSList' and self.contained_type is not None: + self.linphone_module.mslist_types.add(self.contained_type) def __compute(self): splitted_type = self.complete_type.split(' ') @@ -135,6 +139,13 @@ class ArgumentType: self.convert_func = 'PyInt_AsLong' self.fmt_str = 'i' self.cfmt_str = '%d' + elif self.basic_type == 'MSList': + self.type_str = 'list of linphone.' + self.contained_type + self.check_func = 'PyList_Check' + self.convert_func = 'PyList_AsMSListOf' + self.contained_type + self.convert_from_func = 'PyList_FromMSListOf' + self.contained_type + self.fmt_str = 'O' + self.cfmt_str = '%p' elif self.basic_type == 'MSVideoSize': self.type_str = 'linphone.VideoSize' self.check_func = 'PyLinphoneVideoSize_Check' @@ -165,6 +176,7 @@ class MethodDefinition: self.build_value_format = '' self.return_type = 'void' self.return_complete_type = 'void' + self.return_contained_type = None self.method_node = method_node self.class_ = class_ self.linphone_module = linphone_module @@ -178,9 +190,10 @@ class MethodDefinition: if self.xml_method_return is not None: self.return_type = self.xml_method_return.get('type') self.return_complete_type = self.xml_method_return.get('completetype') + self.return_contained_type = self.xml_method_return.get('containedtype') if self.return_complete_type != 'void': body += "\t" + self.return_complete_type + " cresult;\n" - argument_type = ArgumentType(self.return_type, self.return_complete_type, self.linphone_module) + argument_type = ArgumentType(self.return_type, self.return_complete_type, self.return_contained_type, self.linphone_module) self.build_value_format = argument_type.fmt_str if self.build_value_format == 'O': body += "\tPyObject * pyresult;\n" @@ -191,7 +204,8 @@ class MethodDefinition: arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) self.parse_tuple_format += argument_type.fmt_str if argument_type.use_native_pointer: body += "\tPyObject * " + arg_name + ";\n" @@ -234,9 +248,10 @@ class MethodDefinition: arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') + arg_contained_type = xml_method_arg.get('containedtype') if fmt != '': fmt += ', ' - argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) fmt += argument_type.cfmt_str args.append(arg_name) if argument_type.fmt_str == 'O': @@ -254,7 +269,8 @@ class MethodDefinition: arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) if argument_type.convert_func is None: arg_names.append(arg_name + "_native_ptr") else: @@ -294,7 +310,7 @@ class MethodDefinition: }} """.format(func=ref_function, cast_type=self.remove_const_from_complete_type(self.return_complete_type)) else: - return_argument_type = ArgumentType(self.return_type, self.return_complete_type, self.linphone_module) + return_argument_type = ArgumentType(self.return_type, self.return_complete_type, self.return_contained_type, self.linphone_module) if return_argument_type.convert_from_func is not None: convert_from_code = \ """pyresult = {convert_func}(cresult); @@ -357,7 +373,8 @@ class MethodDefinition: arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) if argument_type.fmt_str == 'O': body += \ """ if (({arg_name} != Py_None) && !PyObject_IsInstance({arg_name}, (PyObject *)&pylinphone_{arg_type}Type)) {{ @@ -374,7 +391,8 @@ class MethodDefinition: arg_name = "_" + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) if argument_type.fmt_str == 'O': body += \ """ if (({arg_name} != NULL) && ({arg_name} != Py_None)) {{ @@ -568,7 +586,7 @@ class SetterMethodDefinition(MethodDefinition): return \ """ {native_ptr_check_code} if (value == NULL) {{ - PyErr_SetString(PyExc_TypeError, "Cannot delete the {attribute_name} attribute."); + PyErr_SetString(PyExc_TypeError, "Cannot delete the '{attribute_name}' attribute."); return -1; }} {attribute_type_check_code} @@ -603,8 +621,9 @@ class SetterMethodDefinition(MethodDefinition): self.attribute_name = self.method_node.get('property_name') self.first_arg_type = self.xml_method_args[0].get('type') self.first_arg_complete_type = self.xml_method_args[0].get('completetype') + self.first_arg_contained_type = self.xml_method_args[0].get('containedtype') self.first_arg_name = self.xml_method_args[0].get('name') - self.first_argument_type = ArgumentType(self.first_arg_type, self.first_arg_complete_type, self.linphone_module) + self.first_argument_type = ArgumentType(self.first_arg_type, self.first_arg_complete_type, self.first_arg_contained_type, self.linphone_module) self.first_arg_class = strip_leading_linphone(self.first_arg_type) class EventCallbackMethodDefinition(MethodDefinition): @@ -621,7 +640,8 @@ class EventCallbackMethodDefinition(MethodDefinition): arg_name = 'py' + xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) if argument_type.fmt_str == 'O': specific += "\tPyObject * " + arg_name + " = NULL;\n" return "{common}\n{specific}".format(common=common, specific=specific) @@ -632,7 +652,8 @@ class EventCallbackMethodDefinition(MethodDefinition): arg_name = xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) if argument_type.fmt_str == 'O': type_class = self.find_class_definition(arg_type) get_user_data_code = '' @@ -655,9 +676,10 @@ class EventCallbackMethodDefinition(MethodDefinition): arg_name = xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') + arg_contained_type = xml_method_arg.get('containedtype') if fmt != '': fmt += ', ' - argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) fmt += argument_type.cfmt_str args.append(arg_name) args=', '.join(args) @@ -672,7 +694,8 @@ class EventCallbackMethodDefinition(MethodDefinition): arg_name = xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - argument_type = ArgumentType(arg_type, arg_complete_type, self.linphone_module) + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) fmt += argument_type.fmt_str if argument_type.fmt_str == 'O': args.append('py' + arg_name) @@ -713,6 +736,7 @@ class LinphoneModule(object): def __init__(self, tree, blacklisted_classes, blacklisted_events, blacklisted_functions, hand_written_functions): self.internal_instance_method_names = ['destroy', 'ref', 'unref'] self.internal_property_names = ['user_data'] + self.mslist_types = Set([]) self.enums = [] self.enum_names = [] xml_enums = tree.findall("./enums/enum") @@ -908,6 +932,14 @@ class LinphoneModule(object): except Exception, e: e.args += (c['class_name'], 'dealloc_body') raise + # Convert mslist_types to a list of dictionaries for the template + d = [] + for mslist_type in self.mslist_types: + t = {} + t['c_contained_type'] = mslist_type + t['python_contained_type'] = strip_leading_linphone(mslist_type) + d.append(t) + self.mslist_types = d def __format_doc_node(self, node): desc = '' @@ -973,7 +1005,8 @@ class LinphoneModule(object): arg_name = xml_method_arg.get('name') arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - argument_type = ArgumentType(arg_type, arg_complete_type, self) + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self) arg_doc = self.__format_doc_content(None, xml_method_arg.find('description')) doc += '\n\t' + arg_name + ' [' + argument_type.type_str + ']' if arg_doc != '': @@ -981,9 +1014,10 @@ class LinphoneModule(object): if xml_method_return is not None: return_type = xml_method_return.get('type') return_complete_type = xml_method_return.get('completetype') + return_contained_type = xml_method_return.get('containedtype') if return_complete_type != 'void': return_doc = self.__format_doc_content(None, xml_method_return.find('description')) - return_argument_type = ArgumentType(return_type, return_complete_type, self) + return_argument_type = ArgumentType(return_type, return_complete_type, return_contained_type, self) doc += '\n\nReturns:\n\t[' + return_argument_type.type_str + '] ' + return_doc doc = self.__replace_doc_special_chars(doc) return doc @@ -992,7 +1026,8 @@ class LinphoneModule(object): xml_method_arg = xml_node.findall('./arguments/argument')[1] arg_type = xml_method_arg.get('type') arg_complete_type = xml_method_arg.get('completetype') - argument_type = ArgumentType(arg_type, arg_complete_type, self) + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self) doc = self.__format_doc_content(xml_node.find('briefdescription'), xml_node.find('detaileddescription')) doc = '[' + argument_type.type_str + '] ' + doc doc = self.__replace_doc_special_chars(doc) @@ -1002,7 +1037,8 @@ class LinphoneModule(object): xml_method_return = xml_node.find('./return') return_type = xml_method_return.get('type') return_complete_type = xml_method_return.get('completetype') - return_argument_type = ArgumentType(return_type, return_complete_type, self) + return_contained_type = xml_method_return.get('containedtype') + return_argument_type = ArgumentType(return_type, return_complete_type, return_contained_type, self) doc = self.__format_doc_content(xml_node.find('briefdescription'), xml_node.find('detaileddescription')) doc = '[' + return_argument_type.type_str + '] ' + doc doc = self.__replace_doc_special_chars(doc) diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 005f05edb..62b626843 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -69,6 +69,32 @@ static PyObject * pylinphone_{{class_name}}_instance_method_{{method_name}}(PyOb {{/class_instance_hand_written_methods}} {{/classes}} +{{#mslist_types}} +PyObject * PyList_FromMSListOf{{c_contained_type}}(const MSList *msl) { + PyObject *pyl = PyList_New(0); + while (msl != NULL) { + {{c_contained_type}} *native_ptr = ({{c_contained_type}} *)msl->data; + PyObject *item = pylinphone_{{python_contained_type}}_new_from_native_ptr(&pylinphone_{{python_contained_type}}Type, native_ptr); + PyList_Append(pyl, item); + msl = ms_list_next(msl); + } + return pyl; +} + +MSList * PyList_AsMSListOf{{c_contained_type}}(PyObject *pyl) { + MSList *msl = NULL; + Py_ssize_t idx; + Py_ssize_t size = PyList_Size(pyl); + for (idx = 0; idx < size; idx++) { + PyObject *item = PyList_GetItem(pyl, idx); + {{c_contained_type}} *native_ptr = pylinphone_{{python_contained_type}}_get_native_ptr(item); + msl = ms_list_append(msl, native_ptr); + } + return msl; +} + +{{/mslist_types}} + {{#events}} {{{event_callback_definition}}} {{/events}} From 02bbe939b1223b2592dba057a3646101895928c9 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 14 Aug 2014 16:33:35 +0200 Subject: [PATCH 201/218] Generate the Python wrapper for the functions handling MSList objects. --- tools/python/apixml2python.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 79b2bae0b..f4c681fe7 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -50,28 +50,18 @@ blacklisted_functions = [ 'linphone_chat_room_create_file_transfer_message', # missing LinphoneContent 'linphone_chat_room_create_message_2', # missing time_t 'linphone_core_can_we_add_call', # private function - 'linphone_core_get_audio_codecs', # missing PayloadType and MSList 'linphone_core_get_audio_port_range', # to be handwritten because of result via arguments - 'linphone_core_get_auth_info_list', # missing MSList - 'linphone_core_get_call_logs', # missing MSList - 'linphone_core_get_calls', # missing MSList - 'linphone_core_get_chat_rooms', # missing MSList 'linphone_core_get_default_proxy', # to be handwritten because of double pointer indirection - 'linphone_core_get_friend_list', # missing MSList - 'linphone_core_get_proxy_config_list', # missing MSList 'linphone_core_get_sip_transports', # missing LCSipTransports 'linphone_core_get_sip_transports_used', # missing LCSipTransports 'linphone_core_get_supported_video_sizes', # missing MSVideoSizeDef - 'linphone_core_get_video_codecs', # missing PayloadType and MSList 'linphone_core_get_video_policy', # missing LinphoneVideoPolicy 'linphone_core_get_video_port_range', # to be handwritten because of result via arguments 'linphone_core_publish', # missing LinphoneContent 'linphone_core_serialize_logs', # There is no use to wrap this function - 'linphone_core_set_audio_codecs', # missing PayloadType and MSList 'linphone_core_set_log_file', # There is no use to wrap this function 'linphone_core_set_log_handler', # Hand-written but put directly in the linphone module 'linphone_core_set_log_level', # There is no use to wrap this function - 'linphone_core_set_video_codecs', # missing PayloadType and MSList 'linphone_core_set_video_policy', # missing LinphoneVideoPolicy 'linphone_core_set_sip_transports', # missing LCSipTransports 'linphone_core_subscribe', # missing LinphoneContent From 90c0306f66aad64a797690a98e84535b4672bf7e Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 14 Aug 2014 16:33:53 +0200 Subject: [PATCH 202/218] Fix compilation warnings. --- tools/python/apixml2python/handwritten_definitions.mustache | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/python/apixml2python/handwritten_definitions.mustache b/tools/python/apixml2python/handwritten_definitions.mustache index b3d043c32..41a563687 100644 --- a/tools/python/apixml2python/handwritten_definitions.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -117,8 +117,7 @@ static PyObject * pylinphone_module_method_set_log_handler(PyObject *self, PyObj static PyObject * pylinphone_Core_get_video_devices(PyObject *self, void *closure) { PyObject *_list; - char **_devices; - char *_device; + const char **_devices; LinphoneCore *native_ptr = pylinphone_Core_get_native_ptr(self); if (native_ptr == NULL) { From 719b507b9faab1cbe1e77f2bf7151878553ee4e1 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 14 Aug 2014 17:48:43 +0200 Subject: [PATCH 203/218] Partial implementation of time_t in the Python wrapper. --- tools/python/apixml2python.py | 3 -- .../handwritten_declarations.mustache | 2 ++ .../handwritten_definitions.mustache | 33 +++++++++++++++++++ tools/python/apixml2python/linphone.py | 7 ++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index f4c681fe7..9fe7f214c 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -40,11 +40,9 @@ blacklisted_events = [ blacklisted_functions = [ 'linphone_call_log_get_local_stats', # missing rtp_stats_t 'linphone_call_log_get_remote_stats', # missing rtp_stats_t - 'linphone_call_log_get_start_date', # missing time_t 'linphone_call_params_get_privacy', # missing LinphonePrivacyMask 'linphone_call_params_set_privacy', # missing LinphonePrivacyMask 'linphone_chat_message_get_file_transfer_information', # missing LinphoneContent - 'linphone_chat_message_get_time', # missing time_t 'linphone_chat_message_start_file_download', # to be handwritten because of callback 'linphone_chat_message_state_to_string', # There is no use to wrap this function 'linphone_chat_room_create_file_transfer_message', # missing LinphoneContent @@ -70,7 +68,6 @@ blacklisted_functions = [ 'linphone_event_send_subscribe', # missing LinphoneContent 'linphone_event_update_publish', # missing LinphoneContent 'linphone_event_update_subscribe', # missing LinphoneContent - 'linphone_presence_model_get_timestamp', # missing time_t 'linphone_proxy_config_get_privacy', # missing LinphonePrivacyMask 'linphone_proxy_config_normalize_number', # to be handwritten because of result via arguments 'linphone_proxy_config_set_file_transfer_server', # defined but not implemented in linphone core diff --git a/tools/python/apixml2python/handwritten_declarations.mustache b/tools/python/apixml2python/handwritten_declarations.mustache index 4a50a848b..eac0ba4b3 100644 --- a/tools/python/apixml2python/handwritten_declarations.mustache +++ b/tools/python/apixml2python/handwritten_declarations.mustache @@ -10,3 +10,5 @@ typedef struct { int PyLinphoneVideoSize_Check(PyObject *p); MSVideoSize PyLinphoneVideoSize_AsMSVideoSize(PyObject *obj); PyObject * PyLinphoneVideoSize_FromMSVideoSize(MSVideoSize vs); +time_t PyDateTime_As_time_t(PyObject *obj); +PyObject * PyDateTime_From_time_t(time_t t); diff --git a/tools/python/apixml2python/handwritten_definitions.mustache b/tools/python/apixml2python/handwritten_definitions.mustache index 41a563687..5a602d515 100644 --- a/tools/python/apixml2python/handwritten_definitions.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -393,6 +393,39 @@ PyObject * PyLinphoneVideoSize_FromMSVideoSize(MSVideoSize vs) { } +time_t PyDateTime_As_time_t(PyObject *obj) { +} + +PyObject * PyDateTime_From_time_t(time_t t) { + PyObject *pyret = NULL; + PyObject *datetime_module; + PyObject *datetime_class; + if (t == -1) { + Py_RETURN_NONE; + } + datetime_module = PyImport_ImportModule("datetime"); + if (datetime_module != NULL) { + PyObject *datetime_class = PyObject_GetAttrString(datetime_module, "datetime"); + if (datetime_class != NULL) { + PyObject *utcfromtimestamp = PyObject_GetAttrString(datetime_class, "utcfromtimestamp"); + if (utcfromtimestamp != NULL) { + pyret = PyEval_CallObject(utcfromtimestamp, Py_BuildValue("(f)", (float)t)); + if (pyret == NULL) { + PyErr_Print(); + } + Py_DECREF(utcfromtimestamp); + } + Py_DECREF(datetime_class); + } + Py_DECREF(datetime_module); + } + if (pyret == NULL) { + Py_RETURN_NONE; + } + return pyret; +} + + static PyObject * pylinphone_PayloadTypeType_module_method_string(PyObject *self, PyObject *args) { const char *value_str = "[invalid]"; int value; diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 109f6f5b2..a7b1f21b0 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -139,6 +139,13 @@ class ArgumentType: self.convert_func = 'PyInt_AsLong' self.fmt_str = 'i' self.cfmt_str = '%d' + elif self.basic_type == 'time_t': + self.type_str = 'DateTime' + self.check_func = 'PyDateTime_Check' + self.convert_func = 'PyDateTime_As_time_t' + self.convert_from_func = 'PyDateTime_From_time_t' + self.fmt_str = 'O' + self.cfmt_str = '%p' elif self.basic_type == 'MSList': self.type_str = 'list of linphone.' + self.contained_type self.check_func = 'PyList_Check' From 4a335ba6e1b94c11e64720fd7a02e1c7873002a6 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 18 Aug 2014 13:42:45 +0200 Subject: [PATCH 204/218] Complete handling of time_t type in the Python wrapper. --- tools/python/apixml2python.py | 1 - .../handwritten_definitions.mustache | 18 ++++- tools/python/apixml2python/linphone.py | 80 ++++++++++++++----- .../apixml2python/linphone_module.mustache | 2 + 4 files changed, 79 insertions(+), 22 deletions(-) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 9fe7f214c..b1bf3ebb2 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -46,7 +46,6 @@ blacklisted_functions = [ 'linphone_chat_message_start_file_download', # to be handwritten because of callback 'linphone_chat_message_state_to_string', # There is no use to wrap this function 'linphone_chat_room_create_file_transfer_message', # missing LinphoneContent - 'linphone_chat_room_create_message_2', # missing time_t 'linphone_core_can_we_add_call', # private function 'linphone_core_get_audio_port_range', # to be handwritten because of result via arguments 'linphone_core_get_default_proxy', # to be handwritten because of double pointer indirection diff --git a/tools/python/apixml2python/handwritten_definitions.mustache b/tools/python/apixml2python/handwritten_definitions.mustache index 5a602d515..e52f55e3a 100644 --- a/tools/python/apixml2python/handwritten_definitions.mustache +++ b/tools/python/apixml2python/handwritten_definitions.mustache @@ -394,12 +394,28 @@ PyObject * PyLinphoneVideoSize_FromMSVideoSize(MSVideoSize vs) { time_t PyDateTime_As_time_t(PyObject *obj) { + time_t ret = -1; + PyObject *utctimetuple = PyObject_GetAttrString(obj, "utctimetuple"); + if (utctimetuple != NULL) { + PyObject *calendar_module = PyImport_ImportModule("calendar"); + if (calendar_module != NULL) { + PyObject *timegm = PyObject_GetAttrString(calendar_module, "timegm"); + if (timegm != NULL) { + PyObject *tuple = PyEval_CallObject(utctimetuple, Py_BuildValue("()")); + PyObject *pyres = PyEval_CallObject(timegm, Py_BuildValue("(O)", tuple)); + ret = (time_t)PyLong_AsLong(pyres); + Py_DECREF(timegm); + } + Py_DECREF(calendar_module); + } + Py_DECREF(utctimetuple); + } + return ret; } PyObject * PyDateTime_From_time_t(time_t t) { PyObject *pyret = NULL; PyObject *datetime_module; - PyObject *datetime_class; if (t == -1) { Py_RETURN_NONE; } diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index a7b1f21b0..145a3d9c9 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -50,6 +50,7 @@ class ArgumentType: self.convert_from_func = None self.fmt_str = 'O' self.cfmt_str = '%p' + self.cnativefmt_str = '%p' self.use_native_pointer = False self.cast_convert_func_result = True self.__compute() @@ -146,6 +147,7 @@ class ArgumentType: self.convert_from_func = 'PyDateTime_From_time_t' self.fmt_str = 'O' self.cfmt_str = '%p' + self.cnativefmt_str = "%ld" elif self.basic_type == 'MSList': self.type_str = 'list of linphone.' + self.contained_type self.check_func = 'PyList_Check' @@ -214,9 +216,12 @@ class MethodDefinition: arg_contained_type = xml_method_arg.get('containedtype') argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) self.parse_tuple_format += argument_type.fmt_str - if argument_type.use_native_pointer: + if argument_type.fmt_str == 'O' and argument_type.use_native_pointer: body += "\tPyObject * " + arg_name + ";\n" body += "\t" + arg_complete_type + " " + arg_name + "_native_ptr = NULL;\n" + elif argument_type.fmt_str == 'O' and argument_type.convert_func is not None: + body += "\tPyObject * " + arg_name + ";\n" + body += "\t" + arg_complete_type + " " + arg_name + "_native_obj;\n" elif strip_leading_linphone(arg_complete_type) in self.linphone_module.enum_names: body += "\tint " + arg_name + ";\n" else: @@ -235,15 +240,28 @@ class MethodDefinition: return NULL; }} """.format(fmt=self.parse_tuple_format, args=', '.join(map(lambda a: '&' + a, self.arg_names))) + args_conversion_code = '' + for xml_method_arg in self.xml_method_args: + arg_name = "_" + xml_method_arg.get('name') + arg_type = xml_method_arg.get('type') + arg_complete_type = xml_method_arg.get('completetype') + arg_contained_type = xml_method_arg.get('containedtype') + argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) + if argument_type.fmt_str == 'O' and argument_type.convert_func is not None: + args_conversion_code += \ +""" {arg_name}_native_obj = {convert_func}({arg_name}); +""".format(arg_name=arg_name, convert_func=argument_type.convert_func) return \ """ {class_native_ptr_check_code} {parse_tuple_code} {args_type_check_code} {args_native_ptr_check_code} + {args_conversion_code} """.format(class_native_ptr_check_code=class_native_ptr_check_code, parse_tuple_code=parse_tuple_code, args_type_check_code=self.format_args_type_check(), - args_native_ptr_check_code=self.format_args_native_pointer_check()) + args_native_ptr_check_code=self.format_args_native_pointer_check(), + args_conversion_code=args_conversion_code) def format_enter_trace(self): fmt = '' @@ -262,8 +280,11 @@ class MethodDefinition: fmt += argument_type.cfmt_str args.append(arg_name) if argument_type.fmt_str == 'O': - fmt += ' [' + argument_type.cfmt_str + ']' - args.append(arg_name) + fmt += ' [' + argument_type.cnativefmt_str + ']' + if argument_type.use_native_pointer: + args.append(arg_name + '_native_ptr') + else: + args.append(arg_name + '_native_obj') args = ', '.join(args) if args != '': args = ', ' + args @@ -278,8 +299,10 @@ class MethodDefinition: arg_complete_type = xml_method_arg.get('completetype') arg_contained_type = xml_method_arg.get('containedtype') argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) - if argument_type.convert_func is None: + if argument_type.fmt_str == 'O' and argument_type.use_native_pointer: arg_names.append(arg_name + "_native_ptr") + elif argument_type.fmt_str == 'O' and argument_type.convert_func is not None: + arg_names.append(arg_name + "_native_obj") else: arg_names.append(arg_name) if self.return_type != 'void': @@ -383,13 +406,22 @@ class MethodDefinition: arg_contained_type = xml_method_arg.get('containedtype') argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) if argument_type.fmt_str == 'O': - body += \ + if argument_type.use_native_pointer: + body += \ """ if (({arg_name} != Py_None) && !PyObject_IsInstance({arg_name}, (PyObject *)&pylinphone_{arg_type}Type)) {{ - PyErr_SetString(PyExc_TypeError, "The '{arg_name}' arguments must be a {type_str} instance."); + PyErr_SetString(PyExc_TypeError, "The '{arg_name}' argument must be a {type_str} instance."); return NULL; }} """.format(arg_name=arg_name, arg_type=strip_leading_linphone(arg_type), type_str=argument_type.type_str) - body = body[1:] # Remove leading '\t' + else: + body += \ +""" if (!{check_func}({arg_name})) {{ + PyErr_SetString(PyExc_TypeError, "The '{arg_name}' argument must be a {type_str} instance."); + return NULL; + }} +""".format(arg_name=arg_name, check_func=argument_type.check_func, type_str=argument_type.type_str) + if body != '': + body = body[1:] # Remove leading '\t' return body def format_args_native_pointer_check(self): @@ -400,7 +432,7 @@ class MethodDefinition: arg_complete_type = xml_method_arg.get('completetype') arg_contained_type = xml_method_arg.get('containedtype') argument_type = ArgumentType(arg_type, arg_complete_type, arg_contained_type, self.linphone_module) - if argument_type.fmt_str == 'O': + if argument_type.fmt_str == 'O' and argument_type.use_native_pointer: body += \ """ if (({arg_name} != NULL) && ({arg_name} != Py_None)) {{ if (({arg_name}_native_ptr = pylinphone_{arg_type}_get_native_ptr({arg_name})) == NULL) {{ @@ -408,7 +440,8 @@ class MethodDefinition: }} }} """.format(arg_name=arg_name, arg_type=strip_leading_linphone(arg_type)) - body = body[1:] # Remove leading '\t' + if body != '': + body = body[1:] # Remove leading '\t' return body def parse_method_node(self): @@ -572,14 +605,19 @@ class SetterMethodDefinition(MethodDefinition): return -1; }} """.format(checknotnone=checknotnone, checkfunc=self.first_argument_type.check_func, attribute_name=self.attribute_name, type_str=self.first_argument_type.type_str) - if self.first_argument_type.convert_func is None: - attribute_conversion_code = "{arg_name} = value;\n".format(arg_name="_" + self.first_arg_name) - else: + attribute_conversion_code = '' + if (self.first_argument_type.convert_func is None) or \ + (self.first_argument_type.fmt_str == 'O' and self.first_argument_type.convert_func is not None): + attribute_conversion_code += "{arg_name} = value;\n".format(arg_name="_" + self.first_arg_name) + if self.first_argument_type.convert_func is not None: cast_code = '' + suffix = '' if self.first_argument_type.cast_convert_func_result: cast_code = "({arg_type})".format(arg_type=self.first_arg_complete_type) - attribute_conversion_code = "{arg_name} = {cast_code}{convertfunc}(value);\n".format( - arg_name="_" + self.first_arg_name, cast_code=cast_code, convertfunc=self.first_argument_type.convert_func) + if self.first_argument_type.fmt_str == 'O' and self.first_argument_type.convert_func is not None: + suffix = '_native_obj' + attribute_conversion_code += "\t{arg_name}{suffix} = {cast_code}{convertfunc}(value);\n".format( + arg_name="_" + self.first_arg_name, suffix=suffix, cast_code=cast_code, convertfunc=self.first_argument_type.convert_func) attribute_native_ptr_check_code = '' if self.first_argument_type.use_native_pointer: attribute_native_ptr_check_code = \ @@ -606,13 +644,15 @@ class SetterMethodDefinition(MethodDefinition): attribute_native_ptr_check_code=attribute_native_ptr_check_code) def format_c_function_call(self): - use_native_ptr = '' - if self.first_argument_type.use_native_pointer: - use_native_ptr = '_native_ptr' + suffix = '' + if self.first_argument_type.fmt_str == 'O' and self.first_argument_type.use_native_pointer: + suffix = '_native_ptr' + elif self.first_argument_type.fmt_str == 'O' and self.first_argument_type.convert_func is not None: + suffix = '_native_obj' return \ -""" {method_name}(native_ptr, {arg_name}{use_native_ptr}); +""" {method_name}(native_ptr, {arg_name}{suffix}); pylinphone_dispatch_messages(); -""".format(arg_name="_" + self.first_arg_name, method_name=self.method_node.get('name'), use_native_ptr=use_native_ptr) +""".format(arg_name="_" + self.first_arg_name, method_name=self.method_node.get('name'), suffix=suffix) def format_return_trace(self): return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s -> 0\", __FUNCTION__);\n" diff --git a/tools/python/apixml2python/linphone_module.mustache b/tools/python/apixml2python/linphone_module.mustache index 62b626843..dcd0a7c3a 100644 --- a/tools/python/apixml2python/linphone_module.mustache +++ b/tools/python/apixml2python/linphone_module.mustache @@ -18,6 +18,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include +#include #include #include #include @@ -265,6 +266,7 @@ PyMODINIT_FUNC initlinphone(void) { PyObject *m; PyObject *menum; + PyDateTime_IMPORT; pylinphone_init_logging(); {{#classes}} From 0ac69b6969e2b7020c594437373f991dc918857f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 18 Aug 2014 16:01:01 +0200 Subject: [PATCH 205/218] Update ms2 and oRTP submodules. --- mediastreamer2 | 2 +- oRTP | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index c82cc74f7..a1c7195ff 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit c82cc74f7341378ea3d21257448c427e4e7b91a1 +Subproject commit a1c7195ff8372ac1b77d71dce52c2d9da0173cc6 diff --git a/oRTP b/oRTP index 9d158c2da..b9534b1b2 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 9d158c2daf289bd826b855903e913786f90d5bad +Subproject commit b9534b1b2beb4bc549741e1f55a9c0bfe15d6c90 From 54c3f6efa9525151f06b26dc1dab7ae1a3cc8aa2 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Mon, 18 Aug 2014 16:12:05 +0200 Subject: [PATCH 206/218] Import linphone Python module with "import linphone". --- tools/python/linphone-daemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/python/linphone-daemon.py b/tools/python/linphone-daemon.py index 547aba918..a923c3ee9 100644 --- a/tools/python/linphone-daemon.py +++ b/tools/python/linphone-daemon.py @@ -3,7 +3,7 @@ import logging import sys import threading import time -from linphone import linphone +import linphone class Response: From e0675493479421d43b5ab62a7b5255ba9dfd4046 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Mon, 18 Aug 2014 17:01:59 +0200 Subject: [PATCH 207/218] Provide correct sample rate when G722 is involved --- coreapi/linphonecall.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 9d8b6060c..37a1079a3 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -236,6 +236,14 @@ static MSList *make_codec_list(LinphoneCore *lc, const MSList *codecs, int bandw for(it=codecs;it!=NULL;it=it->next){ PayloadType *pt=(PayloadType*)it->data; if (pt->flags & PAYLOAD_TYPE_ENABLED){ + int sample_rate = payload_type_get_rate(pt); + + if( strcasecmp("G722",pt->mime_type) == 0 ){ + /* G722 spec says 8000 but the codec actually requires 16000 */ + ms_debug("Correcting sample rate for G722"); + sample_rate = 16000; + } + if (bandwidth_limit>0 && !linphone_core_is_payload_type_usable_for_bandwidth(lc,pt,bandwidth_limit)){ ms_message("Codec %s/%i eliminated because of audio bandwidth constraint of %i kbit/s", pt->mime_type,pt->clock_rate,bandwidth_limit); @@ -244,7 +252,7 @@ static MSList *make_codec_list(LinphoneCore *lc, const MSList *codecs, int bandw if (linphone_core_check_payload_type_usability(lc,pt)){ l=ms_list_append(l,payload_type_clone(pt)); nb++; - if (max_sample_rate && payload_type_get_rate(pt)>*max_sample_rate) *max_sample_rate=payload_type_get_rate(pt); + if (max_sample_rate && sample_rate>*max_sample_rate) *max_sample_rate=sample_rate; } } if ((nb_codecs_limit > 0) && (nb >= nb_codecs_limit)) break; From 6ed82cb740677f08d1010c38fd44747b3dae9b93 Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Mon, 18 Aug 2014 19:02:31 +0200 Subject: [PATCH 208/218] Add the equalizer location parameter to the "sound" section of lpconfig. Setting it to "mic" will place it in the input graph, rather than the default location in output graph. This allow to pre-amplify some frequencies in the input device. You still need to eq_active=1 and set eq_gains to what you want to amplify. --- coreapi/linphonecall.c | 7 +++++++ mediastreamer2 | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 37a1079a3..273990742 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1710,6 +1710,13 @@ void linphone_call_init_audio_stream(LinphoneCall *call){ else if (strcasecmp(type,"full")==0) audio_stream_enable_echo_limiter(audiostream,ELControlFull); } + + /* equalizer location in the graph: 'mic' = in input graph, otherwise in output graph. + Any other value than mic will default to output graph for compatibility */ + const char *location = lp_config_get_string(lc->config,"sound","eq_location","hp"); + audiostream->eq_loc = (strcasecmp(location,"mic") == 0) ? MSEqualizerMic : MSEqualizerHP; + ms_error("Equalizer location: %s", location); + audio_stream_enable_gain_control(audiostream,TRUE); if (linphone_core_echo_cancellation_enabled(lc)){ int len,delay,framesize; diff --git a/mediastreamer2 b/mediastreamer2 index a1c7195ff..b1b9fc244 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit a1c7195ff8372ac1b77d71dce52c2d9da0173cc6 +Subproject commit b1b9fc244915ecccbba26db9440b077d5cafa85f From e745c956e7d8e4b68386c73fd8e0567df83a4688 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Tue, 19 Aug 2014 11:29:27 +0200 Subject: [PATCH 209/218] Add method to retrieve size of a conversation history and a part of it --- coreapi/linphonecore.h | 17 ++++- coreapi/linphonecore_jni.cc | 27 ++++++-- coreapi/message_storage.c | 55 +++++++++++++--- .../org/linphone/core/LinphoneChatRoom.java | 38 +++++++---- .../linphone/core/LinphoneChatRoomImpl.java | 54 ++++++++++----- tester/message_tester.c | 62 ++++++++++++++---- tester/messages.db | Bin 1821696 -> 3587072 bytes 7 files changed, 195 insertions(+), 58 deletions(-) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 7147f30e7..5c6ee82b5 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1347,9 +1347,24 @@ LINPHONE_PUBLIC MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int LINPHONE_PUBLIC void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr); LINPHONE_PUBLIC void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg); LINPHONE_PUBLIC void linphone_chat_room_delete_history(LinphoneChatRoom *cr); +/** + * Gets the number of messages in a chat room. + * @param[in] cr The #LinphoneChatRoom object corresponding to the conversation for which size has to be computed + * @return the number of messages. + */ +LINPHONE_PUBLIC int linphone_chat_room_get_history_size(LinphoneChatRoom *cr); /** - * Notify the destination of the chat message being composed that the user is typing a new message. + * Gets the partial list of messages in the given range, sorted from most recent to oldest. + * @param[in] cr The #LinphoneChatRoom object corresponding to the conversation for which messages should be retrieved + * @param[in] begin The first message of the range to be retrieved. History most recent message has index 0. + * @param[in] end The last message of the range to be retrieved. History oldest message has index of history size - 1 (use #linphone_chat_room_get_history_size to retrieve history size) + * @return the list of messages in the given range, or NULL if nothing has been found. + */ +LINPHONE_PUBLIC MSList *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int begin, int end); + +/** + * Notifies the destination of the chat message being composed that the user is typing a new message. * @param[in] cr The #LinphoneChatRoom object corresponding to the conversation for which a new message is being typed. */ LINPHONE_PUBLIC void linphone_chat_room_compose(LinphoneChatRoom *cr); diff --git a/coreapi/linphonecore_jni.cc b/coreapi/linphonecore_jni.cc index 92c253288..dfe90f7d5 100644 --- a/coreapi/linphonecore_jni.cc +++ b/coreapi/linphonecore_jni.cc @@ -2425,12 +2425,11 @@ extern "C" jlong Java_org_linphone_core_LinphoneCoreImpl_getFriendByAddress(JNIE env->ReleaseStringUTFChars(jaddress, address); return (jlong) lf; } -//LinphoneChatRoom -extern "C" jlongArray Java_org_linphone_core_LinphoneChatRoomImpl_getHistory(JNIEnv* env + +extern "C" jlongArray _LinphoneChatRoomImpl_getHistory(JNIEnv* env ,jobject thiz ,jlong ptr - ,jint limit) { - MSList* history = linphone_chat_room_get_history((LinphoneChatRoom*)ptr, limit); + ,MSList* history) { int historySize = ms_list_size(history); jlongArray jHistory = env->NewLongArray(historySize); jlong *jInternalArray = env->GetLongArrayElements(jHistory, NULL); @@ -2446,6 +2445,21 @@ extern "C" jlongArray Java_org_linphone_core_LinphoneChatRoomImpl_getHistory(JNI return jHistory; } +extern "C" jlongArray Java_org_linphone_core_LinphoneChatRoomImpl_getHistoryRange(JNIEnv* env + ,jobject thiz + ,jlong ptr + ,jint start + ,jint end) { + MSList* history = linphone_chat_room_get_history_range((LinphoneChatRoom*)ptr, start, end); + return _LinphoneChatRoomImpl_getHistory(env, thiz, ptr, history); +} +extern "C" jlongArray Java_org_linphone_core_LinphoneChatRoomImpl_getHistory(JNIEnv* env + ,jobject thiz + ,jlong ptr + ,jint limit) { + MSList* history = linphone_chat_room_get_history((LinphoneChatRoom*)ptr, limit); + return _LinphoneChatRoomImpl_getHistory(env, thiz, ptr, history); +} extern "C" jlong Java_org_linphone_core_LinphoneChatRoomImpl_getPeerAddress(JNIEnv* env ,jobject thiz ,jlong ptr) { @@ -2484,6 +2498,11 @@ extern "C" jlong Java_org_linphone_core_LinphoneChatRoomImpl_createLinphoneChatM return (jlong) chatMessage; } +extern "C" jint Java_org_linphone_core_LinphoneChatRoomImpl_getHistorySize (JNIEnv* env + ,jobject thiz + ,jlong ptr) { + return (jint) linphone_chat_room_get_history_size((LinphoneChatRoom*)ptr); +} extern "C" jint Java_org_linphone_core_LinphoneChatRoomImpl_getUnreadMessagesCount(JNIEnv* env ,jobject thiz ,jlong ptr) { diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index d2f83b12a..89120aee3 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -217,14 +217,14 @@ void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *ms sqlite3_free(buf); } -int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ +static int linphone_chat_room_get_messages_count(LinphoneChatRoom *cr, bool_t unread_only){ LinphoneCore *lc=linphone_chat_room_get_lc(cr); int numrows=0; if (lc->db==NULL) return 0; char *peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); - char *buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q AND read = 0;",peer); + char *buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q %s;",peer,unread_only?"AND read = 0":""); sqlite3_stmt *selectStatement; int returnValue = sqlite3_prepare_v2(lc->db,buf,-1,&selectStatement,NULL); if (returnValue == SQLITE_OK){ @@ -238,6 +238,14 @@ int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ return numrows; } +int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ + return linphone_chat_room_get_messages_count(cr, TRUE); +} + +int linphone_chat_room_get_history_size(LinphoneChatRoom *cr){ + return linphone_chat_room_get_messages_count(cr, FALSE); +} + void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { LinphoneCore *lc=cr->lc; @@ -260,31 +268,50 @@ void linphone_chat_room_delete_history(LinphoneChatRoom *cr){ ms_free(peer); } -MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ +MSList *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int startm, int endm){ LinphoneCore *lc=linphone_chat_room_get_lc(cr); MSList *ret; char *buf; char *peer; uint64_t begin,end; + int buf_max_size = 512; if (lc->db==NULL) return NULL; - peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); + peer = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr)); + cr->messages_hist = NULL; - if (nb_message > 0) - buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC LIMIT %i ;",peer,nb_message); - else - buf=sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC;",peer); + + /*since we want to append query parameters depending on arguments given, we use malloc instead of sqlite3_mprintf*/ + buf=ms_malloc(buf_max_size); + buf=sqlite3_snprintf(buf_max_size-1,buf,"SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC",peer); + + if (endm>0&&endm>=startm){ + buf=sqlite3_snprintf(buf_max_size-1,buf,"%s LIMIT %i ",buf,endm+1-startm); + }else if(startm>0){ + ms_message("%s(): end is lower than start (%d < %d). No end assumed.",__FUNCTION__,endm,startm); + buf=sqlite3_snprintf(buf_max_size-1,buf,"%s LIMIT -1",buf); + } + + if (startm>0){ + buf=sqlite3_snprintf(buf_max_size-1,buf,"%s OFFSET %i ",buf,startm); + } + begin=ortp_get_cur_time_ms(); linphone_sql_request_message(lc->db,buf,cr); end=ortp_get_cur_time_ms(); - ms_message("linphone_chat_room_get_history(): completed in %i ms",(int)(end-begin)); - sqlite3_free(buf); + ms_message("%s(): completed in %i ms",__FUNCTION__, (int)(end-begin)); + ms_free(buf); ret=cr->messages_hist; cr->messages_hist=NULL; ms_free(peer); return ret; } +MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ + return linphone_chat_room_get_history_range(cr, 0, nb_message); +} + + void linphone_close_storage(sqlite3* db){ sqlite3_close(db); } @@ -484,6 +511,10 @@ MSList *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){ return NULL; } +LINPHONE_PUBLIC MSList *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int begin, int end){ + return NULL; +} + void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) { } @@ -506,4 +537,8 @@ int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){ return 0; } +int linphone_chat_room_get_history_size(LinphoneChatRoom *cr){ + return 0; +} + #endif diff --git a/java/common/org/linphone/core/LinphoneChatRoom.java b/java/common/org/linphone/core/LinphoneChatRoom.java index c1e635c0a..0d677760c 100644 --- a/java/common/org/linphone/core/LinphoneChatRoom.java +++ b/java/common/org/linphone/core/LinphoneChatRoom.java @@ -21,8 +21,8 @@ package org.linphone.core; import org.linphone.core.LinphoneChatMessage.State; /** - * - * A chat room is the place where text messages are exchanged. + * + * A chat room is the place where text messages are exchanged. Can be created by linphone_core_create_chat_room(). * */ @@ -43,7 +43,7 @@ public interface LinphoneChatRoom { * @param chat message */ void sendMessage(LinphoneChatMessage message, LinphoneChatMessage.StateListener listener); - + /** * Create a LinphoneChatMessage * @param chatRoom chat room associated to the message @@ -51,31 +51,45 @@ public interface LinphoneChatRoom { * @return LinphoneChatMessage object */ LinphoneChatMessage createLinphoneChatMessage(String message); - + /** * Returns the chat history associated with the peer address associated with this chat room * @return an array of LinphoneChatMessage */ LinphoneChatMessage[] getHistory(); - + /** * Returns the chat history associated with the peer address associated with this chat room * @param limit the maximum number of messages to fetch * @return an array of LinphoneChatMessage */ LinphoneChatMessage[] getHistory(int limit); - + + /** + * Returns the chat history associated with the peer address associated with this chat room for the given range + * @param begin the first (most recent) message to retrieve. Newest message has index 0. If negative, use value 0 instead. + * @param end the last (oldest) message to retrieve. Oldest message has value "history size" - 1 (equivalent to -1). If negative or lower than begin value, value is given, use -1. + * @return an array of LinphoneChatMessage, empty if nothing has been found + */ + LinphoneChatMessage[] getHistoryRange(int begin, int end); + /** * Destroys a LinphoneChatRoom. */ void destroy(); - + /** * Returns the amount of unread messages associated with the peer of this chatRoom. * @return the amount of unread messages */ int getUnreadMessagesCount(); - + + /** + * Returns the amount of messages associated with the peer of this chatRoom. + * @return the amount of messages in the conversation + */ + int getHistorySize(); + /** * Deletes all the messages associated with the peer of this chat room */ @@ -91,24 +105,24 @@ public interface LinphoneChatRoom { * @return true if the remote is currently composing a message, false otherwise. */ boolean isRemoteComposing(); - + /** * Marks all the messages in this conversation as read */ void markAsRead(); - + /** * Deletes a message * @param message the message to delete */ void deleteMessage(LinphoneChatMessage message); - + /** * Update the value stored in the database for the external_body_url field * @param message to update */ void updateUrl(LinphoneChatMessage message); - + /** * Create a LinphoneChatMessage * @return LinphoneChatMessage object diff --git a/java/impl/org/linphone/core/LinphoneChatRoomImpl.java b/java/impl/org/linphone/core/LinphoneChatRoomImpl.java index b33f5db4b..5ca8e5628 100644 --- a/java/impl/org/linphone/core/LinphoneChatRoomImpl.java +++ b/java/impl/org/linphone/core/LinphoneChatRoomImpl.java @@ -27,9 +27,11 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { private native long getPeerAddress(long ptr); private native void sendMessage(long ptr, String message); private native void sendMessage2(long ptr, Object msg, long messagePtr, StateListener listener); + private native long[] getHistoryRange(long ptr, int begin, int end); private native long[] getHistory(long ptr, int limit); private native void destroy(long ptr); private native int getUnreadMessagesCount(long ptr); + private native int getHistorySize(long ptr); private native void deleteHistory(long ptr); private native void compose(long ptr); private native boolean isRemoteComposing(long ptr); @@ -53,7 +55,7 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { sendMessage(nativePtr,message); } } - + @Override public void sendMessage(LinphoneChatMessage message, StateListener listener) { synchronized(getCore()){ @@ -67,37 +69,43 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { return new LinphoneChatMessageImpl(createLinphoneChatMessage(nativePtr, message)); } } - + public LinphoneChatMessage[] getHistory() { synchronized(getCore()){ return getHistory(0); } } - + + public LinphoneChatMessage[] getHistoryRange(int begin, int end) { + synchronized(getCore()){ + long[] typesPtr = getHistoryRange(nativePtr, begin, end); + return getHistoryPrivate(typesPtr); + } + } + public LinphoneChatMessage[] getHistory(int limit) { synchronized(getCore()){ long[] typesPtr = getHistory(nativePtr, limit); - if (typesPtr == null) return null; - - LinphoneChatMessage[] messages = new LinphoneChatMessage[typesPtr.length]; - for (int i=0; i < messages.length; i++) { - messages[i] = new LinphoneChatMessageImpl(typesPtr[i]); - } - - return messages; + return getHistoryPrivate(typesPtr); } } - + public void destroy() { destroy(nativePtr); } - + public int getUnreadMessagesCount() { synchronized(getCore()){ return getUnreadMessagesCount(nativePtr); } } - + + public int getHistorySize() { + synchronized(getCore()){ + return getHistorySize(nativePtr); + } + } + public void deleteHistory() { synchronized(getCore()){ deleteHistory(nativePtr); @@ -115,27 +123,27 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { return isRemoteComposing(nativePtr); } } - + public void markAsRead() { synchronized(getCore()){ markAsRead(nativePtr); } } - + public void deleteMessage(LinphoneChatMessage message) { synchronized(getCore()){ if (message != null) deleteMessage(nativePtr, ((LinphoneChatMessageImpl)message).getNativePtr()); } } - + public void updateUrl(LinphoneChatMessage message) { synchronized(getCore()){ if (message != null) updateUrl(nativePtr, ((LinphoneChatMessageImpl)message).getNativePtr()); } } - + @Override public LinphoneChatMessage createLinphoneChatMessage(String message, String url, State state, long timestamp, boolean isRead, @@ -150,4 +158,14 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom { public synchronized LinphoneCore getCore() { return (LinphoneCore)getCore(nativePtr); } + private LinphoneChatMessage[] getHistoryPrivate(long[] typesPtr) { + if (typesPtr == null) return null; + + LinphoneChatMessage[] messages = new LinphoneChatMessage[typesPtr.length]; + for (int i=0; i < messages.length; i++) { + messages[i] = new LinphoneChatMessageImpl(typesPtr[i]); + } + + return messages; + } } diff --git a/tester/message_tester.c b/tester/message_tester.c index d71f2db53..d8bd79174 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -754,12 +754,11 @@ static void is_composing_notification(void) { /* * Copy file "from" to file "to". * Destination file is truncated if existing. - * Return 1 on success, 0 on error (printing an error). + * Return 0 on success, positive value on error. */ static int message_tester_copy_file(const char *from, const char *to) { - char message[256]; FILE *in, *out; char buf[256]; size_t n; @@ -768,21 +767,17 @@ message_tester_copy_file(const char *from, const char *to) in=fopen(from, "r"); if ( in == NULL ) { - snprintf(message, 255, "Can't open %s for reading: %s\n", - from, strerror(errno)); - fprintf(stderr, "%s", message); - return 0; + ms_error("Can't open %s for reading: %s\n",from,strerror(errno)); + return 1; } /* Open "to" file for writing (will truncate existing files) */ out=fopen(to, "w"); if ( out == NULL ) { - snprintf(message, 255, "Can't open %s for writing: %s\n", - to, strerror(errno)); - fprintf(stderr, "%s", message); + ms_error("Can't open %s for writing: %s\n",to,strerror(errno)); fclose(in); - return 0; + return 2; } /* Copy data from "in" to "out" */ @@ -790,16 +785,17 @@ message_tester_copy_file(const char *from, const char *to) { if ( ! fwrite(buf, 1, n, out) ) { + ms_error("Could not write in %s: %s\n",to,strerror(errno)); fclose(in); fclose(out); - return 0; + return 3; } } fclose(in); fclose(out); - return 1; + return 0; } static int check_no_strange_time(void* data,int argc, char** argv,char** cNames) { @@ -814,7 +810,7 @@ static void message_storage_migration() { snprintf(src_db,sizeof(src_db), "%s/messages.db", liblinphone_tester_file_prefix); snprintf(tmp_db,sizeof(tmp_db), "%s/tmp.db", liblinphone_tester_writable_dir_prefix); - CU_ASSERT_EQUAL_FATAL(message_tester_copy_file(src_db, tmp_db), 1); + CU_ASSERT_EQUAL_FATAL(message_tester_copy_file(src_db, tmp_db), 0); // enable to test the performances of the migration step //linphone_core_message_storage_set_debug(marie->lc, TRUE); @@ -830,6 +826,45 @@ static void message_storage_migration() { CU_ASSERT(sqlite3_exec(marie->lc->db, "SELECT * FROM history WHERE time != '-1';", check_no_strange_time, NULL, NULL) == SQLITE_OK ); } +static void history_messages_count() { + LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); + LinphoneAddress *jehan_addr = linphone_address_new(""); + LinphoneChatRoom *chatroom; + MSList *messages; + char src_db[256]; + char tmp_db[256]; + snprintf(src_db,sizeof(src_db), "%s/messages.db", liblinphone_tester_file_prefix); + snprintf(tmp_db,sizeof(tmp_db), "%s/tmp.db", liblinphone_tester_writable_dir_prefix); + + CU_ASSERT_EQUAL_FATAL(message_tester_copy_file(src_db, tmp_db), 0); + + linphone_core_set_chat_database_path(marie->lc, tmp_db); + + chatroom = linphone_core_get_chat_room(marie->lc, jehan_addr); + CU_ASSERT_PTR_NOT_NULL(chatroom); + if (chatroom){ + MSList *history=linphone_chat_room_get_history(chatroom,0); + CU_ASSERT_EQUAL(linphone_chat_room_get_history_size(chatroom), 1270); + CU_ASSERT_EQUAL(ms_list_size(history), linphone_chat_room_get_history_size(chatroom)); + /*check the second most recent message*/ + CU_ASSERT_STRING_EQUAL(linphone_chat_message_get_text((LinphoneChatMessage *)history->next->data), "Fore and aft follow each other."); + + /*test offset+limit: retrieve the 42th latest message only and check its content*/ + messages=linphone_chat_room_get_history_range(chatroom, 42, 42); + CU_ASSERT_EQUAL(ms_list_size(messages), 1); + CU_ASSERT_STRING_EQUAL(linphone_chat_message_get_text((LinphoneChatMessage *)messages->data), "If you open yourself to the Tao is intangible and evasive, yet prefers to keep us at the mercy of the kingdom, then all of the streams of hundreds of valleys because of its limitless possibilities."); + + /*test offset without limit*/ + CU_ASSERT_EQUAL(ms_list_size(linphone_chat_room_get_history_range(chatroom, 1265, -1)), 1270-1265); + + /*test limit without offset*/ + CU_ASSERT_EQUAL(ms_list_size(linphone_chat_room_get_history_range(chatroom, 0, 5)), 6); + } + linphone_core_manager_destroy(marie); + linphone_address_destroy(jehan_addr); +} + + #endif test_t message_tests[] = { @@ -852,6 +887,7 @@ test_t message_tests[] = { { "IsComposing notification", is_composing_notification } #ifdef MSG_STORAGE_ENABLED ,{ "Database migration", message_storage_migration } + ,{ "History count", history_messages_count } #endif }; diff --git a/tester/messages.db b/tester/messages.db index 072aed397b2422206d0e6d819e2860e4aade95af..30fd10efb16cfa781f99d5fc3dd789a70b67547f 100644 GIT binary patch delta 662851 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
    X=Z=XGL{E=rTvSjZI?KD#X5BKTYgnE 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�(TxnU(zwe!ie? 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�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 G7JmU^M7fgy From 8d442bf774e0beb98d59d34e00c7b78f052eb8ee Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 19 Aug 2014 12:17:00 +0200 Subject: [PATCH 210/218] Fix bug 0001406: Missing crypto keys when adding video in an SAVP call. --- coreapi/linphonecall.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 273990742..6e652c477 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -313,7 +313,7 @@ static void setup_encryption_keys(LinphoneCall *call, SalMediaDescription *md){ for(i=0; inb_streams; i++) { if (!sal_stream_description_active(&md->streams[i])) continue; if (sal_stream_description_has_srtp(&md->streams[i]) == TRUE) { - if (keep_srtp_keys && old_md && sal_stream_description_has_srtp(&old_md->streams[i]) == TRUE){ + if (keep_srtp_keys && old_md && (sal_stream_description_active(&old_md->streams[i]) == TRUE) && (sal_stream_description_has_srtp(&old_md->streams[i]) == TRUE)) { int j; ms_message("Keeping same crypto keys."); for(j=0;j Date: Tue, 19 Aug 2014 12:25:49 +0200 Subject: [PATCH 211/218] Update ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index b1b9fc244..d6b60a173 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit b1b9fc244915ecccbba26db9440b077d5cafa85f +Subproject commit d6b60a173628b12c9ab88c07c94c5b51b3ebbadd From ead5352fd8af820bfe46e5abfa9dddfcda62eac5 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 19 Aug 2014 12:56:43 +0200 Subject: [PATCH 212/218] Add unit tests for calls with several video switches. --- tester/call_tester.c | 90 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/tester/call_tester.c b/tester/call_tester.c index a64193a3d..7e6d9f88e 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -26,6 +26,7 @@ #include "private.h" #include "liblinphone_tester.h" +static void srtp_call(void); static void call_base(LinphoneMediaEncryption mode, bool_t enable_video,bool_t enable_relay,LinphoneFirewallPolicy policy); static void disable_all_audio_codecs_except_one(LinphoneCore *lc, const char *mime); @@ -1142,7 +1143,42 @@ static bool_t add_video(LinphoneCoreManager* caller,LinphoneCoreManager* callee) /*send vfu*/ linphone_call_send_vfu_request(call_obj); return wait_for(caller->lc,callee->lc,&callee->stat.number_of_IframeDecoded,initial_callee_stat.number_of_IframeDecoded+1); - } else return 0; + } + return FALSE; +} + +static bool_t remove_video(LinphoneCoreManager *caller, LinphoneCoreManager *callee) { + LinphoneCallParams *callee_params; + LinphoneCall *call_obj; + stats initial_caller_stat = caller->stat; + stats initial_callee_stat = callee->stat; + + if (!linphone_core_get_current_call(callee->lc) + || (linphone_call_get_state(linphone_core_get_current_call(callee->lc)) != LinphoneCallStreamsRunning) + || !linphone_core_get_current_call(caller->lc) + || (linphone_call_get_state(linphone_core_get_current_call(caller->lc)) != LinphoneCallStreamsRunning)) { + ms_warning("bad state for removing video"); + return FALSE; + } + + if ((call_obj = linphone_core_get_current_call(callee->lc))) { + callee_params = linphone_call_params_copy(linphone_call_get_current_params(call_obj)); + + /* Remove video. */ + linphone_call_params_enable_video(callee_params, FALSE); + linphone_core_update_call(callee->lc, call_obj, callee_params); + + CU_ASSERT_TRUE(wait_for(caller->lc, callee->lc, &caller->stat.number_of_LinphoneCallUpdatedByRemote, initial_caller_stat.number_of_LinphoneCallUpdatedByRemote + 1)); + CU_ASSERT_TRUE(wait_for(caller->lc, callee->lc, &callee->stat.number_of_LinphoneCallUpdating, initial_callee_stat.number_of_LinphoneCallUpdating + 1)); + CU_ASSERT_TRUE(wait_for(caller->lc, callee->lc, &callee->stat.number_of_LinphoneCallStreamsRunning, initial_callee_stat.number_of_LinphoneCallStreamsRunning + 1)); + CU_ASSERT_TRUE(wait_for(caller->lc, callee->lc, &caller->stat.number_of_LinphoneCallStreamsRunning, initial_caller_stat.number_of_LinphoneCallStreamsRunning + 1)); + + CU_ASSERT_FALSE(linphone_call_params_video_enabled(linphone_call_get_current_params(linphone_core_get_current_call(callee->lc)))); + CU_ASSERT_FALSE(linphone_call_params_video_enabled(linphone_call_get_current_params(linphone_core_get_current_call(caller->lc)))); + + return TRUE; + } + return FALSE; } static void call_with_video_added(void) { @@ -1181,6 +1217,56 @@ static void call_with_video_added_random_ports(void) { linphone_core_manager_destroy(pauline); } +static void call_with_several_video_switches(void) { + int dummy = 0; + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + CU_ASSERT_TRUE(call(pauline,marie)); + + CU_ASSERT_TRUE(add_video(pauline,marie)); + wait_for_until(pauline->lc,marie->lc,&dummy,1,1000); /* Wait for VFU request exchanges to be finished. */ + CU_ASSERT_TRUE(remove_video(pauline,marie)); + CU_ASSERT_TRUE(add_video(pauline,marie)); + wait_for_until(pauline->lc,marie->lc,&dummy,1,1000); /* Wait for VFU request exchanges to be finished. */ + CU_ASSERT_TRUE(remove_video(pauline,marie)); + /*just to sleep*/ + linphone_core_terminate_all_calls(pauline->lc); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallEnd,1)); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallEnd,1)); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void srtp_call_with_several_video_switches(void) { + int dummy = 0; + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + + if (linphone_core_media_encryption_supported(marie->lc, LinphoneMediaEncryptionSRTP)) { + linphone_core_set_media_encryption(marie->lc, LinphoneMediaEncryptionSRTP); + linphone_core_set_media_encryption(pauline->lc, LinphoneMediaEncryptionSRTP); + + CU_ASSERT_TRUE(call(pauline,marie)); + + CU_ASSERT_TRUE(add_video(pauline,marie)); + wait_for_until(pauline->lc,marie->lc,&dummy,1,1000); /* Wait for VFU request exchanges to be finished. */ + CU_ASSERT_TRUE(remove_video(pauline,marie)); + CU_ASSERT_TRUE(add_video(pauline,marie)); + wait_for_until(pauline->lc,marie->lc,&dummy,1,1000); /* Wait for VFU request exchanges to be finished. */ + CU_ASSERT_TRUE(remove_video(pauline,marie)); + /*just to sleep*/ + linphone_core_terminate_all_calls(pauline->lc); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallEnd,1)); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallEnd,1)); + } else { + ms_warning("Not tested because SRTP is not available."); + } + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + static void call_with_declined_video_base(bool_t using_policy) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); @@ -2785,6 +2871,8 @@ test_t call_tests[] = { { "ZRTP ice video call", zrtp_video_ice_call }, { "Call with video added", call_with_video_added }, { "Call with video added (random ports)", call_with_video_added_random_ports }, + { "Call with several video switches", call_with_several_video_switches }, + { "SRTP call with several video switches", srtp_call_with_several_video_switches }, { "Call with video declined", call_with_declined_video}, { "Call with video declined using policy", call_with_declined_video_using_policy}, { "Call with multiple early media", multiple_early_media }, From 94d0b2dc7b8710a63db2ea09784185bdd990f22f Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 19 Aug 2014 14:45:00 +0200 Subject: [PATCH 213/218] Fix compilation with Visual Studio. --- coreapi/linphonecall.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 6e652c477..364471490 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1687,6 +1687,7 @@ int linphone_call_prepare_ice(LinphoneCall *call, bool_t incoming_offer){ void linphone_call_init_audio_stream(LinphoneCall *call){ LinphoneCore *lc=call->core; AudioStream *audiostream; + const char *location; int dscp; if (call->audiostream != NULL) return; @@ -1713,7 +1714,7 @@ void linphone_call_init_audio_stream(LinphoneCall *call){ /* equalizer location in the graph: 'mic' = in input graph, otherwise in output graph. Any other value than mic will default to output graph for compatibility */ - const char *location = lp_config_get_string(lc->config,"sound","eq_location","hp"); + location = lp_config_get_string(lc->config,"sound","eq_location","hp"); audiostream->eq_loc = (strcasecmp(location,"mic") == 0) ? MSEqualizerMic : MSEqualizerHP; ms_error("Equalizer location: %s", location); From da96438eb7b40fc24d58b3994305e5a44ad55f10 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 19 Aug 2014 14:45:18 +0200 Subject: [PATCH 214/218] Fix documentation for automatic wrapper generation. --- coreapi/linphonecore.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 5c6ee82b5..ec96715af 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1359,7 +1359,7 @@ LINPHONE_PUBLIC int linphone_chat_room_get_history_size(LinphoneChatRoom *cr); * @param[in] cr The #LinphoneChatRoom object corresponding to the conversation for which messages should be retrieved * @param[in] begin The first message of the range to be retrieved. History most recent message has index 0. * @param[in] end The last message of the range to be retrieved. History oldest message has index of history size - 1 (use #linphone_chat_room_get_history_size to retrieve history size) - * @return the list of messages in the given range, or NULL if nothing has been found. + * @return \mslist{LinphoneChatMessage} */ LINPHONE_PUBLIC MSList *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int begin, int end); From c53f70e256c464fef9dc3eea88583ffdc069fc10 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Tue, 19 Aug 2014 15:06:38 +0200 Subject: [PATCH 215/218] Fix erroneous documentation from conversation history new methods --- coreapi/linphonecore.h | 2 +- coreapi/message_storage.c | 8 +++++--- java/common/org/linphone/core/LinphoneChatRoom.java | 2 +- tester/message_tester.c | 7 +++++++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index ec96715af..773701d52 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -1355,7 +1355,7 @@ LINPHONE_PUBLIC void linphone_chat_room_delete_history(LinphoneChatRoom *cr); LINPHONE_PUBLIC int linphone_chat_room_get_history_size(LinphoneChatRoom *cr); /** - * Gets the partial list of messages in the given range, sorted from most recent to oldest. + * Gets the partial list of messages in the given range, sorted from oldest to most recent. * @param[in] cr The #LinphoneChatRoom object corresponding to the conversation for which messages should be retrieved * @param[in] begin The first message of the range to be retrieved. History most recent message has index 0. * @param[in] end The last message of the range to be retrieved. History oldest message has index of history size - 1 (use #linphone_chat_room_get_history_size to retrieve history size) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 89120aee3..adba8f00e 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -112,7 +112,7 @@ void linphone_sql_request_message(sqlite3 *db,const char *stmt,LinphoneChatRoom int ret; ret=sqlite3_exec(db,stmt,callback,cr,&errmsg); if(ret != SQLITE_OK) { - ms_error("Error in creation: %s.\n", errmsg); + ms_error("Error in creation: %s.", errmsg); sqlite3_free(errmsg); } } @@ -122,7 +122,7 @@ int linphone_sql_request(sqlite3* db,const char *stmt){ int ret; ret=sqlite3_exec(db,stmt,NULL,NULL,&errmsg); if(ret != SQLITE_OK) { - ms_error("linphone_sql_request: error sqlite3_exec(): %s.\n", errmsg); + ms_error("linphone_sql_request: error sqlite3_exec(): %s.", errmsg); sqlite3_free(errmsg); } return ret; @@ -134,7 +134,7 @@ void linphone_sql_request_all(sqlite3* db,const char *stmt, LinphoneCore* lc){ int ret; ret=sqlite3_exec(db,stmt,callback_all,lc,&errmsg); if(ret != SQLITE_OK) { - ms_error("linphone_sql_request_all: error sqlite3_exec(): %s.\n", errmsg); + ms_error("linphone_sql_request_all: error sqlite3_exec(): %s.", errmsg); sqlite3_free(errmsg); } } @@ -285,6 +285,8 @@ MSList *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int startm, i buf=ms_malloc(buf_max_size); buf=sqlite3_snprintf(buf_max_size-1,buf,"SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC",peer); + if (startm<0) startm=0; + if (endm>0&&endm>=startm){ buf=sqlite3_snprintf(buf_max_size-1,buf,"%s LIMIT %i ",buf,endm+1-startm); }else if(startm>0){ diff --git a/java/common/org/linphone/core/LinphoneChatRoom.java b/java/common/org/linphone/core/LinphoneChatRoom.java index 0d677760c..722073779 100644 --- a/java/common/org/linphone/core/LinphoneChatRoom.java +++ b/java/common/org/linphone/core/LinphoneChatRoom.java @@ -66,7 +66,7 @@ public interface LinphoneChatRoom { LinphoneChatMessage[] getHistory(int limit); /** - * Returns the chat history associated with the peer address associated with this chat room for the given range + * Returns the chat history associated with the peer address associated with this chat room for the given range, sorted from oldest to most recent * @param begin the first (most recent) message to retrieve. Newest message has index 0. If negative, use value 0 instead. * @param end the last (oldest) message to retrieve. Oldest message has value "history size" - 1 (equivalent to -1). If negative or lower than begin value, value is given, use -1. * @return an array of LinphoneChatMessage, empty if nothing has been found diff --git a/tester/message_tester.c b/tester/message_tester.c index d8bd79174..9a0a21b6c 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -824,6 +824,9 @@ static void message_storage_migration() { // check that all messages have been migrated to the UTC time storage CU_ASSERT(sqlite3_exec(marie->lc->db, "SELECT * FROM history WHERE time != '-1';", check_no_strange_time, NULL, NULL) == SQLITE_OK ); + + linphone_core_manager_destroy(marie); + remove(tmp_db); } static void history_messages_count() { @@ -859,9 +862,13 @@ static void history_messages_count() { /*test limit without offset*/ CU_ASSERT_EQUAL(ms_list_size(linphone_chat_room_get_history_range(chatroom, 0, 5)), 6); + + /*test invalid start*/ + CU_ASSERT_EQUAL(ms_list_size(linphone_chat_room_get_history_range(chatroom, 1265, 1260)), 1270-1265); } linphone_core_manager_destroy(marie); linphone_address_destroy(jehan_addr); + remove(tmp_db); } From 031f3351356450b46cbbeaa28e42a9ccfa749fcc Mon Sep 17 00:00:00 2001 From: Guillaume BIENKOWSKI Date: Tue, 19 Aug 2014 16:57:43 +0200 Subject: [PATCH 216/218] Update ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index d6b60a173..974f7ae2c 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit d6b60a173628b12c9ab88c07c94c5b51b3ebbadd +Subproject commit 974f7ae2c810a4662d4c1b6a644ee9d0be31e894 From 137688ce30784410a4dffa68191e5b8bf4519c33 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 19 Aug 2014 17:25:48 +0200 Subject: [PATCH 217/218] Prevent reference leak in Py_BuildValue in the Python wrapper. --- tools/python/apixml2python/linphone.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tools/python/apixml2python/linphone.py b/tools/python/apixml2python/linphone.py index 145a3d9c9..80f6ea376 100644 --- a/tools/python/apixml2python/linphone.py +++ b/tools/python/apixml2python/linphone.py @@ -349,7 +349,7 @@ class MethodDefinition: else: result_variable = 'cresult' if result_variable != '': - build_value_code = "pyret = Py_BuildValue(\"{fmt}\", {result_variable});\n".format(fmt=self.build_value_format, result_variable=result_variable) + build_value_code = "pyret = Py_BuildValue(\"{fmt}\", {result_variable});\n".format(fmt=self.build_value_format.replace('O', 'N'), result_variable=result_variable) body = \ """ {c_function_call_code} pylinphone_dispatch_messages(); @@ -374,12 +374,7 @@ class MethodDefinition: def format_return_result(self): if self.return_complete_type != 'void': - if self.build_value_format == 'O': - return \ -""" Py_DECREF(pyresult); - return pyret;""" - else: - return "\treturn pyret;" + return "\treturn pyret;" return "\tPy_RETURN_NONE;" def format_return_none_trace(self): @@ -755,7 +750,7 @@ class EventCallbackMethodDefinition(MethodDefinition): PyErr_Print(); }} }} -""".format(fmt=fmt, args=args) +""".format(fmt=fmt.replace('O', 'N'), args=args) def format_return_trace(self): return "\tpylinphone_trace(-1, \"[PYLINPHONE] <<< %s\", __FUNCTION__);\n" From c8bd7e1007c466d5f244c0e82e8d8e5ebb563c2f Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Wed, 20 Aug 2014 09:15:52 +0200 Subject: [PATCH 218/218] Fix android writable dir for tests --- coreapi/message_storage.c | 2 +- tester/tester.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index adba8f00e..848c91017 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -122,7 +122,7 @@ int linphone_sql_request(sqlite3* db,const char *stmt){ int ret; ret=sqlite3_exec(db,stmt,NULL,NULL,&errmsg); if(ret != SQLITE_OK) { - ms_error("linphone_sql_request: error sqlite3_exec(): %s.", errmsg); + ms_error("linphone_sql_request: statement %s -> error sqlite3_exec(): %s.", stmt, errmsg); sqlite3_free(errmsg); } return ret; diff --git a/tester/tester.c b/tester/tester.c index 12dd60a4a..98a61c052 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -50,7 +50,11 @@ const char *liblinphone_tester_file_prefix="."; #endif /* TODO: have the same "static" for QNX and windows as above? */ +#ifdef ANDROID +const char *liblinphone_tester_writable_dir_prefix = "/data/data/org.linphone.tester/cache"; +#else const char *liblinphone_tester_writable_dir_prefix = "."; +#endif const char *userhostsfile = "tester_hosts";