source: trunk/libdjvu/DjVuDocument.cpp @ 101

Last change on this file since 101 was 17, checked in by Eugene Romanenko, 16 years ago

update makefiles, remove absolute paths, update djvulibre to version 3.5.17

File size: 55.0 KB
Line 
1//C-  -*- C++ -*-
2//C- -------------------------------------------------------------------
3//C- DjVuLibre-3.5
4//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
5//C- Copyright (c) 2001  AT&T
6//C-
7//C- This software is subject to, and may be distributed under, the
8//C- GNU General Public License, Version 2. The license should have
9//C- accompanied the software or you may obtain a copy of the license
10//C- from the Free Software Foundation at http://www.fsf.org .
11//C-
12//C- This program is distributed in the hope that it will be useful,
13//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
14//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15//C- GNU General Public License for more details.
16//C-
17//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library
18//C- distributed by Lizardtech Software.  On July 19th 2002, Lizardtech
19//C- Software authorized us to replace the original DjVu(r) Reference
20//C- Library notice by the following text (see doc/lizard2002.djvu):
21//C-
22//C-  ------------------------------------------------------------------
23//C- | DjVu (r) Reference Library (v. 3.5)
24//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
25//C- | The DjVu Reference Library is protected by U.S. Pat. No.
26//C- | 6,058,214 and patents pending.
27//C- |
28//C- | This software is subject to, and may be distributed under, the
29//C- | GNU General Public License, Version 2. The license should have
30//C- | accompanied the software or you may obtain a copy of the license
31//C- | from the Free Software Foundation at http://www.fsf.org .
32//C- |
33//C- | The computer code originally released by LizardTech under this
34//C- | license and unmodified by other parties is deemed "the LIZARDTECH
35//C- | ORIGINAL CODE."  Subject to any third party intellectual property
36//C- | claims, LizardTech grants recipient a worldwide, royalty-free,
37//C- | non-exclusive license to make, use, sell, or otherwise dispose of
38//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the
39//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
40//C- | General Public License.   This grant only confers the right to
41//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
42//C- | the extent such infringement is reasonably necessary to enable
43//C- | recipient to make, have made, practice, sell, or otherwise dispose
44//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
45//C- | any greater extent that may be necessary to utilize further
46//C- | modifications or combinations.
47//C- |
48//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
49//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
50//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
51//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
52//C- +------------------------------------------------------------------
53//
54// $Id: DjVuDocument.cpp,v 1.15 2006/01/19 15:20:41 leonb Exp $
55// $Name:  $
56
57#ifdef HAVE_CONFIG_H
58# include "config.h"
59#endif
60#if NEED_GNUG_PRAGMAS
61# pragma implementation
62#endif
63
64#include "DjVuDocument.h"
65#include "DjVmDoc.h"
66#include "DjVmDir0.h"
67#include "DjVmNav.h"
68#include "DjVuNavDir.h"
69#include "DjVuImage.h"
70#include "DjVuFileCache.h"
71#include "IFFByteStream.h"
72#include "GOS.h"
73#include "DataPool.h"
74#include "IW44Image.h"
75#include "GRect.h"
76
77#include "debug.h"
78
79
80#ifdef HAVE_NAMESPACES
81namespace DJVU {
82# ifdef NOT_DEFINED // Just to fool emacs c++ mode
83}
84#endif
85#endif
86
87
88static const char octets[4]={0x41,0x54,0x26,0x54};
89const float     DjVuDocument::thumb_gamma=(float)2.20;
90
91void (* DjVuDocument::djvu_import_codec)(
92  GP<DataPool> &pool, const GURL &url, bool &needs_compression,
93  bool &needs_rename )=0;
94
95void (* DjVuDocument::djvu_compress_codec)(
96  GP<ByteStream> &doc,const GURL &where,bool bundled)=0;
97
98void
99DjVuDocument::set_import_codec(
100  void (*codec)(
101    GP<DataPool> &pool, const GURL &url, bool &needs_compression, bool &needs_rename ))
102{
103  djvu_import_codec=codec;
104}
105
106void
107DjVuDocument::set_compress_codec(
108  void (* codec)(
109    GP<ByteStream> &doc,const GURL &where,bool bundled))
110{
111  djvu_compress_codec=codec;
112}
113
114DjVuDocument::DjVuDocument(void)
115  : doc_type(UNKNOWN_TYPE),
116    needs_compression_flag(false),
117    can_compress_flag(false),
118    needs_rename_flag(false),
119    has_url_names(false),
120    recover_errors(ABORT),
121    verbose_eof(false),
122    init_started(false),
123    cache(0) 
124{
125}
126
127GP<DjVuDocument>
128DjVuDocument::create(
129  GP<DataPool> pool, GP<DjVuPort> xport, DjVuFileCache * const xcache)
130{
131  DjVuDocument *doc=new DjVuDocument;
132  GP<DjVuDocument> retval=doc;
133  doc->init_data_pool=pool;
134  doc->start_init(GURL(),xport,xcache);
135  return retval;
136}
137
138GP<DjVuDocument>
139DjVuDocument::create(
140  const GP<ByteStream> &bs, GP<DjVuPort> xport, DjVuFileCache * const xcache)
141{
142  return create(DataPool::create(bs),xport,xcache);
143}
144
145GP<DjVuDocument>
146DjVuDocument::create_wait(
147  const GURL &url, GP<DjVuPort> xport, DjVuFileCache * const xcache)
148{
149  GP<DjVuDocument> retval=create(url,xport,xcache);
150  retval->wait_for_complete_init();
151  return retval;
152}
153
154void
155DjVuDocument::start_init(
156  const GURL & url, GP<DjVuPort> xport, DjVuFileCache * xcache)
157{
158   DEBUG_MSG("DjVuDocument::start_init(): initializing class...\n");
159   DEBUG_MAKE_INDENT(3);
160   if (init_started)
161      G_THROW( ERR_MSG("DjVuDocument.2nd_init") );
162   if (!get_count())
163      G_THROW( ERR_MSG("DjVuDocument.not_secure") );
164   if(url.is_empty())
165   {
166     if (!init_data_pool)
167       G_THROW( ERR_MSG("DjVuDocument.empty_url") );
168     if(init_url.is_empty())
169     {
170       init_url=invent_url("document.djvu");
171     }
172   }else
173   {
174     init_url=url;
175   }
176   
177      // Initialize
178   cache=xcache;
179   doc_type=UNKNOWN_TYPE;
180   DjVuPortcaster * pcaster=get_portcaster();
181   if (!xport)
182     xport=simple_port=new DjVuSimplePort();
183   pcaster->add_route(this, xport);
184   pcaster->add_route(this, this);
185
186   if(!url.is_empty())
187   {
188     init_data_pool=pcaster->request_data(this, init_url);
189     if(init_data_pool)
190     {
191       if(!init_url.is_empty() && init_url.is_local_file_url() && djvu_import_codec)
192       {
193         djvu_import_codec(init_data_pool,init_url,needs_compression_flag,needs_rename_flag);
194       }
195       if(needs_rename_flag)
196         can_compress_flag=true;
197     }
198     if (!init_data_pool) 
199     {
200       G_THROW( ERR_MSG("DjVuDocument.fail_URL") "\t"+init_url.get_string());
201     }
202   }
203      // Now we say it is ready
204   init_started=true;
205
206   init_thread_flags=STARTED;
207   init_life_saver=this;
208   init_thr.create(static_init_thread, this);
209}
210
211DjVuDocument::~DjVuDocument(void)
212{
213      // No more messages, please. We're being destroyed.
214   get_portcaster()->del_port(this);
215
216      // We want to stop any DjVuFile which has been created by us
217      // and is still being decoded. We have to stop them manually because
218      // they keep the "life saver" in the decoding thread and won't stop
219      // when we clear the last reference to them
220   {
221      GCriticalSectionLock lock(&ufiles_lock);
222      for(GPosition pos=ufiles_list;pos;++pos)
223      {
224          GP<DjVuFile> file=ufiles_list[pos]->file;
225          file->stop_decode(false);
226          file->stop(false);    // Disable any access to data
227      }
228      ufiles_list.empty();
229   }
230
231   GPList<DjVuPort> ports=get_portcaster()->prefix_to_ports(get_int_prefix());
232   for(GPosition pos=ports;pos;++pos)
233   {
234     GP<DjVuPort> port=ports[pos];
235     if (port->inherits("DjVuFile"))
236     {
237       DjVuFile * file=(DjVuFile *) (DjVuPort *) port;
238       file->stop_decode(false);
239       file->stop(false);       // Disable any access to data
240     }
241   }
242   DataPool::close_all();
243}
244
245void
246DjVuDocument::stop_init(void)
247{
248   DEBUG_MSG("DjVuDocument::stop_init(): making sure that the init thread dies.\n");
249   DEBUG_MAKE_INDENT(3);
250
251   GMonitorLock lock(&init_thread_flags);
252   while((init_thread_flags & STARTED) &&
253         !(init_thread_flags & FINISHED))
254   {
255      if (init_data_pool) init_data_pool->stop(true);   // blocking operation
256
257      if (ndir_file) ndir_file->stop(false);
258
259      {
260         GCriticalSectionLock lock(&ufiles_lock);
261         for(GPosition pos=ufiles_list;pos;++pos)
262            ufiles_list[pos]->file->stop(false);        // Disable any access to data
263         ufiles_list.empty();
264      }
265
266      init_thread_flags.wait(50);
267   }
268}
269
270void
271DjVuDocument::check() const
272{
273  if (!init_started)
274    G_THROW( ERR_MSG("DjVuDocument.not_init") );
275}
276
277void
278DjVuDocument::static_init_thread(void * cl_data)
279{
280  DjVuDocument * th=(DjVuDocument *) cl_data;
281  GP<DjVuDocument> life_saver=th;
282  th->init_life_saver=0;
283  G_TRY {
284    th->init_thread();
285  } G_CATCH(exc) {
286    G_TRY {
287      int changed = DjVuDocument::DOC_INIT_FAILED;
288      th->flags |= changed;
289      get_portcaster()->notify_doc_flags_changed(th, changed, 0);
290    } G_CATCH_ALL {
291    } G_ENDCATCH;
292    G_TRY {
293      th->check_unnamed_files();
294      if (!exc.cmp_cause(ByteStream::EndOfFile) && th->verbose_eof)
295        get_portcaster()->notify_error(th, ERR_MSG("DjVuDocument.init_eof"));
296      else if (!exc.cmp_cause(DataPool::Stop))
297        get_portcaster()->notify_status(th, ERR_MSG("DjVuDocument.stopped"));
298      else
299        get_portcaster()->notify_error(th, exc.get_cause());
300    } G_CATCH_ALL {
301    } G_ENDCATCH;
302    th->init_thread_flags |= FINISHED;
303  } G_ENDCATCH;
304}
305
306void
307DjVuDocument::init_thread(void)
308      // This function is run in a separate thread.
309      // The goal is to detect the document type (BUNDLED, OLD_INDEXED, etc.)
310      // and decode navigation directory.
311{
312   DEBUG_MSG("DjVuDocument::init_thread(): guessing what we're dealing with\n");
313   DEBUG_MAKE_INDENT(3);
314
315   DjVuPortcaster * pcaster=get_portcaster();
316     
317   GP<ByteStream> stream=init_data_pool->get_stream();
318
319   GP<IFFByteStream> giff=IFFByteStream::create(stream);
320   IFFByteStream &iff=*giff;
321   GUTF8String chkid;
322   int size=iff.get_chunk(chkid);
323   if (!size)
324     G_THROW( ByteStream::EndOfFile );
325   if (size < 0)
326     G_THROW( ERR_MSG("DjVuDocument.no_file") );
327   if (size<8)
328     G_THROW( ERR_MSG("DjVuDocument.not_DjVu") );
329   if (chkid=="FORM:DJVM")
330   {
331     DEBUG_MSG("Got DJVM document here\n");
332     DEBUG_MAKE_INDENT(3);
333     
334     size=iff.get_chunk(chkid);
335     if (chkid=="DIRM")
336       {
337         djvm_dir=DjVmDir::create();
338         djvm_dir->decode(iff.get_bytestream());
339         iff.close_chunk();
340         if (djvm_dir->is_bundled())
341           {
342             DEBUG_MSG("Got BUNDLED file.\n");
343             doc_type=BUNDLED;
344           } 
345         else
346           {
347             DEBUG_MSG("Got INDIRECT file.\n");
348             doc_type=INDIRECT;
349           }
350         flags|=DOC_TYPE_KNOWN | DOC_DIR_KNOWN;
351         pcaster->notify_doc_flags_changed(this, 
352                                           DOC_TYPE_KNOWN | DOC_DIR_KNOWN, 0);
353         check_unnamed_files();
354         
355         /* Check for NAVM */
356         size=iff.get_chunk(chkid);
357         if (size && chkid=="NAVM")
358           {
359             djvm_nav=DjVmNav::create();
360             djvm_nav->decode(iff.get_bytestream());
361             iff.close_chunk();
362           }
363       }
364     else if (chkid=="DIR0")
365       {
366         DEBUG_MSG("Got OLD_BUNDLED file.\n");
367         doc_type=OLD_BUNDLED;
368         flags|=DOC_TYPE_KNOWN;
369         pcaster->notify_doc_flags_changed(this, DOC_TYPE_KNOWN, 0);
370         check_unnamed_files();
371       } 
372     else 
373       G_THROW( ERR_MSG("DjVuDocument.bad_format") );
374     
375     if (doc_type==OLD_BUNDLED)
376       {
377         // Read the DjVmDir0 directory. We are unable to tell what
378         // files are pages and what are included at this point.
379         // We only know that the first file with DJVU (BM44 or PM44)
380         // form *is* the first page. The rest will become known
381         // after we decode DjVuNavDir
382         djvm_dir0=DjVmDir0::create();
383         djvm_dir0->decode(*iff.get_bytestream());
384         iff.close_chunk();
385         // Get offset to the first DJVU, PM44 or BM44 chunk
386         int first_page_offset=0;
387         while(!first_page_offset)
388           {
389             int offset;
390             size=iff.get_chunk(chkid, &offset);
391             if (size==0) G_THROW( ERR_MSG("DjVuDocument.no_page") );
392             if (chkid=="FORM:DJVU" || 
393                 chkid=="FORM:PM44" || chkid=="FORM:BM44")
394               {
395                 DEBUG_MSG("Got 1st page offset=" << offset << "\n");
396                 first_page_offset=offset;
397               }
398             iff.close_chunk();
399           }
400         
401         // Now get the name of this file
402         int file_num;
403         for(file_num=0;file_num<djvm_dir0->get_files_num();file_num++)
404           {
405             DjVmDir0::FileRec & file=*djvm_dir0->get_file(file_num);
406             if (file.offset==first_page_offset)
407               {
408                 first_page_name=file.name;
409                 break;
410               }
411           }
412         if (!first_page_name.length())
413           G_THROW( ERR_MSG("DjVuDocument.no_page") );
414         flags|=DOC_DIR_KNOWN;
415         pcaster->notify_doc_flags_changed(this, DOC_DIR_KNOWN, 0);
416         check_unnamed_files();
417       }
418   } 
419   else // chkid!="FORM:DJVM"
420     {
421       // DJVU format
422       DEBUG_MSG("Got DJVU OLD_INDEXED or SINGLE_PAGE document here.\n");
423       doc_type=SINGLE_PAGE;
424       flags |= DOC_TYPE_KNOWN;
425       pcaster->notify_doc_flags_changed(this, DOC_TYPE_KNOWN, 0);
426       check_unnamed_files();
427     }
428   if (doc_type==OLD_BUNDLED || doc_type==SINGLE_PAGE)
429     {
430       DEBUG_MSG("Searching for NDIR chunks...\n");
431       ndir_file=get_djvu_file(-1);
432       if (ndir_file) ndir=ndir_file->decode_ndir();
433       ndir_file=0;     // Otherwise ~DjVuDocument() will stop (=kill) it
434       if (!ndir)
435         {
436           // Seems to be 1-page old-style document. Create dummy NDIR
437           if (doc_type==OLD_BUNDLED)
438             {
439               ndir=DjVuNavDir::create(GURL::UTF8("directory",init_url));
440               ndir->insert_page(-1, first_page_name);
441             } 
442           else
443             {
444               ndir=DjVuNavDir::create(GURL::UTF8("directory",init_url.base()));
445               ndir->insert_page(-1, init_url.fname());
446             }
447         } 
448       else
449         {
450           if (doc_type==SINGLE_PAGE)
451             doc_type=OLD_INDEXED;
452         }
453       flags|=DOC_NDIR_KNOWN;
454       pcaster->notify_doc_flags_changed(this, DOC_NDIR_KNOWN, 0);
455       check_unnamed_files();
456     }
457   
458   flags |= DOC_INIT_OK;
459   pcaster->notify_doc_flags_changed(this, DOC_INIT_OK, 0);
460   check_unnamed_files();
461   init_thread_flags|=FINISHED;
462   DEBUG_MSG("DOCUMENT IS FULLY INITIALIZED now: doc_type='" <<
463             (doc_type==BUNDLED ? "BUNDLED" :
464              doc_type==OLD_BUNDLED ? "OLD_BUNDLED" :
465              doc_type==INDIRECT ? "INDIRECT" :
466              doc_type==OLD_INDEXED ? "OLD_INDEXED" :
467              doc_type==SINGLE_PAGE ? "SINGLE_PAGE" :
468              "UNKNOWN") << "'\n");
469}
470
471bool
472DjVuDocument::wait_for_complete_init(void)
473{
474  flags.enter();
475  while(!(flags & DOC_INIT_FAILED) &&
476        !(flags & DOC_INIT_OK)) flags.wait();
477  flags.leave();
478  init_thread_flags.enter();
479  while (!(init_thread_flags & FINISHED))
480    init_thread_flags.wait();
481  init_thread_flags.leave();
482  return (flags & (DOC_INIT_OK | DOC_INIT_FAILED))!=0;
483}
484
485int
486DjVuDocument::wait_get_pages_num(void) const
487{
488  GSafeFlags &f=const_cast<GSafeFlags &>(flags);
489  f.enter();
490  while(!(f & DOC_TYPE_KNOWN) &&
491        !(f & DOC_INIT_FAILED) &&
492        !(f & DOC_INIT_OK)) f.wait();
493  f.leave();
494  return get_pages_num();
495}
496
497GUTF8String
498DjVuDocument::get_int_prefix(void) const
499{
500      // These NAMEs are used to enable DjVuFile sharing inside the same
501      // DjVuDocument using DjVuPortcaster. Since URLs are unique to the
502      // document, other DjVuDocuments cannot retrieve files until they're
503      // assigned some permanent name. After '?' there should be the real
504      // file's URL. Please note, that output of this function is used only
505      // as name for DjVuPortcaster. Not as a URL.
506   GUTF8String retval;
507   return retval.format("document_%p%d?", this, hash(init_url));
508}
509
510void
511DjVuDocument::set_file_aliases(const DjVuFile * file)
512{
513   DEBUG_MSG("DjVuDocument::set_file_aliases(): setting global aliases for file '"
514             << file->get_url() << "'\n");
515   DEBUG_MAKE_INDENT(3);
516
517   DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
518   
519   GMonitorLock lock(&((DjVuFile *) file)->get_safe_flags());
520   pcaster->clear_aliases(file);
521   if (file->is_decode_ok() && cache)
522   {
523         // If file is successfully decoded and caching is enabled,
524         // assign a global alias to this file, so that any other
525         // DjVuDocument will be able to use it.
526     
527      pcaster->add_alias(file, file->get_url().get_string());
528      if (flags & (DOC_NDIR_KNOWN | DOC_DIR_KNOWN))
529      {
530         int page_num=url_to_page(file->get_url());
531         if (page_num>=0)
532         {
533            if (page_num==0) pcaster->add_alias(file, init_url.get_string()+"#-1");
534            pcaster->add_alias(file, init_url.get_string()+"#"+GUTF8String(page_num));
535         }
536      }
537         // The following line MUST stay here. For OLD_INDEXED documents
538         // a page may finish decoding before DIR or NDIR becomes known
539         // (multithreading, remember), so the code above would not execute
540      pcaster->add_alias(file, file->get_url().get_string()+"#-1");
541   } else pcaster->add_alias(file, get_int_prefix()+file->get_url());
542}
543
544void
545DjVuDocument::check_unnamed_files(void)
546{
547  DEBUG_MSG("DjVuDocument::check_unnamed_files(): Seeing if we can fix some...\n");
548  DEBUG_MAKE_INDENT(3);
549 
550  if (flags & DOC_INIT_FAILED)
551  {
552    // Init failed. All unnamed files should be terminated
553    GCriticalSectionLock lock(&ufiles_lock);
554    for(GPosition pos=ufiles_list;pos;++pos)
555    {
556      GP<DjVuFile> file=ufiles_list[pos]->file;
557      file->stop_decode(true);
558      file->stop(false);        // Disable any access to data
559    }
560    ufiles_list.empty();
561    return;
562  }
563 
564  if ((flags & DOC_TYPE_KNOWN)==0)
565    return;
566 
567  // See the list of unnamed files (created when there was insufficient
568  // information about DjVuDocument structure) and try to fix those,
569  // which can be fixed at this time
570  while(true)
571  {
572    DjVuPortcaster * pcaster=get_portcaster();
573   
574    GP<UnnamedFile> ufile;
575    GURL new_url;
576    GPosition pos ;   
577           GCriticalSectionLock lock(&ufiles_lock);
578     for(pos=ufiles_list;pos;)
579     {
580        G_TRY
581        {
582          GP<UnnamedFile> f=ufiles_list[pos];
583          if (f->id_type==UnnamedFile::ID) 
584            new_url=id_to_url(f->id);
585          else 
586            new_url=page_to_url(f->page_num);
587          if (!new_url.is_empty())
588          {
589            ufile=f;
590            // Don't take it off the list. We want to be
591            // able to stop the init from ~DjVuDocument();
592            //
593            // ufiles_list.del(pos);
594            break;
595          } else if (is_init_complete())
596          {
597            // No empty URLs are allowed at this point.
598            // We now know all information about the document
599            // and can determine if a page is inside it or not
600            f->data_pool->set_eof();
601            GUTF8String msg;
602            if (f->id_type==UnnamedFile::ID)
603              msg= ERR_MSG("DjVuDocument.miss_page_name") "\t"+f->id;
604            else 
605              msg= ERR_MSG("DjVuDocument.miss_page_num") "\t"+GUTF8String(f->page_num);
606            G_THROW(msg);
607          }
608          ++pos;
609        }
610        G_CATCH(exc)
611        {
612          pcaster->notify_error(this, exc.get_cause());
613          GP<DataPool> pool=ufiles_list[pos]->data_pool;
614          if (pool)
615            pool->stop();
616          GPosition this_pos=pos;
617          ++pos;
618          ufiles_list.del(this_pos);
619        }
620        G_ENDCATCH;
621     }
622     
623     if (ufile && !new_url.is_empty())
624       {
625         DEBUG_MSG("Fixing file: '" << ufile->url << "'=>'" << new_url << "'\n");
626         // Now, once we know its real URL we can request a real DataPool and
627         // can connect the DataPool owned by DjVuFile to that real one
628         // Note, that now request_data() will not play fool because
629         // we have enough information
630         
631         G_TRY
632           {
633             if (ufile->data_pool)
634               {
635                 GP<DataPool> new_pool=pcaster->request_data(ufile->file, new_url);
636                 if(!new_pool)
637                   G_THROW( ERR_MSG("DjVuDocument.fail_URL") "\t"+new_url.get_string());
638                 ufile->data_pool->connect(new_pool);
639               }
640             ufile->file->set_name(new_url.fname());
641             ufile->file->move(new_url.base());
642             set_file_aliases(ufile->file);
643           }
644         G_CATCH(exc)
645           {
646             pcaster->notify_error(this, exc.get_cause());
647           }   
648         G_ENDCATCH;
649       }
650     else
651       break;
652     
653     // Remove the 'ufile' from the list
654     for(pos=ufiles_list;pos;++pos)
655       if (ufiles_list[pos]==ufile)
656         {
657           ufiles_list.del(pos);
658           break;
659         }
660  } // while(1)
661}
662
663int
664DjVuDocument::get_pages_num(void) const
665{
666   check();
667   if (flags & DOC_TYPE_KNOWN)
668      if (doc_type==BUNDLED || doc_type==INDIRECT)
669         return djvm_dir->get_pages_num();
670      else if (flags & DOC_NDIR_KNOWN)
671         return ndir->get_pages_num();
672   return 1;
673}
674
675GURL
676DjVuDocument::page_to_url(int page_num) const
677{
678   check();
679   DEBUG_MSG("DjVuDocument::page_to_url(): page_num=" << page_num << "\n");
680   DEBUG_MAKE_INDENT(3);
681   
682   GURL url;
683   if (flags & DOC_TYPE_KNOWN)
684      switch(doc_type)
685      {
686         case SINGLE_PAGE:
687         case OLD_INDEXED:
688         {
689            if (page_num<0) url=init_url;
690            else if (flags & DOC_NDIR_KNOWN) url=ndir->page_to_url(page_num);
691            break;
692         }
693         case OLD_BUNDLED:
694         {
695            if (page_num<0) page_num=0;
696            if (page_num==0 && (flags & DOC_DIR_KNOWN))
697               url=GURL::UTF8(first_page_name,init_url);
698            else if (flags & DOC_NDIR_KNOWN)
699               url=ndir->page_to_url(page_num);
700            break;
701         }
702         case BUNDLED:
703         {
704            if (page_num<0)
705              page_num=0;
706            if (flags & DOC_DIR_KNOWN)
707            {
708              GP<DjVmDir::File> file=djvm_dir->page_to_file(page_num);
709              if (!file) G_THROW( ERR_MSG("DjVuDocument.big_num") );
710              url=GURL::UTF8(file->get_load_name(),init_url);
711            }
712            break;
713         }
714         case INDIRECT:
715         {
716            if (page_num<0) page_num=0;
717            if (flags & DOC_DIR_KNOWN)
718            {
719               GP<DjVmDir::File> file=djvm_dir->page_to_file(page_num);
720               if (!file)
721                 G_THROW( ERR_MSG("DjVuDocument.big_num") );
722               url=GURL::UTF8(file->get_load_name(),init_url.base());
723            }
724            break;
725         }
726         default:
727            G_THROW( ERR_MSG("DjVuDocument.unk_type") );
728      }
729   return url;
730}
731
732int
733DjVuDocument::url_to_page(const GURL & url) const
734{
735   check();
736   DEBUG_MSG("DjVuDocument::url_to_page(): url='" << url << "'\n");
737   DEBUG_MAKE_INDENT(3);
738
739   int page_num=-1;
740   if (flags & DOC_TYPE_KNOWN)
741      switch(doc_type)
742      {
743         case SINGLE_PAGE:
744         case OLD_BUNDLED:
745         case OLD_INDEXED:
746         {
747            if (flags & DOC_NDIR_KNOWN) page_num=ndir->url_to_page(url);
748            break;
749         }
750         case BUNDLED:
751         {
752            if (flags & DOC_DIR_KNOWN)
753            {
754               GP<DjVmDir::File> file;
755               if (url.base()==init_url)
756                 file=djvm_dir->id_to_file(url.fname());
757               if (file)
758                 page_num=file->get_page_num();
759            }
760            break;
761         }
762         case INDIRECT:
763         {
764            if (flags & DOC_DIR_KNOWN)
765            {
766               GP<DjVmDir::File> file;
767               if (url.base()==init_url.base())
768                 file=djvm_dir->id_to_file(url.fname());
769               if (file)
770                 page_num=file->get_page_num();
771            }
772            break;
773         }
774         default:
775            G_THROW( ERR_MSG("DjVuDocument.unk_type") );
776      }
777   return page_num;
778}
779
780GURL
781DjVuDocument::id_to_url(const GUTF8String & id) const
782{
783   check();
784   DEBUG_MSG("DjVuDocument::id_to_url(): translating ID='" << id << "' to URL\n");
785   DEBUG_MAKE_INDENT(3);
786
787   if (flags & DOC_TYPE_KNOWN)
788      switch(doc_type)
789      {
790         case BUNDLED:
791            if (flags & DOC_DIR_KNOWN)
792            {
793              GP<DjVmDir::File> file=djvm_dir->id_to_file(id);
794              if (!file)
795              {
796                file=djvm_dir->name_to_file(id);
797                if (!file)
798                  file=djvm_dir->title_to_file(id);
799              }
800              if (file)
801                return GURL::UTF8(file->get_load_name(),init_url);
802            }
803            break;
804         case INDIRECT:
805            if (flags & DOC_DIR_KNOWN)
806            {
807               GP<DjVmDir::File> file=djvm_dir->id_to_file(id);
808               if (!file)
809               {
810                 file=djvm_dir->name_to_file(id);
811                 if (!file)
812                   file=djvm_dir->title_to_file(id);
813               }
814               if (file)
815                 return GURL::UTF8(file->get_load_name(),init_url.base());
816            }
817            break;
818         case OLD_BUNDLED:
819            if (flags & DOC_DIR_KNOWN)
820            {
821               GP<DjVmDir0::FileRec> frec=djvm_dir0->get_file(id);
822               if (frec)
823                 return GURL::UTF8(id,init_url);
824            }
825            break;
826         case OLD_INDEXED:
827         case SINGLE_PAGE:
828            return GURL::UTF8(id,init_url.base());
829            break;
830      }
831   return GURL();
832}
833
834GURL
835DjVuDocument::id_to_url(const DjVuPort * source, const GUTF8String &id)
836{
837   return id_to_url(id);
838}
839
840GP<DjVuFile>
841DjVuDocument::url_to_file(const GURL & url, bool dont_create) const
842      // This function is private and is called from two places:
843      // id_to_file() and get_djvu_file() ONLY when the structure is known
844{
845   check();
846   DEBUG_MSG("DjVuDocument::url_to_file(): url='" << url << "'\n");
847   DEBUG_MAKE_INDENT(3);
848
849      // Try DjVuPortcaster to find existing files.
850   DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
851   GP<DjVuPort> port;
852
853   if (cache)
854   {
855         // First - fully decoded files
856      port=pcaster->alias_to_port(url.get_string());
857      if (port && port->inherits("DjVuFile"))
858      {
859         DEBUG_MSG("found fully decoded file using DjVuPortcaster\n");
860         return (DjVuFile *) (DjVuPort *) port;
861      }
862   }
863
864      // Second - internal files
865   port=pcaster->alias_to_port(get_int_prefix()+url);
866   if (port && port->inherits("DjVuFile"))
867   {
868      DEBUG_MSG("found internal file using DjVuPortcaster\n");
869      return (DjVuFile *) (DjVuPort *) port;
870   }
871
872   GP<DjVuFile> file;
873   
874   if (!dont_create)
875   {
876      DEBUG_MSG("creating a new file\n");
877      file=DjVuFile::create(url,const_cast<DjVuDocument *>(this),recover_errors,verbose_eof);
878      const_cast<DjVuDocument *>(this)->set_file_aliases(file);
879   }
880
881   return file;
882}
883
884GP<DjVuFile>
885DjVuDocument::get_djvu_file(int page_num, bool dont_create) const
886{
887   check();
888   DEBUG_MSG("DjVuDocument::get_djvu_file(): request for page " << page_num << "\n");
889   DEBUG_MAKE_INDENT(3);
890
891   DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
892   
893   GURL url;
894   {
895         // I'm locking the flags because depending on what page_to_url()
896         // returns me, I'll be creating DjVuFile in different ways.
897         // And I don't want the situation to change between the moment I call
898         // id_to_url() and I actually create DjVuFile
899      GMonitorLock lock(&(const_cast<DjVuDocument *>(this)->flags));
900      url=page_to_url(page_num);
901      if (url.is_empty())
902      {
903            // If init is complete and url is empty, we know for sure, that
904            // smth is wrong with the page_num. So we can return ZERO.
905            // Otherwise we create a temporary file and wait for init to finish
906         if (is_init_complete()) return 0;
907         
908         DEBUG_MSG("Structure is not known => check <doc_url>#<page_num> alias...\n");
909         GP<DjVuPort> port;
910         if (cache)
911            port=pcaster->alias_to_port(init_url.get_string()+"#"+GUTF8String(page_num));
912         if (!port || !port->inherits("DjVuFile"))
913         {
914            DEBUG_MSG("failed => invent dummy URL and proceed\n");
915         
916               // Invent some dummy temporary URL. I don't care what it will
917               // be. I'll remember the page_num and will generate the correct URL
918               // after I learn what the document is
919            GUTF8String name("page");
920            name+=GUTF8String(page_num);
921            name+=".djvu";
922            url=invent_url(name);
923
924            GCriticalSectionLock(&(const_cast<DjVuDocument *>(this)->ufiles_lock));
925            for(GPosition pos=ufiles_list;pos;++pos)
926            {
927               GP<UnnamedFile> f=ufiles_list[pos];
928               if (f->url==url) return f->file;
929            }
930            GP<UnnamedFile> ufile=new UnnamedFile(UnnamedFile::PAGE_NUM, 0,
931                                                  page_num, url, 0);
932
933               // We're adding the record to the list before creating the DjVuFile
934               // because DjVuFile::init() will call request_data(), and the
935               // latter should be able to find the record.
936               //
937               // We also want to keep ufiles_lock to make sure that when
938               // request_data() is called, the record is still there
939            const_cast<DjVuDocument *>(this)->ufiles_list.append(ufile);
940     
941            GP<DjVuFile> file=
942              DjVuFile::create(url,const_cast<DjVuDocument *>(this),recover_errors,verbose_eof);
943            ufile->file=file;
944            return file;
945         } else url=((DjVuFile *) (DjVuPort *) port)->get_url();
946      }
947   }
948   
949   GP<DjVuFile> file=url_to_file(url, dont_create);
950   if (file) 
951     pcaster->add_route(file, const_cast<DjVuDocument *>(this));
952   return file;
953}
954
955GURL
956DjVuDocument::invent_url(const GUTF8String &name) const
957{
958   GUTF8String buffer;
959   buffer.format("djvufileurl://%p/%s", this, (const char *)name);
960   return GURL::UTF8(buffer);
961}
962
963GP<DjVuFile>
964DjVuDocument::get_djvu_file(const GUTF8String& id, bool dont_create)
965{
966  check();
967  DEBUG_MSG("DjVuDocument::get_djvu_file(): ID='" << id << "'\n");
968  DEBUG_MAKE_INDENT(3);
969  if (!id.length())
970    return get_djvu_file(-1);
971
972// Integers are not supported, only ID's 
973//  if (id.is_int())
974//     return get_djvu_file(id.toInt(),dont_create);
975 
976  GURL url;
977  // I'm locking the flags because depending on what id_to_url()
978  // returns me, I'll be creating DjVuFile in different ways.
979  // And I don't want the situation to change between the moment I call
980  // id_to_url() and I actually create DjVuFile
981  {
982    GMonitorLock lock(&flags);
983    url=id_to_url(id);
984    if(url.is_empty() && !id.is_int())
985    {
986      // If init is complete, we know for sure, that there is no such
987      // file with ID 'id' in the document. Otherwise we have to
988      // create a temporary file and wait for the init to finish
989      if (is_init_complete())
990        return 0;
991      // Invent some dummy temporary URL. I don't care what it will
992      // be. I'll remember the ID and will generate the correct URL
993      // after I learn what the document is
994      url=invent_url(id);
995      DEBUG_MSG("Invented url='" << url << "'\n");
996
997      GCriticalSectionLock lock(&ufiles_lock);
998      for(GPosition pos=ufiles_list;pos;++pos)
999      {
1000        GP<UnnamedFile> f=ufiles_list[pos];
1001        if (f->url==url)
1002          return f->file;
1003      }
1004      GP<UnnamedFile> ufile=new UnnamedFile(UnnamedFile::ID, id, 0, url, 0);
1005
1006      // We're adding the record to the list before creating the DjVuFile
1007      // because DjVuFile::init() will call request_data(), and the
1008      // latter should be able to find the record.
1009      //
1010      // We also want to keep ufiles_lock to make sure that when
1011      // request_data() is called, the record is still there
1012      ufiles_list.append(ufile);
1013     
1014      GP<DjVuFile> file=DjVuFile::create(url,this,recover_errors,verbose_eof);
1015      ufile->file=file;
1016      return file;
1017    }
1018  }
1019     
1020  return get_djvu_file(url,dont_create);
1021}
1022
1023GP<DjVuFile>
1024DjVuDocument::get_djvu_file(const GURL& url, bool dont_create)
1025{
1026   check();
1027   DEBUG_MSG("DjVuDocument::get_djvu_file(): URL='" << url << "'\n");
1028   DEBUG_MAKE_INDENT(3);
1029
1030   if (url.is_empty())
1031     return 0;
1032
1033   const GP<DjVuFile> file(url_to_file(url, dont_create));
1034
1035   if (file)
1036     get_portcaster()->add_route(file, this);
1037
1038   return file;
1039}
1040
1041GP<DjVuImage>
1042DjVuDocument::get_page(int page_num, bool sync, DjVuPort * port) const
1043{
1044   check();
1045   DEBUG_MSG("DjVuDocument::get_page(): request for page " << page_num << "\n");
1046   DEBUG_MAKE_INDENT(3);
1047
1048   GP<DjVuImage> dimg;
1049   const GP<DjVuFile> file(get_djvu_file(page_num));
1050   if (file)
1051   {
1052     dimg=DjVuImage::create(file);
1053     if (port)
1054       DjVuPort::get_portcaster()->add_route(dimg, port);
1055   
1056     file->resume_decode();
1057     if (dimg && sync)
1058       dimg->wait_for_complete_decode();
1059   }
1060   return dimg;
1061}
1062
1063GP<DjVuImage>
1064DjVuDocument::get_page(const GUTF8String &id, bool sync, DjVuPort * port)
1065{
1066   check();
1067   DEBUG_MSG("DjVuDocument::get_page(): ID='" << id << "'\n");
1068   DEBUG_MAKE_INDENT(3);
1069
1070   GP<DjVuImage> dimg;
1071   const GP<DjVuFile> file(get_djvu_file(id));
1072   if(file)
1073   {
1074     dimg=DjVuImage::create(file);
1075     if (port)
1076       DjVuPort::get_portcaster()->add_route(dimg, port);
1077   
1078     file->resume_decode();
1079     if (dimg && sync)
1080       dimg->wait_for_complete_decode();
1081   }
1082   return dimg;
1083}
1084
1085void
1086DjVuDocument::process_threqs(void)
1087      // Will look thru threqs_list and try to fulfil every request
1088{
1089  GCriticalSectionLock lock(&threqs_lock);
1090  for(GPosition pos=threqs_list;pos;)
1091  {
1092    GP<ThumbReq> req=threqs_list[pos];
1093    bool remove=false;
1094    if (req->thumb_file)
1095    {
1096      G_TRY {
1097               // There is supposed to be a file with thumbnails
1098        if (req->thumb_file->is_data_present())
1099        {
1100          // Cool, we can extract the thumbnail now
1101          GP<ByteStream> str=req->thumb_file->get_init_data_pool()->get_stream();
1102          GP<IFFByteStream> giff=IFFByteStream::create(str);
1103          IFFByteStream &iff=*giff;
1104          GUTF8String chkid;
1105          if (!iff.get_chunk(chkid) || chkid!="FORM:THUM")
1106            G_THROW( ERR_MSG("DjVuDocument.bad_thumb") );
1107         
1108          for(int i=0;i<req->thumb_chunk;i++)
1109          {
1110            if (!iff.get_chunk(chkid)) 
1111              G_THROW( ERR_MSG("DjVuDocument.bad_thumb") );
1112            iff.close_chunk();
1113          }
1114          if (!iff.get_chunk(chkid) || chkid!="TH44")
1115            G_THROW( ERR_MSG("DjVuDocument.bad_thumb") );
1116         
1117          // Copy the data
1118          char buffer[1024];
1119          int length;
1120          while((length=iff.read(buffer, 1024)))
1121            req->data_pool->add_data(buffer, length);
1122          req->data_pool->set_eof();
1123         
1124          // Also add this file to cache so that we won't have
1125          // to download it next time
1126          add_to_cache(req->thumb_file);
1127         
1128          req->thumb_file=0;
1129          req->image_file=0;
1130          remove=true;
1131        }
1132      } G_CATCH(exc) {
1133        GUTF8String msg= ERR_MSG("DjVuDocument.cant_extract") "\n";
1134        msg+=exc.get_cause();
1135        get_portcaster()->notify_error(this, msg);
1136               // Switch this request to the "decoding" mode
1137        req->image_file=get_djvu_file(req->page_num);
1138        req->thumb_file=0;
1139        req->data_pool->set_eof();
1140        remove=true;
1141      } G_ENDCATCH;
1142    } // if (req->thumb_file)
1143   
1144    if (req->image_file)
1145    {
1146      G_TRY {
1147               // Decode the file if necessary. Or just used predecoded image.
1148        GSafeFlags & file_flags=req->image_file->get_safe_flags();
1149        {
1150          GMonitorLock lock(&file_flags);
1151          if (!req->image_file->is_decoding())
1152          {
1153            if (req->image_file->is_decode_ok())
1154            {
1155              // We can generate it now
1156              const GP<DjVuImage> dimg(DjVuImage::create(req->image_file));
1157             
1158              dimg->wait_for_complete_decode();
1159             
1160              int width = 160;
1161              int height = 160;
1162             
1163              if( dimg->get_width() )
1164                width = dimg->get_width();
1165              if( dimg->get_height() )
1166                height = dimg->get_height();
1167             
1168              GRect rect(0, 0, 160, height*160/width);
1169              GP<GPixmap> pm=dimg->get_pixmap(rect, rect, thumb_gamma);
1170              if (!pm)
1171              {
1172                GP<GBitmap> bm=dimg->get_bitmap(rect, rect, sizeof(int));
1173                if(bm)
1174                  pm=GPixmap::create(*bm);
1175                else
1176                  pm = GPixmap::create(rect.height(), rect.width(), 
1177                                       &GPixel::WHITE);
1178              }
1179             
1180              // Store and compress the pixmap
1181              GP<IW44Image> iwpix=IW44Image::create_encode(*pm);
1182              GP<ByteStream> gstr=ByteStream::create();
1183              IWEncoderParms parms;
1184              parms.slices=97;
1185              parms.bytes=0;
1186              parms.decibels=0;
1187              iwpix->encode_chunk(gstr, parms);
1188              TArray<char> data=gstr->get_data();
1189             
1190              req->data_pool->add_data((const char *) data, data.size());
1191              req->data_pool->set_eof();
1192             
1193              req->thumb_file=0;
1194              req->image_file=0;
1195              remove=true;
1196            } else if (req->image_file->is_decode_failed())
1197            {
1198              // Unfortunately we cannot decode it
1199              req->thumb_file=0;
1200              req->image_file=0;
1201              req->data_pool->set_eof();
1202              remove=true;
1203            } else
1204            {
1205              req->image_file->start_decode();
1206            }
1207          }
1208        }
1209      } G_CATCH(exc) {
1210        GUTF8String msg="Failed to decode thumbnails:\n";
1211        msg+=exc.get_cause();
1212        get_portcaster()->notify_error(this, msg);
1213       
1214               // Get rid of this request
1215        req->image_file=0;
1216        req->thumb_file=0;
1217        req->data_pool->set_eof();
1218        remove=true;
1219      } G_ENDCATCH;
1220    }
1221   
1222    if (remove)
1223    {
1224      GPosition this_pos=pos;
1225      ++pos;
1226      threqs_list.del(this_pos);
1227    } else ++pos;
1228  }
1229}
1230
1231GP<DjVuDocument::ThumbReq>
1232DjVuDocument::add_thumb_req(const GP<ThumbReq> & thumb_req)
1233      // Will look through the list of pending requests for thumbnails
1234      // and try to add the specified request. If a duplicate is found,
1235      // it will be returned and the list will not be modified
1236{
1237   GCriticalSectionLock lock(&threqs_lock);
1238   for(GPosition pos=threqs_list;pos;++pos)
1239   {
1240      GP<ThumbReq> req=threqs_list[pos];
1241      if (req->page_num==thumb_req->page_num)
1242         return req;
1243   }
1244   threqs_list.append(thumb_req);
1245   return thumb_req;
1246}
1247
1248GList<GUTF8String>
1249DjVuDocument::get_id_list(void)
1250{
1251  GList<GUTF8String> ids;
1252  if (is_init_complete())
1253  {
1254    if(djvm_dir)
1255    {
1256      GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
1257      for(GPosition pos=files_list;pos;++pos)
1258      {
1259        ids.append(files_list[pos]->get_load_name());
1260      }
1261    }else
1262    {
1263      const int page_num=get_pages_num();
1264      for(int page=0;page<page_num;page++)
1265      { 
1266        ids.append(page_to_url(page).fname());
1267      }
1268    }
1269  }
1270  return ids;
1271}
1272
1273void
1274DjVuDocument::map_ids(GMap<GUTF8String,void *> &map)
1275{
1276  GList<GUTF8String> ids=get_id_list();
1277  for(GPosition pos=ids;pos;++pos)
1278  {
1279    map[ids[pos]]=0;
1280  }
1281}
1282
1283GP<DataPool>
1284DjVuDocument::get_thumbnail(int page_num, bool dont_decode)
1285{
1286   DEBUG_MSG("DjVuDocument::get_thumbnail(): page_num=" << page_num << "\n");
1287   DEBUG_MAKE_INDENT(3);
1288
1289   if (!is_init_complete()) return 0;
1290   
1291   {
1292         // See if we already have request for this thumbnail pending
1293      GCriticalSectionLock lock(&threqs_lock);
1294      for(GPosition pos=threqs_list;pos;++pos)
1295      {
1296         GP<ThumbReq> req=threqs_list[pos];
1297         if (req->page_num==page_num)
1298            return req->data_pool;      // That's it. Just return it.
1299      }
1300   }
1301
1302      // No pending request for this page... Create one
1303   GP<ThumbReq> thumb_req=new ThumbReq(page_num, DataPool::create());
1304   
1305      // First try to find predecoded thumbnail
1306   if (get_doc_type()==INDIRECT || get_doc_type()==BUNDLED)
1307   {
1308         // Predecoded thumbnails exist for new formats only
1309      GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
1310      GP<DjVmDir::File> thumb_file;
1311      int thumb_start=0;
1312      int page_cnt=-1;
1313      for(GPosition pos=files_list;pos;++pos)
1314      {
1315         GP<DjVmDir::File> f=files_list[pos];
1316         if (f->is_thumbnails())
1317         {
1318            thumb_file=f;
1319            thumb_start=page_cnt+1;
1320         } else if (f->is_page())
1321         {
1322           page_cnt++;
1323         }
1324         if (page_cnt==page_num) break;
1325      }
1326      if (thumb_file)
1327      {
1328            // That's the file with the desired thumbnail image
1329         thumb_req->thumb_file=get_djvu_file(thumb_file->get_load_name());
1330         thumb_req->thumb_chunk=page_num-thumb_start;
1331         thumb_req=add_thumb_req(thumb_req);
1332         process_threqs();
1333         return thumb_req->data_pool;
1334      }
1335   }
1336
1337      // Apparently we're out of luck and need to decode the requested
1338      // page (unless it's already done and if it's allowed) and render
1339      // it into the thumbnail. If dont_decode is true, do not attempt
1340      // to create this file (because this will result in a request for data)
1341   GP<DjVuFile> file=get_djvu_file(page_num, dont_decode);
1342   if (file)
1343   {
1344      thumb_req->image_file=file;
1345
1346         // I'm locking the flags here to make sure, that DjVuFile will not
1347         // change its state in between of the checks.
1348      GSafeFlags & file_flags=file->get_safe_flags();
1349      {
1350         GMonitorLock lock(&file_flags);
1351         if (thumb_req->image_file->is_decode_ok() || !dont_decode)
1352         {
1353               // Just add it to the list and call process_threqs(). It
1354               // will start decoding if necessary
1355            thumb_req=add_thumb_req(thumb_req);
1356            process_threqs();
1357         } else
1358         {
1359               // Nothing can be done return ZERO
1360            thumb_req=0;
1361         }
1362      }
1363   } else thumb_req=0;
1364   
1365   if (thumb_req) return thumb_req->data_pool;
1366   else return 0;
1367}
1368
1369static void
1370add_to_cache(const GP<DjVuFile> & f, GMap<GURL, void *> & map,
1371             DjVuFileCache * cache)
1372{
1373   GURL url=f->get_url();
1374   DEBUG_MSG("DjVuDocument::add_to_cache(): url='" << url << "'\n");
1375   DEBUG_MAKE_INDENT(3);
1376   
1377   if (!map.contains(url))
1378   {
1379      map[url]=0;
1380      cache->add_file(f);
1381     
1382      GPList<DjVuFile> list;
1383      for(GPosition pos=list;pos;++pos)
1384         add_to_cache(list[pos], map, cache);
1385   }
1386}
1387
1388void
1389DjVuDocument::add_to_cache(const GP<DjVuFile> & f)
1390{
1391   if (cache)
1392   {
1393      GMap<GURL, void *> map;
1394      ::add_to_cache(f, map, cache);
1395   }
1396}
1397
1398void
1399DjVuDocument::notify_file_flags_changed(const DjVuFile * source,
1400                                        long set_mask, long clr_mask)
1401{
1402      // Don't check here if the document is initialized or not.
1403      // This function may be called when it's not.
1404      // check();
1405   if (set_mask & DjVuFile::DECODE_OK)
1406   {
1407      set_file_aliases(source);
1408      if (cache) add_to_cache((DjVuFile *) source);
1409      if(!needs_compression_flag)
1410      {
1411        if(source->needs_compression())
1412        {
1413          can_compress_flag=true;
1414          needs_compression_flag=true;
1415        }else if(source->can_compress())
1416        {
1417          can_compress_flag=true;
1418        }
1419      }
1420   }
1421   process_threqs();
1422}
1423
1424GP<DjVuFile>
1425DjVuDocument::id_to_file(const DjVuPort * source, const GUTF8String &id)
1426{
1427   return (DjVuFile *) get_djvu_file(id);
1428}
1429
1430GP<DataPool>
1431DjVuDocument::request_data(const DjVuPort * source, const GURL & url)
1432{
1433   DEBUG_MSG("DjVuDocument::request_data(): seeing if we can do it\n");
1434   DEBUG_MAKE_INDENT(3);
1435
1436   if (url==init_url)
1437     return init_data_pool;
1438
1439   check();     // Don't put it before 'init_data_pool'
1440
1441   {
1442         // See if there is a file in the "UnnamedFiles" list.
1443         // If it's there, then create an empty DataPool and store its
1444         // pointer in the list. The "init thread" will eventually
1445         // do smth with it.
1446      GCriticalSectionLock lock(&ufiles_lock);
1447      for(GPosition pos=ufiles_list;pos;++pos)
1448      {
1449         GP<UnnamedFile> f=ufiles_list[pos];
1450         if (f->url==url)
1451         {
1452            DEBUG_MSG("Found tmp unnamed DjVuFile. Return empty DataPool\n");
1453               // Remember the DataPool. We will connect it to the
1454               // actual data after the document structure becomes known
1455            f->data_pool=DataPool::create();
1456            return f->data_pool;
1457         }
1458      }
1459   }
1460
1461      // Well, the url is not in the "UnnamedFiles" list, but it doesn't
1462      // mean, that it's not "artificial". Stay alert!
1463   GP<DataPool> data_pool;
1464   if (flags & DOC_TYPE_KNOWN)
1465      switch(doc_type)
1466      {
1467         case OLD_BUNDLED:
1468         {
1469            if (flags & DOC_DIR_KNOWN)
1470            {
1471               DEBUG_MSG("The document is in OLD_BUNDLED format\n");
1472               if (url.base()!=init_url)
1473                        G_THROW( ERR_MSG("DjVuDocument.URL_outside") "\t"+url.get_string());
1474         
1475               GP<DjVmDir0::FileRec> file=djvm_dir0->get_file(url.fname());
1476               if (!file)
1477               {
1478                 G_THROW( ERR_MSG("DjVuDocument.file_outside") "\t"+url.fname());
1479               }
1480               data_pool=DataPool::create(init_data_pool, file->offset, file->size);
1481            }
1482            break;
1483         }
1484         case BUNDLED:
1485         {
1486            if (flags & DOC_DIR_KNOWN)
1487            {
1488               DEBUG_MSG("The document is in new BUNDLED format\n");
1489               if (url.base()!=init_url)
1490               {
1491                 G_THROW( ERR_MSG("DjVuDocument.URL_outside") "\t"
1492                   +url.get_string());
1493               }
1494         
1495               GP<DjVmDir::File> file=djvm_dir->id_to_file(url.fname());
1496               if (!file)
1497               {
1498                 G_THROW( ERR_MSG("DjVuDocument.file_outside") "\t"+url.fname());
1499               }
1500               data_pool=DataPool::create(init_data_pool, file->offset, file->size);
1501            }
1502            break;
1503         }
1504         case SINGLE_PAGE:
1505         case OLD_INDEXED:
1506         case INDIRECT:
1507         {
1508            DEBUG_MSG("The document is in SINGLE_PAGE or OLD_INDEXED or INDIRECT format\n");
1509            if (flags & DOC_DIR_KNOWN)
1510               if (doc_type==INDIRECT && !djvm_dir->id_to_file(url.fname()))
1511                        G_THROW( ERR_MSG("DjVuDocument.URL_outside2") "\t"+url.get_string());
1512         
1513            if (url.is_local_file_url())
1514            {
1515//             GUTF8String fname=GOS::url_to_filename(url);
1516//             if (GOS::basename(fname)=="-") fname="-";
1517               DEBUG_MSG("url=" << url << "\n");
1518
1519               data_pool=DataPool::create(url);
1520            }
1521         }
1522      }
1523   return data_pool;
1524}
1525
1526
1527static void
1528add_file_to_djvm(const GP<DjVuFile> & file, bool page,
1529                 DjVmDoc & doc, GMap<GURL, void *> & map)
1530      // This function is used only for obsolete formats.
1531      // For new formats there is no need to process files recursively.
1532      // All information is already available from the DJVM chunk
1533{
1534   GURL url=file->get_url();
1535
1536   if (!map.contains(url))
1537   {
1538      map[url]=0;
1539
1540      if (file->get_chunks_number()>0 && !file->contains_chunk("NDIR"))
1541      {
1542            // Get the data and unlink any file containing NDIR chunk.
1543            // Yes. We're lazy. We don't check if those files contain
1544            // anything else.
1545         GPosition pos;
1546         GPList<DjVuFile> files_list=file->get_included_files(false);
1547         GP<DataPool> data=file->get_djvu_data(false);
1548         for(pos=files_list;pos;++pos)
1549         {
1550            GP<DjVuFile> f=files_list[pos];
1551            if (f->contains_chunk("NDIR"))
1552               data=DjVuFile::unlink_file(data, f->get_url().fname());
1553         }
1554         
1555            // Finally add it to the document
1556         GUTF8String name=file->get_url().fname();
1557         GP<DjVmDir::File> file_rec=DjVmDir::File::create(
1558           name, name, name,
1559           page ? DjVmDir::File::PAGE : DjVmDir::File::INCLUDE );
1560         doc.insert_file(file_rec, data, -1);
1561
1562            // And repeat for all included files
1563         for(pos=files_list;pos;++pos)
1564            add_file_to_djvm(files_list[pos], false, doc, map);
1565      }
1566   }
1567}
1568
1569static void
1570add_file_to_djvm(const GP<DjVuFile> & file, bool page,
1571                 DjVmDoc & doc, GMap<GURL, void *> & map, 
1572                 bool &needs_compression_flag, bool &can_compress_flag )
1573{
1574  if(!needs_compression_flag)
1575  {
1576    if(file->needs_compression())
1577    {
1578      can_compress_flag=true;
1579      needs_compression_flag=true;
1580    }else if(file->can_compress())
1581    {
1582      can_compress_flag=true;
1583    }
1584  }
1585  add_file_to_djvm(file,page,doc,map);
1586}
1587
1588static void
1589local_get_url_names(DjVuFile * f,const GMap<GURL, void *> & map,GMap<GURL,void *> &tmpmap)
1590{
1591   GURL url=f->get_url();
1592   if (!map.contains(url) && !tmpmap.contains(url))
1593   {
1594      tmpmap[url]=0;
1595      f->process_incl_chunks();
1596      GPList<DjVuFile> files_list=f->get_included_files(false);
1597      for(GPosition pos=files_list;pos;++pos)
1598         local_get_url_names(files_list[pos], map, tmpmap);
1599   }
1600}
1601
1602static void
1603local_get_url_names(DjVuFile * f, GMap<GURL, void *> & map)
1604{
1605   GMap<GURL,void *> tmpmap;
1606   local_get_url_names(f,map,tmpmap);
1607   for(GPosition pos=tmpmap;pos;++pos)
1608     map[tmpmap.key(pos)]=0;
1609}
1610
1611GList<GURL>
1612DjVuDocument::get_url_names(void)
1613{
1614  check();
1615
1616  GCriticalSectionLock lock(&url_names_lock);
1617  if(has_url_names)
1618    return url_names;
1619
1620  GMap<GURL, void *> map;
1621  int i;
1622  if (doc_type==BUNDLED || doc_type==INDIRECT)
1623  {
1624    GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
1625    for(GPosition pos=files_list;pos;++pos)
1626    {
1627      GURL url=id_to_url(files_list[pos]->get_load_name());
1628      map[url]=0;
1629    }
1630  }else
1631  {
1632    int pages_num=get_pages_num();
1633    for(i=0;i<pages_num;i++)
1634    {
1635      G_TRY
1636      {
1637        local_get_url_names(get_djvu_file(i), map);
1638      }
1639      G_CATCH(ex)
1640      {
1641        // Why is this try/catch block here?
1642        G_TRY { 
1643          get_portcaster()->notify_error(this, ex.get_cause()); 
1644          GUTF8String emsg = ERR_MSG("DjVuDocument.exclude_page") "\t" + (i+1);
1645          get_portcaster()->notify_error(this, emsg);
1646        }
1647        G_CATCH_ALL
1648        {
1649          G_RETHROW;
1650        }
1651        G_ENDCATCH;
1652      }
1653      G_ENDCATCH;
1654    }
1655  }
1656  for(GPosition j=map;j;++j)
1657  {
1658    if (map.key(j).is_local_file_url())
1659    {
1660      url_names.append(map.key(j));
1661    }
1662  }
1663  has_url_names=true;
1664  return url_names;
1665}
1666
1667GP<DjVmDoc>
1668DjVuDocument::get_djvm_doc()
1669      // This function may block for data
1670{
1671   check();
1672   DEBUG_MSG("DjVuDocument::get_djvm_doc(): creating the DjVmDoc\n");
1673   DEBUG_MAKE_INDENT(3);
1674
1675   if (!is_init_complete())
1676     G_THROW( ERR_MSG("DjVuDocument.init_not_done") );
1677
1678   GP<DjVmDoc> doc=DjVmDoc::create();
1679
1680   if (doc_type==BUNDLED || doc_type==INDIRECT)
1681     {
1682       GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
1683       for(GPosition pos=files_list;pos;++pos)
1684         {
1685           GP<DjVmDir::File> f=new DjVmDir::File(*files_list[pos]);
1686           GP<DjVuFile> file=url_to_file(id_to_url(f->get_load_name()));
1687           GP<DataPool> data;
1688           if (file->is_modified()) 
1689             data=file->get_djvu_data(false);
1690           else 
1691             data=file->get_init_data_pool();
1692           doc->insert_file(f, data);
1693         }
1694       if (djvm_nav)
1695         doc->set_djvm_nav(djvm_nav);
1696     } 
1697   else if (doc_type==SINGLE_PAGE)
1698     {
1699       DEBUG_MSG("Creating: djvm for a single page document.\n");
1700       GMap<GURL, void *> map_add;
1701       GP<DjVuFile> file=get_djvu_file(0);
1702       add_file_to_djvm(file, true, *doc, map_add,
1703                        needs_compression_flag,can_compress_flag);
1704     } 
1705   else
1706     {
1707       DEBUG_MSG("Converting: the document is in an old format.\n");
1708       GMap<GURL, void *> map_add;
1709       if(recover_errors == ABORT)
1710         {
1711           for(int page_num=0;page_num<ndir->get_pages_num();page_num++)
1712             {
1713               GP<DjVuFile> file=url_to_file(ndir->page_to_url(page_num));
1714               add_file_to_djvm(file, true, *doc, map_add,
1715                                needs_compression_flag,can_compress_flag);
1716             }
1717         }
1718       else
1719         {
1720           for(int page_num=0;page_num<ndir->get_pages_num();page_num++)
1721             {
1722               G_TRY
1723                 {
1724                   GP<DjVuFile> file=url_to_file(ndir->page_to_url(page_num));
1725                   add_file_to_djvm(file, true, *doc, map_add,
1726                                    needs_compression_flag,can_compress_flag);
1727                 }
1728               G_CATCH(ex)
1729                 {
1730                   G_TRY { 
1731                     get_portcaster()->notify_error(this, ex.get_cause());
1732                     GUTF8String emsg = ERR_MSG("DjVuDocument.skip_page") "\t" 
1733                                      + (page_num+1);
1734                     get_portcaster()->notify_error(this, emsg);
1735                   }
1736                   G_CATCH_ALL
1737                     {
1738                       G_RETHROW;
1739                     }
1740                   G_ENDCATCH;
1741                 }
1742               G_ENDCATCH;
1743             }
1744         }
1745     }
1746   return doc;
1747}
1748
1749void
1750DjVuDocument::write( const GP<ByteStream> &gstr,
1751  const GMap<GUTF8String,void *> &reserved)
1752{
1753  DEBUG_MSG("DjVuDocument::write(): storing DjVmDoc into ByteStream\n");
1754  DEBUG_MAKE_INDENT(3);
1755  get_djvm_doc()->write(gstr,reserved); 
1756}
1757
1758void
1759DjVuDocument::write(const GP<ByteStream> &gstr, bool force_djvm)
1760{
1761  DEBUG_MSG("DjVuDocument::write(): storing DjVmDoc into ByteStream\n");
1762  DEBUG_MAKE_INDENT(3);
1763   
1764  GP<DjVmDoc> doc=get_djvm_doc();
1765  GP<DjVmDir> dir=doc->get_djvm_dir();
1766  if (force_djvm || dir->get_files_num()>1)
1767  {
1768    doc->write(gstr);
1769  }else
1770  {
1771    GPList<DjVmDir::File> files_list=dir->resolve_duplicates(false);
1772    GP<DataPool> pool=doc->get_data(files_list[files_list]->get_load_name());
1773    GP<ByteStream> pool_str=pool->get_stream();
1774    ByteStream &str=*gstr;
1775    str.writall(octets,4);
1776    str.copy(*pool_str);
1777  }
1778}
1779
1780void
1781DjVuDocument::expand(const GURL &codebase, const GUTF8String &idx_name)
1782{
1783   DEBUG_MSG("DjVuDocument::expand(): codebase='" << codebase << "'\n");
1784   DEBUG_MAKE_INDENT(3);
1785   
1786   GP<DjVmDoc> doc=get_djvm_doc();
1787   doc->expand(codebase, idx_name);
1788}
1789
1790void
1791DjVuDocument::save_as(const GURL &where, const bool bundled)
1792{
1793   DEBUG_MSG("DjVuDocument::save_as(): where='" << where <<
1794             "', bundled=" << bundled << "\n");
1795   DEBUG_MAKE_INDENT(3);
1796   
1797   if (needs_compression())
1798   { 
1799     if(!djvu_compress_codec)
1800     {
1801       G_THROW( ERR_MSG("DjVuDocument.comp_codec") );
1802     }
1803     GP<ByteStream> gmbs=ByteStream::create();
1804     write(gmbs);
1805     ByteStream &mbs=*gmbs;
1806     mbs.flush();
1807     mbs.seek(0,SEEK_SET);
1808     (*djvu_compress_codec)(gmbs,where,bundled);
1809   }else if (bundled)
1810   {
1811      DataPool::load_file(where);
1812      write(ByteStream::create(where, "wb"));
1813   } else 
1814   {
1815     expand(where.base(), where.fname());
1816   }
1817}
1818
1819static const char prolog[]="<?xml version=\"1.0\" ?>\n<!DOCTYPE DjVuXML PUBLIC \"-//W3C//DTD DjVuXML 1.1//EN\" \"pubtext/DjVuXML-s.dtd\">\n<DjVuXML>\n<HEAD>";
1820static const char start_xml[]="</HEAD>\n<BODY>\n";
1821static const char end_xml[]="</BODY>\n</DjVuXML>\n";
1822
1823void
1824DjVuDocument::writeDjVuXML(const GP<ByteStream> &gstr_out,int flags) const
1825{
1826  ByteStream &str_out=*gstr_out;
1827  str_out.writestring(
1828    prolog+get_init_url().get_string().toEscaped()+start_xml);
1829  const int pages=wait_get_pages_num();
1830  for(int page_num=0;page_num<pages;++page_num)
1831  {
1832    const GP<DjVuImage> dimg(get_page(page_num,true));
1833    if(!dimg)
1834    {
1835      G_THROW( ERR_MSG("DjVuToText.decode_failed") );
1836    }
1837    dimg->writeXML(str_out,get_init_url(),flags);
1838  }
1839  str_out.writestring(GUTF8String(end_xml));
1840}
1841
1842
1843#ifdef HAVE_NAMESPACES
1844}
1845# ifndef NOT_USING_DJVU_NAMESPACE
1846using namespace DJVU;
1847# endif
1848#endif
Note: See TracBrowser for help on using the repository browser.