Implement UserPhotosViewer using rpl.

This commit is contained in:
John Preston 2017-09-06 17:50:11 +03:00
parent 2690618da2
commit 696478843e
8 changed files with 239 additions and 206 deletions

View File

@ -25,14 +25,25 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/storage_facade.h" #include "storage/storage_facade.h"
#include "storage/storage_user_photos.h" #include "storage/storage_user_photos.h"
UserPhotosSlice::UserPhotosSlice(Key key) : UserPhotosSlice(key, base::none) { UserPhotosSlice::UserPhotosSlice(Key key) : UserPhotosSlice(
key,
{},
base::none,
base::none,
0) {
} }
UserPhotosSlice::UserPhotosSlice( UserPhotosSlice::UserPhotosSlice(
Key key, Key key,
base::optional<int> fullCount) const std::deque<PhotoId> &ids,
base::optional<int> fullCount,
base::optional<int> skippedBefore,
int skippedAfter)
: _key(key) : _key(key)
, _fullCount(fullCount) { , _ids(ids)
, _fullCount(fullCount)
, _skippedBefore(skippedBefore)
, _skippedAfter(skippedAfter) {
} }
base::optional<int> UserPhotosSlice::indexOf(PhotoId photoId) const { base::optional<int> UserPhotosSlice::indexOf(PhotoId photoId) const {
@ -79,82 +90,62 @@ QString UserPhotosSlice::debug() const {
return before + middle + after; return before + middle + after;
} }
UserPhotosViewer::UserPhotosViewer( class UserPhotosSliceBuilder {
public:
using Key = UserPhotosSlice::Key;
UserPhotosSliceBuilder(Key key, int limitBefore, int limitAfter);
bool applyUpdate(const Storage::UserPhotosResult &update);
bool applyUpdate(const Storage::UserPhotosSliceUpdate &update);
void checkInsufficientPhotos();
rpl::producer<PhotoId> insufficientPhotosAround() const {
return _insufficientPhotosAround.events();
}
UserPhotosSlice snapshot() const;
private:
void mergeSliceData(
base::optional<int> count,
const std::deque<PhotoId> &photoIds,
base::optional<int> skippedBefore,
int skippedAfter);
void sliceToLimits();
Key _key;
std::deque<PhotoId> _ids;
base::optional<int> _fullCount;
base::optional<int> _skippedBefore;
int _skippedAfter = 0;
int _limitBefore = 0;
int _limitAfter = 0;
rpl::event_stream<PhotoId> _insufficientPhotosAround;
};
UserPhotosSliceBuilder::UserPhotosSliceBuilder(
Key key, Key key,
int limitBefore, int limitBefore,
int limitAfter) int limitAfter)
: _key(key) : _key(key)
, _limitBefore(limitBefore) , _limitBefore(limitBefore)
, _limitAfter(limitAfter) , _limitAfter(limitAfter) {
, _data(_key) {
} }
void UserPhotosViewer::start() { bool UserPhotosSliceBuilder::applyUpdate(const Storage::UserPhotosResult &update) {
auto applyUpdateCallback = [this](auto &update) {
this->applyUpdate(update);
};
subscribe(Auth().storage().userPhotosSliceUpdated(), applyUpdateCallback);
loadInitial();
}
void UserPhotosViewer::loadInitial() {
auto weak = base::make_weak_unique(this);
Auth().storage().query(Storage::UserPhotosQuery(
_key,
_limitBefore,
_limitAfter), [weak](Storage::UserPhotosResult &&result) {
if (weak) {
weak->applyStoredResult(std::move(result));
}
});
}
void UserPhotosViewer::applyStoredResult(Storage::UserPhotosResult &&result) {
mergeSliceData( mergeSliceData(
result.count, update.count,
result.photoIds, update.photoIds,
result.skippedBefore, update.skippedBefore,
result.skippedAfter); update.skippedAfter);
return true;
} }
void UserPhotosViewer::mergeSliceData( bool UserPhotosSliceBuilder::applyUpdate(const Storage::UserPhotosSliceUpdate &update) {
base::optional<int> count,
const std::deque<PhotoId> &photoIds,
base::optional<int> skippedBefore,
int skippedAfter) {
if (photoIds.empty()) {
if (_data._fullCount != count) {
_data._fullCount = count;
if (_data._fullCount && *_data._fullCount <= _data.size()) {
_data._fullCount = _data.size();
_data._skippedBefore = _data._skippedAfter = 0;
}
updated.notify(_data);
}
sliceToLimits();
return;
}
if (count) {
_data._fullCount = count;
}
_data._skippedAfter = skippedAfter;
_data._ids = photoIds;
if (_data._fullCount) {
_data._skippedBefore = *_data._fullCount
- _data._skippedAfter
- int(_data._ids.size());
}
sliceToLimits();
updated.notify(_data);
}
void UserPhotosViewer::applyUpdate(const SliceUpdate &update) {
if (update.userId != _key.userId) { if (update.userId != _key.userId) {
return; return false;
} }
auto idsCount = update.photoIds ? int(update.photoIds->size()) : 0; auto idsCount = update.photoIds ? int(update.photoIds->size()) : 0;
mergeSliceData( mergeSliceData(
@ -162,28 +153,99 @@ void UserPhotosViewer::applyUpdate(const SliceUpdate &update) {
update.photoIds ? *update.photoIds : std::deque<PhotoId> {}, update.photoIds ? *update.photoIds : std::deque<PhotoId> {},
update.count | func::add(-idsCount), update.count | func::add(-idsCount),
0); 0);
return true;
} }
void UserPhotosViewer::sliceToLimits() { void UserPhotosSliceBuilder::checkInsufficientPhotos() {
auto aroundIt = base::find(_data._ids, _key.photoId); sliceToLimits();
auto removeFromBegin = (aroundIt - _data._ids.begin() - _limitBefore); }
auto removeFromEnd = (_data._ids.end() - aroundIt - _limitAfter - 1);
void UserPhotosSliceBuilder::mergeSliceData(
base::optional<int> count,
const std::deque<PhotoId> &photoIds,
base::optional<int> skippedBefore,
int skippedAfter) {
if (photoIds.empty()) {
if (_fullCount != count) {
_fullCount = count;
if (_fullCount && *_fullCount <= _ids.size()) {
_fullCount = _ids.size();
_skippedBefore = _skippedAfter = 0;
}
}
} else {
if (count) {
_fullCount = count;
}
_skippedAfter = skippedAfter;
_ids = photoIds;
if (_fullCount) {
_skippedBefore = *_fullCount
- _skippedAfter
- int(_ids.size());
}
}
sliceToLimits();
}
void UserPhotosSliceBuilder::sliceToLimits() {
auto aroundIt = base::find(_ids, _key.photoId);
auto removeFromBegin = (aroundIt - _ids.begin() - _limitBefore);
auto removeFromEnd = (_ids.end() - aroundIt - _limitAfter - 1);
if (removeFromEnd > 0) { if (removeFromEnd > 0) {
_data._ids.erase(_data._ids.end() - removeFromEnd, _data._ids.end()); _ids.erase(_ids.end() - removeFromEnd, _ids.end());
_data._skippedAfter += removeFromEnd; _skippedAfter += removeFromEnd;
} }
if (removeFromBegin > 0) { if (removeFromBegin > 0) {
_data._ids.erase(_data._ids.begin(), _data._ids.begin() + removeFromBegin); _ids.erase(_ids.begin(), _ids.begin() + removeFromBegin);
if (_data._skippedBefore) { if (_skippedBefore) {
*_data._skippedBefore += removeFromBegin; *_skippedBefore += removeFromBegin;
} }
} else if (removeFromBegin < 0 && (!_data._skippedBefore || *_data._skippedBefore > 0)) { } else if (removeFromBegin < 0 && (!_skippedBefore || *_skippedBefore > 0)) {
requestPhotos(); _insufficientPhotosAround.fire(_ids.empty() ? 0 : _ids.front());
} }
} }
void UserPhotosViewer::requestPhotos() { UserPhotosSlice UserPhotosSliceBuilder::snapshot() const {
Auth().api().requestUserPhotos( return UserPhotosSlice(_key, _ids, _fullCount, _skippedBefore, _skippedAfter);
App::user(_key.userId), }
_data._ids.empty() ? 0 : _data._ids.front());
rpl::producer<UserPhotosSlice> UserPhotosViewer(
UserPhotosSlice::Key key,
int limitBefore,
int limitAfter) {
return [key, limitBefore, limitAfter](auto consumer) {
auto lifetime = rpl::lifetime();
auto builder = lifetime.make_state<UserPhotosSliceBuilder>(
key,
limitBefore,
limitAfter);
auto applyUpdate = [=](auto &&update) {
if (builder->applyUpdate(std::move(update))) {
consumer.put_next(builder->snapshot());
}
};
auto requestPhotosAround = [user = App::user(key.userId)](PhotoId photoId) {
Auth().api().requestUserPhotos(user, photoId);
};
builder->insufficientPhotosAround()
| rpl::on_next(requestPhotosAround)
| rpl::start(lifetime);
Auth().storage().userPhotosSliceUpdated()
| rpl::on_next(applyUpdate)
| rpl::start(lifetime);
Auth().storage().query(Storage::UserPhotosQuery(
key,
limitBefore,
limitAfter
))
| rpl::on_next(applyUpdate)
| rpl::on_done([=] { builder->checkInsufficientPhotos(); })
| rpl::start(lifetime);
return lifetime;
};
} }

View File

@ -23,13 +23,17 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/storage_user_photos.h" #include "storage/storage_user_photos.h"
#include "base/weak_unique_ptr.h" #include "base/weak_unique_ptr.h"
class UserPhotosViewer;
class UserPhotosSlice { class UserPhotosSlice {
public: public:
using Key = Storage::UserPhotosKey; using Key = Storage::UserPhotosKey;
UserPhotosSlice(Key key); UserPhotosSlice(Key key);
UserPhotosSlice(Key key, base::optional<int> fullCount); UserPhotosSlice(
Key key,
const std::deque<PhotoId> &ids,
base::optional<int> fullCount,
base::optional<int> skippedBefore,
int skippedAfter);
const Key &key() const { return _key; } const Key &key() const { return _key; }
@ -50,41 +54,11 @@ private:
base::optional<int> _skippedBefore; base::optional<int> _skippedBefore;
int _skippedAfter = 0; int _skippedAfter = 0;
friend class UserPhotosViewer; friend class UserPhotosSliceBuilder;
}; };
class UserPhotosViewer : rpl::producer<UserPhotosSlice> UserPhotosViewer(
private base::Subscriber, UserPhotosSlice::Key key,
public base::enable_weak_from_this { int limitBefore,
public: int limitAfter);
using Key = Storage::UserPhotosKey;
UserPhotosViewer(Key key, int limitBefore, int limitAfter);
void start();
base::Observable<UserPhotosSlice> updated;
private:
using InitialResult = Storage::UserPhotosResult;
using SliceUpdate = Storage::UserPhotosSliceUpdate;
void loadInitial();
void requestPhotos();
void applyStoredResult(InitialResult &&result);
void applyUpdate(const SliceUpdate &update);
void sliceToLimits();
void mergeSliceData(
base::optional<int> count,
const std::deque<PhotoId> &photoIds,
base::optional<int> skippedBefore,
int skippedAfter);
Key _key;
int _limitBefore = 0;
int _limitAfter = 0;
UserPhotosSlice _data;
};

View File

@ -78,13 +78,12 @@ struct MediaView::SharedMedia {
}; };
struct MediaView::UserPhotos { struct MediaView::UserPhotos {
UserPhotos(UserPhotosViewer::Key key) UserPhotos(UserPhotosSlice::Key key)
: key(key) : key(key) {
, slice(key, kIdsLimit, kIdsLimit) {
} }
UserPhotosViewer::Key key; UserPhotosSlice::Key key;
UserPhotosViewer slice; rpl::lifetime lifetime;
}; };
MediaView::MediaView() : TWidget(nullptr) MediaView::MediaView() : TWidget(nullptr)
@ -1075,7 +1074,7 @@ void MediaView::validateSharedMedia() {
} }
void MediaView::handleSharedMediaUpdate(const SharedMediaSliceWithLast &update) { void MediaView::handleSharedMediaUpdate(const SharedMediaSliceWithLast &update) {
if (isHidden() || (!_photo && !_doc) || !_sharedMedia) { if ((!_photo && !_doc) || !_sharedMedia) {
_sharedMediaData = base::none; _sharedMediaData = base::none;
} else { } else {
_sharedMediaData = update; _sharedMediaData = update;
@ -1120,21 +1119,24 @@ bool MediaView::validUserPhotos() const {
void MediaView::validateUserPhotos() { void MediaView::validateUserPhotos() {
if (auto key = userPhotosKey()) { if (auto key = userPhotosKey()) {
_userPhotos = std::make_unique<UserPhotos>(*key); _userPhotos = std::make_unique<UserPhotos>(*key);
subscribe(_userPhotos->slice.updated, [this](const UserPhotosSlice &data) { UserPhotosViewer(
handleUserPhotosUpdate(data); *key,
}); kIdsLimit,
_userPhotos->slice.start(); kIdsLimit
) | rpl::on_next([this](UserPhotosSlice &&update) {
handleUserPhotosUpdate(std::move(update));
}) | rpl::start(_userPhotos->lifetime);
} else { } else {
_userPhotos = nullptr; _userPhotos = nullptr;
_userPhotosData = base::none; _userPhotosData = base::none;
} }
} }
void MediaView::handleUserPhotosUpdate(const UserPhotosSlice &update) { void MediaView::handleUserPhotosUpdate(UserPhotosSlice &&update) {
if (isHidden() || !_photo || !_userPhotos) { if (!_photo || !_userPhotos) {
_userPhotosData = base::none; _userPhotosData = base::none;
} else { } else {
_userPhotosData = update; _userPhotosData = std::move(update);
} }
findCurrent(); findCurrent();
updateControls(); updateControls();

View File

@ -178,11 +178,11 @@ private:
void handleSharedMediaUpdate(const SharedMediaSliceWithLast &update); void handleSharedMediaUpdate(const SharedMediaSliceWithLast &update);
struct UserPhotos; struct UserPhotos;
using UserPhotosKey = UserPhotosViewer::Key; using UserPhotosKey = UserPhotosSlice::Key;
base::optional<UserPhotosKey> userPhotosKey() const; base::optional<UserPhotosKey> userPhotosKey() const;
bool validUserPhotos() const; bool validUserPhotos() const;
void validateUserPhotos(); void validateUserPhotos();
void handleUserPhotosUpdate(const UserPhotosSlice &update); void handleUserPhotosUpdate(UserPhotosSlice &&update);
void refreshMediaViewer(); void refreshMediaViewer();
void refreshNavVisibility(); void refreshNavVisibility();

View File

@ -44,11 +44,9 @@ public:
void add(UserPhotosAddSlice &&query); void add(UserPhotosAddSlice &&query);
void remove(UserPhotosRemoveOne &&query); void remove(UserPhotosRemoveOne &&query);
void remove(UserPhotosRemoveAfter &&query); void remove(UserPhotosRemoveAfter &&query);
void query( rpl::producer<UserPhotosResult> query(UserPhotosQuery &&query) const;
UserPhotosQuery &&query,
base::lambda_once<void(UserPhotosResult&&)> &&callback);
base::Observable<UserPhotosSliceUpdate> &userPhotosSliceUpdated(); rpl::producer<UserPhotosSliceUpdate> userPhotosSliceUpdated() const;
private: private:
SharedMedia _sharedMedia; SharedMedia _sharedMedia;
@ -110,14 +108,12 @@ void Facade::Impl::remove(UserPhotosRemoveAfter &&query) {
return _userPhotos.remove(std::move(query)); return _userPhotos.remove(std::move(query));
} }
void Facade::Impl::query( rpl::producer<UserPhotosResult> Facade::Impl::query(UserPhotosQuery &&query) const {
UserPhotosQuery &&query, return _userPhotos.query(std::move(query));
base::lambda_once<void(UserPhotosResult&&)> &&callback) {
return _userPhotos.query(std::move(query), std::move(callback));
} }
base::Observable<UserPhotosSliceUpdate> &Facade::Impl::userPhotosSliceUpdated() { rpl::producer<UserPhotosSliceUpdate> Facade::Impl::userPhotosSliceUpdated() const {
return _userPhotos.sliceUpdated; return _userPhotos.sliceUpdated();
} }
Facade::Facade() : _impl(std::make_unique<Impl>()) { Facade::Facade() : _impl(std::make_unique<Impl>()) {
@ -177,13 +173,11 @@ void Facade::remove(UserPhotosRemoveAfter &&query) {
return _impl->remove(std::move(query)); return _impl->remove(std::move(query));
} }
void Facade::query( rpl::producer<UserPhotosResult> Facade::query(UserPhotosQuery &&query) const {
UserPhotosQuery &&query, return _impl->query(std::move(query));
base::lambda_once<void(UserPhotosResult&&)> &&callback) {
return _impl->query(std::move(query), std::move(callback));
} }
base::Observable<UserPhotosSliceUpdate> &Facade::userPhotosSliceUpdated() { rpl::producer<UserPhotosSliceUpdate> Facade::userPhotosSliceUpdated() const {
return _impl->userPhotosSliceUpdated(); return _impl->userPhotosSliceUpdated();
} }

View File

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once #pragma once
#include "base/enum_mask.h" #include "base/enum_mask.h"
#include "rpl/producer.h"
namespace Storage { namespace Storage {
@ -62,11 +63,9 @@ public:
void add(UserPhotosAddSlice &&query); void add(UserPhotosAddSlice &&query);
void remove(UserPhotosRemoveOne &&query); void remove(UserPhotosRemoveOne &&query);
void remove(UserPhotosRemoveAfter &&query); void remove(UserPhotosRemoveAfter &&query);
void query(
UserPhotosQuery &&query,
base::lambda_once<void(UserPhotosResult&&)> &&callback);
base::Observable<UserPhotosSliceUpdate> &userPhotosSliceUpdated(); rpl::producer<UserPhotosResult> query(UserPhotosQuery &&query) const;
rpl::producer<UserPhotosSliceUpdate> userPhotosSliceUpdated() const;
~Facade(); ~Facade();

View File

@ -81,38 +81,37 @@ void UserPhotos::List::sendUpdate() {
auto update = SliceUpdate(); auto update = SliceUpdate();
update.photoIds = &_photoIds; update.photoIds = &_photoIds;
update.count = _count; update.count = _count;
sliceUpdated.notify(update, true); _sliceUpdated.fire(std::move(update));
} }
void UserPhotos::List::query( rpl::producer<UserPhotosResult> UserPhotos::List::query(
const UserPhotosQuery &query, UserPhotosQuery &&query) const {
base::lambda_once<void(UserPhotosResult&&)> &&callback) { return [this, query = std::move(query)](auto consumer) {
auto result = UserPhotosResult {}; auto result = UserPhotosResult {};
result.count = _count; result.count = _count;
auto position = base::find(_photoIds, query.key.photoId); auto position = base::find(_photoIds, query.key.photoId);
if (position != _photoIds.end()) { if (position != _photoIds.end()) {
auto haveBefore = int(position - _photoIds.begin()); auto haveBefore = int(position - _photoIds.begin());
auto haveEqualOrAfter = int(_photoIds.end() - position); auto haveEqualOrAfter = int(_photoIds.end() - position);
auto before = qMin(haveBefore, query.limitBefore); auto before = qMin(haveBefore, query.limitBefore);
auto equalOrAfter = qMin(haveEqualOrAfter, query.limitAfter + 1); auto equalOrAfter = qMin(haveEqualOrAfter, query.limitAfter + 1);
result.photoIds = std::deque<PhotoId>( result.photoIds = std::deque<PhotoId>(
position - before, position - before,
position + equalOrAfter); position + equalOrAfter);
auto skippedInIds = (haveBefore - before); auto skippedInIds = (haveBefore - before);
result.skippedBefore = _count result.skippedBefore = _count
| func::add(-int(_photoIds.size()) + skippedInIds); | func::add(-int(_photoIds.size()) + skippedInIds);
result.skippedBefore = haveBefore - before; result.skippedBefore = haveBefore - before;
result.skippedAfter = (haveEqualOrAfter - equalOrAfter); result.skippedAfter = (haveEqualOrAfter - equalOrAfter);
} consumer.put_next(std::move(result));
base::TaskQueue::Main().Put( } else if (_count) {
[ consumer.put_next(std::move(result));
callback = std::move(callback), }
result = std::move(result) consumer.put_done();
]() mutable { return rpl::lifetime();
callback(std::move(result)); };
});
} }
std::map<UserId, UserPhotos::List>::iterator std::map<UserId, UserPhotos::List>::iterator
@ -122,12 +121,13 @@ UserPhotos::enforceLists(UserId user) {
return result; return result;
} }
result = _lists.emplace(user, List {}).first; result = _lists.emplace(user, List {}).first;
subscribe(result->second.sliceUpdated, [this, user](const SliceUpdate &update) { result->second.sliceUpdated(
sliceUpdated.notify(UserPhotosSliceUpdate( ) | rpl::on_next([this, user](SliceUpdate &&update) {
_sliceUpdated.fire(UserPhotosSliceUpdate(
user, user,
update.photoIds, update.photoIds,
update.count), true); update.count));
}); }) | rpl::start(_lifetime);
return result; return result;
} }
@ -157,20 +157,15 @@ void UserPhotos::remove(UserPhotosRemoveAfter &&query) {
} }
} }
void UserPhotos::query( rpl::producer<UserPhotosResult> UserPhotos::query(UserPhotosQuery &&query) const {
const UserPhotosQuery &query,
base::lambda_once<void(UserPhotosResult&&)> &&callback) {
auto userIt = _lists.find(query.key.userId); auto userIt = _lists.find(query.key.userId);
if (userIt != _lists.end()) { if (userIt != _lists.end()) {
userIt->second.query(query, std::move(callback)); return userIt->second.query(std::move(query));
} else {
base::TaskQueue::Main().Put(
[
callback = std::move(callback)
]() mutable {
callback(UserPhotosResult());
});
} }
return [](auto consumer) {
consumer.put_done();
return rpl::lifetime();
};
} }
} // namespace Storage } // namespace Storage

View File

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once #pragma once
#include "storage/storage_facade.h" #include "storage/storage_facade.h"
#include "rpl/event_stream.h"
namespace Storage { namespace Storage {
@ -135,17 +136,18 @@ struct UserPhotosSliceUpdate {
base::optional<int> count; base::optional<int> count;
}; };
class UserPhotos : private base::Subscriber { class UserPhotos {
public: public:
void add(UserPhotosAddNew &&query); void add(UserPhotosAddNew &&query);
void add(UserPhotosAddSlice &&query); void add(UserPhotosAddSlice &&query);
void remove(UserPhotosRemoveOne &&query); void remove(UserPhotosRemoveOne &&query);
void remove(UserPhotosRemoveAfter &&query); void remove(UserPhotosRemoveAfter &&query);
void query(
const UserPhotosQuery &query,
base::lambda_once<void(UserPhotosResult&&)> &&callback);
base::Observable<UserPhotosSliceUpdate> sliceUpdated; rpl::producer<UserPhotosResult> query(UserPhotosQuery &&query) const;
rpl::producer<UserPhotosSliceUpdate> sliceUpdated() const {
return _sliceUpdated.events();
}
private: private:
class List { class List {
@ -156,15 +158,15 @@ private:
int count); int count);
void removeOne(PhotoId photoId); void removeOne(PhotoId photoId);
void removeAfter(PhotoId photoId); void removeAfter(PhotoId photoId);
void query( rpl::producer<UserPhotosResult> query(UserPhotosQuery &&query) const;
const UserPhotosQuery &query,
base::lambda_once<void(UserPhotosResult&&)> &&callback);
struct SliceUpdate { struct SliceUpdate {
const std::deque<PhotoId> *photoIds = nullptr; const std::deque<PhotoId> *photoIds = nullptr;
base::optional<int> count; base::optional<int> count;
}; };
base::Observable<SliceUpdate> sliceUpdated; rpl::producer<SliceUpdate> sliceUpdated() const {
return _sliceUpdated.events();
}
private: private:
void sendUpdate(); void sendUpdate();
@ -172,6 +174,8 @@ private:
base::optional<int> _count; base::optional<int> _count;
std::deque<PhotoId> _photoIds; std::deque<PhotoId> _photoIds;
rpl::event_stream<SliceUpdate> _sliceUpdated;
}; };
using SliceUpdate = List::SliceUpdate; using SliceUpdate = List::SliceUpdate;
@ -179,6 +183,9 @@ private:
std::map<UserId, List> _lists; std::map<UserId, List> _lists;
rpl::lifetime _lifetime;
rpl::event_stream<UserPhotosSliceUpdate> _sliceUpdated;
}; };
} // namespace Storage } // namespace Storage