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