search_hash.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /*
  2. * Copyright (c) 2009 Openmoko Inc.
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include <stdio.h>
  18. #include <string.h>
  19. #include <ctype.h>
  20. #include <stddef.h>
  21. #include <stdlib.h>
  22. #include <errno.h>
  23. #ifdef WIKIPCF
  24. #include <malloc.h>
  25. #else
  26. #include <malloc-simple.h>
  27. #include <file-io.h>
  28. #include <input.h>
  29. #include "msg.h"
  30. #include "search.h"
  31. #include "wiki_info.h"
  32. #endif
  33. #include "search_hash.h"
  34. #include "lcd_buf_draw.h"
  35. // pedia.hsh format:
  36. // The first 4 bytes contain the hash entry count
  37. // Each hash entry is defined as struct SEARCH_HASH_TABLE
  38. #ifdef WIKIPCF
  39. //SEARCH_HASH_TABLE *search_hash_table;
  40. //SEARCH_HASH_STRING *search_hash_strings;
  41. //uint32_t nHashEntries = 0;
  42. //FILE *fdHsh;
  43. long nNeedMoreEntries = 0;
  44. #else
  45. extern int search_interrupted;
  46. int bHashInited[MAX_WIKIS];
  47. //SEARCH_HASH_TABLE *search_hash_table[MAX_WIKIS];
  48. //SEARCH_HASH_STRING *search_hash_strings[MAX_WIKIS];
  49. //uint32_t nHashEntries[MAX_WIKIS];
  50. //int fdHsh[MAX_WIKIS];
  51. int fdFnd[MAX_WIKIS];
  52. int *bHashBlockLoaded[MAX_WIKIS];
  53. #define FND_BUF_COUNT 1024
  54. #define ENTRIES_PER_HASH_BLOCK 256
  55. // FND_BUF_BLOCK_SIZE needs to be larger than MAX_RESULTS * sizeof(TITLE_SEARCH)
  56. #define FND_BUF_BLOCK_SIZE 2048
  57. struct __attribute__ ((packed)) _fnd_buf {
  58. uint32_t offset;
  59. uint32_t len;
  60. uint32_t used_seq;
  61. char buf[FND_BUF_BLOCK_SIZE];
  62. } *fnd_bufs[MAX_WIKIS];
  63. long nUsedSeq = 1;
  64. long lenFnd[MAX_WIKIS];
  65. #endif
  66. //static unsigned long hash_key(char *s, int len)
  67. //{
  68. // unsigned long hash = 5381;
  69. // char str[MAX_SEARCH_STRING_HASHED_LEN + 1];
  70. // int i;
  71. //
  72. // if (len > MAX_SEARCH_STRING_HASHED_LEN)
  73. // len = MAX_SEARCH_STRING_HASHED_LEN;
  74. // memcpy(str, s, len);
  75. // str[len] = '\0';
  76. // i = 0;
  77. // while(i < len) {
  78. // int c = str[i];
  79. // if ('A' <= c && c <='Z')
  80. // c += 32;
  81. // hash = ((hash << 5) + hash) + c;
  82. // i++;
  83. // }
  84. // return hash % MAX_SEARCH_HASH_KEY;
  85. //}
  86. //
  87. #ifdef WIKIPCF
  88. //void create_search_hash(const char *filename)
  89. //{
  90. // search_hash_table = malloc(sizeof(SEARCH_HASH_TABLE) * MAX_SEARCH_HASH_TABLE_ENTRIES);
  91. // search_hash_strings = malloc(sizeof(SEARCH_HASH_STRING) * MAX_SEARCH_HASH_TABLE_ENTRIES);
  92. // if (!search_hash_table || !search_hash_strings)
  93. // {
  94. // printf("malloc search_hash_table/search_hash_strings error\n");
  95. // exit(-1);
  96. // }
  97. // nHashEntries = MAX_SEARCH_HASH_KEY;
  98. // memset(search_hash_table, 0, sizeof(SEARCH_HASH_TABLE) * MAX_SEARCH_HASH_TABLE_ENTRIES);
  99. // fdHsh = fopen(filename, "wb");
  100. // if (!fdHsh)
  101. // {
  102. // printf("cannot open file '%s', error: %s\n", filename, strerror(errno));
  103. // exit(-1);
  104. // }
  105. //}
  106. //
  107. //long add_search_hash(char *sInput, int len, long offset_fnd)
  108. //{
  109. // long nHashKey;
  110. // char sSearchString[MAX_SEARCH_STRING_HASHED_LEN + 1];
  111. //
  112. // memcpy(sSearchString, sInput, len);
  113. // sSearchString[len] = '\0';
  114. // nHashKey = hash_key(sSearchString, len);
  115. // if (search_hash_table[nHashKey].offset_fnd)
  116. // {
  117. // if (strcmp(search_hash_strings[nHashKey].str, sSearchString))
  118. // {
  119. // int bFound = 0;
  120. // while (!bFound && (search_hash_table[nHashKey].next_entry_idx & 0x0FFFFFFF))
  121. // {
  122. // nHashKey = search_hash_table[nHashKey].next_entry_idx & 0x0FFFFFFF;
  123. // if (!strcmp(search_hash_strings[nHashKey].str, sSearchString))
  124. // bFound = 1;
  125. // }
  126. // if (!bFound)
  127. // {
  128. // if (nHashEntries >= MAX_SEARCH_HASH_TABLE_ENTRIES)
  129. // {
  130. // nNeedMoreEntries++;
  131. // }
  132. // else
  133. // {
  134. // search_hash_table[nHashKey].next_entry_idx |= nHashEntries;
  135. // search_hash_table[nHashEntries].next_entry_idx = len << 28;
  136. // search_hash_table[nHashEntries].offset_fnd = offset_fnd;
  137. // strncpy(search_hash_strings[nHashEntries].str, sSearchString, MAX_SEARCH_STRING_HASHED_LEN);
  138. // search_hash_strings[nHashEntries].str[MAX_SEARCH_STRING_HASHED_LEN] = '\0';
  139. // nHashEntries++;
  140. // }
  141. // }
  142. // }
  143. // }
  144. // else
  145. // {
  146. // search_hash_table[nHashKey].next_entry_idx = (len << 28);
  147. // search_hash_table[nHashKey].offset_fnd = offset_fnd;
  148. // strncpy(search_hash_strings[nHashKey].str, sSearchString, MAX_SEARCH_STRING_HASHED_LEN);
  149. // search_hash_strings[nHashKey].str[MAX_SEARCH_STRING_HASHED_LEN] = '\0';
  150. // }
  151. // return nHashKey;
  152. //}
  153. //
  154. //void save_search_hash(void)
  155. //{
  156. // fwrite(&nHashEntries, sizeof(nHashEntries), 1, fdHsh);
  157. // fwrite(search_hash_table, sizeof(SEARCH_HASH_TABLE), nHashEntries, fdHsh);
  158. // free(search_hash_table);
  159. // fclose(fdHsh);
  160. // if (nNeedMoreEntries)
  161. // printf("Search hash table need %ld more entries!\n", nNeedMoreEntries);
  162. //}
  163. #else
  164. void init_search_hash(void)
  165. {
  166. static int bFirstCall = 1;
  167. int i;
  168. if (bFirstCall)
  169. {
  170. for (i = 0; i < MAX_WIKIS; i++)
  171. {
  172. bHashInited[i] = 0;
  173. lenFnd[i] = 0;
  174. }
  175. bFirstCall = 0;
  176. }
  177. if (!bHashInited[nCurrentWiki])
  178. {
  179. /* Disable hashing
  180. fdHsh[nCurrentWiki] = wl_open(get_wiki_file_path(nCurrentWiki, "wiki.hsh"), WL_O_RDONLY);
  181. wl_read(fdHsh[nCurrentWiki], &nHashEntries[nCurrentWiki], sizeof(nHashEntries[nCurrentWiki]));
  182. search_hash_table[nCurrentWiki] = (SEARCH_HASH_TABLE *)malloc_simple(sizeof(SEARCH_HASH_TABLE) * nHashEntries[nCurrentWiki], MEM_TAG_INDEX_M1);
  183. bHashBlockLoaded[nCurrentWiki] = (int *)malloc_simple(sizeof(int) * (nHashEntries[nCurrentWiki] / ENTRIES_PER_HASH_BLOCK), MEM_TAG_INDEX_M1);
  184. memset((char *)bHashBlockLoaded[nCurrentWiki], 0, sizeof(int) * (nHashEntries[nCurrentWiki] / ENTRIES_PER_HASH_BLOCK));
  185. */
  186. fdFnd[nCurrentWiki] = wl_open(get_wiki_file_path(nCurrentWiki, "wiki.fnd"), WL_O_RDONLY);
  187. init_bigram(fdFnd[nCurrentWiki]);
  188. fnd_bufs[nCurrentWiki] = (struct _fnd_buf *)malloc_simple(sizeof(struct _fnd_buf) * FND_BUF_COUNT, MEM_TAG_INDEX_M1);
  189. for (i = 0; i < FND_BUF_COUNT; i++)
  190. fnd_bufs[nCurrentWiki][i].offset = 0;
  191. bHashInited[nCurrentWiki] = 1;
  192. }
  193. }
  194. //int nHashJumps;
  195. //long get_search_hash_offset_fnd(char *sSearchString, int len)
  196. //{
  197. // long nHashKey;
  198. // //TITLE_SEARCH title_search;
  199. // char sTitleSearch[MAX_TITLE_SEARCH];
  200. // char sTitleActual[MAX_TITLE_ACTUAL];
  201. // int bFound = 0;
  202. // int lenHashed;
  203. // int idxBlock;
  204. //
  205. // nHashJumps = 0;
  206. // nHashKey = hash_key(sSearchString, len);
  207. // idxBlock = nHashKey / ENTRIES_PER_HASH_BLOCK;
  208. // if (!bHashBlockLoaded[nCurrentWiki][idxBlock])
  209. // {
  210. // wl_seek(fdHsh[nCurrentWiki], idxBlock * ENTRIES_PER_HASH_BLOCK * sizeof(SEARCH_HASH_TABLE) + sizeof(nHashEntries[nCurrentWiki]));
  211. // wl_read(fdHsh[nCurrentWiki], &search_hash_table[nCurrentWiki][idxBlock * ENTRIES_PER_HASH_BLOCK],
  212. // ENTRIES_PER_HASH_BLOCK * sizeof(SEARCH_HASH_TABLE));
  213. // bHashBlockLoaded[nCurrentWiki][idxBlock]++;
  214. //#ifdef INCLUDED_FROM_KERNEL
  215. // if (wl_input_event_pending())
  216. // {
  217. // search_interrupted = 2;
  218. // goto interrupted;
  219. // }
  220. //#endif
  221. // }
  222. //
  223. // while (!bFound && nHashKey >= 0 && search_hash_table[nCurrentWiki][nHashKey].offset_fnd)
  224. // {
  225. // if (search_hash_table[nCurrentWiki][nHashKey].offset_fnd > 0)
  226. // {
  227. // retrieve_titles_from_fnd(search_hash_table[nCurrentWiki][nHashKey].offset_fnd, sTitleSearch, sTitleActual);
  228. // //copy_fnd_to_buf(search_hash_table[nCurrentWiki][nHashKey].offset_fnd, (char *)&title_search, sizeof(title_search));
  229. // if (search_interrupted)
  230. // {
  231. // search_interrupted = 6;
  232. // goto interrupted;
  233. // }
  234. // //bigram_decode(sDecoded, title_search.sTitleSearch, MAX_TITLE_SEARCH);
  235. // }
  236. // else
  237. // sTitleSearch[0] = '\0';
  238. // lenHashed = (search_hash_table[nCurrentWiki][nHashKey].next_entry_idx >> 28) & 0x000000FF;
  239. // sTitleSearch[lenHashed] = '\0';
  240. // if (!search_string_cmp(sTitleSearch, sSearchString, len))
  241. // bFound = 1;
  242. // if (!bFound)
  243. // {
  244. // if (search_hash_table[nCurrentWiki][nHashKey].next_entry_idx & 0x0FFFFFFF)
  245. // {
  246. // nHashJumps++;
  247. // nHashKey = search_hash_table[nCurrentWiki][nHashKey].next_entry_idx & 0x0FFFFFFF;
  248. // idxBlock = nHashKey / ENTRIES_PER_HASH_BLOCK;
  249. // if (!bHashBlockLoaded[nCurrentWiki][idxBlock])
  250. // {
  251. // wl_seek(fdHsh[nCurrentWiki], idxBlock * ENTRIES_PER_HASH_BLOCK * sizeof(SEARCH_HASH_TABLE) + sizeof(nHashEntries[nCurrentWiki]));
  252. // wl_read(fdHsh[nCurrentWiki], &search_hash_table[nCurrentWiki][idxBlock * ENTRIES_PER_HASH_BLOCK],
  253. // ENTRIES_PER_HASH_BLOCK * sizeof(SEARCH_HASH_TABLE));
  254. // bHashBlockLoaded[nCurrentWiki][idxBlock]++;
  255. //#ifdef INCLUDED_FROM_KERNEL
  256. // if (wl_input_event_pending())
  257. // {
  258. // search_interrupted = 3;
  259. // goto interrupted;
  260. // }
  261. //#endif
  262. // }
  263. // }
  264. // else
  265. // nHashKey = -1;
  266. // }
  267. // }
  268. // if (bFound)
  269. // {
  270. // return search_hash_table[nCurrentWiki][nHashKey].offset_fnd;
  271. // }
  272. // else
  273. // {
  274. // return 0;
  275. // }
  276. //interrupted:
  277. // return 0;
  278. //}
  279. int copy_fnd_to_buf(long offset, char *buf, int len)
  280. {
  281. int i = 0;
  282. int bFound = 0;
  283. int nLeastUsedSeq = 0;
  284. int iLeastUsed = 0;
  285. int nCopyLen;
  286. long blocked_offset;
  287. if (lenFnd[nCurrentWiki] > 0 && offset >= lenFnd[nCurrentWiki])
  288. return 0;
  289. while (!bFound && i < FND_BUF_COUNT)
  290. {
  291. if (fnd_bufs[nCurrentWiki][i].offset)
  292. {
  293. if (fnd_bufs[nCurrentWiki][i].offset <= offset && offset - fnd_bufs[nCurrentWiki][i].offset < FND_BUF_BLOCK_SIZE)
  294. {
  295. bFound = 1;
  296. }
  297. else
  298. {
  299. if (nLeastUsedSeq == 0 || fnd_bufs[nCurrentWiki][i].used_seq < nLeastUsedSeq)
  300. {
  301. nLeastUsedSeq = fnd_bufs[nCurrentWiki][i].used_seq;
  302. iLeastUsed = i;
  303. }
  304. }
  305. }
  306. else // the block of the offset to be read into the null entry
  307. {
  308. blocked_offset = ((offset - SIZE_BIGRAM_BUF) / FND_BUF_BLOCK_SIZE) * FND_BUF_BLOCK_SIZE + SIZE_BIGRAM_BUF;
  309. wl_seek(fdFnd[nCurrentWiki], blocked_offset);
  310. fnd_bufs[nCurrentWiki][i].len = wl_read(fdFnd[nCurrentWiki], &fnd_bufs[nCurrentWiki][i].buf,
  311. sizeof(fnd_bufs[nCurrentWiki][i].buf));
  312. #ifdef INCLUDED_FROM_KERNEL
  313. if (wl_input_event_pending())
  314. {
  315. search_interrupted = 4;
  316. }
  317. #endif
  318. if (fnd_bufs[nCurrentWiki][i].len < FND_BUF_BLOCK_SIZE)
  319. {
  320. lenFnd[nCurrentWiki] = wl_tell(fdFnd[nCurrentWiki]);
  321. if (fnd_bufs[nCurrentWiki][i].len <= 0)
  322. {
  323. fnd_bufs[nCurrentWiki][i].offset = 0;
  324. return 0;
  325. }
  326. }
  327. fnd_bufs[nCurrentWiki][i].offset = blocked_offset;
  328. bFound = 1;
  329. if (search_interrupted)
  330. goto interrupted;
  331. }
  332. if (!bFound)
  333. i++;
  334. }
  335. if (!bFound)
  336. {
  337. i = iLeastUsed;
  338. blocked_offset = ((offset - SIZE_BIGRAM_BUF) / FND_BUF_BLOCK_SIZE) * FND_BUF_BLOCK_SIZE + SIZE_BIGRAM_BUF;
  339. wl_seek(fdFnd[nCurrentWiki], blocked_offset);
  340. fnd_bufs[nCurrentWiki][i].len = wl_read(fdFnd[nCurrentWiki], fnd_bufs[nCurrentWiki][i].buf,
  341. sizeof(fnd_bufs[nCurrentWiki][i].buf));
  342. #ifdef INCLUDED_FROM_KERNEL
  343. if (wl_input_event_pending())
  344. {
  345. search_interrupted = 5;
  346. }
  347. #endif
  348. if (fnd_bufs[nCurrentWiki][i].len < FND_BUF_BLOCK_SIZE)
  349. {
  350. lenFnd[nCurrentWiki] = wl_tell(fdFnd[nCurrentWiki]);
  351. if (fnd_bufs[nCurrentWiki][i].len <= 0)
  352. {
  353. fnd_bufs[nCurrentWiki][i].offset = 0;
  354. return 0;
  355. }
  356. }
  357. fnd_bufs[nCurrentWiki][i].offset = blocked_offset;
  358. if (search_interrupted)
  359. goto interrupted;
  360. }
  361. fnd_bufs[nCurrentWiki][i].used_seq = nUsedSeq++;
  362. if (len > fnd_bufs[nCurrentWiki][i].len - (offset - fnd_bufs[nCurrentWiki][i].offset)) // the buf to be copied is separated into two blocks or end of file
  363. nCopyLen = fnd_bufs[nCurrentWiki][i].len - (offset - fnd_bufs[nCurrentWiki][i].offset);
  364. else
  365. nCopyLen = len;
  366. if (nCopyLen < 0)
  367. nCopyLen = 0;
  368. else
  369. memcpy(buf, &fnd_bufs[nCurrentWiki][i].buf[offset - fnd_bufs[nCurrentWiki][i].offset], nCopyLen);
  370. if (nCopyLen < len)
  371. nCopyLen += copy_fnd_to_buf(fnd_bufs[nCurrentWiki][i].offset + fnd_bufs[nCurrentWiki][i].len, &buf[nCopyLen], len - nCopyLen);
  372. return nCopyLen;
  373. interrupted:
  374. return 0;
  375. }
  376. long locate_previous_title_search(long offset_fnd)
  377. {
  378. long len_buf;
  379. int nZeros;
  380. int i;
  381. char buf[sizeof(TITLE_SEARCH)];
  382. if (offset_fnd > sizeof(TITLE_SEARCH))
  383. len_buf = sizeof(TITLE_SEARCH);
  384. else
  385. len_buf = offset_fnd;
  386. offset_fnd -= len_buf;
  387. copy_fnd_to_buf(offset_fnd, buf, len_buf);
  388. i = len_buf - 1;
  389. nZeros = 0;
  390. while (i >= sizeof(uint32_t) && nZeros < 3)
  391. {
  392. if (!buf[i])
  393. nZeros++;
  394. i--;
  395. }
  396. i -= sizeof(uint32_t) - 1;
  397. return offset_fnd + i;
  398. }
  399. void retrieve_titles_from_fnd(long offset_fnd, unsigned char *sTitleSearchOut, unsigned char *sTitleActual)
  400. {
  401. TITLE_SEARCH aTitleSearch[SEARCH_HASH_SEQUENTIAL_SEARCH_THRESHOLD];
  402. unsigned char sTitleSearch[MAX_TITLE_SEARCH];
  403. int nTitleSearch = 0;
  404. int bFound1 = 0;
  405. int bFound2 = 0;
  406. int i;
  407. int lenDuplicated;
  408. // Find the title that is fully spelled out.
  409. // The repeated characters with the previous title at the beginning of the current title will be replace by
  410. // a character whose binary value is the number of the repeated characters.
  411. while ((!bFound1 || !bFound2) && offset_fnd >= SIZE_BIGRAM_BUF + sizeof(uint32_t) && nTitleSearch < SEARCH_HASH_SEQUENTIAL_SEARCH_THRESHOLD)
  412. {
  413. char *p;
  414. copy_fnd_to_buf(offset_fnd, (char *)&aTitleSearch[nTitleSearch], sizeof(TITLE_SEARCH));
  415. p = aTitleSearch[nTitleSearch].sTitleSearch;
  416. strncpy(sTitleSearch, p, MAX_TITLE_SEARCH);
  417. sTitleSearch[MAX_TITLE_SEARCH - 1] = '\0';
  418. //bigram_decode(sTitleSearch, p, MAX_TITLE_SEARCH);
  419. p += strlen(aTitleSearch[nTitleSearch].sTitleSearch) + 1; // pointing to actual title
  420. //bigram_decode(sTitleActual, p, MAX_TITLE_SEARCH);
  421. strncpy(sTitleActual, p, MAX_TITLE_ACTUAL);
  422. sTitleActual[MAX_TITLE_ACTUAL - 1] = '\0';
  423. strcpy(aTitleSearch[nTitleSearch].sTitleSearch, sTitleSearch);
  424. strcpy(aTitleSearch[nTitleSearch].sTitleActual, sTitleActual);
  425. if ((unsigned char)aTitleSearch[nTitleSearch].sTitleSearch[0] >= ' ')
  426. bFound1 = 1;
  427. if ((unsigned char)aTitleSearch[nTitleSearch].sTitleActual[0] >= ' ')
  428. bFound2 = 1;
  429. if (!bFound1 || !bFound2)
  430. offset_fnd = locate_previous_title_search(offset_fnd);
  431. nTitleSearch++;
  432. }
  433. sTitleSearch[0] = '\0';
  434. if (bFound1)
  435. {
  436. for (i = nTitleSearch - 1; i >= 0; i--)
  437. {
  438. if ((unsigned char)aTitleSearch[i].sTitleSearch[0] >= ' ')
  439. {
  440. strncpy(sTitleSearch, aTitleSearch[i].sTitleSearch, MAX_TITLE_SEARCH);
  441. sTitleSearch[MAX_TITLE_SEARCH - 1] = '\0';
  442. }
  443. else if (sTitleSearch[0])
  444. {
  445. lenDuplicated = aTitleSearch[i].sTitleSearch[0] + 1;
  446. memcpy(&sTitleSearch[lenDuplicated], &aTitleSearch[i].sTitleSearch[1], MAX_TITLE_SEARCH - lenDuplicated - 1);
  447. sTitleSearch[MAX_TITLE_SEARCH - 1] = '\0';
  448. }
  449. }
  450. }
  451. bigram_decode(sTitleSearchOut, sTitleSearch, MAX_TITLE_SEARCH);
  452. sTitleActual[0] = '\0';
  453. if (bFound2)
  454. {
  455. for (i = nTitleSearch - 1; i >= 0; i--)
  456. {
  457. if ((unsigned char)aTitleSearch[i].sTitleActual[0] >= ' ')
  458. {
  459. strncpy(sTitleActual, aTitleSearch[i].sTitleActual, MAX_TITLE_ACTUAL);
  460. sTitleActual[MAX_TITLE_ACTUAL - 1] = '\0';
  461. }
  462. else if (sTitleActual[0])
  463. {
  464. lenDuplicated = aTitleSearch[i].sTitleActual[0] + 1;
  465. memcpy(&sTitleActual[lenDuplicated], &aTitleSearch[i].sTitleActual[1], MAX_TITLE_ACTUAL - lenDuplicated - 1);
  466. sTitleActual[MAX_TITLE_ACTUAL - 1] = '\0';
  467. }
  468. }
  469. }
  470. }
  471. #endif