I started writing Grantlee::Tubes some time in December 2010. In the course of writing it I’ve mostly been researching what the dependable API of QIODevice is. I don’t know when Tubes will get into a release of the Grantlee libraries, but it probably won’t be the next release. All of the classes don’t do error handling that they could do yet, and the QIODevice research is still ongoing.
Nevertheless I decided to start publishing blogs about Tubes in mid-March to give context to the April fools post about it.
Talk about building up a joke.
The library and concepts are real and useful though, so I’ll push on with publishing these experimental devices to the repo.
Consider a use case where you want to read from a QIODevice that is being written to by another class. For example, QTextDocumentWriter writes stream of valid ODF XML to a QIODevice and QXMLStreamReader reads a stream of XML data from a QIODevice.
How can we connect them together?
One way might be to use a QBuffer.
QBuffer buffer; QTextDocumentWriter writer(&buffer); writer.write(document); buffer.seek(0); QXMLStreamReader reader(buffer); // ... Do something with the XML.
This works, but it’s not a generic solution. If we wanted to do asynchronous writing of data to the device and asynchronous line based reading from it, we would have to make the buffer a member of a class, and when reading from it we would have to do something like this:
void MyClass::onReadyRead() { if (!m_buffer->canReadLine()) return; m_buffer->seek(0); const QByteArray line = m_buffer->readLine(); m_buffer->buffer.chop(line.length()); m_buffer->seek(m_buffer->size()); useLine(line); }
Reading from a buffer does not invalidate the data it holds. We have to use a method returning the QByteArray to chop off the part we read ourselves. We also have to remember to seek() a few times on the buffer. I didn’t even try the code out for off-by-ones.
Enter Grantlee::Channel.
Grantlee::Channel already made an appearance in my last post. The idea is to solve a connection problem with QIODevices. While Pump can transfer data from a device that should be read to a device that should be written to, Grantlee::Channel is an intermediary providing both a device that consumes data and one that produces data.
There are several significant differences between it and QBuffer. QBuffer is not a sequential device, but Channel is. That means that The pos() and seek() methods are relevant API when working with a QBuffer, but irrelevant and meaningless when working with a Channel. As an implementor of the QIODevice API that means I don’t have to implement any meaning into those virtual methods and can ignore them. Instead I can implement the readData and writeData methods to implement a FIFO semantic. The Channel can be written to at any time, and read from whenever it has data. There is no need for seek()ing, and it automatically discards data that has been read, meaning no manual memory conservation responsibility for the caller.
QTextDocument *document = getDocument(); Grantlee::Channel *channel = new Grantlee::Channel(this); channel->open(QIODevice::ReadWrite); // Write to the channel QTextDocumentWriter *writer = new QTextDocumentWriter(channel, this); // Read from the channel QXmlStreamReader reader(channel); // Use the reader.
Easy.