1 /*
2 * Copyright (C) 2012 David Edmundson <kde@davidedmundson.co.uk>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18 
19 #include "contact.h"
20 
21 #include <TelepathyQt/ContactManager>
22 #include <TelepathyQt/Connection>
23 #include <TelepathyQt/ContactCapabilities>
24 #include <TelepathyQt/AvatarData>
25 #include <TelepathyQt/Utils>
26 
27 #include <QBitmap>
28 #include <QPixmap>
29 #include <QPixmapCache>
30 
31 #include <KIconLoader>
32 #include <KConfigGroup>
33 #include <KConfig>
34 
35 #include "capabilities-hack-private.h"
36 
Contact(Tp::ContactManager * manager,const Tp::ReferencedHandles & handle,const Tp::Features & requestedFeatures,const QVariantMap & attributes)37 KTp::Contact::Contact(Tp::ContactManager *manager, const Tp::ReferencedHandles &handle, const Tp::Features &requestedFeatures, const QVariantMap &attributes)
38     : Tp::Contact(manager, handle, requestedFeatures, attributes)
39 {
40     connect(manager->connection().data(), SIGNAL(destroyed()), SIGNAL(invalidated()));
41     connect(manager->connection().data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), SIGNAL(invalidated()));
42     connect(this, SIGNAL(avatarTokenChanged(QString)), SLOT(invalidateAvatarCache()));
43     connect(this, SIGNAL(avatarDataChanged(Tp::AvatarData)), SLOT(invalidateAvatarCache()));
44     connect(this, SIGNAL(presenceChanged(Tp::Presence)), SLOT(onPresenceChanged(Tp::Presence)));
45 }
46 
onPresenceChanged(const Tp::Presence & presence)47 void KTp::Contact::onPresenceChanged(const Tp::Presence &presence)
48 {
49     Q_UNUSED(presence)
50     /* Temporary workaround for upstream bug https://bugs.freedesktop.org/show_bug.cgi?id=55883)
51      * Close https://bugs.kde.org/show_bug.cgi?id=308217 when fixed upstream */
52     Q_EMIT clientTypesChanged(clientTypes());
53 }
54 
accountUniqueIdentifier() const55 QString KTp::Contact::accountUniqueIdentifier() const
56 {
57     if (m_accountUniqueIdentifier.isEmpty() && manager()->connection()) {
58         const_cast<KTp::Contact*>(this)->m_accountUniqueIdentifier = manager()->connection()->property("accountUID").toString();
59     }
60     return m_accountUniqueIdentifier;
61 }
62 
uri() const63 QString KTp::Contact::uri() const
64 {
65     // so real ID will look like
66     // ktp://gabble/jabber/blah/asdfjwer?foo@bar.com
67     // ? is used as it is not a valid character in the dbus path that makes up the account UID
68     return QStringLiteral("ktp://") + accountUniqueIdentifier() + QLatin1Char('?') + id();
69 }
70 
presence() const71 KTp::Presence KTp::Contact::presence() const
72 {
73     if (!manager() || !manager()->connection()) {
74         return Tp::Presence::offline();
75     }
76 
77     return KTp::Presence(Tp::Contact::presence());
78 }
79 
textChatCapability() const80 bool KTp::Contact::textChatCapability() const
81 {
82     if (!manager() || !manager()->connection()) {
83         return false;
84     }
85 
86     return capabilities().textChats();
87 }
88 
audioCallCapability() const89 bool KTp::Contact::audioCallCapability() const
90 {
91     if (!manager() || !manager()->connection()) {
92         return false;
93     }
94 
95     Tp::ConnectionPtr connection = manager()->connection();
96     bool contactCanStreamAudio = CapabilitiesHackPrivate::audioCalls(
97                 capabilities(), connection->cmName());
98     bool selfCanStreamAudio = CapabilitiesHackPrivate::audioCalls(
99                 connection->selfContact()->capabilities(), connection->cmName());
100     return contactCanStreamAudio && selfCanStreamAudio;
101 }
102 
videoCallCapability() const103 bool KTp::Contact::videoCallCapability() const
104 {
105     if (!manager() || !manager()->connection()) {
106         return false;
107     }
108 
109     Tp::ConnectionPtr connection = manager()->connection();
110     bool contactCanStreamVideo = CapabilitiesHackPrivate::videoCalls(
111                 capabilities(), connection->cmName());
112     bool selfCanStreamVideo = CapabilitiesHackPrivate::videoCalls(
113                 connection->selfContact()->capabilities(), connection->cmName());
114     return contactCanStreamVideo && selfCanStreamVideo;
115 }
116 
fileTransferCapability() const117 bool KTp::Contact::fileTransferCapability()  const
118 {
119     if (!manager() || !manager()->connection()) {
120         return false;
121     }
122 
123     bool contactCanHandleFiles = capabilities().fileTransfers();
124     bool selfCanHandleFiles = manager()->connection()->selfContact()->capabilities().fileTransfers();
125     return contactCanHandleFiles && selfCanHandleFiles;
126 }
127 
collaborativeEditingCapability() const128 bool KTp::Contact::collaborativeEditingCapability() const
129 {
130     if (!manager() || !manager()->connection()) {
131         return false;
132     }
133 
134     static const QString collab(QLatin1String("infinote"));
135     bool selfCanShare = manager()->connection()->selfContact()->capabilities().streamTubes(collab);
136     bool otherCanShare = capabilities().streamTubes(collab);
137     return selfCanShare && otherCanShare;
138 }
139 
dbusTubeServicesCapability() const140 QStringList KTp::Contact::dbusTubeServicesCapability() const
141 {
142     if (!manager() || !manager()->connection()) {
143         return QStringList();
144     }
145 
146     return getCommonElements(capabilities().dbusTubeServices(),
147                              manager()->connection()->selfContact()->capabilities().dbusTubeServices());
148 }
149 
streamTubeServicesCapability() const150 QStringList KTp::Contact::streamTubeServicesCapability() const
151 {
152     if (!manager() || !manager()->connection()) {
153         return QStringList();
154     }
155 
156     return getCommonElements(capabilities().streamTubeServices(),
157                              manager()->connection()->selfContact()->capabilities().streamTubeServices());
158 }
159 
clientTypes() const160 QStringList KTp::Contact::clientTypes() const
161 {
162     /* Temporary workaround for upstream bug https://bugs.freedesktop.org/show_bug.cgi?id=55883)
163      * Close https://bugs.kde.org/show_bug.cgi?id=308217 when fixed upstream */
164     if (Tp::Contact::presence().type() == Tp::ConnectionPresenceTypeOffline) {
165         return QStringList();
166     }
167 
168     //supress any errors trying to access ClientTypes when we don't have them
169     if (! actualFeatures().contains(Tp::Contact::FeatureClientTypes)) {
170         return QStringList();
171     }
172 
173     return Tp::Contact::clientTypes();
174 }
175 
avatarPixmap()176 QPixmap KTp::Contact::avatarPixmap()
177 {
178     QPixmap avatar;
179 
180     //check pixmap cache for the avatar, if not present, load the avatar
181     if (!QPixmapCache::find(keyCache(), avatar)){
182         QString file = avatarData().fileName;
183 
184         //if contact does not provide path, let's see if we have avatar for the stored token
185         if (file.isEmpty()) {
186             KConfig config(QLatin1String("ktelepathy-avatarsrc"));
187             KConfigGroup avatarTokenGroup = config.group(id());
188             QString avatarToken = avatarTokenGroup.readEntry(QLatin1String("avatarToken"));
189             //only bother loading the pixmap if the token is not empty
190             if (!avatarToken.isEmpty()) {
191                 avatar.load(buildAvatarPath(avatarToken));
192             }
193         } else {
194             avatar.load(file);
195         }
196 
197         //if neither above succeeded, return empty QPixmap,
198         //PersonsModel will return the default icon instead
199         if (avatar.isNull()) {
200             return QPixmap();
201         }
202 
203         //insert the contact into pixmap cache for faster lookup
204         QPixmapCache::insert(keyCache(), avatar);
205     }
206 
207     return avatar;
208 }
209 
avatarToGray(QPixmap & avatar)210 void KTp::Contact::avatarToGray(QPixmap &avatar)
211 {
212     QImage image = avatar.toImage();
213     QImage alpha= image.alphaChannel();
214     for (int i = 0; i < image.width(); ++i) {
215         for (int j = 0; j < image.height(); ++j) {
216             int colour = qGray(image.pixel(i, j));
217             image.setPixel(i, j, qRgb(colour, colour, colour));
218         }
219     }
220     image.setAlphaChannel(alpha);
221     avatar = QPixmap::fromImage(image);
222 }
223 
keyCache() const224 QString KTp::Contact::keyCache() const
225 {
226     return id() + (presence().type() == Tp::ConnectionPresenceTypeOffline ? QLatin1String("-offline") : QLatin1String("-online"));
227 }
228 
buildAvatarPath(const QString & avatarToken)229 QString KTp::Contact::buildAvatarPath(const QString &avatarToken)
230 {
231     QString cacheDir = QString::fromLatin1(qgetenv("XDG_CACHE_HOME"));
232     if (cacheDir.isEmpty()) {
233         cacheDir = QStringLiteral("%1/.cache").arg(QLatin1String(qgetenv("HOME")));
234     }
235 
236     if (manager().isNull()) {
237         return QString();
238     }
239 
240     if (manager()->connection().isNull()) {
241         return QString();
242     }
243 
244     Tp::ConnectionPtr conn = manager()->connection();
245     QString path = QStringLiteral("%1/telepathy/avatars/%2/%3").
246         arg(cacheDir).arg(conn->cmName()).arg(conn->protocolName());
247 
248     QString avatarFileName = QStringLiteral("%1/%2").arg(path).arg(Tp::escapeAsIdentifier(avatarToken));
249 
250     return avatarFileName;
251 }
252 
invalidateAvatarCache()253 void KTp::Contact::invalidateAvatarCache()
254 {
255     QPixmapCache::remove(id() + QLatin1String("-offline"));
256     QPixmapCache::remove(id() + QLatin1String("-online"));
257 }
258 
getCommonElements(const QStringList & list1,const QStringList & list2)259 QStringList KTp::Contact::getCommonElements(const QStringList &list1, const QStringList &list2)
260 {
261     /* QStringList::contains(QString) perform iterative comparsion, so there is no reason
262      * to select smaller list as base for this cycle. */
263     QStringList commonElements;
264     Q_FOREACH(const QString &i, list1) {
265         if (list2.contains(i)) {
266             commonElements << i;
267         }
268     }
269     return commonElements;
270 }
271