1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
|
/******************************************************************************
* Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include "jobs/generated/create_room.h"
#include "joinstate.h"
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <QtCore/QSize>
#include <functional>
#include <memory>
namespace QMatrixClient
{
class Room;
class User;
class RoomEvent;
class ConnectionData;
class SyncJob;
class SyncData;
class RoomMessagesJob;
class PostReceiptJob;
class ForgetRoomJob;
class MediaThumbnailJob;
class JoinRoomJob;
class UploadContentJob;
class GetContentJob;
class DownloadFileJob;
class Connection: public QObject {
Q_OBJECT
/** Whether or not the rooms state should be cached locally
* \sa loadState(), saveState()
*/
Q_PROPERTY(User* localUser READ user CONSTANT)
Q_PROPERTY(QString localUserId READ userId CONSTANT)
Q_PROPERTY(QString deviceId READ deviceId CONSTANT)
Q_PROPERTY(QByteArray accessToken READ accessToken CONSTANT)
Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged)
public:
using room_factory_t =
std::function<Room*(Connection*, const QString&, JoinState joinState)>;
using user_factory_t =
std::function<User*(Connection*, const QString&)>;
enum RoomVisibility { PublishRoom, UnpublishRoom }; // FIXME: Should go inside CreateRoomJob
explicit Connection(QObject* parent = nullptr);
explicit Connection(const QUrl& server, QObject* parent = nullptr);
virtual ~Connection();
QHash<QPair<QString, bool>, Room*> roomMap() const;
QMap<QString, User*> users() const;
/** Sends /forget to the server and also deletes room locally.
* This method is in Connection, not in Room, since it's a
* room lifecycle operation, and Connection is an acting room manager.
* It ensures that the local user is not a member of a room (running /leave,
* if necessary) then issues a /forget request and if that one doesn't fail
* deletion of the local Room object is ensured.
* \param id - the room id to forget
* \return - the ongoing /forget request to the server; note that the
* success() signal of this request is connected to deleteLater()
* of a respective room so by the moment this finishes, there might be no
* Room object anymore.
*/
ForgetRoomJob* forgetRoom(const QString& id);
// FIXME: Convert Q_INVOKABLEs to Q_PROPERTIES
// (breaks back-compatibility)
QUrl homeserver() const;
Q_INVOKABLE User* user(const QString& userId);
User* user();
QString userId() const;
QString deviceId() const;
/** @deprecated Use accessToken() instead. */
Q_INVOKABLE QString token() const;
QByteArray accessToken() const;
Q_INVOKABLE SyncJob* syncJob() const;
Q_INVOKABLE int millisToReconnect() const;
/**
* Call this before first sync to load from previously saved file.
*
* \param fromFile A local path to read the state from. Uses QUrl
* to be QML-friendly. Empty parameter means using a path
* defined by stateCachePath().
*/
Q_INVOKABLE void loadState(const QUrl &fromFile = {});
/**
* This method saves the current state of rooms (but not messages
* in them) to a local cache file, so that it could be loaded by
* loadState() on a next run of the client.
*
* \param toFile A local path to save the state to. Uses QUrl to be
* QML-friendly. Empty parameter means using a path defined by
* stateCachePath().
*/
Q_INVOKABLE void saveState(const QUrl &toFile = {}) const;
/**
* The default path to store the cached room state, defined as
* follows:
* QStandardPaths::writeableLocation(QStandardPaths::CacheLocation) + _safeUserId + "_state.json"
* where `_safeUserId` is userId() with `:` (colon) replaced with
* `_` (underscore)
* /see loadState(), saveState()
*/
Q_INVOKABLE QString stateCachePath() const;
bool cacheState() const;
void setCacheState(bool newValue);
/**
* This is a universal method to start a job of a type passed
* as a template parameter. Arguments to callApi() are arguments
* to the job constructor _except_ the first ConnectionData*
* argument - callApi() will pass it automatically.
*/
template <typename JobT, typename... JobArgTs>
JobT* callApi(JobArgTs&&... jobArgs) const
{
auto job = new JobT(std::forward<JobArgTs>(jobArgs)...);
job->start(connectionData());
return job;
}
/** Generates a new transaction id. Transaction id's are unique within
* a single Connection object
*/
Q_INVOKABLE QByteArray generateTxnId();
template <typename T = Room>
static void setRoomType()
{
roomFactory =
[](Connection* c, const QString& id, JoinState joinState)
{ return new T(c, id, joinState); };
}
template <typename T = User>
static void setUserType()
{
userFactory =
[](Connection* c, const QString& id) { return new T(id, c); };
}
public slots:
/** Set the homeserver base URL */
void setHomeserver(const QUrl& baseUrl);
/** Determine and set the homeserver from domain or MXID */
void resolveServer(const QString& mxidOrDomain);
void connectToServer(const QString& user, const QString& password,
const QString& initialDeviceName,
const QString& deviceId = {});
void connectWithToken(const QString& userId, const QString& accessToken,
const QString& deviceId);
/** @deprecated Use stopSync() instead */
void disconnectFromServer() { stopSync(); }
void logout();
void sync(int timeout = -1);
void stopSync();
virtual MediaThumbnailJob* getThumbnail(const QString& mediaId,
QSize requestedSize) const;
MediaThumbnailJob* getThumbnail(const QUrl& url,
QSize requestedSize) const;
MediaThumbnailJob* getThumbnail(const QUrl& url,
int requestedWidth,
int requestedHeight) const;
// QIODevice* should already be open
UploadContentJob* uploadContent(QIODevice* contentSource,
const QString& filename = {},
const QString& contentType = {}) const;
UploadContentJob* uploadFile(const QString& fileName,
const QString& contentType = {});
GetContentJob* getContent(const QString& mediaId) const;
GetContentJob* getContent(const QUrl& url) const;
// If localFilename is empty, a temporary file will be created
DownloadFileJob* downloadFile(const QUrl& url,
const QString& localFilename = {}) const;
/**
* \brief Create a room (generic method)
* This method allows to customize room entirely to your liking,
* providing all the attributes the original CS API provides.
*/
CreateRoomJob* createRoom(RoomVisibility visibility,
const QString& alias, const QString& name, const QString& topic,
const QVector<QString>& invites, const QString& presetName = {}, bool isDirect = false,
bool guestsCanJoin = false,
const QVector<CreateRoomJob::StateEvent>& initialState = {},
const QVector<CreateRoomJob::Invite3pid>& invite3pids = {},
const QJsonObject creationContent = {});
/** Create a direct chat with a single user, optional name and topic */
CreateRoomJob* createDirectChat(const QString& userId,
const QString& topic = {}, const QString& name = {});
virtual JoinRoomJob* joinRoom(const QString& roomAlias);
// Old API that will be abolished any time soon. DO NOT USE.
/** @deprecated Use callApi<PostMessageJob>() or Room::postMessage() instead */
virtual void postMessage(Room* room, const QString& type,
const QString& message) const;
/** @deprecated Use callApi<PostReceiptJob>() or Room::postReceipt() instead */
virtual PostReceiptJob* postReceipt(Room* room,
RoomEvent* event) const;
/** @deprecated Use callApi<LeaveRoomJob>() or Room::leaveRoom() instead */
virtual void leaveRoom( Room* room );
/** @deprecated User callApi<RoomMessagesJob>() or Room::getPreviousContent() instead */
virtual RoomMessagesJob* getMessages(Room* room,
const QString& from) const;
signals:
/**
* @deprecated
* This was a signal resulting from a successful resolveServer().
* Since Connection now provides setHomeserver(), the HS URL
* may change even without resolveServer() invocation. Use
* homeserverChanged() instead of resolved(). You can also use
* connectToServer and connectWithToken without the HS URL set in
* advance (i.e. without calling resolveServer), as they now trigger
* server name resolution from MXID if the server URL is not valid.
*/
void resolved();
void resolveError(QString error);
void homeserverChanged(QUrl baseUrl);
void connected();
void reconnected(); //< Unused; use connected() instead
void loggedOut();
void loginError(QString error);
void networkError(int retriesTaken, int inMilliseconds);
void syncDone();
void syncError(QString error);
void newUser(User* user);
/**
* \group Signals emitted on room transitions
*
* Note: Rooms in Invite state are always stored separately from
* rooms in Join/Leave state, because of special treatment of
* invite_state in Matrix CS API (see The Spec on /sync for details).
* Therefore, objects below are: r - room in Join/Leave state;
* i - room in Invite state
*
* 1. none -> Invite: newRoom(r), invitedRoom(r,nullptr)
* 2. none -> Join: newRoom(r), joinedRoom(r,nullptr)
* 3. none -> Leave: newRoom(r), leftRoom(r,nullptr)
* 4. Invite -> Join:
* newRoom(r), joinedRoom(r,i), aboutToDeleteRoom(i)
* 4a. Leave and Invite -> Join:
* joinedRoom(r,i), aboutToDeleteRoom(i)
* 5. Invite -> Leave:
* newRoom(r), leftRoom(r,i), aboutToDeleteRoom(i)
* 5a. Leave and Invite -> Leave:
* leftRoom(r,i), aboutToDeleteRoom(i)
* 6. Join -> Leave: leftRoom(r)
* 7. Leave -> Invite: newRoom(i), invitedRoom(i,r)
* 8. Leave -> Join: joinedRoom(r)
* The following transitions are only possible via forgetRoom()
* so far; if a room gets forgotten externally, sync won't tell
* about it:
* 9. any -> none: as any -> Leave, then aboutToDeleteRoom(r)
*/
/** A new room object has been created */
void newRoom(Room* room);
/** Invitation to a room received
*
* If the same room is in Left state, it's passed in prev.
*/
void invitedRoom(Room* room, Room* prev);
/** A room has just been joined
*
* It's not the same as receiving a room in "join" section of sync
* response (rooms will be there even after joining). If this room
* was in Invite state before, the respective object is passed in
* prev (and it will be deleted shortly afterwards).
*/
void joinedRoom(Room* room, Room* prev);
/** A room has just been left
*
* If this room has been in Invite state (as in case of rejecting
* an invitation), the respective object will be passed in prev
* (and will be deleted shortly afterwards).
*/
void leftRoom(Room* room, Room* prev);
/** The room object is about to be deleted */
void aboutToDeleteRoom(Room* room);
void cacheStateChanged();
protected:
/**
* @brief Access the underlying ConnectionData class
*/
const ConnectionData* connectionData() const;
/**
* @brief Find a (possibly new) Room object for the specified id
* Use this method whenever you need to find a Room object in
* the local list of rooms. Note that this does not interact with
* the server; in particular, does not automatically create rooms
* on the server.
* @return a pointer to a Room object with the specified id; nullptr
* if roomId is empty if roomFactory() failed to create a Room object.
*/
Room* provideRoom(const QString& roomId, JoinState joinState);
/**
* Completes loading sync data.
*/
void onSyncSuccess(SyncData &&data);
private:
class Private;
std::unique_ptr<Private> d;
/**
* A single entry for functions that need to check whether the
* homeserver is valid before running. May either execute connectFn
* synchronously or asynchronously (if tryResolve is true and
* a DNS lookup is initiated); in case of errors, emits resolveError
* if the homeserver URL is not valid and cannot be resolved from
* userId.
*
* @param userId - fully-qualified MXID to resolve HS from
* @param connectFn - a function to execute once the HS URL is good
*/
void checkAndConnect(const QString& userId,
std::function<void()> connectFn);
void doConnectToServer(const QString& user, const QString& password,
const QString& initialDeviceName,
const QString& deviceId = {});
static room_factory_t roomFactory;
static user_factory_t userFactory;
};
} // namespace QMatrixClient
Q_DECLARE_METATYPE(QMatrixClient::Connection*)
|