File indexing completed on 2024-12-01 04:33:08
0001 /** 0002 * SPDX-FileCopyrightText: 2013 Albert Vaca <albertvaka@gmail.com> 0003 * SPDX-FileCopyrightText: 2018 Simon Redman <simon@ergotech.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "smsplugin.h" 0009 0010 #include <KLocalizedString> 0011 #include <KPluginFactory> 0012 0013 #include <QDBusConnection> 0014 #include <QDebug> 0015 #include <QFile> 0016 #include <QFileInfo> 0017 #include <QMimeDatabase> 0018 #include <QProcess> 0019 #include <QTextCodec> 0020 0021 #include <core/daemon.h> 0022 #include <core/device.h> 0023 #include <core/filetransferjob.h> 0024 0025 #include "plugin_sms_debug.h" 0026 0027 K_PLUGIN_CLASS_WITH_JSON(SmsPlugin, "kdeconnect_sms.json") 0028 0029 SmsPlugin::SmsPlugin(QObject *parent, const QVariantList &args) 0030 : KdeConnectPlugin(parent, args) 0031 , m_telepathyInterface(QStringLiteral("org.freedesktop.Telepathy.ConnectionManager.kdeconnect"), QStringLiteral("/kdeconnect")) 0032 , m_conversationInterface(new ConversationsDbusInterface(this)) 0033 { 0034 m_codec = QTextCodec::codecForName(CODEC_NAME); 0035 } 0036 0037 SmsPlugin::~SmsPlugin() 0038 { 0039 // m_conversationInterface is self-deleting, see ~ConversationsDbusInterface for more information 0040 } 0041 0042 void SmsPlugin::receivePacket(const NetworkPacket &np) 0043 { 0044 if (np.type() == PACKET_TYPE_SMS_MESSAGES) { 0045 handleBatchMessages(np); 0046 } 0047 0048 if (np.type() == PACKET_TYPE_SMS_ATTACHMENT_FILE && np.hasPayload()) { 0049 handleSmsAttachmentFile(np); 0050 } 0051 } 0052 0053 void SmsPlugin::sendSms(const QVariantList &addresses, const QString &textMessage, const QVariantList &attachmentUrls, const qint64 subID) 0054 { 0055 QVariantList addressMapList; 0056 for (const QVariant &address : addresses) { 0057 QVariantMap addressMap({{QStringLiteral("address"), qdbus_cast<ConversationAddress>(address).address()}}); 0058 addressMapList.append(addressMap); 0059 } 0060 0061 QVariantMap packetMap({{QStringLiteral("version"), SMS_REQUEST_PACKET_VERSION}, {QStringLiteral("addresses"), addressMapList}}); 0062 0063 // If there is any text message add it to the network packet 0064 if (textMessage != QStringLiteral("")) { 0065 packetMap[QStringLiteral("messageBody")] = textMessage; 0066 } 0067 0068 if (subID != -1) { 0069 packetMap[QStringLiteral("subID")] = subID; 0070 } 0071 0072 QVariantList attachmentMapList; 0073 for (const QVariant &attachmentUrl : attachmentUrls) { 0074 const Attachment attachment = createAttachmentFromUrl(attachmentUrl.toString()); 0075 QVariantMap attachmentMap({{QStringLiteral("fileName"), attachment.uniqueIdentifier()}, 0076 {QStringLiteral("base64EncodedFile"), attachment.base64EncodedFile()}, 0077 {QStringLiteral("mimeType"), attachment.mimeType()}}); 0078 attachmentMapList.append(attachmentMap); 0079 } 0080 0081 // If there is any attachment add it to the network packet 0082 if (!attachmentMapList.isEmpty()) { 0083 packetMap[QStringLiteral("attachments")] = attachmentMapList; 0084 } 0085 0086 NetworkPacket np(PACKET_TYPE_SMS_REQUEST, packetMap); 0087 qCDebug(KDECONNECT_PLUGIN_SMS) << "Dispatching SMS send request to remote"; 0088 sendPacket(np); 0089 } 0090 0091 void SmsPlugin::requestAllConversations() 0092 { 0093 NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATIONS); 0094 0095 sendPacket(np); 0096 } 0097 0098 void SmsPlugin::requestConversation(const qint64 conversationID, const qint64 rangeStartTimestamp, const qint64 numberToRequest) const 0099 { 0100 NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATION); 0101 np.set(QStringLiteral("threadID"), conversationID); 0102 np.set(QStringLiteral("rangeStartTimestamp"), rangeStartTimestamp); 0103 np.set(QStringLiteral("numberToRequest"), numberToRequest); 0104 0105 sendPacket(np); 0106 } 0107 0108 void SmsPlugin::requestAttachment(const qint64 &partID, const QString &uniqueIdentifier) 0109 { 0110 const QVariantMap packetMap({{QStringLiteral("part_id"), partID}, {QStringLiteral("unique_identifier"), uniqueIdentifier}}); 0111 0112 NetworkPacket np(PACKET_TYPE_SMS_REQUEST_ATTACHMENT, packetMap); 0113 0114 sendPacket(np); 0115 } 0116 0117 void SmsPlugin::forwardToTelepathy(const ConversationMessage &message) 0118 { 0119 // If we don't have a valid Telepathy interface, bail out 0120 if (!(m_telepathyInterface.isValid())) 0121 return; 0122 0123 qCDebug(KDECONNECT_PLUGIN_SMS) << "Passing a text message to the telepathy interface"; 0124 connect(&m_telepathyInterface, SIGNAL(messageReceived(QString, QString)), this, SLOT(sendSms(QString, QString)), Qt::UniqueConnection); 0125 const QString messageBody = message.body(); 0126 const QString contactName; // TODO: When telepathy support is improved, look up the contact with KPeople 0127 const QString phoneNumber = message.addresses()[0].address(); 0128 m_telepathyInterface.call(QDBus::NoBlock, QStringLiteral("sendMessage"), phoneNumber, contactName, messageBody); 0129 } 0130 0131 bool SmsPlugin::handleBatchMessages(const NetworkPacket &np) 0132 { 0133 const auto messages = np.get<QVariantList>(QStringLiteral("messages")); 0134 QList<ConversationMessage> messagesList; 0135 messagesList.reserve(messages.count()); 0136 0137 for (const QVariant &body : messages) { 0138 ConversationMessage message(body.toMap()); 0139 if (message.containsTextBody()) { 0140 forwardToTelepathy(message); 0141 } 0142 messagesList.append(message); 0143 } 0144 0145 m_conversationInterface->addMessages(messagesList); 0146 0147 return true; 0148 } 0149 0150 bool SmsPlugin::handleSmsAttachmentFile(const NetworkPacket &np) 0151 { 0152 const QString fileName = np.get<QString>(QStringLiteral("filename")); 0153 0154 QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); 0155 cacheDir.append(QStringLiteral("/") + device()->name() + QStringLiteral("/")); 0156 QDir attachmentsCacheDir(cacheDir); 0157 0158 if (!attachmentsCacheDir.exists()) { 0159 qDebug() << attachmentsCacheDir.absolutePath() << " directory doesn't exist."; 0160 return false; 0161 } 0162 0163 QUrl fileUrl = QUrl::fromLocalFile(attachmentsCacheDir.absolutePath()); 0164 fileUrl = fileUrl.adjusted(QUrl::StripTrailingSlash); 0165 fileUrl.setPath(fileUrl.path() + QStringLiteral("/") + fileName, QUrl::DecodedMode); 0166 0167 FileTransferJob *job = np.createPayloadTransferJob(fileUrl); 0168 connect(job, &FileTransferJob::result, this, [this, fileName](KJob *job) -> void { 0169 FileTransferJob *ftjob = qobject_cast<FileTransferJob *>(job); 0170 if (ftjob && !job->error()) { 0171 // Notify SMS app about the newly downloaded attachment 0172 m_conversationInterface->attachmentDownloaded(ftjob->destination().path(), fileName); 0173 } else { 0174 qCDebug(KDECONNECT_PLUGIN_SMS) << ftjob->errorString() << (ftjob ? ftjob->destination() : QUrl()); 0175 } 0176 }); 0177 job->start(); 0178 0179 return true; 0180 } 0181 0182 void SmsPlugin::getAttachment(const qint64 &partID, const QString &uniqueIdentifier) 0183 { 0184 QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); 0185 cacheDir.append(QStringLiteral("/") + device()->name() + QStringLiteral("/")); 0186 QDir fileDirectory(cacheDir); 0187 0188 bool fileFound = false; 0189 if (fileDirectory.exists()) { 0190 // Search for the attachment file locally before sending request to remote device 0191 fileFound = fileDirectory.exists(uniqueIdentifier); 0192 } else { 0193 bool ret = fileDirectory.mkpath(QStringLiteral(".")); 0194 if (!ret) { 0195 qWarning() << "couldn't create directorty " << fileDirectory.absolutePath(); 0196 } 0197 } 0198 0199 if (!fileFound) { 0200 // If the file is not present in the local dir request the remote device for the file 0201 requestAttachment(partID, uniqueIdentifier); 0202 } else { 0203 const QString fileDestination = fileDirectory.absoluteFilePath(uniqueIdentifier); 0204 m_conversationInterface->attachmentDownloaded(fileDestination, uniqueIdentifier); 0205 } 0206 } 0207 0208 Attachment SmsPlugin::createAttachmentFromUrl(const QString &url) 0209 { 0210 QFile file(url); 0211 file.open(QIODevice::ReadOnly); 0212 0213 if (!file.exists()) { 0214 return Attachment(); 0215 } 0216 0217 QFileInfo fileInfo(file); 0218 QString fileName(fileInfo.fileName()); 0219 0220 QByteArray byteArray = file.readAll().toBase64(); 0221 file.close(); 0222 0223 QString base64EncodedFile = m_codec->toUnicode(byteArray); 0224 0225 QMimeDatabase mimeDatabase; 0226 QString mimeType = mimeDatabase.mimeTypeForFile(url).name(); 0227 0228 Attachment attachment(-1, mimeType, base64EncodedFile, fileName); 0229 return attachment; 0230 } 0231 0232 QString SmsPlugin::dbusPath() const 0233 { 0234 return QLatin1String("/modules/kdeconnect/devices/%1/sms").arg(device()->id()); 0235 } 0236 0237 void SmsPlugin::launchApp() 0238 { 0239 QProcess::startDetached(QLatin1String("kdeconnect-sms"), {QStringLiteral("--device"), device()->id()}); 0240 } 0241 0242 #include "moc_smsplugin.cpp" 0243 #include "smsplugin.moc"