Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F1039559
matrix-sdk.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
matrix-sdk.cpp
View Options
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <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
<KConfig>
#include
<KConfigGroup>
#include
<eventemitter/lagerstoreeventemitter.hpp>
#include
<job/cprjobhandler.hpp>
#include
<asio-promise-handler.hpp>
#include
<client/sdk.hpp>
#include
<base/descendent.hpp>
#include
<zug/util.hpp>
#include
<boost/asio.hpp>
#include
<lager/event_loop/boost_asio.hpp>
#include
<lager/event_loop/qt.hpp>
#include
"matrix-sdk.hpp"
#include
"matrix-room-list.hpp"
#include
"helper.hpp"
#include
"kazv-path-config.hpp"
#include
"kazv-version.hpp"
#include
"qt-rand-adapter.hpp"
using
namespace
Kazv
;
// Sdk with Boost event loop, identity transform and no enhancers
using
ExecT
=
decltype
(
std
::
declval
<
boost
::
asio
::
io_context
>
().
get_executor
());
using
SdkT
=
decltype
(
makeSdk
(
SdkModel
{},
detail
::
declref
<
JobInterface
>
(),
detail
::
declref
<
EventInterface
>
(),
AsioPromiseHandler
{
std
::
declval
<
boost
::
asio
::
io_context
>
().
get_executor
()},
zug
::
identity
,
withRandomGenerator
(
detail
::
declref
<
RandomInterface
>
())));
struct
MatrixSdkPrivate
{
MatrixSdkPrivate
(
MatrixSdk
*
q
);
MatrixSdkPrivate
(
MatrixSdk
*
q
,
SdkModel
model
);
RandomInterface
randomGenerator
;
boost
::
asio
::
io_context
io
;
CprJobHandler
jobHandler
;
LagerStoreEventEmitter
ee
;
LagerStoreEventEmitter
::
Watchable
watchable
;
SdkT
sdk
;
QTimer
saveTimer
;
QMutex
ioContextLock
;
using
SecondaryRootT
=
decltype
(
sdk
.
createSecondaryRoot
(
std
::
declval
<
lager
::
with_qt_event_loop
>
()));
SecondaryRootT
secondaryRoot
;
Client
clientOnSecondaryRoot
;
void
runIoContext
()
{
std
::
thread
([
this
]
{
// This lock is to persist as long as io.run() does not return.
// Together with the measures in emplace(), it ensures that
// the io context is not destructed prematurely.
QMutexLocker
l
(
&
ioContextLock
);
qDebug
()
<<
"running io context..."
;
io
.
run
();
qDebug
()
<<
"io context is done"
;
}).
detach
();
}
};
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
)
:
randomGenerator
(
QtRandAdapter
{})
,
io
{}
,
jobHandler
{
io
.
get_executor
()}
,
ee
{
lager
::
with_boost_asio_event_loop
{
io
.
get_executor
()}}
,
watchable
(
ee
.
watchable
())
,
sdk
(
makeDefaultSdkWithCryptoRandom
(
randomGenerator
.
generateRange
<
std
::
string
>
(
makeDefaultSdkWithCryptoRandomSize
()),
static_cast
<
JobInterface
&>
(
jobHandler
),
static_cast
<
EventInterface
&>
(
ee
),
AsioPromiseHandler
{
io
.
get_executor
()},
zug
::
identity
,
withRandomGenerator
(
randomGenerator
)))
,
secondaryRoot
(
sdk
.
createSecondaryRoot
(
lager
::
with_qt_event_loop
{
*
q
}))
,
clientOnSecondaryRoot
(
sdk
.
clientFromSecondaryRoot
(
secondaryRoot
))
{
}
MatrixSdkPrivate
::
MatrixSdkPrivate
(
MatrixSdk
*
q
,
SdkModel
model
)
:
randomGenerator
(
QtRandAdapter
{})
,
io
{}
,
jobHandler
{
io
.
get_executor
()}
,
ee
{
lager
::
with_boost_asio_event_loop
{
io
.
get_executor
()}}
,
watchable
(
ee
.
watchable
())
,
sdk
(
makeSdk
(
std
::
move
(
model
),
static_cast
<
JobInterface
&>
(
jobHandler
),
static_cast
<
EventInterface
&>
(
ee
),
AsioPromiseHandler
{
io
.
get_executor
()},
zug
::
identity
,
withRandomGenerator
(
randomGenerator
)))
,
secondaryRoot
(
sdk
.
createSecondaryRoot
(
lager
::
with_qt_event_loop
{
*
q
}))
,
clientOnSecondaryRoot
(
sdk
.
clientFromSecondaryRoot
(
secondaryRoot
))
{
}
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
(
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
)
);
});
connect
(
&
m_d
->
saveTimer
,
&
QTimer
::
timeout
,
&
m_d
->
saveTimer
,
[
m_d
=
m_d
.
get
()]()
{
serializeClientToFile
(
m_d
->
clientOnSecondaryRoot
);
});
const
int
saveIntervalMs
=
1000
*
60
*
5
;
m_d
->
saveTimer
.
start
(
std
::
chrono
::
milliseconds
{
saveIntervalMs
});
}
MatrixSdk
::
MatrixSdk
(
QObject
*
parent
)
:
MatrixSdk
(
std
::
make_unique
<
MatrixSdkPrivate
>
(
this
),
parent
)
{
}
MatrixSdk
::
MatrixSdk
(
SdkModel
model
,
QObject
*
parent
)
:
MatrixSdk
(
std
::
make_unique
<
MatrixSdkPrivate
>
(
this
,
std
::
move
(
model
)),
parent
)
{
}
static
void
cleanupDPointer
(
std
::
unique_ptr
<
MatrixSdkPrivate
>
oldD
)
{
oldD
->
saveTimer
.
disconnect
();
oldD
->
saveTimer
.
stop
();
QtConcurrent
::
run
(
[
oldD
=
std
::
shared_ptr
(
std
::
move
(
oldD
))]
{
qDebug
()
<<
"start to clean up everything"
;
// Not running io_context::stop() because it will
// disregard any remaining work guards. Here we just
// want to end any periodic jobs but still wait for
// current remaining jobs.
oldD
->
clientOnSecondaryRoot
.
stopSyncing
();
oldD
->
jobHandler
.
stop
();
// This lock will not be unlocked as long as io.run()
// in runIoContext() does not return.
QMutexLocker
l
(
&
(
oldD
->
ioContextLock
));
// qDebug() << "serialize final client state";
// serializeClientToFile(oldD->sdk.client());
qDebug
()
<<
"from here, the old data can be destructed"
;
});
}
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
()));
}
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
)
{
if
(
m_d
)
{
cleanupDPointer
(
std
::
move
(
m_d
));
}
m_d
=
(
model
.
has_value
()
?
std
::
make_unique
<
MatrixSdkPrivate
>
(
this
,
std
::
move
(
model
.
value
()))
:
std
::
make_unique
<
MatrixSdkPrivate
>
(
this
));
// 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
{
serializeClientToFile
(
m_d
->
clientOnSecondaryRoot
);
}
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
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Wed, May 14, 10:55 AM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
92795
Default Alt Text
matrix-sdk.cpp (13 KB)
Attached To
Mode
rK kazv
Attached
Detach File
Event Timeline
Log In to Comment