Replace self-destruct media service messages text.

Also support runtime components with align up to std::max_align_t.
This commit is contained in:
John Preston 2017-07-17 23:09:55 +03:00
parent 2e0513a30f
commit 9bd89121e8
7 changed files with 211 additions and 53 deletions

View File

@ -761,6 +761,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_action_took_screenshot" = "{from} took a screenshot!";
"lng_action_you_took_screenshot" = "You took a screenshot!";
"lng_ttl_photo_received" = "{from} sent you a self-destructing photo. Please view it on your mobile.";
"lng_ttl_photo_sent" = "You sent a self-destructing photo.";
"lng_ttl_photo_expired" = "Photo has expired";
"lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile.";
"lng_ttl_video_sent" = "You sent a self-destructing video.";
"lng_ttl_video_expired" = "Video has expired";
"lng_profile_migrate_reached#one" = "{count} member limit reached";
"lng_profile_migrate_reached#other" = "{count} members limit reached";
"lng_profile_migrate_body" = "To get over this limit, you can upgrade your group to a supergroup.";

View File

@ -26,8 +26,8 @@ typedef void(*RuntimeComponentDestruct)(void *location);
typedef void(*RuntimeComponentMove)(void *location, void *waslocation);
struct RuntimeComponentWrapStruct {
// don't init any fields, because it is only created in
// global scope, so it will be filled by zeros from the start
// Don't init any fields, because it is only created in
// global scope, so it will be filled by zeros from the start.
RuntimeComponentWrapStruct() = default;
RuntimeComponentWrapStruct(std::size_t size, std::size_t align, RuntimeComponentConstruct construct, RuntimeComponentDestruct destruct, RuntimeComponentMove move)
: Size(size)
@ -54,7 +54,8 @@ extern QAtomicInt RuntimeComponentIndexLast;
template <typename Type>
struct RuntimeComponent {
RuntimeComponent() {
static_assert(alignof(Type) <= alignof(SmallestSizeType), "Components should align to a pointer!");
// While there is no std::aligned_alloc().
static_assert(alignof(Type) <= alignof(std::max_align_t), "Components should align to std::max_align_t!");
}
RuntimeComponent(const RuntimeComponent &other) = delete;
RuntimeComponent &operator=(const RuntimeComponent &other) = delete;
@ -62,17 +63,17 @@ struct RuntimeComponent {
RuntimeComponent &operator=(RuntimeComponent &&other) = default;
static int Index() {
static QAtomicInt _index(0);
if (int index = _index.loadAcquire()) {
static QAtomicInt MyIndex(0);
if (auto index = MyIndex.loadAcquire()) {
return index - 1;
}
while (true) {
int last = RuntimeComponentIndexLast.loadAcquire();
auto last = RuntimeComponentIndexLast.loadAcquire();
if (RuntimeComponentIndexLast.testAndSetOrdered(last, last + 1)) {
t_assert(last < 64);
if (_index.testAndSetOrdered(0, last + 1)) {
if (MyIndex.testAndSetOrdered(0, last + 1)) {
RuntimeComponentWraps[last] = RuntimeComponentWrapStruct(
CeilDivideMinimumOne<sizeof(Type), sizeof(SmallestSizeType)>::Result * sizeof(SmallestSizeType),
sizeof(Type),
alignof(Type),
Type::RuntimeComponentConstruct,
Type::RuntimeComponentDestruct,
@ -81,15 +82,13 @@ struct RuntimeComponent {
break;
}
}
return _index.loadAcquire() - 1;
return MyIndex.loadAcquire() - 1;
}
static uint64 Bit() {
return (1ULL << Index());
}
protected:
using SmallestSizeType = void*;
static void RuntimeComponentConstruct(void *location, RuntimeComposer *composer) {
new (location) Type();
}
@ -104,30 +103,32 @@ protected:
class RuntimeComposerMetadata {
public:
RuntimeComposerMetadata(uint64 mask) : size(0), last(64), _mask(mask) {
for (int i = 0; i < 64; ++i) {
uint64 m = (1ULL << i);
if (_mask & m) {
int s = RuntimeComponentWraps[i].Size;
if (s) {
RuntimeComposerMetadata(uint64 mask) : _mask(mask) {
for (int i = 0; i != 64; ++i) {
auto componentBit = (1ULL << i);
if (_mask & componentBit) {
auto componentSize = RuntimeComponentWraps[i].Size;
if (componentSize) {
auto componentAlign = RuntimeComponentWraps[i].Align;
if (auto badAlign = (size % componentAlign)) {
size += (componentAlign - badAlign);
}
offsets[i] = size;
size += s;
} else {
offsets[i] = -1;
size += componentSize;
accumulate_max(align, componentAlign);
}
} else if (_mask < m) {
} else if (_mask < componentBit) {
last = i;
for (; i < 64; ++i) {
offsets[i] = -1;
}
} else {
offsets[i] = -1;
break;
}
}
}
int size, last;
int offsets[64];
// Meta pointer in the start.
std::size_t size = sizeof(const RuntimeComposerMetadata*);
std::size_t align = alignof(const RuntimeComposerMetadata*);
std::size_t offsets[64] = { 0 };
int last = 64;
bool equals(uint64 mask) const {
return _mask == mask;
@ -150,28 +151,28 @@ class RuntimeComposer {
public:
RuntimeComposer(uint64 mask = 0) : _data(zerodata()) {
if (mask) {
const RuntimeComposerMetadata *meta = GetRuntimeComposerMetadata(mask);
int size = sizeof(meta) + meta->size;
auto meta = GetRuntimeComposerMetadata(mask);
auto data = operator new(size);
auto data = operator new(meta->size);
t_assert(data != nullptr);
_data = data;
_meta() = meta;
for (int i = 0; i < meta->last; ++i) {
int offset = meta->offsets[i];
if (offset >= 0) {
auto offset = meta->offsets[i];
if (offset >= sizeof(_meta())) {
try {
auto constructAt = _dataptrunsafe(offset);
auto space = RuntimeComponentWraps[i].Size;
auto alignedAt = std::align(RuntimeComponentWraps[i].Align, space, constructAt, space);
auto alignedAt = constructAt;
std::align(RuntimeComponentWraps[i].Align, space, alignedAt, space);
t_assert(alignedAt == constructAt);
RuntimeComponentWraps[i].Construct(constructAt, this);
} catch (...) {
while (i > 0) {
--i;
offset = meta->offsets[--i];
if (offset >= 0) {
if (offset >= sizeof(_meta())) {
RuntimeComponentWraps[i].Destruct(_dataptrunsafe(offset));
}
}
@ -187,8 +188,8 @@ public:
if (_data != zerodata()) {
auto meta = _meta();
for (int i = 0; i < meta->last; ++i) {
int offset = meta->offsets[i];
if (offset >= 0) {
auto offset = meta->offsets[i];
if (offset >= sizeof(_meta())) {
RuntimeComponentWraps[i].Destruct(_dataptrunsafe(offset));
}
}
@ -198,7 +199,7 @@ public:
template <typename Type>
bool Has() const {
return (_meta()->offsets[Type::Index()] >= 0);
return (_meta()->offsets[Type::Index()] >= sizeof(_meta()));
}
template <typename Type>
@ -218,8 +219,9 @@ protected:
if (_data != zerodata() && tmp._data != zerodata()) {
auto meta = _meta(), wasmeta = tmp._meta();
for (int i = 0; i < meta->last; ++i) {
int offset = meta->offsets[i], wasoffset = wasmeta->offsets[i];
if (offset >= 0 && wasoffset >= 0) {
auto offset = meta->offsets[i];
auto wasoffset = wasmeta->offsets[i];
if (offset >= sizeof(_meta()) && wasoffset >= sizeof(_meta())) {
RuntimeComponentWraps[i].Move(_dataptrunsafe(offset), tmp._dataptrunsafe(wasoffset));
}
}
@ -240,15 +242,15 @@ private:
}
void *_dataptrunsafe(int skip) const {
return (char*)_data + sizeof(_meta()) + skip;
return (char*)_data + skip;
}
void *_dataptr(int skip) const {
return (skip >= 0) ? _dataptrunsafe(skip) : 0;
return (skip >= sizeof(_meta())) ? _dataptrunsafe(skip) : nullptr;
}
const RuntimeComposerMetadata *&_meta() const {
return *static_cast<const RuntimeComposerMetadata**>(_data);
}
void *_data;
void *_data = nullptr;
void swap(RuntimeComposer &other) {
std::swap(_data, other._data);

View File

@ -764,6 +764,37 @@ void Histories::savePinnedToServer() const {
MTP::send(MTPmessages_ReorderPinnedDialogs(MTP_flags(flags), MTP_vector(peers)));
}
void Histories::selfDestructIn(gsl::not_null<HistoryItem*> item, TimeMs delay) {
_selfDestructItems.push_back(item->fullId());
if (!_selfDestructTimer.isActive() || _selfDestructTimer.remainingTime() > delay) {
_selfDestructTimer.callOnce(delay);
}
}
void Histories::checkSelfDestructItems() {
auto now = getms(true);
auto nextDestructIn = TimeMs(0);
for (auto i = _selfDestructItems.begin(); i != _selfDestructItems.cend();) {
if (auto item = App::histItemById(*i)) {
if (auto destructIn = item->getSelfDestructIn(now)) {
if (nextDestructIn > 0) {
accumulate_min(nextDestructIn, destructIn);
} else {
nextDestructIn = destructIn;
}
++i;
} else {
i = _selfDestructItems.erase(i);
}
} else {
i = _selfDestructItems.erase(i);
}
}
if (nextDestructIn > 0) {
_selfDestructTimer.callOnce(nextDestructIn);
}
}
HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) {
auto msgId = MsgId(0);
switch (msg.type()) {
@ -799,7 +830,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
Good,
Unsupported,
Empty,
HasTTL,
HasTimeToLive,
};
auto badMedia = MediaCheckResult::Good;
if (m.has_media()) switch (m.vmedia.type()) {
@ -822,7 +853,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
case mtpc_messageMediaPhoto: {
auto &photo = m.vmedia.c_messageMediaPhoto();
if (photo.has_ttl_seconds()) {
badMedia = MediaCheckResult::HasTTL;
badMedia = MediaCheckResult::HasTimeToLive;
} else if (!photo.has_photo()) {
badMedia = MediaCheckResult::Empty;
} else {
@ -836,7 +867,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
case mtpc_messageMediaDocument: {
auto &document = m.vmedia.c_messageMediaDocument();
if (document.has_ttl_seconds()) {
badMedia = MediaCheckResult::HasTTL;
badMedia = MediaCheckResult::HasTimeToLive;
} else if (!document.has_document()) {
badMedia = MediaCheckResult::Empty;
} else {
@ -872,9 +903,8 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
} else if (badMedia == MediaCheckResult::Empty) {
auto message = HistoryService::PreparedText { lang(lng_message_empty) };
result = HistoryService::create(this, m.vid.v, date(m.vdate), message, m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0);
} else if (badMedia == MediaCheckResult::HasTTL) {
auto message = HistoryService::PreparedText { qsl("Self-destruct media, see mobile") };
result = HistoryService::create(this, m.vid.v, date(m.vdate), message, m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0);
} else if (badMedia == MediaCheckResult::HasTimeToLive) {
result = HistoryService::create(this, m);
} else {
result = HistoryMessage::create(this, m);
}

View File

@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "dialogs/dialogs_common.h"
#include "ui/effects/send_action_animations.h"
#include "base/observer.h"
#include "base/timer.h"
void HistoryInit();
@ -43,6 +44,7 @@ public:
Map map;
Histories() : _a_typings(animation(this, &Histories::step_typings)) {
_selfDestructTimer.setCallback([this] { checkSelfDestructItems(); });
}
void regSendAction(History *history, UserData *user, const MTPSendMessageAction &action, TimeId when);
@ -95,13 +97,19 @@ public:
base::Observable<SendActionAnimationUpdate> &sendActionAnimationUpdated() {
return _sendActionAnimationUpdated;
}
void selfDestructIn(gsl::not_null<HistoryItem*> item, TimeMs delay);
private:
void checkSelfDestructItems();
int _unreadFull = 0;
int _unreadMuted = 0;
base::Observable<SendActionAnimationUpdate> _sendActionAnimationUpdated;
OrderedSet<History*> _pinnedDialogs;
base::Timer _selfDestructTimer;
std::vector<FullMsgId> _selfDestructItems;
};
class HistoryBlock;

View File

@ -579,7 +579,14 @@ public:
}
void markMediaRead() {
_flags &= ~MTPDmessage::Flag::f_media_unread;
markMediaAsReadHook();
}
// Zero result means this message is not self-destructing right now.
virtual TimeMs getSelfDestructIn(TimeMs now) {
return 0;
}
bool definesReplyKeyboard() const {
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
@ -918,13 +925,16 @@ public:
protected:
HistoryItem(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime msgDate, int32 from);
// to completely create history item we need to call
// a virtual method, it can not be done from constructor
// To completely create history item we need to call
// a virtual method, it can not be done from constructor.
virtual void finishCreate();
// called from resizeGetHeight() when MTPDmessage_ClientFlag::f_pending_init_dimensions is set
// Called from resizeGetHeight() when MTPDmessage_ClientFlag::f_pending_init_dimensions is set.
virtual void initDimensions() = 0;
virtual void markMediaAsReadHook() {
}
virtual int resizeContentGetHeight() = 0;
void finishEdition(int oldKeyboardTop);

View File

@ -220,6 +220,13 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
}
}
void HistoryService::setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds) {
UpdateComponents(HistoryServiceSelfDestruct::Bit());
auto selfdestruct = Get<HistoryServiceSelfDestruct>();
selfdestruct->timeToLive = ttlSeconds * 1000LL;
selfdestruct->type = type;
}
bool HistoryService::updateDependent(bool force) {
auto dependent = GetDependentData();
t_assert(dependent != nullptr);
@ -393,10 +400,14 @@ HistoryService::PreparedText HistoryService::preparePaymentSentText() {
return result;
}
HistoryService::HistoryService(gsl::not_null<History*> history, const MTPDmessage &message) :
HistoryItem(history, message.vid.v, message.vflags.v, ::date(message.vdate), message.has_from_id() ? message.vfrom_id.v : 0) {
createFromMtp(message);
}
HistoryService::HistoryService(gsl::not_null<History*> history, const MTPDmessageService &message) :
HistoryItem(history, message.vid.v, mtpCastFlags(message.vflags.v), ::date(message.vdate), message.has_from_id() ? message.vfrom_id.v : 0) {
createFromMtp(message);
setMessageByAction(message.vaction);
}
HistoryService::HistoryService(gsl::not_null<History*> history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags, int32 from, PhotoData *photo) :
@ -521,6 +532,35 @@ int HistoryService::resizeContentGetHeight() {
return _height;
}
void HistoryService::markMediaAsReadHook() {
if (auto selfdestruct = Get<HistoryServiceSelfDestruct>()) {
if (!selfdestruct->destructAt) {
selfdestruct->destructAt = getms(true) + selfdestruct->timeToLive;
App::histories().selfDestructIn(this, selfdestruct->timeToLive);
}
}
}
TimeMs HistoryService::getSelfDestructIn(TimeMs now) {
if (auto selfdestruct = Get<HistoryServiceSelfDestruct>()) {
if (selfdestruct->destructAt > 0) {
if (selfdestruct->destructAt <= now) {
auto text = [selfdestruct] {
switch (selfdestruct->type) {
case HistoryServiceSelfDestruct::Type::Photo: return lang(lng_ttl_photo_expired);
case HistoryServiceSelfDestruct::Type::Video: return lang(lng_ttl_video_expired);
}
Unexpected("Type in HistoryServiceSelfDestruct::Type");
};
setServiceText({ text() });
return 0;
}
return selfdestruct->destructAt - now;
}
}
return 0;
}
bool HistoryService::hasPoint(QPoint point) const {
auto g = countGeometry();
if (g.width() < 1) {
@ -580,6 +620,48 @@ HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest requ
return result;
}
void HistoryService::createFromMtp(const MTPDmessage &message) {
auto mediaType = message.vmedia.type();
switch (mediaType) {
case mtpc_messageMediaPhoto: {
if (message.is_media_unread()) {
auto &photo = message.vmedia.c_messageMediaPhoto();
t_assert(photo.has_ttl_seconds());
setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, photo.vttl_seconds.v);
if (out()) {
setServiceText({ lang(lng_ttl_photo_sent) });
} else {
auto result = PreparedText();
result.links.push_back(fromLink());
result.text = lng_ttl_photo_received(lt_from, fromLinkText());
setServiceText(std::move(result));
}
} else {
setServiceText({ lang(lng_ttl_photo_expired) });
}
} break;
case mtpc_messageMediaDocument: {
if (message.is_media_unread()) {
auto &document = message.vmedia.c_messageMediaDocument();
t_assert(document.has_ttl_seconds());
setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, document.vttl_seconds.v);
if (out()) {
setServiceText({ lang(lng_ttl_video_sent) });
} else {
auto result = PreparedText();
result.links.push_back(fromLink());
result.text = lng_ttl_video_received(lt_from, fromLinkText());
setServiceText(std::move(result));
}
} else {
setServiceText({ lang(lng_ttl_video_expired) });
}
} break;
default: Unexpected("Media type in HistoryService::createFromMtp()");
}
}
void HistoryService::createFromMtp(const MTPDmessageService &message) {
if (message.vaction.type() == mtpc_messageActionGameScore) {
UpdateComponents(HistoryServiceGameScore::Bit());

View File

@ -37,6 +37,16 @@ struct HistoryServicePayment : public RuntimeComponent<HistoryServicePayment>, p
QString amount;
};
struct HistoryServiceSelfDestruct : public RuntimeComponent<HistoryServiceSelfDestruct> {
enum class Type {
Photo,
Video,
};
Type type = Type::Photo;
TimeMs timeToLive = 0;
TimeMs destructAt = 0;
};
namespace HistoryLayout {
class ServiceMessagePainter;
} // namespace HistoryLayout
@ -48,6 +58,9 @@ public:
QList<ClickHandlerPtr> links;
};
static gsl::not_null<HistoryService*> create(gsl::not_null<History*> history, const MTPDmessage &message) {
return _create(history, message);
}
static gsl::not_null<HistoryService*> create(gsl::not_null<History*> history, const MTPDmessageService &message) {
return _create(history, message);
}
@ -83,6 +96,7 @@ public:
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
void applyEdition(const MTPDmessageService &message) override;
TimeMs getSelfDestructIn(TimeMs now) override;
int32 addToOverview(AddToOverviewMethod method) override;
void eraseFromOverview() override;
@ -102,6 +116,7 @@ public:
protected:
friend class HistoryLayout::ServiceMessagePainter;
HistoryService(gsl::not_null<History*> history, const MTPDmessage &message);
HistoryService(gsl::not_null<History*> history, const MTPDmessageService &message);
HistoryService(gsl::not_null<History*> history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = 0);
friend class HistoryItemInstantiated<HistoryService>;
@ -109,6 +124,8 @@ protected:
void initDimensions() override;
int resizeContentGetHeight() override;
void markMediaAsReadHook() override;
void setServiceText(const PreparedText &prepared);
QString fromLinkText() const {
@ -138,8 +155,10 @@ private:
void updateDependentText();
void clearDependency();
void createFromMtp(const MTPDmessage &message);
void createFromMtp(const MTPDmessageService &message);
void setMessageByAction(const MTPmessageAction &action);
void setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds);
PreparedText preparePinnedText();
PreparedText prepareGameScoreText();