qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
sessionchatlog.cpp
Go to the documentation of this file.
1 /*
2  Copyright © 2019 by The qTox Project Contributors
3 
4  This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6  qTox is libre software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  qTox is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "sessionchatlog.h"
21 #include "src/friendlist.h"
22 #include "src/grouplist.h"
23 
24 #include <QDebug>
25 #include <QtGlobal>
26 #include <mutex>
27 
28 namespace {
29 
34 struct MessageDateAdaptor
35 {
36  static const QDateTime invalidDateTime;
37  MessageDateAdaptor(const std::pair<const ChatLogIdx, ChatLogItem>& item)
38  : timestamp(item.second.getContentType() == ChatLogItem::ContentType::message
39  ? item.second.getContentAsMessage().message.timestamp
40  : invalidDateTime)
41  {}
42 
43  MessageDateAdaptor(const QDateTime& timestamp)
44  : timestamp(timestamp)
45  {}
46 
47  const QDateTime& timestamp;
48 };
49 
50 const QDateTime MessageDateAdaptor::invalidDateTime;
51 
57 QRegularExpression getRegexpForPhrase(const QString& phrase, FilterSearch filter)
58 {
59  constexpr auto regexFlags = QRegularExpression::UseUnicodePropertiesOption;
60  constexpr auto caseInsensitiveFlags = QRegularExpression::CaseInsensitiveOption;
61 
62  switch (filter) {
64  return QRegularExpression(QRegularExpression::escape(phrase), regexFlags);
66  return QRegularExpression(SearchExtraFunctions::generateFilterWordsOnly(phrase),
67  caseInsensitiveFlags);
69  return QRegularExpression(SearchExtraFunctions::generateFilterWordsOnly(phrase), regexFlags);
71  return QRegularExpression(phrase, regexFlags);
73  return QRegularExpression(phrase, caseInsensitiveFlags);
74  default:
75  return QRegularExpression(QRegularExpression::escape(phrase), caseInsensitiveFlags);
76  }
77 }
78 
82 bool toxFileIsComplete(ToxFile::FileStatus status)
83 {
84  switch (status) {
86  case ToxFile::PAUSED:
88  return false;
89  case ToxFile::BROKEN:
90  case ToxFile::CANCELED:
91  case ToxFile::FINISHED:
92  default:
93  return true;
94  }
95 }
96 
97 std::map<ChatLogIdx, ChatLogItem>::const_iterator
98 firstItemAfterDate(QDate date, const std::map<ChatLogIdx, ChatLogItem>& items)
99 {
100 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
101  return std::lower_bound(items.begin(), items.end(), QDateTime(date.startOfDay()),
102 #else
103  return std::lower_bound(items.begin(), items.end(), QDateTime(date),
104 #endif
105  [](const MessageDateAdaptor& a, MessageDateAdaptor const& b) {
106  return a.timestamp.date() < b.timestamp.date();
107  });
108 }
109 
110 QString resolveToxPk(const ToxPk& pk)
111 {
113  if (f) {
114  return f->getDisplayedName();
115  }
116 
117  for (Group* it : GroupList::getAllGroups()) {
118  QString res = it->resolveToxPk(pk);
119  if (!res.isEmpty()) {
120  return res;
121  }
122  }
123 
124  return pk.toString();
125 }
126 } // namespace
127 
129  : coreIdHandler(coreIdHandler)
130 {}
131 
136  : coreIdHandler(coreIdHandler)
137  , nextIdx(initialIdx)
138 {}
139 
141 
143 {
144  bool isSelf = sender == coreIdHandler.getSelfPublicKey();
145  QString myNickName = coreIdHandler.getUsername().isEmpty() ? sender.toString() : coreIdHandler.getUsername();
146 
147  return isSelf ? myNickName : resolveToxPk(sender);
148 }
149 
151 {
152  auto item = items.find(idx);
153  if (item == items.end()) {
154  std::terminate();
155  }
156 
157  return item->second;
158 }
159 
160 SearchResult SessionChatLog::searchForward(SearchPos startPos, const QString& phrase,
161  const ParameterSearch& parameter) const
162 {
163  if (startPos.logIdx >= getNextIdx()) {
164  SearchResult res;
165  res.found = false;
166  return res;
167  }
168 
169  auto currentPos = startPos;
170 
171  auto regexp = getRegexpForPhrase(phrase, parameter.filter);
172 
173  for (auto it = items.find(currentPos.logIdx); it != items.end(); ++it) {
174  const auto& key = it->first;
175  const auto& item = it->second;
176 
177  if (item.getContentType() != ChatLogItem::ContentType::message) {
178  continue;
179  }
180 
181  const auto& content = item.getContentAsMessage();
182 
183  auto match = regexp.globalMatch(content.message.content, 0);
184 
185  auto numMatches = 0;
186  QRegularExpressionMatch lastMatch;
187  while (match.isValid() && numMatches <= static_cast<int>(currentPos.numMatches) && match.hasNext()) {
188  lastMatch = match.next();
189  numMatches++;
190  }
191 
192  if (numMatches > static_cast<int>(currentPos.numMatches)) {
193  SearchResult res;
194  res.found = true;
195  res.pos.logIdx = key;
196  res.pos.numMatches = numMatches;
197  res.start = lastMatch.capturedStart();
198  res.len = lastMatch.capturedLength();
199  return res;
200  }
201 
202  // After the first iteration we force this to 0 to search the whole
203  // message
204  currentPos.numMatches = 0;
205  }
206 
207  // We should have returned from the above loop if we had found anything
208  SearchResult ret;
209  ret.found = false;
210  return ret;
211 }
212 
213 SearchResult SessionChatLog::searchBackward(SearchPos startPos, const QString& phrase,
214  const ParameterSearch& parameter) const
215 {
216  auto currentPos = startPos;
217  auto regexp = getRegexpForPhrase(phrase, parameter.filter);
218  auto startIt = items.find(currentPos.logIdx);
219 
220  // If we don't have it we'll start at the end
221  if (startIt == items.end()) {
222  if (items.empty()) {
223  SearchResult ret;
224  ret.found = false;
225  return ret;
226  }
227  startIt = std::prev(items.end());
228  startPos.numMatches = 0;
229  }
230 
231  // Off by 1 due to reverse_iterator api
232  auto rStartIt = std::reverse_iterator<decltype(startIt)>(std::next(startIt));
233  auto rEnd = std::reverse_iterator<decltype(startIt)>(items.begin());
234 
235  for (auto it = rStartIt; it != rEnd; ++it) {
236  const auto& key = it->first;
237  const auto& item = it->second;
238 
239  if (item.getContentType() != ChatLogItem::ContentType::message) {
240  continue;
241  }
242 
243  const auto& content = item.getContentAsMessage();
244  auto match = regexp.globalMatch(content.message.content, 0);
245 
246  auto totalMatches = 0;
247  auto numMatchesBeforePos = 0;
248  QRegularExpressionMatch lastMatch;
249  while (match.isValid() && match.hasNext()) {
250  auto currentMatch = match.next();
251  totalMatches++;
252  if (currentPos.numMatches == 0 || static_cast<int>(currentPos.numMatches) > numMatchesBeforePos) {
253  lastMatch = currentMatch;
254  numMatchesBeforePos++;
255  }
256  }
257 
258  if ((numMatchesBeforePos < static_cast<int>(currentPos.numMatches) || currentPos.numMatches == 0)
259  && numMatchesBeforePos > 0) {
260  SearchResult res;
261  res.found = true;
262  res.pos.logIdx = key;
263  res.pos.numMatches = numMatchesBeforePos;
264  res.start = lastMatch.capturedStart();
265  res.len = lastMatch.capturedLength();
266  return res;
267  }
268 
269  // After the first iteration we force this to 0 to search the whole
270  // message
271  currentPos.numMatches = 0;
272  }
273 
274  // We should have returned from the above loop if we had found anything
275  SearchResult ret;
276  ret.found = false;
277  return ret;
278 }
279 
281 {
282  if (items.empty()) {
283  return nextIdx;
284  }
285 
286  return items.begin()->first;
287 }
288 
290 {
291  return nextIdx;
292 }
293 
294 std::vector<IChatLog::DateChatLogIdxPair> SessionChatLog::getDateIdxs(const QDate& startDate,
295  size_t maxDates) const
296 {
297  std::vector<DateChatLogIdxPair> ret;
298  auto dateIt = startDate;
299 
300  while (true) {
301  auto it = firstItemAfterDate(dateIt, items);
302 
303  if (it == items.end()) {
304  break;
305  }
306 
307  DateChatLogIdxPair pair;
308  pair.date = dateIt;
309  pair.idx = it->first;
310 
311  ret.push_back(std::move(pair));
312 
313  dateIt = dateIt.addDays(1);
314  if (startDate.daysTo(dateIt) > static_cast<long>(maxDates) && maxDates != 0) {
315  break;
316  }
317  }
318 
319  return ret;
320 }
321 
323 {
324  auto messageIdx = nextIdx++;
325 
326  items.emplace(messageIdx, ChatLogItem(message));
327 
328  emit this->itemUpdated(messageIdx);
329 }
330 
331 void SessionChatLog::insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName,
332  const ChatLogMessage& message)
333 {
334  auto item = ChatLogItem(sender, senderName, message);
335 
336  assert(message.state == MessageState::complete);
337 
338  items.emplace(idx, std::move(item));
339 }
340 
341 void SessionChatLog::insertIncompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName,
342  const ChatLogMessage& message,
343  DispatchedMessageId dispatchId)
344 {
345  auto item = ChatLogItem(sender, senderName, message);
346 
347  assert(message.state == MessageState::pending);
348 
349  items.emplace(idx, std::move(item));
350  outgoingMessages.insert(dispatchId, idx);
351 }
352 
353 void SessionChatLog::insertBrokenMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName,
354  const ChatLogMessage& message)
355 {
356  auto item = ChatLogItem(sender, senderName, message);
357 
358  assert(message.state == MessageState::broken);
359 
360  items.emplace(idx, std::move(item));
361 }
362 
363 void SessionChatLog::insertFileAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName, const ChatLogFile& file)
364 {
365  auto item = ChatLogItem(sender, senderName, file);
366 
367  items.emplace(idx, std::move(item));
368 }
369 
371 {
372  auto item = ChatLogItem(std::move(message));
373 
374  items.emplace(idx, std::move(item));
375 }
376 
381 void SessionChatLog::onMessageReceived(const ToxPk& sender, const Message& message)
382 {
383  auto messageIdx = nextIdx++;
384 
385  ChatLogMessage chatLogMessage;
386  chatLogMessage.state = MessageState::complete;
387  chatLogMessage.message = message;
388  items.emplace(messageIdx, ChatLogItem(sender, resolveSenderNameFromSender(sender), chatLogMessage));
389 
390  emit this->itemUpdated(messageIdx);
391 }
392 
398 {
399  auto messageIdx = nextIdx++;
400 
401  ChatLogMessage chatLogMessage;
402  chatLogMessage.state = MessageState::pending;
403  chatLogMessage.message = message;
404  const ToxPk selfPk = coreIdHandler.getSelfPublicKey();
405  const QString selfName = resolveSenderNameFromSender(selfPk);
406  items.emplace(messageIdx, ChatLogItem(selfPk, selfName, chatLogMessage));
407 
408  outgoingMessages.insert(id, messageIdx);
409 
410  emit this->itemUpdated(messageIdx);
411 }
412 
418 {
419  auto chatLogIdxIt = outgoingMessages.find(id);
420 
421  if (chatLogIdxIt == outgoingMessages.end()) {
422  qWarning() << "Failed to find outgoing message";
423  return;
424  }
425 
426  const auto& chatLogIdx = *chatLogIdxIt;
427  auto messageIt = items.find(chatLogIdx);
428 
429  if (messageIt == items.end()) {
430  qWarning() << "Failed to look up message in chat log";
431  return;
432  }
433 
434  messageIt->second.getContentAsMessage().state = MessageState::complete;
435 
436  emit this->itemUpdated(messageIt->first);
437 }
438 
440 {
441  auto chatLogIdxIt = outgoingMessages.find(id);
442 
443  if (chatLogIdxIt == outgoingMessages.end()) {
444  qWarning() << "Failed to find outgoing message";
445  return;
446  }
447 
448  const auto& chatLogIdx = *chatLogIdxIt;
449  auto messageIt = items.find(chatLogIdx);
450 
451  if (messageIt == items.end()) {
452  qWarning() << "Failed to look up message in chat log";
453  return;
454  }
455 
456  // NOTE: Reason for broken message not currently shown in UI, but it could be
457  messageIt->second.getContentAsMessage().state = MessageState::broken;
458 
459  emit this->itemUpdated(messageIt->first);
460 }
461 
467 void SessionChatLog::onFileUpdated(const ToxPk& sender, const ToxFile& file)
468 {
469  auto fileIt =
470  std::find_if(currentFileTransfers.begin(), currentFileTransfers.end(),
471  [&](const CurrentFileTransfer& transfer) { return transfer.file == file; });
472 
473  ChatLogIdx messageIdx;
474  if (fileIt == currentFileTransfers.end() && file.status == ToxFile::INITIALIZING) {
475  assert(file.status == ToxFile::INITIALIZING);
476  CurrentFileTransfer currentTransfer;
477  currentTransfer.file = file;
478  currentTransfer.idx = nextIdx++;
479  currentFileTransfers.push_back(currentTransfer);
480 
481  const auto chatLogFile = ChatLogFile{QDateTime::currentDateTime(), file};
482  items.emplace(currentTransfer.idx, ChatLogItem(sender, resolveSenderNameFromSender(sender), chatLogFile));
483  messageIdx = currentTransfer.idx;
484  } else if (fileIt != currentFileTransfers.end()) {
485  messageIdx = fileIt->idx;
486  fileIt->file = file;
487 
488  items.at(messageIdx).getContentAsFile().file = file;
489  } else {
490  // This may be a file unbroken message that we don't handle ATM
491  return;
492  }
493 
494  if (toxFileIsComplete(file.status)) {
495  currentFileTransfers.erase(fileIt);
496  }
497 
498  emit this->itemUpdated(messageIdx);
499 }
500 
502  bool /*paused*/)
503 {
504  onFileUpdated(sender, file);
505 }
506 
508  bool /*broken*/)
509 {
510  onFileUpdated(sender, file);
511 }
SearchResult::pos
SearchPos pos
Definition: ichatlog.h:73
SessionChatLog::getFirstIdx
ChatLogIdx getFirstIdx() const override
The underlying chat log instance may not want to start at 0.
Definition: sessionchatlog.cpp:280
SearchResult::len
size_t len
Definition: ichatlog.h:75
SessionChatLog::onMessageBroken
void onMessageBroken(DispatchedMessageId id, BrokenMessageReason reason)
Definition: sessionchatlog.cpp:439
FilterSearch
FilterSearch
Definition: searchtypes.h:25
FilterSearch::Register
@ Register
ChatLogItem
Definition: chatlogitem.h:42
IChatLog::DateChatLogIdxPair::idx
ChatLogIdx idx
Definition: ichatlog.h:129
SessionChatLog::onFileUpdated
void onFileUpdated(const ToxPk &sender, const ToxFile &file)
Updates file state in the chatlog.
Definition: sessionchatlog.cpp:467
ToxFile::BROKEN
@ BROKEN
Definition: toxfile.h:41
friendlist.h
FilterSearch::RegisterAndWordsOnly
@ RegisterAndWordsOnly
ParameterSearch::filter
FilterSearch filter
Definition: searchtypes.h:48
SessionChatLog::getDateIdxs
std::vector< DateChatLogIdxPair > getDateIdxs(const QDate &startDate, size_t maxDates) const override
Gets indexes for each new date starting at startDate.
Definition: sessionchatlog.cpp:294
ICoreIdHandler::getUsername
virtual QString getUsername() const =0
SessionChatLog::CurrentFileTransfer::idx
ChatLogIdx idx
Definition: sessionchatlog.h:82
ParameterSearch
Definition: searchtypes.h:47
SessionChatLog::onMessageSent
void onMessageSent(DispatchedMessageId id, const Message &message)
Inserts message data into the chatlog buffer.
Definition: sessionchatlog.cpp:397
SessionChatLog::CurrentFileTransfer
Definition: sessionchatlog.h:80
ToxFile::TRANSMITTING
@ TRANSMITTING
Definition: toxfile.h:40
ChatLogItem::ContentType::message
@ message
HistMessageContentType::file
@ file
ToxFile::FileStatus
FileStatus
Definition: toxfile.h:36
SessionChatLog::searchBackward
SearchResult searchBackward(SearchPos startIdx, const QString &phrase, const ParameterSearch &parameter) const override
searches backwards through the chat log until phrase is found according to parameter
Definition: sessionchatlog.cpp:213
SessionChatLog::outgoingMessages
QMap< DispatchedMessageId, ChatLogIdx > outgoingMessages
Definition: sessionchatlog.h:97
IChatLog::DateChatLogIdxPair::date
QDate date
Definition: ichatlog.h:128
SystemMessage
Definition: systemmessage.h:47
SearchResult::start
size_t start
Definition: ichatlog.h:74
SessionChatLog::addSystemMessage
void addSystemMessage(const SystemMessage &message) override
Inserts a system message at the end of the chat.
Definition: sessionchatlog.cpp:322
SessionChatLog::onFileTransferRemotePausedUnpaused
void onFileTransferRemotePausedUnpaused(const ToxPk &sender, const ToxFile &file, bool paused)
Definition: sessionchatlog.cpp:501
SessionChatLog::at
const ChatLogItem & at(ChatLogIdx idx) const override
Returns reference to item at idx.
Definition: sessionchatlog.cpp:150
SessionChatLog::onFileTransferBrokenUnbroken
void onFileTransferBrokenUnbroken(const ToxPk &sender, const ToxFile &file, bool broken)
Definition: sessionchatlog.cpp:507
FriendList::findFriend
static Friend * findFriend(const ToxPk &friendPk)
Definition: friendlist.cpp:47
SessionChatLog::insertBrokenMessageAtIdx
void insertBrokenMessageAtIdx(ChatLogIdx idx, const ToxPk &sender, QString senderName, const ChatLogMessage &message)
Definition: sessionchatlog.cpp:353
SessionChatLog::coreIdHandler
const ICoreIdHandler & coreIdHandler
Definition: sessionchatlog.h:74
HistMessageContentType::message
@ message
grouplist.h
SessionChatLog::insertCompleteMessageAtIdx
void insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk &sender, QString senderName, const ChatLogMessage &message)
Definition: sessionchatlog.cpp:331
ToxPk
This class represents a Tox Public Key, which is a part of Tox ID.
Definition: toxpk.h:26
Friend
Definition: friend.h:31
SessionChatLog::getNextIdx
ChatLogIdx getNextIdx() const override
Definition: sessionchatlog.cpp:289
ToxFile::INITIALIZING
@ INITIALIZING
Definition: toxfile.h:38
ToxFile::PAUSED
@ PAUSED
Definition: toxfile.h:39
ChatLogMessage
Definition: chatlogitem.h:30
ICoreIdHandler::getSelfPublicKey
virtual ToxPk getSelfPublicKey() const =0
ToxFile::CANCELED
@ CANCELED
Definition: toxfile.h:42
DispatchedMessageId
NamedType< size_t, struct SentMessageIdTag, Orderable, Incrementable > DispatchedMessageId
Definition: imessagedispatcher.h:31
ToxFile::FINISHED
@ FINISHED
Definition: toxfile.h:43
SessionChatLog::currentFileTransfers
std::vector< CurrentFileTransfer > currentFileTransfers
Definition: sessionchatlog.h:91
IChatLog::itemUpdated
void itemUpdated(ChatLogIdx idx)
SessionChatLog::insertIncompleteMessageAtIdx
void insertIncompleteMessageAtIdx(ChatLogIdx idx, const ToxPk &sender, QString senderName, const ChatLogMessage &message, DispatchedMessageId dispatchId)
Definition: sessionchatlog.cpp:341
Friend::getDisplayedName
QString getDisplayedName() const override
Friend::getDisplayedName Gets the name that should be displayed for a user.
Definition: friend.cpp:112
SessionChatLog::SessionChatLog
SessionChatLog(const ICoreIdHandler &coreIdHandler)
Definition: sessionchatlog.cpp:128
BrokenMessageReason
BrokenMessageReason
Definition: brokenmessagereason.h:23
SessionChatLog::items
std::map< ChatLogIdx, ChatLogItem > items
Definition: sessionchatlog.h:78
FilterSearch::WordsOnly
@ WordsOnly
Group
Definition: group.h:34
SearchPos::numMatches
size_t numMatches
Definition: ichatlog.h:47
SessionChatLog::CurrentFileTransfer::file
ToxFile file
Definition: sessionchatlog.h:83
ChatLogIdx
NamedType< size_t, struct ChatLogIdxTag, Orderable, UnderlyingAddable, UnitlessDifferencable, Incrementable > ChatLogIdx
Definition: ichatlog.h:38
IChatLog::DateChatLogIdxPair
Definition: ichatlog.h:126
SearchPos
Definition: ichatlog.h:41
sessionchatlog.h
SessionChatLog::~SessionChatLog
~SessionChatLog()
MessageState::complete
@ complete
ICoreIdHandler
Definition: icoreidhandler.h:25
SessionChatLog::insertSystemMessageAtIdx
void insertSystemMessageAtIdx(ChatLogIdx idx, SystemMessage message)
Definition: sessionchatlog.cpp:370
SessionChatLog::searchForward
SearchResult searchForward(SearchPos startIdx, const QString &phrase, const ParameterSearch &parameter) const override
searches forwards through the chat log until phrase is found according to parameter
Definition: sessionchatlog.cpp:160
FilterSearch::RegisterAndRegular
@ RegisterAndRegular
SessionChatLog::resolveSenderNameFromSender
QString resolveSenderNameFromSender(const ToxPk &sender)
Definition: sessionchatlog.cpp:142
GroupList::getAllGroups
static QList< Group * > getAllGroups()
Definition: grouplist.cpp:64
ContactId::toString
QString toString() const
Converts the ContactId to a uppercase hex string.
Definition: contactid.cpp:78
SearchExtraFunctions::generateFilterWordsOnly
static QString generateFilterWordsOnly(const QString &phrase)
generateFilterWordsOnly generate string for filter "Whole words only" for correct search phrase conta...
Definition: searchtypes.h:72
ChatLogMessage::message
Message message
Definition: chatlogitem.h:33
SessionChatLog::onMessageReceived
void onMessageReceived(const ToxPk &sender, const Message &message)
Inserts message data into the chatlog buffer.
Definition: sessionchatlog.cpp:381
SessionChatLog::onMessageComplete
void onMessageComplete(DispatchedMessageId id)
Marks the associated message as complete and notifies any listeners.
Definition: sessionchatlog.cpp:417
ToxFile
Definition: toxfile.h:32
ChatLogMessage::state
MessageState state
Definition: chatlogitem.h:32
MessageState::broken
@ broken
FilterSearch::Regular
@ Regular
SessionChatLog::insertFileAtIdx
void insertFileAtIdx(ChatLogIdx idx, const ToxPk &sender, QString senderName, const ChatLogFile &file)
Definition: sessionchatlog.cpp:363
SearchPos::logIdx
ChatLogIdx logIdx
Definition: ichatlog.h:44
SessionChatLog::nextIdx
ChatLogIdx nextIdx
Definition: sessionchatlog.h:76
Message
Definition: message.h:52
SearchResult
Definition: ichatlog.h:70
SearchResult::found
bool found
Definition: ichatlog.h:72
ChatLogFile
Definition: chatlogitem.h:36
MessageState::pending
@ pending