• Skip to content
  • Skip to link menu
KDE 3.5 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

kviewshell

DjVuDocument.cpp

Go to the documentation of this file.
00001 //C-  -*- C++ -*-
00002 //C- -------------------------------------------------------------------
00003 //C- DjVuLibre-3.5
00004 //C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
00005 //C- Copyright (c) 2001  AT&T
00006 //C-
00007 //C- This software is subject to, and may be distributed under, the
00008 //C- GNU General Public License, Version 2. The license should have
00009 //C- accompanied the software or you may obtain a copy of the license
00010 //C- from the Free Software Foundation at http://www.fsf.org .
00011 //C-
00012 //C- This program is distributed in the hope that it will be useful,
00013 //C- but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 //C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 //C- GNU General Public License for more details.
00016 //C- 
00017 //C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library
00018 //C- distributed by Lizardtech Software.  On July 19th 2002, Lizardtech 
00019 //C- Software authorized us to replace the original DjVu(r) Reference 
00020 //C- Library notice by the following text (see doc/lizard2002.djvu):
00021 //C-
00022 //C-  ------------------------------------------------------------------
00023 //C- | DjVu (r) Reference Library (v. 3.5)
00024 //C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
00025 //C- | The DjVu Reference Library is protected by U.S. Pat. No.
00026 //C- | 6,058,214 and patents pending.
00027 //C- |
00028 //C- | This software is subject to, and may be distributed under, the
00029 //C- | GNU General Public License, Version 2. The license should have
00030 //C- | accompanied the software or you may obtain a copy of the license
00031 //C- | from the Free Software Foundation at http://www.fsf.org .
00032 //C- |
00033 //C- | The computer code originally released by LizardTech under this
00034 //C- | license and unmodified by other parties is deemed "the LIZARDTECH
00035 //C- | ORIGINAL CODE."  Subject to any third party intellectual property
00036 //C- | claims, LizardTech grants recipient a worldwide, royalty-free, 
00037 //C- | non-exclusive license to make, use, sell, or otherwise dispose of 
00038 //C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the 
00039 //C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU 
00040 //C- | General Public License.   This grant only confers the right to 
00041 //C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to 
00042 //C- | the extent such infringement is reasonably necessary to enable 
00043 //C- | recipient to make, have made, practice, sell, or otherwise dispose 
00044 //C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to 
00045 //C- | any greater extent that may be necessary to utilize further 
00046 //C- | modifications or combinations.
00047 //C- |
00048 //C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
00049 //C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
00050 //C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
00051 //C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
00052 //C- +------------------------------------------------------------------
00053 // 
00054 // $Id: DjVuDocument.cpp,v 1.13 2005/05/25 20:24:52 leonb Exp $
00055 // $Name: release_3_5_15 $
00056 
00057 #ifdef HAVE_CONFIG_H
00058 # include "config.h"
00059 #endif
00060 #if NEED_GNUG_PRAGMAS
00061 # pragma implementation
00062 #endif
00063 
00064 #include "DjVuDocument.h"
00065 #include "DjVmDoc.h"
00066 #include "DjVmDir0.h"
00067 #include "DjVmNav.h"
00068 #include "DjVuNavDir.h"
00069 #include "DjVuImage.h"
00070 #include "DjVuFileCache.h"
00071 #include "IFFByteStream.h"
00072 #include "GOS.h"
00073 #include "DataPool.h"
00074 #include "IW44Image.h"
00075 #include "GRect.h"
00076 
00077 #include "debug.h"
00078 
00079 
00080 #ifdef HAVE_NAMESPACES
00081 namespace DJVU {
00082 # ifdef NOT_DEFINED // Just to fool emacs c++ mode
00083 }
00084 #endif
00085 #endif
00086 
00087 
00088 static const char octets[4]={0x41,0x54,0x26,0x54};
00089 const float DjVuDocument::thumb_gamma=(float)2.20;
00090 
00091 void (* DjVuDocument::djvu_import_codec)(
00092   GP<DataPool> &pool, const GURL &url, bool &needs_compression,
00093   bool &needs_rename )=0;
00094 
00095 void (* DjVuDocument::djvu_compress_codec)(
00096   GP<ByteStream> &doc,const GURL &where,bool bundled)=0;
00097 
00098 void
00099 DjVuDocument::set_import_codec(
00100   void (*codec)(
00101     GP<DataPool> &pool, const GURL &url, bool &needs_compression, bool &needs_rename ))
00102 {
00103   djvu_import_codec=codec;
00104 }
00105 
00106 void
00107 DjVuDocument::set_compress_codec(
00108   void (* codec)(
00109     GP<ByteStream> &doc,const GURL &where,bool bundled))
00110 {
00111   djvu_compress_codec=codec;
00112 }
00113 
00114 DjVuDocument::DjVuDocument(void)
00115   : doc_type(UNKNOWN_TYPE),
00116     needs_compression_flag(false),
00117     can_compress_flag(false),
00118     needs_rename_flag(false),
00119     has_url_names(false),
00120     recover_errors(ABORT),
00121     verbose_eof(false),
00122     init_started(false),
00123     cache(0) 
00124 {
00125 }
00126 
00127 GP<DjVuDocument>
00128 DjVuDocument::create(
00129   GP<DataPool> pool, GP<DjVuPort> xport, DjVuFileCache * const xcache)
00130 {
00131   DjVuDocument *doc=new DjVuDocument;
00132   GP<DjVuDocument> retval=doc;
00133   doc->init_data_pool=pool;
00134   doc->start_init(GURL(),xport,xcache);
00135   return retval;
00136 }
00137 
00138 GP<DjVuDocument>
00139 DjVuDocument::create(
00140   const GP<ByteStream> &bs, GP<DjVuPort> xport, DjVuFileCache * const xcache)
00141 {
00142   return create(DataPool::create(bs),xport,xcache);
00143 }
00144 
00145 GP<DjVuDocument>
00146 DjVuDocument::create_wait(
00147   const GURL &url, GP<DjVuPort> xport, DjVuFileCache * const xcache)
00148 {
00149   GP<DjVuDocument> retval=create(url,xport,xcache);
00150   retval->wait_for_complete_init();
00151   return retval;
00152 }
00153 
00154 void
00155 DjVuDocument::start_init(
00156   const GURL & url, GP<DjVuPort> xport, DjVuFileCache * xcache)
00157 {
00158    DEBUG_MSG("DjVuDocument::start_init(): initializing class...\n");
00159    DEBUG_MAKE_INDENT(3);
00160    if (init_started)
00161       G_THROW( ERR_MSG("DjVuDocument.2nd_init") );
00162    if (!get_count())
00163       G_THROW( ERR_MSG("DjVuDocument.not_secure") );
00164    if(url.is_empty())
00165    {
00166      if (!init_data_pool)
00167        G_THROW( ERR_MSG("DjVuDocument.empty_url") );
00168      if(init_url.is_empty())
00169      {
00170        init_url=invent_url("document.djvu");
00171      }
00172    }else
00173    {
00174      init_url=url;
00175    }
00176    
00177       // Initialize
00178    cache=xcache;
00179    doc_type=UNKNOWN_TYPE;
00180    DjVuPortcaster * pcaster=get_portcaster();
00181    if (!xport)
00182      xport=simple_port=new DjVuSimplePort();
00183    pcaster->add_route(this, xport);
00184    pcaster->add_route(this, this);
00185 
00186    if(!url.is_empty())
00187    {
00188      init_data_pool=pcaster->request_data(this, init_url);
00189      if(init_data_pool)
00190      {
00191        if(!init_url.is_empty() && init_url.is_local_file_url() && djvu_import_codec)
00192        {
00193          djvu_import_codec(init_data_pool,init_url,needs_compression_flag,needs_rename_flag);
00194        }
00195        if(needs_rename_flag)
00196          can_compress_flag=true;
00197      }
00198      if (!init_data_pool) 
00199      {
00200        G_THROW( ERR_MSG("DjVuDocument.fail_URL") "\t"+init_url.get_string());
00201      }
00202    }
00203       // Now we say it is ready
00204    init_started=true;
00205 
00206    init_thread_flags=STARTED;
00207    init_life_saver=this;
00208    init_thr.create(static_init_thread, this);
00209 }
00210 
00211 DjVuDocument::~DjVuDocument(void)
00212 {
00213       // No more messages, please. We're being destroyed.
00214    get_portcaster()->del_port(this);
00215 
00216       // We want to stop any DjVuFile which has been created by us
00217       // and is still being decoded. We have to stop them manually because
00218       // they keep the "life saver" in the decoding thread and won't stop
00219       // when we clear the last reference to them
00220    {
00221       GCriticalSectionLock lock(&ufiles_lock);
00222       for(GPosition pos=ufiles_list;pos;++pos)
00223       {
00224           GP<DjVuFile> file=ufiles_list[pos]->file;
00225           file->stop_decode(false);
00226           file->stop(false);    // Disable any access to data
00227       }
00228       ufiles_list.empty();
00229    }
00230 
00231    GPList<DjVuPort> ports=get_portcaster()->prefix_to_ports(get_int_prefix());
00232    for(GPosition pos=ports;pos;++pos)
00233    {
00234      GP<DjVuPort> port=ports[pos];
00235      if (port->inherits("DjVuFile"))
00236      {
00237        DjVuFile * file=(DjVuFile *) (DjVuPort *) port;
00238        file->stop_decode(false);
00239        file->stop(false);   // Disable any access to data
00240      }
00241    }
00242    DataPool::close_all();
00243 }
00244 
00245 void
00246 DjVuDocument::stop_init(void)
00247 {
00248    DEBUG_MSG("DjVuDocument::stop_init(): making sure that the init thread dies.\n");
00249    DEBUG_MAKE_INDENT(3);
00250 
00251    GMonitorLock lock(&init_thread_flags);
00252    while((init_thread_flags & STARTED) &&
00253      !(init_thread_flags & FINISHED))
00254    {
00255       if (init_data_pool) init_data_pool->stop(true);   // blocking operation
00256 
00257       if (ndir_file) ndir_file->stop(false);
00258 
00259       {
00260      GCriticalSectionLock lock(&ufiles_lock);
00261      for(GPosition pos=ufiles_list;pos;++pos)
00262         ufiles_list[pos]->file->stop(false);    // Disable any access to data
00263      ufiles_list.empty();
00264       }
00265 
00266       init_thread_flags.wait(50);
00267    }
00268 }
00269 
00270 void
00271 DjVuDocument::check() const
00272 {
00273   if (!init_started)
00274     G_THROW( ERR_MSG("DjVuDocument.not_init") );
00275 }
00276 
00277 void
00278 DjVuDocument::static_init_thread(void * cl_data)
00279 {
00280   DjVuDocument * th=(DjVuDocument *) cl_data;
00281   GP<DjVuDocument> life_saver=th;
00282   th->init_life_saver=0;
00283   G_TRY {
00284     th->init_thread();
00285   } G_CATCH(exc) {
00286     th->flags|=DjVuDocument::DOC_INIT_FAILED;
00287     G_TRY {
00288       th->check_unnamed_files();
00289       if (!exc.cmp_cause(ByteStream::EndOfFile) && th->verbose_eof)
00290         get_portcaster()->notify_error(th, ERR_MSG("DjVuDocument.init_eof") );
00291       else if (!exc.cmp_cause(DataPool::Stop))
00292         get_portcaster()->notify_status(th, ERR_MSG("DjVuDocument.stopped") );
00293       else
00294         get_portcaster()->notify_error(th, exc.get_cause());
00295     } G_CATCH_ALL {} G_ENDCATCH;
00296     th->init_thread_flags|=FINISHED;
00297   } G_ENDCATCH;
00298 }
00299 
00300 void
00301 DjVuDocument::init_thread(void)
00302       // This function is run in a separate thread.
00303       // The goal is to detect the document type (BUNDLED, OLD_INDEXED, etc.)
00304       // and decode navigation directory.
00305 {
00306    DEBUG_MSG("DjVuDocument::init_thread(): guessing what we're dealing with\n");
00307    DEBUG_MAKE_INDENT(3);
00308 
00309    DjVuPortcaster * pcaster=get_portcaster();
00310       
00311    GP<ByteStream> stream=init_data_pool->get_stream();
00312 
00313    GP<IFFByteStream> giff=IFFByteStream::create(stream);
00314    IFFByteStream &iff=*giff;
00315    GUTF8String chkid;
00316    int size=iff.get_chunk(chkid);
00317    if (!size)
00318      G_THROW( ByteStream::EndOfFile );
00319    if (size < 0)
00320      G_THROW( ERR_MSG("DjVuDocument.no_file") );
00321    if (size<8)
00322    {
00323      G_THROW( ERR_MSG("DjVuDocument.not_DjVu") );
00324    }
00325    if (chkid=="FORM:DJVM")
00326    {
00327      DEBUG_MSG("Got DJVM document here\n");
00328      DEBUG_MAKE_INDENT(3);
00329      
00330      size=iff.get_chunk(chkid);
00331      if (chkid=="DIRM")
00332        {
00333      djvm_dir=DjVmDir::create();
00334      djvm_dir->decode(iff.get_bytestream());
00335      iff.close_chunk();
00336      if (djvm_dir->is_bundled())
00337            {
00338              DEBUG_MSG("Got BUNDLED file.\n");
00339              doc_type=BUNDLED;
00340            } 
00341          else
00342            {
00343              DEBUG_MSG("Got INDIRECT file.\n");
00344              doc_type=INDIRECT;
00345            }
00346      flags|=DOC_TYPE_KNOWN | DOC_DIR_KNOWN;
00347      pcaster->notify_doc_flags_changed(this, DOC_TYPE_KNOWN | DOC_DIR_KNOWN, 0);
00348      check_unnamed_files();
00349          
00350          /* Check for NAVM */
00351          size=iff.get_chunk(chkid);
00352          if (size && chkid=="NAVM")
00353            {
00354              djvm_nav=DjVmNav::create();
00355              djvm_nav->decode(iff.get_bytestream());
00356              iff.close_chunk();
00357            }
00358        }
00359      else if (chkid=="DIR0")
00360        {
00361      DEBUG_MSG("Got OLD_BUNDLED file.\n");
00362      doc_type=OLD_BUNDLED;
00363      flags|=DOC_TYPE_KNOWN;
00364      pcaster->notify_doc_flags_changed(this, DOC_TYPE_KNOWN, 0);
00365      check_unnamed_files();
00366        } 
00367      else 
00368        G_THROW( ERR_MSG("DjVuDocument.bad_format") );
00369      
00370      if (doc_type==OLD_BUNDLED)
00371        {
00372          // Read the DjVmDir0 directory. We are unable to tell what
00373          // files are pages and what are included at this point.
00374          // We only know that the first file with DJVU (BM44 or PM44)
00375          // form *is* the first page. The rest will become known
00376          // after we decode DjVuNavDir
00377      djvm_dir0=DjVmDir0::create();
00378      djvm_dir0->decode(*iff.get_bytestream());
00379      iff.close_chunk();
00380          // Get offset to the first DJVU, PM44 or BM44 chunk
00381      int first_page_offset=0;
00382      while(!first_page_offset)
00383            {
00384              int offset;
00385              size=iff.get_chunk(chkid, &offset);
00386              if (size==0) G_THROW( ERR_MSG("DjVuDocument.no_page") );
00387              if (chkid=="FORM:DJVU" || chkid=="FORM:PM44" || chkid=="FORM:BM44")
00388                {
00389                  DEBUG_MSG("Got 1st page offset=" << offset << "\n");
00390                  first_page_offset=offset;
00391                }
00392              iff.close_chunk();
00393            }
00394          
00395          // Now get the name of this file
00396      int file_num;
00397      for(file_num=0;file_num<djvm_dir0->get_files_num();file_num++)
00398            {
00399              DjVmDir0::FileRec & file=*djvm_dir0->get_file(file_num);
00400              if (file.offset==first_page_offset)
00401                {
00402                  first_page_name=file.name;
00403                  break;
00404                }
00405            }
00406      if (!first_page_name.length())
00407            G_THROW( ERR_MSG("DjVuDocument.no_page") );
00408      flags|=DOC_DIR_KNOWN;
00409      pcaster->notify_doc_flags_changed(this, DOC_DIR_KNOWN, 0);
00410      check_unnamed_files();
00411        }
00412    } 
00413    else // chkid!="FORM:DJVM"
00414      {
00415        // DJVU format
00416        DEBUG_MSG("Got DJVU OLD_INDEXED or SINGLE_PAGE document here.\n");
00417        doc_type=SINGLE_PAGE;
00418        flags|=DOC_TYPE_KNOWN;
00419        pcaster->notify_doc_flags_changed(this, DOC_TYPE_KNOWN, 0);
00420        check_unnamed_files();
00421      }
00422    if (doc_type==OLD_BUNDLED || doc_type==SINGLE_PAGE)
00423      {
00424        DEBUG_MSG("Searching for NDIR chunks...\n");
00425        ndir_file=get_djvu_file(-1);
00426        if (ndir_file) ndir=ndir_file->decode_ndir();
00427        ndir_file=0; // Otherwise ~DjVuDocument() will stop (=kill) it
00428        if (!ndir)
00429          {
00430            // Seems to be 1-page old-style document. Create dummy NDIR
00431            if (doc_type==OLD_BUNDLED)
00432              {
00433                ndir=DjVuNavDir::create(GURL::UTF8("directory",init_url));
00434                ndir->insert_page(-1, first_page_name);
00435              } 
00436            else
00437              {
00438                ndir=DjVuNavDir::create(GURL::UTF8("directory",init_url.base()));
00439                ndir->insert_page(-1, init_url.fname());
00440              }
00441          } 
00442        else
00443          {
00444            if (doc_type==SINGLE_PAGE)
00445              doc_type=OLD_INDEXED;
00446          }
00447        flags|=DOC_NDIR_KNOWN;
00448        pcaster->notify_doc_flags_changed(this, DOC_NDIR_KNOWN, 0);
00449        check_unnamed_files();
00450      }
00451    
00452    flags|=DOC_INIT_OK;
00453    pcaster->notify_doc_flags_changed(this, DOC_INIT_OK, 0);
00454    check_unnamed_files();
00455    init_thread_flags|=FINISHED;
00456    DEBUG_MSG("DOCUMENT IS FULLY INITIALIZED now: doc_type='" <<
00457          (doc_type==BUNDLED ? "BUNDLED" :
00458           doc_type==OLD_BUNDLED ? "OLD_BUNDLED" :
00459           doc_type==INDIRECT ? "INDIRECT" :
00460           doc_type==OLD_INDEXED ? "OLD_INDEXED" :
00461           doc_type==SINGLE_PAGE ? "SINGLE_PAGE" :
00462           "UNKNOWN") << "'\n");
00463 }
00464 
00465 bool
00466 DjVuDocument::wait_for_complete_init(void)
00467 {
00468   flags.enter();
00469   while(!(flags & DOC_INIT_FAILED) &&
00470         !(flags & DOC_INIT_OK)) flags.wait();
00471   flags.leave();
00472   init_thread_flags.enter();
00473   while (!(init_thread_flags & FINISHED))
00474     init_thread_flags.wait();
00475   init_thread_flags.leave();
00476   return (flags & (DOC_INIT_OK | DOC_INIT_FAILED))!=0;
00477 }
00478 
00479 int
00480 DjVuDocument::wait_get_pages_num(void) const
00481 {
00482   GSafeFlags &f=const_cast<GSafeFlags &>(flags);
00483   f.enter();
00484   while(!(f & DOC_TYPE_KNOWN) &&
00485         !(f & DOC_INIT_FAILED) &&
00486         !(f & DOC_INIT_OK)) f.wait();
00487   f.leave();
00488   return get_pages_num();
00489 }
00490 
00491 GUTF8String
00492 DjVuDocument::get_int_prefix(void) const
00493 {
00494       // These NAMEs are used to enable DjVuFile sharing inside the same
00495       // DjVuDocument using DjVuPortcaster. Since URLs are unique to the
00496       // document, other DjVuDocuments cannot retrieve files until they're
00497       // assigned some permanent name. After '?' there should be the real
00498       // file's URL. Please note, that output of this function is used only
00499       // as name for DjVuPortcaster. Not as a URL.
00500    GUTF8String retval;
00501    return retval.format("document_%p%d?", this, hash(init_url));
00502 }
00503 
00504 void
00505 DjVuDocument::set_file_aliases(const DjVuFile * file)
00506 {
00507    DEBUG_MSG("DjVuDocument::set_file_aliases(): setting global aliases for file '"
00508          << file->get_url() << "'\n");
00509    DEBUG_MAKE_INDENT(3);
00510 
00511    DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
00512    
00513    GMonitorLock lock(&((DjVuFile *) file)->get_safe_flags());
00514    pcaster->clear_aliases(file);
00515    if (file->is_decode_ok() && cache)
00516    {
00517      // If file is successfully decoded and caching is enabled,
00518      // assign a global alias to this file, so that any other
00519      // DjVuDocument will be able to use it.
00520       
00521       pcaster->add_alias(file, file->get_url().get_string());
00522       if (flags & (DOC_NDIR_KNOWN | DOC_DIR_KNOWN))
00523       {
00524      int page_num=url_to_page(file->get_url());
00525      if (page_num>=0)
00526      {
00527         if (page_num==0) pcaster->add_alias(file, init_url.get_string()+"#-1");
00528         pcaster->add_alias(file, init_url.get_string()+"#"+GUTF8String(page_num));
00529      }
00530       }
00531      // The following line MUST stay here. For OLD_INDEXED documents
00532      // a page may finish decoding before DIR or NDIR becomes known
00533      // (multithreading, remember), so the code above would not execute
00534       pcaster->add_alias(file, file->get_url().get_string()+"#-1");
00535    } else pcaster->add_alias(file, get_int_prefix()+file->get_url());
00536 }
00537 
00538 void
00539 DjVuDocument::check_unnamed_files(void)
00540 {
00541   DEBUG_MSG("DjVuDocument::check_unnamed_files(): Seeing if we can fix some...\n");
00542   DEBUG_MAKE_INDENT(3);
00543   
00544   if (flags & DOC_INIT_FAILED)
00545   {
00546     // Init failed. All unnamed files should be terminated
00547     GCriticalSectionLock lock(&ufiles_lock);
00548     for(GPosition pos=ufiles_list;pos;++pos)
00549     {
00550       GP<DjVuFile> file=ufiles_list[pos]->file;
00551       file->stop_decode(true);
00552       file->stop(false);    // Disable any access to data
00553     }
00554     ufiles_list.empty();
00555     return;
00556   }
00557   
00558   if ((flags & DOC_TYPE_KNOWN)==0)
00559     return;
00560   
00561   // See the list of unnamed files (created when there was insufficient
00562   // information about DjVuDocument structure) and try to fix those,
00563   // which can be fixed at this time
00564   while(true)
00565   {
00566     DjVuPortcaster * pcaster=get_portcaster();
00567     
00568     GP<UnnamedFile> ufile;
00569     GURL new_url;
00570     GPosition pos ;   
00571        GCriticalSectionLock lock(&ufiles_lock);
00572      for(pos=ufiles_list;pos;)
00573      {
00574     G_TRY
00575         {
00576           GP<UnnamedFile> f=ufiles_list[pos];
00577           if (f->id_type==UnnamedFile::ID) 
00578             new_url=id_to_url(f->id);
00579           else 
00580             new_url=page_to_url(f->page_num);
00581           if (!new_url.is_empty())
00582           {
00583             ufile=f;
00584             // Don't take it off the list. We want to be
00585             // able to stop the init from ~DjVuDocument();
00586             //
00587             // ufiles_list.del(pos);
00588             break;
00589           } else if (is_init_complete())
00590           {
00591             // No empty URLs are allowed at this point.
00592             // We now know all information about the document
00593             // and can determine if a page is inside it or not
00594             f->data_pool->set_eof();
00595             GUTF8String msg;
00596             if (f->id_type==UnnamedFile::ID)
00597               msg= ERR_MSG("DjVuDocument.miss_page_name") "\t"+f->id;
00598             else 
00599               msg= ERR_MSG("DjVuDocument.miss_page_num") "\t"+GUTF8String(f->page_num);
00600             G_THROW(msg);
00601           }
00602           ++pos;
00603         }
00604         G_CATCH(exc)
00605         {
00606           pcaster->notify_error(this, exc.get_cause());
00607           GP<DataPool> pool=ufiles_list[pos]->data_pool;
00608           if (pool)
00609             pool->stop();
00610           GPosition this_pos=pos;
00611           ++pos;
00612           ufiles_list.del(this_pos);
00613         }
00614         G_ENDCATCH;
00615      }
00616      
00617      if (ufile && !new_url.is_empty())
00618        {
00619          DEBUG_MSG("Fixing file: '" << ufile->url << "'=>'" << new_url << "'\n");
00620          // Now, once we know its real URL we can request a real DataPool and
00621          // can connect the DataPool owned by DjVuFile to that real one
00622          // Note, that now request_data() will not play fool because
00623          // we have enough information
00624          
00625          G_TRY
00626            {
00627              if (ufile->data_pool)
00628                {
00629                  GP<DataPool> new_pool=pcaster->request_data(ufile->file, new_url);
00630                  if(!new_pool)
00631                    G_THROW( ERR_MSG("DjVuDocument.fail_URL") "\t"+new_url.get_string());
00632                  ufile->data_pool->connect(new_pool);
00633                }
00634              ufile->file->set_name(new_url.fname());
00635              ufile->file->move(new_url.base());
00636              set_file_aliases(ufile->file);
00637            }
00638          G_CATCH(exc)
00639            {
00640              pcaster->notify_error(this, exc.get_cause());
00641            }   
00642          G_ENDCATCH;
00643        }
00644      else
00645        break;
00646      
00647      // Remove the 'ufile' from the list
00648      for(pos=ufiles_list;pos;++pos)
00649        if (ufiles_list[pos]==ufile)
00650          {
00651            ufiles_list.del(pos);
00652            break;
00653          }
00654   } // while(1)
00655 }
00656 
00657 int
00658 DjVuDocument::get_pages_num(void) const
00659 {
00660    check();
00661    if (flags & DOC_TYPE_KNOWN)
00662       if (doc_type==BUNDLED || doc_type==INDIRECT)
00663      return djvm_dir->get_pages_num();
00664       else if (flags & DOC_NDIR_KNOWN)
00665      return ndir->get_pages_num();
00666    return 1;
00667 }
00668 
00669 GURL
00670 DjVuDocument::page_to_url(int page_num) const
00671 {
00672    check();
00673    DEBUG_MSG("DjVuDocument::page_to_url(): page_num=" << page_num << "\n");
00674    DEBUG_MAKE_INDENT(3);
00675    
00676    GURL url;
00677    if (flags & DOC_TYPE_KNOWN)
00678       switch(doc_type)
00679       {
00680      case SINGLE_PAGE:
00681      case OLD_INDEXED:
00682      {
00683         if (page_num<0) url=init_url;
00684         else if (flags & DOC_NDIR_KNOWN) url=ndir->page_to_url(page_num);
00685         break;
00686      }
00687      case OLD_BUNDLED:
00688      {
00689         if (page_num<0) page_num=0;
00690         if (page_num==0 && (flags & DOC_DIR_KNOWN))
00691            url=GURL::UTF8(first_page_name,init_url);
00692         else if (flags & DOC_NDIR_KNOWN)
00693            url=ndir->page_to_url(page_num);
00694         break;
00695      }
00696      case BUNDLED:
00697      {
00698         if (page_num<0)
00699               page_num=0;
00700         if (flags & DOC_DIR_KNOWN)
00701         {
00702           GP<DjVmDir::File> file=djvm_dir->page_to_file(page_num);
00703           if (!file) G_THROW( ERR_MSG("DjVuDocument.big_num") );
00704           url=GURL::UTF8(file->get_load_name(),init_url);
00705         }
00706         break;
00707      }
00708      case INDIRECT:
00709      {
00710         if (page_num<0) page_num=0;
00711         if (flags & DOC_DIR_KNOWN)
00712         {
00713            GP<DjVmDir::File> file=djvm_dir->page_to_file(page_num);
00714            if (!file)
00715                  G_THROW( ERR_MSG("DjVuDocument.big_num") );
00716            url=GURL::UTF8(file->get_load_name(),init_url.base());
00717         }
00718         break;
00719      }
00720      default:
00721         G_THROW( ERR_MSG("DjVuDocument.unk_type") );
00722       }
00723    return url;
00724 }
00725 
00726 int
00727 DjVuDocument::url_to_page(const GURL & url) const
00728 {
00729    check();
00730    DEBUG_MSG("DjVuDocument::url_to_page(): url='" << url << "'\n");
00731    DEBUG_MAKE_INDENT(3);
00732 
00733    int page_num=-1;
00734    if (flags & DOC_TYPE_KNOWN)
00735       switch(doc_type)
00736       {
00737      case SINGLE_PAGE:
00738      case OLD_BUNDLED:
00739      case OLD_INDEXED:
00740      {
00741         if (flags & DOC_NDIR_KNOWN) page_num=ndir->url_to_page(url);
00742         break;
00743      }
00744      case BUNDLED:
00745      {
00746         if (flags & DOC_DIR_KNOWN)
00747         {
00748            GP<DjVmDir::File> file;
00749            if (url.base()==init_url)
00750                  file=djvm_dir->id_to_file(url.fname());
00751            if (file)
00752                  page_num=file->get_page_num();
00753         }
00754         break;
00755      }
00756      case INDIRECT:
00757      {
00758         if (flags & DOC_DIR_KNOWN)
00759         {
00760            GP<DjVmDir::File> file;
00761            if (url.base()==init_url.base())
00762                  file=djvm_dir->id_to_file(url.fname());
00763            if (file)
00764                  page_num=file->get_page_num();
00765         }
00766         break;
00767      }
00768      default:
00769         G_THROW( ERR_MSG("DjVuDocument.unk_type") );
00770       }
00771    return page_num;
00772 }
00773 
00774 GURL
00775 DjVuDocument::id_to_url(const GUTF8String & id) const
00776 {
00777    check();
00778    DEBUG_MSG("DjVuDocument::id_to_url(): translating ID='" << id << "' to URL\n");
00779    DEBUG_MAKE_INDENT(3);
00780 
00781    if (flags & DOC_TYPE_KNOWN)
00782       switch(doc_type)
00783       {
00784      case BUNDLED:
00785         if (flags & DOC_DIR_KNOWN)
00786         {
00787           GP<DjVmDir::File> file=djvm_dir->id_to_file(id);
00788           if (!file)
00789               {
00790                 file=djvm_dir->name_to_file(id);
00791             if (!file)
00792                   file=djvm_dir->title_to_file(id);
00793               }
00794           if (file)
00795             return GURL::UTF8(file->get_load_name(),init_url);
00796         }
00797         break;
00798      case INDIRECT:
00799         if (flags & DOC_DIR_KNOWN)
00800         {
00801            GP<DjVmDir::File> file=djvm_dir->id_to_file(id);
00802            if (!file)
00803                {
00804                  file=djvm_dir->name_to_file(id);
00805              if (!file)
00806                    file=djvm_dir->title_to_file(id);
00807                }
00808            if (file)
00809              return GURL::UTF8(file->get_load_name(),init_url.base());
00810         }
00811         break;
00812      case OLD_BUNDLED:
00813         if (flags & DOC_DIR_KNOWN)
00814         {
00815            GP<DjVmDir0::FileRec> frec=djvm_dir0->get_file(id);
00816            if (frec)
00817                  return GURL::UTF8(id,init_url);
00818         }
00819         break;
00820      case OLD_INDEXED:
00821      case SINGLE_PAGE:
00822         return GURL::UTF8(id,init_url.base());
00823         break;
00824       }
00825    return GURL();
00826 }
00827 
00828 GURL
00829 DjVuDocument::id_to_url(const DjVuPort * source, const GUTF8String &id)
00830 {
00831    return id_to_url(id);
00832 }
00833 
00834 GP<DjVuFile>
00835 DjVuDocument::url_to_file(const GURL & url, bool dont_create) const
00836       // This function is private and is called from two places:
00837       // id_to_file() and get_djvu_file() ONLY when the structure is known
00838 {
00839    check();
00840    DEBUG_MSG("DjVuDocument::url_to_file(): url='" << url << "'\n");
00841    DEBUG_MAKE_INDENT(3);
00842 
00843       // Try DjVuPortcaster to find existing files.
00844    DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
00845    GP<DjVuPort> port;
00846 
00847    if (cache)
00848    {
00849      // First - fully decoded files
00850       port=pcaster->alias_to_port(url.get_string());
00851       if (port && port->inherits("DjVuFile"))
00852       {
00853      DEBUG_MSG("found fully decoded file using DjVuPortcaster\n");
00854      return (DjVuFile *) (DjVuPort *) port;
00855       }
00856    }
00857 
00858       // Second - internal files
00859    port=pcaster->alias_to_port(get_int_prefix()+url);
00860    if (port && port->inherits("DjVuFile"))
00861    {
00862       DEBUG_MSG("found internal file using DjVuPortcaster\n");
00863       return (DjVuFile *) (DjVuPort *) port;
00864    }
00865 
00866    GP<DjVuFile> file;
00867    
00868    if (!dont_create)
00869    {
00870       DEBUG_MSG("creating a new file\n");
00871       file=DjVuFile::create(url,const_cast<DjVuDocument *>(this),recover_errors,verbose_eof);
00872       const_cast<DjVuDocument *>(this)->set_file_aliases(file);
00873    }
00874 
00875    return file;
00876 }
00877 
00878 GP<DjVuFile>
00879 DjVuDocument::get_djvu_file(int page_num, bool dont_create) const
00880 {
00881    check();
00882    DEBUG_MSG("DjVuDocument::get_djvu_file(): request for page " << page_num << "\n");
00883    DEBUG_MAKE_INDENT(3);
00884 
00885    DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
00886    
00887    GURL url;
00888    {
00889      // I'm locking the flags because depending on what page_to_url()
00890      // returns me, I'll be creating DjVuFile in different ways.
00891      // And I don't want the situation to change between the moment I call
00892      // id_to_url() and I actually create DjVuFile
00893       GMonitorLock lock(&(const_cast<DjVuDocument *>(this)->flags));
00894       url=page_to_url(page_num);
00895       if (url.is_empty())
00896       {
00897         // If init is complete and url is empty, we know for sure, that
00898         // smth is wrong with the page_num. So we can return ZERO.
00899         // Otherwise we create a temporary file and wait for init to finish
00900      if (is_init_complete()) return 0;
00901      
00902      DEBUG_MSG("Structure is not known => check <doc_url>#<page_num> alias...\n");
00903      GP<DjVuPort> port;
00904      if (cache)
00905         port=pcaster->alias_to_port(init_url.get_string()+"#"+GUTF8String(page_num));
00906      if (!port || !port->inherits("DjVuFile"))
00907      {
00908         DEBUG_MSG("failed => invent dummy URL and proceed\n");
00909      
00910            // Invent some dummy temporary URL. I don't care what it will
00911            // be. I'll remember the page_num and will generate the correct URL
00912            // after I learn what the document is
00913             GUTF8String name("page");
00914             name+=GUTF8String(page_num);
00915             name+=".djvu";
00916             url=invent_url(name);
00917 
00918             GCriticalSectionLock(&(const_cast<DjVuDocument *>(this)->ufiles_lock));
00919         for(GPosition pos=ufiles_list;pos;++pos)
00920         {
00921            GP<UnnamedFile> f=ufiles_list[pos];
00922            if (f->url==url) return f->file;
00923         }
00924         GP<UnnamedFile> ufile=new UnnamedFile(UnnamedFile::PAGE_NUM, 0,
00925                           page_num, url, 0);
00926 
00927            // We're adding the record to the list before creating the DjVuFile
00928            // because DjVuFile::init() will call request_data(), and the
00929            // latter should be able to find the record.
00930            //
00931            // We also want to keep ufiles_lock to make sure that when
00932            // request_data() is called, the record is still there
00933         const_cast<DjVuDocument *>(this)->ufiles_list.append(ufile);
00934       
00935         GP<DjVuFile> file=
00936               DjVuFile::create(url,const_cast<DjVuDocument *>(this),recover_errors,verbose_eof);
00937         ufile->file=file;
00938         return file;
00939      } else url=((DjVuFile *) (DjVuPort *) port)->get_url();
00940       }
00941    }
00942    
00943    GP<DjVuFile> file=url_to_file(url, dont_create);
00944    if (file) 
00945      pcaster->add_route(file, const_cast<DjVuDocument *>(this));
00946    return file;
00947 }
00948 
00949 GURL
00950 DjVuDocument::invent_url(const GUTF8String &name) const
00951 {
00952    GUTF8String buffer;
00953    buffer.format("djvufileurl://%p/%s", this, (const char *)name);
00954    return GURL::UTF8(buffer);
00955 }
00956 
00957 GP<DjVuFile>
00958 DjVuDocument::get_djvu_file(const GUTF8String& id, bool dont_create)
00959 {
00960   check();
00961   DEBUG_MSG("DjVuDocument::get_djvu_file(): ID='" << id << "'\n");
00962   DEBUG_MAKE_INDENT(3);
00963   if (!id.length())
00964     return get_djvu_file(-1);
00965 
00966 // Integers are not supported, only ID's  
00967 //  if (id.is_int())
00968 //     return get_djvu_file(id.toInt(),dont_create);
00969   
00970   GURL url;
00971   // I'm locking the flags because depending on what id_to_url()
00972   // returns me, I'll be creating DjVuFile in different ways.
00973   // And I don't want the situation to change between the moment I call
00974   // id_to_url() and I actually create DjVuFile
00975   {
00976     GMonitorLock lock(&flags);
00977     url=id_to_url(id);
00978     if(url.is_empty() && !id.is_int())
00979     {
00980       // If init is complete, we know for sure, that there is no such
00981       // file with ID 'id' in the document. Otherwise we have to
00982       // create a temporary file and wait for the init to finish
00983       if (is_init_complete())
00984         return 0;
00985       // Invent some dummy temporary URL. I don't care what it will
00986       // be. I'll remember the ID and will generate the correct URL
00987       // after I learn what the document is
00988       url=invent_url(id);
00989       DEBUG_MSG("Invented url='" << url << "'\n");
00990 
00991       GCriticalSectionLock lock(&ufiles_lock);
00992       for(GPosition pos=ufiles_list;pos;++pos)
00993       {
00994         GP<UnnamedFile> f=ufiles_list[pos];
00995         if (f->url==url)
00996           return f->file;
00997       }
00998       GP<UnnamedFile> ufile=new UnnamedFile(UnnamedFile::ID, id, 0, url, 0);
00999 
01000       // We're adding the record to the list before creating the DjVuFile
01001       // because DjVuFile::init() will call request_data(), and the
01002       // latter should be able to find the record.
01003       //
01004       // We also want to keep ufiles_lock to make sure that when
01005       // request_data() is called, the record is still there
01006       ufiles_list.append(ufile);
01007       
01008       GP<DjVuFile> file=DjVuFile::create(url,this,recover_errors,verbose_eof);
01009       ufile->file=file;
01010       return file;
01011     }
01012   }
01013      
01014   return get_djvu_file(url,dont_create);
01015 }
01016 
01017 GP<DjVuFile>
01018 DjVuDocument::get_djvu_file(const GURL& url, bool dont_create)
01019 {
01020    check();
01021    DEBUG_MSG("DjVuDocument::get_djvu_file(): URL='" << url << "'\n");
01022    DEBUG_MAKE_INDENT(3);
01023 
01024    if (url.is_empty())
01025      return 0;
01026 
01027    const GP<DjVuFile> file(url_to_file(url, dont_create));
01028 
01029    if (file)
01030      get_portcaster()->add_route(file, this);
01031 
01032    return file;
01033 }
01034 
01035 GP<DjVuImage>
01036 DjVuDocument::get_page(int page_num, bool sync, DjVuPort * port) const
01037 {
01038    check();
01039    DEBUG_MSG("DjVuDocument::get_page(): request for page " << page_num << "\n");
01040    DEBUG_MAKE_INDENT(3);
01041 
01042    GP<DjVuImage> dimg;
01043    const GP<DjVuFile> file(get_djvu_file(page_num));
01044    if (file)
01045    {
01046      dimg=DjVuImage::create(file);
01047      if (port)
01048        DjVuPort::get_portcaster()->add_route(dimg, port);
01049    
01050      file->resume_decode();
01051      if (dimg && sync)
01052        dimg->wait_for_complete_decode();
01053    }
01054    return dimg;
01055 }
01056 
01057 GP<DjVuImage>
01058 DjVuDocument::get_page(const GUTF8String &id, bool sync, DjVuPort * port)
01059 {
01060    check();
01061    DEBUG_MSG("DjVuDocument::get_page(): ID='" << id << "'\n");
01062    DEBUG_MAKE_INDENT(3);
01063 
01064    GP<DjVuImage> dimg;
01065    const GP<DjVuFile> file(get_djvu_file(id));
01066    if(file)
01067    {
01068      dimg=DjVuImage::create(file);
01069      if (port)
01070        DjVuPort::get_portcaster()->add_route(dimg, port);
01071    
01072      file->resume_decode();
01073      if (dimg && sync)
01074        dimg->wait_for_complete_decode();
01075    }
01076    return dimg;
01077 }
01078 
01079 void
01080 DjVuDocument::process_threqs(void)
01081       // Will look thru threqs_list and try to fulfil every request
01082 {
01083   GCriticalSectionLock lock(&threqs_lock);
01084   for(GPosition pos=threqs_list;pos;)
01085   {
01086     GP<ThumbReq> req=threqs_list[pos];
01087     bool remove=false;
01088     if (req->thumb_file)
01089     {
01090       G_TRY {
01091            // There is supposed to be a file with thumbnails
01092         if (req->thumb_file->is_data_present())
01093         {
01094           // Cool, we can extract the thumbnail now
01095           GP<ByteStream> str=req->thumb_file->get_init_data_pool()->get_stream();
01096           GP<IFFByteStream> giff=IFFByteStream::create(str);
01097           IFFByteStream &iff=*giff;
01098           GUTF8String chkid;
01099           if (!iff.get_chunk(chkid) || chkid!="FORM:THUM")
01100             G_THROW( ERR_MSG("DjVuDocument.bad_thumb") );
01101           
01102           for(int i=0;i<req->thumb_chunk;i++)
01103           {
01104             if (!iff.get_chunk(chkid)) 
01105               G_THROW( ERR_MSG("DjVuDocument.bad_thumb") );
01106             iff.close_chunk();
01107           }
01108           if (!iff.get_chunk(chkid) || chkid!="TH44")
01109             G_THROW( ERR_MSG("DjVuDocument.bad_thumb") );
01110           
01111           // Copy the data
01112           char buffer[1024];
01113           int length;
01114           while((length=iff.read(buffer, 1024)))
01115             req->data_pool->add_data(buffer, length);
01116           req->data_pool->set_eof();
01117           
01118           // Also add this file to cache so that we won't have
01119           // to download it next time
01120           add_to_cache(req->thumb_file);
01121           
01122           req->thumb_file=0;
01123           req->image_file=0;
01124           remove=true;
01125         }
01126       } G_CATCH(exc) {
01127         GUTF8String msg= ERR_MSG("DjVuDocument.cant_extract") "\n";
01128         msg+=exc.get_cause();
01129         get_portcaster()->notify_error(this, msg);
01130            // Switch this request to the "decoding" mode
01131         req->image_file=get_djvu_file(req->page_num);
01132         req->thumb_file=0;
01133         req->data_pool->set_eof();
01134         remove=true;
01135       } G_ENDCATCH;
01136     } // if (req->thumb_file)
01137     
01138     if (req->image_file)
01139     {
01140       G_TRY {
01141            // Decode the file if necessary. Or just used predecoded image.
01142         GSafeFlags & file_flags=req->image_file->get_safe_flags();
01143         {
01144           GMonitorLock lock(&file_flags);
01145           if (!req->image_file->is_decoding())
01146           {
01147             if (req->image_file->is_decode_ok())
01148             {
01149               // We can generate it now
01150               const GP<DjVuImage> dimg(DjVuImage::create(req->image_file));
01151               
01152               dimg->wait_for_complete_decode();
01153               
01154               int width = 160;
01155               int height = 160;
01156               
01157               if( dimg->get_width() )
01158                 width = dimg->get_width();
01159               if( dimg->get_height() )
01160                 height = dimg->get_height();
01161               
01162               GRect rect(0, 0, 160, height*160/width);
01163               GP<GPixmap> pm=dimg->get_pixmap(rect, rect, thumb_gamma);
01164               if (!pm)
01165               {
01166                 GP<GBitmap> bm=dimg->get_bitmap(rect, rect, sizeof(int));
01167                 if(bm)
01168                   pm=GPixmap::create(*bm);
01169                 else
01170                   pm = GPixmap::create(rect.height(), rect.width(), 
01171                                        &GPixel::WHITE);
01172               }
01173               
01174               // Store and compress the pixmap
01175               GP<IW44Image> iwpix=IW44Image::create_encode(*pm);
01176               GP<ByteStream> gstr=ByteStream::create();
01177               IWEncoderParms parms;
01178               parms.slices=97;
01179               parms.bytes=0;
01180               parms.decibels=0;
01181               iwpix->encode_chunk(gstr, parms);
01182               TArray<char> data=gstr->get_data();
01183               
01184               req->data_pool->add_data((const char *) data, data.size());
01185               req->data_pool->set_eof();
01186               
01187               req->thumb_file=0;
01188               req->image_file=0;
01189               remove=true;
01190             } else if (req->image_file->is_decode_failed())
01191             {
01192               // Unfortunately we cannot decode it
01193               req->thumb_file=0;
01194               req->image_file=0;
01195               req->data_pool->set_eof();
01196               remove=true;
01197             } else
01198             {
01199               req->image_file->start_decode();
01200             }
01201           }
01202         }
01203       } G_CATCH(exc) {
01204         GUTF8String msg="Failed to decode thumbnails:\n";
01205         msg+=exc.get_cause();
01206         get_portcaster()->notify_error(this, msg);
01207         
01208            // Get rid of this request
01209         req->image_file=0;
01210         req->thumb_file=0;
01211         req->data_pool->set_eof();
01212         remove=true;
01213       } G_ENDCATCH;
01214     }
01215     
01216     if (remove)
01217     {
01218       GPosition this_pos=pos;
01219       ++pos;
01220       threqs_list.del(this_pos);
01221     } else ++pos;
01222   }
01223 }
01224 
01225 GP<DjVuDocument::ThumbReq>
01226 DjVuDocument::add_thumb_req(const GP<ThumbReq> & thumb_req)
01227       // Will look through the list of pending requests for thumbnails
01228       // and try to add the specified request. If a duplicate is found,
01229       // it will be returned and the list will not be modified
01230 {
01231    GCriticalSectionLock lock(&threqs_lock);
01232    for(GPosition pos=threqs_list;pos;++pos)
01233    {
01234       GP<ThumbReq> req=threqs_list[pos];
01235       if (req->page_num==thumb_req->page_num)
01236      return req;
01237    }
01238    threqs_list.append(thumb_req);
01239    return thumb_req;
01240 }
01241 
01242 GList<GUTF8String>
01243 DjVuDocument::get_id_list(void)
01244 {
01245   GList<GUTF8String> ids;
01246   if (is_init_complete())
01247   {
01248     if(djvm_dir)
01249     {
01250       GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
01251       for(GPosition pos=files_list;pos;++pos)
01252       {
01253         ids.append(files_list[pos]->get_load_name());
01254       }
01255     }else
01256     {
01257       const int page_num=get_pages_num();
01258       for(int page=0;page<page_num;page++)
01259       { 
01260         ids.append(page_to_url(page).fname());
01261       }
01262     }
01263   }
01264   return ids;
01265 }
01266 
01267 void
01268 DjVuDocument::map_ids(GMap<GUTF8String,void *> &map)
01269 {
01270   GList<GUTF8String> ids=get_id_list();
01271   for(GPosition pos=ids;pos;++pos)
01272   {
01273     map[ids[pos]]=0;
01274   }
01275 }
01276 
01277 GP<DataPool>
01278 DjVuDocument::get_thumbnail(int page_num, bool dont_decode)
01279 {
01280    DEBUG_MSG("DjVuDocument::get_thumbnail(): page_num=" << page_num << "\n");
01281    DEBUG_MAKE_INDENT(3);
01282 
01283    if (!is_init_complete()) return 0;
01284    
01285    {
01286      // See if we already have request for this thumbnail pending
01287       GCriticalSectionLock lock(&threqs_lock);
01288       for(GPosition pos=threqs_list;pos;++pos)
01289       {
01290      GP<ThumbReq> req=threqs_list[pos];
01291      if (req->page_num==page_num)
01292         return req->data_pool;  // That's it. Just return it.
01293       }
01294    }
01295 
01296       // No pending request for this page... Create one
01297    GP<ThumbReq> thumb_req=new ThumbReq(page_num, DataPool::create());
01298    
01299       // First try to find predecoded thumbnail
01300    if (get_doc_type()==INDIRECT || get_doc_type()==BUNDLED)
01301    {
01302      // Predecoded thumbnails exist for new formats only
01303       GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
01304       GP<DjVmDir::File> thumb_file;
01305       int thumb_start=0;
01306       int page_cnt=-1;
01307       for(GPosition pos=files_list;pos;++pos)
01308       {
01309      GP<DjVmDir::File> f=files_list[pos];
01310      if (f->is_thumbnails())
01311      {
01312         thumb_file=f;
01313         thumb_start=page_cnt+1;
01314      } else if (f->is_page())
01315          {
01316            page_cnt++;
01317          }
01318      if (page_cnt==page_num) break;
01319       }
01320       if (thumb_file)
01321       {
01322         // That's the file with the desired thumbnail image
01323      thumb_req->thumb_file=get_djvu_file(thumb_file->get_load_name());
01324      thumb_req->thumb_chunk=page_num-thumb_start;
01325      thumb_req=add_thumb_req(thumb_req);
01326      process_threqs();
01327      return thumb_req->data_pool;
01328       }
01329    }
01330 
01331       // Apparently we're out of luck and need to decode the requested
01332       // page (unless it's already done and if it's allowed) and render
01333       // it into the thumbnail. If dont_decode is true, do not attempt
01334       // to create this file (because this will result in a request for data)
01335    GP<DjVuFile> file=get_djvu_file(page_num, dont_decode);
01336    if (file)
01337    {
01338       thumb_req->image_file=file;
01339 
01340      // I'm locking the flags here to make sure, that DjVuFile will not
01341      // change its state in between of the checks.
01342       GSafeFlags & file_flags=file->get_safe_flags();
01343       {
01344      GMonitorLock lock(&file_flags);
01345      if (thumb_req->image_file->is_decode_ok() || !dont_decode)
01346      {
01347            // Just add it to the list and call process_threqs(). It
01348            // will start decoding if necessary
01349         thumb_req=add_thumb_req(thumb_req);
01350         process_threqs();
01351      } else
01352      {
01353            // Nothing can be done return ZERO
01354         thumb_req=0;
01355      }
01356       }
01357    } else thumb_req=0;
01358    
01359    if (thumb_req) return thumb_req->data_pool;
01360    else return 0;
01361 }
01362 
01363 static void
01364 add_to_cache(const GP<DjVuFile> & f, GMap<GURL, void *> & map,
01365          DjVuFileCache * cache)
01366 {
01367    GURL url=f->get_url();
01368    DEBUG_MSG("DjVuDocument::add_to_cache(): url='" << url << "'\n");
01369    DEBUG_MAKE_INDENT(3);
01370    
01371    if (!map.contains(url))
01372    {
01373       map[url]=0;
01374       cache->add_file(f);
01375       
01376       GPList<DjVuFile> list;
01377       for(GPosition pos=list;pos;++pos)
01378      add_to_cache(list[pos], map, cache);
01379    }
01380 }
01381 
01382 void
01383 DjVuDocument::add_to_cache(const GP<DjVuFile> & f)
01384 {
01385    if (cache)
01386    {
01387       GMap<GURL, void *> map;
01388       ::add_to_cache(f, map, cache);
01389    }
01390 }
01391 
01392 void
01393 DjVuDocument::notify_file_flags_changed(const DjVuFile * source,
01394                     long set_mask, long clr_mask)
01395 {
01396       // Don't check here if the document is initialized or not.
01397       // This function may be called when it's not.
01398       // check();
01399    if (set_mask & DjVuFile::DECODE_OK)
01400    {
01401       set_file_aliases(source);
01402       if (cache) add_to_cache((DjVuFile *) source);
01403       if(!needs_compression_flag)
01404       {
01405         if(source->needs_compression())
01406         {
01407           can_compress_flag=true;
01408           needs_compression_flag=true;
01409         }else if(source->can_compress())
01410         {
01411           can_compress_flag=true;
01412         }
01413       }
01414       process_threqs();
01415    }
01416    
01417    if (set_mask & DjVuFile::DATA_PRESENT)
01418       process_threqs();     // May be we can extract thumbnails now
01419 }
01420 
01421 GP<DjVuFile>
01422 DjVuDocument::id_to_file(const DjVuPort * source, const GUTF8String &id)
01423 {
01424    return (DjVuFile *) get_djvu_file(id);
01425 }
01426 
01427 GP<DataPool>
01428 DjVuDocument::request_data(const DjVuPort * source, const GURL & url)
01429 {
01430    DEBUG_MSG("DjVuDocument::request_data(): seeing if we can do it\n");
01431    DEBUG_MAKE_INDENT(3);
01432 
01433    if (url==init_url)
01434      return init_data_pool;
01435 
01436    check(); // Don't put it before 'init_data_pool'
01437 
01438    {
01439      // See if there is a file in the "UnnamedFiles" list.
01440      // If it's there, then create an empty DataPool and store its
01441      // pointer in the list. The "init thread" will eventually
01442      // do smth with it.
01443       GCriticalSectionLock lock(&ufiles_lock);
01444       for(GPosition pos=ufiles_list;pos;++pos)
01445       {
01446      GP<UnnamedFile> f=ufiles_list[pos];
01447      if (f->url==url)
01448      {
01449         DEBUG_MSG("Found tmp unnamed DjVuFile. Return empty DataPool\n");
01450            // Remember the DataPool. We will connect it to the
01451            // actual data after the document structure becomes known
01452         f->data_pool=DataPool::create();
01453         return f->data_pool;
01454      }
01455       }
01456    }
01457 
01458       // Well, the url is not in the "UnnamedFiles" list, but it doesn't
01459       // mean, that it's not "artificial". Stay alert!
01460    GP<DataPool> data_pool;
01461    if (flags & DOC_TYPE_KNOWN)
01462       switch(doc_type)
01463       {
01464      case OLD_BUNDLED:
01465      {
01466         if (flags & DOC_DIR_KNOWN)
01467         {
01468            DEBUG_MSG("The document is in OLD_BUNDLED format\n");
01469            if (url.base()!=init_url)
01470                 G_THROW( ERR_MSG("DjVuDocument.URL_outside") "\t"+url.get_string());
01471      
01472            GP<DjVmDir0::FileRec> file=djvm_dir0->get_file(url.fname());
01473            if (!file)
01474                {
01475                  G_THROW( ERR_MSG("DjVuDocument.file_outside") "\t"+url.fname());
01476                }
01477            data_pool=DataPool::create(init_data_pool, file->offset, file->size);
01478         }
01479         break;
01480      }
01481      case BUNDLED:
01482      {
01483         if (flags & DOC_DIR_KNOWN)
01484         {
01485            DEBUG_MSG("The document is in new BUNDLED format\n");
01486            if (url.base()!=init_url)
01487                {
01488          G_THROW( ERR_MSG("DjVuDocument.URL_outside") "\t"
01489                    +url.get_string());
01490                }
01491      
01492            GP<DjVmDir::File> file=djvm_dir->id_to_file(url.fname());
01493            if (!file)
01494                {
01495                  G_THROW( ERR_MSG("DjVuDocument.file_outside") "\t"+url.fname());
01496                }
01497            data_pool=DataPool::create(init_data_pool, file->offset, file->size);
01498         }
01499         break;
01500      }
01501      case SINGLE_PAGE:
01502      case OLD_INDEXED:
01503      case INDIRECT:
01504      {
01505         DEBUG_MSG("The document is in SINGLE_PAGE or OLD_INDEXED or INDIRECT format\n");
01506         if (flags & DOC_DIR_KNOWN)
01507            if (doc_type==INDIRECT && !djvm_dir->id_to_file(url.fname()))
01508                 G_THROW( ERR_MSG("DjVuDocument.URL_outside2") "\t"+url.get_string());
01509      
01510         if (url.is_local_file_url())
01511         {
01512 //         GUTF8String fname=GOS::url_to_filename(url);
01513 //         if (GOS::basename(fname)=="-") fname="-";
01514            DEBUG_MSG("url=" << url << "\n");
01515 
01516            data_pool=DataPool::create(url);
01517         }
01518      }
01519       }
01520    return data_pool;
01521 }
01522 
01523 
01524 static void
01525 add_file_to_djvm(const GP<DjVuFile> & file, bool page,
01526          DjVmDoc & doc, GMap<GURL, void *> & map)
01527       // This function is used only for obsolete formats.
01528       // For new formats there is no need to process files recursively.
01529       // All information is already available from the DJVM chunk
01530 {
01531    GURL url=file->get_url();
01532 
01533    if (!map.contains(url))
01534    {
01535       map[url]=0;
01536 
01537       if (file->get_chunks_number()>0 && !file->contains_chunk("NDIR"))
01538       {
01539         // Get the data and unlink any file containing NDIR chunk.
01540         // Yes. We're lazy. We don't check if those files contain
01541         // anything else.
01542      GPosition pos;
01543      GPList<DjVuFile> files_list=file->get_included_files(false);
01544      GP<DataPool> data=file->get_djvu_data(false);
01545      for(pos=files_list;pos;++pos)
01546      {
01547         GP<DjVuFile> f=files_list[pos];
01548         if (f->contains_chunk("NDIR"))
01549            data=DjVuFile::unlink_file(data, f->get_url().fname());
01550      }
01551      
01552         // Finally add it to the document
01553      GUTF8String name=file->get_url().fname();
01554      GP<DjVmDir::File> file_rec=DjVmDir::File::create(
01555            name, name, name,
01556            page ? DjVmDir::File::PAGE : DjVmDir::File::INCLUDE );
01557      doc.insert_file(file_rec, data, -1);
01558 
01559         // And repeat for all included files
01560      for(pos=files_list;pos;++pos)
01561         add_file_to_djvm(files_list[pos], false, doc, map);
01562       }
01563    }
01564 }
01565 
01566 static void
01567 add_file_to_djvm(const GP<DjVuFile> & file, bool page,
01568          DjVmDoc & doc, GMap<GURL, void *> & map, 
01569                  bool &needs_compression_flag, bool &can_compress_flag )
01570 {
01571   if(!needs_compression_flag)
01572   {
01573     if(file->needs_compression())
01574     {
01575       can_compress_flag=true;
01576       needs_compression_flag=true;
01577     }else if(file->can_compress())
01578     {
01579       can_compress_flag=true;
01580     }
01581   }
01582   add_file_to_djvm(file,page,doc,map);
01583 }
01584 
01585 static void
01586 local_get_url_names(DjVuFile * f,const GMap<GURL, void *> & map,GMap<GURL,void *> &tmpmap)
01587 {
01588    GURL url=f->get_url();
01589    if (!map.contains(url) && !tmpmap.contains(url))
01590    {
01591       tmpmap[url]=0;
01592       f->process_incl_chunks();
01593       GPList<DjVuFile> files_list=f->get_included_files(false);
01594       for(GPosition pos=files_list;pos;++pos)
01595          local_get_url_names(files_list[pos], map, tmpmap);
01596    }
01597 }
01598 
01599 static void
01600 local_get_url_names(DjVuFile * f, GMap<GURL, void *> & map)
01601 {
01602    GMap<GURL,void *> tmpmap;
01603    local_get_url_names(f,map,tmpmap);
01604    for(GPosition pos=tmpmap;pos;++pos)
01605      map[tmpmap.key(pos)]=0;
01606 }
01607 
01608 GList<GURL>
01609 DjVuDocument::get_url_names(void)
01610 {
01611   check();
01612 
01613   GCriticalSectionLock lock(&url_names_lock);
01614   if(has_url_names)
01615     return url_names;
01616 
01617   GMap<GURL, void *> map;
01618   int i;
01619   if (doc_type==BUNDLED || doc_type==INDIRECT)
01620   {
01621     GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
01622     for(GPosition pos=files_list;pos;++pos)
01623     {
01624       GURL url=id_to_url(files_list[pos]->get_load_name());
01625       map[url]=0;
01626     }
01627   }else
01628   {
01629     int pages_num=get_pages_num();
01630     for(i=0;i<pages_num;i++)
01631     {
01632       G_TRY
01633       {
01634         local_get_url_names(get_djvu_file(i), map);
01635       }
01636       G_CATCH(ex)
01637       {
01638         // Why is this try/catch block here?
01639         G_TRY { 
01640           get_portcaster()->notify_error(this, ex.get_cause()); 
01641           GUTF8String emsg = ERR_MSG("DjVuDocument.exclude_page") "\t" + (i+1);
01642           get_portcaster()->notify_error(this, emsg);
01643         }
01644         G_CATCH_ALL
01645         {
01646           G_RETHROW;
01647         }
01648         G_ENDCATCH;
01649       }
01650       G_ENDCATCH;
01651     }
01652   }
01653   for(GPosition j=map;j;++j)
01654   {
01655     if (map.key(j).is_local_file_url())
01656     {
01657       url_names.append(map.key(j));
01658     }
01659   }
01660   has_url_names=true;
01661   return url_names;
01662 }
01663 
01664 GP<DjVmDoc>
01665 DjVuDocument::get_djvm_doc()
01666       // This function may block for data
01667 {
01668    check();
01669    DEBUG_MSG("DjVuDocument::get_djvm_doc(): creating the DjVmDoc\n");
01670    DEBUG_MAKE_INDENT(3);
01671 
01672    if (!is_init_complete())
01673      G_THROW( ERR_MSG("DjVuDocument.init_not_done") );
01674 
01675    GP<DjVmDoc> doc=DjVmDoc::create();
01676 
01677    if (doc_type==BUNDLED || doc_type==INDIRECT)
01678      {
01679        GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
01680        for(GPosition pos=files_list;pos;++pos)
01681          {
01682            GP<DjVmDir::File> f=new DjVmDir::File(*files_list[pos]);
01683            GP<DjVuFile> file=url_to_file(id_to_url(f->get_load_name()));
01684            GP<DataPool> data;
01685            if (file->is_modified()) 
01686              data=file->get_djvu_data(false);
01687            else 
01688              data=file->get_init_data_pool();
01689            doc->insert_file(f, data);
01690          }
01691        if (djvm_nav)
01692          doc->set_djvm_nav(djvm_nav);
01693      } 
01694    else if (doc_type==SINGLE_PAGE)
01695      {
01696        DEBUG_MSG("Creating: djvm for a single page document.\n");
01697        GMap<GURL, void *> map_add;
01698        GP<DjVuFile> file=get_djvu_file(0);
01699        add_file_to_djvm(file, true, *doc, map_add,
01700                         needs_compression_flag,can_compress_flag);
01701      } 
01702    else
01703      {
01704        DEBUG_MSG("Converting: the document is in an old format.\n");
01705        GMap<GURL, void *> map_add;
01706        if(recover_errors == ABORT)
01707          {
01708            for(int page_num=0;page_num<ndir->get_pages_num();page_num++)
01709              {
01710                GP<DjVuFile> file=url_to_file(ndir->page_to_url(page_num));
01711                add_file_to_djvm(file, true, *doc, map_add,
01712                                 needs_compression_flag,can_compress_flag);
01713              }
01714          }
01715        else
01716          {
01717            for(int page_num=0;page_num<ndir->get_pages_num();page_num++)
01718              {
01719                G_TRY
01720                  {
01721                    GP<DjVuFile> file=url_to_file(ndir->page_to_url(page_num));
01722                    add_file_to_djvm(file, true, *doc, map_add,
01723                                     needs_compression_flag,can_compress_flag);
01724                  }
01725                G_CATCH(ex)
01726                  {
01727                    G_TRY { 
01728                      get_portcaster()->notify_error(this, ex.get_cause());
01729                      GUTF8String emsg = ERR_MSG("DjVuDocument.skip_page") "\t" 
01730                                       + (page_num+1);
01731                      get_portcaster()->notify_error(this, emsg);
01732                    }
01733                    G_CATCH_ALL
01734                      {
01735                        G_RETHROW;
01736                      }
01737                    G_ENDCATCH;
01738                  }
01739                G_ENDCATCH;
01740              }
01741          }
01742      }
01743    return doc;
01744 }
01745 
01746 void
01747 DjVuDocument::write( const GP<ByteStream> &gstr,
01748   const GMap<GUTF8String,void *> &reserved)
01749 {
01750   DEBUG_MSG("DjVuDocument::write(): storing DjVmDoc into ByteStream\n");
01751   DEBUG_MAKE_INDENT(3);
01752   get_djvm_doc()->write(gstr,reserved); 
01753 }
01754 
01755 void
01756 DjVuDocument::write(const GP<ByteStream> &gstr, bool force_djvm)
01757 {
01758   DEBUG_MSG("DjVuDocument::write(): storing DjVmDoc into ByteStream\n");
01759   DEBUG_MAKE_INDENT(3);
01760    
01761   GP<DjVmDoc> doc=get_djvm_doc();
01762   GP<DjVmDir> dir=doc->get_djvm_dir();
01763   if (force_djvm || dir->get_files_num()>1)
01764   {
01765     doc->write(gstr);
01766   }else
01767   {
01768     GPList<DjVmDir::File> files_list=dir->resolve_duplicates(false);
01769     GP<DataPool> pool=doc->get_data(files_list[files_list]->get_load_name());
01770     GP<ByteStream> pool_str=pool->get_stream();
01771     ByteStream &str=*gstr;
01772     str.writall(octets,4);
01773     str.copy(*pool_str);
01774   }
01775 }
01776 
01777 void
01778 DjVuDocument::expand(const GURL &codebase, const GUTF8String &idx_name)
01779 {
01780    DEBUG_MSG("DjVuDocument::expand(): codebase='" << codebase << "'\n");
01781    DEBUG_MAKE_INDENT(3);
01782    
01783    GP<DjVmDoc> doc=get_djvm_doc();
01784    doc->expand(codebase, idx_name);
01785 }
01786 
01787 void
01788 DjVuDocument::save_as(const GURL &where, const bool bundled)
01789 {
01790    DEBUG_MSG("DjVuDocument::save_as(): where='" << where <<
01791          "', bundled=" << bundled << "\n");
01792    DEBUG_MAKE_INDENT(3);
01793    
01794    if (needs_compression())
01795    { 
01796      if(!djvu_compress_codec)
01797      {
01798        G_THROW( ERR_MSG("DjVuDocument.comp_codec") );
01799      }
01800      GP<ByteStream> gmbs=ByteStream::create();
01801      write(gmbs);
01802      ByteStream &mbs=*gmbs;
01803      mbs.flush();
01804      mbs.seek(0,SEEK_SET);
01805      (*djvu_compress_codec)(gmbs,where,bundled);
01806    }else if (bundled)
01807    {
01808       DataPool::load_file(where);
01809       write(ByteStream::create(where, "wb"));
01810    } else 
01811    {
01812      expand(where.base(), where.fname());
01813    }
01814 }
01815 
01816 static 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>";
01817 static const char start_xml[]="</HEAD>\n<BODY>\n";
01818 static const char end_xml[]="</BODY>\n</DjVuXML>\n";
01819 
01820 void
01821 DjVuDocument::writeDjVuXML(const GP<ByteStream> &gstr_out,int flags) const
01822 {
01823   ByteStream &str_out=*gstr_out;
01824   str_out.writestring(
01825     prolog+get_init_url().get_string().toEscaped()+start_xml);
01826   const int pages=wait_get_pages_num();
01827   for(int page_num=0;page_num<pages;++page_num)
01828   {
01829     const GP<DjVuImage> dimg(get_page(page_num,true));
01830     if(!dimg)
01831     {
01832       G_THROW( ERR_MSG("DjVuToText.decode_failed") );
01833     }
01834     dimg->writeXML(str_out,get_init_url(),flags);
01835   }
01836   str_out.writestring(GUTF8String(end_xml));
01837 }
01838 
01839 
01840 #ifdef HAVE_NAMESPACES
01841 }
01842 # ifndef NOT_USING_DJVU_NAMESPACE
01843 using namespace DJVU;
01844 # endif
01845 #endif

kviewshell

Skip menu "kviewshell"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members

API Reference

Skip menu "API Reference"
  • kviewshell
Generated for API Reference by doxygen 1.5.9
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal