// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "pd_messages.h"
#include
#include
namespace RY_PDS
{
/*
* Build Log string
*/
std::string CDbMessage::buildLogString(const CDBDescriptionParser& description) const
{
if (getType() != Log)
return "";
const CDatabaseNode& db = description.getDatabaseNode();
// check overflow
if (getLogId() >= db.Logs.size())
{
return NLMISC::toString("", getLogId());;
}
const std::string& logmsg = db.Logs[getLogId()].Message;
const std::vector& logbuffer = getLogBuffer();
uint i;
std::string result;
for (i=0; i='0' && logmsg[i]<='9')
++i;
// check no overflow
if (param >= db.Logs[getLogId()].Parameters.size())
break;
const CLogNode::CParameter& lparam = db.Logs[getLogId()].Parameters[param];
// check no overflow
if (lparam.ByteOffset+lparam.ByteSize > logbuffer.size())
break;
const uint8* dataptr = (&(logbuffer[0])) + lparam.ByteOffset;
if (lparam.DataType == ExtLogTypeString)
{
uint16 stringOffset = *(uint16*)dataptr;
if (stringOffset < _ExtLogBuffer.size())
result += (char*)(&(_ExtLogBuffer[stringOffset]));
}
else
{
const CTypeNode& typenode = db.Types[lparam.TypeId];
switch (typenode.DataType)
{
case PDS_bool: result += (*dataptr != 0 ? "true" : "false"); break;
case PDS_char: result += *(char*)dataptr; break;
case PDS_ucchar: result += NLMISC::toString(*(ucchar*)dataptr); break;
case PDS_uint8: result += NLMISC::toString(*(uint8*)dataptr); break;
case PDS_uint16: result += NLMISC::toString(*(uint16*)dataptr); break;
case PDS_uint32: result += NLMISC::toString(*(uint32*)dataptr); break;
case PDS_uint64: result += NLMISC::toString(*(uint64*)dataptr); break;
case PDS_sint8: result += NLMISC::toString(*(sint8*)dataptr); break;
case PDS_sint16: result += NLMISC::toString(*(sint16*)dataptr); break;
case PDS_sint32: result += NLMISC::toString(*(sint32*)dataptr); break;
case PDS_sint64: result += NLMISC::toString(*(sint64*)dataptr); break;
case PDS_float: result += NLMISC::toString(*(float*)dataptr); break;
case PDS_double: result += NLMISC::toString(*(double*)dataptr); break;
case PDS_CSheetId: result += NLMISC::toString(*(uint32*)dataptr); break;
case PDS_CEntityId: result += ((NLMISC::CEntityId*)dataptr)->toString(); break;
case PDS_enum:
{
result += typenode.getEnumName(*(uint32*)dataptr);
}
break;
case PDS_dimension:
{
if (typenode.ByteSize == 1) result += NLMISC::toString(*(uint8*)dataptr);
else if (typenode.ByteSize == 2) result += NLMISC::toString(*(uint16*)dataptr);
else if (typenode.ByteSize == 4) result += NLMISC::toString(*(uint32*)dataptr);
}
break;
}
}
}
else
{
result += logmsg[i++];
}
}
return result;
}
/*
* Dump Message content to string as a human readable message
*/
void CDbMessage::getHRContent(const CDBDescriptionParser& description, std::string& result) const
{
const CDatabaseNode& db = description.getDatabaseNode();
switch (getType())
{
case UpdateValue:
{
if (getTable() >= db.Tables.size())
return;
const CTableNode& table = db.Tables[getTable()];
if (getColumn() >= table.Columns.size())
return;
const CColumnNode& column = table.Columns[getColumn()];
std::string strValue;
switch (column.DataType)
{
case PDS_bool: strValue = (asBool() ? "true" : "false"); break;
case PDS_char: strValue = asChar(); break;
case PDS_ucchar: strValue = NLMISC::toString(asUCChar()); break;
case PDS_uint8: strValue = NLMISC::toString(asUint8()); break;
case PDS_uint16: strValue = NLMISC::toString(asUint16()); break;
case PDS_uint32: strValue = NLMISC::toString(asUint32()); break;
case PDS_uint64: strValue = NLMISC::toString(asUint64()); break;
case PDS_sint8: strValue = NLMISC::toString(asSint8()); break;
case PDS_sint16: strValue = NLMISC::toString(asSint16()); break;
case PDS_sint32: strValue = NLMISC::toString(asSint32()); break;
case PDS_sint64: strValue = NLMISC::toString(asSint64()); break;
case PDS_float: strValue = NLMISC::toString(asFloat()); break;
case PDS_double: strValue = NLMISC::toString(asDouble()); break;
case PDS_CSheetId: strValue = NLMISC::toString(asUint32()); break;
case PDS_CEntityId: strValue = asEntityId().toString(); break;
case PDS_enum:
{
if (column.TypeId >= db.Types.size())
return;
const CTypeNode& type = db.Types[column.TypeId];
strValue = type.getEnumName(asUint32());
}
break;
case PDS_dimension:
{
if (column.ByteSize == 1) strValue = NLMISC::toString(asUint8());
else if (column.ByteSize == 2) strValue = NLMISC::toString(asUint16());
else if (column.ByteSize == 4) strValue = NLMISC::toString(asUint32());
}
break;
}
result = NLMISC::toString("%-12s: ", "UpdateValue");
if (objectEntityIdPresent())
{
result += getObjectId().toString();
result += ' ';
}
result += NLMISC::toString("%s:%u:%s = %s", table.Name.c_str(), getRow(), column.Name.c_str(), strValue.c_str());
}
break;
case SetParent:
{
if (getTable() >= db.Tables.size())
return;
const CTableNode& table = db.Tables[getTable()];
if (getColumn() >= table.Columns.size())
return;
const CColumnNode& column = table.Columns[getColumn()];
result = NLMISC::toString("%-12s: ", "SetParent");
if (objectEntityIdPresent())
{
result += getObjectId().toString();
result += ' ';
}
result += NLMISC::toString("%s:%u:%s = %s", table.Name.c_str(), getRow(), column.Name.c_str(), getObjectIndex().toString().c_str());
if (parentsEntityIdPresent())
result += NLMISC::toString(" new=%s previous=%s", getNewParentId().toString().c_str(), getPreviousParentId().toString().c_str());
}
break;
case AllocRow:
{
if (getTable() >= db.Tables.size())
return;
const CTableNode& table = db.Tables[getTable()];
/// \todo display mapping value
if (objectEntityIdPresent())
{
result = getObjectId().toString();
result += ' ';
}
result = NLMISC::toString("%-12s: %s%s:%u", "AllocRow", result.c_str(), table.Name.c_str(), getRow());
}
break;
case DeallocRow:
{
if (getTable() >= db.Tables.size())
return;
const CTableNode& table = db.Tables[getTable()];
if (objectEntityIdPresent())
{
result = getObjectId().toString();
result += ' ';
}
result = NLMISC::toString("%-12s: %s%s:%u", "DeallocRow", result.c_str(), table.Name.c_str(), getRow());
}
break;
case LoadRow:
{
if (getTable() >= db.Tables.size())
return;
const CTableNode& table = db.Tables[getTable()];
result = NLMISC::toString("%-12s: %s %"NL_I64"X %s", "LoadRow", table.Name.c_str(), asUint64(), asEntityId().toString().c_str());
}
break;
case AddString:
{
result = NLMISC::toString("%-12s: %s='%s'", "AddString", asEntityId().toString().c_str(), getString().toString().c_str());
}
break;
case UnmapString:
{
result = NLMISC::toString("%-12s: %s", "UnmapString", asEntityId().toString().c_str());
}
break;
case ReleaseRow:
{
if (getTable() >= db.Tables.size())
return;
const CTableNode& table = db.Tables[getTable()];
result = NLMISC::toString("%-12s: %s:%u", "ReleaseRow", table.Name.c_str(), getRow());
}
break;
case Log:
{
result = NLMISC::toString("%-12s: ", "Log")+buildLogString(description);
}
break;
case PushContext:
{
result = NLMISC::toString("%-12s:", "PushContext");
//indent += "..";
}
break;
case PopContext:
{
//if (indent.size() >= 2)
// indent = indent.substr(0, indent.size()-2);
result = NLMISC::toString("%-12s:", "PopContext");
}
break;
case LogChat:
{
result = NLMISC::toString("%-12s: %s says '%s' to", "LogChat", asEntityId().toString().c_str(), _String.toString().c_str());
if (_LogBuffer.empty())
{
result += " no one";
}
else
{
if (_LogBuffer.empty())
return;
const NLMISC::CEntityId* ptr = (const NLMISC::CEntityId*)(&(_LogBuffer[0]));
uint num = _LogBuffer.size()/sizeof(NLMISC::CEntityId);
uint i;
for (i=0; i= db.Tables.size())
return false;
const CTableNode& table = db.Tables[getTable()];
if (getColumn() >= table.Columns.size())
return false;
const CColumnNode& column = table.Columns[getColumn()];
if (column.DataType == PDS_CEntityId && compareEId(asEntityId(), id))
return true;
}
break;
case AllocRow:
case DeallocRow:
{
if (objectEntityIdPresent() && compareEId(getObjectId(), id))
return true;
}
break;
case SetParent:
if ((objectEntityIdPresent() && compareEId(getObjectId(), id)) ||
(parentsEntityIdPresent() && (compareEId(getNewParentId(), id) || compareEId(getPreviousParentId(), id))))
return true;
break;
case UnmapString:
case AddString:
if (compareEId(asEntityId(), id))
return true;
break;
case Log:
{
if (getLogId() >= db.Logs.size())
return false;
const CLogNode& log = db.Logs[getLogId()];
const std::vector& logbuffer = getLogBuffer();
uint i;
for (i=0; i logbuffer.size())
continue;
const uint8* dataptr = (&(logbuffer[0])) + lparam.ByteOffset;
if (compareEId(*((NLMISC::CEntityId*)dataptr), id))
return true;
}
}
}
break;
case LogChat:
{
if (compareEId(asEntityId(), id))
return true;
if (_LogBuffer.empty())
break;
const NLMISC::CEntityId* ptr = (const NLMISC::CEntityId*)(&(_LogBuffer[0]));
uint num = _LogBuffer.size()/sizeof(NLMISC::CEntityId);
uint i;
for (i=0; i");
return;
}
const CDatabaseNode& db = description.getDatabaseNode();
uint contextIndent = 0;
uint msg;
std::string indent;
for (msg=0; msg<_Updates->getNumMessages(); ++msg)
{
CDbMessage& message = _Updates->getMessage(msg);
if (onlySelected && !message.Selected)
continue;
std::string result;
message.getHRContent(description, result);
log.displayNL("#$ %04X:%02X: %s", msg, message.ContextDepth, result.c_str());
}
}
/*
* Select contexts and messages containing a given entityId
*/
bool CUpdateLog::selectMessages(const CDBDescriptionParser& description, const NLMISC::CEntityId& id)
{
bool selected = false;
std::vector > contextsStart;
uint msg;
for (msg=0; msg<_Updates->getNumMessages(); ++msg)
{
CDbMessage& message = _Updates->getMessage(msg);
if (message.getType() == CDbMessage::PushContext)
{
contextsStart.push_back(std::make_pair(msg, false));
}
else if (message.getType() == CDbMessage::PopContext)
{
uint contextStart = contextsStart.back().first;
uint contextEnd = msg;
bool contextSelected = contextsStart.back().second;
contextsStart.pop_back();
if (contextSelected)
{
if (!contextsStart.empty())
contextsStart.back().second = true;
uint i;
uint level = 0;
for (i=contextStart; i<=contextEnd; ++i)
{
CDbMessage& message = _Updates->getMessage(i);
if (message.getType() == CDbMessage::PushContext) ++level;
else if (message.getType() == CDbMessage::PopContext) --level;
if (level <= 1)
message.Selected = true;
}
}
}
else if (message.contains(description, id))
{
if (!contextsStart.empty())
contextsStart.back().second = true;
message.Selected = true;
selected = true;
}
}
return selected;
}
/*
* Select contexts and messages containing a given entityId
*/
bool CUpdateLog::selectMessages(const CDBDescriptionParser& description, const std::string& str)
{
bool selected = false;
std::vector > contextsStart;
uint msg;
for (msg=0; msg<_Updates->getNumMessages(); ++msg)
{
CDbMessage& message = _Updates->getMessage(msg);
if (message.getType() == CDbMessage::PushContext)
{
contextsStart.push_back(std::make_pair(msg, false));
}
else if (message.getType() == CDbMessage::PopContext)
{
uint contextStart = contextsStart.back().first;
uint contextEnd = msg;
bool contextSelected = contextsStart.back().second;
contextsStart.pop_back();
if (contextSelected)
{
if (!contextsStart.empty())
contextsStart.back().second = true;
uint i;
uint level = 0;
for (i=contextStart; i<=contextEnd; ++i)
{
CDbMessage& message = _Updates->getMessage(i);
if (message.getType() == CDbMessage::PushContext) ++level;
else if (message.getType() == CDbMessage::PopContext) --level;
if (level <= 1)
message.Selected = true;
}
}
}
else if (message.contains(description, str))
{
if (!contextsStart.empty())
contextsStart.back().second = true;
message.Selected = true;
selected = true;
}
}
return selected;
}
/*
* Select contexts and messages containing modification of a value for a given entityId
* return true if there were at least one message selected
*/
bool CUpdateLog::selectMessages(const CDBDescriptionParser& description, const NLMISC::CEntityId& id, const std::string& valuePath)
{
bool selected = false;
uint pos = valuePath.find('.');
if (pos == std::string::npos)
return false;
std::string tableName = valuePath.substr(0, pos);
std::string columnName = valuePath.substr(pos+1);
const CDatabaseNode& db = description.getDatabaseNode();
uint table;
for (table=0; table > contextsStart;
uint msg;
for (msg=0; msg<_Updates->getNumMessages(); ++msg)
{
CDbMessage& message = _Updates->getMessage(msg);
if (message.getType() == CDbMessage::PushContext)
{
contextsStart.push_back(std::make_pair(msg, false));
}
else if (message.getType() == CDbMessage::PopContext)
{
uint contextStart = contextsStart.back().first;
uint contextEnd = msg;
bool contextSelected = contextsStart.back().second;
contextsStart.pop_back();
if (contextSelected)
{
if (!contextsStart.empty())
contextsStart.back().second = true;
uint i;
uint level = 0;
for (i=contextStart; i<=contextEnd; ++i)
{
CDbMessage& message = _Updates->getMessage(i);
if (message.getType() == CDbMessage::PushContext) ++level;
else if (message.getType() == CDbMessage::PopContext) --level;
if (level <= 1)
message.Selected = true;
}
}
}
else if (message.contains(description, id) && message.valueModified(table, column))
{
if (!contextsStart.empty())
contextsStart.back().second = true;
message.Selected = true;
selected = true;
}
}
return selected;
}
class CSelectContext
{
public:
CSelectContext() : Start(0), End(0), Selects(0) { }
~CSelectContext()
{
for (uint i=0; i SubContexts;
void spreadSelects()
{
uint i;
for (i=0; iSelects != 0)
{
SubContexts[i]->Selects |= Selects;
SubContexts[i]->spreadSelects();
}
}
}
bool select(CDbMessageQueue* queue, uint32 mask, bool first = false, bool forceEval = false)
{
// context is selected ?
if (Selects != mask && !forceEval)
return false;
bool selected = false;
uint msg, sub = 0;
for (msg=Start; msgEnd > msg)
++sub;
// if msg not in a sub context, select it
if ((sub >= SubContexts.size() || msg < SubContexts[sub]->Start) && !first)
selected |= (queue->getMessage(msg).Selected = true);
}
// select sub contexts
for (sub=0; subselect(queue, mask);
return selected;
}
};
/*
* Select contexts and messages containing a list of entityIds
* return true if there were at least one message selected
*/
bool CUpdateLog::selectMessages(const CDBDescriptionParser& description, const std::vector& ids)
{
typedef std::pair TEvalSub;
typedef std::vector TContextStack;
CSelectContext root;
CSelectContext* current = &root;
TContextStack contextStack;
contextStack.push_back(TEvalSub(&root, 0));
root.Start = 0;
root.End = _Updates->getNumMessages();
uint32 globalMask = (1 << ids.size())-1;
// rebuild context hierarchy
// and select contexts depending on ids appearing in message
uint m;
for (m=0; m<_Updates->getNumMessages(); ++m)
{
CDbMessage& msg = _Updates->getMessage(m);
// push a new context
if (msg.getType() == CDbMessage::PushContext)
{
current->SubContexts.push_back(new CSelectContext());
current = current->SubContexts.back();
contextStack.push_back(TEvalSub(current, 0));
current->Start = m;
}
// pop the current context
else if (msg.getType() == CDbMessage::PopContext)
{
contextStack.pop_back();
uint32 contextMask = current->Selects;
current->End = m+1;
current = contextStack.back().first;
++(contextStack.back().second);
current->Selects |= contextMask;
}
else
{
uint32 idMask = 1;
uint32 msgMask = 0;
uint i;
for (i=0; i 1)
current->Selects |= msgMask;
if (msgMask == globalMask)
msg.Selected = true;
}
}
root.Selects = 0;
root.spreadSelects();
return root.select(_Updates, globalMask, true, true);
}
/*
* Display log for a given entity id, between 2 dates
*/
void CUpdateLog::displayLogs(const CDBDescriptionParser& description,
const NLMISC::CEntityId& id,
const CTimestamp& begin,
const CTimestamp& end,
const std::string& path,
NLMISC::CLog& log,
float* progress)
{
if (end-begin > 86400)
{
log.displayNL("#! time interval exceeds 1 day");
return;
}
std::vector files;
NLMISC::CPath::getPathContent(path, false, false, true, files);
std::sort(files.begin(), files.end());
uint i;
// preselect files
for (i=0; i end)
{
files.erase(files.begin()+i);
--i;
continue;
}
}
for (i=0; i end)
continue;
std::vector logs;
NLMISC::CIFile file;
if (!file.open(files[i]))
continue;
bool displayedFile = false;
while (!file.eof())
{
try
{
file.serialCont(logs);
}
catch (NLMISC::Exception&)
{
break;
}
uint k;
for (k=0; k& files,
std::vector& descriptions)
{
NLMISC::CPath::getPathContent(path, false, false, true, files);
std::sort(files.begin(), files.end());
uint i;
// preselect description files
char buffer[64];
std::vector Descriptions;
for (i=0; i 1)
{
// drop first file if next one is older than begin
sscanf(NLMISC::CFile::getFilenameWithoutExtension(files[1]).c_str(), "%28s", buffer);
CTimestamp timestamp;
timestamp.fromString(buffer);
if (timestamp >= begin)
break;
files.erase(files.begin());
}
while (!files.empty())
{
sscanf(NLMISC::CFile::getFilenameWithoutExtension(files.back()).c_str(), "%28s", buffer);
CTimestamp timestamp;
timestamp.fromString(buffer);
if (timestamp <= end)
break;
files.pop_back();
}
}
/*
* Display log for a given entity id, between 2 dates
*/
void CUpdateLog::processLogs(const std::string& path,
const CTimestamp& begin,
const CTimestamp& end,
NLMISC::CLog& log,
CLogProcessor* processor,
float* progress)
{
if (end-begin > 86400)
{
log.displayNL("#! time interval exceeds 1 day");
return;
}
uint i;
std::vector files;
std::vector Descriptions;
selectLogFiles(path, begin, end, files, Descriptions);
bool selectedFiles = false;
bool selectedMessages = false;
for (i=0; i logs;
NLMISC::CIFile file;
if (!file.open(files[i]))
continue;
bool displayedFile = false;
while (!file.eof())
{
try
{
file.serialCont(logs);
}
catch (NLMISC::Exception&)
{
break;
}
if (logs.empty())
continue;
// select matching description
uint j;
for (j=0; j= Descriptions[j].Start && (j+1 >= Descriptions.size() || logs[0].StartStamp < Descriptions[j+1].Start))
break;
if (j >= Descriptions.size())
continue;
// load description (if needed)
if (!Descriptions[j].Loaded)
{
if (!Descriptions[j].Description.loadDescriptionFile(Descriptions[j].Filename))
continue;
if (!Descriptions[j].Description.buildColumns())
continue;
Descriptions[j].Loaded = true;
}
if (!Descriptions[j].Loaded)
continue;
selectedFiles = true;
uint k;
for (k=0; kprocessLog(logs[k], Descriptions[j].Description))
continue;
if (!displayedFile)
{
log.displayNL("#? In '%s':", files[i].c_str());
displayedFile = true;
}
log.displayNL("## - at %s/%s", logs[k].StartStamp.toString().c_str(), logs[k].EndStamp.toString().c_str());
selectedMessages = true;
logs[k].display(Descriptions[j].Description, log, processor != NULL);
}
}
}
if (!selectedFiles)
log.displayNL("#! No file selected");
else if (!selectedMessages)
log.displayNL("#! No message selected");
}
/*
* Display log for a given entity id, between 2 dates
*/
class CSelectIdProcessor : public CUpdateLog::CLogProcessor
{
public:
NLMISC::CEntityId Id;
virtual bool processLog(CUpdateLog& log, const CDBDescriptionParser& description)
{
return log.selectMessages(description, Id);
}
};
void CUpdateLog::displayLogs(const std::string& path,
const NLMISC::CEntityId& id,
const CTimestamp& begin,
const CTimestamp& end,
NLMISC::CLog& log,
float* progress)
{
CSelectIdProcessor p;
p.Id = id;
processLogs(path, begin, end, log, &p, progress);
}
/*
* Display log for a given entity id, between 2 dates
*/
void CUpdateLog::displayLogs(const std::string& path,
const CTimestamp& begin,
const CTimestamp& end,
NLMISC::CLog& log,
float* progress)
{
processLogs(path, begin, end, log, NULL, progress);
}
/*
* Display log for a given entity id, between 2 dates
*/
class CSelectIdValProcessor : public CUpdateLog::CLogProcessor
{
public:
NLMISC::CEntityId Id;
std::string Val;
virtual bool processLog(CUpdateLog& log, const CDBDescriptionParser& description)
{
return log.selectMessages(description, Id, Val);
}
};
void CUpdateLog::displayLogs(const std::string& path,
const NLMISC::CEntityId& id,
const std::string& valuePath,
const CTimestamp& begin,
const CTimestamp& end,
NLMISC::CLog& log,
float* progress)
{
CSelectIdValProcessor p;
p.Id = id;
p.Val = valuePath;
processLogs(path, begin, end, log, &p, progress);
}
/*
* Display log for a given entity id, between 2 dates
*/
class CSelectIdsProcessor : public CUpdateLog::CLogProcessor
{
public:
std::vector Ids;
virtual bool processLog(CUpdateLog& log, const CDBDescriptionParser& description)
{
return log.selectMessages(description, Ids);
}
};
void CUpdateLog::displayLogs(const std::string& path,
const std::vector& ids,
const CTimestamp& begin,
const CTimestamp& end,
NLMISC::CLog& log,
float* progress)
{
CSelectIdsProcessor p;
p.Ids = ids;
processLogs(path, begin, end, log, &p, progress);
}
/*
* Display log for a given entity id, between 2 dates
*/
class CSelectStrProcessor : public CUpdateLog::CLogProcessor
{
public:
std::string Str;
virtual bool processLog(CUpdateLog& log, const CDBDescriptionParser& description)
{
return log.selectMessages(description, Str);
}
};
void CUpdateLog::displayLogs(const std::string& path,
const std::string& str,
const CTimestamp& begin,
const CTimestamp& end,
NLMISC::CLog& log,
float* progress)
{
CSelectStrProcessor p;
p.Str = str;
processLogs(path, begin, end, log, &p, progress);
}
/*
* Elect matching description
*/
std::string CUpdateLog::electDescription(const std::string& logFile)
{
std::string path = NLMISC::CFile::getPath(logFile);
std::string file = NLMISC::CFile::getFilenameWithoutExtension(logFile);
CTimestamp timestamp;
timestamp.fromString(file.c_str());
std::vector files;
NLMISC::CPath::getPathContent(path, false, false, true, files);
std::sort(files.begin(), files.end());
uint elected = 0xffffffff;
for (uint i=0; i end || EndStamp < begin);
}
/*
* Is Empty
*/
bool CUpdateLog::isEmpty()
{
return _Updates == NULL || _Updates->getNumMessages() == 0;
}
CUpdateLog::~CUpdateLog()
{
releaseUpdates();
}
/*
* Set updates
*/
void CUpdateLog::setUpdates(CDbMessageQueue* updates)
{
releaseUpdates();
_Updates = updates;
_OwnUpdates = false;
}
/*
* Create updates
*/
void CUpdateLog::createUpdates()
{
releaseUpdates();
_Updates = new CDbMessageQueue();
_OwnUpdates = true;
}
}