File indexing completed on 2023-12-10 04:56:57

0001 /*
0002 * Copyright (C) 2012 David Edmundson <kde@davidedmundson.co.uk>
0003 *
0004 * This library is free software; you can redistribute it and/or
0005 * modify it under the terms of the GNU Lesser General Public
0006 * License as published by the Free Software Foundation; either
0007 * version 2.1 of the License, or (at your option) any later version.
0008 *
0009 * This library is distributed in the hope that it will be useful,
0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012 * Lesser General Public License for more details.
0013 *
0014 * You should have received a copy of the GNU Lesser General Public
0015 * License along with this library; if not, write to the Free Software
0016 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
0017 */
0018 
0019 #include "contact.h"
0020 
0021 #include <TelepathyQt/ContactManager>
0022 #include <TelepathyQt/Connection>
0023 #include <TelepathyQt/ContactCapabilities>
0024 #include <TelepathyQt/AvatarData>
0025 #include <TelepathyQt/Utils>
0026 
0027 #include <QBitmap>
0028 #include <QPixmap>
0029 #include <QPixmapCache>
0030 
0031 #include <KIconLoader>
0032 #include <KConfigGroup>
0033 #include <KConfig>
0034 
0035 #include "capabilities-hack-private.h"
0036 
0037 KTp::Contact::Contact(Tp::ContactManager *manager, const Tp::ReferencedHandles &handle, const Tp::Features &requestedFeatures, const QVariantMap &attributes)
0038     : Tp::Contact(manager, handle, requestedFeatures, attributes)
0039 {
0040     connect(manager->connection().data(), SIGNAL(destroyed()), SIGNAL(invalidated()));
0041     connect(manager->connection().data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), SIGNAL(invalidated()));
0042     connect(this, SIGNAL(avatarTokenChanged(QString)), SLOT(invalidateAvatarCache()));
0043     connect(this, SIGNAL(avatarDataChanged(Tp::AvatarData)), SLOT(invalidateAvatarCache()));
0044     connect(this, SIGNAL(presenceChanged(Tp::Presence)), SLOT(onPresenceChanged(Tp::Presence)));
0045 }
0046 
0047 void KTp::Contact::onPresenceChanged(const Tp::Presence &presence)
0048 {
0049     Q_UNUSED(presence)
0050     /* Temporary workaround for upstream bug https://bugs.freedesktop.org/show_bug.cgi?id=55883)
0051      * Close https://bugs.kde.org/show_bug.cgi?id=308217 when fixed upstream */
0052     Q_EMIT clientTypesChanged(clientTypes());
0053 }
0054 
0055 QString KTp::Contact::accountUniqueIdentifier() const
0056 {
0057     if (m_accountUniqueIdentifier.isEmpty() && manager()->connection()) {
0058         const_cast<KTp::Contact*>(this)->m_accountUniqueIdentifier = manager()->connection()->property("accountUID").toString();
0059     }
0060     return m_accountUniqueIdentifier;
0061 }
0062 
0063 QString KTp::Contact::uri() const
0064 {
0065     // so real ID will look like
0066     // ktp://gabble/jabber/blah/asdfjwer?foo@bar.com
0067     // ? is used as it is not a valid character in the dbus path that makes up the account UID
0068     return QStringLiteral("ktp://") + accountUniqueIdentifier() + QLatin1Char('?') + id();
0069 }
0070 
0071 KTp::Presence KTp::Contact::presence() const
0072 {
0073     if (!manager() || !manager()->connection()) {
0074         return Tp::Presence::offline();
0075     }
0076 
0077     return KTp::Presence(Tp::Contact::presence());
0078 }
0079 
0080 bool KTp::Contact::textChatCapability() const
0081 {
0082     if (!manager() || !manager()->connection()) {
0083         return false;
0084     }
0085 
0086     return capabilities().textChats();
0087 }
0088 
0089 bool KTp::Contact::audioCallCapability() const
0090 {
0091     if (!manager() || !manager()->connection()) {
0092         return false;
0093     }
0094 
0095     Tp::ConnectionPtr connection = manager()->connection();
0096     bool contactCanStreamAudio = CapabilitiesHackPrivate::audioCalls(
0097                 capabilities(), connection->cmName());
0098     bool selfCanStreamAudio = CapabilitiesHackPrivate::audioCalls(
0099                 connection->selfContact()->capabilities(), connection->cmName());
0100     return contactCanStreamAudio && selfCanStreamAudio;
0101 }
0102 
0103 bool KTp::Contact::videoCallCapability() const
0104 {
0105     if (!manager() || !manager()->connection()) {
0106         return false;
0107     }
0108 
0109     Tp::ConnectionPtr connection = manager()->connection();
0110     bool contactCanStreamVideo = CapabilitiesHackPrivate::videoCalls(
0111                 capabilities(), connection->cmName());
0112     bool selfCanStreamVideo = CapabilitiesHackPrivate::videoCalls(
0113                 connection->selfContact()->capabilities(), connection->cmName());
0114     return contactCanStreamVideo && selfCanStreamVideo;
0115 }
0116 
0117 bool KTp::Contact::fileTransferCapability()  const
0118 {
0119     if (!manager() || !manager()->connection()) {
0120         return false;
0121     }
0122 
0123     bool contactCanHandleFiles = capabilities().fileTransfers();
0124     bool selfCanHandleFiles = manager()->connection()->selfContact()->capabilities().fileTransfers();
0125     return contactCanHandleFiles && selfCanHandleFiles;
0126 }
0127 
0128 bool KTp::Contact::collaborativeEditingCapability() const
0129 {
0130     if (!manager() || !manager()->connection()) {
0131         return false;
0132     }
0133 
0134     static const QString collab(QLatin1String("infinote"));
0135     bool selfCanShare = manager()->connection()->selfContact()->capabilities().streamTubes(collab);
0136     bool otherCanShare = capabilities().streamTubes(collab);
0137     return selfCanShare && otherCanShare;
0138 }
0139 
0140 QStringList KTp::Contact::dbusTubeServicesCapability() const
0141 {
0142     if (!manager() || !manager()->connection()) {
0143         return QStringList();
0144     }
0145 
0146     return getCommonElements(capabilities().dbusTubeServices(),
0147                              manager()->connection()->selfContact()->capabilities().dbusTubeServices());
0148 }
0149 
0150 QStringList KTp::Contact::streamTubeServicesCapability() const
0151 {
0152     if (!manager() || !manager()->connection()) {
0153         return QStringList();
0154     }
0155 
0156     return getCommonElements(capabilities().streamTubeServices(),
0157                              manager()->connection()->selfContact()->capabilities().streamTubeServices());
0158 }
0159 
0160 QStringList KTp::Contact::clientTypes() const
0161 {
0162     /* Temporary workaround for upstream bug https://bugs.freedesktop.org/show_bug.cgi?id=55883)
0163      * Close https://bugs.kde.org/show_bug.cgi?id=308217 when fixed upstream */
0164     if (Tp::Contact::presence().type() == Tp::ConnectionPresenceTypeOffline) {
0165         return QStringList();
0166     }
0167 
0168     //supress any errors trying to access ClientTypes when we don't have them
0169     if (! actualFeatures().contains(Tp::Contact::FeatureClientTypes)) {
0170         return QStringList();
0171     }
0172 
0173     return Tp::Contact::clientTypes();
0174 }
0175 
0176 QPixmap KTp::Contact::avatarPixmap()
0177 {
0178     QPixmap avatar;
0179 
0180     //check pixmap cache for the avatar, if not present, load the avatar
0181     if (!QPixmapCache::find(keyCache(), avatar)){
0182         QString file = avatarData().fileName;
0183 
0184         //if contact does not provide path, let's see if we have avatar for the stored token
0185         if (file.isEmpty()) {
0186             KConfig config(QLatin1String("ktelepathy-avatarsrc"));
0187             KConfigGroup avatarTokenGroup = config.group(id());
0188             QString avatarToken = avatarTokenGroup.readEntry(QLatin1String("avatarToken"));
0189             //only bother loading the pixmap if the token is not empty
0190             if (!avatarToken.isEmpty()) {
0191                 avatar.load(buildAvatarPath(avatarToken));
0192             }
0193         } else {
0194             avatar.load(file);
0195         }
0196 
0197         //if neither above succeeded, return empty QPixmap,
0198         //PersonsModel will return the default icon instead
0199         if (avatar.isNull()) {
0200             return QPixmap();
0201         }
0202 
0203         //insert the contact into pixmap cache for faster lookup
0204         QPixmapCache::insert(keyCache(), avatar);
0205     }
0206 
0207     return avatar;
0208 }
0209 
0210 void KTp::Contact::avatarToGray(QPixmap &avatar)
0211 {
0212     QImage image = avatar.toImage();
0213     QImage alpha= image.alphaChannel();
0214     for (int i = 0; i < image.width(); ++i) {
0215         for (int j = 0; j < image.height(); ++j) {
0216             int colour = qGray(image.pixel(i, j));
0217             image.setPixel(i, j, qRgb(colour, colour, colour));
0218         }
0219     }
0220     image.setAlphaChannel(alpha);
0221     avatar = QPixmap::fromImage(image);
0222 }
0223 
0224 QString KTp::Contact::keyCache() const
0225 {
0226     return id() + (presence().type() == Tp::ConnectionPresenceTypeOffline ? QLatin1String("-offline") : QLatin1String("-online"));
0227 }
0228 
0229 QString KTp::Contact::buildAvatarPath(const QString &avatarToken)
0230 {
0231     QString cacheDir = QString::fromLatin1(qgetenv("XDG_CACHE_HOME"));
0232     if (cacheDir.isEmpty()) {
0233         cacheDir = QStringLiteral("%1/.cache").arg(QLatin1String(qgetenv("HOME")));
0234     }
0235 
0236     if (manager().isNull()) {
0237         return QString();
0238     }
0239 
0240     if (manager()->connection().isNull()) {
0241         return QString();
0242     }
0243 
0244     Tp::ConnectionPtr conn = manager()->connection();
0245     QString path = QStringLiteral("%1/telepathy/avatars/%2/%3").
0246         arg(cacheDir).arg(conn->cmName()).arg(conn->protocolName());
0247 
0248     QString avatarFileName = QStringLiteral("%1/%2").arg(path).arg(Tp::escapeAsIdentifier(avatarToken));
0249 
0250     return avatarFileName;
0251 }
0252 
0253 void KTp::Contact::invalidateAvatarCache()
0254 {
0255     QPixmapCache::remove(id() + QLatin1String("-offline"));
0256     QPixmapCache::remove(id() + QLatin1String("-online"));
0257 }
0258 
0259 QStringList KTp::Contact::getCommonElements(const QStringList &list1, const QStringList &list2)
0260 {
0261     /* QStringList::contains(QString) perform iterative comparsion, so there is no reason
0262      * to select smaller list as base for this cycle. */
0263     QStringList commonElements;
0264     Q_FOREACH(const QString &i, list1) {
0265         if (list2.contains(i)) {
0266             commonElements << i;
0267         }
0268     }
0269     return commonElements;
0270 }