diff --git a/src/common/contacts.py b/src/common/contacts.py index d04486f8b..a4a64a0dd 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -581,11 +581,10 @@ class MetacontactManager(): #FIXME: can this append ? assert False - def get_metacontacts_tags(self, account): - '''return a list of tags for a given account''' - if not account in self._metacontacts_tags: - return [] - return self._metacontacts_tags[account].keys() + def iter_metacontacts_families(self, account): + for tag in self._metacontacts_tags[account]: + family = self._get_metacontacts_family_from_tag(account, tag) + yield family def _get_metacontacts_tag(self, account, jid): '''Returns the tag of a jid''' @@ -643,7 +642,7 @@ class MetacontactManager(): tag = self._get_metacontacts_tag(account, jid) if not tag: return False - meta_jids = self.get_metacontacts_jids(tag, accounts) + meta_jids = self._get_metacontacts_jids(tag, accounts) return len(meta_jids) > 1 or len(meta_jids[account]) > 1 def is_big_brother(self, account, jid, accounts): @@ -651,12 +650,12 @@ class MetacontactManager(): if family: nearby_family = [data for data in family if account in accounts] - bb_data = self.get_metacontacts_big_brother(nearby_family) + bb_data = self._get_metacontacts_big_brother(nearby_family) if bb_data['jid'] == jid and bb_data['account'] == account: return True return False - def get_metacontacts_jids(self, tag, accounts): + def _get_metacontacts_jids(self, tag, accounts): '''Returns all jid for the given tag in the form {acct: [jid1, jid2],.}''' answers = {} for account in self._metacontacts_tags: @@ -673,9 +672,9 @@ class MetacontactManager(): [{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional''' tag = self._get_metacontacts_tag(account, jid) - return self.get_metacontacts_family_from_tag(account, tag) + return self._get_metacontacts_family_from_tag(account, tag) - def get_metacontacts_family_from_tag(self, account, tag): + def _get_metacontacts_family_from_tag(self, account, tag): if not tag: return [] answers = [] @@ -765,8 +764,30 @@ class MetacontactManager(): if account2 > account1: return -1 return 0 + + def get_nearby_family_and_big_brother(self, family, account): + '''Return the nearby family and its Big Brother - def get_metacontacts_big_brother(self, family): + Nearby family is the part of the family that is grouped with the metacontact. + A metacontact may be over different accounts. If accounts are not merged + then the given family is split account wise. + + (nearby_family, big_brother_jid, big_brother_account) + ''' + if common.gajim.config.get('mergeaccounts'): + # group all together + nearby_family = family + else: + # we want one nearby_family per account + nearby_family = [data for data in family if account == data['account']] + + big_brother_data = self._get_metacontacts_big_brother(nearby_family) + big_brother_jid = big_brother_data['jid'] + big_brother_account = big_brother_data['account'] + + return (nearby_family, big_brother_jid, big_brother_account) + + def _get_metacontacts_big_brother(self, family): '''which of the family will be the big brother under wich all others will be ?''' family.sort(cmp=self._compare_metacontacts) diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py index 98ca5be7a..8f4746f68 100644 --- a/src/common/xmpp/dispatcher_nb.py +++ b/src/common/xmpp/dispatcher_nb.py @@ -462,7 +462,10 @@ class XMPPDispatcher(PlugIn): # we have released dispatcher, so self._owner has no methods if not res: return - self._owner.remove_timeout() + if 'remove_timeout' in self._owner.__dict__: + # When we receive data after we started disconnecting, Transport may + # already be plugged out + self._owner.remove_timeout() for (_id, _iq) in self._expected.items(): if _iq is None: # If the expected Stanza would have arrived, ProcessNonBlocking diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py index e23348bba..704cad170 100644 --- a/src/common/xmpp/transports_nb.py +++ b/src/common/xmpp/transports_nb.py @@ -645,44 +645,44 @@ class NonBlockingHTTP(NonBlockingTCP): if self.get_state() == PROXY_CONNECTING: NonBlockingTCP._on_receive(self, data) return - if not self.recvbuff: - # recvbuff empty - fresh HTTP message was received - try: - statusline, headers, self.recvbuff = self.parse_http_message(data) - except ValueError: - self.disconnect() - return - if statusline[1] != '200': - log.error('HTTP Error: %s %s' % (statusline[1], statusline[2])) - self.disconnect() - return - self.expected_length = int(headers['Content-Length']) - if 'Connection' in headers and headers['Connection'].strip()=='close': - self.close_current_connection = True - else: - #sth in recvbuff - append currently received data to HTTP msg in buffer - self.recvbuff = '%s%s' % (self.recvbuff, data) - if self.expected_length > len(self.recvbuff): + # append currently received data to HTTP msg in buffer + self.recvbuff = '%s%s' % (self.recvbuff or '', data) + statusline, headers, httpbody, buffer_rest = self.parse_http_message( + self.recvbuff) + + if not (statusline and headers and httpbody): + log.debug('Received incomplete HTTP response') + return + + if statusline[1] != '200': + log.error('HTTP Error: %s %s' % (statusline[1], statusline[2])) + self.disconnect() + return + self.expected_length = int(headers['Content-Length']) + if 'Connection' in headers and headers['Connection'].strip()=='close': + self.close_current_connection = True + + if self.expected_length > len(httpbody): # If we haven't received the whole HTTP mess yet, let's end the thread. # It will be finnished from one of following recvs on plugged socket. log.info('not enough bytes in HTTP response - %d expected, %d got' % (self.expected_length, len(self.recvbuff))) - return + else: + # First part of buffer has been extraced and is going to be handled, + # remove it from buffer + self.recvbuff = buffer_rest - # everything was received - httpbody = self.recvbuff + # everything was received + self.expected_length = 0 - self.recvbuff = '' - self.expected_length = 0 - - if not self.http_persistent or self.close_current_connection: - # not-persistent connections disconnect after response - self.disconnect(do_callback=False) - self.close_current_connection = False - self.last_recv_time = time.time() - self.on_receive(data=httpbody, socket=self) - self.on_http_request_possible() + if not self.http_persistent or self.close_current_connection: + # not-persistent connections disconnect after response + self.disconnect(do_callback=False) + self.close_current_connection = False + self.last_recv_time = time.time() + self.on_receive(data=httpbody, socket=self) + self.on_http_request_possible() def build_http_message(self, httpbody, method='POST'): ''' @@ -707,7 +707,7 @@ class NonBlockingHTTP(NonBlockingTCP): headers.append('Connection: Keep-Alive') headers.append('\r\n') headers = '\r\n'.join(headers) - return('%s%s\r\n' % (headers, httpbody)) + return('%s%s' % (headers, httpbody)) def parse_http_message(self, message): ''' @@ -716,20 +716,27 @@ class NonBlockingHTTP(NonBlockingTCP): headers - dictionary of headers e.g. {'Content-Length': '604', 'Content-Type': 'text/xml; charset=utf-8'}, httpbody - string with http body) + http_rest - what is left in the message after a full HTTP header + body ''' message = message.replace('\r','') - # Remove latest \n - if message.endswith('\n'): - message = message[:-1] - (header, httpbody) = message.split('\n\n', 1) - header = header.split('\n') - statusline = header[0].split(' ', 2) - header = header[1:] - headers = {} - for dummy in header: - row = dummy.split(' ', 1) - headers[row[0][:-1]] = row[1] - return (statusline, headers, httpbody) + splitted = message.split('\n\n') + if len(splitted) < 2: + # no complete http message. Keep filling the buffer until we find one + buffer_rest = message + return ('', '', '', buffer_rest) + else: + (header, httpbody) = splitted[:2] + if httpbody.endswith('\n'): + httpbody = httpbody[:-1] + buffer_rest = "\n\n".join(splitted[2:]) + header = header.split('\n') + statusline = header[0].split(' ', 2) + header = header[1:] + headers = {} + for dummy in header: + row = dummy.split(' ', 1) + headers[row[0][:-1]] = row[1] + return (statusline, headers, httpbody, buffer_rest) class NonBlockingHTTPBOSH(NonBlockingHTTP): diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py index d9158c568..730eb3ec3 100644 --- a/src/common/zeroconf/client_zeroconf.py +++ b/src/common/zeroconf/client_zeroconf.py @@ -494,7 +494,7 @@ class P2PConnection(IdleObject, PlugIn): self.disconnect() return True - def disconnect(self): + def disconnect(self, message=''): ''' Closes the socket. ''' gajim.idlequeue.remove_timeout(self.fd) gajim.idlequeue.unplug_idle(self.fd) diff --git a/src/gui_interface.py b/src/gui_interface.py index 0898ae39f..812aba08a 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -2145,7 +2145,7 @@ class Interface: family = gajim.contacts.get_metacontacts_family(account, jid) if family: nearby_family, bb_jid, bb_account = \ - self.roster._get_nearby_family_and_big_brother(family, account) + gajim.contacts.get_nearby_family_and_big_brother(family, account) else: bb_jid, bb_account = jid, account self.roster.select_contact(bb_jid, bb_account) diff --git a/src/roster_window.py b/src/roster_window.py index 2493f4dba..4ea9d884a 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -604,29 +604,7 @@ class RosterWindow: big_brother_account=big_brother_account) def _get_nearby_family_and_big_brother(self, family, account): - '''Return the nearby family and its Big Brother - - Nearby family is the part of the family that is grouped with the metacontact. - A metacontact may be over different accounts. If regroup is s False the - given family is split account wise. - - (nearby_family, big_brother_jid, big_brother_account) - ''' - if self.regroup: - # group all together - nearby_family = family - else: - # we want one nearby_family per account - nearby_family = [data for data in family - if account == data['account']] - - big_brother_data = gajim.contacts.get_metacontacts_big_brother( - nearby_family) - big_brother_jid = big_brother_data['jid'] - big_brother_account = big_brother_data['account'] - - return (nearby_family, big_brother_jid, big_brother_account) - + return gajim.contacts.get_nearby_family_and_big_brother(family, account) def _add_self_contact(self, account): '''Add account's SelfContact to roster and draw it and the account. @@ -649,10 +627,8 @@ class RosterWindow: return contact - def redraw_metacontacts(self, account): - for tag in gajim.contacts.get_metacontacts_tags(account): - family = gajim.contacts.get_metacontacts_family_from_tag(account, tag) + for family in gajim.contacts.iter_metacontacts_families(account): self._recalibrate_metacontact_family(family, account) def add_contact(self, jid, account): diff --git a/src/session.py b/src/session.py index 52f7b6045..04f9241eb 100644 --- a/src/session.py +++ b/src/session.py @@ -382,7 +382,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): family = gajim.contacts.get_metacontacts_family(self.conn.name, jid) if family: nearby_family, bb_jid, bb_account = \ - gajim.interface.roster._get_nearby_family_and_big_brother(family, + gajim.contacts.get_nearby_family_and_big_brother(family, self.conn.name) else: bb_jid, bb_account = jid, self.conn.name diff --git a/test/integration/test_xmpp_transports_nb.py b/test/integration/test_xmpp_transports_nb.py index f6cbab510..6e56ddf94 100644 --- a/test/integration/test_xmpp_transports_nb.py +++ b/test/integration/test_xmpp_transports_nb.py @@ -227,9 +227,10 @@ class TestNonBlockingHTTP(AbstractTransportTest): data = "Please don't fail!" http_message = transport.build_http_message(data) - statusline, headers, http_body = transport.parse_http_message( + statusline, headers, http_body, buffer_rest = transport.parse_http_message( http_message) + self.assertFalse(bool(buffer_rest)) self.assertTrue(statusline and isinstance(statusline, list)) self.assertTrue(headers and isinstance(headers, dict)) self.assertEqual(data, http_body, msg='Input and output are different') @@ -250,26 +251,26 @@ class TestNonBlockingHTTP(AbstractTransportTest): transport._on_receive(message) self.assertTrue(self.have_received_expected(), msg='Failed: In one go') -# FIXME: Not yet implemented. -# def test_receive_http_message_in_chunks(self): -# ''' Let _on_receive handle some chunked http messages ''' -# transport = self._get_transport(self.bosh_http_dict) -# -# header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" + -# "Content-Length: 88\r\n\r\n") -# payload = "Please don't fail!" -# body = "%s" \ -# % payload -# message = "%s%s" % (header, body) -# -# chunk1, chunk2, chunk3 = message[:20], message[20:73], message[73:] -# nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x" -# chunks = (chunk1, chunk2, chunk3, nextmessage_chunk) -# -# transport.onreceive(self.expect_receive(body, msg='Failed: In chunks')) -# for chunk in chunks: -# transport._on_receive(chunk) -# self.assertTrue(self.have_received_expected(), msg='Failed: In chunks') + def test_receive_http_message_in_chunks(self): + ''' Let _on_receive handle some chunked http messages ''' + transport = self._get_transport(self.bosh_http_dict) + + header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" + + "Content-Length: 88\r\n\r\n") + payload = "Please don't fail!" + body = "%s" \ + % payload + message = "%s%s" % (header, body) + + chunk1, chunk2, chunk3, chunk4 = message[:20], message[20:73], \ + message[73:85], message[85:] + nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x" + chunks = (chunk1, chunk2, chunk3, chunk4, nextmessage_chunk) + + transport.onreceive(self.expect_receive(body, msg='Failed: In chunks')) + for chunk in chunks: + transport._on_receive(chunk) + self.assertTrue(self.have_received_expected(), msg='Failed: In chunks') if __name__ == '__main__': unittest.main()