1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-03 09:57:46 +01:00
Telegram/Telegraph/TGChatMessageListSignal.mm
2016-02-25 01:03:51 +01:00

495 lines
21 KiB
Plaintext

#import "TGChatMessageListSignal.h"
#import "TGDatabase.h"
#import "ActionStage.h"
#import "TGTelegraph.h"
#import "TGSharedPtrWrapper.h"
#import "TGPeerIdAdapter.h"
#import "TGConversationReadHistoryActor.h"
@interface TGChatMessageListAdapter : NSObject <ASWatcher>
{
int64_t _peerId;
void (^_viewUpdated)(TGChatMessageListView *);
NSUInteger _rangeMessageCount;
SSignal *_initialSignal;
TGChatMessageListView *_currentView; // no need for any kind of locking, all mutations are guaranteed to run on the ActionStage thread
}
@property (nonatomic, strong) ASHandle *actionHandle;
@end
@implementation TGChatMessageListAdapter
+ (void)sortMessageList:(NSMutableArray *)list
{
[list sortUsingComparator:^NSComparisonResult(TGMessage *message1, TGMessage *message2)
{
NSTimeInterval date1 = message1.date;
NSTimeInterval date2 = message2.date;
if (ABS(date1 - date2) < DBL_EPSILON)
{
if (message1.mid > message2.mid)
return NSOrderedAscending;
else
return NSOrderedDescending;
}
return date1 > date2 ? NSOrderedAscending : NSOrderedDescending;
}];
}
- (NSString *)_conversationIdPathComponent
{
return [[NSString alloc] initWithFormat:@"%" PRId64 "", _peerId];
}
- (instancetype)initWithPeerId:(int64_t)peerId currentView:(TGChatMessageListView *)currentView rangeMessageCount:(NSUInteger)rangeMessageCount initialSignal:(SSignal *)initialSignal viewUpdated:(void (^)(TGChatMessageListView *))viewUpdated
{
self = [super init];
if (self != nil)
{
_peerId = peerId;
_viewUpdated = [viewUpdated copy];
_currentView = currentView;
_rangeMessageCount = rangeMessageCount;
_initialSignal = initialSignal;
_actionHandle = [[ASHandle alloc] initWithDelegate:self];
if (_currentView.isChannelGroup)
{
[ActionStageInstance() watchForPaths:@
[
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/messages", [self _conversationIdPathComponent]],
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/localMessages", [self _conversationIdPathComponent]],
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/unimportantMessages", [self _conversationIdPathComponent]],
@"/as/updateRelativeTimestamps",
@"/tg/conversation/*/readmessageContents"
] watcher:self];
}
else if (TGPeerIdIsChannel(peerId))
{
[ActionStageInstance() watchForPaths:@
[
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/messages", [self _conversationIdPathComponent]],
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/localMessages", [self _conversationIdPathComponent]],
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/importantMessages", [self _conversationIdPathComponent]],
@"/as/updateRelativeTimestamps",
@"/tg/conversation/*/readmessageContents"
] watcher:self];
}
else
{
[ActionStageInstance() watchForPaths:@
[
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/messages", [self _conversationIdPathComponent]],
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/localMessages", [self _conversationIdPathComponent]],
@"/tg/conversation/*/readmessages",
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/readmessages", [self _conversationIdPathComponent]],
@"/tg/conversation/*/failmessages",
[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/messagesDeleted", [self _conversationIdPathComponent]],
[NSString stringWithFormat:@"/tg/conversation/(%lld)/messagesChanged", _peerId],
@"/as/updateRelativeTimestamps",
@"/tg/conversation/historyCleared",
@"/tg/conversation/*/readmessageContents"
] watcher:self];
}
}
return self;
}
- (void)dealloc
{
[_actionHandle reset];
}
- (void)actionStageResourceDispatched:(NSString *)path resource:(id)resource arguments:(id)__unused arguments
{
NSString *conversationId = [self _conversationIdPathComponent];
if ([path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/messages", conversationId]]
|| [path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/localMessages", conversationId]])
{
if (_currentView.laterReferenceMessageId != nil)
return;
NSArray *messages = ((SGraphObjectNode *)resource).object;
NSMutableSet *currentMessageIds = [[NSMutableSet alloc] init];
for (TGMessage *message in _currentView.messages)
[currentMessageIds addObject:@(message.mid)];
NSMutableArray *updatedMessages = [[NSMutableArray alloc] initWithArray:_currentView.messages];
for (TGMessage *message in messages)
{
if (![currentMessageIds containsObject:@(message.mid)])
{
[currentMessageIds addObject:@(message.mid)];
bool added = false;
for (NSUInteger i = 0; i < updatedMessages.count; i++)
{
TGMessage *listMessage = updatedMessages[i];
if (listMessage.date < message.date || (ABS(listMessage.date - message.date) < FLT_EPSILON && listMessage.mid < message.mid))
{
[updatedMessages insertObject:message atIndex:i];
added = true;
break;
}
}
if (!added)
[updatedMessages addObject:message];
}
}
while (updatedMessages.count > _rangeMessageCount)
[updatedMessages removeLastObject];
TGChatMessageListView *updatedView = [[TGChatMessageListView alloc] initWithMessages:updatedMessages earlierReferenceMessageId:nil laterReferenceMessageId:nil];
updatedView.rangeCount = _currentView.rangeCount;
updatedView.maybeHasMessagesOnTop = _currentView.maybeHasMessagesOnTop;
updatedView.isChannel = _currentView.isChannel;
updatedView.isChannelGroup = _currentView.isChannelGroup;
_currentView = updatedView;
if (_viewUpdated)
_viewUpdated(updatedView);
}
else if ([path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/unimportantMessages", [self _conversationIdPathComponent]]])
{
//if (((NSArray *)resource[@"removed"]).count != 0)
// [self _deleteMessages:resource[@"removed"] animated:true];
if (((NSArray *)resource[@"added"]).count != 0)
{
[self actionStageResourceDispatched:[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/messages", conversationId] resource:[[SGraphObjectNode alloc] initWithObject:resource[@"added"]] arguments:@{@"treatIncomingAsUnread": @true}];
}
//if (((NSDictionary *)resource[@"updated"]).count != 0)
// [self _updateMessages:resource[@"updated"]];
}
else if ([path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/messagesDeleted", [self _conversationIdPathComponent]]])
{
NSMutableSet *deletedMessageIds = [[NSMutableSet alloc] init];
for (NSNumber *nMessageId in ((SGraphObjectNode *)resource).object)
[deletedMessageIds addObject:nMessageId];
NSMutableArray *updatedMessages = [[NSMutableArray alloc] initWithArray:_currentView.messages];
for (NSInteger i = 0; i < (NSInteger)updatedMessages.count; i++)
{
TGMessage *message = updatedMessages[i];
if ([deletedMessageIds containsObject:@(message.mid)])
{
[updatedMessages removeObjectAtIndex:i];
i--;
}
}
if (updatedMessages.count < _rangeMessageCount && _currentView.maybeHasMessagesOnTop)
{
[_initialSignal startWithNext:^(TGChatMessageListView *view)
{
_currentView = view;
if (_viewUpdated)
_viewUpdated(view);
}];
}
else
{
TGChatMessageListView *updatedView = [[TGChatMessageListView alloc] initWithMessages:updatedMessages earlierReferenceMessageId:nil laterReferenceMessageId:nil];
updatedView.rangeCount = _currentView.rangeCount;
updatedView.maybeHasMessagesOnTop = _currentView.maybeHasMessagesOnTop;
updatedView.isChannel = _currentView.isChannel;
updatedView.isChannelGroup = _currentView.isChannelGroup;
_currentView = updatedView;
if (_viewUpdated)
_viewUpdated(updatedView);
}
}
else if ([path isEqualToString:[NSString stringWithFormat:@"/tg/conversation/(%lld)/messagesChanged", _peerId]])
{
NSMutableSet *currentMessageIds = [[NSMutableSet alloc] init];
for (TGMessage *message in _currentView.messages)
[currentMessageIds addObject:@(message.mid)];
NSArray *midMessagePairs = ((SGraphObjectNode *)resource).object;
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
for (NSUInteger i = 0; i < midMessagePairs.count; i += 2)
dict[midMessagePairs[i]] = midMessagePairs[i + 1];
NSMutableArray *updatedMessages = nil;
for (NSUInteger i = 0 ; i < _currentView.messages.count; i++)
{
TGMessage *previousMessage = _currentView.messages[i];
TGMessage *updatedMessage = dict[@(previousMessage.mid)];
if (updatedMessage != nil)
{
if (![currentMessageIds containsObject:@(updatedMessage.mid)])
{
[currentMessageIds addObject:@(updatedMessage.mid)];
updatedMessage.date = previousMessage.date;
if (updatedMessages == nil)
updatedMessages = [[NSMutableArray alloc] initWithArray:_currentView.messages];
updatedMessages[i] = updatedMessage;
}
}
}
if (updatedMessages != nil)
{
NSNumber *earlierReferenceMessageId = nil;
NSNumber *laterReferenceMessageId = nil;
for (NSUInteger i = 0; i < updatedMessages.count; i++)
{
if (i >= _rangeMessageCount / 5)
{
TGMessage *bottomMessage = updatedMessages[i];
TGMessage *topMessage = updatedMessages[updatedMessages.count - i - 1];
earlierReferenceMessageId = @(topMessage.mid);
laterReferenceMessageId = @(bottomMessage.mid);
break;
}
}
TGChatMessageListView *updatedView = [[TGChatMessageListView alloc] initWithMessages:updatedMessages earlierReferenceMessageId:earlierReferenceMessageId laterReferenceMessageId:_currentView.laterReferenceMessageId == nil ? nil : laterReferenceMessageId];
updatedView.rangeCount = _currentView.rangeCount;
updatedView.maybeHasMessagesOnTop = _currentView.maybeHasMessagesOnTop;
updatedView.isChannel = _currentView.isChannel;
updatedView.isChannelGroup = _currentView.isChannelGroup;
_currentView = updatedView;
if (_viewUpdated)
_viewUpdated(updatedView);
_currentView = updatedView;
}
}
else if ([path isEqualToString:@"/tg/conversation/*/readmessages"])
{
TGSharedPtrWrapper *ptrWrapper = ((SGraphObjectNode *)resource).object;
if (ptrWrapper == nil)
return;
std::tr1::shared_ptr<std::set<int> > mids = std::tr1::static_pointer_cast<std::set<int> >([ptrWrapper ptr]);
if (mids != NULL)
{
NSMutableSet *messageIds = [[NSMutableSet alloc] init];
for (int mid : *(mids.get()))
[messageIds addObject:@(mid)];
NSMutableArray *updatedMessages = nil;
for (NSUInteger i = 0; i < _currentView.messages.count; i++)
{
TGMessage *previousMessage = _currentView.messages[i];
if ([messageIds containsObject:@(i)])
{
TGMessage *updatedMessage = [previousMessage copy];
updatedMessage.unread = false;
if (updatedMessages == nil)
updatedMessages = [[NSMutableArray alloc] init];
updatedMessages[i] = updatedMessage;
}
}
if (updatedMessages != nil)
{
TGChatMessageListView *updatedView = [[TGChatMessageListView alloc] initWithMessages:updatedMessages earlierReferenceMessageId:_currentView.earlierReferenceMessageId laterReferenceMessageId:_currentView.laterReferenceMessageId];
updatedView.rangeCount = _currentView.rangeCount;
updatedView.maybeHasMessagesOnTop = _currentView.maybeHasMessagesOnTop;
updatedView.isChannel = _currentView.isChannel;
updatedView.isChannelGroup = _currentView.isChannelGroup;
_currentView = updatedView;
if (_viewUpdated)
_viewUpdated(updatedView);
_currentView = updatedView;
}
}
}
else if ([path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/conversation/(%@)/readmessages", [self _conversationIdPathComponent]]])
{
bool isOutbox = [resource[@"outbox"] boolValue];
int32_t maxMessageId = [resource[@"maxMessageId"] intValue];
NSMutableArray *updatedMessages = nil;
NSInteger index = -1;
for (TGMessage *message in _currentView.messages)
{
index++;
if (message.outgoing == isOutbox && message.mid <= maxMessageId && message.unread)
{
if (updatedMessages == nil)
updatedMessages = [[NSMutableArray alloc] initWithArray:_currentView.messages];
TGMessage *updatedMessage = [message copy];
updatedMessage.unread = false;
updatedMessages[index] = updatedMessage;
}
}
if (updatedMessages != nil)
{
TGChatMessageListView *updatedView = [[TGChatMessageListView alloc] initWithMessages:updatedMessages earlierReferenceMessageId:_currentView.earlierReferenceMessageId laterReferenceMessageId:_currentView.laterReferenceMessageId];
updatedView.rangeCount = _currentView.rangeCount;
updatedView.maybeHasMessagesOnTop = _currentView.maybeHasMessagesOnTop;
updatedView.isChannel = _currentView.isChannel;
updatedView.isChannelGroup = _currentView.isChannelGroup;
_currentView = updatedView;
if (_viewUpdated)
_viewUpdated(updatedView);
_currentView = updatedView;
}
}
}
@end
@implementation TGChatMessageListSignal
+ (SSignal *)chatMessageListViewWithPeerId:(int64_t)peerId atMessageId:(int32_t)messageId rangeMessageCount:(NSUInteger)rangeMessageCount
{
NSUInteger expandedRange = (int)floor(rangeMessageCount * 1.5f);
SSignal *chatInitialSignal = [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
__block NSArray *topMessages = nil;
__block bool blockIsAtBottom = true;
[TGDatabaseInstance() dispatchOnDatabaseThread:^
{
[TGDatabaseInstance() loadMessagesFromConversation:peerId maxMid:INT_MAX maxDate:INT_MAX maxLocalMid:INT_MAX atMessageId:messageId limit:(int)expandedRange extraUnread:false completion:^(NSArray *messages, bool historyExistsBelow)
{
topMessages = messages;
blockIsAtBottom = !historyExistsBelow;
}];
int minRemoteMid = INT_MAX;
int maxRemoteMid = INT_MIN;
for (TGMessage *message in topMessages)
{
if (message.mid < TGMessageLocalMidBaseline)
{
minRemoteMid = MIN(message.mid, minRemoteMid);
maxRemoteMid = MAX(message.mid, maxRemoteMid);
}
}
if (minRemoteMid <= maxRemoteMid)
topMessages = [TGDatabaseInstance() excludeMessagesWithHolesFromArray:topMessages peerId:peerId aroundMessageId:0];
} synchronous:true];
NSMutableArray *sortedTopMessages = [[NSMutableArray alloc] initWithArray:topMessages];
[TGChatMessageListAdapter sortMessageList:sortedTopMessages];
while (sortedTopMessages.count > expandedRange)
[sortedTopMessages removeLastObject];
TGChatMessageListView *listView = [[TGChatMessageListView alloc] initWithMessages:sortedTopMessages earlierReferenceMessageId:nil laterReferenceMessageId:nil];
listView.rangeCount = rangeMessageCount;
listView.maybeHasMessagesOnTop = (sortedTopMessages.count > rangeMessageCount);
[subscriber putNext:listView];
[subscriber putCompletion];
return nil;
}];
SSignal *(^channelInitialSignal)(int64_t, bool) = ^SSignal *(int64_t peerId, bool isChannelGroup)
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
__block NSMutableArray *topMessages;
[TGDatabaseInstance() dispatchOnDatabaseThread:^
{
TGMessageTransparentSortKey maxSortKey = TGMessageTransparentSortKeyUpperBound(peerId);
[TGDatabaseInstance() channelMessages:peerId maxTransparentSortKey:maxSortKey count:expandedRange important:!isChannelGroup mode:TGChannelHistoryRequestAround completion:^(NSArray *messages, __unused bool hasLater)
{
NSMutableArray *filteredMessages = [[NSMutableArray alloc] init];
for (TGMessage *message in messages)
{
if (isChannelGroup || ( message.mid >= 0 && message.fromUid == peerId))
[filteredMessages addObject:message];
}
topMessages = filteredMessages;
}];
} synchronous:true];
while (topMessages.count > expandedRange)
[topMessages removeLastObject];
TGChatMessageListView *listView = [[TGChatMessageListView alloc] initWithMessages:topMessages earlierReferenceMessageId:nil laterReferenceMessageId:nil];
listView.rangeCount = rangeMessageCount;
listView.maybeHasMessagesOnTop = (topMessages.count > rangeMessageCount);
listView.isChannel = true;
listView.isChannelGroup = isChannelGroup;
[subscriber putNext:listView];
[subscriber putCompletion];
return nil;
}];
};
SSignal *initialSignal = nil;
if (TGPeerIdIsChannel(peerId))
{
initialSignal = [[[TGDatabaseInstance() existingChannel:peerId] take:1] mapToSignal:^SSignal *(TGConversation *channel)
{
return channelInitialSignal(peerId, channel.isChannelGroup);
}];
}
else
{
initialSignal = chatInitialSignal;
}
return [initialSignal mapToSignal:^SSignal *(TGChatMessageListView *initialView)
{
SSignal *updatedView = [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
TGChatMessageListAdapter *adapter = [[TGChatMessageListAdapter alloc] initWithPeerId:peerId currentView:initialView rangeMessageCount:rangeMessageCount initialSignal:initialSignal viewUpdated:^(TGChatMessageListView *view)
{
[subscriber putNext:view];
}];
return [[SBlockDisposable alloc] initWithBlock:^
{
[adapter description];
}];
}];
return [[SSignal single:initialView] then:updatedView];
}];
}
+ (SSignal *)readChatMessageListWithPeerId:(int64_t)peerId
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
[TGConversationReadHistoryActor executeStandalone:peerId];
[subscriber putCompletion];
return nil;
}];
}
@end