Warning, file /utilities/telly-skout/src/xmltvfetcher.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 // SPDX-FileCopyrightText: 2022 Plata Hill <plata.hill@kdemail.net>
0002 // SPDX-License-Identifier: LGPL-2.1-or-later
0003 
0004 #include "xmltvfetcher.h"
0005 
0006 #include "TellySkoutSettings.h"
0007 #include "programdata.h"
0008 
0009 #include <KLocalizedString>
0010 
0011 #include <QDateTime>
0012 #include <QDebug>
0013 #include <QString>
0014 #include <QStringView>
0015 
0016 XmltvFetcher::XmltvFetcher()
0017     : m_doc("xmltv")
0018 {
0019     m_provider.get(QUrl::fromLocalFile(TellySkoutSettings::xmltvFile()), std::bind(&XmltvFetcher::open, this, std::placeholders::_1), [](const Error &error) {
0020         Q_UNUSED(error)
0021         qCritical() << "Failed to open" << TellySkoutSettings::xmltvFile();
0022     });
0023 
0024     connect(TellySkoutSettings::self(), &TellySkoutSettings::xmltvFileChanged, this, [this]() {
0025         m_provider.get(QUrl::fromLocalFile(TellySkoutSettings::xmltvFile()),
0026                        std::bind(&XmltvFetcher::open, this, std::placeholders::_1),
0027                        [](const Error &error) {
0028                            Q_UNUSED(error)
0029                            qCritical() << "Failed to open" << TellySkoutSettings::xmltvFile();
0030                        });
0031     });
0032 }
0033 
0034 void XmltvFetcher::fetchGroups(std::function<void(const QVector<GroupData> &)> callback, std::function<void(const Error &)> errorCallback)
0035 {
0036     Q_UNUSED(errorCallback);
0037 
0038     QVector<GroupData> groups;
0039 
0040     GroupData data;
0041     data.m_id = GroupId("xmltv");
0042     data.m_name = i18n("XMLTV");
0043     data.m_url = TellySkoutSettings::xmltvFile();
0044 
0045     groups.push_back(data);
0046 
0047     if (callback) {
0048         callback(groups);
0049     }
0050 }
0051 
0052 void XmltvFetcher::fetchGroup(const QString &url,
0053                               const GroupId &groupId,
0054                               std::function<void(const QList<ChannelData> &)> callback,
0055                               std::function<void(const Error &)> errorCallback)
0056 {
0057     Q_UNUSED(errorCallback)
0058 
0059     qDebug() << "Starting to fetch group (" << groupId.value() << ", " << url << ")";
0060 
0061     QMap<ChannelId, ChannelData> channels;
0062 
0063     QDomNodeList nodes = m_doc.elementsByTagName("channel");
0064 
0065     for (int i = 0; i < nodes.count(); i++) {
0066         QDomNode elm = nodes.at(i);
0067         if (elm.isElement()) {
0068             const QDomNamedNodeMap &attributes = elm.attributes();
0069             const ChannelId id = ChannelId(attributes.namedItem("id").toAttr().value());
0070 
0071             const QString &name = elm.firstChildElement("display-name").text();
0072             const QString &icon = elm.firstChildElement("icon").attributes().namedItem("src").toAttr().value();
0073 
0074             fetchChannel(id, name, icon, channels);
0075         }
0076     }
0077 
0078     if (callback) {
0079         callback(channels.values());
0080     }
0081 }
0082 
0083 void XmltvFetcher::fetchProgram(const ChannelId &channelId,
0084                                 std::function<void(const QVector<ProgramData> &)> callback,
0085                                 std::function<void(const Error &)> errorCallback)
0086 {
0087     Q_UNUSED(errorCallback)
0088 
0089     QVector<ProgramData> programs;
0090 
0091     QDomNodeList programNodes = m_doc.elementsByTagName("programme");
0092     for (int i = 0; i < programNodes.count(); i++) {
0093         const QDomNode &program = programNodes.at(i);
0094         const QDomNamedNodeMap &attributes = program.attributes();
0095 
0096         if (channelId.value() != attributes.namedItem("channel").toAttr().value()) {
0097             continue; // TODO: do not loop all programs for each channel
0098         }
0099         programs.push_back(processProgram(program));
0100     }
0101 
0102     if (callback) {
0103         callback(programs);
0104     }
0105 }
0106 
0107 void XmltvFetcher::fetchProgramDescription(const ChannelId &channelId,
0108                                            const ProgramId &programId,
0109                                            const QString &url,
0110                                            std::function<void(const QString &)> callback,
0111                                            std::function<void(const Error &)> errorCallback)
0112 {
0113     Q_UNUSED(channelId)
0114     Q_UNUSED(programId)
0115     Q_UNUSED(url)
0116     Q_UNUSED(callback)
0117     Q_UNUSED(errorCallback)
0118 
0119     // nothing to be done (already fetched as part of the program)
0120 }
0121 
0122 QString XmltvFetcher::image(const QString &url, std::function<void()> callback, std::function<void(const Error &)> errorCallback)
0123 {
0124     Q_UNUSED(url);
0125     Q_UNUSED(callback)
0126     Q_UNUSED(errorCallback)
0127 
0128     return "";
0129 }
0130 
0131 QString XmltvFetcher::imagePath(const QString &url)
0132 {
0133     Q_UNUSED(url);
0134     return "";
0135 }
0136 
0137 void XmltvFetcher::open(QByteArray data)
0138 {
0139     QString errorMsg;
0140     int errorLine;
0141     int errorColumn;
0142     if (!m_doc.setContent(data, &errorMsg, &errorLine, &errorColumn)) {
0143         qCritical() << "Could not read XML:" << errorMsg << "(l" << errorLine << ":" << errorColumn << ")";
0144     }
0145 }
0146 
0147 void XmltvFetcher::fetchChannel(const ChannelId &channelId, const QString &name, const QString &icon, QMap<ChannelId, ChannelData> &channels)
0148 {
0149     if (!channels.contains(channelId)) {
0150         ChannelData data;
0151         data.m_id = channelId;
0152         data.m_name = name;
0153         data.m_url = "";
0154         data.m_image = icon;
0155 
0156         channels.insert(channelId, data);
0157     }
0158 }
0159 
0160 ProgramData XmltvFetcher::processProgram(const QDomNode &program)
0161 {
0162     ProgramData data;
0163 
0164     const QDomNamedNodeMap &attributes = program.attributes();
0165     data.m_channelId = ChannelId(attributes.namedItem("channel").toAttr().value());
0166     const QString &startTimeString = attributes.namedItem("start").toAttr().value();
0167     QDateTime startTime = QDateTime::fromString(startTimeString.left(14), "yyyyMMddHHmmss");
0168     const int startTimeOffset = QStringView{startTimeString}.right(5).left(3).toInt();
0169     startTime.setOffsetFromUtc(startTimeOffset * 3600);
0170     startTime = startTime.toUTC();
0171     data.m_startTime = startTime;
0172     // channel + start time can be used as ID
0173     data.m_id = ProgramId(data.m_channelId.value() + "_" + QString::number(startTime.toSecsSinceEpoch()));
0174     const QString &stopTimeString = attributes.namedItem("stop").toAttr().value();
0175     QDateTime stopTime = QDateTime::fromString(stopTimeString.left(14), "yyyyMMddHHmmss");
0176     const int stopTimeOffset = QStringView{stopTimeString}.right(5).left(3).toInt();
0177     stopTime.setOffsetFromUtc(stopTimeOffset * 3600);
0178     stopTime = stopTime.toUTC();
0179     data.m_stopTime = stopTime;
0180     data.m_title = program.namedItem("title").toElement().text();
0181     data.m_subtitle = program.namedItem("sub-title").toElement().text();
0182     data.m_description = program.namedItem("desc").toElement().text();
0183 
0184     if (program.isElement()) {
0185         QDomNodeList categoryNodes = program.toElement().elementsByTagName("category");
0186         for (int i = 0; i < categoryNodes.count(); i++) {
0187             data.m_categories.push_back(categoryNodes.at(i).toElement().text());
0188         }
0189     }
0190 
0191     data.m_descriptionFetched = true;
0192 
0193     return data;
0194 }