diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index b69ddf54..57b41fc0 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -70,6 +70,7 @@ class MpdSession(network.LineProtocol): terminator = protocol.LINE_TERMINATOR encoding = protocol.ENCODING + delimeter = r'\r?\n' def __init__(self, connection): super(MpdSession, self).__init__(connection) diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index 719def69..9306ccd7 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -278,9 +278,13 @@ class LineProtocol(ThreadingActor): then splitting data along line boundaries. """ - #: What terminator to use to split lines. + #: Line terminator to use for outputed lines. terminator = '\n' + #: Regex to use for spliting lines, will be set compiled version of its + #: own value, or to ``terminator``s value if it is not set itself. + delimeter = None + #: What encoding to expect incomming data to be in, can be :class:`None`. encoding = 'utf-8' @@ -289,6 +293,11 @@ class LineProtocol(ThreadingActor): self.prevent_timeout = False self.recv_buffer = '' + if self.delimeter: + self.delimeter = re.compile(self.delimeter) + else: + self.delimeter = re.compile(self.terminator) + @property def host(self): return self.connection.host @@ -315,7 +324,8 @@ class LineProtocol(ThreadingActor): for line in self.parse_lines(): line = self.decode(line) - self.on_line_received(line) + if line is not None: + self.on_line_received(line) if not self.prevent_timeout: self.connection.enable_timeout() @@ -327,7 +337,7 @@ class LineProtocol(ThreadingActor): def parse_lines(self): """Consume new data and yield any lines found.""" while re.search(self.terminator, self.recv_buffer): - line, self.recv_buffer = re.split(self.terminator, + line, self.recv_buffer = self.delimeter.split( self.recv_buffer, 1) yield line diff --git a/tests/utils/network/lineprotocol_test.py b/tests/utils/network/lineprotocol_test.py index 41d3fbf2..3d16f81c 100644 --- a/tests/utils/network/lineprotocol_test.py +++ b/tests/utils/network/lineprotocol_test.py @@ -1,5 +1,6 @@ #encoding: utf-8 +import re import unittest from mopidy.utils import network @@ -9,16 +10,27 @@ from mock import sentinel, Mock class LineProtocolTest(unittest.TestCase): def setUp(self): self.mock = Mock(spec=network.LineProtocol) + self.mock.terminator = network.LineProtocol.terminator self.mock.encoding = network.LineProtocol.encoding + self.mock.delimeter = network.LineProtocol.delimeter self.mock.prevent_timeout = False def test_init_stores_values_in_attributes(self): + delimeter = re.compile(network.LineProtocol.terminator) network.LineProtocol.__init__(self.mock, sentinel.connection) self.assertEqual(sentinel.connection, self.mock.connection) self.assertEqual('', self.mock.recv_buffer) + self.assertEqual(delimeter, self.mock.delimeter) self.assertFalse(self.mock.prevent_timeout) + def test_init_compiles_delimeter(self): + self.mock.delimeter = '\r?\n' + delimeter = re.compile('\r?\n') + + network.LineProtocol.__init__(self.mock, sentinel.connection) + self.assertEqual(delimeter, self.mock.delimeter) + def test_on_receive_no_new_lines_adds_to_recv_buffer(self): self.mock.connection = Mock(spec=network.Connection) self.mock.recv_buffer = '' @@ -75,6 +87,15 @@ class LineProtocolTest(unittest.TestCase): network.LineProtocol.on_receive(self.mock, {'received': 'data\n'}) self.mock.on_line_received.assert_called_once_with(sentinel.decoded) + def test_on_receive_with_new_line_with_failed_decode(self): + self.mock.connection = Mock(spec=network.Connection) + self.mock.recv_buffer = '' + self.mock.parse_lines.return_value = [sentinel.line] + self.mock.decode.return_value = None + + network.LineProtocol.on_receive(self.mock, {'received': 'data\n'}) + self.assertEqual(0, self.mock.on_line_received.call_count) + def test_on_receive_with_new_lines_calls_on_recieve(self): self.mock.connection = Mock(spec=network.Connection) self.mock.recv_buffer = '' @@ -86,18 +107,21 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual(2, self.mock.on_line_received.call_count) def test_parse_lines_emtpy_buffer(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = '' lines = network.LineProtocol.parse_lines(self.mock) self.assertRaises(StopIteration, lines.next) def test_parse_lines_no_terminator(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'data' lines = network.LineProtocol.parse_lines(self.mock) self.assertRaises(StopIteration, lines.next) def test_parse_lines_termintor(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'data\n' lines = network.LineProtocol.parse_lines(self.mock) @@ -105,7 +129,17 @@ class LineProtocolTest(unittest.TestCase): self.assertRaises(StopIteration, lines.next) self.assertEqual('', self.mock.recv_buffer) + def test_parse_lines_termintor_with_carriage_return(self): + self.mock.delimeter = re.compile(r'\r?\n') + self.mock.recv_buffer = 'data\r\n' + + lines = network.LineProtocol.parse_lines(self.mock) + self.assertEqual('data', lines.next()) + self.assertRaises(StopIteration, lines.next) + self.assertEqual('', self.mock.recv_buffer) + def test_parse_lines_no_data_before_terminator(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = '\n' lines = network.LineProtocol.parse_lines(self.mock) @@ -114,6 +148,7 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual('', self.mock.recv_buffer) def test_parse_lines_extra_data_after_terminator(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'data1\ndata2' lines = network.LineProtocol.parse_lines(self.mock) @@ -122,6 +157,7 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual('data2', self.mock.recv_buffer) def test_parse_lines_unicode(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = u'æøå\n'.encode('utf-8') lines = network.LineProtocol.parse_lines(self.mock) @@ -130,6 +166,7 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual('', self.mock.recv_buffer) def test_parse_lines_multiple_lines(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'abc\ndef\nghi\njkl' lines = network.LineProtocol.parse_lines(self.mock) @@ -140,6 +177,7 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual('jkl', self.mock.recv_buffer) def test_parse_lines_multiple_calls(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'data1' lines = network.LineProtocol.parse_lines(self.mock)