New binary log format, with color/face support

Binary logs are no longer stored as a single message with a list of
strings and int64s ("body", "user_name", "user_id", "when"), but a
message with sub-messages (IM_MESSAGE_RECEIVED) verbatim saved.

This has the main benefit of preserving message formatting (coloring,
bold, italics, etc).
This commit is contained in:
Jaidyn Ann 2021-08-17 13:22:20 -05:00
parent 5aeaf9284f
commit 8512e9ad03
4 changed files with 45 additions and 117 deletions

View File

@ -76,9 +76,8 @@ enum im_what_code {
If chat_id is ommitted, the message is sent to the protocol's system If chat_id is ommitted, the message is sent to the protocol's system
buffer, rather than a specific conversation. buffer, rather than a specific conversation.
face_start and face_length specify the location of formatted text in face_start and face_length specify the location of formatted text in
the body, and "face" is the desired font face. Unsupported in bulk, the body, and "face" is the desired font face.
color_* works much the same, but with colors. Not much else to say. color_* works much the same, but with colors. Not much else to say.
i.e., IM_LOGS_RECEIVED.
Requires: String "body" Requires: String "body"
Allows: String "chat_id", String "user_id", String "user_name", Allows: String "chat_id", String "user_id", String "user_name",
int32s "face_start", int32s "face_length", uint16s "face" int32s "face_start", int32s "face_length", uint16s "face"
@ -87,9 +86,8 @@ enum im_what_code {
IM_MESSAGE_RECEIVED = 22, IM_MESSAGE_RECEIVED = 22,
/*! Logs received →App /*! Logs received →App
Without "when" (a time_t), the logged message will lack a timestamp Should be a message with several sub-messages of IM_MESSAGE_RECEIVED.
Requires: Strings "chat_id", Strings "user_id", Strings "body" Requires: Messages "message" */
Accepts: in64s "when" */
IM_LOGS_RECEIVED = 23, IM_LOGS_RECEIVED = 23,

View File

@ -123,7 +123,6 @@ Conversation::ImMessage(BMessage* msg)
if (icon == NULL) if (icon == NULL)
icon = ProtocolBitmap(); icon = ProtocolBitmap();
BNotification notification(B_INFORMATION_NOTIFICATION); BNotification notification(B_INFORMATION_NOTIFICATION);
notification.SetGroup(BString(APP_NAME)); notification.SetGroup(BString(APP_NAME));
notification.SetTitle(notifyTitle); notification.SetTitle(notifyTitle);
@ -402,7 +401,6 @@ Conversation::GetView()
BMessage logMsg; BMessage logMsg;
if (_GetChatLogs(&logMsg) == B_OK) if (_GetChatLogs(&logMsg) == B_OK)
fChatView->MessageReceived(&logMsg); fChatView->MessageReceived(&logMsg);
return fChatView; return fChatView;
} }
@ -499,66 +497,42 @@ Conversation::_WarnUser(BString message)
void void
Conversation::_LogChatMessage(BMessage* msg) Conversation::_LogChatMessage(BMessage* msg)
{ {
// Binary logs
// TODO: Don't hardcode 31, expose maximum as a setting
const int32 MAX = 31;
BMessage logMsg(IM_MESSAGE);
if (_GetChatLogs(&logMsg) != B_OK) {
logMsg.what = IM_MESSAGE;
logMsg.AddInt32("im_what", IM_LOGS_RECEIVED);
}
BMessage last;
if (logMsg.FindMessage("message", MAX, &last) == B_OK)
logMsg.RemoveData("message", 0);
msg->AddInt64("when", time(NULL));
logMsg.AddMessage("message", msg);
BFile logFile(fCachePath.Path(), B_READ_WRITE | B_OPEN_AT_END | B_CREATE_FILE);
WriteAttributeMessage(&logFile, "Chat:logs", &logMsg);
// Plain-text logs
// Gotta make sure the formatting's pretty!
BString date; BString date;
fDateFormatter.Format(date, time(0), B_SHORT_DATE_FORMAT, B_MEDIUM_TIME_FORMAT); fDateFormatter.Format(date, time(0), B_SHORT_DATE_FORMAT, B_MEDIUM_TIME_FORMAT);
BString id = msg->FindString("user_id"); BString id = msg->FindString("user_id");
BString name = msg->FindString("user_name"); BString name = msg->FindString("user_name");
BString body = msg->FindString("body"); BString body = msg->FindString("body");
if (id.IsEmpty() == true) if (id.IsEmpty() == true && name.IsEmpty() == true)
return; return;
else if (name.IsEmpty() == true) {
if (name.IsEmpty() == true) {
User* user = UserById(id); User* user = UserById(id);
if (user == NULL) name = user ? user->GetName() : id;
name = id;
else
name = user->GetName();
} }
// Binary logs
// TODO: Don't hardcode 31, expose maximum as a setting
int32 max = 31;
BStringList user_ids, user_names, bodies;
int64 times[max] = { 0 };
times[0] = (int64)time(NULL);
BMessage logMsg;
if (_GetChatLogs(&logMsg) == B_OK) {
logMsg.FindStrings("body", &bodies);
logMsg.FindStrings("user_id", &user_ids);
logMsg.FindStrings("user_name", &user_names);
int64 found;
for (int i = 0; i < max; i++)
if (logMsg.FindInt64("when", i, &found) == B_OK)
times[i + 1] = found;
bodies.Remove(max);
user_ids.Remove(max);
user_names.Remove(max);
bodies.Add(body, 0);
user_ids.Add(id, 0);
user_names.Add(name, 0);
}
BMessage newLogMsg(IM_MESSAGE);
newLogMsg.AddInt32("im_what", IM_LOGS_RECEIVED);
newLogMsg.AddStrings("body", bodies);
newLogMsg.AddStrings("user_id", user_ids);
newLogMsg.AddStrings("user_name", user_names);
newLogMsg.AddInt64("when", time(NULL));
for (int i = 0; i < max; i++)
newLogMsg.AddInt64("when", times[i]);
BFile logFile(fCachePath.Path(), B_READ_WRITE | B_OPEN_AT_END | B_CREATE_FILE);
WriteAttributeMessage(&logFile, "Chat:logs", &newLogMsg);
// Plain-text logs
BString logLine("["); BString logLine("[");
logLine << date << "] <" << name << "> " << body << "\n"; logLine << date << "] <" << name << "> " << body << "\n";
logFile.Write(logLine.String(), logLine.Length()); logFile.Write(logLine.String(), logLine.Length());
} }
@ -567,9 +541,7 @@ status_t
Conversation::_GetChatLogs(BMessage* msg) Conversation::_GetChatLogs(BMessage* msg)
{ {
_EnsureCachePath(); _EnsureCachePath();
BFile logFile(fCachePath.Path(), B_READ_WRITE | B_CREATE_FILE); BFile logFile(fCachePath.Path(), B_READ_WRITE | B_CREATE_FILE);
return ReadAttributeMessage(&logFile, "Chat:logs", msg); return ReadAttributeMessage(&logFile, "Chat:logs", msg);
} }
@ -581,7 +553,6 @@ Conversation::_CacheRoomFlags()
BFile cacheFile(fCachePath.Path(), B_READ_WRITE | B_CREATE_FILE); BFile cacheFile(fCachePath.Path(), B_READ_WRITE | B_CREATE_FILE);
if (cacheFile.InitCheck() != B_OK) if (cacheFile.InitCheck() != B_OK)
return; return;
cacheFile.WriteAttr("Chat:flags", B_INT32_TYPE, 0, &fRoomFlags, sizeof(int32)); cacheFile.WriteAttr("Chat:flags", B_INT32_TYPE, 0, &fRoomFlags, sizeof(int32));
} }
@ -593,7 +564,6 @@ Conversation::_LoadRoomFlags()
BFile cacheFile(fCachePath.Path(), B_READ_ONLY); BFile cacheFile(fCachePath.Path(), B_READ_ONLY);
if (cacheFile.InitCheck() != B_OK) if (cacheFile.InitCheck() != B_OK)
return; return;
cacheFile.ReadAttr("Chat:flags", B_INT32_TYPE, 0, &fRoomFlags, sizeof(int32)); cacheFile.ReadAttr("Chat:flags", B_INT32_TYPE, 0, &fRoomFlags, sizeof(int32));
} }

View File

@ -393,6 +393,15 @@ ConversationView::_AppendOrEnqueueMessage(BMessage* msg)
// later [AttachedToWindow()], since you can't edit an unattached // later [AttachedToWindow()], since you can't edit an unattached
// RenderView. // RenderView.
if (Window() == NULL) { if (Window() == NULL) {
// If contains multiple chat messages (e.g., IM_LOGS_RECEIVED), add all
int32 i = -1;
BMessage text;
while (msg->FindMessage("message", i + 1, &text) == B_OK) {
fMessageQueue.AddItem(new BMessage(text));
i++;
}
// Else, add the lonely, lonely, single-messaged one
if (i == -1)
fMessageQueue.AddItem(new BMessage(*msg)); fMessageQueue.AddItem(new BMessage(*msg));
return false; return false;
} }
@ -412,62 +421,7 @@ ConversationView::_AppendMessage(BMessage* msg)
return; return;
} }
// … else we're jamming a message into this view no matter what it takes! // Otherwise, it's message time!
if (msg->HasInt32("face_start") || msg->HasInt32("color_start")) {
_AppendFormattedMessage(msg);
return;
}
// For unformatted (normal or bulk) messages
BStringList user_ids, user_names, bodies;
if (msg->FindStrings("body", &bodies) != B_OK)
return;
msg->FindStrings("user_id", &user_ids);
msg->FindStrings("user_name", &user_names);
for (int i = bodies.CountStrings(); i >= 0; i--) {
User* sender = NULL;
if (fConversation != NULL)
sender = fConversation->UserById(user_ids.StringAt(i));
BString sender_id = user_ids.StringAt(i);
BString sender_name = user_names.StringAt(i);
BString body = bodies.StringAt(i);
rgb_color userColor = ui_color(B_PANEL_TEXT_COLOR);
int64 timeInt;
if (msg->FindInt64("when", i, &timeInt) != B_OK)
timeInt = (int64)time(NULL);
if (sender != NULL) {
sender_name = sender->GetName();
userColor = sender->fItemColor;
}
if (sender_name.IsEmpty() == true && sender_id.IsEmpty() == false)
sender_name = sender_id;
if (sender_id.IsEmpty() == true && sender_name.IsEmpty() == true) {
fReceiveView->AppendGeneric(body.String());
continue;
}
if (body.StartsWith("/me ")) {
BString meMsg = "** ";
meMsg << sender_name.String() << " ";
meMsg << body.RemoveFirst("/me ");
fReceiveView->AppendGeneric(meMsg.String());
continue;
}
fReceiveView->AppendMessage(sender_name.String(), body.String(),
userColor, (time_t)timeInt);
}
}
void
ConversationView::_AppendFormattedMessage(BMessage* msg)
{
int64 timeInt; int64 timeInt;
BString user_id; BString user_id;
BString user_name = msg->FindString("user_name"); BString user_name = msg->FindString("user_name");
@ -496,6 +450,13 @@ ConversationView::_AppendFormattedMessage(BMessage* msg)
return; return;
} }
if (body.StartsWith("/me ")) {
BString meMsg = "** ";
meMsg << user_name.String() << " ";
meMsg << body.RemoveFirst("/me ");
fReceiveView->AppendGeneric(meMsg.String());
return;
}
fReceiveView->AppendTimestamp(timeInt); fReceiveView->AppendTimestamp(timeInt);
fReceiveView->AppendUserstamp(user_name, userColor); fReceiveView->AppendUserstamp(user_name, userColor);

View File

@ -57,7 +57,6 @@ private:
bool _AppendOrEnqueueMessage(BMessage* msg); bool _AppendOrEnqueueMessage(BMessage* msg);
void _AppendMessage(BMessage* msg); void _AppendMessage(BMessage* msg);
void _AppendFormattedMessage(BMessage* msg);
// Helper functions for _AppendFormattedMessage() // Helper functions for _AppendFormattedMessage()
void _EnableStartingFaces(BMessage* msg, int32 index, void _EnableStartingFaces(BMessage* msg, int32 index,