1 /** \file hist.cpp
2 \brief file history and bookmarks container
3
4 CoolReader Engine
5
6 (c) Vadim Lopatin, 2000-2007
7 This source code is distributed under the terms of
8 GNU General Public License
9 See LICENSE file for details
10 */
11
12 #include "../include/lvtinydom.h"
13 #include "../include/hist.h"
14 #include "../include/crlog.h"
15
clear()16 void CRFileHist::clear()
17 {
18 _records.clear();
19 }
20
21 /// XML parser callback interface
22 class CRHistoryFileParserCallback : public LVXMLParserCallback
23 {
24 protected:
25 LVFileFormatParser * _parser;
26 CRFileHist * _hist;
27 CRBookmark * _curr_bookmark;
28 CRFileHistRecord * _curr_file;
29 enum state_t {
30 in_xml,
31 in_fbm,
32 in_file,
33 in_file_info,
34 in_bm_list,
35 in_bm,
36 in_start_point,
37 in_end_point,
38 in_header_txt,
39 in_selection_txt,
40 in_comment_txt,
41 in_title,
42 in_author,
43 in_series,
44 in_filename,
45 in_filepath,
46 in_filesize,
47 in_dom_version
48 };
49 state_t state;
50 public:
51 ///
CRHistoryFileParserCallback(CRFileHist * hist)52 CRHistoryFileParserCallback( CRFileHist * hist )
53 : _hist(hist), _curr_bookmark(NULL), _curr_file(NULL)
54 {
55 state = in_xml;
56 }
getFlags()57 virtual lUInt32 getFlags() { return TXTFLG_PRE; }
58 /// called on parsing start
OnStart(LVFileFormatParser * parser)59 virtual void OnStart(LVFileFormatParser * parser)
60 {
61 _parser = parser;
62 parser->SetSpaceMode(false);
63 }
64 /// called on parsing end
OnStop()65 virtual void OnStop()
66 {
67 }
68 /// called on opening tag end
OnTagBody()69 virtual void OnTagBody()
70 {
71 }
72 /// add named BLOB data to document
OnBlob(lString32 name,const lUInt8 * data,int size)73 virtual bool OnBlob(lString32 name, const lUInt8 * data, int size) {
74 CR_UNUSED3(name, data, size);
75 return true;
76 }
77 /// called on opening tag
OnTagOpen(const lChar32 * nsname,const lChar32 * tagname)78 virtual ldomNode * OnTagOpen( const lChar32 * nsname, const lChar32 * tagname)
79 {
80 CR_UNUSED(nsname);
81 if ( lStr_cmp(tagname, "FictionBookMarks")==0 && state==in_xml ) {
82 state = in_fbm;
83 } else if ( lStr_cmp(tagname, "file")==0 && state==in_fbm ) {
84 state = in_file;
85 _curr_file = new CRFileHistRecord();
86 } else if ( lStr_cmp(tagname, "file-info")==0 && state==in_file ) {
87 state = in_file_info;
88 } else if ( lStr_cmp(tagname, "bookmark-list")==0 && state==in_file ) {
89 state = in_bm_list;
90 } else if ( lStr_cmp(tagname, "doc-title")==0 && state==in_file_info ) {
91 state = in_title;
92 } else if ( lStr_cmp(tagname, "doc-author")==0 && state==in_file_info ) {
93 state = in_author;
94 } else if ( lStr_cmp(tagname, "doc-series")==0 && state==in_file_info ) {
95 state = in_series;
96 } else if ( lStr_cmp(tagname, "doc-filename")==0 && state==in_file_info ) {
97 state = in_filename;
98 } else if ( lStr_cmp(tagname, "doc-filepath")==0 && state==in_file_info ) {
99 state = in_filepath;
100 } else if ( lStr_cmp(tagname, "doc-filesize")==0 && state==in_file_info ) {
101 state = in_filesize;
102 } else if ( lStr_cmp(tagname, "doc-dom-version")==0 && state==in_file_info ) {
103 state = in_dom_version;
104 } else if ( lStr_cmp(tagname, "bookmark")==0 && state==in_bm_list ) {
105 state = in_bm;
106 _curr_bookmark = new CRBookmark();
107 } else if ( lStr_cmp(tagname, "start-point")==0 && state==in_bm ) {
108 state = in_start_point;
109 } else if ( lStr_cmp(tagname, "end-point")==0 && state==in_bm ) {
110 state = in_end_point;
111 } else if ( lStr_cmp(tagname, "header-text")==0 && state==in_bm ) {
112 state = in_header_txt;
113 } else if ( lStr_cmp(tagname, "selection-text")==0 && state==in_bm ) {
114 state = in_selection_txt;
115 } else if ( lStr_cmp(tagname, "comment-text")==0 && state==in_bm ) {
116 state = in_comment_txt;
117 }
118 return NULL;
119 }
120 /// called on closing
OnTagClose(const lChar32 * nsname,const lChar32 * tagname,bool self_closing_tag=false)121 virtual void OnTagClose( const lChar32 * nsname, const lChar32 * tagname, bool self_closing_tag=false )
122 {
123 if ( lStr_cmp(nsname, "FictionBookMarks")==0 && state==in_fbm ) {
124 state = in_xml;
125 } else if ( lStr_cmp(tagname, "file")==0 && state==in_file ) {
126 state = in_fbm;
127 if ( _curr_file )
128 _hist->getRecords().add( _curr_file );
129 _curr_file = NULL;
130 } else if ( lStr_cmp(tagname, "file-info")==0 && state==in_file_info ) {
131 state = in_file;
132 } else if ( lStr_cmp(tagname, "bookmark-list")==0 && state==in_bm_list ) {
133 state = in_file;
134 } else if ( lStr_cmp(tagname, "doc-title")==0 && state==in_title ) {
135 state = in_file_info;
136 } else if ( lStr_cmp(tagname, "doc-author")==0 && state==in_author ) {
137 state = in_file_info;
138 } else if ( lStr_cmp(tagname, "doc-series")==0 && state==in_series ) {
139 state = in_file_info;
140 } else if ( lStr_cmp(tagname, "doc-filename")==0 && state==in_filename ) {
141 state = in_file_info;
142 } else if ( lStr_cmp(tagname, "doc-filepath")==0 && state==in_filepath ) {
143 state = in_file_info;
144 } else if ( lStr_cmp(tagname, "doc-filesize")==0 && state==in_filesize ) {
145 state = in_file_info;
146 } else if ( lStr_cmp(tagname, "doc-dom-version")==0 && state==in_dom_version ) {
147 state = in_file_info;
148 } else if ( lStr_cmp(tagname, "bookmark")==0 && state==in_bm ) {
149 state = in_bm_list;
150 if ( _curr_bookmark ) {
151 if ( _curr_bookmark->getType() == bmkt_lastpos ) {
152 _curr_file->setLastPos(_curr_bookmark);
153 delete _curr_bookmark;
154 } else {
155 _curr_file->getBookmarks().add(_curr_bookmark);
156 }
157 _curr_bookmark = NULL;
158 }
159 } else if ( lStr_cmp(tagname, "start-point")==0 && state==in_start_point ) {
160 state = in_bm;
161 } else if ( lStr_cmp(tagname, "end-point")==0 && state==in_end_point ) {
162 state = in_bm;
163 } else if ( lStr_cmp(tagname, "header-text")==0 && state==in_header_txt ) {
164 state = in_bm;
165 } else if ( lStr_cmp(tagname, "selection-text")==0 && state==in_selection_txt ) {
166 state = in_bm;
167 } else if ( lStr_cmp(tagname, "comment-text")==0 && state==in_comment_txt ) {
168 state = in_bm;
169 }
170 }
171 /// called on element attribute
OnAttribute(const lChar32 * nsname,const lChar32 * attrname,const lChar32 * attrvalue)172 virtual void OnAttribute( const lChar32 * nsname, const lChar32 * attrname, const lChar32 * attrvalue )
173 {
174 CR_UNUSED(nsname);
175 if ( lStr_cmp(attrname, "type")==0 && state==in_bm ) {
176 static const char * tnames[] = {"lastpos", "position", "comment", "correction"};
177 for ( int i=0; i<4; i++) {
178 if ( lStr_cmp(attrvalue, tnames[i])==0 ) {
179 _curr_bookmark->setType( (bmk_type)i );
180 return;
181 }
182 }
183 } else if ( lStr_cmp(attrname, "shortcut")==0 && state==in_bm ) {
184 int n = lString32( attrvalue ).atoi();
185 _curr_bookmark->setShortcut( n );
186 } else if ( lStr_cmp(attrname, "percent")==0 && state==in_bm ) {
187 int n1=0, n2=0;
188 int i=0;
189 for ( ; attrvalue[i]>='0' && attrvalue[i]<='9'; i++)
190 n1 = n1*10 + (attrvalue[i]-'0');
191 if ( attrvalue[i]=='.' ) {
192 i++;
193 if (attrvalue[i]>='0' && attrvalue[i]<='9')
194 n2 = (attrvalue[i++]-'0')*10;
195 if (attrvalue[i]>='0' && attrvalue[i]<='9')
196 n2 = (attrvalue[i++]-'0');
197 }
198 _curr_bookmark->setPercent( n1*100 + n2 );
199 } else if ( lStr_cmp(attrname, "timestamp")==0 && state==in_bm ) {
200 time_t n1=0;
201 int i=0;
202 for ( ; attrvalue[i]>='0' && attrvalue[i]<='9'; i++)
203 n1 = n1*10 + (attrvalue[i]-'0');
204 _curr_bookmark->setTimestamp( n1 );
205 } else if (lStr_cmp(attrname, "page")==0 && state==in_bm) {
206 _curr_bookmark->setBookmarkPage(lString32( attrvalue ).atoi());
207 }
208 }
209 /// called on text
OnText(const lChar32 * text,int len,lUInt32 flags)210 virtual void OnText( const lChar32 * text, int len, lUInt32 flags )
211 {
212 CR_UNUSED(flags);
213 lString32 txt( text, len );
214 switch (state) {
215 case in_start_point:
216 _curr_bookmark->setStartPos( txt );
217 break;
218 case in_end_point:
219 _curr_bookmark->setEndPos( txt );
220 break;
221 case in_header_txt:
222 _curr_bookmark->setTitleText( txt );
223 break;
224 case in_selection_txt:
225 _curr_bookmark->setPosText( txt );
226 break;
227 case in_comment_txt:
228 _curr_bookmark->setCommentText( txt );
229 break;
230 case in_author:
231 _curr_file->setAuthor( txt );
232 break;
233 case in_title:
234 _curr_file->setTitle( txt );
235 break;
236 case in_series:
237 _curr_file->setSeries( txt );
238 break;
239 case in_filename:
240 _curr_file->setFileName( txt );
241 break;
242 case in_filepath:
243 _curr_file->setFilePath( txt );
244 break;
245 case in_filesize:
246 _curr_file->setFileSize( txt.atoi() );
247 break;
248 case in_dom_version:
249 _curr_file->setDOMversion( txt.atoi() );
250 break;
251 default:
252 break;
253 }
254 }
255 /// destructor
~CRHistoryFileParserCallback()256 virtual ~CRHistoryFileParserCallback()
257 {
258 if ( _curr_file )
259 delete _curr_file;
260 }
261 };
262
loadFromStream(LVStreamRef stream)263 bool CRFileHist::loadFromStream( LVStreamRef stream )
264 {
265 CRHistoryFileParserCallback cb(this);
266 LVXMLParser parser( stream, &cb );
267 if ( !parser.CheckFormat() )
268 return false;
269 if ( !parser.Parse() )
270 return false;
271 return true;
272 }
273
putTagValue(LVStream * stream,int level,const char * tag,lString32 value)274 static void putTagValue( LVStream * stream, int level, const char * tag, lString32 value )
275 {
276 for ( int i=0; i<level; i++ )
277 *stream << " ";
278 *stream << "<" << tag;
279 if ( value.empty() ) {
280 *stream << "/>\r\n";
281 } else {
282 *stream << ">" << UnicodeToUtf8( value ).c_str() << "</" << tag << ">\r\n";
283 }
284 }
285
putTag(LVStream * stream,int level,const char * tag)286 static void putTag( LVStream * stream, int level, const char * tag )
287 {
288 for ( int i=0; i<level; i++ )
289 *stream << " ";
290 *stream << "<" << tag << ">\r\n";
291 }
292
putBookmark(LVStream * stream,CRBookmark * bmk)293 static void putBookmark( LVStream * stream, CRBookmark * bmk )
294 {
295 static const char * tnames[] = {"lastpos", "position", "comment", "correction"};
296 const char * tname = bmk->getType()>=bmkt_lastpos && bmk->getType()<=bmkt_correction ? tnames[bmk->getType()] : "unknown";
297 char bmktag[256];
298 sprintf(bmktag, "bookmark type=\"%s\" percent=\"%d.%02d%%\" timestamp=\"%d\" shortcut=\"%d\" page=\"%d\"", tname,
299 bmk->getPercent()/100, bmk->getPercent()%100,
300 (int)bmk->getTimestamp(), (int)bmk->getShortcut(), (int)bmk->getBookmarkPage());
301 putTag(stream, 3, bmktag);
302 putTagValue( stream, 4, "start-point", bmk->getStartPos() );
303 putTagValue( stream, 4, "end-point", bmk->getEndPos() );
304 putTagValue( stream, 4, "header-text", bmk->getTitleText() );
305 putTagValue( stream, 4, "selection-text", bmk->getPosText() );
306 putTagValue( stream, 4, "comment-text", bmk->getCommentText() );
307 putTag(stream, 3, "/bookmark");
308 }
309
saveToStream(LVStream * targetStream)310 bool CRFileHist::saveToStream( LVStream * targetStream )
311 {
312 LVStreamRef streamref = LVCreateMemoryStream(NULL, 0, false, LVOM_WRITE);
313 LVStream * stream = streamref.get();
314 const char * xml_hdr = "\xef\xbb\xbf<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<FictionBookMarks>\r\n";
315 const char * xml_ftr = "</FictionBookMarks>\r\n";
316 //const char * crlf = "\r\n";
317 *stream << xml_hdr;
318 for ( int i=0; i<_records.length(); i++ ) {
319 CRFileHistRecord * rec = _records[i];
320 putTag( stream, 1, "file" );
321 putTag( stream, 2, "file-info" );
322 putTagValue( stream, 3, "doc-title", rec->getTitle() );
323 putTagValue( stream, 3, "doc-author", rec->getAuthor() );
324 putTagValue( stream, 3, "doc-series", rec->getSeries() );
325 putTagValue( stream, 3, "doc-filename", rec->getFileName() );
326 putTagValue( stream, 3, "doc-filepath", rec->getFilePath() );
327 putTagValue( stream, 3, "doc-filesize", lString32::itoa( (unsigned int)rec->getFileSize() ) );
328 putTagValue( stream, 3, "doc-dom-version", lString32::itoa( (unsigned int)rec->getDOMversion() ) );
329 putTag( stream, 2, "/file-info" );
330 putTag( stream, 2, "bookmark-list" );
331 putBookmark( stream, rec->getLastPos() );
332 for ( int j=0; j<rec->getBookmarks().length(); j++) {
333 CRBookmark * bmk = rec->getBookmarks()[j];
334 putBookmark( stream, bmk );
335 }
336 putTag( stream, 2, "/bookmark-list" );
337 putTag( stream, 1, "/file" );
338 }
339 *stream << xml_ftr;
340 LVPumpStream( targetStream, stream );
341 return true;
342 }
343
splitFName(lString32 pathname,lString32 & path,lString32 & name)344 static void splitFName( lString32 pathname, lString32 & path, lString32 & name )
345 {
346 //
347 int spos = -1;
348 for ( spos=pathname.length()-1; spos>=0; spos-- ) {
349 lChar32 ch = pathname[spos];
350 if ( ch=='\\' || ch=='/' ) {
351 break;
352 }
353 }
354 if ( spos>=0 ) {
355 path = pathname.substr( 0, spos+1 );
356 name = pathname.substr( spos+1, pathname.length()-spos-1 );
357 } else {
358 path.clear();
359 name = pathname;
360 }
361 }
362
setShortcutBookmark(int shortcut,ldomXPointer ptr)363 CRBookmark * CRFileHistRecord::setShortcutBookmark( int shortcut, ldomXPointer ptr )
364 {
365 if ( ptr.isNull() )
366 return NULL;
367 CRBookmark * bmk = new CRBookmark( ptr );
368 bmk->setType( bmkt_pos );
369 bmk->setShortcut( shortcut );
370 for ( int i=0; i<_bookmarks.length(); i++ ) {
371 if ( _bookmarks[i]->getShortcut() == shortcut ) {
372 _bookmarks[i] = bmk;
373 return bmk;
374 }
375 }
376 _bookmarks.insert( 0, bmk );
377 return bmk;
378 }
379
getShortcutBookmark(int shortcut)380 CRBookmark * CRFileHistRecord::getShortcutBookmark( int shortcut )
381 {
382 for ( int i=0; i<_bookmarks.length(); i++ ) {
383 if ( _bookmarks[i]->getShortcut() == shortcut && _bookmarks[i]->getType() == bmkt_pos )
384 return _bookmarks[i];
385 }
386 return NULL;
387 }
388
389 #define MAX_SHORTCUT_BOOKMARKS 64
390
391 /// returns first available placeholder for new bookmark, -1 if no more space
getLastShortcutBookmark()392 int CRFileHistRecord::getLastShortcutBookmark()
393 {
394 int last = -1;
395 for ( int i=0; i<_bookmarks.length(); i++ ) {
396 if ( _bookmarks[i]->getShortcut()>0 && _bookmarks[i]->getShortcut() > last && _bookmarks[i]->getShortcut() < MAX_SHORTCUT_BOOKMARKS
397 && _bookmarks[i]->getType() == bmkt_pos )
398 last = _bookmarks[i]->getShortcut();
399 }
400 return last;
401 }
402
403 /// returns first available placeholder for new bookmark, -1 if no more space
getFirstFreeShortcutBookmark()404 int CRFileHistRecord::getFirstFreeShortcutBookmark()
405 {
406 //int last = -1;
407 char flags[MAX_SHORTCUT_BOOKMARKS+1] = { 0 };
408 for ( int i=0; i<_bookmarks.length(); i++ ) {
409 if ( _bookmarks[i]->getShortcut()>0 && _bookmarks[i]->getShortcut() < MAX_SHORTCUT_BOOKMARKS && _bookmarks[i]->getType() == bmkt_pos )
410 flags[ _bookmarks[i]->getShortcut() ] = 1;
411 }
412 for ( int j=1; j<MAX_SHORTCUT_BOOKMARKS; j++ ) {
413 if ( flags[j]==0 )
414 return j;
415 }
416 return -1;
417 }
418
findEntry(const lString32 & fname,const lString32 & fpath,lvsize_t sz) const419 int CRFileHist::findEntry( const lString32 & fname, const lString32 & fpath, lvsize_t sz ) const
420 {
421 CR_UNUSED(fpath);
422 for ( int i=0; i<_records.length(); i++ ) {
423 CRFileHistRecord * rec = _records[i];
424 if ( rec->getFileName().compare(fname) )
425 continue;
426 if ( rec->getFileSize()!=sz ) {
427 CRLog::warn("CRFileHist::findEntry() Filename matched %s but sizes are different %d!=%d", LCSTR(fname), sz, rec->getFileSize() );
428 continue;
429 }
430 return i;
431 }
432 return -1;
433 }
434
makeTop(int index)435 void CRFileHist::makeTop( int index )
436 {
437 if ( index<=0 || index>=_records.length() )
438 return;
439 CRFileHistRecord * rec = _records[index];
440 for ( int i=index; i>0; i-- )
441 _records[i] = _records[i-1];
442 _records[0] = rec;
443 }
444
getRecord(const lString32 & fileName,size_t fileSize)445 CRFileHistRecord* CRFileHist::getRecord(const lString32 &fileName, size_t fileSize)
446 {
447 lString32 name;
448 lString32 path;
449 splitFName( fileName, path, name );
450 int index = findEntry( name, path, (lvsize_t)fileSize );
451 if ( index>=0 ) {
452 return _records[index];
453 }
454 return NULL;
455 }
456
setLastPos(CRBookmark * bmk)457 void CRFileHistRecord::setLastPos( CRBookmark * bmk )
458 {
459 _lastpos = *bmk;
460 }
461
convertBookmarks(ldomDocument * doc,int newDOMversion)462 void CRFileHistRecord::convertBookmarks(ldomDocument *doc, int newDOMversion)
463 {
464 // TODO: Don't call tinyNodeCollection::setDOMVersionRequested()
465 // but directly use functions ldomDocument::createXPointerV1() & ldomDocument::createXPointerV2().
466 for ( int i=0; i< getBookmarks().length(); i++) {
467 CRBookmark * bmk = getBookmarks()[i];
468
469 if( bmk->isValid() ) {
470 if (bmk->getType() != bmkt_lastpos) {
471 doc->setDOMVersionRequested(getDOMversion());
472 ldomXPointer p = doc->createXPointer(bmk->getStartPos());
473 if ( !p.isNull() ) {
474 doc->setDOMVersionRequested(newDOMversion);
475 bmk->setStartPos(p.toString());
476 }
477 lString32 endPos = bmk->getEndPos();
478 if( !endPos.empty() ) {
479 doc->setDOMVersionRequested(getDOMversion());
480 p = doc->createXPointer(endPos);
481 if( !p.isNull() ) {
482 doc->setDOMVersionRequested(newDOMversion);
483 bmk->setEndPos(p.toString());
484 }
485 }
486 }
487 }
488 }
489 setDOMversion(newDOMversion);
490 }
491
getChapterName(ldomXPointer ptr)492 lString32 CRBookmark::getChapterName( ldomXPointer ptr )
493 {
494 //CRLog::trace("CRBookmark::getChapterName()");
495 lString32 chapter;
496 int lastLevel = -1;
497 bool foundAnySection = false;
498 lUInt16 section_id = ptr.getNode()->getDocument()->getElementNameIndex( U"section" );
499 if ( !ptr.isNull() )
500 {
501 ldomXPointerEx p( ptr );
502 p.nextText();
503 while ( !p && !p.isNull() ) {
504 if ( !p.prevElement() )
505 break;
506 bool foundSection = p.findElementInPath( section_id ) > 0;
507 //(p.toString().pos("section") >=0 );
508 foundAnySection = foundAnySection || foundSection;
509 if ( !foundSection && foundAnySection )
510 continue;
511 lString32 nname = p.getNode()->getNodeName();
512 if ( !nname.compare("title") || !nname.compare("h1") || !nname.compare("h2") || !nname.compare("h3") ) {
513 if ( lastLevel!=-1 && p.getLevel()>=lastLevel )
514 continue;
515 lastLevel = p.getLevel();
516 if ( !chapter.empty() )
517 chapter = " / " + chapter;
518 chapter = p.getText(' ') + chapter;
519 if ( !p.parent() )
520 break;
521 }
522 }
523 }
524 return chapter;
525 }
526
savePosition(lString32 fpathname,size_t sz,const lString32 & title,const lString32 & author,const lString32 & series,ldomXPointer ptr)527 CRFileHistRecord * CRFileHist::savePosition( lString32 fpathname, size_t sz,
528 const lString32 & title,
529 const lString32 & author,
530 const lString32 & series,
531 ldomXPointer ptr )
532 {
533 //CRLog::trace("CRFileHist::savePosition");
534 lString32 name;
535 lString32 path;
536 splitFName( fpathname, path, name );
537 CRBookmark bmk( ptr );
538 //CRLog::trace("Bookmark created");
539 int index = findEntry( name, path, (lvsize_t)sz );
540 //CRLog::trace("findEntry exited");
541 if ( index>=0 ) {
542 makeTop( index );
543 _records[0]->setLastPos( &bmk );
544 _records[0]->setLastTime( (time_t)time(0) );
545 return _records[0];
546 }
547 CRFileHistRecord * rec = new CRFileHistRecord();
548 rec->setTitle( title );
549 rec->setAuthor( author );
550 rec->setSeries( series );
551 rec->setFileName( name );
552 rec->setFilePath( path );
553 rec->setFileSize( (lvsize_t)sz );
554 rec->setLastPos( &bmk );
555 rec->setLastTime( (time_t)time(0) );
556 rec->setDOMversion( gDOMVersionCurrent );
557
558 _records.insert( 0, rec );
559 //CRLog::trace("CRFileHist::savePosition - exit");
560 return rec;
561 }
562
restorePosition(ldomDocument * doc,lString32 fpathname,size_t sz)563 ldomXPointer CRFileHist::restorePosition( ldomDocument * doc, lString32 fpathname, size_t sz )
564 {
565 lString32 name;
566 lString32 path;
567 splitFName( fpathname, path, name );
568 int index = findEntry( name, path, (lvsize_t)sz );
569 if ( index>=0 ) {
570 makeTop( index );
571 return doc->createXPointer( _records[0]->getLastPos()->getStartPos() );
572 }
573 return ldomXPointer();
574 }
575
CRBookmark(ldomXPointer ptr)576 CRBookmark::CRBookmark (ldomXPointer ptr )
577 : _startpos(lString32::empty_str)
578 , _endpos(lString32::empty_str)
579 , _percent(0)
580 , _type(0)
581 , _shortcut(0)
582 , _postext(lString32::empty_str)
583 , _titletext(lString32::empty_str)
584 , _commenttext(lString32::empty_str)
585 , _timestamp(time_t(0))
586 , _page(0)
587 {
588 //
589 if ( ptr.isNull() )
590 return;
591
592 //CRLog::trace("CRBookmark::CRBookmark() started");
593 lString32 path;
594
595 //CRLog::trace("CRBookmark::CRBookmark() calling ptr.toPoint");
596 lvPoint pt = ptr.toPoint();
597 //CRLog::trace("CRBookmark::CRBookmark() calculating percent");
598 ldomDocument * doc = ptr.getNode()->getDocument();
599 int h = doc->getFullHeight();
600 if ( pt.y > 0 && h > 0 ) {
601 if ( pt.y < h ) {
602 _percent = (int)((lInt64)pt.y * 10000 / h);
603 } else {
604 _percent = 10000;
605 }
606 }
607 //CRLog::trace("CRBookmark::CRBookmark() calling getChaptername");
608 setTitleText( CRBookmark::getChapterName( ptr ) );
609 _startpos = ptr.toString();
610 CRLog::debug("new Xpath: %s, old Xpath: %s", LCSTR(_startpos), LCSTR(ptr.toString(XPATH_USE_NAMES)));
611 _timestamp = (time_t)time(0);
612 lvPoint endpt = pt;
613 endpt.y += 100;
614 //CRLog::trace("CRBookmark::CRBookmark() creating xpointer for endp");
615 ldomXPointer endptr = doc->createXPointer( endpt );
616 //CRLog::trace("CRBookmark::CRBookmark() finished");
617 }
618
619
getLastTimeString(bool longFormat)620 lString32 CRFileHistRecord::getLastTimeString( bool longFormat )
621 {
622
623 time_t t = getLastTime();
624 tm * bt = localtime(&t);
625 char str[20];
626 if ( !longFormat )
627 sprintf(str, "%02d.%02d.%04d", bt->tm_mday, 1+bt->tm_mon, 1900+bt->tm_year );
628 else
629 sprintf(str, "%02d.%02d.%04d %02d:%02d", bt->tm_mday, 1+bt->tm_mon, 1900+bt->tm_year, bt->tm_hour, bt->tm_min);
630 return Utf8ToUnicode( lString8( str ) );
631 }
632
633
634 #define START_TAG "# start record"
635 #define END_TAG "# end record"
636 #define START_TAG_BYTES "# start record\n"
637 #define END_TAG_BYTES "# end record\n"
638 #define ACTION_TAG "ACTION"
639 #define ACTION_DELETE_TAG "DELETE"
640 #define ACTION_UPDATE_TAG "UPDATE"
641 #define FILE_TAG "FILE"
642 #define TYPE_TAG "TYPE"
643 #define START_POS_TAG "STARTPOS"
644 #define END_POS_TAG "ENDPOS"
645 #define TIMESTAMP_TAG "TIMESTAMP"
646 #define PERCENT_TAG "PERCENT"
647 #define SHORTCUT_TAG "SHORTCUT"
648 #define TITLE_TEXT_TAG "TITLETEXT"
649 #define POS_TEXT_TAG "POSTEXT"
650 #define COMMENT_TEXT_TAG "COMMENTTEXT"
651
encodeText(lString32 text32)652 static lString8 encodeText(lString32 text32) {
653 if (text32.empty())
654 return lString8::empty_str;
655 lString8 text = UnicodeToUtf8(text32);
656 lString8 buf;
657 for (int i=0; i<text.length(); i++) {
658 char ch = text[i];
659 switch (ch) {
660 case '\\':
661 buf << "\\\\";
662 break;
663 case '\n':
664 buf << "\\n";
665 break;
666 case '\r':
667 buf << "\\r";
668 break;
669 case '\t':
670 buf << "\\t";
671 break;
672 default:
673 buf << ch;
674 break;
675 }
676 }
677 return buf;
678 }
679
decodeText(lString8 text)680 static lString32 decodeText(lString8 text) {
681 if (text.empty())
682 return lString32::empty_str;
683 lString8 buf;
684 bool lastControl = false;
685 for (int i=0; i<text.length(); i++) {
686 char ch = buf[i];
687 if (lastControl) {
688 switch (ch) {
689 case 'r':
690 buf.append(1, '\r');
691 break;
692 case 'n':
693 buf.append(1, '\n');
694 break;
695 case 't':
696 buf.append(1, '\t');
697 break;
698 default:
699 buf.append(1, ch);
700 break;
701 }
702 lastControl = false;
703 continue;
704 }
705 if (ch == '\\') {
706 lastControl = true;
707 continue;
708 }
709 buf.append(1, ch);
710 }
711 return Utf8ToUnicode(buf);
712 }
713
findBytes(lChar8 * buf,int start,int end,const lChar8 * pattern)714 static int findBytes(lChar8 * buf, int start, int end, const lChar8 * pattern) {
715 int len = lStr_len(pattern);
716 for (int i = start; i <= end - len; i++) {
717 int j = 0;
718 for (; j < len; j++) {
719 if (buf[i+j] != pattern[j])
720 break;
721 }
722 if (j == len)
723 return i;
724 }
725 return -1;
726 }
727
ChangeInfo(CRBookmark * bookmark,lString32 fileName,bool deleted)728 ChangeInfo::ChangeInfo(CRBookmark * bookmark, lString32 fileName, bool deleted)
729 : _bookmark(bookmark ? new CRBookmark(*bookmark) : NULL), _fileName(fileName), _deleted(deleted)
730 {
731 _timestamp = bookmark && bookmark->getTimestamp() > 0 ? bookmark->getTimestamp() : (time_t)time(0);
732 }
733
toString()734 lString8 ChangeInfo::toString() {
735 lString8 buf;
736 buf << START_TAG << "\n";
737 buf << FILE_TAG << "=" << encodeText(_fileName) << "\n";
738 buf << ACTION_TAG << "=" << (_deleted ? ACTION_DELETE_TAG : ACTION_UPDATE_TAG) << "\n";
739 buf << TIMESTAMP_TAG << "=" << fmt::decimal(_timestamp * 1000) << "\n";
740 if (_bookmark) {
741 buf << TYPE_TAG << "=" << fmt::decimal(_bookmark->getType()) << "\n";
742 buf << START_POS_TAG << "=" << encodeText(_bookmark->getStartPos()) << "\n";
743 buf << END_POS_TAG << "=" << encodeText(_bookmark->getEndPos()) << "\n";
744 buf << PERCENT_TAG << "=" << fmt::decimal(_bookmark->getPercent()) << "\n";
745 buf << SHORTCUT_TAG << "=" << fmt::decimal(_bookmark->getShortcut()) << "\n";
746 buf << TITLE_TEXT_TAG << "=" << encodeText(_bookmark->getTitleText()) << "\n";
747 buf << POS_TEXT_TAG << "=" << encodeText(_bookmark->getPosText()) << "\n";
748 buf << COMMENT_TEXT_TAG << "=" << encodeText(_bookmark->getCommentText()) << "\n";
749 }
750 buf << END_TAG << "\n";
751 return buf;
752 }
753
fromString(lString8 s)754 ChangeInfo * ChangeInfo::fromString(lString8 s) {
755 lString8Collection rows(s, cs8("\n"));
756 if (rows.length() < 3 || rows[0] != START_TAG || rows[rows.length() - 1] != END_TAG)
757 return NULL;
758 ChangeInfo * ci = new ChangeInfo();
759 CRBookmark bmk;
760 for (int i=1; i<rows.length() - 1; i++) {
761 lString8 row = rows[i];
762 int p = row.pos("=");
763 if (p<1)
764 continue;
765 lString8 name = row.substr(0, p);
766 lString8 value = row.substr(p + 1);
767 if (name == ACTION_TAG) {
768 ci->_deleted = (value == ACTION_DELETE_TAG);
769 } else if (name == FILE_TAG) {
770 ci->_fileName = decodeText(value);
771 } else if (name == TYPE_TAG) {
772 bmk.setType(value.atoi());
773 } else if (name == START_POS_TAG) {
774 bmk.setStartPos(decodeText(value));
775 } else if (name == END_POS_TAG) {
776 bmk.setEndPos(decodeText(value));
777 } else if (name == TIMESTAMP_TAG) {
778 ci->_timestamp = value.atoi64() / 1000;
779 bmk.setTimestamp(ci->_timestamp);
780 } else if (name == PERCENT_TAG) {
781 bmk.setPercent(value.atoi());
782 } else if (name == SHORTCUT_TAG) {
783 bmk.setShortcut(value.atoi());
784 } else if (name == TITLE_TEXT_TAG) {
785 bmk.setTitleText(decodeText(value));
786 } else if (name == POS_TEXT_TAG) {
787 bmk.setPosText(decodeText(value));
788 } else if (name == COMMENT_TEXT_TAG) {
789 bmk.setCommentText(decodeText(value));
790 }
791 }
792 if (bmk.isValid())
793 ci->_bookmark = new CRBookmark(bmk);
794 if (ci->_fileName.empty() || ci->_timestamp == 0 || (!ci->_bookmark && !ci->_deleted)) {
795 delete ci;
796 return NULL;
797 }
798 return ci;
799 }
800
fromBytes(lChar8 * buf,int start,int end)801 ChangeInfo * ChangeInfo::fromBytes(lChar8 * buf, int start, int end) {
802 lString8 s(buf + start, end - start);
803 return fromString(s);
804 }
805
findNextRecordBounds(lChar8 * buf,int start,int end,int & recordStart,int & recordEnd)806 bool ChangeInfo::findNextRecordBounds(lChar8 * buf, int start, int end, int & recordStart, int & recordEnd) {
807 int startTagPos = findBytes(buf, start, end, START_TAG_BYTES);
808 if (startTagPos < 0)
809 return false;
810 int endTagPos = findBytes(buf, startTagPos, end, END_TAG_BYTES);
811 if (endTagPos < 0)
812 return false;
813 recordStart = startTagPos;
814 recordEnd = endTagPos + lStr_len(END_TAG_BYTES);
815 return true;
816 }
817