Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F325628
matrix-sdk.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
matrix-sdk.cpp
View Options
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include
<libkazv-config.hpp>
#include
<immer/config.hpp>
// https://github.com/arximboldi/immer/issues/168
#include
<boost/archive/text_oarchive.hpp>
#include
<boost/archive/text_iarchive.hpp>
#include
<fstream>
#include
<filesystem>
#include
<chrono>
#include
<QMutex>
#include
<QMutexLocker>
#include
<QtConcurrent>
#include
<QThreadPool>
#include
<KConfig>
#include
<KConfigGroup>
#include
<eventemitter/lagerstoreeventemitter.hpp>
#include
<client/sdk.hpp>
#include
<client/notification-handler.hpp>
#include
<zug/util.hpp>
#include
<lager/event_loop/qt.hpp>
#include
"matrix-sdk.hpp"
#include
"matrix-room-list.hpp"
#include
"matrix-promise.hpp"
#include
"matrix-event.hpp"
#include
"helper.hpp"
#include
"kazv-path-config.hpp"
#include
"kazv-version.hpp"
#include
"qt-json.hpp"
#include
"qt-rand-adapter.hpp"
#include
"qt-promise-handler.hpp"
#include
"qt-job-handler.hpp"
#include
"device-mgmt/matrix-device-list.hpp"
using
namespace
Kazv
;
// Sdk with qt event loop, identity transform and no enhancers
using
SdkT
=
decltype
(
makeSdk
(
SdkModel
{},
detail
::
declref
<
JobInterface
>
(),
detail
::
declref
<
EventInterface
>
(),
QtPromiseHandler
(
detail
::
declref
<
QObject
>
()),
zug
::
identity
,
withRandomGenerator
(
detail
::
declref
<
RandomInterface
>
())));
static
void
serializeClientToFile
(
Client
c
);
struct
QtEventLoop
{
QObject
*
m_obj
;
template
<
class
Fn
>
void
async
(
Fn
&&
)
{
throw
std
::
runtime_error
{
"not implemented!"
};
}
template
<
class
Fn
>
void
post
(
Fn
&&
fn
)
{
QMetaObject
::
invokeMethod
(
m_obj
,
std
::
forward
<
Fn
>
(
fn
),
Qt
::
QueuedConnection
);
}
void
finish
()
{}
void
pause
()
{
throw
std
::
runtime_error
{
"not implemented!"
};
}
void
resume
()
{
throw
std
::
runtime_error
{
"not implemented!"
};
}
};
struct
MatrixSdkPrivate
{
MatrixSdkPrivate
(
MatrixSdk
*
q
,
bool
testing
);
MatrixSdkPrivate
(
MatrixSdk
*
q
,
bool
testing
,
SdkModel
model
);
bool
testing
;
RandomInterface
randomGenerator
;
QThread
*
thread
;
QObject
*
obj
;
QtJobHandler
*
jobHandler
;
LagerStoreEventEmitter
ee
;
LagerStoreEventEmitter
::
Watchable
watchable
;
SdkT
sdk
;
QTimer
saveTimer
;
using
SecondaryRootT
=
decltype
(
sdk
.
createSecondaryRoot
(
std
::
declval
<
lager
::
with_qt_event_loop
>
()));
SecondaryRootT
secondaryRoot
;
Client
clientOnSecondaryRoot
;
NotificationHandler
notificationHandler
;
void
runIoContext
()
{
thread
->
start
();
}
void
maybeSerialize
()
{
if
(
!
testing
)
{
serializeClientToFile
(
clientOnSecondaryRoot
);
}
}
};
// Cleaning up notes:
// 0. Callback functions may store the context for an indefinite time
// 1. The QThread event loop can be stopped
// 2. QtJobHandler::submit() should only happen in the primary event loop thread
// 3. QtJobHandler lives in the primary event loop thread
// 4. QtJobs live in the primary event loop thread
// 5. Job callbacks are called in the primary event loop thread
// 6. QtPromise::then() callbacks are called in the primary event loop thread
// 7. When the QThread event loop stops, no more callbacks will be executed (there is nothing to post to)
// 8. The QThread should stop before obj is deleted
class
CleanupHelper
:
public
QObject
{
Q_OBJECT
public
:
explicit
CleanupHelper
(
std
::
unique_ptr
<
MatrixSdkPrivate
>
d
)
:
oldD
(
std
::
move
(
d
))
{
// After the thread's event loop is finished, we can delete the root object
connect
(
oldD
->
thread
,
&
QThread
::
finished
,
oldD
->
obj
,
&
QObject
::
deleteLater
);
connect
(
oldD
->
obj
,
&
QObject
::
destroyed
,
this
,
&
QObject
::
deleteLater
);
}
std
::
shared_ptr
<
MatrixSdkPrivate
>
oldD
;
void
cleanup
()
{
qCInfo
(
kazvLog
)
<<
"start to clean up everything"
;
oldD
->
clientOnSecondaryRoot
.
stopSyncing
()
.
then
([
oldD
=
oldD
](
auto
&&
)
{
qCDebug
(
kazvLog
)
<<
"stopped syncing"
;
QMetaObject
::
invokeMethod
(
oldD
->
obj
,
[
thread
=
oldD
->
thread
]()
{
thread
->
quit
();
});
});
oldD
->
thread
->
wait
();
oldD
->
thread
->
deleteLater
();
qCInfo
(
kazvLog
)
<<
"thread is done"
;
}
};
static
void
serializeClientToFile
(
Client
c
)
{
using
namespace
Kazv
::
CursorOp
;
auto
userId
=
+
c
.
userId
();
auto
deviceId
=
+
c
.
deviceId
();
if
(
userId
.
empty
()
||
deviceId
.
empty
())
{
qDebug
()
<<
"Not logged in, nothing to serialize"
;
return
;
}
using
StdPath
=
std
::
filesystem
::
path
;
auto
userDataDir
=
StdPath
(
kazvUserDataDir
().
toStdString
());
auto
sessionDir
=
userDataDir
/
"sessions"
/
userId
/
deviceId
;
auto
storeFile
=
sessionDir
/
"store"
;
auto
metadataFile
=
sessionDir
/
"metadata"
;
qDebug
()
<<
"storeFile="
<<
QString
::
fromStdString
(
storeFile
.
native
());
std
::
error_code
err
;
if
((
!
std
::
filesystem
::
create_directories
(
sessionDir
,
err
))
&&
err
)
{
qDebug
()
<<
"Unable to create sessionDir"
;
return
;
}
{
auto
storeStream
=
std
::
ofstream
(
storeFile
);
if
(
!
storeStream
)
{
qDebug
()
<<
"Unable to open storeFile"
;
return
;
}
using
OAr
=
boost
::
archive
::
text_oarchive
;
auto
archive
=
OAr
{
storeStream
};
c
.
serializeTo
(
archive
);
qDebug
()
<<
"Serialization done"
;
}
// store metadata
{
KConfig
metadata
(
QString
::
fromStdString
(
metadataFile
.
native
()));
KConfigGroup
mdGroup
(
&
metadata
,
"Metadata"
);
mdGroup
.
writeEntry
(
"kazvVersion"
,
QString
::
fromStdString
(
kazvVersionString
()));
mdGroup
.
writeEntry
(
"archiveFormat"
,
"text"
);
}
}
MatrixSdkPrivate
::
MatrixSdkPrivate
(
MatrixSdk
*
q
,
bool
testing
)
:
testing
(
testing
)
,
randomGenerator
(
QtRandAdapter
{})
,
thread
(
new
QThread
())
,
obj
(
new
QObject
())
,
jobHandler
(
new
QtJobHandler
(
obj
))
,
ee
{
QtEventLoop
{
obj
}}
,
watchable
(
ee
.
watchable
())
,
sdk
(
makeDefaultSdkWithCryptoRandom
(
randomGenerator
.
generateRange
<
std
::
string
>
(
makeDefaultSdkWithCryptoRandomSize
()),
static_cast
<
JobInterface
&>
(
*
jobHandler
),
static_cast
<
EventInterface
&>
(
ee
),
QtPromiseHandler
(
*
obj
),
zug
::
identity
,
withRandomGenerator
(
randomGenerator
)))
,
secondaryRoot
(
sdk
.
createSecondaryRoot
(
QtEventLoop
{
q
}))
,
clientOnSecondaryRoot
(
sdk
.
clientFromSecondaryRoot
(
secondaryRoot
))
,
notificationHandler
(
clientOnSecondaryRoot
.
notificationHandler
())
{
obj
->
moveToThread
(
thread
);
}
MatrixSdkPrivate
::
MatrixSdkPrivate
(
MatrixSdk
*
q
,
bool
testing
,
SdkModel
model
)
:
testing
(
testing
)
,
randomGenerator
(
QtRandAdapter
{})
,
thread
(
new
QThread
())
,
obj
(
new
QObject
())
,
jobHandler
(
new
QtJobHandler
(
obj
))
,
ee
{
QtEventLoop
{
obj
}}
,
watchable
(
ee
.
watchable
())
,
sdk
(
makeSdk
(
model
,
static_cast
<
JobInterface
&>
(
*
jobHandler
),
static_cast
<
EventInterface
&>
(
ee
),
QtPromiseHandler
(
*
obj
),
zug
::
identity
,
withRandomGenerator
(
randomGenerator
)))
,
secondaryRoot
(
sdk
.
createSecondaryRoot
(
QtEventLoop
{
q
},
std
::
move
(
model
)))
,
clientOnSecondaryRoot
(
sdk
.
clientFromSecondaryRoot
(
secondaryRoot
))
,
notificationHandler
(
clientOnSecondaryRoot
.
notificationHandler
())
{
obj
->
moveToThread
(
thread
);
}
MatrixSdk
::
MatrixSdk
(
std
::
unique_ptr
<
MatrixSdkPrivate
>
d
,
QObject
*
parent
)
:
QObject
(
parent
)
,
m_d
(
std
::
move
(
d
))
{
init
();
connect
(
this
,
&
MatrixSdk
::
trigger
,
this
,
[](
KazvEvent
e
)
{
qDebug
()
<<
"receiving trigger:"
;
if
(
std
::
holds_alternative
<
LoginSuccessful
>
(
e
))
{
qDebug
()
<<
"Login successful"
;
}
});
}
void
MatrixSdk
::
init
()
{
LAGER_QT
(
serverUrl
)
=
m_d
->
clientOnSecondaryRoot
.
serverUrl
().
xform
(
strToQt
);
Q_EMIT
serverUrlChanged
(
serverUrl
());
LAGER_QT
(
userId
)
=
m_d
->
clientOnSecondaryRoot
.
userId
().
xform
(
strToQt
);
Q_EMIT
userIdChanged
(
userId
());
LAGER_QT
(
token
)
=
m_d
->
clientOnSecondaryRoot
.
token
().
xform
(
strToQt
);
Q_EMIT
tokenChanged
(
token
());
LAGER_QT
(
deviceId
)
=
m_d
->
clientOnSecondaryRoot
.
deviceId
().
xform
(
strToQt
);
Q_EMIT
deviceIdChanged
(
deviceId
());
m_d
->
watchable
.
afterAll
(
[
this
](
KazvEvent
e
)
{
Q_EMIT
this
->
trigger
(
e
);
});
m_d
->
watchable
.
after
<
LoginSuccessful
>
(
[
this
](
LoginSuccessful
e
)
{
Q_EMIT
this
->
loginSuccessful
(
e
);
});
m_d
->
watchable
.
after
<
LoginFailed
>
(
[
this
](
LoginFailed
e
)
{
Q_EMIT
this
->
loginFailed
(
QString
::
fromStdString
(
e
.
errorCode
),
QString
::
fromStdString
(
e
.
error
)
);
});
m_d
->
watchable
.
after
<
ReceivingRoomTimelineEvent
>
(
[
this
](
ReceivingRoomTimelineEvent
e
)
{
Q_EMIT
this
->
receivedMessage
(
QString
::
fromStdString
(
e
.
roomId
),
QString
::
fromStdString
(
e
.
event
.
id
())
);
});
connect
(
&
m_d
->
saveTimer
,
&
QTimer
::
timeout
,
&
m_d
->
saveTimer
,
[
m_d
=
m_d
.
get
()]()
{
m_d
->
maybeSerialize
();
});
const
int
saveIntervalMs
=
1000
*
60
*
5
;
m_d
->
saveTimer
.
start
(
std
::
chrono
::
milliseconds
{
saveIntervalMs
});
}
MatrixSdk
::
MatrixSdk
(
QObject
*
parent
)
:
MatrixSdk
(
std
::
make_unique
<
MatrixSdkPrivate
>
(
this
,
/* testing = */
false
),
parent
)
{
}
MatrixSdk
::
MatrixSdk
(
SdkModel
model
,
bool
testing
,
QObject
*
parent
)
:
MatrixSdk
(
std
::
make_unique
<
MatrixSdkPrivate
>
(
this
,
testing
,
std
::
move
(
model
)),
parent
)
{
}
static
void
cleanupDPointer
(
std
::
unique_ptr
<
MatrixSdkPrivate
>
oldD
)
{
oldD
->
saveTimer
.
disconnect
();
oldD
->
saveTimer
.
stop
();
auto
helper
=
new
CleanupHelper
(
std
::
move
(
oldD
));
helper
->
cleanup
();
}
MatrixSdk
::~
MatrixSdk
()
{
if
(
m_d
)
{
serializeToFile
();
cleanupDPointer
(
std
::
move
(
m_d
));
}
}
QString
MatrixSdk
::
mxcUriToHttp
(
QString
mxcUri
)
const
{
return
QString
::
fromStdString
(
m_d
->
clientOnSecondaryRoot
.
mxcUriToHttp
(
mxcUri
.
toStdString
()));
}
MatrixDeviceList
*
MatrixSdk
::
devicesOfUser
(
QString
userId
)
const
{
return
new
MatrixDeviceList
(
m_d
->
clientOnSecondaryRoot
.
devicesOfUser
(
userId
.
toStdString
()));
}
void
MatrixSdk
::
login
(
const
QString
&
userId
,
const
QString
&
password
)
{
m_d
->
clientOnSecondaryRoot
.
autoDiscover
(
userId
.
toStdString
())
.
then
([
this
,
client
=
m_d
->
clientOnSecondaryRoot
.
toEventLoop
(),
userId
,
password
](
auto
res
)
{
if
(
!
res
.
success
())
{
// FIXME use real error codes and msgs when available in libkazv
Q_EMIT
this
->
discoverFailed
(
""
,
""
);
return
res
;
}
auto
serverUrl
=
res
.
dataStr
(
"homeserverUrl"
);
client
.
passwordLogin
(
serverUrl
,
userId
.
toStdString
(),
password
.
toStdString
(),
"kazv 0.0.0"
);
return
res
;
});
m_d
->
runIoContext
();
}
MatrixRoomList
*
MatrixSdk
::
roomList
()
const
{
return
new
MatrixRoomList
(
m_d
->
clientOnSecondaryRoot
);
}
void
MatrixSdk
::
emplace
(
std
::
optional
<
SdkModel
>
model
)
{
auto
testing
=
m_d
->
testing
;
if
(
m_d
)
{
cleanupDPointer
(
std
::
move
(
m_d
));
}
m_d
=
(
model
.
has_value
()
?
std
::
make_unique
<
MatrixSdkPrivate
>
(
this
,
testing
,
std
::
move
(
model
.
value
()))
:
std
::
make_unique
<
MatrixSdkPrivate
>
(
this
,
testing
));
// Re-initialize lager-qt cursors and watchable connections
init
();
m_d
->
runIoContext
();
m_d
->
clientOnSecondaryRoot
.
startSyncing
();
Q_EMIT
sessionChanged
();
}
QStringList
MatrixSdk
::
allSessions
()
const
{
using
StdPath
=
std
::
filesystem
::
path
;
auto
userDataDir
=
StdPath
(
kazvUserDataDir
().
toStdString
());
auto
allSessionsDir
=
userDataDir
/
"sessions"
;
QStringList
sessionNames
;
try
{
for
(
const
auto
&
p
:
std
::
filesystem
::
directory_iterator
(
allSessionsDir
))
{
if
(
p
.
is_directory
())
{
auto
userId
=
p
.
path
().
filename
().
native
();
for
(
const
auto
&
q
:
std
::
filesystem
::
directory_iterator
(
p
.
path
()))
{
auto
path
=
q
.
path
();
auto
deviceId
=
path
.
filename
().
native
();
std
::
error_code
err
;
if
(
std
::
filesystem
::
exists
(
path
/
"store"
,
err
))
{
sessionNames
.
append
(
QString
::
fromStdString
(
userId
+
"/"
+
deviceId
));
}
}
}
}
}
catch
(
const
std
::
filesystem
::
filesystem_error
&
)
{
qDebug
()
<<
"sessionDir not available, ignoring"
;
}
return
sessionNames
;
}
void
MatrixSdk
::
serializeToFile
()
const
{
m_d
->
maybeSerialize
();
}
bool
MatrixSdk
::
loadSession
(
QString
sessionName
)
{
qDebug
()
<<
"in loadSession(), sessionName="
<<
sessionName
;
using
StdPath
=
std
::
filesystem
::
path
;
auto
userDataDir
=
StdPath
(
kazvUserDataDir
().
toStdString
());
auto
sessionDir
=
userDataDir
/
"sessions"
/
sessionName
.
toStdString
();
auto
storeFile
=
sessionDir
/
"store"
;
auto
metadataFile
=
sessionDir
/
"metadata"
;
if
(
!
std
::
filesystem
::
exists
(
storeFile
))
{
qDebug
()
<<
"storeFile does not exist, skip loading session "
<<
sessionName
;
return
false
;
}
if
(
std
::
filesystem
::
exists
(
metadataFile
))
{
KConfig
metadata
(
QString
::
fromStdString
(
metadataFile
.
native
()));
KConfigGroup
mdGroup
(
&
metadata
,
"Metadata"
);
auto
format
=
mdGroup
.
readEntry
(
"archiveFormat"
);
if
(
format
!=
QStringLiteral
(
"text"
))
{
qDebug
()
<<
"Unknown archive format:"
<<
format
;
return
false
;
}
auto
version
=
mdGroup
.
readEntry
(
"kazvVersion"
);
auto
curVersion
=
kazvVersionString
();
if
(
version
!=
QString
::
fromStdString
(
curVersion
))
{
qDebug
()
<<
"A different version from the current one, making a backup"
;
std
::
error_code
err
;
auto
now
=
std
::
chrono
::
system_clock
::
now
();
auto
backupName
=
std
::
to_string
(
std
::
chrono
::
duration_cast
<
std
::
chrono
::
seconds
>
(
now
.
time_since_epoch
()).
count
());
auto
backupDir
=
sessionDir
/
"backup"
/
backupName
;
if
(
!
std
::
filesystem
::
create_directories
(
backupDir
,
err
)
&&
err
)
{
qDebug
()
<<
"Cannot create backup directory"
;
return
false
;
}
std
::
filesystem
::
copy_file
(
storeFile
,
backupDir
/
"store"
);
std
::
filesystem
::
copy_file
(
metadataFile
,
backupDir
/
"metadata"
);
}
}
SdkModel
model
;
try
{
auto
storeStream
=
std
::
ifstream
(
storeFile
);
if
(
!
storeStream
)
{
qDebug
()
<<
"Unable to open storeFile"
;
return
false
;
}
using
IAr
=
boost
::
archive
::
text_iarchive
;
auto
archive
=
IAr
{
storeStream
};
archive
>>
model
;
qDebug
()
<<
"Finished loading session"
;
}
catch
(
const
std
::
exception
&
e
)
{
qDebug
()
<<
"Error when loading session:"
<<
QString
::
fromStdString
(
e
.
what
());
return
false
;
}
emplace
(
std
::
move
(
model
));
return
true
;
}
bool
MatrixSdk
::
startNewSession
()
{
emplace
(
std
::
nullopt
);
return
true
;
}
static
std
::
optional
<
std
::
string
>
optMaybe
(
QString
s
)
{
if
(
s
.
isEmpty
())
{
return
std
::
nullopt
;
}
else
{
return
s
.
toStdString
();
}
}
void
MatrixSdk
::
createRoom
(
bool
isPrivate
,
const
QString
&
name
,
const
QString
&
alias
,
const
QStringList
&
invite
,
bool
isDirect
,
bool
allowFederate
,
const
QString
&
topic
,
const
QJsonValue
&
powerLevelContentOverride
)
{
m_d
->
clientOnSecondaryRoot
.
createRoom
(
isPrivate
?
Kazv
::
RoomVisibility
::
Private
:
Kazv
::
RoomVisibility
::
Public
,
optMaybe
(
name
),
optMaybe
(
alias
),
qStringListToStdF
(
invite
),
isDirect
,
allowFederate
,
optMaybe
(
topic
),
nlohmann
::
json
(
powerLevelContentOverride
)
)
.
then
([
this
,
client
=
m_d
->
clientOnSecondaryRoot
.
toEventLoop
()](
auto
stat
)
{
if
(
stat
.
success
())
{
Q_EMIT
createRoomSuccessful
();
}
else
{
Q_EMIT
createRoomFailed
(
""
,
""
);
}
});
}
MatrixPromise
*
MatrixSdk
::
joinRoom
(
const
QString
&
idOrAlias
,
const
QStringList
&
servers
)
{
return
new
MatrixPromise
(
m_d
->
clientOnSecondaryRoot
.
joinRoom
(
idOrAlias
.
toStdString
(),
qStringListToStdF
(
servers
)
)
.
then
([
this
,
idOrAlias
,
client
=
m_d
->
clientOnSecondaryRoot
.
toEventLoop
()](
auto
stat
)
{
if
(
stat
.
success
())
{
Q_EMIT
joinRoomSuccessful
(
idOrAlias
);
}
else
{
Q_EMIT
joinRoomFailed
(
idOrAlias
,
""
,
""
);
}
}));
}
MatrixPromise
*
MatrixSdk
::
setDeviceTrustLevel
(
QString
userId
,
QString
deviceId
,
QString
trustLevel
)
{
return
new
MatrixPromise
(
m_d
->
clientOnSecondaryRoot
.
setDeviceTrustLevel
(
userId
.
toStdString
(),
deviceId
.
toStdString
(),
qStringToTrustLevelFunc
(
trustLevel
)
)
);
}
MatrixPromise
*
MatrixSdk
::
getSelfProfile
()
{
return
new
MatrixPromise
(
m_d
->
clientOnSecondaryRoot
.
getProfile
(
userId
().
toStdString
())
);
}
MatrixPromise
*
MatrixSdk
::
setDisplayName
(
QString
displayName
)
{
return
new
MatrixPromise
(
m_d
->
clientOnSecondaryRoot
.
setDisplayName
(
displayName
.
isEmpty
()
?
std
::
nullopt
:
std
::
optional
<
std
::
string
>
(
displayName
.
toStdString
())
)
);
}
MatrixPromise
*
MatrixSdk
::
setAvatarUrl
(
QString
avatarUrl
)
{
return
new
MatrixPromise
(
m_d
->
clientOnSecondaryRoot
.
setAvatarUrl
(
avatarUrl
.
isEmpty
()
?
std
::
nullopt
:
std
::
optional
<
std
::
string
>
(
avatarUrl
.
toStdString
())
)
);
}
void
MatrixSdk
::
startThread
()
{
m_d
->
runIoContext
();
}
RandomInterface
&
MatrixSdk
::
randomGenerator
()
const
{
return
m_d
->
randomGenerator
;
}
bool
MatrixSdk
::
shouldNotify
(
MatrixEvent
*
event
)
const
{
// Do not notify own event
if
(
event
->
sender
()
==
userId
())
{
return
false
;
}
return
m_d
->
notificationHandler
.
handleNotification
(
event
->
underlyingEvent
()).
shouldNotify
;
}
#include
"matrix-sdk.moc"
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Thu, Apr 24, 4:18 AM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
88096
Default Alt Text
matrix-sdk.cpp (17 KB)
Attached To
Mode
rK kazv
Attached
Detach File
Event Timeline
Log In to Comment