There’s a lot of discussion these days about how to handle errors in C++ without using exceptions, or more specifically, the best currently-usable alternative to std::expected. Do you use a boost::outcome? An abseil::StatusOr? An std::variant? These are all ways to return a single value containing both a result and its error information, which may be “all okay”. This is the pattern used in Haskell and Rust.
I tried setting up a Go-style system where the result and its error information are returned as separate values. I think it’s pretty good. What follows is my contribution to the discussion.
This error-handling system extends the familiar idea of using an std::pair, where the first half contains the result and the second contains the error-status information. With std::tuple, structured binding and std::tie, you have C++’s answer to what Python calls unpacking and JavasScript calls destructuring.
Let me introduce an example of a partial libmpdclient wrapper to demonstrate this.
There were two technical considerations that went into the wrapper. One is that while libmpdclient uses error codes, those error codes aren’t necessarily exceptional. In some cases, you’re supposed to do a second query for more specific error information. Another is that when its functions return strings in the form of const char pointers, those strings become invalidated with subsequent API calls. Therefore, it makes sense to copy them immediately.
I chose not to wrap the API in a class; instead, I just replaced the C struct pointers with unique_ptrs. Also, I chose to return the error code and its error message as two separate values. It’s my bikeshed.
The main function connects to MPD and prints the list of albums, handling errors along the way. I think you’ll find that neither the library code nor the client code are unreasonably verbose.
A conventionally formatted version is here:
https://gist.github.com/duganchen/e40ff8a31d749f8b942e89022ba4271e
And this is the blog-formatted version, where the line lengths fit in the template:
#include <iostream>
#include <memory>
#include <mpd/client.h>
#include <string>
#include <tuple>
#include <vector>
namespace mpd {
using namespace std::string_literals;
typedef std::unique_ptr<mpd_connection,
decltype(&mpd_connection_free)>
connection;
auto get_error(const connection &conn)
{
mpd_error error_code{
mpd_connection_get_error(conn.get())};
if (MPD_ERROR_SUCCESS != error_code) {
std::string message{
mpd_connection_get_error_message(
conn.get())};
return std::make_tuple(error_code, message);
}
return std::make_tuple(error_code, ""s);
}
auto connection_new(const char *host,
unsigned port,
unsigned timeout_ms)
{
auto ptr{
mpd_connection_new(host, port, timeout_ms)};
connection conn(ptr, &mpd_connection_free);
if (!conn) {
return std::make_tuple(std::move(conn),
MPD_ERROR_OOM,
""s);
}
auto [error_code, error_message] = get_error(conn);
return std::make_tuple(std::move(conn),
error_code,
error_message);
}
auto search_db_tags(const connection &conn,
mpd_tag_type tag_type)
{
if (mpd_search_db_tags(conn.get(), tag_type)) {
return std::make_tuple(MPD_ERROR_SUCCESS, ""s);
}
return get_error(conn);
}
auto search_commit(const connection &conn)
{
if (mpd_search_commit(conn.get())) {
return std::make_tuple(MPD_ERROR_SUCCESS, ""s);
}
return get_error(conn);
}
auto recv_tags(const connection &conn,
mpd_tag_type tag_type)
{
std::vector<std::string> tags;
mpd_pair *pair{};
while ((pair = mpd_recv_pair_tag(conn.get(),
tag_type))
!= nullptr) {
tags.emplace_back(pair->value);
mpd_return_pair(conn.get(), pair);
}
auto [error_code, message] = get_error(conn);
return std::make_tuple(std::move(tags),
error_code,
message);
}
}; // namespace mpd
int main()
{
auto [connection, error_code, error_message]
= mpd::connection_new("localhost", 6600, 0);
if (MPD_ERROR_SUCCESS != error_code) {
std::cout << error_message << "\n";
return 1;
}
std::tie(error_code, error_message)
= mpd::search_db_tags(connection,
MPD_TAG_ALBUM);
if (MPD_ERROR_SUCCESS != error_code) {
std::cout << error_message << "\n";
return 1;
}
std::tie(error_code, error_message)
= mpd::search_commit(connection);
if (MPD_ERROR_SUCCESS != error_code) {
std::cout << error_message << "\n";
return 1;
}
std::vector<std::string> tags;
std::tie(tags, error_code, error_message)
= mpd::recv_tags(connection, MPD_TAG_ALBUM);
if (MPD_ERROR_SUCCESS != error_code) {
std::cout << error_message << "\n";
return 1;
}
for (auto tag : tags) {
std::cout << tag << "\n";
}
return 0;
}
This is the simplest autojump implementation for Zsh:
autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
add-zsh-hook chpwd chpwd_recent_dirs
zstyle ':chpwd:*' recent-dirs-default yes
zstyle ':completion:*' recent-dirs-insert always
alias j=cdr
As you can see, all it does is set up “cdr’, which is included with Zsh (see the “zshcontrib” manpage) and alias it to “j”, which is the command that would be installed by autojump.
Now, when you “cd” around, Zsh keeps the history and allows you to return to directories in that history with “j”. It’s integrated into Zsh’s completion system. Which, as we all know, can be made quite friendly and powerful.
The following well-known configuration option, for example, will give you a menu of completion items, for all commands including “j”:
zstyle ':completion:*' menu select
You can also set up completions to match substrings. That means that you can type “j” followed by a substring of the directory you want to go back to, press TAB, and have all directories containing that substring as the completion:
setopt complete_in_word
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|=*' 'l:|=* r:|=*'
Credit where credit is due: these useful lines are taken from oh-my-zsh:
https://github.com/ohmyzsh/ohmyzsh/blob/master/lib/completion.zsh
If you’re using FISH, it provides the “cdh” command, which is its counterpart to autojump and cdr.
There’s long been a cottage industry of CLI tools that keep a history of your cd’ing and let you jump quickly to directories in that history. That includes a number of recent projects in Rust. Nothing wrong with those, but if you’re using Zsh or FISH, then try what’s included first.
(I wrote this because I recently discovered “cdr”, I think it’s great, and I also think it’s not nearly well-known enough).
Quetzalcoatl MPD client.
It’s at the the old GitHub page:
https://github.com/duganchen/quetzalcoatl
Previous versions have moved here:
https://github.com/duganchen/quetzalcoatl-legacy
The master branch of the legacy repository is the first release, and the experimental branch is the rewrite that I eventually developed into this one.
Why An MPD Client?
I designed Quetzalcoatl as a standard desktop music player, so why is it an MPD client?
The honest answer is that the decision was technical. Gapless playback was important to me, and using MPD as the backend was the most efficient way to implement it.
C++ or Python?
I decided to use C++, not Python, this time. C++ is Qt’s native language, and with the advances in C++ over the last decade, Python’s efficiency advantages aren’t the same as they used to be.
Plus, I’ve had too many experiences (such as PyQt 4 not having the QSignalSpy class) where the Python bindings weren’t perfect.
QML or QWidgets?
Quetzalcoatl was designed for QWidgets, and QWidgets remains a better match for it than QML.
As for why I decided to do a new release of Quetzalcoatl instead of designing a new player to implement in QML: a lot of passion went into Quetzalcoatl’s UX design, and I continued to refine it long after the initial decisions. I’m just going to quote an old email where I explained my design decisions:
From the iPod I borrowed the tag-based navigation structure. I also recognized that the iPod’s navigation structure is a tree, and therefore used a tree widget to display it. From Rockbox I learned that when the user plays a song, I should queue up the entire album before starting to play that song. From Foobar2000 I took a “total time” label which shows the combined time of all selected songs in the playlist: useful when picking songs for mix CDs. Foobar2000 also gave me the drag-and-drop arrangeable playlist. From the Linux player “moc” I took the two-pane design, with the left half being the music library and the right half being the playlist.
Networking Architecture
The foundation of any interactive (as opposed to batch) application is the Event Loop. Network connections are exposed to the application (by the operating system) as sockets. One event that needs to be handled is data being available to read on those sockets. For that, the operating system provides services such as select. Integrating the select call (or equivalent) into the event loop gives you the foundation of the Reactor Pattern. Qt provides an API to handle networking in the main event loop and, if you want to, implement a reactor. It recommends that API, here:
When to Use Alternatives to Threads
Generally speaking, you should only use threads for networking if a single-threaded event-based architecture is not enough.
MPD Integration
MPD is, of course, a server that Quetzalcoatl connects to by opening a socket.
Most of its calls are synchronous; you get a response back immediately. The exception is the “idle” command, which, I believe, was introduced after I started the project. It returns data only when there is a change in the server state. While waiting for that data, the only command allowed is “noidle”, which cancels the “idle” call and returns its results immediately.
It’s not unusual, for authors of graphical desktop MPD clients to write their own data access layers to make calls to MPD. I, however, decided to use MPD’s official one: libmpdclient.
This, from MPD’s mailing list, is how you use libmpdclient in an interactive program:
mpd_send_idle(), then register libmpdclient’s socket (see mpd_connection_get_fd()) in your I/O event loop.
As soon as MPD sends the result, your event loop triggers your callback, and you can use mpd_recv_idle() to read the result. After you’re done handling those idle events, re-enter idle.
If you want to interact with MPD in the meantime, call mpd_run_noidle() and handle idle events which may have occurred; then send the desired commands to MPD. Finally re-enter idle.
This is not magic. And it’s not specific to MPD. This is basic event-based programming. Just one connection and one thread, no latencies, no timeouts, no waiting.
Re: [mpd-devel] Proper Use of IDLE
I start by create an active QSocketNotifier for MPD’s file descriptor. Then, I take the following steps to execute one synchronous MPD command:
- deactivate the socket notifier
- send noidle
- run the synchronous command (send it, immediately receive results)
- activate the socket notifier
- send idle
I’ve found that as long as those steps are followed, the only time when you actually need to worry about blocking is when you’re connecting to MPD. The mpd_connection_new function, internally, makes a getaddrinfo call, which typically is allowed to block for up to 5 seconds if it cannot resolve a hostname. The solution? Use Qt’s QHostInfo class to validate the hostname first.
While I do not want to disparage the authors of other MPD clients, I’ve seen too many that use a separate connection to handle idle notifications. One of the goals of this project is to be an example of how to avoid that.
Automated Testing
Testability is often one of the largest considerations behind a program’s architecture. See, for example, all the designs based around Dependency Injection and Inversion of control. The problem with these two patterns is that they expect the parts of the code that access external systems (such as MPD) to be abstracted into easily mockable classes. Obviously, libmpdclient does not have that quality. I mean, just look at it. There are a number ways to to deal with that (such as wrapping libmpdclient in classes), but I decided to take the recommendation of my college friend Kevin S, who personifies Joel Spolsky’s ideal of smart and gets things done. His recommendation? Have the unit test runner spin up and tear down instances of MPD.
This type of automated integration testing is not unusual. The unit test suite of a Django project, for example, will typically spin up and tear down SQLite databases as needed.
Application Architecture
In QML, you would create the UI, and expose instances of C++ classes for it to use. I decided to start with this approach, not because I’m planning an eventual port to QML, but because it’s a good approach.
Starting there, you already have the foundation of a Model-view-presenter architecture.
Model-view architectures, including variations such as the Model View Controller and the Model View Viewmodel, start by treating the UI, the “View”, as a separate application. There are at least two advantages to this. First, it separates the part you probably won’t unit test (the UI) from the part that you should (everything else). Second, it gives you the freedom to update the UI and the rest of the code independently, with changes to one having a lower chance of breaking the other.
The boundary between the View and the rest of the application is more obvious if you have a declarative UI. This is one of the advantages of declarative UI technologies.
If you have a main window with three tabs, two dialog boxes, and an Excel-style data grid, then you have one View. Remember, part of the point is to allow the UX designer to make revisions without requiring changes to the underlying code.
The Model-View-Presenter architecture includes a middle layer between the UI and the rest of the application. While I don’t know the actual reason for its name, I know that the UI is sometimes called the Presentation Tier, and I suspect that this is the reason the layer behind it is called the Presenter. I also wonder if this is the reason why OS/2’s user interface was called the Presentation Manager. This middle layer can be thin, an implementation of the Facade pattern.
Because the Presenter’s client is the View, and the View’s needs are stable and clearly defined by the program’s UX design, the Presenter can have a very clean, testable API.
The View is a Passive View, and it is also an Observer that subscribes to events from the Presenter.
This version of Quetzalcoatl follows this pattern when communicating with MPD. It encapulates libmpdclient behind a layer. The view sends commands to this layer, and it listens for the events that it emit: queue changes, status changes, and connection loss. Some of these are expected to come back after certain commands, but they can also be generated by another client. By spinning up instances of MPD, we can unit test this layer.
One of my proudest moments with the Python version, was when I launched two copies, dragged songs around in one, and watched the other one automatically update. It felt cool, in part, because I had never planned it. This time, I made a point to implement the responses to the server notifications first, before implementing the commands that would generate those responses. For example, I implemented the responses to the changes in the stored playlists before I implemented renaming or deleting them. I knew, that this way, I would have the architecture I wanted.
Future Plans
One feature from the Python version that I removed from the scope was album art. The Python version would download album art from last.fm. I even wrote a program, the Last Tagger, to tag your collection to facilitate that. For this version, I want to do it using MPD and libmpdclient. As their album art support (via the “readpicture” and “albumart” commands) looks to me to be under development, I’m going to wait a bit before implementing it.
Previous Attempts
There were two previous releases, both written in Python:
As these were the product of years of continuous refinement and dogfooding, it made sense to keep as much as I could from them. This is, after all, why Joel Spolsky recommends us to never rewrite from scratch. I hope that this fulfills their promise by keeping what was worth keeping, and also finally reaching what they were heading towards.
This is how you can get nicer directory indexes in Apache and Nginx.
First, have you ever used h5ai? I have. It’s great. But now, I want something lighter. I want a solution that does not defer to a CGI, an application server, or equivalent. I want something that uses only the web server itself. This solution should render valid HTML5, formatted using something nice, such as the Twitter Bootstrap.
Here are solutions for Apache and Nginx.
In both cases, I assume that you have “user directories” turned on, using the default settings. For example, ~dugan/public_html/blah.jpg will be served as http://SERVER/~dugan/blah.jpg.
Apache
With Apache, it’s simple. You create two HTML files: one for everything above the listing and one for everything below it.
You have the following HTML file, header.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <title>Listing directory</title> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-12">
And you have the following HTML file, footer.html:
</div> </div> </div> <script> (function () { 'use strict'; window.addEventListener('DOMContentLoaded', function () { var i, items = document.querySelectorAll('li'); document.querySelector('ul').classList.add('list-group'); for (i = 0; i < items.length; i += 1) { items[i].classList.add('list-group-item'); } }); }()); </script> </body> </html>
And then, in the root of the directory that you’re indexing, you have the following .htaccess file:
HeaderName /path/to/header.html ReadmeName /path/to/footer.html IndexOptions +SuppressHTMLPreamble -FancyIndexing
Global Installation
For example, if header.html is at http://SERVER/header.html and footer.html is at http://SERVER/footer.html, then the .htaccess will be:
HeaderName /header.html ReadmeName /footer.html IndexOptions +SuppressHTMLPreamble -FancyIndexing
Per User Installation
And if header.html is at http://SERVER/~dugan/header.html and footer.html is at http://SERVER/~dugan/footer.html, then .htaccess, which can be at ~dugan/public_html/.htaccess, will be:
HeaderName /~dugan/header.html ReadmeName /~dugan/footer.html IndexOptions +SuppressHTMLPreamble -FancyIndexing
Nginx
Nginx, unfortunately, is not as easy. Its directory listings aren’t configurable at all unless you use NgxFancyIndex, and even then I ended up forking it in order to get what I wanted.
I followed the instructions in its HACKING.rst to get the HTML that I wanted.
I also patched NgxFancyIndex to not truncate filenames at 50 characters. I changed it to a more reasonable 255 characters. The instructions to do so apply to NgxFancyIndex. In the source file ngx_http_fancyindex_module.c, I changed the definitions of NGX_HTTP_FANCYINDEX_PREALLOCATE and NGX_HTTP_FANCYINDEX_NAME_LEN from 50 to 255.
Well, I’ve done all that. Just clone my fork:
git clone https://github.com/duganchen/ngx-fancyindex.git
And build nginx against it:
./configure --add-module=/path/to/ngx-fancyindex
And then, in nginx.conf, set your user directories to use fancy indexing:
location ~ ^/~(.+?)(/.*)?$ { fancyindex on; alias /home/$1/public_html$2; index index.html index.htm; }
UPDATE: Please use my ngx-responsiveindex project instead.
I tried to implement jQuery’s Promises and Deferreds in PyQt. I was moderately successful.
The use case here is chaining QNetworkAccessManager calls. When you have the name of an artist, it takes two calls to fetch the artist’s photo from last.fm. The first call fetches a reply that has the image URL. The second call fetches the image.
I want to see how feasible it would be to do it the way you would in jQuery. Here is an actual line that you’re about to see:
service.getArtistInfo(artist).then(getUrl)\ .then(fetchArt)
In the above, getUrl and fetchArt are both callbacks.
The following example builds on my last entry, only with the artist’s bio replaced with the artist’s photo.
As mentioned, I was only moderately successful. When you write PyQt, you’re writing C++ in Python, and I had to fight the two languages the entire way. To deal with the fact that C++ is strongly-typed, I made the signals and callbacks always pass and take unicode strings. I also had to pay attention to Qt’s memory management model, where objects are deleted when their parents are.
The resulting example, with all its callbacks, is arguably even uglier and less readable than normal PyQt code would be. And no, I’m not going to try to unit test it. Yes, I realize that “I’m not going to try to unit test it” actually means “this isn’t actually usable.”
Nevertheless, here it is:
#!/usr/bin/env python from sip import setapi setapi("QDate", 2) setapi("QDateTime", 2) setapi("QTextStream", 2) setapi("QTime", 2) setapi("QVariant", 2) setapi("QString", 2) setapi("QUrl", 2) from PyQt4.QtCore import (QObject, Qt, QUrl, pyqtSignal) from PyQt4.QtGui import (QApplication, QComboBox, QLabel, QPixmap, QMainWindow, QVBoxLayout, QWidget) from PyQt4.QtNetwork import (QNetworkAccessManager, QNetworkRequest) import base64 import json import urlparse import sys def main(): app = QApplication(sys.argv) view = BigFourView() service = ArtistInfoService() presenter = BigFourPresenter(view, service) view.artistIndexChanged.connect( presenter.loadArtistAtIndex) presenter.loadArtistAtIndex(0) view.show() sys.exit(app.exec_()) class BigFourView(QMainWindow): artistIndexChanged = pyqtSignal(int) def __init__(self, parent=None): super(BigFourView, self).__init__(parent) layout = QVBoxLayout() artistBox = QComboBox() artistBox.currentIndexChanged.connect( self._onArtistIndexChanged) artistBox.addItem('Anthrax') artistBox.addItem('Megadeth') artistBox.addItem('Metallica') artistBox.addItem('Slayer') layout.addWidget(artistBox) self._artistBox = artistBox self._photoLabel = QLabel() self._photoPixmap = QPixmap() self._photoLabel.setPixmap( self._photoPixmap) layout.addWidget(self._photoLabel) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) text = self.tr('Artist Bios') self.setWindowTitle(text) def artistData(self, index): return self._artistBox.itemData( index, Qt.DisplayRole) def artistIndex(self): return self._artistBox.currentIndex() def _onArtistIndexChanged(self, index): self.artistIndexChanged.emit(index) def setPixmapData(self, data): self._photoPixmap.loadFromData(data) self._photoLabel.setPixmap( self._photoPixmap) class BigFourPresenter(object): def __init__(self, view, artistInfoService): self._view = view self._artistInfoService = artistInfoService def loadArtistAtIndex(self, index): artist = self._view.artistData(index) service = self._artistInfoService def getUrl(promise, data): deserialized = json.loads(data) artist = deserialized['artist'] url = artist['image'][-1]['#text'] promise._resolved.emit(url) promise.deleteLater() def fetchArt(promise, url): # Prevent the Promise from being # garbage-collected. promise.setParent(self._view) def artCallback(data): self._view.setPixmapData(data) promise.deleteLater() service.getArtistPhotoReply( url, artCallback) # A chain of network calls. The line that # justifies this example. service.getArtistInfo(artist)\ .then(getUrl).then(fetchArt) def _onArtistDataReceive(self, data): bio = data['artist']['bio']['content'] self._view.setBioHtml(bio) class Promise(QObject): _resolved = pyqtSignal(unicode) def __init__(self, callback, parent=None): super(Promise, self).__init__(parent) self._callback = callback def then(self, callback): promise = Promise(callback, self) self._resolved.connect(promise.resolve) return promise def resolve(self, data): self._callback(self, data) class ArtistInfoService(QObject): def __init__(self, parent=None): super(ArtistInfoService, self).__init__( parent) self._apiKey = base64.b64decode( 'Mjk1YTAxY2ZhNjVmOWU1MjFiZGQyY2Mz' 'YzM2ZDdjODk=') self._network = QNetworkAccessManager(self) def getArtistInfo(self, artist): qurl = QUrl() url = urlparse.urlunsplit([ 'http', 'ws.audioscrobbler.com', '2.0/', '', '']) qurl.setEncodedUrl(url) qurl.addEncodedQueryItem('method', 'artist.getinfo') qurl.addEncodedQueryItem('artist', artist) qurl.addEncodedQueryItem('api_key', self._apiKey) qurl.addEncodedQueryItem('format', 'json') request = QNetworkRequest(qurl) reply = self._network.get(request) def callback(myPromise, replyText): myPromise._resolved.emit(replyText) myPromise.deleteLater() promise = Promise(callback, self) def handleReply(): newReply = self.sender() newReply.deleteLater() data = reply.readAll().data() promise.resolve(data) reply.finished.connect(handleReply) return promise def getArtistPhotoReply(self, url, callback): qurl = QUrl.fromEncoded(url) request = QNetworkRequest(qurl) reply = self._network.get(request) def handleReply(): newReply = self.sender() newReply.deleteLater() data = newReply.readAll() callback(data) reply.finished.connect(handleReply) if __name__ == '__main__': main()
I had a look at Microsoft’s Model View Presenter example, and I couldn’t resist blogging a PyQt version.
For the PyQt version, we’ll have a dropdown list of music artists. When one artist is selected, its bio is retrieved from Last.FM and loaded into the UI.
I only loosely skimmed the article and its source code. What I have is consistent with Microsoft’s class diagram. As Microsoft does, I also pass instances of both the View and the Service into the Presenter.
The Service layer deserves mention. It smoothes out one of Qt’s more annoying and inconsistent parts, which is handling QNetworkReplys. See: Why QNetworkAccessManager should not have the finished(QNetworkReply *) signal. The ArtistInfoService’s “getArtistInfo” method returns an ArtistInfoReply. The ArtistInfoReply class has a “dataReceived” signal. When data is read from the network, the dataReceived signal is emitted with the deserialized reply, then both the ArtistInfoReply and the QNetworkReply that it encapsulates are automatically cleaned up. There is no need for clients to call self.sender(), or to call deleteLater() on anything.
I did what I could to not have my Last.FM API key in plain text. Yes, you can still easily steal it. This is my way of asking you not to.
As in previous examples, although the Presenter takes a View, neither the View nor the Presenter are responsible for connecting the View’s signals to the Presenter’s slots. This is to decouple them for testing.
Here is the complete application.
artist_bios.py
#!/usr/bin/env python from sip import setapi setapi("QDate", 2) setapi("QDateTime", 2) setapi("QTextStream", 2) setapi("QTime", 2) setapi("QVariant", 2) setapi("QString", 2) setapi("QUrl", 2) from PyQt4.QtCore import (QObject, Qt, QUrl, pyqtSignal) from PyQt4.QtGui import (QApplication, QComboBox, QMainWindow, QTextEdit, QVBoxLayout, QWidget) from PyQt4.QtNetwork import (QNetworkAccessManager, QNetworkRequest) import base64 import urlparse import json import sys def main(): app = QApplication(sys.argv) view = BigFourView() service = ArtistInfoService() presenter = BigFourPresenter(view, service) view.artistIndexChanged.connect( presenter.loadArtistAtIndex) presenter.loadArtistAtIndex(0) view.show() sys.exit(app.exec_()) class BigFourView(QMainWindow): artistIndexChanged = pyqtSignal(int) def __init__(self, parent=None): super(BigFourView, self).__init__(parent) layout = QVBoxLayout() artistBox = QComboBox() artistBox.currentIndexChanged.connect( self._onArtistIndexChanged) artistBox.addItem('Anthrax') artistBox.addItem('Megadeth') artistBox.addItem('Metallica') artistBox.addItem('Slayer') layout.addWidget(artistBox) self._artistBox = artistBox self._bioEdit = QTextEdit() self._bioEdit.setReadOnly(True) layout.addWidget(self._bioEdit) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) text = self.tr('Artist Bios') self.setWindowTitle(text) def artistData(self, index): return self._artistBox.itemData( index, Qt.DisplayRole) def artistIndex(self): return self._artistBox.currentIndex() def setBioHtml(self, html): self._bioEdit.setHtml(html) def _onArtistIndexChanged(self, index): self.artistIndexChanged.emit(index) class BigFourPresenter(object): def __init__(self, view, artistInfoService): self._view = view self._artistInfoService = artistInfoService def loadArtistAtIndex(self, index): artist = self._view.artistData(index) service = self._artistInfoService reply = service.getArtistInfo(artist) reply.dataReceived.connect( self._onArtistDataReceive) def _onArtistDataReceive(self, data): bio = data['artist']['bio']['content'] self._view.setBioHtml(bio) class ArtistInfoService(QObject): def __init__(self, parent=None): super(ArtistInfoService, self).__init__( parent) self._apiKey = base64.b64decode( 'Mjk1YTAxY2ZhNjVmOWU1MjFiZGQyY2Mz' 'YzM2ZDdjODk=') self._network = QNetworkAccessManager(self) def getArtistInfo(self, artist): qurl = QUrl() url = urlparse.urlunsplit([ 'http', 'ws.audioscrobbler.com', '2.0/', '', '']) qurl.setEncodedUrl(url) qurl.addEncodedQueryItem('method', 'artist.getinfo') qurl.addEncodedQueryItem('artist', artist) qurl.addEncodedQueryItem('api_key', self._apiKey) qurl.addEncodedQueryItem('format', 'json') request = QNetworkRequest(qurl) networkReply = self._network.get(request) return ArtistInfoReply(networkReply, self) class ArtistInfoReply(QObject): dataReceived = pyqtSignal(dict) def __init__(self, reply, parent=None): super(ArtistInfoReply, self).__init__( parent) reply.finished.connect(self._onFinish) def _onFinish(self): self.deleteLater() reply = self.sender() reply.deleteLater() data = reply.readAll().data() deserialized = json.loads(data) self.dataReceived.emit(deserialized) if __name__ == '__main__': main()
For tests, we’re only unit testing the Presenter. The View doesn’t need to be tested, and the Service needs not unit tests but integration tests.
Testing is slightly complicated by the implementation of the Presenter. It calls the Service to get an ArtistReply, and then connects the signal on the ArtistReply to a slot on the Presenter. When this signal is emitted, the Presenter updates the View.
This implementation is testable. We mock the View, the ArtistService and the ArtistReply. To the mock ArtistReply, we add a method to force the emission of the dataReceived signal. In two steps, the test suite first calls loadArtistAtIndex, which just hooks up the signal and exits, and then forces the signal to be emitted. Finally, it asserts that the value that was emitted has been propagated to the View.
Here is the test suite.
from sip import setapi setapi("QDate", 2) setapi("QDateTime", 2) setapi("QTextStream", 2) setapi("QTime", 2) setapi("QVariant", 2) setapi("QString", 2) setapi("QUrl", 2) from PyQt4.QtCore import QObject, pyqtSignal from artist_bios import BigFourPresenter from nose.tools import eq_ def test_presenter(): view = MockView() view.setBioHtml('') reply = MockReply() service = MockService(reply) presenter = BigFourPresenter(view, service) presenter.loadArtistAtIndex(0) reply.emitDataReceived('this is the bio') eq_('this is the bio', view.bioHtml()) class MockView(object): def bioHtml(self): return self._bioHtml def artistData(self, index): return def setBioHtml(self, html): self._bioHtml = html class MockService(object): def __init__(self, reply): self._reply = reply def getArtistInfo(self, artist): return self._reply class MockReply(QObject): dataReceived = pyqtSignal(dict) def __init__(self, parent=None): super(MockReply, self).__init__(parent) def emitDataReceived(self, bio): data = {'artist': {'bio': {'content': bio}} } self.dataReceived.emit(data)
Some of the peer feedback I received on my MVP for Qt (PyQt and PySide) made good points. Pushing all the complexity to a single monolithic Presenter class simply moves the problem; you still have to subdivide that complexity, or the Presenter will be huge. Furthermore, if you have a tabbed layout, there are benefits to having one behavioral class (Presenter, controller, etc) per tab. That way, you can decouple one tab’s behavior from that of the other tabs or from that of the main window. Decoupling is good architecture! I think, actually, that I can resolve this.
The solution is to introduce the “M” in MVP. The View layer and the Presenter layer remain, and they keep the same interfaces. Most of the Presenter’s implementation, however, will be refactored into a separate Model layer. The View uses signals to call the Presenter. The Presenter has references to both the View and the Model classes. It calls the Model classes to get things done, and then calls the View to update it as necessary.
It is a Model layer, and it can contain as many classes as you want. Including one per tab.
Furthermore, the Presenter now dispatches requests between the View and the Model layer, and does little else. All of the logic is now in the Model layer. That means that the unit test coverage will now be for the Model layer, and not for the Presenter or View.
Here’s a new example. Its main window has a text box where you enter your name. Below that are two tabs, both of which use the information in the text box. The “Greeting” tab has a “Greet” button and a “Clear” button. Clicking “Greet” causes the read-only text box on the Greeting tab to say “Hi, “, followed by the name that you entered. The “Snubbing” tab works similarly: it says “You suck” instead of “Hi”.
The View
First, let’s start with the View. For readability, I’ve defined each tab as a separate class. However, what I proposed yesterday still applies. The View is the entire main window, and it has getters and setters for properties on both tabs.
view.py
#!/usr/bin/env python from sip import setapi setapi("QDate", 2) setapi("QDateTime", 2) setapi("QTextStream", 2) setapi("QTime", 2) setapi("QVariant", 2) setapi("QString", 2) setapi("QUrl", 2) from PyQt4.QtCore import pyqtSignal from PyQt4.QtGui import (QFormLayout, QLineEdit, QMainWindow, QPushButton, QTabWidget, QVBoxLayout, QWidget) class MainWindow(QMainWindow): clearGreetingClicked = pyqtSignal() greetClicked = pyqtSignal() clearSnubbingClicked = pyqtSignal() snubClicked = pyqtSignal() def __init__(self, parent=None): super(MainWindow, self).__init__(parent) layout = QVBoxLayout() self._nameEdit = QLineEdit() nameLayout = QFormLayout() nameLayout.addRow(self.tr('&Name'), self._nameEdit) layout.addLayout(nameLayout) tabWidget = QTabWidget() self._greetTab = GreetTab() self._greetTab.writeClicked.connect( self._onGreetClick) self._greetTab.clearClicked.connect( self._onClearGreetingClick) tabWidget.addTab(self._greetTab, self.tr('&Greet')) self._snubTab = SnubTab() self._snubTab.writeClicked.connect( self._onSnubClick) self._snubTab.clearClicked.connect( self._onClearSnubbingClick) tabWidget.addTab(self._snubTab, self.tr('&Snub')) layout.addWidget(tabWidget) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) def name(self): return self._nameEdit.text() def setGreeting(self, value): self._greetTab.setMessage(value) def setSnubbing(self, value): self._snubTab.setMessage(value) def _onGreetClick(self): self.greetClicked.emit() def _onClearGreetingClick(self): self.clearGreetingClicked.emit() def _onSnubClick(self): self.snubClicked.emit() def _onClearSnubbingClick(self): self.clearSnubbingClicked.emit() class GreetTab(QWidget): clearClicked = pyqtSignal() writeClicked = pyqtSignal() def __init__(self, parent=None): super(GreetTab, self).__init__(parent) layout = QVBoxLayout() greetLabel = self.tr('Gr&eet') greetButton = QPushButton(greetLabel) greetButton.clicked.connect( self._onWriteClick) layout.addWidget(greetButton) self._greeting = QLineEdit() self._greeting.setReadOnly(True) layout.addWidget(self._greeting) clearLabel = self.tr('&Clear Greeting') clearButton = QPushButton(clearLabel) clearButton.clicked.connect( self._onClearClick) layout.addWidget(clearButton) self.setLayout(layout) def setMessage(self, value): self._greeting.setText(value) def _onClearClick(self): self.clearClicked.emit() def _onWriteClick(self): self.writeClicked.emit() class SnubTab(QWidget): clearClicked = pyqtSignal() writeClicked = pyqtSignal() def __init__(self, parent=None): super(SnubTab, self).__init__(parent) layout = QVBoxLayout() snubButton = QPushButton(self.tr('Sn&ub')) snubButton.clicked.connect( self._onWriteClick) layout.addWidget(snubButton) self._snubbing = QLineEdit() self._snubbing.setReadOnly(True) layout.addWidget(self._snubbing) clearLabel = self.tr('C&lear Snubing') clearButton = QPushButton(clearLabel) clearButton.clicked.connect( self._onClearClick) layout.addWidget(clearButton) self.setLayout(layout) def setMessage(self, value): self._snubbing.setText(value) def _onClearClick(self): self.clearClicked.emit() def _onWriteClick(self): self.writeClicked.emit()
The Presenter
The Presenter takes three constructor parameters. These parameters are the View and two Models: one for each tab. All it does is dispatch requests from the View to the Models and then update the View with what’s returned.
It has a handler for each message that the main window can send, and it knows about the entire main window. This makes it very easy to pass data from any part of the main window to any other part.
presenter.py
class Presenter(object): def __init__(self, view, greetingModel, snubbingModel): self._view = view self._greetingModel = greetingModel self._snubbingModel = snubbingModel def onGreetClick(self): name = self._view.name() greeting = self._greetingModel.getText(name) self._view.setGreeting(greeting) def onClearGreetingClick(self): self._view.setGreeting('') def onSnubClick(self): name = self._view.name() snubbing = self._snubbingModel.getText(name) self._view.setSnubbing(snubbing) def onClearSnubbingClick(self): self._view.setSnubbing('')
The Models
Each of the two Model classes implements one tab’s behavior. This is a small example, but not too small to show this:
models.py
class AbstractModel(object): def __init__(self, intro): # Where intro is "You suck" or "Hi" self._intro = intro def getText(self, name): if len(name): return self._intro + ', ' + name return '' class GreetingModel(AbstractModel): def __init__(self): super(GreetingModel, self).__init__('Hi') class SnubbingModel(AbstractModel): def __init__(self): super(SnubbingModel, self).__init__( 'You suck')
The Unit Tests
The unit test coverage is, of course, now for the Model classes.
tests.py
from nose.tools import eq_, ok_ from models import (AbstractModel, GreetingModel, SnubbingModel) def test_get_text(): model = AbstractModel('Yo') eq_('Yo, Dugan', model.getText('Dugan')) def test_get_text_empty(): model = AbstractModel('') eq_('', model.getText('')) def test_instantiations(): ok_(GreetingModel()) ok_(SnubbingModel())
The Application
The application starts the Qt event loop, instantiates the classes, connects the signals, etc.
greet_or_snub.py
#!/usr/bin/env python from sip import setapi setapi("QDate", 2) setapi("QDateTime", 2) setapi("QTextStream", 2) setapi("QTime", 2) setapi("QVariant", 2) setapi("QString", 2) setapi("QUrl", 2) from PyQt4 import QtGui from views import MainWindow from models import GreetingModel, SnubbingModel from presenters import Presenter import sys def main(): app = QtGui.QApplication(sys.argv) view = MainWindow() presenter = Presenter(view, GreetingModel(), SnubbingModel()) view.greetClicked.connect( presenter.onGreetClick) view.clearGreetingClicked.connect( presenter.onClearGreetingClick) view.snubClicked.connect(presenter.onSnubClick) view.clearSnubbingClicked.connect( presenter.onClearSnubbingClick) view.show() sys.exit(app.exec_()) if __name__ == '__main__': main()
I took my PyQt implementation of Michael Feather’s Humble Dialog box example, and I refactored it into a Model View Presenter implementation that fits PyQt and PySide better.
I’ll show you the code first, and then I’ll discuss it:
The code, filter_chain.py
#!/usr/bin/env python from sip import setapi setapi('QDate', 2) setapi('QDateTime', 2) setapi('QTextStream', 2) setapi('QTime', 2) setapi('QVariant', 2) setapi('QString', 2) setapi('QUrl', 2) from PyQt4.QtCore import QObject, pyqtSignal from PyQt4.QtGui import (QApplication, QDialog, QHBoxLayout, QLabel, QListView, QPushButton, QStandardItem, QStandardItemModel, QVBoxLayout) import sys def main(): app = QApplication(sys.argv) view = ChainComposerDialog() presenter = ChainPresenter(view) view.addClicked.connect(presenter.onAddClick) presenter.initialize() view.show() sys.exit(app.exec_()) class ChainPresenter(object): def __init__(self, view): self._view = view def initialize(self): item = QStandardItem('Reverb') self._view.appendToFilters(item) def onAddClick(self): view = self._view() for index in view.selectedFilterIndexes(): data = view.filterData(index) view.appendToChain(QStandardItem(data)) class ChainComposerDialog(QDialog): addClicked = pyqtSignal() def __init__(self, parent=None): super(ChainComposerDialog, self).__init__( parent) is called self.setWindowTitle('Composer Chain') layout = QVBoxLayout() mainLayout = QHBoxLayout() selectionLayout = QVBoxLayout() label = QLabel('Available Filters:') selectionLayout.addWidget(label) self._filterView = QListView() self._filterModel = QStandardItemModel() self._filterView.setModel(self._filterModel) selectionLayout.addWidget(self._filterView) mainLayout.addLayout(selectionLayout) actionsLayout = QVBoxLayout() actionsLayout.addStretch() addButton = QPushButton('Add>>') addButton.clicked.connect(self._onAdd) actionsLayout.addWidget(addButton) removeButton = QPushButton('Remove') actionsLayout.addWidget(removeButton) removeAllButton = QPushButton('Remove All') actionsLayout.addWidget(removeAllButton) actionsLayout.addStretch() mainLayout.addLayout(actionsLayout) chainLayout = QVBoxLayout() chainLayout.addWidget(QLabel('Chain:')) chainView = QListView() self._chainModel = QStandardItemModel() chainView.setModel(self._chainModel) chainLayout.addWidget(chainView) mainLayout.addLayout(chainLayout) layout.addLayout(mainLayout) buttonLayout = QHBoxLayout() okButton = QPushButton('OK') okButton.clicked.connect(self.accept) buttonLayout.addWidget(okButton) cancelButton = QPushButton('Cancel') cancelButton.clicked.connect(self.reject) buttonLayout.addWidget(cancelButton) layout.addLayout(buttonLayout) self.setLayout(layout) def appendToFilters(self, item): self._filterModel.appendRow(item) def selectedFilterIndexes(self): view = self._filterView selection = view.selectionModel() return selection.selectedIndexes() def filterData(self, index): return self._filterModel.data(index) def appendToChain(self, item): self._chainModel.appendRow(item) def filterCount(self): return self._filterModel.rowCount() def chainCount(self): return self._chainModel.rowCount() def _onAdd(self): self.addClicked.emit() if __name__ == '__main__': main()
The code itself is PyQt, but the discussion below also applies to PySide.
First, I replaced the QListWidgets with QStandardItemModel/QListView combinations. The architectural changes are as follows:
How It Works
The View is more Passive than it was before. It has no state except for what’s contained in the widgets, and it and does not have a reference to the Presenter. It has a clean public interface that consists entirely of getters, setters and signals. The getters and setters do just enough so that the Presenter can use the view without violating the Law of Demeter. All View widgets are treated as private.
The View has an “add” button. Clicking this causes the View to emit an “addClicked” signal. The way this works is that the add button’s “clicked” signal is connected to a private slot on the View, and this slot emits the View’s “addClicked” signal.
The View’s “addClicked” signal is connected to an “onAddClick” slot on the Presenter. The View communicates with the Presenter using signals, and the Presenter has a reference to the View. In the “onAddClick” slot, the Presenter uses this reference to manipulate the View.
In effect, events in the View send data to the Presenter, which then does any necessary processing, and then propagates any changes back to the View. This is consistent with how model-view-whatever implementations are usually supposed to work. WPF works like this; the main difference is that WPF calls the Presenter the ViewModel.
No signal from a widget is ever connected directly to a slot on another widget. There will be cases where one widget in the View just causes the hidden method to be called with a value, sending that value to the Presenter, and then then Presenter just sends that value directly back to the View, which puts it in another widget. This may seem crazy: why not just connect the signal on the first widget to the slot on the second widget? Well, that works fine until you actually want to do some processing on the value. What this architecture does is ensure that any growth of complexity will take place in the Presenter.
Notes on Scaling
For programs larger than this example, here are a couple of notes.
First, one main window or dialog box is one “View.” In tabbed layout, you might be tempted to treat each tab as a separate View, and create one Presenter per tab. I recommend not doing that.
Second, the “models” in Qt’s Model/View Programming classes are, in this architecture, part of the View. They hold data for widgets. I recommend using QStandardItemModel whenever possible. Subclass it if you need behavior that you need to subclass it for. If you need to store application-specific data, then have Qt return it when asked for that data in the Qt.UserRole. If you inherit from an abstract class higher up the hierarchy (such as QAbstractItemModel), then you’d be tempted to store data in the widget’s “model” that should be in the application’s Presenter.
How It Tests
How unit testable is this? Actually, very. First, remember that we only need to test the Presenter. The Presenter is a plain old Python object that is completely decoupled from Qt. Its View reference is introduced via dependency injection. The View itself has a very clean, very mockable interface. Here’s the unit test suite for the above code, using nose and mock:
tests.py
from filter_chain import ChainPresenter from mock import MagicMock from nose.tools import eq_, ok_ def test_initialize(): view = MagicMock() presenter = ChainPresenter(view) presenter.initialize() ok_(view.appendToFilters.called) def test_add_click(): chain = [] view = MagicMock() view.selectedFilterIndexes = MagicMock( return_value=['']) def appendToChain(value): chain.append(value) view.appendToChain = appendToChain presenter = ChainPresenter(view) presenter.onAddClick() eq_(1, len(chain))
I’ve know I’ve said this before, but I’m at work on the next version of Quetzalcoatl, my Qt-based MPD client for KDE, written in Python.
PyQt and PySide
I did not say “PyQt-based”, because PySide compatibility is on the table. Normalizing PyQt for API-compatibility with PySide is easy. If you’re importing PyQt, you set its API to version 2, and monkey patch Signal and Slot aliases (for pyqtSignal and pyqtSlot) to QtCore.
C++ or Python
C++ or Python? Both are good for writing Qt applications. This is no less a consideration than when I started.
C++ has, of course, advantages. It gives you increased performance and reduced memory usage (over Python) for free; it gives you access to classes, such as QtConcurrent and QSignalSpy, that may not be available in the Python bindings. QtConcurrent, in particular, will likely never be available in Python, because of the architecture of Python’s interpreter. Finally, C++ is easier to package (just distribute the executables and link libraries) for Windows and OS X, than Python is.
To me, these aren’t compelling enough to justify the massive increase in complexity that would come from implementing the application in C++. The performance difference isn’t meaningful for an application that spends close to 100 percent of its time waiting for user or network input. Furthermore, I’m targeting only one platform: KDE. KDE is used only on Linux, which invariably has excellent Python support. KDE is also one of the more “heavyweight” platforms available for Linux anyway.
Application Architecture
When I started Quetzalcoatl, I did not know what a socket was. I went straight to searching for Python MPD libraries, and treated them as black boxes. While this was arguably the correct way to start the first iteration, when I wanted to to get it working, it is not the correct way to start the second iteration, when I want to get it right. The new architecture will be based on an understanding of how both desktop applications and asynchronous networking work.
All desktop applications work on the principle of an event loop. This is often handled by the framework and transparent to the programmer, but raw Windows API is an exception. Events, such as clicks, drags, and mouseovers, are events, and organized as a queue. The application itself is single-threaded loop. Event handlers are registered using some variation of the Observer pattern. On each iteration, events are taken from the queue, and registered event handlers are executed.
Asynchronous networking works similarly. It also has event loop. On each iteration, open sockets are checked for the presence of incoming data. Via some implementation of the Observer pattern, you set up code to be executed when data arrives. Observer variations vary widely: callbacks, coroutines, promises, deferreds, futures and others, have all been tried. The asyncio library introduced with Python 3.4 works this way too; it just has a very, very elegant Observer implementation.
The conclusion, of course, is that the only way to do networking in a desktop application, is the following. You integrate the socket notifier into the event loop, and treat incoming data as events.
Qt’s QtNetwork module is based around this architecture. If you’re doing networking in a Qt application without it, you’re doing it wrong.
Networking in Qt
Qt Networking
For some complicated diagrams of how Qt’s networking classes work, see Inside the Qt HTTP stack.
In short, Qt provides the QTcpSocket class, which is based on a socket SELECT that is integrated into the event loop of the GUI thread. For HTTP and FTP, it also provides the higher-level QNetworkAcccessManager, or “QNAM”, which is based on a QTcpSocket. Clients for daemons with custom protocols—redis, mongodb, MPD—should be implemented using a QTcpSocket. Web service clients should be implemented using using a QNAM.
If you need Qt-based client and it doesn’t exist, you should, if it’s feasible, write it. If your client’s scope encompasses most of the protocol or web service API, then you should open-source and release it. The fact that there isn’t already a large selection of Qt-based clients to choose from is a shortcoming of the Qt programming community.
Exceptions
At work, I had an external web service client that I needed to use in Qt applications. The client was of a blocking, synchronous design. The latency was high enough to block the event loop. It was impossible to use with a QNAM. The protocol was undocumented, and rewriting it wasn’t an option.
The most obvious solution would have been to make the calls in a separate thread, but I came up with a better solution. It was to set up an internal web service (written in Flask). The Qt application would use a QNAM to call the internal web service. The internal web service would instantiate the client, use it to call the external web service, and then return the results to the Qt application. I also added a cache (I used redis, but memcached would have worked just as well) to the “proxy” web service, to cut the latency to zero.
Alternatives
I’m aware of the QtReactor project, to use Twisted with PyQt and PySide. I’m not going to say you should never use it, but do you really want to “integrate” a completely separate event loop and a completely different Observer implementation into your Qt application? Qt has its own event loop, and its own Observer implementation (signals and slots).
The MPD Protocol
Getting Quetzalcoatl’s architecture right means writing my own Qt-based MPD client. This client will implement the MPD protocol. MPD is a server and, as with all *nix servers, you can simply telnet to it and issue it commands. The client will write commands to it, via a socket.
The protocol includes the “idle” command, to subscribe to real-time notifications. When you are subscribed, you can issue no commands other than “noidle”, which fetches the results and cancels the subscription. When you are not subscribed, you can issue it any other command. Those commands return, with any data fetched, immediately.
A client should have first-class support for real-time notifications. It should go into “idle” mode when connected. When you need to issue a command, it should “noidle”, handle any results sent back, issue the command, handle any results sent back, and then go into “idle” mode again. If notifications arrive when it’s in “idle’ mode, then it should handle them, and then “idle” again.
The MPD Client
My client, therefore, will be similar to the clients available for node.js. Here is a simplified view, with one “idle” signal and one command.
class MPDClient(QtNetwork.QTcpSocket): playlist = QtCore.Signal() def status(self): # Implementation goes here.
The “playlist” signal is emitted whenever there is a change in the current playlist.
On the other hand, status returns an object similar to a QNetworkReply. It has a “completed” signal, which is emitted, along with the deserialized data, when (and not before) the reply arrives on the socket. After emitting the “completed” signal, the reply object cleans itself up (the implementation calls self.deleteLater()). You use it like this:
def handler(self, status): pprint.pprint(status) reply = client.status() reply.completed.connect(handler)
Note that handler can be an inner function. Both PyQt and PySide allow you to bind signals to anything that you can call. It’s not quite as elegant as a corresponding node.js client, where the language allows anonymous functions of arbitrary length, but it’s probably as good as you can get with Python and Qt.
Qt 5, as a C++ framework, allow you to bind signals to C++ lambdas. I would expect a C++ implementation to take advantage of that.
Implementation Plans
The current standard in Python MPD clients, python-mpd2, instantiates and encapsulates its sockets. It is designed for a synchronous pattern where you send data, then block until the reply appears. It is tightly coupled to Python’s socket implementation. The parts that serialize and deserialize data are tightly coupled to the socket operations. Even though it can be used asynchronously, in a Qt event loop (the source code has examples of using “idle” in a GTK event loop), it is not what I need.
What I want from python-mpd2 are its serializers and deserializers, decoupled from any socket operations. I’ve been extracting them into a separate library, MPD Serializers. This library is LGPL licensed, because it uses code from python-mpd2, and python-mpd2 is LGPL-licensed. I will release this library, separately, first.
The MPD client itself, the QMPDSocket, will have MPD Serializers as a dependency. It will be based on a QTcpSocket. I will release it after MPDSerializers.
A new version of Quetzalcoatl, with an architecture based on these building blocks, will follow.
Quetzalcoatl also uses last.fm. For now, it uses only a tiny percentage of the last.fm API. If I use more, I will think about releasing a QNAM-based last.fm client separately. That might be something to consider for version 3.
I’ve gotten to really like the nginx web server. Here’s how to set it up on Slackware.
Nginx
Just install the nginx SlackBuild from SlackBuilds.org.
Then make nginx’s startup script executable, and start it.
chmod +x /etc/rc.d/rc.nginx /etc/rc.d/rc.nginx start
Browse to http://localhost/, and you should see nginx’s start page.
Add the following to /etc/rc.d/rc.local, so that nginx is started on boot:
if [ -x /etc/rc.d/rc.nginx ]; then /etc/rc.d/rc.nginx start fi
MySQL
The commands for getting MySQL working are boilerplate:
cd /usr/share/mysql cp my-huge.cnf my.cnf chmod +x /etc/rc.d/rc.mysqld mysql_install_db chown -R mysql:mysql /var/lib/mysql /etc/rc.d/rc.mysqld start mysql_secure_installation
PHP
For PHP support in nginx, you need php-fpm running:
chmod +x /etc/rc.d/rc.php-fpm /etc/rc.d/rc.php-fpm start
Add the following to /etc/rc.d/rc.local, so that rc.php-fpm is started on boot:
if [ -x /etc/rc.d/rc.php-fpm ]; then /etc/rc.d/rc.php-fpm start fi
Uncomment the following section of /etc/nginx/nginx.conf:
location ~ \.php$ { root /var/www/html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }
In the same file, set nginx to recognize index.php files:
location / { root /var/www/html; index index.php index.html index.htm; }
Create /var/www/html/phpinfo.php, with the following contents:
<?php phpinfo(); ?>
Restart nginx:
/etc/rc.d/rc.nginx restart
Browse to http://localhost/phpinfo.php, and you should see that PHP is working.
PHPMyAdmin
This is where it comes together. Install PHPMyAdmin from SlackBuilds.org.
Build it with the appropriate flags:
DOCROOT=/var/www/html PHPGROUP=root ./phpmyadmin.SlackBuild
Install it, then set the permissions so that nginx can see it:
chmod 0755 /var/www/html/phpmyadmin
Browse to http://localhost/phpmyadmin/, and you can now log in and browse your MySQL databases.
I’d call that a start.
A PHP server setup isn’t complete until the server can send email. PHP’s standard library’s mail command needs to be configured for that. Typically, you want PHP to relay mail through an external SMTP server. The easiest way to do that is with msmtp.
Install, in order, libgsasl and msmtp, both from SlackBuilds.org. Use sbopkg to build a queue of packages to install, and you’ll be done in no time.
Edit /etc/httpd/php.ini and set your sendmail_path to the msmtp binary:
/usr/bin/msmtp -t
Then edit /etc/msmtprc and add the authentication credentials for your SMTP server. Templates for servers such as GMail and SendGrid should be very easy to find. Here’s one for GMail, adapted from the Arch Linux wiki:
# Accounts will inherit settings from this section defaults auth on tls on # A first gmail address account gmail host smtp.gmail.com port 587 from username@gmail.com user username@gmail.com password my_password tls_trust_file /etc/ssl/certs/ca-certificates.crt account default: gmail
Then send out a test email from PHP:
php -r 'mail("myusername@gmail.com", "testing from PHP", "Did this arrive?");'
If you want to set up Postfix (Howto) or, God forbid, Sendmail (Howto) to do this instead, you can. Those are valid options. But if the use case is just to get one application (PHP) to relay mail to an external SMTP server, then msmtp the more focused solution.