File indexing completed on 2023-05-30 11:40:29

0001 /*
0002     Telepathy status message parser class
0003     Copyright (C) 2017  James D. Smith <smithjd15@gmail.com>
0004 
0005     This library is free software; you can redistribute it and/or
0006     modify it under the terms of the GNU Lesser General Public
0007     License as published by the Free Software Foundation; either
0008     version 2.1 of the License, or (at your option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013     Lesser General Public License for more details.
0014 
0015     You should have received a copy of the GNU Lesser General Public
0016     License along with this library; if not, write to the Free Software
0017     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0018 */
0019 
0020 #include <QTimer>
0021 #include <QElapsedTimer>
0022 #include <QDateTime>
0023 #include <QHash>
0024 #include <QString>
0025 #include <QRegularExpression>
0026 
0027 #include "status-message-parser.h"
0028 #include "telepathy-mpris.h"
0029 #include "ktp_kded_debug.h"
0030 
0031 static const QList<QString> parserTokens = QList<QString>() << QLatin1String("%tr")
0032                  << QLatin1String("%te") << QLatin1String("%time") << QLatin1String("%utc")
0033                  << QLatin1String("%title") << QLatin1String("%artist") << QLatin1String("%album")
0034                  << QLatin1String("%track") << QLatin1String("%tu") << QLatin1String("%tx")
0035                  << QLatin1String("%xm") << QLatin1String("%tf") << QLatin1String("%uf")
0036                  << QLatin1String("%sp") << QLatin1String("%um");
0037 
0038 StatusMessageParser::StatusMessageParser(QObject *parent)
0039     : QObject(parent),
0040     m_elapsedTimer(new QElapsedTimer()),
0041     m_updateTimer(new QTimer(this)),
0042     m_expireTimer(new QTimer(this)),
0043     m_mpris(new TelepathyMPRIS(this))
0044 {
0045     clearStatusMessage();
0046 
0047     m_expireTimer->setSingleShot(true);
0048     connect(m_expireTimer, &QTimer::timeout, [this] {
0049         Q_EMIT statusMessageChanged(parseStatusMessage(m_followUp));
0050     });
0051 
0052     connect(m_updateTimer, &QTimer::timeout, [this] {
0053         updateMessage();
0054         Q_EMIT statusMessageChanged(m_statusMessage);
0055     });
0056 
0057     connect(m_mpris, &TelepathyMPRIS::playerChange, [this] {
0058         if ((m_mpris->player()->playState <= TelepathyMPRIS::Stopped) && m_nowPlayingExpire) {
0059             parseStatusMessage(m_followUp);
0060         }
0061 
0062         updateMessage();
0063         Q_EMIT statusMessageChanged(m_statusMessage);
0064     });
0065 }
0066 
0067 StatusMessageParser::~StatusMessageParser()
0068 {
0069 }
0070 
0071 QString StatusMessageParser::statusMessage() const
0072 {
0073     return m_statusMessage;
0074 }
0075 
0076 QHash<QString, QString> StatusMessageParser::tokens() const
0077 {
0078     return m_tokens;
0079 }
0080 
0081 QString StatusMessageParser::parseStatusMessage(QString message)
0082 {
0083     if (message.isEmpty()) {
0084         clearStatusMessage();
0085         return QString();
0086     } else {
0087         m_tokens.clear();
0088     }
0089 
0090     QRegularExpression tokenRegexp(QLatin1String("([\\%|\\\\\\%]+[a-z]+\\+\"([^\"]*|[\"]*)\"*)*(?1)|[\\%|\\\\\\%]+[a-z]+\\+?[\\w\\.]+"));
0091     QRegularExpressionMatchIterator tokens = tokenRegexp.globalMatch(message);
0092     while (tokens.hasNext()) {
0093         QRegularExpressionMatch fullmatch = tokens.next();
0094         QString token = fullmatch.captured(0).section(QLatin1String("+"), 0, 0);
0095         QString tokenParam = fullmatch.captured(0).section(QLatin1String("+"), 1);
0096 
0097         if (!parserTokens.contains(token)) {
0098             qCDebug(KTP_KDED_MODULE) << "token match ignore" << token;
0099             continue;
0100         } else if (!tokenParam.isEmpty()) {
0101             if (tokenParam.startsWith(QLatin1String("\"")) && tokenParam.endsWith(QLatin1String("\""))) {
0102                 tokenParam.remove(0, 1);
0103                 tokenParam.remove(-1, 1);
0104             }
0105             qCDebug(KTP_KDED_MODULE) << "token command match" << token << tokenParam;
0106         } else {
0107             qCDebug(KTP_KDED_MODULE) << "token match" << token;
0108         }
0109 
0110         m_tokens.insert(token, tokenParam);
0111 
0112         if ((token == QLatin1String("%tr")) && !tokenParam.isEmpty()) {
0113             m_expireTimer->stop();
0114             m_expireTimer->setInterval(tokenParam.toDouble() * 1000 * 60);
0115             message.replace(fullmatch.captured(0), token);
0116         }
0117 
0118         if ((token == QLatin1String("%te")) && !tokenParam.isEmpty()) {
0119             m_elapsedTimer->invalidate();
0120             m_intervalElapsed = (tokenParam.toDouble() * 1000 * 60);
0121             message.replace(fullmatch.captured(0), token);
0122         }
0123 
0124         if ((token == QLatin1String("%time")) && !tokenParam.isEmpty()) {
0125             m_expireTimer->stop();
0126             m_expireTimer->setInterval(tokenParam.toDouble() * 1000 * 60);
0127             message.replace(fullmatch.captured(0), QDateTime::currentDateTime().addMSecs(tokenParam.toDouble() * 1000 * 60).toString(m_timeFormat));
0128         }
0129 
0130         if ((token == QLatin1String("%utc")) && !tokenParam.isEmpty()) {
0131             m_expireTimer->stop();
0132             m_expireTimer->setInterval(tokenParam.toDouble() * 1000 * 60);
0133             message.replace(fullmatch.captured(0), QDateTime::currentDateTimeUtc().addMSecs(tokenParam.toDouble() * 1000 * 60).toString(m_utcFormat));
0134         }
0135 
0136         if ((token == QLatin1String("%tu"))) {
0137             m_updateTimer->stop();
0138             m_updateTimer->setInterval(tokenParam.toDouble() * 1000 * 60);
0139             message.remove(fullmatch.captured(0));
0140         }
0141 
0142         if ((token == QLatin1String("%tx"))) {
0143             m_nowPlayingExpire = (tokenParam == QLatin1String("np"));
0144             m_expireTimer->stop();
0145             m_expireTimer->setInterval(tokenParam.toDouble() * 1000 * 60);
0146             message.remove(fullmatch.captured(0));
0147         }
0148 
0149         if ((token == QLatin1String("%xm"))) {
0150             m_followUp = tokenParam;
0151             message.remove(fullmatch.captured(0));
0152         }
0153 
0154         if ((token == QLatin1String("%tf"))) {
0155             m_timeFormat = tokenParam;
0156             message.remove(fullmatch.captured(0));
0157         }
0158 
0159         if ((token == QLatin1String("%uf"))) {
0160             m_utcFormat = tokenParam;
0161             message.remove(fullmatch.captured(0));
0162         }
0163 
0164         if ((token == QLatin1String("%sp"))) {
0165             m_separator = tokenParam;
0166             message.remove(fullmatch.captured(0));
0167         }
0168 
0169         if ((token == QLatin1String("%um"))) {
0170             message.remove(fullmatch.captured(0));
0171         }
0172     }
0173 
0174     if (m_tokens.contains(QLatin1String("%time"))) {
0175         message.replace(QRegularExpression(QLatin1String("\\B%time\\b")), QDateTime::currentDateTime().toString(m_timeFormat));
0176     }
0177 
0178     if (m_tokens.contains(QLatin1String("%utc"))) {
0179         message.replace(QRegularExpression(QLatin1String("\\B%utc\\b")), QDateTime::currentDateTimeUtc().toString(m_utcFormat));
0180     }
0181 
0182     if ((m_expireTimer->interval() != 0) && !m_expireTimer->isActive()) {
0183         m_expireTimer->start();
0184     }
0185 
0186     if (!m_elapsedTimer->isValid() && m_tokens.contains(QLatin1String("%te"))) {
0187         m_elapsedTimer->start();
0188     }
0189 
0190     message.replace(QRegularExpression(QLatin1String("\\B\\\\%(?=[a-z]+)")), QLatin1String("%"));
0191 
0192     auto hasMpris = [] (const QString &message) {
0193         bool hasMpris = message.contains(QRegularExpression(QLatin1String("\\B%title\\b")))
0194           || message.contains(QRegularExpression(QLatin1String("\\B%artist\\b")))
0195           || message.contains(QRegularExpression(QLatin1String("\\B%album\\b")))
0196           || message.contains(QRegularExpression(QLatin1String("\\B%track\\b")));
0197 
0198         return hasMpris;
0199     };
0200 
0201     if (hasMpris(message) != hasMpris(m_parsedMessage)) {
0202         m_parsedMessage = message;
0203         m_mpris->enable(hasMpris(message));
0204     } else {
0205         m_parsedMessage = message;
0206     }
0207 
0208     updateMessage();
0209 
0210     m_updateTimer->start();
0211 
0212     return m_statusMessage;
0213 }
0214 
0215 void StatusMessageParser::updateMessage()
0216 {
0217     QString message = m_parsedMessage;
0218     QString remaining;
0219     QString elapsed;
0220     QString title, artist, album, trackNr;
0221     title = artist = album = trackNr = m_separator;
0222 
0223     uint remainingTime = std::round(m_expireTimer->remainingTime() / 1000 / 60);
0224     if (remainingTime < 1) {
0225         remaining = QLatin1String("<1");
0226     } else {
0227         remaining = QString::number(remainingTime);
0228     }
0229 
0230     uint elapsedTime = std::round((m_elapsedTimer->elapsed() + m_intervalElapsed) / 1000 / 60);
0231     if (elapsedTime < 1) {
0232         elapsed = QLatin1String("<1");
0233     } else {
0234         elapsed = QString::number(elapsedTime);
0235     }
0236 
0237     if (m_tokens.contains(QLatin1String("%tr"))) {
0238         message.replace(QRegularExpression(QLatin1String("%tr")), remaining);
0239     }
0240 
0241     if (m_tokens.contains(QLatin1String("%te"))) {
0242         message.replace(QRegularExpression(QLatin1String("%te")), elapsed);
0243     }
0244 
0245     if (m_mpris->player()->playState == TelepathyMPRIS::Playing) {
0246         if (!m_mpris->player()->metadata.value(QLatin1String("xesam:title")).toString().isEmpty()) {
0247             title = m_mpris->player()->metadata.value(QLatin1String("xesam:title")).toString();
0248         }
0249 
0250         if (!m_mpris->player()->metadata.value(QLatin1String("xesam:artist")).toString().isEmpty()) {
0251             artist = m_mpris->player()->metadata.value(QLatin1String("xesam:artist")).toString();
0252         } else if (!m_mpris->player()->metadata.value(QLatin1String("xesam:albumArtist")).toString().isEmpty()) {
0253             artist = m_mpris->player()->metadata.value(QLatin1String("xesam:albumArtist")).toString();
0254         }
0255 
0256         if (!m_mpris->player()->metadata.value(QLatin1String("xesam:album")).toString().isEmpty()) {
0257             album = m_mpris->player()->metadata.value(QLatin1String("xesam:album")).toString();
0258         }
0259 
0260         if (!m_mpris->player()->metadata.value(QLatin1String("xesam:trackNumber")).toString().isEmpty()) {
0261             trackNr = m_mpris->player()->metadata.value(QLatin1String("xesam:trackNumber")).toString();
0262         }
0263     }
0264 
0265     if (m_tokens.contains(QLatin1String("%title"))) {
0266         message.replace(QRegularExpression(QLatin1String("\\B(?<![\\\\|%])%title\\b")), title);
0267     }
0268 
0269     if (m_tokens.contains(QLatin1String("%album"))) {
0270         message.replace(QRegularExpression(QLatin1String("\\B(?<![\\\\|%])%album\\b")), album);
0271     }
0272 
0273     if (m_tokens.contains(QLatin1String("%artist"))) {
0274         message.replace(QRegularExpression(QLatin1String("\\B(?<![\\\\|%])%artist\\b")), artist);
0275     }
0276 
0277     if (m_tokens.contains(QLatin1String("%track"))) {
0278         message.replace(QRegularExpression(QLatin1String("\\B(?<![\\\\|%])%track\\b")), trackNr);
0279     }
0280 
0281     m_statusMessage = message.simplified();
0282 }
0283 
0284 void StatusMessageParser::clearStatusMessage()
0285 {
0286     m_expireTimer->stop();
0287     m_updateTimer->stop();
0288     m_updateTimer->setInterval(300000);
0289     m_elapsedTimer->invalidate();
0290     m_intervalElapsed = 0;
0291 
0292     m_nowPlayingExpire = false;
0293     m_mpris->enable(false);
0294 
0295     m_statusMessage.clear();
0296     m_parsedMessage.clear();
0297     m_followUp.clear();
0298     m_tokens.clear();
0299 
0300     m_timeFormat = QLatin1String("h:mm AP t");
0301     m_utcFormat = QLatin1String("hh:mm t");
0302     m_separator = QLatin1String("...");
0303 }