]> Some of my projects - aniplayer.git/commitdiff
Add feature plugins
authorAPTX <marek321@gmail.com>
Wed, 1 Mar 2017 19:32:59 +0000 (20:32 +0100)
committerAPTX <marek321@gmail.com>
Wed, 1 Mar 2017 19:37:30 +0000 (20:37 +0100)
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.

16 files changed:
aniplayer3.pro
core/core.pro
core/include/aniplayer/featurepluginbase.h [new file with mode: 0644]
core/instancemanager.cpp
core/instancemanager.h
core/player.cpp
core/player.h
core/pluginmanager.cpp
core/pluginmanager.h
featureplugins/feature_localmylist/feature_localmylist.json [new file with mode: 0644]
featureplugins/feature_localmylist/feature_localmylist.pro [new file with mode: 0644]
featureplugins/feature_localmylist/feature_localmylist_global.h [new file with mode: 0644]
featureplugins/feature_localmylist/featurelocalmylist.cpp [new file with mode: 0644]
featureplugins/feature_localmylist/featurelocalmylist.h [new file with mode: 0644]
featureplugins/featurebuildconfig.pri [new file with mode: 0644]
featureplugins/featureplugins.pro [new file with mode: 0644]

index 821e32ced55f96240e56136883f3b7b06824fb23..fb53cae2214a4dd4080ba8ca0a8e6fed0301aa6c 100644 (file)
@@ -2,4 +2,5 @@ TEMPLATE = subdirs
 
 SUBDIRS += \
     core \
-    backendplugins
+    backendplugins \
+    featureplugins
index c69085c568ae6327f62a53fee052ef33e11b6297..22a282b90d492a30c4fe932d7c4320b2346f92f2 100644 (file)
@@ -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 (file)
index 0000000..ff08d62
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef FEATUREPLUGINBASE_H
+#define FEATUREPLUGINBASE_H
+
+#include <QObject>
+
+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
index 43eb2365b85aade5e0784c866be77edb8f9ba4bf..d04d10dd22e56037805e06ec59db486567c57234 100644 (file)
@@ -6,6 +6,7 @@
 #include <QQmlApplicationEngine>
 #include <QQmlContext>
 
+#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<FeaturePluginBase>(
+      [player](FeaturePluginBase *plugin) { plugin->instanceCreated(player); });
   return player;
 }
index da36a511e0cead02255e909897f1e2af50c00b05..d852c098cf22ebb7b0f11615308f5da320d3ea23 100644 (file)
@@ -42,6 +42,7 @@ private:
   QCommandLineParser m_parser;
   QSet<Player *> m_instances;
   PluginManager *m_backendPluginManager;
+  PluginManager *m_featurePluginManager;
 };
 
 #endif // INSTANCEMANAGER_H
index 906f760c6d5153930305ed92ff2610c2b46dfa18..6dd0544cfee094cdbe482f9731a1286441f70e28 100644 (file)
@@ -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())
index 49a9e3ce71bd445e965543ed56c267d45447f0fb..2ad9a4c989a9a37850db1587d05f4f4bd96326da 100644 (file)
@@ -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<QString>;
   using SubtitleStreams = QList<QString>;
   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
index bc8645dd1c17ffc18784a2eaf577378cf7db013e..3118935b855dd701cdaf667ab8cd046caa427957 100644 (file)
@@ -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);
index 5ae839178e3b503b20b58eaa617d5e19d452cac1..ff04f1af67e9252b280ead6e539a75d332de3097 100644 (file)
@@ -1,10 +1,10 @@
 #ifndef PLUGINMANAGER_H
 #define PLUGINMANAGER_H
 
+#include <QMap>
 #include <QObject>
 #include <QPluginLoader>
 #include <QStringList>
-#include <QMap>
 
 class PluginManager : public QObject {
   Q_OBJECT
@@ -32,11 +32,22 @@ public:
     return qobject_cast<Interface *>(bestQObjectInstance());
   }
 
+  // TODO remove this hack
+  template <typename Interface, typename Func> void forEach(Func &&func) {
+    for (const auto &plugin : m_plugins) {
+      const auto instance = qobject_cast<Interface *>(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 (file)
index 0000000..e69de29
diff --git a/featureplugins/feature_localmylist/feature_localmylist.pro b/featureplugins/feature_localmylist/feature_localmylist.pro
new file mode 100644 (file)
index 0000000..289194d
--- /dev/null
@@ -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 (file)
index 0000000..9abab26
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef FEATURE_LOCALMYLIST_GLOBAL_H
+#define FEATURE_LOCALMYLIST_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#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 (file)
index 0000000..3a7eeb1
--- /dev/null
@@ -0,0 +1,110 @@
+#include "featurelocalmylist.h"
+
+#include <QLoggingCategory>
+#include <QMetaProperty>
+
+#include <LocalMyList/Database>
+#include <LocalMyList/MyList>
+
+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 (file)
index 0000000..b93043f
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef FEATURELOCALMYLIST_H
+#define FEATURELOCALMYLIST_H
+
+#include <QHash>
+#include <QObject>
+#include <QUrl>
+#include <QtPlugin>
+
+#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<QObject *, FileData> m_instanceMap;
+};
+
+#endif // FEATURELOCALMYLIST_H
diff --git a/featureplugins/featurebuildconfig.pri b/featureplugins/featurebuildconfig.pri
new file mode 100644 (file)
index 0000000..288e559
--- /dev/null
@@ -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 (file)
index 0000000..acb012a
--- /dev/null
@@ -0,0 +1,7 @@
+TEMPLATE = subdirs
+
+include(../config.pri)
+
+feature_plugin_localmylist {
+    SUBDIRS += feature_localmylist
+}