You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ryzom-core/ryzom/client/src/string_manager_client.cpp

1799 lines
49 KiB
C++

// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010-2017 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 <http://www.gnu.org/licenses/>.
#include "stdpch.h"
#include "string_manager_client.h"
#include "nel/misc/file.h"
#include "client_cfg.h"
#include "net_manager.h"
#include "connection.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/misc/algo.h"
#include "misc.h"
#include "entity_cl.h"
#ifdef DEBUG_NEW
#define new DEBUG_NEW
#endif
using namespace std;
using namespace NLMISC;
namespace STRING_MANAGER
{
// ***************************************************************************
map<string, CStringManagerClient::CItem> CStringManagerClient::_SpecItem_TempMap;
map<string, string> CStringManagerClient::_DynStrings;
vector<string> CStringManagerClient::_TitleWords;
bool CStringManagerClient::_SpecItem_MemoryCompressed = false;
char *CStringManagerClient::_SpecItem_Labels = NULL;
char *CStringManagerClient::_SpecItem_NameDesc = NULL;
vector<CStringManagerClient::CItemLight> CStringManagerClient::_SpecItems;
bool MustReleaseStaticArrays = true;
CStringManagerClient *CStringManagerClient::_Instance= NULL;
string CStringManagerClient::_WaitString("???");
CStringManagerClient::CStringManagerClient()
{
_CacheInited = false;
_CacheLoaded = false;
// insert the empty string.
_ReceivedStrings.insert(make_pair((uint)EmptyStringId, string()));
// reserve some place to avoid reallocation as possible
_CacheStringToSave.reserve(1024);
}
// destructor.
CStringManagerClient::~CStringManagerClient()
{
if (MustReleaseStaticArrays)
{
if (_SpecItem_MemoryCompressed)
{
delete [] _SpecItem_Labels;
_SpecItem_Labels = NULL;
delete [] _SpecItem_NameDesc;
_SpecItem_NameDesc = NULL;
_SpecItems.clear();
_SpecItem_MemoryCompressed = false;
}
}
}
CStringManagerClient *CStringManagerClient::instance()
{
if (_Instance == 0)
_Instance = new CStringManagerClient();
return _Instance;
}
void CStringManagerClient::release(bool mustReleaseStaticArrays)
{
if (_Instance != 0)
{
bool prev = MustReleaseStaticArrays;
MustReleaseStaticArrays = mustReleaseStaticArrays;
delete _Instance;
MustReleaseStaticArrays = prev;
_Instance =0;
}
}
void CStringManagerClient::initCache(const std::string &shardId, const std::string &languageCode)
{
H_AUTO( CStringManagerClient_initCache )
_ShardId = shardId;
_LanguageCode = languageCode;
// to be inited, shard id and language code must be filled
if (!_ShardId.empty() && !_LanguageCode.empty())
_CacheInited = true;
else
_CacheInited = false;
}
void CStringManagerClient::loadCache(uint32 timestamp)
{
H_AUTO( CStringManagerClient_loadCache )
if (_CacheInited)
{
try
{
const uint currentVersion = 1;
_CacheFilename = std::string("save/") + _ShardId.substr(0, _ShardId.find(":")) + ".string_cache";
nlinfo("SM : Try to open the string cache : %s", _CacheFilename.c_str());
if (CFile::fileExists(_CacheFilename))
{
// there is a cache file, check date reset it if needed
{
NLMISC::CIFile file(_CacheFilename);
file.setVersionException(false, false);
file.serialVersion(currentVersion);
file.serial(_Timestamp);
}
if (_Timestamp != timestamp)
{
nlinfo("SM: Clearing string cache : outofdate");
// the cache is not sync, reset it
NLMISC::COFile file(_CacheFilename);
file.serialVersion(currentVersion);
file.serial(timestamp);
}
else
{
nlinfo("SM : string cache in sync. cool");
}
}
else
{
nlinfo("SM: Creating string cache");
// cache file don't exist, create it with the timestamp
NLMISC::COFile file(_CacheFilename);
file.serialVersion(currentVersion);
file.serial(timestamp);
}
// clear all current data.
_ReceivedStrings.clear();
_ReceivedDynStrings.clear();
// NB : we keep the waiting strings and dyn strings
// insert the empty string.
_ReceivedStrings.insert(make_pair((uint)EmptyStringId, string()));
// load the cache file
NLMISC::CIFile file(_CacheFilename);
int version = file.serialVersion(currentVersion);
file.serial(_Timestamp);
nlassert(_Timestamp == timestamp);
nlassert(version >= 1); // Initial version
while (!file.eof())
{
uint32 id;
string str;
file.serial(id);
file.serial(str);
//nldebug("SM : loading string [%6u] as [%s] in cache", id, str.toString().c_str());
_ReceivedStrings.insert(std::make_pair(id, str));
}
_CacheLoaded = true;
}
catch(const NLMISC::Exception &e)
{
nlinfo("SM : loadCache failed, exception : %s", e.what());
nlinfo("SM : cache deactivated");
// unactivated cache.
_CacheFilename.erase();
}
}
}
void CStringManagerClient::waitString(uint32 stringId, const IStringWaiterRemover *premover, string *result)
{
H_AUTO( CStringManagerClient_waitString )
nlassert(premover && result);
string value;
if (getString(stringId, value))
*result = value;
else
{
// wait for the string
TStringWaiter sw;
sw.Result = result;
sw.Remover = premover;
_StringsWaiters.insert(std::make_pair(stringId, sw));
}
}
void CStringManagerClient::waitString(uint32 stringId, IStringWaitCallback *pcallback)
{
H_AUTO( CStringManagerClient_waitString2 )
nlassert(pcallback != 0);
string value;
if (getString(stringId, value))
{
pcallback->onStringAvailable(stringId, value);
}
else
{
// wait for the string
_StringsCallbacks.insert(std::make_pair(stringId, pcallback));
}
}
void CStringManagerClient::waitDynString(uint32 stringId, const IStringWaiterRemover *premover, string *result)
{
H_AUTO( CStringManagerClient_waitDynString )
nlassert(premover && result);
string value;
if (getDynString(stringId, value))
*result = value;
else
{
// wait for the string
TStringWaiter sw;
sw.Result = result;
sw.Remover = premover;
_DynStringsWaiters.insert(std::make_pair(stringId, sw));
}
}
void CStringManagerClient::waitDynString(uint32 stringId, IStringWaitCallback *pcallback)
{
H_AUTO( CStringManagerClient_waitDynString2 )
nlassert(pcallback != 0);
string value;
if (getDynString(stringId, value))
{
pcallback->onDynStringAvailable(stringId, value);
}
else
{
_DynStringsCallbacks.insert(std::make_pair(stringId, pcallback));
}
}
void CStringManagerClient::removeStringWaiter(const IStringWaiterRemover *remover)
{
H_AUTO( CStringManagerClient_removeStringWaiter )
// search in waiting string
{
restartLoop1:
TStringWaitersContainer::iterator first(_StringsWaiters.begin()), last(_StringsWaiters.end());
for (; first != last; ++first)
{
if (first->second.Remover == remover)
{
_StringsWaiters.erase(first);
goto restartLoop1;
}
}
}
// search in waiting dyn string
{
restartLoop2:
TStringWaitersContainer::iterator first(_DynStringsWaiters.begin()), last(_DynStringsWaiters.end());
for (; first != last; ++first)
{
if (first->second.Remover == remover)
{
_DynStringsWaiters.erase(first);
goto restartLoop2;
}
}
}
}
void CStringManagerClient::removeStringWaiter(const IStringWaitCallback *callback)
{
// H_AUTO( CStringManagerClient_removeStringWaiter2 )
// search in waiting string
{
restartLoop3:
TStringCallbacksContainer::iterator first(_StringsCallbacks.begin()), last(_StringsCallbacks.end());
for (; first != last; ++first)
{
if (first->second == callback)
{
_StringsCallbacks.erase(first);
goto restartLoop3;
}
}
}
// search in waiting dyn string
{
restartLoop4:
TStringCallbacksContainer::iterator first(_DynStringsCallbacks.begin()), last(_DynStringsCallbacks.end());
for (; first != last; ++first)
{
if (first->second == callback)
{
_DynStringsCallbacks.erase(first);
goto restartLoop4;
}
}
}
}
bool CStringManagerClient::getString(uint32 stringId, string &result)
{
H_AUTO( CStringManagerClient_getString )
if (ClientCfg.Local)
{
TStringsContainer::iterator it(_ReceivedStrings.find(stringId));
if (it != _ReceivedStrings.end())
{
result = it->second;
}
else
{
result = "StringID = " + toString(stringId);
}
}
else
{
TStringsContainer::iterator it(_ReceivedStrings.find(stringId));
if (it == _ReceivedStrings.end())
{
CHashSet<uint>::iterator it(_WaitingStrings.find(stringId));
if (it == _WaitingStrings.end())
{
_WaitingStrings.insert(stringId);
// need to ask for this string.
NLMISC::CBitMemStream bms;
static const string msgType = "STRING_MANAGER:STRING_RQ";
if( GenericMsgHeaderMngr.pushNameToStream(msgType,bms) )
{
bms.serial( stringId );
NetMngr.push( bms );
//nldebug("<CStringManagerClient::getString> sending 'STRING_MANAGER:STRING_RQ' message to server");
}
else
{
nldebug("<CStringManagerClient::getString> unknown message name 'STRING_MANAGER:STRING_RQ'");
}
}
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "<WAIT STR %u>", stringId);
result = tmp;
}
else
result.erase(); // = _WaitString;
return false;
}
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "<STR %u>", stringId);
result = tmp + it->second;
}
else
{
result = it->second;
if (result.size() > 9 && result.substr(0, 9) == "<missing:")
{
map<string, string>::iterator itds = _DynStrings.find(result.substr(9, result.size()-10));
if (itds != _DynStrings.end())
result = itds->second;
}
}
}
return true;
}
void CStringManagerClient::receiveString(uint32 stringId, const string &str)
{
H_AUTO( CStringManagerClient_receiveString )
//nlinfo("String %u available : [%s]", stringId, str.toString().c_str());
CHashSet<uint>::iterator it(_WaitingStrings.find(stringId));
if (it != _WaitingStrings.end())
{
_WaitingStrings.erase(it);
}
bool updateCache = true;
if (_ReceivedStrings.find(stringId) != _ReceivedStrings.end())
{
TStringsContainer::iterator it(_ReceivedStrings.find(stringId));
nlwarning("Receiving stringID %u (%s), already in received string (%s), replacing with new value.",
stringId,
str.c_str(),
it->second.c_str());
if (it->second != str)
it->second = str;
else
updateCache = false;
}
else
{
_ReceivedStrings.insert(std::make_pair(stringId, str));
}
if (updateCache)
{
// update the string cache. DON'T SAVE now cause
if (_CacheInited && !_CacheFilename.empty())
{
CCacheString cs;
cs.StringId= stringId;
cs.String= str;
_CacheStringToSave.push_back(cs);
}
}
// update the waiting strings
{
std::pair<TStringWaitersContainer::iterator, TStringWaitersContainer::iterator> range =
_StringsWaiters.equal_range(stringId);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
TStringWaiter &sw = range.first->second;
*(sw.Result) = str;
}
_StringsWaiters.erase(stringId);
}
}
// callback the waiter
{
std::pair<TStringCallbacksContainer::iterator, TStringCallbacksContainer::iterator> range =
_StringsCallbacks.equal_range(stringId);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
range.first->second->onStringAvailable(stringId, str);
}
_StringsCallbacks.erase(stringId);
}
}
// try to complete any pending dyn string
{
TDynStringsContainer::iterator first, last;
restartLoop:
first = _WaitingDynStrings.begin();
last = _WaitingDynStrings.end();
for (; first != last; ++first)
{
string value;
uint number = first->first;
/// Warning: if getDynString() return true, 'first' is erased => don't use it after in this loop
if (getDynString(number, value))
{
//nlinfo("DynString %u available : [%s]", number, value.toString().c_str());
// this dyn string is now complete !
// update the waiting dyn strings
{
std::pair<TStringWaitersContainer::iterator, TStringWaitersContainer::iterator> range =
_DynStringsWaiters.equal_range(number);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
TStringWaiter &sw = range.first->second;
*(sw.Result) = str;
}
_DynStringsWaiters.erase(number);
}
}
// callback the waiting dyn strings
{
std::pair<TStringCallbacksContainer::iterator, TStringCallbacksContainer::iterator> range =
_DynStringsCallbacks.equal_range(number);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
range.first->second->onDynStringAvailable(number, value);
}
_DynStringsCallbacks.erase(number);
}
}
goto restartLoop;
}
}
}
}
void CStringManagerClient::flushStringCache()
{
if(!_CacheStringToSave.empty())
{
NLMISC::COFile file(_CacheFilename, true);
for(uint i=0;i<_CacheStringToSave.size();i++)
{
file.serial(_CacheStringToSave[i].StringId);
file.serial(_CacheStringToSave[i].String);
}
_CacheStringToSave.clear();
}
}
void CStringManagerClient::receiveDynString(NLMISC::CBitMemStream &bms)
{
H_AUTO( CStringManagerClient_receiveDynString )
TDynStringInfo dynInfo;
dynInfo.Status = TDynStringInfo::received;
// read the dynamic string Id
uint32 dynId;
bms.serial(dynId);
/// read the base string Id
bms.serial(dynInfo.StringId);
// try to build the string
dynInfo.Message = bms;
buildDynString(dynInfo);
if (dynInfo.Status == TDynStringInfo::complete)
{
if (!ClientCfg.Light)
{
//nlinfo("DynString %u available : [%s]", dynId, dynInfo.String.toString().c_str());
}
_ReceivedDynStrings.insert(std::make_pair(dynId, dynInfo));
// security, if dynstring Message received twice, it is possible that the dynstring is still in waiting list
_WaitingDynStrings.erase(dynId);
// update the waiting dyn strings
{
std::pair<TStringWaitersContainer::iterator, TStringWaitersContainer::iterator> range =
_DynStringsWaiters.equal_range(dynId);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
TStringWaiter &sw = range.first->second;
*(sw.Result) = dynInfo.String;
}
_DynStringsWaiters.erase(dynId);
}
}
// callback the waiting dyn strings
{
std::pair<TStringCallbacksContainer::iterator, TStringCallbacksContainer::iterator> range =
_DynStringsCallbacks.equal_range(dynId);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
range.first->second->onDynStringAvailable(dynId, dynInfo.String);
}
_DynStringsCallbacks.erase(dynId);
}
}
}
else
_WaitingDynStrings.insert(std::make_pair(dynId, dynInfo));
}
bool CStringManagerClient::buildDynString(TDynStringInfo &dynInfo)
{
H_AUTO( CStringManagerClient_buildDynString )
if (dynInfo.Status == TDynStringInfo::received)
{
if (!getString(dynInfo.StringId, dynInfo.String))
{
// can't continue now, need the base string.
return false;
}
// ok, we have the base string, we can serial the parameters
string::iterator first(dynInfo.String.begin()), last(dynInfo.String.end());
for (; first != last; ++first)
{
if (*first == '%')
{
first ++;
if (first != last && *first != '%')
{
// we have a replacement point.
TParamValue param;
param.ReplacementPoint = (first-1) - dynInfo.String.begin();
switch(*first)
{
case 's':
param.Type = string_id;
try
{
dynInfo.Message.serial(param.StringId);
}
catch(const Exception &)
{
param.StringId = EmptyStringId;
}
break;
case 'i':
param.Type = integer;
try
{
dynInfo.Message.serial(param.Integer);
}
catch(const Exception &)
{
param.Integer= 0;
}
break;
case 't':
param.Type = time;
try
{
dynInfo.Message.serial(param.Time);
}
catch(const Exception &)
{
param.Time= 0;
}
break;
case '$':
param.Type = money;
try
{
dynInfo.Message.serial(param.Money);
}
catch(const Exception &)
{
param.Money= 0;
}
break;
case 'm':
param.Type = dyn_string_id;
try
{
dynInfo.Message.serial(param.DynStringId);
}
catch(const Exception &)
{
param.DynStringId= EmptyDynStringId;
}
break;
default:
nlwarning("Error: unknown replacement tag %%%c", (char)*first);
return false;
}
dynInfo.Params.push_back(param);
}
}
}
dynInfo.Status = TDynStringInfo::serialized;
}
if (dynInfo.Status == TDynStringInfo::serialized)
{
// try to retreive all string parameter to build the string.
string temp;
temp.reserve(dynInfo.String.size() * 2);
string::iterator src(dynInfo.String.begin());
string::iterator move = src;
std::vector<TParamValue>::iterator first(dynInfo.Params.begin()), last(dynInfo.Params.end());
for (; first != last; ++first)
{
TParamValue &param = *first;
switch(param.Type)
{
case string_id:
{
string str;
if (!getString(param.StringId, str))
return false;
ucstring::size_type p1 = str.find('[');
if (p1 != ucstring::npos)
{
str = str.substr(0, p1)+STRING_MANAGER::CStringManagerClient::getLocalizedName(str.substr(p1));
}
// If the string is a player name, we may have to remove the shard name (if the string looks like a player name)
if(!str.empty() && !PlayerSelectedHomeShardNameWithParenthesis.empty())
{
// fast pre-test
if( str[str.size()-1]==')' )
{
// the player name must be at least bigger than the string with ()
if(str.size()>PlayerSelectedHomeShardNameWithParenthesis.size())
{
// If the shard name is the same as the player home shard name, remove it
uint len= (uint)PlayerSelectedHomeShardNameWithParenthesis.size();
uint start= (uint)str.size()-len;
if(ucstrnicmp(str, start, len, PlayerSelectedHomeShardNameWithParenthesis)==0) // TODO: NLMISC::compareCaseInsensitive
str.resize(start);
}
}
}
// If the string contains a title, then remove it
string::size_type pos = str.find('$');
if ( ! str.empty() && pos != string::npos)
{
str = CEntityCL::removeTitleFromName(str);
}
// append this string
temp.append(move, src+param.ReplacementPoint);
temp += str;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
break;
case integer:
{
char value[1024];
sprintf(value, "%d", param.Integer);
temp.append(move, src+param.ReplacementPoint);
temp += value;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
break;
case time:
{
string value;
uint32 time = (uint32)param.Time;
if( time >= (10*60*60) )
{
uint32 nbHours = time / (10*60*60);
time -= nbHours * 10 * 60 * 60;
value = toString("%d ", nbHours) + CI18N::get("uiMissionTimerHour") + " ";
uint32 nbMinutes = time / (10*60);
time -= nbMinutes * 10 * 60;
value = value + toString("%d ", nbMinutes) + CI18N::get("uiMissionTimerMinute") + " ";
}
else if( time >= (10*60) )
{
uint32 nbMinutes = time / (10*60);
time -= nbMinutes * 10 * 60;
value = value + toString("%d ", nbMinutes) + CI18N::get("uiMissionTimerMinute") + " ";
}
uint32 nbSeconds = time / 10;
value = value + toString("%d", nbSeconds) + CI18N::get("uiMissionTimerSecond");
temp.append(move, src+param.ReplacementPoint);
temp+=value;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
break;
case money:
///\todo nicoB/Boris : this is a temp patch that display money as integers
{
char value[1024];
sprintf(value, "%u", (uint32)param.Money);
temp.append(move, src+param.ReplacementPoint);
temp += value;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
// TODO
// temp.append(move, src+param.ReplacementPoint);
// move = dynInfo.String.begin()+param.ReplacementPoint+2;
break;
case dyn_string_id:
{
string dynStr;
if (!getDynString(param.DynStringId, dynStr))
return false;
temp.append(move, src+param.ReplacementPoint);
temp += dynStr;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
break;
default:
nlwarning("Unknown parameter type.");
break;
}
}
// append the rest of the string
temp.append(move, dynInfo.String.end());
// apply any 'delete' character in the string and replace double '%'
{
ptrdiff_t i =0;
while (i < (ptrdiff_t)temp.size())
{
if (temp[i] == 8)
{
// remove the 'delete' char AND the next char
temp.erase(i, 2);
}
else if (temp[i] == '%' && i < temp.size()-1 && temp[i+1] == '%')
{
temp.erase(i, 1);
}
else
++i;
}
}
dynInfo.Status = TDynStringInfo::complete;
dynInfo.Message.clear();
dynInfo.String = temp;
return true;
}
if (dynInfo.Status == TDynStringInfo::complete)
return true;
nlwarning("Inconsistent dyn string status : %u", dynInfo.Status);
return false;
}
bool CStringManagerClient::getDynString(uint32 dynStringId, std::string &result)
{
H_AUTO( CStringManagerClient_getDynString )
if (dynStringId == EmptyDynStringId)
return true;
if (ClientCfg.Local)
{
TDynStringsContainer::iterator it(_ReceivedDynStrings.find(dynStringId));
if (it != _ReceivedDynStrings.end())
{
result = it->second.String;
}
else
{
result = "DYNSTR = " + toString(dynStringId);
}
return true;
}
else
{
TDynStringsContainer::iterator it(_ReceivedDynStrings.find(dynStringId));
if (it != _ReceivedDynStrings.end())
{
// ok, we have the string with all the parts.
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "<DYNSTR %u>", dynStringId);
result = tmp + it->second.String;
}
else
result = it->second.String;
// security/antiloop checking
it = _WaitingDynStrings.find(dynStringId);
if (it != _WaitingDynStrings.end())
{
nlwarning("CStringManager::getDynString : the string %u is received but still in _WaintingDynStrings !", dynStringId);
_WaitingDynStrings.erase(it);
}
return true;
}
else
{
// check to see if the string is available now.
it = _WaitingDynStrings.find(dynStringId);
if (it == _WaitingDynStrings.end())
{
if (ClientCfg.DebugStringManager)
{
nlwarning("DynStringID %u is unknown !", dynStringId);
char tmp[1024];
sprintf(tmp, "<UNKNOWN DYNSTR %u>", dynStringId);
result = tmp;
}
else
result.erase(); //_WaitString;
return false;
}
if (buildDynString(it->second))
{
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "<DYNSTR %u>", dynStringId);
result = tmp + it->second.String;
}
else
result = it->second.String;
_ReceivedDynStrings.insert(std::make_pair(dynStringId, it->second));
_WaitingDynStrings.erase(it);
return true;
}
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "<WAIT DYNSTR %u>", dynStringId);
result = tmp;
}
else
result.erase(); // = _WaitString;
return false;
}
}
}
// Tool fct to lookup a reference file
static string lookupReferenceFile(const string &fileName)
{
string referenceFile;
// special location for the "wk" language
if(ClientCfg.LanguageCode=="wk")
{
referenceFile = "./translation/translated/"+CFile::getFilename(fileName);
if (!CFile::fileExists(referenceFile))
{
nlwarning("Translation file: %s not found", referenceFile.c_str());
referenceFile.clear();
}
}
else
{
referenceFile = CPath::lookup(fileName, false);
}
return referenceFile;
}
void CLoadProxy::loadStringFile(const string &filename, ucstring &text)
{
vector<TStringInfo> reference;
vector<TStringInfo> addition;
vector<TStringInfo> diff;
// get the correct path name of the ref file
string referenceFile= lookupReferenceFile(filename);
// load the reference file
if (!referenceFile.empty())
{
STRING_MANAGER::loadStringFile(referenceFile, reference, false);
}
// try to find the working file
string workingFile("./translation/work/"+CFile::getFilename(filename));
if (CFile::fileExists(workingFile))
{
STRING_MANAGER::loadStringFile(workingFile, addition, false);
}
else
{
// no addition file ? just copy the reference into addition
addition = reference;
}
TStringDiffContext context(addition, reference, diff);
TStringDiff differ;
differ.makeDiff(this, context);
text.erase();
text = prepareStringFile(context.Diff, true);
}
void CLoadProxy::onEquivalent(uint addIndex, uint /* refIndex */, TStringDiffContext &context)
{
context.Diff.push_back(context.Addition[addIndex]);
}
void CLoadProxy::onAdd(uint addIndex, uint /* refIndex */, TStringDiffContext &context)
{
context.Diff.push_back(context.Addition[addIndex]);
//nldebug("Adding new string '%s' in CI18N", context.Addition[addIndex].Identifier.c_str());
if (ClientCfg.DebugStringManager)
context.Diff.back().Text = ucstring("<NEW>")+context.Diff.back().Text;
}
void CLoadProxy::onRemove(uint /* addIndex */, uint /* refIndex */, TStringDiffContext &/* context */)
{
// nothing to do because we don't insert bad value
}
void CLoadProxy::onChanged(uint addIndex, uint /* refIndex */, TStringDiffContext &context)
{
// we use the addition value in this case
context.Diff.push_back(context.Addition[addIndex]);
//nldebug("Using changed string '%s' in CI18N", context.Addition[addIndex].Identifier.c_str());
if (ClientCfg.DebugStringManager)
context.Diff.back().Text = ucstring("<CHG>")+context.Diff.back().Text;
}
void CLoadProxy::onSwap(uint /* newIndex */, uint /* refIndex */, TStringDiffContext &/* context */)
{
// don't swap.
}
// ***************************************************************************
/*
The readed for skill_word_en.txt for example. Do all the job of Diffs with work and translated dirs.
*/
class CReadWorkSheetFile : public TWorkSheetDiff::IDiffCallback
{
public:
void readWorkSheetFile(const string &filename, ucstring &text)
{
TWorksheet addition;
TWorksheet reference;
TWorksheet diff;
// get the correct path name of the ref file
string referenceFile= lookupReferenceFile(filename);
// load the reference file
if (!referenceFile.empty())
{
STRING_MANAGER::loadExcelSheet(referenceFile, reference);
STRING_MANAGER::makeHashCode(reference, false);
}
// try to find the working file
string workingFile("./translation/work/"+CFile::getFilename(filename));
if (CFile::fileExists(workingFile))
{
STRING_MANAGER::loadExcelSheet(workingFile, addition);
STRING_MANAGER::makeHashCode(addition, true);
}
else
{
text = prepareExcelSheet(reference);
return;
}
if (addition.size() == 0)
return;
// check column consistency.
bool doDiff = true;
uint i;
if (reference.ColCount != addition.ColCount)
{
nlwarning("Can't check for difference for file %s, column number is not the same!", filename.c_str());
doDiff = false;
}
if (doDiff)
{
// check eachg column name
for (i=0; i<addition.ColCount; ++i)
{
if (addition.getData(0, i) != reference.getData(0, i))
{
nlwarning("Can't check difference for file %s, collumn name differ !", filename.c_str());
doDiff = false;
break;
}
}
if (doDiff)
{
// create the column header.
while (diff.ColCount < addition.ColCount)
diff.insertColumn(0);
diff.push_back(*addition.begin());
TWordsDiffContext context(addition, reference, diff);
TWorkSheetDiff differ;
differ.makeDiff(this, context, true);
}
}
if (!doDiff)
{
diff = reference;
}
text = prepareExcelSheet(diff);
}
void onEquivalent(uint /* addIndex */, uint refIndex, TWordsDiffContext &context)
{
context.Diff.push_back(context.Reference[refIndex]);
}
void onAdd(uint addIndex, uint /* refIndex */, TWordsDiffContext &context)
{
context.Diff.push_back(context.Addition[addIndex]);
nlinfo("Using newly sheet row %s", context.Diff.getData(context.Diff.size()-1, 1).toString().c_str());
}
void onRemove(uint /* addIndex */, uint /* refIndex */, TWordsDiffContext &/* context */)
{
// nothing to do because we don't insert bad value
}
void onChanged(uint addIndex, uint /* refIndex */, TWordsDiffContext &context)
{
context.Diff.push_back(context.Addition[addIndex]);
nlinfo("Using changed sheet row %s", context.Diff.getData(context.Diff.size()-1, 1).toString().c_str());
}
void onSwap(uint /* newIndex */, uint /* refIndex */, TWordsDiffContext &/* context */)
{
// don't swap.
}
};
// ***************************************************************************
const string StringClientPackedFileName= "./save/string_client.pack";
// Must Increment this number if change are made to the code (else change not taken into account)
const uint StringClientPackedVersion= 0;
bool CStringManagerClient::checkWordFileDates(vector<CFileCheck> &fileChecks, const std::vector<string> &fileNames, const string &languageCode)
{
fileChecks.resize(fileNames.size());
// **** First get the date of all the .txt files.
for(uint i=0;i<fileChecks.size();i++)
{
// get the correct path name of the ref file
string referenceFile= lookupReferenceFile(fileNames[i]);
fileChecks[i].ReferenceDate= referenceFile.empty()?0:CFile::getFileModificationDate(referenceFile);
// get then one of the working File (NB: 0 is a valid reponse for Final Client: no working file)
string workingFile("./translation/work/"+CFile::getFilename(fileNames[i]));
fileChecks[i].AdditionDate= CPath::exists(workingFile)?CFile::getFileModificationDate(workingFile):0;
}
// **** Compare with the packed file
CIFile packFile;
if(packFile.open(StringClientPackedFileName))
{
CPackHeader packHeader;
packFile.serial(packHeader);
// Compare!
return fileChecks == packHeader.FileChecks &&
languageCode == packHeader.LanguageCode &&
StringClientPackedVersion == packHeader.PackedVersion;
}
else
// must rebuild!
return false;
}
// ***************************************************************************
void CStringManagerClient::initI18NSpecialWords(const string &languageCode)
{
ucstring womenNameColIdent = ucstring("women_name");
ucstring descColIdent = ucstring("description");
ucstring descColIdent2 = ucstring("description2");
// List of words to append to the local CI18N system.
static const char *specialWords[]=
{
// The first is the name of the file. Second is the Key column identifier.
// 3rd is the extenstion to add to the key for correct matchup (sheet cases)
"skill", "skill ID", "",
"faction", "faction", "",
"place", "placeId", "",
"item", "item ID", ".sitem",
"creature", "creature ID", ".creature",
"sbrick", "sbrick ID", ".sbrick",
"sphrase", "sphrase ID", ".sphrase",
"title", "title_id", "",
"classificationtype", "classification_type", "",
"outpost", "outpost ID", "",
};
uint numSpecialWords= sizeof(specialWords) / (3*sizeof(specialWords[0]));
// Build the file names
vector<string> fileNames;
fileNames.resize(numSpecialWords);
for(uint i=0;i<numSpecialWords;i++)
{
fileNames[i]= string(specialWords[i*3+0]) + "_words_" + languageCode + ".txt";
}
// **** Check the file modification dates.
vector<CFileCheck> fileChecks;
bool mustRebuild= !checkWordFileDates(fileChecks, fileNames, languageCode);
// **** rebuild or load?
if(mustRebuild)
{
for(uint i=0;i<numSpecialWords;i++)
{
uint32 profile0= (uint32)ryzomGetLocalTime();
ucstring ucs;
string fileName = fileNames[i];
string keyExtenstion = specialWords[i*3+2];
// read the ucstring and make diffs with data in ./translation/work.
CReadWorkSheetFile rwsf;
rwsf.readWorkSheetFile(fileName, ucs);
if(ucs.empty())
continue;
// transform the text into a WorkSheet.
TWorksheet ws;
STRING_MANAGER::readExcelSheet(ucs, ws);
// Get the Key and Data ColIndex.
uint nameColIndex = 0, keyColIndex = 0;
if( !ws.findCol(ucstring("name"), nameColIndex) )
continue;
if( !ws.findCol(ucstring(specialWords[i*3+1]), keyColIndex) )
continue;
// Get the women name index if possible.
uint womenNameColIndex = std::numeric_limits<uint>::max();
if( !ws.findCol(womenNameColIdent, womenNameColIndex) )
womenNameColIndex= std::numeric_limits<uint>::max();
// Get the description index if possible.
uint descColIndex = std::numeric_limits<uint>::max();
if( !ws.findCol(descColIdent, descColIndex) )
descColIndex= std::numeric_limits<uint>::max();
uint descColIndex2 = std::numeric_limits<uint>::max();
if( !ws.findCol(descColIdent2, descColIndex2) )
descColIndex2= std::numeric_limits<uint>::max();
// For all rows minus the first header one.
for(uint j=1;j<ws.size();j++)
{
// Get the key and name string.
string key= ws.getData(j, keyColIndex).toUtf8(); // FIXME: const string & when UTF-8
string name= ws.getData(j, nameColIndex).toUtf8(); // FIXME: const string & when UTF-8
// Append to the I18N.
// avoid case problems
string keyStr = NLMISC::toLower(key);
// append the special key extension.
keyStr+= keyExtenstion;
// insert in map.
std::map<string, CItem>::iterator it;
it= _SpecItem_TempMap.find( keyStr );
if ( it!=_SpecItem_TempMap.end() )
{
nlwarning("Error in initI18NSpecialWords(), %s already exist (not replaced)!", keyStr.c_str());
}
else
{
_SpecItem_TempMap[keyStr].Name= name;
// replace all \n in the desc with true \n
while(strFindReplace(_SpecItem_TempMap[keyStr].Name, "\\n", "\n"));
// insert in map of Women Name if OK.
if(womenNameColIndex!=std::numeric_limits<uint>::max())
{
const ucstring &womenName= ws.getData(j, womenNameColIndex);
_SpecItem_TempMap[keyStr].WomenName= womenName.toUtf8();
// replace all \n in the women name with true \n
while(strFindReplace(_SpecItem_TempMap[keyStr].WomenName, "\\n", "\n"));
}
// insert in map of Description if OK.
if(descColIndex!=std::numeric_limits<uint>::max())
{
const ucstring &desc= ws.getData(j, descColIndex);
_SpecItem_TempMap[keyStr].Desc= desc.toUtf8();
// replace all \n in the desc with true \n
while(strFindReplace(_SpecItem_TempMap[keyStr].Desc, "\\n", "\n"));
}
// insert in map of Description2 if OK.
if(descColIndex2!=std::numeric_limits<uint>::max())
{
const ucstring &desc= ws.getData(j, descColIndex2);
_SpecItem_TempMap[keyStr].Desc2= desc.toUtf8();
// replace all \n in the desc with true \n
while(strFindReplace(_SpecItem_TempMap[keyStr].Desc2, "\\n", "\n"));
}
}
}
nlinfo ("%d seconds for I18N words: Total: %s", (uint32)(ryzomGetLocalTime()-profile0+500)/1000, fileName.c_str());
}
}
else
{
CIFile packFile;
nlverify(packFile.open(StringClientPackedFileName));
// Serial the header
CPackHeader packHeader;
packFile.serial(packHeader);
// Serial the map
packFile.serialCont(_SpecItem_TempMap);
}
// **** Save if rebuilt
if(mustRebuild)
{
COFile packFile;
if(packFile.open(StringClientPackedFileName))
{
// Write the header
CPackHeader packHeader;
packHeader.LanguageCode= languageCode;
packHeader.FileChecks= fileChecks;
packHeader.PackedVersion= StringClientPackedVersion;
packFile.serial(packHeader);
// Serial the map
packFile.serialCont(_SpecItem_TempMap);
}
}
}
// ***************************************************************************
void CStringManagerClient::specialWordsMemoryCompress()
{
// Convert map to the light structure
// Count
uint32 nNbEntries = 0, nLabelSize = 0, nNameDescSize = 0;
map<string, CItem>::iterator it = _SpecItem_TempMap.begin();
while (it != _SpecItem_TempMap.end())
{
nNbEntries++;
nLabelSize += (uint32)it->first.size() + 1;
nNameDescSize += (uint32)it->second.Name.size() + 1;
nNameDescSize += (uint32)it->second.WomenName.size() + 1;
nNameDescSize += (uint32)it->second.Desc.size() + 1;
nNameDescSize += (uint32)it->second.Desc2.size() + 1;
it++;
}
// Make big strings
_SpecItems.resize(nNbEntries);
_SpecItem_Labels = new char[nLabelSize];
_SpecItem_NameDesc = new char[nNameDescSize];
nNbEntries = 0;
nLabelSize = 0;
nNameDescSize = 0;
it = _SpecItem_TempMap.begin();
while (it != _SpecItem_TempMap.end())
{
if (NLMISC::startsWith(it->first.c_str(), "bf"))
{
uint nDbg = 0;
nDbg++;
}
_SpecItems[nNbEntries].Label = _SpecItem_Labels+nLabelSize;
strcpy(_SpecItem_Labels+nLabelSize, it->first.c_str());
nLabelSize += (uint32)it->first.size() + 1;
_SpecItems[nNbEntries].Name = _SpecItem_NameDesc+nNameDescSize;
strcpy(_SpecItem_NameDesc+nNameDescSize, it->second.Name.c_str());
nNameDescSize += (uint32)it->second.Name.size() + 1;
_SpecItems[nNbEntries].WomenName = _SpecItem_NameDesc+nNameDescSize;
strcpy(_SpecItem_NameDesc+nNameDescSize, it->second.WomenName.c_str());
nNameDescSize += (uint32)it->second.WomenName.size() + 1;
_SpecItems[nNbEntries].Desc = _SpecItem_NameDesc+nNameDescSize;
strcpy(_SpecItem_NameDesc+nNameDescSize, it->second.Desc.c_str());
nNameDescSize += (uint32)it->second.Desc.size() + 1;
_SpecItems[nNbEntries].Desc2 = _SpecItem_NameDesc+nNameDescSize;
strcpy(_SpecItem_NameDesc+nNameDescSize, it->second.Desc2.c_str());
nNameDescSize += (uint32)it->second.Desc2.size() + 1;
nNbEntries++;
it++;
}
// No need to sort the vector because the map was already sorted in the good order
contReset(_SpecItem_TempMap);
_SpecItem_MemoryCompressed = true;
}
// ***************************************************************************
const char *CStringManagerClient::getSpecialWord(const string &label, bool women)
{
if (label.empty())
{
static string emptyString;
return emptyString.c_str();
}
if (label[0] == '#')
{
return getLocalizedName(label.substr(1, label.size()-1));
}
// avoid case problems
static string lwrLabel;
lwrLabel = toLower(label);
if (_SpecItem_MemoryCompressed)
{
CItemLight tmp;
tmp.Label = (char*)lwrLabel.c_str();
vector<CItemLight>::iterator it = lower_bound(_SpecItems.begin(), _SpecItems.end(), tmp, CItemLightComp());
if (it != _SpecItems.end())
{
if (strcmp(it->Label, lwrLabel.c_str()) == 0)
{
if( UseFemaleTitles && women )
{
if( !it->WomenName[0] )
return it->WomenName;
}
return it->Name;
}
}
}
else
{
map<string,CItem>::iterator it = _SpecItem_TempMap.find(lwrLabel);
if (it != _SpecItem_TempMap.end())
{
if( UseFemaleTitles && women )
if (!it->second.WomenName.empty())
return it->second.WomenName.c_str();
return it->second.Name.c_str();
}
}
static string badString;
badString = "<NotExist:" + lwrLabel + ">";
return badString.c_str();
}
// ***************************************************************************
const char *CStringManagerClient::getSpecialDesc(const string &label)
{
static string emptyString;
if (label.empty())
return emptyString.c_str();
// avoid case problems
static string lwrLabel;
lwrLabel = toLower(label);
if (_SpecItem_MemoryCompressed)
{
CItemLight tmp;
tmp.Label = lwrLabel.c_str();
vector<CItemLight>::iterator it = lower_bound(_SpecItems.begin(), _SpecItems.end(), tmp, CItemLightComp());
if (it != _SpecItems.end())
{
if (strcmp(it->Label, lwrLabel.c_str()) == 0)
return it->Desc;
}
}
else
{
map<string,CItem>::iterator it = _SpecItem_TempMap.find(lwrLabel);
if (it != _SpecItem_TempMap.end())
return it->second.Desc.c_str();
}
return emptyString.c_str();
}
// ***************************************************************************
const char *CStringManagerClient::getSpecialDesc2(const string &label)
{
static string emptyString;
if (label.empty())
return emptyString.c_str();
// avoid case problems
static string lwrLabel;
lwrLabel = toLower(label);
if (_SpecItem_MemoryCompressed)
{
CItemLight tmp;
tmp.Label = lwrLabel.c_str();
vector<CItemLight>::iterator it = lower_bound(_SpecItems.begin(), _SpecItems.end(), tmp, CItemLightComp());
if (it != _SpecItems.end())
{
if (strcmp(it->Label, lwrLabel.c_str()) == 0)
return it->Desc2;
}
}
else
{
map<string,CItem>::iterator it = _SpecItem_TempMap.find(lwrLabel);
if (it != _SpecItem_TempMap.end())
{
return it->second.Desc2.c_str();
}
}
return emptyString.c_str();
}
// ***************************************************************************
/*const ucchar *CStringManagerClient::getBrickLocalizedName(BRICK_FAMILIES::TBrickFamily e)
{
return getSpecialWord(BRICK_FAMILIES::toString(e));
}
*/
// ***************************************************************************
const char *CStringManagerClient::getPlaceLocalizedName(const string &placeNameID)
{
return getSpecialWord(placeNameID);
}
// ***************************************************************************
const char *CStringManagerClient::getFactionLocalizedName(const string &factionNameID)
{
return getSpecialWord(factionNameID);
}
// ***************************************************************************
const char *CStringManagerClient::getSkillLocalizedName(SKILLS::ESkills e)
{
return getSpecialWord(SKILLS::toString(e));
}
// ***************************************************************************
const char *CStringManagerClient::getItemLocalizedName(CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getCreatureLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSBrickLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSPhraseLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
/*const char *CStringManagerClient::getBrickLocalizedDescription(BRICK_FAMILIES::TBrickFamily e)
{
return getSpecialDesc(BRICK_FAMILIES::toString(e));
}
*/
// ***************************************************************************
const char *CStringManagerClient::getSkillLocalizedDescription(SKILLS::ESkills e)
{
return getSpecialDesc(SKILLS::toString(e));
}
// ***************************************************************************
const char *CStringManagerClient::getItemLocalizedDescription(CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSBrickLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSBrickLocalizedCompositionDescription(NLMISC::CSheetId id)
{
return getSpecialDesc2(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSPhraseLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getTitleLocalizedName(const string &titleId, bool women)
{
vector<string> listInfos = getTitleInfos(titleId, women);
if (!listInfos.empty())
{
_TitleWords.push_back(listInfos[0]);
return getLocalizedName(_TitleWords.back());
}
return getLocalizedName(titleId);
}
const ucchar *CStringManagerClient::getLocalizedName(const string &uctext)
{
string text = uctext;
if (text[0] == '[')
{
vector<string> textLocalizations;
static string defaultText;
splitString(text.substr(1), "[", textLocalizations);
if (!textLocalizations.empty())
{
for(uint i = 0; i<textLocalizations.size(); i++)
{
if (textLocalizations[i].substr(0, 3) == CI18N::getCurrentLanguageCode()+"]")
{
defaultText = textLocalizations[i].substr(3);
return defaultText.c_str();
}
else if (textLocalizations[i].substr(0, 3) == "wk]")
{
defaultText = extLocalizations[i].substr(3);
}
}
}
if (!defaultText.empty()) {
return defaultText.c_str();
}
}
return uctext.c_str();
}
// ***************************************************************************
vector<string> CStringManagerClient::getTitleInfos(const string &titleId, bool women)
{
vector<string> listInfos;
splitString(titleId, string("#"), listInfos);
if (!listInfos.empty())
{
if (titleId[0] != '#')
{
listInfos[0] = getSpecialWord(listInfos[0], women);
}
}
return listInfos;
}
// ***************************************************************************
const char *CStringManagerClient::getClassificationTypeLocalizedName(EGSPD::CClassificationType::TClassificationType type)
{
return getSpecialDesc(EGSPD::CClassificationType::toString(type));
}
// ***************************************************************************
const char *CStringManagerClient::getOutpostLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getOutpostLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getOutpostBuildingLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getOutpostBuildingLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSquadLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSquadLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
void CStringManagerClient::replaceDynString(const std::string &name, const std::string &text)
{
_DynStrings[name] = text;
}
// ***************************************************************************
void CStringManagerClient::replaceSBrickName(NLMISC::CSheetId id, const std::string &name, const std::string &desc, const std::string &desc2)
{
string label= id.toString();
if (label.empty())
{
return;
}
// avoid case problems
static string lwrLabel;
lwrLabel = toLower(label);
nlassert(!_SpecItem_MemoryCompressed); // Not allowed, strings are released!
if (_SpecItem_MemoryCompressed)
{
#if 0
const char *strName = name.c_str();
const char *strDesc = desc.c_str();
const char *strDesc2 = desc2.c_str();
CItemLight tmp;
tmp.Label = lwrLabel.c_str();
vector<CItemLight>::iterator it = lower_bound(_SpecItems.begin(), _SpecItems.end(), tmp, CItemLightComp());
if (it != _SpecItems.end())
{
if (strcmp(it->Label, lwrLabel.c_str()) == 0)
{
it->Name = strName;
it->Desc = strDesc;
it->Desc2 = strDesc2;
}
else
{
it->Label = tmp.Label;
it->Name = strName;
it->Desc = strDesc;
it->Desc2 = strDesc2;
}
}
else
{
tmp.Name = strName;
tmp.Desc = strDesc;
tmp.Desc2 = strDesc2;
_SpecItems.push_back(tmp);
}
#endif
}
else
{
map<string, CItem>::iterator it(_SpecItem_TempMap.find(lwrLabel));
if (it != _SpecItem_TempMap.end())
{
it->second.Name= name;
it->second.Desc= desc;
it->second.Desc2= desc2;
}
else
{
CItem newItem;
newItem.Name = name;
newItem.Desc = desc;
newItem.Desc2 = desc2;
_SpecItem_TempMap.insert(pair<string,CItem>(lwrLabel,newItem));
}
}
}
} // namespace STRING_MANAGER