Store chatlogs in binary and text formats

If chat logs are stored in an endless plain-text file, they're going to be a
pain to parse. If they're stored in a binary file, they're a pain for
the user to `grep` or go through manually, but they're easier to parse.

Why not both?

Logs are now stored with a BMessage (with BStringLists) in the 'logs'
attribute, and the plain-text logs in the file itself.

The attribute will only store 20 messages, but the file itself will be
appended to forever.

The logs directory also changed, from ~/config/settings/Caya/Cache/Logs
to ~/config/settings/Caya/Logs.

Useful functions for reading/writing messages to and from an attribute were
borrowed from BePodder's libfunky. :-)
This commit is contained in:
Jaidyn Ann 2021-06-06 12:02:26 -05:00
parent fe99f3d83a
commit dc3fdd65c8
6 changed files with 119 additions and 57 deletions

View File

@ -1,11 +1,8 @@
/*
* Copyright 2009-2011, Pier Luigi Fiorini. All rights reserved.
* Copyright 2014, Funky Idea Software
* Distributed under the terms of the MIT License.
*
* Authors:
* Pier Luigi Fiorini, pierluigi.fiorini@gmail.com
*/
#include <memory.h>
#include <Bitmap.h>
@ -15,6 +12,8 @@
#include <IconUtils.h>
#include <Path.h>
#include <kernel/fs_attr.h>
#include "CayaUtils.h"
@ -129,8 +128,11 @@ CayaCachePath()
const char*
CayaLogPath(const char* signature, const char* subsignature)
{
BPath path(CayaCachePath());
path.Append("Logs");
BPath path;
if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
return NULL;
path.Append("Caya/Logs");
path.Append(signature);
if (BString(signature) != BString(subsignature)
@ -175,6 +177,62 @@ CayaTintColor(rgb_color color, int severity)
}
status_t
ReadAttributeData(BNode* node, const char* name, char** buffer, int32 *size) {
attr_info info;
status_t ret = node->GetAttrInfo(name, &info);
if (ret == B_OK) {
*buffer = (char *)calloc(info.size, sizeof(char));
ret = node->ReadAttr(name, info.type, 0, *buffer, info.size);
if (ret > B_OK) {
*size = ret;
ret = B_OK;
}
else
free(*buffer);
}
return ret;
}
status_t
WriteAttributeMessage(BNode* node, const char* name, BMessage* data)
{
BMallocIO malloc;
status_t ret=data->Flatten(&malloc);
if( ret == B_OK) {
ret = node->WriteAttr(name,B_ANY_TYPE,0,malloc.Buffer(),malloc.BufferLength());
if(ret > B_OK)
ret=B_OK;
}
return ret;
}
status_t
ReadAttributeMessage(BNode* node, const char* name, BMessage* data)
{
char *buffer = NULL;
int32 size = 0;
status_t ret = ReadAttributeData(node,name,&buffer,&size);
if(size>0 && buffer!=NULL) {
BMemoryIO mem(buffer,size);
ret = data->Unflatten(&mem);
free(buffer);
}
return ret;
}
extern "C" {
status_t

View File

@ -24,9 +24,13 @@ const char* CayaAccountPath(const char* signature, const char* subsignature);
const char* CayaCachePath();
const char* CayaLogPath(const char* signature, const char* subsignature);
// Will return a tinted color― light or dark― based on brightness.
rgb_color CayaTintColor(rgb_color color, int severity);
// Borrowed from BePodder's own libfunky
status_t ReadAttributeData(BNode* node, const char* name, char** buffer, int32 *size);
status_t WriteAttributeMessage(BNode* node, const char* name, BMessage* data);
status_t ReadAttributeMessage(BNode* node, const char* name, BMessage* data);
extern "C" status_t our_image(image_info& image);

View File

@ -173,11 +173,9 @@ Conversation::GetView()
if (fLooper->Protocol()->SaveLogs() == false)
return fChatView;
BStringList logs = _GetChatLogs();
BMessage logMsg(IM_MESSAGE);
logMsg.AddInt32("im_what", IM_LOGS_RECEIVED);
logMsg.AddStrings("body", logs);
fChatView->MessageReceived(&logMsg);
BMessage logMsg;
if (_GetChatLogs(&logMsg) == B_OK)
fChatView->MessageReceived(&logMsg);
RegisterObserver(fChatView);
return fChatView;
@ -266,8 +264,32 @@ Conversation::_LogChatMessage(BMessage* msg)
fDateFormatter.Format(date, time(0), B_SHORT_DATE_FORMAT, B_MEDIUM_TIME_FORMAT);
BString id = msg->FindString("user_id");
BString uname;
BString body = msg->FindString("body");
// Binary logs
// TODO: Don't hardcode 21, expose maximum as a setting
BStringList users, bodies;
BMessage logMsg;
if (_GetChatLogs(&logMsg) == B_OK) {
logMsg.FindStrings("body", &bodies);
logMsg.FindStrings("user_id", &users);
bodies.Remove(21);
users.Remove(21);
bodies.Add(body, 0);
users.Add(id, 0);
}
BMessage newLogMsg(IM_MESSAGE);
newLogMsg.AddInt32("im_what", IM_LOGS_RECEIVED);
newLogMsg.AddStrings("body", bodies);
newLogMsg.AddStrings("user_id", users);
BFile logFile(fLogPath.Path(), B_READ_WRITE | B_OPEN_AT_END | B_CREATE_FILE);
WriteAttributeMessage(&logFile, "logs", &newLogMsg);
// Plain-text logs
BString uname;
if (id.IsEmpty() == false)
uname = UserById(id)->GetName();
else
@ -278,37 +300,21 @@ Conversation::_LogChatMessage(BMessage* msg)
logLine << "] ";
logLine << uname;
logLine << ": ";
logLine << msg->FindString("body");
logLine << body;
logLine << "\n";
// TODO: Don't hardcode 21, expose maximum as a setting
BStringList logs = _GetChatLogs();
logs.Remove(21);
logs.Add(logLine, 0);
BMessage newLogMsg;
newLogMsg.AddStrings("log", logs);
BFile logFile(fLogPath.Path(), B_READ_WRITE | B_CREATE_FILE);
newLogMsg.Flatten(&logFile);
logFile.Write(logLine.String(), logLine.Length());
}
BStringList
Conversation::_GetChatLogs()
status_t
Conversation::_GetChatLogs(BMessage* msg)
{
_EnsureLogPath();
BFile logFile(fLogPath.Path(), B_READ_WRITE | B_CREATE_FILE);
BMessage logMsg;
BStringList logs;
if (logMsg.Unflatten(&logFile) == B_OK) {
logMsg.FindStrings("log", &logs);
}
return logs;
return ReadAttributeMessage(&logFile, "logs", msg);
}

View File

@ -68,7 +68,7 @@ public:
private:
void _LogChatMessage(BMessage* msg);
BStringList _GetChatLogs();
status_t _GetChatLogs(BMessage* msg);
void _EnsureLogPath();
User* _EnsureUser(BMessage* msg);

View File

@ -275,31 +275,25 @@ ConversationView::_AppendOrEnqueueMessage(BMessage* msg)
void
ConversationView::_AppendMessage(BMessage* msg)
{
BStringList users, bodies;
if (msg->FindStrings("body", &bodies) != B_OK
|| msg->FindStrings("user_id", &users) != B_OK)
return;
BString message = msg->FindString("body");
BString id = msg->FindString("user_id");
BString uname = "";
for (int i = bodies.CountStrings(); i >= 0; i--) {
User* sender = fConversation->UserById(users.StringAt(i));
BString sender_name;
BString body = bodies.StringAt(i);
if (id.IsEmpty() == false) {
User* sender = fConversation->UserById(id);
if (sender != NULL)
sender_name = sender->GetName();
if (sender == NULL || (uname = sender->GetName()) == NULL)
uname = id;
}
if (sender_name.IsEmpty() == true) {
fReceiveView->AppendGenericMessage(body.String());
continue;
}
if (msg->FindInt32("im_what") == IM_MESSAGE_SENT)
fReceiveView->AppendOwnMessage(message.String());
else if (uname.IsEmpty() == false)
fReceiveView->AppendOtherMessage(uname.String(), message.String());
else {
BStringList bodies;
if (msg->FindStrings("body", &bodies) != B_OK)
return;
for (int i = bodies.CountStrings(); i >= 0; i--)
fReceiveView->AppendGenericMessage(bodies.StringAt(i).String());
fReceiveView->AppendOtherMessage(sender_name.String(), body.String());
}
}

View File

@ -688,7 +688,7 @@ JabberHandler::_MessageSent(const char* id, const char* subject,
{
BMessage msg(IM_MESSAGE);
msg.AddInt32("im_what", IM_MESSAGE_SENT);
msg.AddString("user_id", id);
msg.AddString("user_id", fJid.bare().c_str());
msg.AddString("chat_id", id);
msg.AddString("subject", subject);
msg.AddString("body", body);