source: trunk/libdjvu/DjVuDocument.cpp @ 15

Last change on this file since 15 was 15, checked in by Eugene Romanenko, 15 years ago

needed libs update

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