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