From 7910bb23f8970dc080d8866d84d3783ffed79aa2 Mon Sep 17 00:00:00 2001 From: APTX Date: Wed, 1 Mar 2017 20:32:59 +0100 Subject: [PATCH] Add feature plugins Feature plugins are plugins meant to provide additional features. Feature loaded at startup and are notified of instances being created destroyed. Only the included localmylist plugin is planned as a feature plugin. This change removes the TimeStamp type alias from Player due to the issues a type alias has in the Meta Object system. --- aniplayer3.pro | 3 +- core/core.pro | 1 + core/include/aniplayer/featurepluginbase.h | 19 +++ core/instancemanager.cpp | 19 ++- core/instancemanager.h | 1 + core/player.cpp | 4 +- core/player.h | 14 +-- core/pluginmanager.cpp | 7 ++ core/pluginmanager.h | 13 ++- .../feature_localmylist.json | 0 .../feature_localmylist.pro | 23 ++++ .../feature_localmylist_global.h | 12 ++ .../featurelocalmylist.cpp | 110 ++++++++++++++++++ .../feature_localmylist/featurelocalmylist.h | 41 +++++++ featureplugins/featurebuildconfig.pri | 2 + featureplugins/featureplugins.pro | 7 ++ 16 files changed, 263 insertions(+), 13 deletions(-) create mode 100644 core/include/aniplayer/featurepluginbase.h create mode 100644 featureplugins/feature_localmylist/feature_localmylist.json create mode 100644 featureplugins/feature_localmylist/feature_localmylist.pro create mode 100644 featureplugins/feature_localmylist/feature_localmylist_global.h create mode 100644 featureplugins/feature_localmylist/featurelocalmylist.cpp create mode 100644 featureplugins/feature_localmylist/featurelocalmylist.h create mode 100644 featureplugins/featurebuildconfig.pri create mode 100644 featureplugins/featureplugins.pro diff --git a/aniplayer3.pro b/aniplayer3.pro index 821e32c..fb53cae 100644 --- a/aniplayer3.pro +++ b/aniplayer3.pro @@ -2,4 +2,5 @@ TEMPLATE = subdirs SUBDIRS += \ core \ - backendplugins + backendplugins \ + featureplugins diff --git a/core/core.pro b/core/core.pro index c69085c..22a282b 100644 --- a/core/core.pro +++ b/core/core.pro @@ -22,6 +22,7 @@ HEADERS += \ player.h \ include/aniplayer/backendpluginbase.h \ include/aniplayer/playerplugininterface.h \ + include/aniplayer/featurepluginbase.h \ pluginmanager.h \ videoelement.h \ timeformatter.h \ diff --git a/core/include/aniplayer/featurepluginbase.h b/core/include/aniplayer/featurepluginbase.h new file mode 100644 index 0000000..ff08d62 --- /dev/null +++ b/core/include/aniplayer/featurepluginbase.h @@ -0,0 +1,19 @@ +#ifndef FEATUREPLUGINBASE_H +#define FEATUREPLUGINBASE_H + +#include + +class FeaturePluginBase { +public: + virtual ~FeaturePluginBase() = default; + + virtual void instanceCreated(QObject *instance) = 0; + virtual void instanceDestroyed(QObject *instance) = 0; +}; + +#define ANIPLAYER_FEATURE_PLUGIN_INTERFACE_IID \ + "org.aptx.aniplayer.FeaturePluginInterface" + +Q_DECLARE_INTERFACE(FeaturePluginBase, ANIPLAYER_FEATURE_PLUGIN_INTERFACE_IID) + +#endif // FEATUREPLUGINBASE_H diff --git a/core/instancemanager.cpp b/core/instancemanager.cpp index 43eb236..d04d10d 100644 --- a/core/instancemanager.cpp +++ b/core/instancemanager.cpp @@ -6,6 +6,7 @@ #include #include +#include "aniplayer/featurepluginbase.h" #include "player.h" #include "pluginmanager.h" #include "timeformatter.h" @@ -22,6 +23,20 @@ InstanceManager::InstanceManager(QObject *parent) m_parser.addOption(positionOption); m_parser.addOption(multiInstanceOption); m_parser.addOption(cwdOption); + +#ifdef Q_OS_WIN + static QStringList pluginPaths{"featureplugins"}; +#else + static QStringList pluginPaths{"featureplugins", + "/usr/lib/aniplayer/featureplugins"}; +#endif + + m_featurePluginManager = new PluginManager; + m_featurePluginManager->setPluginDirectories(pluginPaths); + m_featurePluginManager->setPluginPrefix("feature"); + // TODO feature plugins should be looked up and not preferred. + m_featurePluginManager->setPreferredPlugins({"localmylist"}); + m_featurePluginManager->loadAll(); } void InstanceManager::startFirstInstance() { @@ -40,7 +55,6 @@ void InstanceManager::startFirstInstance() { if (!positionalArgs.empty()) { auto url = parseUserInput(positionalArgs[0], m_parser.value(cwdOption)); qCDebug(imCategory) << "Parsed positional argument as" << url; - qCDebug(imCategory) << "Parsed positional argument as" << url; player->setNextSource(url); } auto timeFormatter = new TimeFormatter{this}; @@ -126,5 +140,8 @@ Player *InstanceManager::createInstance() { auto player = new Player{instance, this}; Q_CHECK_PTR(player); + + m_featurePluginManager->forEach( + [player](FeaturePluginBase *plugin) { plugin->instanceCreated(player); }); return player; } diff --git a/core/instancemanager.h b/core/instancemanager.h index da36a51..d852c09 100644 --- a/core/instancemanager.h +++ b/core/instancemanager.h @@ -42,6 +42,7 @@ private: QCommandLineParser m_parser; QSet m_instances; PluginManager *m_backendPluginManager; + PluginManager *m_featurePluginManager; }; #endif // INSTANCEMANAGER_H diff --git a/core/player.cpp b/core/player.cpp index 906f760..6dd0544 100644 --- a/core/player.cpp +++ b/core/player.cpp @@ -59,9 +59,9 @@ Player::SubtitleStreams Player::availableSubtitleStreams() const { return m_availableSubtitleStreams; } -Player::TimeStamp Player::duration() const { return m_duration; } +double Player::duration() const { return m_duration; } -PlayerPluginInterface::TimeStamp Player::position() const { return m_position; } +double Player::position() const { return m_position; } void Player::load(const QUrl &resource) { if (canLoadVideoNow()) diff --git a/core/player.h b/core/player.h index 49a9e3c..2ad9a4c 100644 --- a/core/player.h +++ b/core/player.h @@ -18,9 +18,8 @@ class Player : public QObject, Q_PROPERTY(QUrl nextSource READ nextSource WRITE setNextSource NOTIFY nextSourceChanged) - Q_PROPERTY(Player::TimeStamp duration READ duration NOTIFY durationChanged) - Q_PROPERTY(Player::TimeStamp position READ position WRITE seek NOTIFY - positionChanged) + Q_PROPERTY(double duration READ duration NOTIFY durationChanged) + Q_PROPERTY(double position READ position WRITE seek NOTIFY positionChanged) Q_PROPERTY(Player::PlayState state READ state NOTIFY stateChanged) Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged) @@ -49,7 +48,6 @@ public: using VideoStreams = QList; using SubtitleStreams = QList; using Volume = double; - using TimeStamp = PlayerPluginInterface::TimeStamp; static const constexpr Volume MAX_VOLUME = Volume{1.0}; @@ -84,8 +82,8 @@ public: VideoStreams availableVideoStreams() const; SubtitleStreams availableSubtitleStreams() const; - Player::TimeStamp duration() const; - Player::TimeStamp position() const; + double duration() const; + double position() const; signals: void stateChanged(PlayState state); @@ -105,8 +103,8 @@ signals: void currentSourceChanged(QUrl currentSource); void nextSourceChanged(QUrl nextSource); - void durationChanged(Player::TimeStamp duration); - void positionChanged(Player::TimeStamp position); + void durationChanged(double duration); + void positionChanged(double position); public slots: // Basic Play state diff --git a/core/pluginmanager.cpp b/core/pluginmanager.cpp index bc8645d..3118935 100644 --- a/core/pluginmanager.cpp +++ b/core/pluginmanager.cpp @@ -44,6 +44,13 @@ void PluginManager::setPreferredPlugins(QStringList preferredPlugins) { emit preferredPluginsChanged(preferredPlugins); } +void PluginManager::loadAll() +{ + // TODO should load all plugins found, not all preferred plugins + for (const auto &pluginName : m_preferredPlugins) + load(pluginName); +} + bool PluginManager::load(const QString &plugin) { const auto it = m_plugins.find(plugin); diff --git a/core/pluginmanager.h b/core/pluginmanager.h index 5ae8391..ff04f1a 100644 --- a/core/pluginmanager.h +++ b/core/pluginmanager.h @@ -1,10 +1,10 @@ #ifndef PLUGINMANAGER_H #define PLUGINMANAGER_H +#include #include #include #include -#include class PluginManager : public QObject { Q_OBJECT @@ -32,11 +32,22 @@ public: return qobject_cast(bestQObjectInstance()); } + // TODO remove this hack + template void forEach(Func &&func) { + for (const auto &plugin : m_plugins) { + const auto instance = qobject_cast(plugin->instance()); + if (instance) + func(instance); + } + } + public slots: void setPluginDirectories(QStringList pluginDirectories); void setPluginPrefix(QString pluginPrefix); void setPreferredPlugins(QStringList preferredPlugins); + void loadAll(); + bool load(const QString &plugin); QObject *qObjectInstance(const QString &plugin); diff --git a/featureplugins/feature_localmylist/feature_localmylist.json b/featureplugins/feature_localmylist/feature_localmylist.json new file mode 100644 index 0000000..e69de29 diff --git a/featureplugins/feature_localmylist/feature_localmylist.pro b/featureplugins/feature_localmylist/feature_localmylist.pro new file mode 100644 index 0000000..289194d --- /dev/null +++ b/featureplugins/feature_localmylist/feature_localmylist.pro @@ -0,0 +1,23 @@ +TARGET = feature_localmylist +QT -= gui +QT += sql +TEMPLATE = lib + +include(../../core/core.pri) +include(../featurebuildconfig.pri) + +DEFINES += FEATURE_LOCALMYLIST_LIBRARY QT_DEPRECATED_WARNINGS + +SOURCES += featurelocalmylist.cpp + +HEADERS += featurelocalmylist.h\ + feature_localmylist_global.h + +DISTFILES += feature_localmylist.json + +LIBS += -llocalmylist + +unix { + target.path = $${PREFIX}/lib/aniplayer/featureplugins + INSTALLS += target +} diff --git a/featureplugins/feature_localmylist/feature_localmylist_global.h b/featureplugins/feature_localmylist/feature_localmylist_global.h new file mode 100644 index 0000000..9abab26 --- /dev/null +++ b/featureplugins/feature_localmylist/feature_localmylist_global.h @@ -0,0 +1,12 @@ +#ifndef FEATURE_LOCALMYLIST_GLOBAL_H +#define FEATURE_LOCALMYLIST_GLOBAL_H + +#include + +#if defined(FEATURE_LOCALMYLIST_LIBRARY) +# define FEATURE_LOCALMYLISTSHARED_EXPORT Q_DECL_EXPORT +#else +# define FEATURE_LOCALMYLISTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // FEATURE_LOCALMYLIST_GLOBAL_H diff --git a/featureplugins/feature_localmylist/featurelocalmylist.cpp b/featureplugins/feature_localmylist/featurelocalmylist.cpp new file mode 100644 index 0000000..3a7eeb1 --- /dev/null +++ b/featureplugins/feature_localmylist/featurelocalmylist.cpp @@ -0,0 +1,110 @@ +#include "featurelocalmylist.h" + +#include +#include + +#include +#include + +Q_LOGGING_CATEGORY(lmlCategory, "LML") + +constexpr const double PERCENT = 80.0; + +FeatureLocalMyList::FeatureLocalMyList(QObject *parent) : QObject{parent} { + LocalMyList::instance()->loadLocalSettings(); + + if (LocalMyList::instance()->database()->connect()) + qCInfo(lmlCategory) << "Init successful"; + else + qCWarning(lmlCategory) << "Init failed"; +} + +void FeatureLocalMyList::instanceCreated(QObject *instance) { + qCDebug(lmlCategory) << "Registering with instance" << instance; + connect(instance, SIGNAL(currentSourceChanged(QUrl)), this, + SLOT(sourceChanged(QUrl))); + connect(instance, SIGNAL(positionChanged(double)), this, + SLOT(positionChanged(double))); + connect(instance, SIGNAL(durationChanged(double)), this, + SLOT(durationChanged(double))); +} + +void FeatureLocalMyList::instanceDestroyed(QObject *instance) { + qCDebug(lmlCategory) << "Unregistering from instance" << instance; + m_instanceMap.remove(instance); +} + +void FeatureLocalMyList::sourceChanged(const QUrl &source) { + if (!source.isLocalFile()) + return; + const auto path = source.toLocalFile(); + + const auto file = LocalMyList::instance()->database()->getFileByPath(path); + + if (!file.fid) { + qCInfo(lmlCategory) << "File" << path << "is not in LocalMyList"; + return; + } + + if (file.myWatched.isValid()) { + qCInfo(lmlCategory) << "File" << path << " already marked watched"; + return; + } + + qCInfo(lmlCategory) << "File" << path + << "found in LocalMyList, fid =" << file.fid; + + auto &data = m_instanceMap[sender()]; + data.duration = readDuration(sender()); + data.fid = file.fid; + data.path = path; +} + +void FeatureLocalMyList::durationChanged(double duration) +{ + qCDebug(lmlCategory) << "Duration changed for " << sender(); + const auto it = m_instanceMap.find(sender()); + if (it == m_instanceMap.cend()) + return; + + auto &data = it.value(); + + data.duration = duration; +} + +void FeatureLocalMyList::positionChanged(double position) { + { + const auto it = m_instanceMap.find(sender()); + if (it == m_instanceMap.cend()) + return; + + const auto &data = it.value(); + + if (data.duration < 1.0) + return; + + if (!data.fid) + return; + + if (position / data.duration * 100.0 < PERCENT) + return; + + qCInfo(lmlCategory) << "Marking file" << data.path << "watched"; + LocalMyList::instance()->markWatchedIfUnwatched(data.fid); + } + m_instanceMap.remove(sender()); +} + +double FeatureLocalMyList::readDuration(QObject *obj) +{ + const auto mo = obj->metaObject(); + const auto durationIdx = mo->indexOfProperty("duration"); + qCDebug(lmlCategory) << "duration propert index" << durationIdx; + const auto durationVariant = mo->property(durationIdx).read(obj); + if (!durationVariant.isValid()) + qCWarning(lmlCategory) << "Failed to read duration"; + + const auto duration = durationVariant.toDouble(); + qCDebug(lmlCategory) << "File duration read" << duration; + return duration; +} diff --git a/featureplugins/feature_localmylist/featurelocalmylist.h b/featureplugins/feature_localmylist/featurelocalmylist.h new file mode 100644 index 0000000..b93043f --- /dev/null +++ b/featureplugins/feature_localmylist/featurelocalmylist.h @@ -0,0 +1,41 @@ +#ifndef FEATURELOCALMYLIST_H +#define FEATURELOCALMYLIST_H + +#include +#include +#include +#include + +#include "aniplayer/featurepluginbase.h" +#include "feature_localmylist_global.h" + +class FEATURE_LOCALMYLISTSHARED_EXPORT FeatureLocalMyList + : public QObject, + public FeaturePluginBase { + Q_OBJECT + Q_PLUGIN_METADATA(IID ANIPLAYER_FEATURE_PLUGIN_INTERFACE_IID FILE + "feature_localmylist.json") + Q_INTERFACES(FeaturePluginBase) +public: + FeatureLocalMyList(QObject *parent = nullptr); + + void instanceCreated(QObject *instance) override; + void instanceDestroyed(QObject *instance) override; + +private slots: + void sourceChanged(const QUrl &source); + void durationChanged(double duration); + void positionChanged(double position); + +private: + static double readDuration(QObject *obj); + + struct FileData { + int fid; + QString path; + double duration; + }; + QHash m_instanceMap; +}; + +#endif // FEATURELOCALMYLIST_H diff --git a/featureplugins/featurebuildconfig.pri b/featureplugins/featurebuildconfig.pri new file mode 100644 index 0000000..288e559 --- /dev/null +++ b/featureplugins/featurebuildconfig.pri @@ -0,0 +1,2 @@ +include(../buildconfig.pri) +DESTDIR=../../build/featureplugins \ No newline at end of file diff --git a/featureplugins/featureplugins.pro b/featureplugins/featureplugins.pro new file mode 100644 index 0000000..acb012a --- /dev/null +++ b/featureplugins/featureplugins.pro @@ -0,0 +1,7 @@ +TEMPLATE = subdirs + +include(../config.pri) + +feature_plugin_localmylist { + SUBDIRS += feature_localmylist +} -- 2.52.0