16.3.  The Façade Pattern

[ fromfile: facade.xml id: facade ]

Experience gained from the struggle to reuse classes with difficult interfaces can provide valuable motivation for designing elegant, friendly, and useful interfaces for your own classes. In this example, we discuss a Qt wrapper for a multimedia metadata system.

A multimedia file contains audio and/or video content: sound effects, music, graphic images, animation, movies, and so on. Metadata is information that describes the audio or video content of a multimedia file. Metadata is often referred to as tag data or, simply, tags.

Multimedia files typically store metadata in the same file that contains the media. Tag information can be used by a media player to display pertinent information (e.g., title, artist, genre, and so on) about a selection that is currently being played or that is being queued for play. It can also be used to organize collections of files or to create playlists.

There are many libraries that can be used to read metadata from files. Earlier versions of this book used id3lib, which is no longer supported. Now there is Phonon, which was added to Qt 4.4. Phonon supports more file formats, but did not work well with MP3s on Windows, and only reads, but does not write back tag data. There is also Taglib 1.6, a library that is similar to id3lib, but supports modern file formats, and builds on Windows, Linux and Mac OS/X. TagLib is used by many open source tools such as Amarok and kid3.

We have organized our code in such a way that we can easily switch between Phonon and TagLib in our applications. Figure 16.4 shows the way that MetaDataLoader and MetaDataValue fit together, providing a façade between our applications and the underlying metadata library used. If we wanted a way of dynamically choosing one of these in our program (depending perhaps, on the platform or file format that is used), we might further package phononmetadata and filetagger into plugins.

Figure 16.4.  MetaDataLoader and MetaDataValue

MetaDataLoader and MetaDataValue

MetaDataValue, shown in Example 16.15, is the base class interface for working with metadata values.

Example 16.15. src/libs/metadata/metadatavalue.h

[ . . . . ]
class METADATAEXPORT MetaDataValue {
public:

    friend METADATAEXPORT QTextStream& operator<< (QTextStream& os, 
                                         const MetaDataValue& mdv);
    friend METADATAEXPORT QTextStream& operator>> (QTextStream& is,
                                         MetaDataValue& mdv);
    friend METADATAEXPORT QDataStream& operator<< (QDataStream& os,
                                         const MetaDataValue& mdv);
    friend METADATAEXPORT QDataStream& operator>> (QDataStream& is,
                                         MetaDataValue& mdv);
    friend METADATAEXPORT bool operator==(const MetaDataValue&,
                                          const MetaDataValue&);
[ . . . . ]
    virtual QString fileName() const ;
    virtual Preference preference() const ;
    virtual QString genre() const;
    virtual QString artist() const;
    virtual QString albumTitle() const;
    virtual QString trackTitle() const;
    virtual QString trackNumber() const;
    virtual const QImage &image() const;
    virtual QTime trackTime() const;
    virtual QString trackTimeString() const;
    virtual QString comment() const;
[ . . . . ]
protected:
    bool m_isNull;
    QUrl m_Url;
    QString m_TrackNumber;
    QString m_TrackTitle;
    QString m_Comment;
    Preference m_Preference;
    QString m_Genre;
    QString m_Artist;
    QTime m_TrackTime;
    QString m_AlbumTitle;
    QImage m_Image;
};
Q_DECLARE_METATYPE(MetaDataValue);  1
[ . . . . ]

1

Add to QVariant type system.


MetaDataValue objects can come from tags on disk, a database, or the user. Different libraries supply a custom MetaDataLoader class which emits a value of this type in a signal parameter.

Example 16.16. src/libs/metadata/abstractmetadataloader.h

[ . . . . ]
namespace Abstract {
class METADATAEXPORT MetaDataLoader : public QObject {
    Q_OBJECT
public:
    
    explicit MetaDataLoader(QObject *parent = 0) 
    	: QObject(parent) {}
    static MetaDataLoader* instance();
    virtual MetaDataLoader* clone(QObject* parent=0) = 0;
    virtual ~MetaDataLoader();
    virtual const QStringList& supportedExtensions() = 0;
    virtual void get(QString path) = 0;
    virtual void get(QStringList path) = 0;
    virtual bool isRunning() const = 0;
public slots:
    virtual void cancel() = 0;
    
signals:
    void fetched(const MetaDataValue& mdv);
    void progressValueChanged(int);
    void progressRangeChanged(int, int);
    void finished();

};
}

#endif // AMETADATALOADER_H

AbstractMetaDataLoader, shown in Example 16.16 has a simple, nonblocking get() method that will return immediately. It emits a fetched(MetaDataValue) signal, which can be connected to a slot of another object, such as a PlayListModel. Example 16.17 shows a concrete MetaDataLoader for TagLib.

Example 16.17. src/libs/filetagger/tmetadataloader.h

[ . . . . ]
class FILETAGGER_EXPORT MetaDataLoader 
                          : public Abstract::MetaDataLoader {
    Q_OBJECT
public:
    typedef Abstract::MetaDataLoader SUPER;
    explicit MetaDataLoader(QObject *parent = 0);
    static MetaDataLoader* instance();
    virtual ~MetaDataLoader() {}
    const QStringList &supportedExtensions() ;
    MetaDataLoader* clone(QObject *parent) ;
    void get(QString path);
    void get(QStringList path);
    bool isRunning() const {return m_running;}
public slots:
    void cancel();
private slots:
    void checkForWork();

private:
    bool m_running;
    bool m_canceled;
    int m_processingMax;
    QStringList m_queue;
    QTimer m_timer;
};
}

[ . . . . ]

Example 16.18, shows the checkForWork() method that performs its operation in a loop. It calls qApp->processEvents() to enable the GUI to remain responsive in long-running loops such as this one.

Example 16.18. src/libs/filetagger/tmetadataloader.cpp

[ . . . . ]

TagLib::MetaDataLoader::MetaDataLoader(QObject *parent) :
    SUPER(parent) {
    m_processingMax = 0;
    m_running = false;
    qDebug() << "TagLib::MetaDataLoader created.";
    connect (this, SIGNAL(finished()), this, SLOT(checkForWork()),
             Qt::QueuedConnection);
}

void TagLib::MetaDataLoader::get(QString path) {
    m_queue << path;
    m_timer.singleShot(2000, this, SLOT(checkForWork()));
}

void TagLib::MetaDataLoader::checkForWork() {
    MetaDataFunctor functor;
    if (m_queue.isEmpty() && !m_running) {
        m_processingMax = 0;
        return;
    }
    if (m_running ) return;
    m_running = true;
    m_canceled = false;
    while (!m_queue.isEmpty()) {
        QStringList sl = m_queue;
        m_queue = QStringList();
        m_processingMax = sl.length();
        emit progressRangeChanged(0, m_processingMax);
        for (int i=0; i<m_processingMax;++i) {
            if (m_canceled) break;
            emit fetched(functor(sl[i]));
            emit progressValueChanged(i);
            qApp->processEvents();          1
        }
        m_running = false;
    }
    emit finished();
}

1

Allow the GUI to process events (and our signals to be delivered).


In Example 16.19, the actual TagLib code can be found. We have it in a functor because this was originally going to be used in a QtConcurrent algorithm. After some testing, we found that the functor was not thread-safe, so now we call it in sequence from a loop. All conversions between TagLib types and Qt's types are done here. Clients do not need to be concerned about how the data is fetched, or the string library that was used, and may continue using the MetaDataValue interface from libmetadata.

Example 16.19. src/libs/filetagger/tmetadataloader.cpp

[ . . . . ]

MetaDataValue MetaDataFunctor::operator ()(QString path) {
    using namespace TagLib;
    MetaDataValue retval;
    FileRef f(path.toLocal8Bit().constData());
    const Tag* t = f.tag();
    Q_ASSERT( t != NULL ) ;
    retval.setFileName(path);
    retval.setTrackTitle(toQString(t->title()));
    retval.setArtist(toQString(t->artist()));
    retval.setAlbumTitle(toQString(t->album()));
[ . . . . ]

    QTime time(0,0,0,0);
    const AudioProperties* ap = f.audioProperties();
    time = time.addSecs(ap->length());
    retval.setTrackTime(time);
    return retval;
}

[Note] qRegisterMetaType() and Queued Connections

You must call qRegisterMetaType<MetaDataValue>("MetaDataValue") before emitting signals over queued connections with MetaDataValue parameters. This is because under the covers, another instance is being created dynamically by QMetaType::construct().