1 /**
2  * This file is part of TelepathyQt
3  *
4  * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
5  * @copyright Copyright (C) 2010 Nokia Corporation
6  * @license LGPL 2.1
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #include <TelepathyQt/ProfileManager>
24 
25 #include "TelepathyQt/_gen/profile-manager.moc.hpp"
26 #include "TelepathyQt/debug-internal.h"
27 
28 #include <TelepathyQt/ConnectionManager>
29 #include <TelepathyQt/PendingComposite>
30 #include <TelepathyQt/PendingReady>
31 #include <TelepathyQt/PendingStringList>
32 #include <TelepathyQt/Profile>
33 #include <TelepathyQt/ReadinessHelper>
34 
35 #include <QFile>
36 #include <QFileInfo>
37 #include <QString>
38 #include <QStringList>
39 
40 namespace Tp
41 {
42 
43 struct TP_QT_NO_EXPORT ProfileManager::Private
44 {
45     Private(ProfileManager *parent, const QDBusConnection &bus);
46 
47     static void introspectMain(Private *self);
48     static void introspectFakeProfiles(Private *self);
49 
50     ProfileManager *parent;
51     ReadinessHelper *readinessHelper;
52     QDBusConnection bus;
53     QHash<QString, ProfilePtr> profiles;
54     QList<ConnectionManagerPtr> cms;
55 };
56 
Private(ProfileManager * parent,const QDBusConnection & bus)57 ProfileManager::Private::Private(ProfileManager *parent, const QDBusConnection &bus)
58     : parent(parent),
59       readinessHelper(parent->readinessHelper()),
60       bus(bus)
61 {
62     ReadinessHelper::Introspectables introspectables;
63 
64     ReadinessHelper::Introspectable introspectableCore(
65         QSet<uint>() << 0,                                           // makesSenseForStatuses
66         Features(),                                                  // dependsOnFeatures
67         QStringList(),                                               // dependsOnInterfaces
68         (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
69         this);
70     introspectables[FeatureCore] = introspectableCore;
71 
72     ReadinessHelper::Introspectable introspectableFakeProfiles(
73         QSet<uint>() << 0,                                           // makesSenseForStatuses
74         Features() << FeatureCore,                                   // dependsOnFeatures
75         QStringList(),                                               // dependsOnInterfaces
76         (ReadinessHelper::IntrospectFunc) &Private::introspectFakeProfiles,
77         this);
78     introspectables[FeatureFakeProfiles] = introspectableFakeProfiles;
79 
80     readinessHelper->addIntrospectables(introspectables);
81 }
82 
introspectMain(ProfileManager::Private * self)83 void ProfileManager::Private::introspectMain(ProfileManager::Private *self)
84 {
85     QStringList searchDirs = Profile::searchDirs();
86 
87     foreach (const QString searchDir, searchDirs) {
88         QDir dir(searchDir);
89         dir.setFilter(QDir::Files);
90 
91         QFileInfoList list = dir.entryInfoList();
92         for (int i = 0; i < list.size(); ++i) {
93             QFileInfo fi = list.at(i);
94 
95             if (fi.completeSuffix() !=  QLatin1String("profile")) {
96                 continue;
97             }
98 
99             QString fileName = fi.absoluteFilePath();
100             QString serviceName = fi.baseName();
101 
102             if (self->profiles.contains(serviceName)) {
103                 debug() << "Profile for service" << serviceName << "already "
104                     "exists. Ignoring profile file:" << fileName;
105                 continue;
106             }
107 
108             ProfilePtr profile = Profile::createForFileName(fileName);
109             if (!profile->isValid()) {
110                 continue;
111             }
112 
113             if (profile->type() != QLatin1String("IM")) {
114                 debug() << "Ignoring profile for service" << serviceName <<
115                     ": type != IM. Profile file:" << fileName;
116                 continue;
117             }
118 
119             debug() << "Found profile for service" << serviceName <<
120                 "- profile file:" << fileName;
121             self->profiles.insert(serviceName, profile);
122         }
123     }
124 
125     self->readinessHelper->setIntrospectCompleted(FeatureCore, true);
126 }
127 
introspectFakeProfiles(ProfileManager::Private * self)128 void ProfileManager::Private::introspectFakeProfiles(ProfileManager::Private *self)
129 {
130     PendingStringList *pendingCmNames = ConnectionManager::listNames(self->bus);
131     self->parent->connect(pendingCmNames,
132             SIGNAL(finished(Tp::PendingOperation *)),
133             SLOT(onCmNamesRetrieved(Tp::PendingOperation *)));
134 }
135 
136 /**
137  * \class ProfileManager
138  * \headerfile TelepathyQt/profile-manager.h <TelepathyQt/ProfileManager>
139  *
140  * \brief The ProfileManager class provides helper methods to retrieve Profile
141  * objects.
142  */
143 
144 /**
145  * Feature representing the core that needs to become ready to make the ProfileManager
146  * object usable.
147  *
148  * Note that this feature must be enabled in order to use all ProfileManager methods.
149  *
150  * When calling isReady(), becomeReady(), this feature is implicitly added
151  * to the requested features.
152  */
153 const Feature ProfileManager::FeatureCore = Feature(QLatin1String(ProfileManager::staticMetaObject.className()), 0, true);
154 
155 /**
156  * Enabling this feature will make ProfileManager create fake Profile objects to all protocols
157  * supported on the installed connection managers, even if they don't have .profile files installed
158  * making use of them.
159  *
160  * Fake profiles are identified by Profile::isFake() returning \c true.
161  *
162  * The fake profile will contain the following info:
163  *  - Profile::type() will return "IM"
164  *  - Profile::provider() will return an empty string
165  *  - Profile::serviceName() will return cmName-protocolName
166  *  - Profile::name() and Profile::protocolName() will return protocolName
167  *  - Profile::iconName() will return "im-protocolName"
168  *  - Profile::cmName() will return cmName
169  *  - Profile::parameters() will return a list matching CM default parameters for protocol with name
170  *    protocolName.
171  *  - Profile::presences() will return an empty list and
172  *    Profile::allowOtherPresences() will return \c true, meaning that CM
173  *    presences should be used
174  *  - Profile::unsupportedChannelClassSpecs() will return an empty list
175  *
176  * Where cmName and protocolName are the name of the connection manager and the name of the protocol
177  * for which this fake Profile is created, respectively.
178  */
179 const Feature ProfileManager::FeatureFakeProfiles = Feature(QLatin1String(ProfileManager::staticMetaObject.className()), 1);
180 
181 /**
182  * Create a new ProfileManager object.
183  */
create(const QDBusConnection & bus)184 ProfileManagerPtr ProfileManager::create(const QDBusConnection &bus)
185 {
186     return ProfileManagerPtr(new ProfileManager(bus));
187 }
188 
189 /**
190  * Construct a new ProfileManager object.
191  */
ProfileManager(const QDBusConnection & bus)192 ProfileManager::ProfileManager(const QDBusConnection &bus)
193     : Object(),
194       ReadyObject(this, FeatureCore),
195       mPriv(new Private(this, bus))
196 {
197 }
198 
199 /**
200  * Class destructor.
201  */
~ProfileManager()202 ProfileManager::~ProfileManager()
203 {
204     delete mPriv;
205 }
206 
207 /**
208  * Return a list of all available profiles.
209  *
210  * \return A list of all available profiles.
211  */
profiles() const212 QList<ProfilePtr> ProfileManager::profiles() const
213 {
214     return mPriv->profiles.values();
215 }
216 
217 /**
218  * Return a list of all available profiles for a given connection manager.
219  *
220  * \param cmName Connection manager name.
221  * \return A list of all available profiles for a given connection manager.
222  */
profilesForCM(const QString & cmName) const223 QList<ProfilePtr> ProfileManager::profilesForCM(const QString &cmName) const
224 {
225     QList<ProfilePtr> ret;
226     foreach (const ProfilePtr &profile, mPriv->profiles) {
227         if (profile->cmName() == cmName) {
228             ret << profile;
229         }
230     }
231     return ret;
232 }
233 
234 /**
235  * Return a list of all available profiles for a given \a protocol.
236  *
237  * \param protocolName Protocol name.
238  * \return A list of all available profiles for a given \a protocol.
239  */
profilesForProtocol(const QString & protocolName) const240 QList<ProfilePtr> ProfileManager::profilesForProtocol(
241         const QString &protocolName) const
242 {
243     QList<ProfilePtr> ret;
244     foreach (const ProfilePtr &profile, mPriv->profiles) {
245         if (profile->protocolName() == protocolName) {
246             ret << profile;
247         }
248     }
249     return ret;
250 }
251 
252 /**
253  * Return the profile for a given \a service.
254  *
255  * \param serviceName Service name.
256  * \return The profile for \a service.
257  */
profileForService(const QString & serviceName) const258 ProfilePtr ProfileManager::profileForService(const QString &serviceName) const
259 {
260     return mPriv->profiles.value(serviceName);
261 }
262 
onCmNamesRetrieved(Tp::PendingOperation * op)263 void ProfileManager::onCmNamesRetrieved(Tp::PendingOperation *op)
264 {
265     if (op->isError()) {
266         warning().nospace() << "Getting available CMs failed with " <<
267             op->errorName() << ":" << op->errorMessage();
268         mPriv->readinessHelper->setIntrospectCompleted(FeatureFakeProfiles, false,
269                 op->errorName(), op->errorMessage());
270         return;
271     }
272 
273     PendingStringList *pendingCmNames = qobject_cast<PendingStringList *>(op);
274     QStringList cmNames(pendingCmNames->result());
275     if (cmNames.isEmpty()) {
276         mPriv->readinessHelper->setIntrospectCompleted(FeatureFakeProfiles, true);
277         return;
278     }
279 
280     QList<PendingOperation *> ops;
281     foreach (const QString &cmName, cmNames) {
282         ConnectionManagerPtr cm = ConnectionManager::create(mPriv->bus, cmName);
283         mPriv->cms.append(cm);
284         ops.append(cm->becomeReady());
285     }
286 
287     PendingComposite *pc = new PendingComposite(ops, false, ProfileManagerPtr(this));
288     connect(pc,
289             SIGNAL(finished(Tp::PendingOperation *)),
290             SLOT(onCMsReady(Tp::PendingOperation *)));
291 }
292 
onCMsReady(Tp::PendingOperation * op)293 void ProfileManager::onCMsReady(Tp::PendingOperation *op)
294 {
295     if (op->isError()) {
296         warning() << "Failed introspecting all CMs, trying to create fake profiles anyway";
297     }
298 
299     ProfilePtr profile;
300     foreach (const ConnectionManagerPtr &cm, mPriv->cms) {
301         if (!cm->isReady()) {
302             continue;
303         }
304 
305         foreach (const QString &protocolName, cm->supportedProtocols()) {
306             /* check if there is a profile whose service name is protocolName, and if found,
307              * check if the profile is for cm, if not check if there is a profile whose service
308              * name is cm-protocolName, and if not found create one named cm-protocolName. */
309             profile = profileForService(protocolName);
310             if (profile && profile->cmName() == cm->name()) {
311                 continue;
312             }
313 
314             QString serviceName = QString(QLatin1String("%1-%2")).arg(cm->name()).arg(protocolName);
315             profile = profileForService(serviceName);
316             if (profile) {
317                 continue;
318             }
319 
320             profile = ProfilePtr(new Profile(
321                         serviceName,
322                         cm->name(),
323                         protocolName,
324                         cm->protocol(protocolName)));
325             mPriv->profiles.insert(serviceName, profile);
326         }
327     }
328 
329     mPriv->readinessHelper->setIntrospectCompleted(FeatureFakeProfiles, true);
330 }
331 
332 } // Tp
333