source: trunk/libdjvu/DjVuDocument.cpp @ 280

Last change on this file since 280 was 280, checked in by rbri, 11 years ago

DJVU plugin: djvulibre updated to version 3.5.22

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