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

kviewshell

DjVuDocEditor.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: DjVuDocEditor.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 "DjVuDocEditor.h"
00065 #include "DjVuImage.h"
00066 #include "IFFByteStream.h"
00067 #include "DataPool.h"
00068 #include "IW44Image.h"
00069 #include "GOS.h"
00070 #include "GURL.h"
00071 #include "DjVuAnno.h"
00072 #include "GRect.h"
00073 #include "DjVmNav.h"
00074 
00075 #include "debug.h"
00076 
00077 #include <ctype.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 
00090 int        DjVuDocEditor::thumbnails_per_file=10;
00091 
00092 // This is a structure for active files and DataPools. It may contain
00093 // a DjVuFile, which is currently being used by someone (I check the list
00094 // and get rid of hanging files from time to time) or a DataPool,
00095 // which is "custom" with respect to the document (was modified or
00096 // inserted), or both.
00097 //
00098 // DjVuFile is set to smth!=0 when it's created using url_to_file().
00099 //          It's reset back to ZERO in clean_files_map() when
00100 //    it sees, that a given file is not used by anyone.
00101 // DataPool is updated when a file is inserted
00102 class DjVuDocEditor::File : public GPEnabled
00103 {
00104 public:
00105   // 'pool' below may be non-zero only if it cannot be retrieved
00106   // by the DjVuDocument, that is it either corresponds to a
00107   // modified DjVuFile or it has been inserted. Otherwise it's ZERO
00108   // Once someone assigns a non-zero DataPool, it remains non-ZERO
00109   // (may be updated if the file gets modified) and may be reset
00110   // only by save() or save_as() functions.
00111   GP<DataPool>  pool;
00112 
00113   // If 'file' is non-zero, it means, that it's being used by someone
00114   // We check for unused files from time to time and ZERO them.
00115   // But before we do it, we may save the DataPool in the case if
00116   // file has been modified.
00117   GP<DjVuFile>  file;
00118 };
00119 
00120 void
00121 DjVuDocEditor::check(void)
00122 {
00123    if (!initialized) G_THROW( ERR_MSG("DjVuDocEditor.not_init") );
00124 }
00125 
00126 DjVuDocEditor::DjVuDocEditor(void)
00127 {
00128    initialized=false;
00129    refresh_cb=0;
00130    refresh_cl_data=0;
00131 }
00132 
00133 DjVuDocEditor::~DjVuDocEditor(void)
00134 {
00135    if (!tmp_doc_url.is_empty())
00136    {
00137      tmp_doc_url.deletefile();
00138    }
00139 
00140    GCriticalSectionLock lock(&thumb_lock);
00141    thumb_map.empty();
00142    DataPool::close_all();
00143 }
00144 
00145 void
00146 DjVuDocEditor::init(void)
00147 {
00148    DEBUG_MSG("DjVuDocEditor::init() called\n");
00149    DEBUG_MAKE_INDENT(3);
00150 
00151       // If you remove this check be sure to delete thumb_map
00152    if (initialized) G_THROW( ERR_MSG("DjVuDocEditor.init") );
00153 
00154    doc_url=GURL::Filename::UTF8("noname.djvu");
00155 
00156    const GP<DjVmDoc> doc(DjVmDoc::create());
00157    const GP<ByteStream> gstr(ByteStream::create());
00158    doc->write(gstr);
00159    gstr->seek(0, SEEK_SET);
00160    doc_pool=DataPool::create(gstr);
00161 
00162    orig_doc_type=UNKNOWN_TYPE;
00163    orig_doc_pages=0;
00164 
00165    initialized=true;
00166 
00167    DjVuDocument::init(doc_url, this);
00168 }
00169 
00170 void
00171 DjVuDocEditor::init(const GURL &url)
00172 {
00173    DEBUG_MSG("DjVuDocEditor::init() called: url='" << url << "'\n");
00174    DEBUG_MAKE_INDENT(3);
00175 
00176       // If you remove this check be sure to delete thumb_map
00177    if (initialized)
00178      G_THROW( ERR_MSG("DjVuDocEditor.init") );
00179 
00180       // First - create a temporary DjVuDocument and check its type
00181    doc_pool=DataPool::create(url);
00182    doc_url=url;
00183    const GP<DjVuDocument> tmp_doc(DjVuDocument::create_wait(doc_url,this));
00184    if (!tmp_doc->is_init_ok())
00185       G_THROW( ERR_MSG("DjVuDocEditor.open_fail") "\t" +url.get_string());
00186 
00187    orig_doc_type=tmp_doc->get_doc_type();
00188    orig_doc_pages=tmp_doc->get_pages_num();
00189    if (orig_doc_type==OLD_BUNDLED ||
00190        orig_doc_type==OLD_INDEXED ||
00191        orig_doc_type==SINGLE_PAGE)
00192    {
00193          // Suxx. I need to convert it NOW.
00194          // We will unlink this file in the destructor
00195       tmp_doc_url=GURL::Filename::Native(tmpnam(0));
00196       const GP<ByteStream> gstr(ByteStream::create(tmp_doc_url, "wb"));
00197       tmp_doc->write(gstr, true);        // Force DJVM format
00198       gstr->flush();
00199       doc_pool=DataPool::create(tmp_doc_url);
00200    }
00201 
00202       // OK. Now doc_pool contains data of the document in one of the
00203       // new formats. It will be a lot easier to insert/delete pages now.
00204 
00205       // 'doc_url' below of course doesn't refer to the file with the converted
00206       // data, but we will take care of it by redirecting the request_data().
00207    initialized=true;
00208    DjVuDocument::init(doc_url, this);
00209 
00210       // Cool. Now extract the thumbnails...
00211    GCriticalSectionLock lock(&thumb_lock);
00212    int pages_num=get_pages_num();
00213    for(int page_num=0;page_num<pages_num;page_num++)
00214    {
00215      // Call DjVuDocument::get_thumbnail() here to bypass logic
00216      // of DjVuDocEditor::get_thumbnail(). init() is the only safe
00217      // place where we can still call DjVuDocument::get_thumbnail();
00218       const GP<DataPool> pool(DjVuDocument::get_thumbnail(page_num, true));
00219       if (pool)
00220       {
00221         thumb_map[page_to_id(page_num)]=pool;
00222       }
00223    }
00224       // And remove then from DjVmDir so that DjVuDocument
00225       // does not try to use them
00226    unfile_thumbnails();
00227 }
00228 
00229 GP<DataPool>
00230 DjVuDocEditor::request_data(const DjVuPort * source, const GURL & url)
00231 {
00232    DEBUG_MSG("DjVuDocEditor::request_data(): url='" << url << "'\n");
00233    DEBUG_MAKE_INDENT(3);
00234 
00235       // Check if we have either original data or converted (to new format),
00236       // if all the story is about the DjVuDocument's data
00237    if (url==doc_url)
00238      return doc_pool;
00239 
00240       // Now see if we have any file matching the url
00241    const GP<DjVmDir::File> frec(djvm_dir->name_to_file(url.fname()));
00242    if (frec)
00243    {
00244       GCriticalSectionLock lock(&files_lock);
00245       GPosition pos;
00246       if (files_map.contains(frec->get_load_name(), pos))
00247       {
00248          const GP<File> f(files_map[pos]);
00249          if (f->file && f->file->get_init_data_pool())
00250             return f->file->get_init_data_pool();// Favor DjVuFile's knowledge
00251          else if (f->pool) return f->pool;
00252       }
00253    }
00254 
00255       // Finally let DjVuDocument cope with it. It may be a connected DataPool
00256       // for a BUNDLED format. Or it may be a file. Anyway, it was not
00257       // manually included, so it should be in the document.
00258    const GP<DataPool> pool(DjVuDocument::request_data(source, url));
00259 
00260       // We do NOT update the 'File' structure, because our rule is that
00261       // we keep a separate copy of DataPool in 'File' only if it cannot
00262       // be retrieved from DjVuDocument (like it has been "inserted" or
00263       // corresponds to a modified file).
00264    return pool;
00265 }
00266 
00267 void
00268 DjVuDocEditor::clean_files_map(void)
00269       // Will go thru the map of files looking for unreferenced
00270       // files or records w/o DjVuFile and DataPool.
00271       // These will be modified and/or removed.
00272 {
00273    DEBUG_MSG("DjVuDocEditor::clean_files_map() called\n");
00274    DEBUG_MAKE_INDENT(3);
00275 
00276    GCriticalSectionLock lock(&files_lock);
00277 
00278       // See if there are too old items in the "cache", which are
00279       // not referenced by anyone. If the corresponding DjVuFile has been
00280       // modified, obtain the new data and replace the 'pool'. Clear the
00281       // DjVuFile anyway. If both DataPool and DjVuFile are zero, remove
00282       // the entry.
00283    for(GPosition pos=files_map;pos;)
00284    {
00285       const GP<File> f(files_map[pos]);
00286       if (f->file && f->file->get_count()==1)
00287       {
00288          DEBUG_MSG("ZEROing file '" << f->file->get_url() << "'\n");
00289          if (f->file->is_modified())
00290             f->pool=f->file->get_djvu_data(false);
00291          f->file=0;
00292       }
00293       if (!f->file && !f->pool)
00294       {
00295          DEBUG_MSG("Removing record '" << files_map.key(pos) << "'\n");
00296          GPosition this_pos=pos;
00297          ++pos;
00298          files_map.del(this_pos);
00299       } else ++pos;
00300    }
00301 }
00302 
00303 GP<DjVuFile>
00304 DjVuDocEditor::url_to_file(const GURL & url, bool dont_create) const
00305 {
00306    DEBUG_MSG("DjVuDocEditor::url_to_file(): url='" << url << "'\n");
00307    DEBUG_MAKE_INDENT(3);
00308 
00309       // Check if have a DjVuFile with this url cached (created before
00310       // and either still active or left because it has been modified)
00311    GP<DjVmDir::File> frec;
00312    if((const DjVmDir *)djvm_dir)
00313      frec=djvm_dir->name_to_file(url.fname());
00314    if (frec)
00315    {
00316       GCriticalSectionLock lock(&(const_cast<DjVuDocEditor *>(this)->files_lock));
00317       GPosition pos;
00318       if (files_map.contains(frec->get_load_name(), pos))
00319       {
00320          const GP<File> f(files_map[pos]);
00321          if (f->file)
00322            return f->file;
00323       }
00324    }
00325 
00326    const_cast<DjVuDocEditor *>(this)->clean_files_map();
00327 
00328       // We don't have the file cached. Let DjVuDocument create the file.
00329    const GP<DjVuFile> file(DjVuDocument::url_to_file(url, dont_create));
00330 
00331       // And add it to our private "cache"
00332    if (file && frec)
00333    {
00334       GCriticalSectionLock lock(&(const_cast<DjVuDocEditor *>(this)->files_lock));
00335       GPosition pos;
00336       if (files_map.contains(frec->get_load_name(), pos))
00337       {
00338          files_map[frec->get_load_name()]->file=file;
00339       }else
00340       {
00341          const GP<File> f(new File());
00342          f->file=file;
00343          const_cast<DjVuDocEditor *>(this)->files_map[frec->get_load_name()]=f;
00344       }
00345    }
00346 
00347    return file;
00348 }
00349 
00350 GUTF8String
00351 DjVuDocEditor::page_to_id(int page_num) const
00352 {
00353    if (page_num<0 || page_num>=get_pages_num())
00354      G_THROW( ERR_MSG("DjVuDocEditor.page_num") "\t"+GUTF8String(page_num));
00355    const GP<DjVmDir::File> f(djvm_dir->page_to_file(page_num));
00356    if (! f)
00357      G_THROW( ERR_MSG("DjVuDocEditor.page_num") "\t"+GUTF8String(page_num));
00358 
00359    return f->get_load_name();
00360 }
00361 
00362 GUTF8String
00363 DjVuDocEditor::find_unique_id(GUTF8String id)
00364 {
00365   const GP<DjVmDir> dir(get_djvm_dir());
00366 
00367   GUTF8String base, ext;
00368   const int dot=id.rsearch('.');
00369   if(dot >= 0)
00370   {
00371     base=id.substr(0,dot);
00372     ext=id.substr(dot+1,(unsigned int)-1);
00373   }else
00374   {
00375     base=id;
00376   }
00377 
00378   int cnt=0;
00379   while (!(!dir->id_to_file(id) &&
00380            !dir->name_to_file(id) &&
00381            !dir->title_to_file(id)))
00382   {
00383      cnt++;
00384      id=base+"_"+GUTF8String(cnt);
00385      if (ext.length())
00386        id+="."+ext;
00387   }
00388   return id;
00389 }
00390 
00391 GP<DataPool>
00392 DjVuDocEditor::strip_incl_chunks(const GP<DataPool> & pool_in)
00393 {
00394    DEBUG_MSG("DjVuDocEditor::strip_incl_chunks() called\n");
00395    DEBUG_MAKE_INDENT(3);
00396 
00397    const GP<IFFByteStream> giff_in(
00398      IFFByteStream::create(pool_in->get_stream()));
00399 
00400    const GP<ByteStream> gbs_out(ByteStream::create());
00401    const GP<IFFByteStream> giff_out(IFFByteStream::create(gbs_out));
00402 
00403    IFFByteStream &iff_in=*giff_in;
00404    IFFByteStream &iff_out=*giff_out;
00405 
00406    bool have_incl=false;
00407    int chksize;
00408    GUTF8String chkid;
00409    if (iff_in.get_chunk(chkid))
00410    {
00411       iff_out.put_chunk(chkid);
00412       while((chksize=iff_in.get_chunk(chkid)))
00413       {
00414          if (chkid!="INCL")
00415          {
00416             iff_out.put_chunk(chkid);
00417             iff_out.copy(*iff_in.get_bytestream());
00418             iff_out.close_chunk();
00419          } else
00420          {
00421            have_incl=true;
00422          }
00423          iff_in.close_chunk();
00424       }
00425       iff_out.close_chunk();
00426    }
00427 
00428    if (have_incl)
00429    {
00430       gbs_out->seek(0,SEEK_SET);
00431       return DataPool::create(gbs_out);
00432    } else return pool_in;
00433 }
00434 
00435 GUTF8String
00436 DjVuDocEditor::insert_file(const GURL &file_url, const GUTF8String &parent_id,
00437                            int chunk_num, DjVuPort *source)
00438       // Will open the 'file_name' and insert it into an existing DjVuFile
00439       // with ID 'parent_id'. Will insert the INCL chunk at position chunk_num
00440       // Will NOT process ANY files included into the file being inserted.
00441       // Moreover it will strip out any INCL chunks in that file...
00442 {
00443    DEBUG_MSG("DjVuDocEditor::insert_file(): fname='" << file_url <<
00444              "', parent_id='" << parent_id << "'\n");
00445    DEBUG_MAKE_INDENT(3);
00446    const GP<DjVmDir> dir(get_djvm_dir());
00447 
00448    if(!source)
00449      source=this;
00450       // Create DataPool and see if the file exists
00451    GP<DataPool> file_pool;
00452    if(file_url.is_empty()||file_url.is_local_file_url())
00453    {
00454      file_pool=DataPool::create(file_url);
00455    }else
00456    {
00457      file_pool=source->request_data(source, file_url);
00458      if(source != this)
00459      {
00460        file_pool=DataPool::create(file_pool->get_stream()->duplicate());
00461      }
00462    }
00463    if(file_pool && file_url && DjVuDocument::djvu_import_codec)
00464    {
00465      (*DjVuDocument::djvu_import_codec)(file_pool,file_url,needs_compression_flag,can_compress_flag);
00466    }
00467 
00468       // Strip any INCL chunks
00469    file_pool=strip_incl_chunks(file_pool);
00470 
00471       // Check if parent ID is valid
00472    GP<DjVmDir::File> parent_frec(dir->id_to_file(parent_id));
00473    if (!parent_frec)
00474      parent_frec=dir->name_to_file(parent_id);
00475    if (!parent_frec)
00476      parent_frec=dir->title_to_file(parent_id);
00477    if (!parent_frec)
00478      G_THROW( ERR_MSG("DjVuDocEditor.no_file") "\t" +parent_id);
00479    const GP<DjVuFile> parent_file(get_djvu_file(parent_id));
00480    if (!parent_file)
00481      G_THROW( ERR_MSG("DjVuDocEditor.create_fail") "\t"+parent_id);
00482 
00483       // Now obtain ID for the new file
00484    const GUTF8String id(find_unique_id(file_url.fname()));
00485 
00486       // Add it into the directory
00487    const GP<DjVmDir::File> frec(
00488      DjVmDir::File::create(id, id, id, DjVmDir::File::INCLUDE));
00489    int pos=dir->get_file_pos(parent_frec);
00490    if (pos>=0)
00491      ++pos;
00492    dir->insert_file(frec, pos);
00493 
00494       // Add it to our "cache"
00495    {
00496       const GP<File> f(new File);
00497       f->pool=file_pool;
00498       GCriticalSectionLock lock(&files_lock);
00499       files_map[id]=f;
00500    }
00501 
00502       // And insert it into the parent DjVuFile
00503    parent_file->insert_file(id, chunk_num);
00504 
00505    return id;
00506 }
00507 
00508       // First it will insert the 'file_url' at position 'file_pos'.
00509       //
00510       // Then it will process all the INCL chunks in the file and try to do
00511       // the same thing with the included files. If insertion of an included
00512       // file fails, it will proceed with other INCL chunks until it does
00513       // them all. In the very end we will throw exception to let the caller
00514       // know about problems with included files.
00515       //
00516       // If the name of a file being inserted conflicts with some other
00517       // name, which has been in DjVmDir prior to call to this function,
00518       // it will be modified. name2id is the translation table to
00519       // keep track of these modifications.
00520       //
00521       // Also, if a name is in name2id, we will not insert that file again.
00522       //
00523       // Will return TRUE if the file has been successfully inserted.
00524       // FALSE, if the file contains NDIR chunk and has been skipped.
00525 bool
00526 DjVuDocEditor::insert_file(const GURL &file_url, bool is_page,
00527   int & file_pos, GMap<GUTF8String, GUTF8String> & name2id,
00528   DjVuPort *source)
00529 {
00530 
00531   DEBUG_MSG("DjVuDocEditor::insert_file(): file_url='" << file_url <<
00532              "', is_page='" << is_page << "'\n");
00533   DEBUG_MAKE_INDENT(3);
00534   if (refresh_cb)
00535     refresh_cb(refresh_cl_data);
00536 
00537 
00538       // We do not want to insert the same file twice (important when
00539       // we insert a group of files at the same time using insert_group())
00540       // So we check if we already did that and return if so.
00541   if (name2id.contains(file_url.fname()))
00542     return true;
00543 
00544   if(!source)
00545     source=this;
00546 
00547   GP<DataPool> file_pool;
00548   if(file_url.is_empty()||file_url.is_local_file_url())
00549   {
00550     file_pool=DataPool::create(file_url);
00551   }
00552   else
00553   {
00554     file_pool=source->request_data(source, file_url);
00555     if(source != this)
00556     {
00557       file_pool=DataPool::create(file_pool->get_stream());
00558     }
00559   }
00560        // Create DataPool and see if the file exists
00561   if(file_pool && !file_url.is_empty() && DjVuDocument::djvu_import_codec)
00562   {
00563       (*DjVuDocument::djvu_import_codec)(file_pool,file_url,
00564                                          needs_compression_flag,
00565                                          can_compress_flag);
00566   }
00567 
00568          // Oh. It does exist... Check that it has IFF structure
00569   {
00570        const GP<IFFByteStream> giff(
00571          IFFByteStream::create(file_pool->get_stream()));
00572        IFFByteStream &iff=*giff;
00573        GUTF8String chkid;
00574 
00575        int length;
00576        length=iff.get_chunk(chkid);
00577        if (chkid!="FORM:DJVI" && chkid!="FORM:DJVU" &&
00578          chkid!="FORM:BM44" && chkid!="FORM:PM44")
00579        G_THROW( ERR_MSG("DjVuDocEditor.not_1_page") "\t"+file_url.get_string());
00580 
00581        // Wonderful. It's even a DjVu file. Scan for NDIR chunks.
00582        // If NDIR chunk is found, ignore the file
00583        while(iff.get_chunk(chkid))
00584        {
00585          if (chkid=="NDIR")
00586            return false;
00587          iff.close_chunk();
00588        }
00589   }
00590   return insert_file(file_pool,file_url,is_page,file_pos,name2id,source);
00591 }
00592 
00593 bool
00594 DjVuDocEditor::insert_file(const GP<DataPool> &file_pool,
00595   const GURL &file_url, bool is_page,
00596   int & file_pos, GMap<GUTF8String, GUTF8String> & name2id,
00597   DjVuPort *source)
00598 {
00599   GUTF8String errors;
00600   if(file_pool)
00601   {
00602     const GP<DjVmDir> dir(get_djvm_dir());
00603     G_TRY
00604     {
00605          // Now get a unique name for this file.
00606          // Check the name2id first...
00607       const GUTF8String name=file_url.fname();
00608       GUTF8String id;
00609       if (name2id.contains(name))
00610       {
00611         id=name2id[name];
00612       }else
00613       {
00614            // Check to see if this page exists with a different name.
00615         if(!is_page)
00616         {
00617           GPList<DjVmDir::File> list(dir->get_files_list());
00618           for(GPosition pos=list;pos;++pos)
00619           {
00620             DEBUG_MSG("include " << list[pos]->is_include() 
00621                       << " size=" << list[pos]->size << " length=" 
00622                       << file_pool->get_length() << "\n");
00623             if(list[pos]->is_include() 
00624                && (!list[pos]->size 
00625                    || (list[pos]->size == file_pool->get_length())))
00626             {
00627               id=list[pos]->get_load_name();
00628               GP<DjVuFile> file(get_djvu_file(id,false));
00629               const GP<DataPool> pool(file->get_djvu_data(false));
00630               if(file_pool->simple_compare(*pool))
00631               {
00632                 // The files are the same, so just store the alias.
00633                 name2id[name]=id;
00634               }
00635               const GP<IFFByteStream> giff_old(IFFByteStream::create(pool->get_stream()));
00636               const GP<IFFByteStream> giff_new(IFFByteStream::create(file_pool->get_stream()));
00637               file=0;
00638               if(giff_old->compare(*giff_new))
00639               {
00640                 // The files are the same, so just store the alias.
00641                 name2id[name]=id;
00642                 return true;
00643               }
00644             } 
00645           }
00646         }
00647         // Otherwise create a new unique ID and remember the translation
00648         id=find_unique_id(name);
00649         name2id[name]=id;
00650       }
00651 
00652          // Good. Before we continue with the included files we want to
00653          // complete insertion of this one. Notice, that insertion of
00654          // children may fail, in which case we will have to modify
00655          // data for this file to get rid of invalid INCL
00656 
00657          // Create a file record with the chosen ID
00658       const GP<DjVmDir::File> file(DjVmDir::File::create(id, id, id,
00659         is_page ? DjVmDir::File::PAGE : DjVmDir::File::INCLUDE ));
00660 
00661          // And insert it into the directory
00662       file_pos=dir->insert_file(file, file_pos);
00663 
00664          // And add the File record (containing the file URL and DataPool)
00665       {
00666          const GP<File> f(new File);
00667          f->pool=file_pool;
00668          GCriticalSectionLock lock(&files_lock);
00669          files_map[id]=f;
00670       }
00671 
00672          // The file has been added. If it doesn't include anything else,
00673          // that will be enough. Otherwise repeat what we just did for every
00674          // included child. Don't forget to modify the contents of INCL
00675          // chunks due to name2id translation.
00676          // We also want to include here our file with shared annotations,
00677          // if it exists.
00678       GUTF8String chkid;
00679       const GP<IFFByteStream> giff_in(
00680         IFFByteStream::create(file_pool->get_stream()));
00681       IFFByteStream &iff_in=*giff_in;
00682       const GP<ByteStream> gstr_out(ByteStream::create());
00683       const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
00684       IFFByteStream &iff_out=*giff_out;
00685 
00686       const GP<DjVmDir::File> shared_frec(djvm_dir->get_shared_anno_file());
00687 
00688       iff_in.get_chunk(chkid);
00689       iff_out.put_chunk(chkid);
00690       while(iff_in.get_chunk(chkid))
00691       {
00692          if (chkid!="INCL")
00693          {
00694             iff_out.put_chunk(chkid);
00695             iff_out.copy(*iff_in.get_bytestream());
00696             iff_in.close_chunk();
00697             iff_out.close_chunk();
00698             if (shared_frec && chkid=="INFO")
00699             {
00700                iff_out.put_chunk("INCL");
00701                iff_out.get_bytestream()->writestring(shared_frec->get_load_name());
00702                iff_out.close_chunk();
00703             }
00704          } else
00705          {
00706             GUTF8String name;
00707             char buffer[1024];
00708             int length;
00709             while((length=iff_in.read(buffer, 1024)))
00710                name+=GUTF8String(buffer, length);
00711             while(isspace(name[0]))
00712             {
00713               name=name.substr(1,(unsigned int)-1);
00714             }
00715             while(isspace(name[(int)name.length()-1]))
00716             {
00717               name.setat(name.length()-1, 0);
00718             }
00719             const GURL::UTF8 full_url(name,file_url.base());
00720             iff_in.close_chunk();
00721 
00722             G_TRY {
00723                if (insert_file(full_url, false, file_pos, name2id, source))
00724                {
00725                      // If the child file has been inserted (doesn't
00726                      // contain NDIR chunk), add INCL chunk.
00727                   GUTF8String id=name2id[name];
00728                   iff_out.put_chunk("INCL");
00729                   iff_out.get_bytestream()->writestring(id);
00730                   iff_out.close_chunk();
00731                }
00732             } G_CATCH(exc) {
00733                   // Should an error occur, we move on. INCL chunk will
00734                   // not be copied.
00735                if (errors.length())
00736                  errors+="\n\n";
00737                errors+=exc.get_cause();
00738             } G_ENDCATCH;
00739          }
00740       } // while(iff_in.get_chunk(chkid))
00741       iff_out.close_chunk();
00742 
00743       // Increment the file_pos past the page inserted.
00744       if (file_pos>=0) file_pos++;
00745 
00746          // We have just inserted every included file. We may have modified
00747          // contents of the INCL chunks. So we need to update the DataPool...
00748       gstr_out->seek(0);
00749       const GP<DataPool> new_file_pool(DataPool::create(gstr_out));
00750       {
00751             // It's important that we replace the pool here anyway.
00752             // By doing this we load the file into memory. And this is
00753             // exactly what insert_group() wants us to do because
00754             // it creates temporary files.
00755          GCriticalSectionLock lock(&files_lock);
00756          files_map[id]->pool=new_file_pool;
00757       }
00758     } G_CATCH(exc) {
00759       if (errors.length())
00760         errors+="\n\n";
00761       errors+=exc.get_cause();
00762       G_THROW(errors);
00763     } G_ENDCATCH;
00764 
00765       // The only place where we intercept exceptions is when we process
00766       // included files. We want to process all of them even if we failed to
00767       // process one. But here we need to let the exception propagate...
00768     if (errors.length())
00769       G_THROW(errors);
00770 
00771     return true;
00772   }
00773   return false;
00774 }
00775 
00776 void
00777 DjVuDocEditor::insert_group(const GList<GURL> & file_urls, int page_num,
00778                              void (* _refresh_cb)(void *), void * _cl_data)
00779       // The function will insert every file from the list at position
00780       // corresponding to page_num. If page_num is negative, concatenation
00781       // will occur. Included files will be processed as well
00782 {
00783   refresh_cb=_refresh_cb;
00784   refresh_cl_data=_cl_data;
00785 
00786   G_TRY
00787   {
00788 
00789      // First translate the page_num to file_pos.
00790     const GP<DjVmDir> dir(get_djvm_dir());
00791     int file_pos;
00792     if (page_num<0 || page_num>=dir->get_pages_num())
00793     {
00794       file_pos=-1;
00795     }
00796     else
00797     {
00798       file_pos=dir->get_page_pos(page_num);
00799     }
00800 
00801        // Now call the insert_file() for every page. We will remember the
00802        // name2id translation table. Thus insert_file() will remember IDs
00803        // it assigned to shared files
00804     GMap<GUTF8String, GUTF8String> name2id;
00805 
00806     GUTF8String errors;
00807     for(GPosition pos=file_urls;pos;++pos)
00808     {
00809       const GURL &furl=file_urls[pos];
00810       DEBUG_MSG( "Inserting file '" << furl << "'\n" );
00811       G_TRY
00812       {
00813                // Check if it's a multipage document...
00814         GP<DataPool> xdata_pool(DataPool::create(furl));
00815         if(xdata_pool && furl.is_valid()
00816            && furl.is_local_file_url() && DjVuDocument::djvu_import_codec)
00817         {
00818           (*DjVuDocument::djvu_import_codec)(xdata_pool,furl,
00819                                              needs_compression_flag,
00820                                              can_compress_flag);
00821         }
00822         GUTF8String chkid;
00823         IFFByteStream::create(xdata_pool->get_stream())->get_chunk(chkid);
00824         if (name2id.contains(furl.fname())||(chkid=="FORM:DJVM"))
00825         {
00826           GMap<GUTF8String,void *> map;
00827           map_ids(map);
00828           DEBUG_MSG("Read DjVuDocument furl='" << furl << "'\n");
00829           GP<ByteStream> gbs(ByteStream::create());
00830           GP<DjVuDocument> doca(DjVuDocument::create_noinit());
00831           doca->set_verbose_eof(verbose_eof);
00832           doca->set_recover_errors(recover_errors);
00833           doca->init(furl /* ,this */ );
00834           doca->wait_for_complete_init();
00835           get_portcaster()->add_route(doca,this);
00836           DEBUG_MSG("Saving DjVuDocument url='" << furl << "' with unique names\n");
00837           doca->write(gbs,map);
00838           gbs->seek(0L);
00839           DEBUG_MSG("Loading unique names\n");
00840           GP<DjVuDocument> doc(DjVuDocument::create(gbs));
00841           doc->set_verbose_eof(verbose_eof);
00842           doc->set_recover_errors(recover_errors);
00843           doc->wait_for_complete_init();
00844           get_portcaster()->add_route(doc,this);
00845           gbs=0;
00846           DEBUG_MSG("Inserting pages\n");
00847           int pages_num=doc->get_pages_num();
00848           for(int page_num=0;page_num<pages_num;page_num++)
00849           {
00850             const GURL url(doc->page_to_url(page_num));
00851             insert_file(url, true, file_pos, name2id, doc);
00852           }
00853         }
00854         else
00855         {
00856           insert_file(furl, true, file_pos, name2id, this);
00857         }
00858       } G_CATCH(exc)
00859       {
00860         if (errors.length())
00861         {
00862           errors+="\n\n";
00863         }
00864         errors+=exc.get_cause();
00865       }
00866       G_ENDCATCH;
00867     }
00868     if (errors.length())
00869     {
00870       G_THROW(errors);
00871     }
00872   } G_CATCH_ALL
00873   {
00874     refresh_cb=0;
00875     refresh_cl_data=0;
00876     G_RETHROW;
00877   } G_ENDCATCH;
00878   refresh_cb=0;
00879   refresh_cl_data=0;
00880 }
00881 
00882 void
00883 DjVuDocEditor::insert_page(const GURL &file_url, int page_num)
00884 {
00885    DEBUG_MSG("DjVuDocEditor::insert_page(): furl='" << file_url << "'\n");
00886    DEBUG_MAKE_INDENT(3);
00887 
00888    GList<GURL> list;
00889    list.append(file_url);
00890 
00891    insert_group(list, page_num);
00892 }
00893 
00894 void
00895 DjVuDocEditor::insert_page(GP<DataPool> & _file_pool,
00896                const GURL & file_url, int page_num)
00897       // Use _file_pool as source of data, create a new DjVuFile
00898       // with name file_name, and insert it as page number page_num
00899 {
00900    DEBUG_MSG("DjVuDocEditor::insert_page(): pool size='" <<
00901          _file_pool->get_size() << "'\n");
00902    DEBUG_MAKE_INDENT(3);
00903 
00904    const GP<DjVmDir> dir(get_djvm_dir());
00905 
00906       // Strip any INCL chunks (we do not allow to insert hierarchies
00907       // using this function)
00908    const GP<DataPool> file_pool(strip_incl_chunks(_file_pool));
00909    
00910       // Now obtain ID for the new file
00911    const GUTF8String id(find_unique_id(file_url.fname()));
00912 
00913       // Add it into the directory
00914    const GP<DjVmDir::File> frec(DjVmDir::File::create(
00915      id, id, id, DjVmDir::File::PAGE));
00916    int pos=dir->get_page_pos(page_num);
00917    dir->insert_file(frec, pos);
00918 
00919       // Add it to our "cache"
00920    {
00921       GP<File> f=new File;
00922       f->pool=file_pool;
00923       GCriticalSectionLock lock(&files_lock);
00924       files_map[id]=f;
00925    }
00926 }
00927 
00928 void
00929 DjVuDocEditor::generate_ref_map(const GP<DjVuFile> & file,
00930                 GMap<GUTF8String, void *> & ref_map,
00931                 GMap<GURL, void *> & visit_map)
00932       // This private function is used to generate a list (implemented as map)
00933       // of files referencing the given file. To get list of all parents
00934       // for file with ID 'id' iterate map obtained as
00935       // *((GMap<GUTF8String, void *> *) ref_map[id])
00936 {
00937    const GURL url=file->get_url();
00938    const GUTF8String id(djvm_dir->name_to_file(url.fname())->get_load_name());
00939    if (!visit_map.contains(url))
00940    {
00941       visit_map[url]=0;
00942 
00943       GPList<DjVuFile> files_list=file->get_included_files(false);
00944       for(GPosition pos=files_list;pos;++pos)
00945       {
00946          GP<DjVuFile> child_file=files_list[pos];
00947             // First: add the current file to the list of parents for
00948             // the child being processed
00949          GURL child_url=child_file->get_url();
00950          const GUTF8String child_id(
00951            djvm_dir->name_to_file(child_url.fname())->get_load_name());
00952          GMap<GUTF8String, void *> * parents=0;
00953          if (ref_map.contains(child_id))
00954             parents=(GMap<GUTF8String, void *> *) ref_map[child_id];
00955          else
00956             ref_map[child_id]=parents=new GMap<GUTF8String, void *>();
00957          (*parents)[id]=0;
00958             // Second: go recursively
00959          generate_ref_map(child_file, ref_map, visit_map);
00960       }
00961    }
00962 }
00963 
00964 void
00965 DjVuDocEditor::remove_file(const GUTF8String &id, bool remove_unref,
00966                            GMap<GUTF8String, void *> & ref_map)
00967       // Private function, which will remove file with ID id.
00968       //
00969       // If will also remove all INCL chunks in parent files pointing
00970       // to this one
00971       //
00972       // Finally, if remove_unref is TRUE, we will go down the files
00973       // hierarchy removing every file, which becomes unreferenced.
00974       //
00975       // ref_map will be used to find out list of parents referencing
00976       // this file (required when removing INCL chunks)
00977 {
00978       // First get rid of INCL chunks in parents
00979    GMap<GUTF8String, void *> * parents=(GMap<GUTF8String, void *> *) ref_map[id];
00980    if (parents)
00981    {
00982       for(GPosition pos=*parents;pos;++pos)
00983       {
00984          const GUTF8String parent_id((*parents).key(pos));
00985          const GP<DjVuFile> parent(get_djvu_file(parent_id));
00986          if (parent)
00987            parent->unlink_file(id);
00988       }
00989       delete parents;
00990       parents=0;
00991       ref_map.del(id);
00992    }
00993 
00994       // We will accumulate errors here.
00995    GUTF8String errors;
00996 
00997       // Now modify the ref_map and process children if necessary
00998    GP<DjVuFile> file=get_djvu_file(id);
00999    if (file)
01000    {
01001       G_TRY {
01002          GPList<DjVuFile> files_list=file->get_included_files(false);
01003          for(GPosition pos=files_list;pos;++pos)
01004          {
01005             GP<DjVuFile> child_file=files_list[pos];
01006             GURL child_url=child_file->get_url();
01007             const GUTF8String child_id(
01008               djvm_dir->name_to_file(child_url.fname())->get_load_name());
01009             GMap<GUTF8String, void *> * parents=(GMap<GUTF8String, void *> *) ref_map[child_id];
01010             if (parents) parents->del(id);
01011 
01012             if (remove_unref && (!parents || !parents->size()))
01013                remove_file(child_id, remove_unref, ref_map);
01014          }
01015       } G_CATCH(exc) {
01016          if (errors.length()) errors+="\n\n";
01017          errors+=exc.get_cause();
01018       } G_ENDCATCH;
01019    }
01020 
01021       // Finally remove this file from the directory.
01022    djvm_dir->delete_file(id);
01023 
01024       // And get rid of its thumbnail, if any
01025    GCriticalSectionLock lock(&thumb_lock);
01026    GPosition pos(thumb_map.contains(id));
01027    if (pos)
01028    {
01029      thumb_map.del(pos);
01030    }
01031    if (errors.length())
01032      G_THROW(errors);
01033 }
01034 
01035 void
01036 DjVuDocEditor::remove_file(const GUTF8String &id, bool remove_unref)
01037 {
01038    DEBUG_MSG("DjVuDocEditor::remove_file(): id='" << id << "'\n");
01039    DEBUG_MAKE_INDENT(3);
01040 
01041    if (!djvm_dir->id_to_file(id))
01042       G_THROW( ERR_MSG("DjVuDocEditor.no_file") "\t"+id);
01043 
01044       // First generate a map of references (containing the list of parents
01045       // including this particular file. This will speed things up
01046       // significatly.
01047    GMap<GUTF8String, void *> ref_map;        // GMap<GUTF8String, GMap<GUTF8String, void *> *> in fact
01048    GMap<GURL, void *> visit_map;        // To avoid loops
01049 
01050    int pages_num=djvm_dir->get_pages_num();
01051    for(int page_num=0;page_num<pages_num;page_num++)
01052       generate_ref_map(get_djvu_file(page_num), ref_map, visit_map);
01053 
01054       // Now call the function, which will do the removal recursively
01055    remove_file(id, remove_unref, ref_map);
01056 
01057       // And clear the ref_map
01058    GPosition pos;
01059    while((pos=ref_map))
01060    {
01061       GMap<GUTF8String, void *> * parents=(GMap<GUTF8String, void *> *) ref_map[pos];
01062       delete parents;
01063       ref_map.del(pos);
01064    }
01065 }
01066 
01067 void
01068 DjVuDocEditor::remove_page(int page_num, bool remove_unref)
01069 {
01070    DEBUG_MSG("DjVuDocEditor::remove_page(): page_num=" << page_num << "\n");
01071    DEBUG_MAKE_INDENT(3);
01072 
01073       // Translate the page_num to ID
01074    GP<DjVmDir> djvm_dir=get_djvm_dir();
01075    if (page_num<0 || page_num>=djvm_dir->get_pages_num())
01076       G_THROW( ERR_MSG("DjVuDocEditor.bad_page") "\t"+GUTF8String(page_num));
01077 
01078       // And call general remove_file()
01079    remove_file(djvm_dir->page_to_file(page_num)->get_load_name(), remove_unref);
01080 }
01081 
01082 void
01083 DjVuDocEditor::remove_pages(const GList<int> & page_list, bool remove_unref)
01084 {
01085    DEBUG_MSG("DjVuDocEditor::remove_pages() called\n");
01086    DEBUG_MAKE_INDENT(3);
01087 
01088       // First we need to translate page numbers to IDs (they will
01089       // obviously be changing while we're removing pages one after another)
01090    GP<DjVmDir> djvm_dir=get_djvm_dir();
01091    GPosition pos ;
01092    if (djvm_dir)
01093    {
01094       GList<GUTF8String> id_list;
01095       for(pos=page_list;pos;++pos)
01096       {
01097          GP<DjVmDir::File> frec=djvm_dir->page_to_file(page_list[pos]);
01098          if (frec)
01099             id_list.append(frec->get_load_name());
01100       }
01101 
01102       for(pos=id_list;pos;++pos)
01103       {
01104          GP<DjVmDir::File> frec=djvm_dir->id_to_file(id_list[pos]);
01105          if (frec)
01106             remove_page(frec->get_page_num(), remove_unref);
01107       }
01108    }
01109 }
01110 
01111 void
01112 DjVuDocEditor::move_file(const GUTF8String &id, int & file_pos,
01113                          GMap<GUTF8String, void *> & map)
01114       // NOTE! file_pos here is the desired position in DjVmDir *after*
01115       // the record with ID 'id' is removed.
01116 {
01117    if (!map.contains(id))
01118    {
01119       map[id]=0;
01120 
01121       GP<DjVmDir::File> file_rec=djvm_dir->id_to_file(id);
01122       if (file_rec)
01123       {
01124          file_rec=new DjVmDir::File(*file_rec);
01125          djvm_dir->delete_file(id);
01126          djvm_dir->insert_file(file_rec, file_pos);
01127 
01128          if (file_pos>=0)
01129          {
01130             file_pos++;
01131         
01132                // We care to move included files only if we do not append
01133                // This is because the only reason why we move included
01134                // files is to made them available sooner than they would
01135                // be available if we didn't move them. By appending files
01136                // we delay the moment when the data for the file becomes
01137                // available, of course.
01138             GP<DjVuFile> djvu_file=get_djvu_file(id);
01139             if (djvu_file)
01140             {
01141                GPList<DjVuFile> files_list=djvu_file->get_included_files(false);
01142                for(GPosition pos=files_list;pos;++pos)
01143                {
01144                   const GUTF8String name(files_list[pos]->get_url().fname());
01145                   GP<DjVmDir::File> child_frec=djvm_dir->name_to_file(name);
01146 
01147                      // If the child is positioned in DjVmDir AFTER the
01148                      // file being processed (position is file_pos or greater),
01149                      // move it to file_pos position
01150                   if (child_frec)
01151                      if (djvm_dir->get_file_pos(child_frec)>file_pos)
01152                         move_file(child_frec->get_load_name(), file_pos, map);
01153                }
01154             }
01155          }
01156       }
01157    }
01158 }
01159 
01160 void
01161 DjVuDocEditor::move_page(int page_num, int new_page_num)
01162 {
01163    DEBUG_MSG("DjVuDocEditor::move_page(): page_num=" << page_num <<
01164              ", new_page_num=" << new_page_num << "\n");
01165    DEBUG_MAKE_INDENT(3);
01166 
01167    if (page_num==new_page_num) return;
01168 
01169    int pages_num=get_pages_num();
01170    if (page_num<0 || page_num>=pages_num)
01171       G_THROW( ERR_MSG("DjVuDocEditor.bad_page") "\t"+GUTF8String(page_num));
01172 
01173    const GUTF8String id(page_to_id(page_num));
01174    int file_pos=-1;
01175    if (new_page_num>=0 && new_page_num<pages_num)
01176       if (new_page_num>page_num)        // Moving toward the end
01177       {
01178          if (new_page_num<pages_num-1)
01179             file_pos=djvm_dir->get_page_pos(new_page_num+1)-1;
01180       } else
01181          file_pos=djvm_dir->get_page_pos(new_page_num);
01182 
01183    GMap<GUTF8String, void *> map;
01184    move_file(id, file_pos, map);
01185 }
01186 #ifdef _WIN32_WCE_EMULATION         // Work around odd behavior under WCE Emulation
01187 #define CALLINGCONVENTION __cdecl
01188 #else
01189 #define CALLINGCONVENTION  /* */
01190 #endif
01191 
01192 static int
01193 CALLINGCONVENTION
01194 cmp(const void * ptr1, const void * ptr2)
01195 {
01196    int num1=*(int *) ptr1;
01197    int num2=*(int *) ptr2;
01198    return num1<num2 ? -1 : num1>num2 ? 1 : 0;
01199 }
01200 
01201 static GList<int>
01202 sortList(const GList<int> & list)
01203 {
01204    GArray<int> a(list.size()-1);
01205    int cnt;
01206    GPosition pos;
01207    for(pos=list, cnt=0;pos;++pos, cnt++)
01208       a[cnt]=list[pos];
01209 
01210    qsort((int *) a, a.size(), sizeof(int), cmp);
01211 
01212    GList<int> l;
01213    for(int i=0;i<a.size();i++)
01214       l.append(a[i]);
01215 
01216    return l;
01217 }
01218 
01219 void
01220 DjVuDocEditor::move_pages(const GList<int> & _page_list, int shift)
01221 {
01222    if (!shift) return;
01223 
01224    GList<int> page_list=sortList(_page_list);
01225 
01226    GList<GUTF8String> id_list;
01227    for(GPosition pos=page_list;pos;++pos)
01228    {
01229       GP<DjVmDir::File> frec=djvm_dir->page_to_file(page_list[pos]);
01230       if (frec)
01231          id_list.append(frec->get_load_name());
01232    }
01233 
01234    if (shift<0)
01235    {
01236          // We have to start here from the smallest page number
01237          // We will move it according to the 'shift', and all
01238          // further moves are guaranteed not to affect its page number.
01239 
01240          // We will be changing the 'min_page' to make sure that
01241          // pages moved beyond the document will still be in correct order
01242       int min_page=0;
01243       for(GPosition pos=id_list;pos;++pos)
01244       {
01245          GP<DjVmDir::File> frec=djvm_dir->id_to_file(id_list[pos]);
01246          if (frec)
01247          {
01248             int page_num=frec->get_page_num();
01249             int new_page_num=page_num+shift;
01250             if (new_page_num<min_page)
01251                new_page_num=min_page++;
01252             move_page(page_num, new_page_num);
01253          }
01254       }
01255    } else
01256    {
01257          // We have to start here from the biggest page number
01258          // We will move it according to the 'shift', and all
01259          // further moves will not affect its page number.
01260 
01261          // We will be changing the 'max_page' to make sure that
01262          // pages moved beyond the document will still be in correct order
01263       int max_page=djvm_dir->get_pages_num()-1;
01264       for(GPosition pos=id_list.lastpos();pos;--pos)
01265       {
01266          GP<DjVmDir::File> frec=djvm_dir->id_to_file(id_list[pos]);
01267          if (frec)
01268          {
01269             int page_num=frec->get_page_num();
01270             int new_page_num=page_num+shift;
01271             if (new_page_num>max_page)
01272                new_page_num=max_page--;
01273             move_page(page_num, new_page_num);
01274          }
01275       }
01276    }
01277 }
01278 
01279 void
01280 DjVuDocEditor::set_file_name(const GUTF8String &id, const GUTF8String &name)
01281 {
01282    DEBUG_MSG("DjVuDocEditor::set_file_name(), id='" << id << "', name='" << name << "'\n");
01283    DEBUG_MAKE_INDENT(3);
01284 
01285       // It's important to get the URL now, because later (after we
01286       // change DjVmDir) id_to_url() will be returning a modified value
01287    GURL url=id_to_url(id);
01288 
01289       // Change DjVmDir. It will check if the name is unique
01290    djvm_dir->set_file_name(id, name);
01291 
01292       // Now find DjVuFile (if any) and rename it
01293    GPosition pos;
01294    if (files_map.contains(id, pos))
01295    {
01296       GP<File> file=files_map[pos];
01297       GP<DataPool> pool=file->pool;
01298       if (pool) pool->load_file();
01299       GP<DjVuFile> djvu_file=file->file;
01300       if (djvu_file) djvu_file->set_name(name);
01301    }
01302 }
01303 
01304 void
01305 DjVuDocEditor::set_page_name(int page_num, const GUTF8String &name)
01306 {
01307    DEBUG_MSG("DjVuDocEditor::set_page_name(), page_num='" << page_num << "'\n");
01308    DEBUG_MAKE_INDENT(3);
01309 
01310    if (page_num<0 || page_num>=get_pages_num())
01311       G_THROW( ERR_MSG("DjVuDocEditor.bad_page") "\t"+GUTF8String(page_num));
01312 
01313    set_file_name(page_to_id(page_num), name);
01314 }
01315 
01316 void
01317 DjVuDocEditor::set_file_title(const GUTF8String &id, const GUTF8String &title)
01318 {
01319    DEBUG_MSG("DjVuDocEditor::set_file_title(), id='" << id << "', title='" << title << "'\n");
01320    DEBUG_MAKE_INDENT(3);
01321 
01322       // Just change DjVmDir. It will check if the title is unique
01323    djvm_dir->set_file_title(id, title);
01324 }
01325 
01326 void
01327 DjVuDocEditor::set_page_title(int page_num, const GUTF8String &title)
01328 {
01329    DEBUG_MSG("DjVuDocEditor::set_page_title(), page_num='" << page_num << "'\n");
01330    DEBUG_MAKE_INDENT(3);
01331 
01332    if (page_num<0 || page_num>=get_pages_num())
01333       G_THROW( ERR_MSG("DjVuDocEditor.bad_page") "\t"+GUTF8String(page_num));
01334 
01335    set_file_title(page_to_id(page_num), title);
01336 }
01337 
01338 //****************************************************************************
01339 //************************** Shared annotations ******************************
01340 //****************************************************************************
01341 
01342 void
01343 DjVuDocEditor::simplify_anno(void (* progress_cb)(float progress, void *),
01344                              void * cl_data)
01345       // It's important that no decoding is done while this function
01346       // is running. Otherwise the DjVuFile's decoding routines and
01347       // this function may attempt to decode/modify a file's
01348       // annotations at the same time.
01349 {
01350       // Get the name of the SHARED_ANNO file. We will not
01351       // touch that file (will not move annotations from it)
01352    GP<DjVmDir::File> shared_file=djvm_dir->get_shared_anno_file();
01353    GUTF8String shared_id;
01354    if (shared_file)
01355       shared_id=shared_file->get_load_name();
01356 
01357    GList<GURL> ignore_list;
01358    if (shared_id.length())
01359       ignore_list.append(id_to_url(shared_id));
01360 
01361       // First, for every page get merged (or "flatten" or "projected")
01362       // annotations and store them inside the top-level page file
01363    int pages_num=djvm_dir->get_pages_num();
01364    for(int page_num=0;page_num<pages_num;page_num++)
01365    {
01366       GP<DjVuFile> djvu_file=get_djvu_file(page_num);
01367       if (!djvu_file)
01368          G_THROW( ERR_MSG("DjVuDocEditor.page_fail") "\t"+page_num);
01369       int max_level=0;
01370       GP<ByteStream> anno;
01371       anno=djvu_file->get_merged_anno(ignore_list, &max_level);
01372       if (anno && max_level>0)
01373       {
01374             // This is the moment when we try to modify DjVuFile's annotations
01375             // Make sure, that it's not being decoded
01376          GSafeFlags & file_flags=djvu_file->get_safe_flags();
01377          GMonitorLock lock(&file_flags);
01378          while(file_flags & DjVuFile::DECODING)
01379             file_flags.wait();
01380         
01381             // Merge all chunks in one by decoding and encoding DjVuAnno
01382          const GP<DjVuAnno> dec_anno(DjVuAnno::create());
01383          dec_anno->decode(anno);
01384          const GP<ByteStream> new_anno(ByteStream::create());
01385          dec_anno->encode(new_anno);
01386          new_anno->seek(0);
01387 
01388             // And store it in the file
01389          djvu_file->anno=new_anno;
01390          djvu_file->rebuild_data_pool();
01391          if ((file_flags & (DjVuFile::DECODE_OK |
01392                             DjVuFile::DECODE_FAILED |
01393                             DjVuFile::DECODE_STOPPED))==0)
01394             djvu_file->anno=0;
01395       }
01396       if (progress_cb)
01397     progress_cb((float)(page_num/2.0/pages_num), cl_data);
01398    }
01399 
01400       // Now remove annotations from every file except for
01401       // the top-level page files and SHARED_ANNO file.
01402       // Unlink empty files too.
01403    GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
01404    int cnt;
01405    GPosition pos;
01406    for(pos=files_list, cnt=0;pos;++pos, cnt++)
01407    {
01408       GP<DjVmDir::File> frec=files_list[pos];
01409       if (!frec->is_page() && frec->get_load_name()!=shared_id)
01410       {
01411          GP<DjVuFile> djvu_file=get_djvu_file(frec->get_load_name());
01412          if (djvu_file)
01413          {
01414             djvu_file->remove_anno();
01415             if (djvu_file->get_chunks_number()==0)
01416                remove_file(frec->get_load_name(), true);
01417          }
01418       }
01419       if (progress_cb)
01420          progress_cb((float)(0.5+cnt/2.0/files_list.size()), cl_data);
01421    }
01422 }
01423 
01424 void
01425 DjVuDocEditor::create_shared_anno_file(void (* progress_cb)(float progress, void *),
01426                                        void * cl_data)
01427 {
01428    if (djvm_dir->get_shared_anno_file())
01429       G_THROW( ERR_MSG("DjVuDocEditor.share_fail") );
01430 
01431       // Prepare file with ANTa chunk inside
01432    const GP<ByteStream> gstr(ByteStream::create());
01433    const GP<IFFByteStream> giff(IFFByteStream::create(gstr));
01434    IFFByteStream &iff=*giff;
01435    iff.put_chunk("FORM:DJVI");
01436    iff.put_chunk("ANTa");
01437    iff.close_chunk();
01438    iff.close_chunk();
01439    ByteStream &str=*gstr;
01440    str.flush();
01441    str.seek(0);
01442    const GP<DataPool> file_pool(DataPool::create(gstr));
01443 
01444       // Get a unique ID for the new file
01445    const GUTF8String id(find_unique_id("shared_anno.iff"));
01446 
01447       // Add it into the directory
01448    GP<DjVmDir::File> frec(DjVmDir::File::create(id, id, id,
01449      DjVmDir::File::SHARED_ANNO));
01450    djvm_dir->insert_file(frec, 1);
01451 
01452       // Add it to our "cache"
01453    {
01454       GP<File> f=new File;
01455       f->pool=file_pool;
01456       GCriticalSectionLock lock(&files_lock);
01457       files_map[id]=f;
01458    }
01459 
01460       // Now include this shared file into every top-level page file
01461    int pages_num=djvm_dir->get_pages_num();
01462    for(int page_num=0;page_num<pages_num;page_num++)
01463    {
01464       GP<DjVuFile> djvu_file=get_djvu_file(page_num);
01465       djvu_file->insert_file(id, 1);
01466 
01467       if (progress_cb)
01468          progress_cb((float) page_num/pages_num, cl_data);
01469    }
01470 }
01471 
01472 void 
01473 DjVuDocEditor::set_djvm_nav(GP<DjVmNav> n)
01474 {
01475   if (n && ! n->isValidBookmark())
01476     G_THROW("Invalid bookmark data");
01477   djvm_nav = n;
01478 }
01479 
01480 GP<DjVuFile>
01481 DjVuDocEditor::get_shared_anno_file(void)
01482 {
01483    GP<DjVuFile> djvu_file;
01484 
01485    GP<DjVmDir::File> frec=djvm_dir->get_shared_anno_file();
01486    if (frec)
01487       djvu_file=get_djvu_file(frec->get_load_name());
01488 
01489    return djvu_file;
01490 }
01491 
01492 GP<DataPool>
01493 DjVuDocEditor::get_thumbnail(int page_num, bool dont_decode)
01494       // We override DjVuDocument::get_thumbnail() here because
01495       // pages may have been shuffled and those "thumbnail file records"
01496       // from the DjVmDir do not describe things correctly.
01497       //
01498       // So, first we will check the thumb_map[] if we have a predecoded
01499       // thumbnail for the given page. If this is the case, we will
01500       // return it. Otherwise we will ask DjVuDocument to generate
01501       // this thumbnail for us.
01502 {
01503    const GUTF8String id(page_to_id(page_num));
01504 
01505    GCriticalSectionLock lock(&thumb_lock);
01506    const GPosition pos(thumb_map.contains(id));
01507    if (pos)
01508    {
01509          // Get the image from the map
01510       return thumb_map[pos];
01511    } else
01512    {
01513       unfile_thumbnails();
01514       return DjVuDocument::get_thumbnail(page_num, dont_decode);
01515    }
01516 }
01517 
01518 int
01519 DjVuDocEditor::get_thumbnails_num(void) const
01520 {
01521    GCriticalSectionLock lock((GCriticalSection *) &thumb_lock);
01522 
01523    int cnt=0;
01524    int pages_num=get_pages_num();
01525    for(int page_num=0;page_num<pages_num;page_num++)
01526    {
01527      if (thumb_map.contains(page_to_id(page_num)))
01528        cnt++;
01529    }
01530    return cnt;
01531 }
01532 
01533 int
01534 DjVuDocEditor::get_thumbnails_size(void) const
01535 {
01536    DEBUG_MSG("DjVuDocEditor::remove_thumbnails(): doing it\n");
01537    DEBUG_MAKE_INDENT(3);
01538 
01539    GCriticalSectionLock lock((GCriticalSection *) &thumb_lock);
01540 
01541    int pages_num=get_pages_num();
01542    for(int page_num=0;page_num<pages_num;page_num++)
01543    {
01544      const GPosition pos(thumb_map.contains(page_to_id(page_num)));
01545      if (pos)
01546      {
01547        const GP<ByteStream> gstr(thumb_map[pos]->get_stream());
01548        GP<IW44Image> iwpix=IW44Image::create_decode(IW44Image::COLOR);
01549        iwpix->decode_chunk(gstr);
01550       
01551        int width=iwpix->get_width();
01552        int height=iwpix->get_height();
01553        return width<height ? width : height;
01554     }
01555   }
01556   return -1;
01557 }
01558 
01559 void
01560 DjVuDocEditor::remove_thumbnails(void)
01561 {
01562    DEBUG_MSG("DjVuDocEditor::remove_thumbnails(): doing it\n");
01563    DEBUG_MAKE_INDENT(3);
01564 
01565    unfile_thumbnails();
01566 
01567    DEBUG_MSG("clearing thumb_map\n");
01568    GCriticalSectionLock lock(&thumb_lock);
01569    thumb_map.empty();
01570 }
01571 
01572 void
01573 DjVuDocEditor::unfile_thumbnails(void)
01574       // Will erase all "THUMBNAILS" files from DjVmDir.
01575       // This function is useful when filing thumbnails (to get rid of
01576       // those files, which currently exist: they need to be replaced
01577       // anyway) and when calling DjVuDocument::get_thumbnail() to
01578       // be sure, that it will not use wrong information from DjVmDir
01579 {
01580    DEBUG_MSG("DjVuDocEditor::unfile_thumbnails(): updating DjVmDir\n");
01581    DEBUG_MAKE_INDENT(3);
01582 
01583    {
01584      GCriticalSectionLock lock(&threqs_lock);
01585      threqs_list.empty();
01586    }
01587    if((const DjVmDir *)djvm_dir)
01588    {
01589      GPList<DjVmDir::File> xfiles_list=djvm_dir->get_files_list();
01590      for(GPosition pos=xfiles_list;pos;++pos)
01591      {
01592        GP<DjVmDir::File> f=xfiles_list[pos];
01593        if (f->is_thumbnails())
01594          djvm_dir->delete_file(f->get_load_name());
01595      }
01596    }
01597 }
01598 
01599 void
01600 DjVuDocEditor::file_thumbnails(void)
01601       // The purpose of this function is to create files containing
01602       // thumbnail images and register them in DjVmDir.
01603       // If some of the thumbnail images are missing, they'll
01604       // be generated with generate_thumbnails()
01605 {
01606    DEBUG_MSG("DjVuDocEditor::file_thumbnails(): updating DjVmDir\n");
01607    DEBUG_MAKE_INDENT(3);
01608    unfile_thumbnails();
01609 
01610       // Generate thumbnails if they're missing due to some reason.
01611    int thumb_num=get_thumbnails_num();
01612    int size=thumb_num>0 ? get_thumbnails_size() : 128;
01613    if (thumb_num!=get_pages_num())
01614    {
01615      generate_thumbnails(size);
01616    }
01617 
01618    DEBUG_MSG("filing thumbnails\n");
01619 
01620    GCriticalSectionLock lock(&thumb_lock);
01621 
01622       // The first thumbnail file always contains only one thumbnail
01623    int ipf=1;
01624    int image_num=0;
01625    int page_num=0, pages_num=djvm_dir->get_pages_num();
01626    GP<ByteStream> str(ByteStream::create());
01627    GP<IFFByteStream> iff(IFFByteStream::create(str));
01628    iff->put_chunk("FORM:THUM");
01629    for(;;)
01630    {
01631       GUTF8String id(page_to_id(page_num));
01632       const GPosition pos(thumb_map.contains(id));
01633       if (! pos)
01634       {
01635         G_THROW( ERR_MSG("DjVuDocEditor.no_thumb") "\t"+GUTF8String(page_num));
01636       }
01637       iff->put_chunk("TH44");
01638       iff->copy(*(thumb_map[pos]->get_stream()));
01639       iff->close_chunk();
01640       image_num++;
01641       page_num++;
01642       if (image_num>=ipf || page_num>=pages_num)
01643       {
01644          int i=id.rsearch('.');
01645          if(i<=0)
01646          {
01647            i=id.length();
01648          }
01649          id=id.substr(0,i)+".thumb";
01650             // Get unique ID for this file
01651          id=find_unique_id(id);
01652 
01653             // Create a file record with the chosen ID
01654          GP<DjVmDir::File> file(DjVmDir::File::create(id, id, id,
01655            DjVmDir::File::THUMBNAILS));
01656 
01657             // Set correct file position (so that it will cover the next
01658             // ipf pages)
01659          int file_pos=djvm_dir->get_page_pos(page_num-image_num);
01660          djvm_dir->insert_file(file, file_pos);
01661 
01662             // Now add the File record (containing the file URL and DataPool)
01663             // After we do it a simple save_as() will save the document
01664             // with the thumbnails. This is because DjVuDocument will see
01665             // the file in DjVmDir and will ask for data. We will intercept
01666             // the request for data and will provide this DataPool
01667          iff->close_chunk();
01668          str->seek(0);
01669          const GP<DataPool> file_pool(DataPool::create(str));
01670          GP<File> f=new File;
01671          f->pool=file_pool;
01672          GCriticalSectionLock lock(&files_lock);
01673          files_map[id]=f;
01674 
01675             // And create new streams
01676          str=ByteStream::create();
01677          iff=IFFByteStream::create(str);
01678          iff->put_chunk("FORM:THUM");
01679          image_num=0;
01680 
01681             // Reset ipf to correct value (after we stored first
01682             // "exceptional" file with thumbnail for the first page)
01683          if (page_num==1) ipf=thumbnails_per_file;
01684          if (page_num>=pages_num) break;
01685       }
01686    }
01687 }
01688 
01689 int
01690 DjVuDocEditor::generate_thumbnails(int thumb_size, int page_num)
01691 {
01692    DEBUG_MSG("DjVuDocEditor::generate_thumbnails(): doing it\n");
01693    DEBUG_MAKE_INDENT(3);
01694 
01695    if(page_num<(djvm_dir->get_pages_num()))
01696    {
01697       const GUTF8String id(page_to_id(page_num));
01698       if (!thumb_map.contains(id))
01699         {
01700           const GP<DjVuImage> dimg(get_page(page_num, true));
01701          
01702           GRect rect(0, 0, thumb_size, dimg->get_height()*thumb_size/dimg->get_width());
01703           GP<GPixmap> pm=dimg->get_pixmap(rect, rect, get_thumbnails_gamma());
01704           if (!pm)
01705             {
01706               const GP<GBitmap> bm(dimg->get_bitmap(rect, rect, sizeof(int)));
01707               if (bm) 
01708                 pm = GPixmap::create(*bm);
01709               else
01710                 pm = GPixmap::create(rect.height(), rect.width(), &GPixel::WHITE);
01711             }
01712           // Store and compress the pixmap
01713           const GP<IW44Image> iwpix(IW44Image::create_encode(*pm));
01714           const GP<ByteStream> gstr(ByteStream::create());
01715           IWEncoderParms parms;
01716           parms.slices=97;
01717           parms.bytes=0;
01718           parms.decibels=0;
01719           iwpix->encode_chunk(gstr, parms);
01720           gstr->seek(0L);
01721           thumb_map[id]=DataPool::create(gstr);
01722         }
01723       ++page_num;
01724    }
01725    else
01726    {
01727      page_num = -1;
01728    }
01729    return page_num;
01730 }
01731 
01732 void
01733 DjVuDocEditor::generate_thumbnails(int thumb_size,
01734                                    bool (* cb)(int page_num, void *),
01735                                    void * cl_data)
01736 {
01737    int page_num=0;
01738    do
01739    {
01740      page_num=generate_thumbnails(thumb_size,page_num);
01741      if (cb) if (cb(page_num, cl_data)) return;
01742    } while(page_num>=0);
01743 }
01744 
01745 static void
01746 store_file(const GP<DjVmDir> & src_djvm_dir, const GP<DjVmDoc> & djvm_doc,
01747            GP<DjVuFile> & djvu_file, GMap<GURL, void *> & map)
01748 {
01749    GURL url=djvu_file->get_url();
01750    if (!map.contains(url))
01751    {
01752       map[url]=0;
01753 
01754          // Store included files first
01755       GPList<DjVuFile> djvu_files_list=djvu_file->get_included_files(false);
01756       for(GPosition pos=djvu_files_list;pos;++pos)
01757          store_file(src_djvm_dir, djvm_doc, djvu_files_list[pos], map);
01758 
01759          // Now store contents of this file
01760       GP<DataPool> file_data=djvu_file->get_djvu_data(false);
01761       GP<DjVmDir::File> frec=src_djvm_dir->name_to_file(url.name());
01762       if (frec)
01763       {
01764          frec=new DjVmDir::File(*frec);
01765          djvm_doc->insert_file(frec, file_data, -1);
01766       }
01767    }
01768 }
01769 
01770 void
01771 DjVuDocEditor::save_pages_as(
01772   const GP<ByteStream> &str, const GList<int> & _page_list)
01773 {
01774    GList<int> page_list=sortList(_page_list);
01775 
01776    GP<DjVmDoc> djvm_doc=DjVmDoc::create();
01777    GMap<GURL, void *> map;
01778    for(GPosition pos=page_list;pos;++pos)
01779    {
01780       GP<DjVmDir::File> frec=djvm_dir->page_to_file(page_list[pos]);
01781       if (frec)
01782       {
01783          GP<DjVuFile> djvu_file=get_djvu_file(frec->get_load_name());
01784          if (djvu_file)
01785             store_file(djvm_dir, djvm_doc, djvu_file, map);
01786       }
01787    }
01788    djvm_doc->write(str);
01789 }
01790 
01791 void
01792 DjVuDocEditor::save_file(const GUTF8String &file_id, const GURL &codebase,
01793   const bool only_modified, GMap<GUTF8String,GUTF8String> & map)
01794 {
01795   if(only_modified)
01796   {
01797     for(GPosition pos=files_map;pos;++pos)
01798     {
01799       const GP<File> file_rec(files_map[pos]);
01800       const bool file_modified=file_rec->pool ||
01801         (file_rec->file && file_rec->file->is_modified());
01802       if(!file_modified)
01803       {
01804         const GUTF8String id=files_map.key(pos);
01805         const GUTF8String save_name(djvm_dir->id_to_file(id)->get_save_name());
01806         if(id == save_name)
01807         {
01808           map[id]=id;
01809         }
01810       }
01811     }
01812   }
01813   save_file(file_id,codebase,map);
01814 }
01815 
01816 void
01817 DjVuDocEditor::save_file(
01818   const GUTF8String &file_id, const GURL &codebase,
01819   GMap<GUTF8String,GUTF8String> & map)
01820 {
01821    DEBUG_MSG("DjVuDocEditor::save_file(): ID='" << file_id << "'\n");
01822    DEBUG_MAKE_INDENT(3);
01823 
01824    if (!map.contains(file_id))
01825    {
01826       const GP<DjVmDir::File> file(djvm_dir->id_to_file(file_id));
01827 
01828       GP<DataPool> file_pool;
01829       const GPosition pos(files_map.contains(file_id));
01830       if (pos)
01831       {
01832          const GP<File> file_rec(files_map[pos]);
01833          if (file_rec->file)
01834             file_pool=file_rec->file->get_djvu_data(false);
01835          else
01836             file_pool=file_rec->pool;
01837       }
01838 
01839       if (!file_pool)
01840       {
01841          DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
01842          file_pool=pcaster->request_data(this, id_to_url(file_id));
01843       }
01844 
01845       if (file_pool)
01846       {
01847          GMap<GUTF8String,GUTF8String> incl;
01848          map[file_id]=get_djvm_doc()->save_file(codebase,*file,incl,file_pool);
01849          for(GPosition pos=incl;pos;++pos)
01850          {
01851            save_file(incl.key(pos),codebase ,map);
01852          }
01853       }else
01854       {
01855         map[file_id]=file->get_save_name();
01856       }
01857    }
01858 }
01859 
01860 void
01861 DjVuDocEditor::save(void)
01862 {
01863    DEBUG_MSG("DjVuDocEditor::save(): saving the file\n");
01864    DEBUG_MAKE_INDENT(3);
01865 
01866    if (!can_be_saved())
01867      G_THROW( ERR_MSG("DjVuDocEditor.cant_save") );
01868    save_as(GURL(), orig_doc_type!=INDIRECT);
01869 }
01870 
01871 void
01872 DjVuDocEditor::write(const GP<ByteStream> &gbs, bool force_djvm)
01873 {
01874   DEBUG_MSG("DjVuDocEditor::write()\n");
01875   DEBUG_MAKE_INDENT(3);
01876   if (get_thumbnails_num()==get_pages_num())
01877   {
01878     file_thumbnails();
01879   }else
01880   { 
01881     remove_thumbnails();
01882   }
01883   clean_files_map();
01884   DjVuDocument::write(gbs,force_djvm);
01885 }
01886 
01887 void
01888 DjVuDocEditor::write(
01889   const GP<ByteStream> &gbs,const GMap<GUTF8String,void *> &reserved)
01890 {
01891   DEBUG_MSG("DjVuDocEditor::write()\n");
01892   DEBUG_MAKE_INDENT(3);
01893   if (get_thumbnails_num()==get_pages_num())
01894   {
01895     file_thumbnails();
01896   }else
01897   { 
01898     remove_thumbnails();
01899   }
01900   clean_files_map();
01901   DjVuDocument::write(gbs,reserved);
01902 }
01903 
01904 void
01905 DjVuDocEditor::save_as(const GURL &where, bool bundled)
01906 {
01907    DEBUG_MSG("DjVuDocEditor::save_as(): where='" << where << "'\n");
01908    DEBUG_MAKE_INDENT(3);
01909 
01910       // First see if we need to generate (or just reshuffle) thumbnails...
01911       // If we have an icon for every page, we will just call
01912       // file_thumbnails(), which will update DjVmDir and will create
01913       // the actual bundles with thumbnails (very fast)
01914       // Otherwise we will remove the thumbnails completely because
01915       // we really don't want to deal with documents, which have only
01916       // some of their pages thumbnailed.
01917    if (get_thumbnails_num()==get_pages_num())
01918    {
01919      file_thumbnails();
01920    }else
01921    { 
01922      remove_thumbnails();
01923    }
01924 
01925    GURL save_doc_url;
01926 
01927    if (where.is_empty())
01928    {
01929          // Assume, that we just want to 'save'. Check, that it's possible
01930          // and proceed.
01931       bool can_be_saved_bundled=orig_doc_type==BUNDLED ||
01932                                 orig_doc_type==OLD_BUNDLED ||
01933                                 orig_doc_type==SINGLE_PAGE ||
01934                                 orig_doc_type==OLD_INDEXED && orig_doc_pages==1;
01935       if ((bundled ^ can_be_saved_bundled)!=0)
01936          G_THROW( ERR_MSG("DjVuDocEditor.cant_save2") );
01937       save_doc_url=doc_url;
01938    } else
01939    {
01940       save_doc_url=where;
01941    }
01942 
01943    int save_doc_type=bundled ? BUNDLED : INDIRECT;
01944 
01945    clean_files_map();
01946 
01947    GCriticalSectionLock lock(&files_lock);
01948 
01949    DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
01950 
01951       // First consider saving in SINGLE_FILE format (one file)
01952    if(needs_compression())
01953    {
01954      DEBUG_MSG("Compressing on output\n");
01955      remove_thumbnails();
01956      if(! djvu_compress_codec)
01957      {
01958        G_THROW( ERR_MSG("DjVuDocEditor.no_codec") );
01959      }
01960      const GP<DjVmDoc> doc(get_djvm_doc());
01961      GP<ByteStream> mbs(ByteStream::create());
01962      doc->write(mbs);
01963      mbs->flush();
01964      mbs->seek(0,SEEK_SET);
01965      djvu_compress_codec(mbs,save_doc_url,(!(const DjVmDir *)djvm_dir)||(djvm_dir->get_files_num()==1)||(save_doc_type!=INDIRECT));
01966      files_map.empty();
01967      doc_url=GURL();
01968    }else
01969    {
01970      if (djvm_dir->get_files_num()==1)
01971      {
01972        // Here 'bundled' has no effect: we will save it as one page.
01973        DEBUG_MSG("saving one file...\n");
01974        GURL file_url=page_to_url(0);
01975        const GUTF8String file_id(djvm_dir->page_to_file(0)->get_load_name());
01976        GP<DataPool> file_pool;
01977        GPosition pos=files_map.contains(file_id);
01978        if (pos)
01979        {
01980          const GP<File> file_rec(files_map[pos]);
01981          if (file_rec->pool && (!file_rec->file ||
01982                                 !file_rec->file->is_modified()))
01983          {
01984            file_pool=file_rec->pool;
01985          }else if (file_rec->file)
01986          {
01987            file_pool=file_rec->file->get_djvu_data(false);
01988          }
01989        }
01990        // Even if file has not been modified (pool==0) we still want
01991        // to save it.
01992        if (!file_pool)
01993          file_pool=pcaster->request_data(this, file_url);
01994        if (file_pool)
01995        {
01996          DEBUG_MSG("Saving '" << file_url << "' to '" << save_doc_url << "'\n");
01997          DataPool::load_file(save_doc_url);
01998          const GP<ByteStream> gstr_out(ByteStream::create(save_doc_url, "wb"));
01999          ByteStream &str_out=*gstr_out;
02000          str_out.writall(octets, 4);
02001          const GP<ByteStream> str_in(file_pool->get_stream());
02002          str_out.copy(*str_in);
02003        }
02004 
02005        // Update the document's DataPool (to save memory)
02006        const GP<DjVmDoc> doc(get_djvm_doc());
02007        const GP<ByteStream> gstr=ByteStream::create();// One page: we can do it in the memory
02008        doc->write(gstr);
02009        gstr->seek(0, SEEK_SET);
02010        const GP<DataPool> pool(DataPool::create(gstr));
02011        doc_pool=pool;
02012        init_data_pool=pool;
02013 
02014          // Also update DjVmDir (to reflect changes in offsets)
02015        djvm_dir=doc->get_djvm_dir();
02016      } else if (save_doc_type==INDIRECT)
02017      {
02018        DEBUG_MSG("Saving in INDIRECT format to '" << save_doc_url << "'\n");
02019        bool save_only_modified=!(save_doc_url!=doc_url || save_doc_type!=orig_doc_type);
02020        GPList<DjVmDir::File> xfiles_list=djvm_dir->resolve_duplicates(false);
02021        const GURL codebase=save_doc_url.base();
02022        int pages_num=djvm_dir->get_pages_num();
02023        GMap<GUTF8String, GUTF8String> map;
02024        // First go thru the pages
02025        for(int page_num=0;page_num<pages_num;page_num++)
02026        {
02027          const GUTF8String id(djvm_dir->page_to_file(page_num)->get_load_name());
02028          save_file(id, codebase, save_only_modified, map);
02029        }
02030        // Next go thru thumbnails and similar stuff
02031        GPosition pos;
02032        for(pos=xfiles_list;pos;++pos)
02033          save_file(xfiles_list[pos]->get_load_name(), codebase, save_only_modified, map);
02034 
02035          // Finally - save the top-level index file
02036        for(pos=xfiles_list;pos;++pos)
02037        {
02038          const GP<DjVmDir::File> file(xfiles_list[pos]);
02039          file->offset=0;
02040          file->size=0;
02041        }
02042        DataPool::load_file(save_doc_url);
02043        const GP<ByteStream> gstr(ByteStream::create(save_doc_url, "wb"));
02044        const GP<IFFByteStream> giff(IFFByteStream::create(gstr));
02045        IFFByteStream &iff=*giff;
02046 
02047        iff.put_chunk("FORM:DJVM", 1);
02048        iff.put_chunk("DIRM");
02049        djvm_dir->encode(giff->get_bytestream());
02050        iff.close_chunk();
02051        iff.close_chunk();
02052        iff.flush();
02053 
02054        // Update the document data pool (not required, but will save memory)
02055        doc_pool=DataPool::create(save_doc_url);
02056        init_data_pool=doc_pool;
02057 
02058        // No reason to update DjVmDir as for this format it doesn't
02059        // contain DJVM offsets
02060      } else if (save_doc_type==BUNDLED || save_doc_type==OLD_BUNDLED)
02061      {
02062         DEBUG_MSG("Saving in BUNDLED format to '" << save_doc_url << "'\n");
02063 
02064          // Can't be very smart here. Simply overwrite the file.
02065         const GP<DjVmDoc> doc(get_djvm_doc());
02066         DataPool::load_file(save_doc_url);
02067         const GP<ByteStream> gstr(ByteStream::create(save_doc_url, "wb"));
02068         doc->write(gstr);
02069         gstr->flush();
02070 
02071          // Update the document data pool (not required, but will save memory)
02072         doc_pool=DataPool::create(save_doc_url);
02073         init_data_pool=doc_pool;
02074 
02075          // Also update DjVmDir (to reflect changes in offsets)
02076         djvm_dir=doc->get_djvm_dir();
02077      } else
02078      {
02079        G_THROW( ERR_MSG("DjVuDocEditor.cant_save") );
02080      }
02081 
02082         // Now, after we have saved the document w/o any error, detach DataPools,
02083         // which are in the 'File's list to save memory. Detach everything.
02084         // Even in the case when File->file is non-zero. If File->file is zero,
02085         // remove the item from the list at all. If it's non-zero, it has
02086         // to stay there because by definition files_map[] contains the list
02087         // of all active files and customized DataPools
02088         //
02089         // In addition to it, look thru all active files and change their URLs
02090         // to reflect changes in the document's URL (if there was a change)
02091         // Another reason why file's URLs must be changed is that we may have
02092         // saved the document in a different format, which changes the rules
02093         // of file url composition.
02094      for(GPosition pos=files_map;pos;)
02095      {
02096         const GP<File> file_rec(files_map[pos]);
02097         file_rec->pool=0;
02098         if (file_rec->file==0)
02099         {
02100          GPosition this_pos=pos;
02101          ++pos;
02102          files_map.del(this_pos);
02103         } else
02104         {
02105             // Change the file's url;
02106          if (doc_url!=save_doc_url ||
02107              orig_doc_type!=save_doc_type)
02108             if (save_doc_type==BUNDLED)
02109                file_rec->file->move(save_doc_url);
02110             else file_rec->file->move(save_doc_url.base());
02111          ++pos;
02112         }
02113      }
02114 
02115    }
02116    orig_doc_type=save_doc_type;
02117    doc_type=save_doc_type;
02118 
02119    if (doc_url!=save_doc_url)
02120    {
02121      // Also update document's URL (we moved, didn't we?)
02122      doc_url=save_doc_url;
02123      init_url=save_doc_url;
02124    }
02125 }
02126 
02127 GP<DjVuDocEditor> 
02128 DjVuDocEditor::create_wait(void)
02129 {
02130   DjVuDocEditor *doc=new DjVuDocEditor();
02131   const GP<DjVuDocEditor> retval(doc);
02132   doc->init();
02133   return retval;
02134 }
02135 
02136 GP<DjVuDocEditor> 
02137 DjVuDocEditor::create_wait(const GURL &url)
02138 {
02139   DjVuDocEditor *doc=new DjVuDocEditor();
02140   const GP<DjVuDocEditor> retval(doc);
02141   doc->init(url);
02142   return retval;
02143 }
02144 
02145 bool
02146 DjVuDocEditor::inherits(const GUTF8String &class_name) const
02147 {
02148    return (class_name == "DjVuDocEditor")||DjVuDocument::inherits(class_name);
02149 }
02150 
02151 int
02152 DjVuDocEditor::get_orig_doc_type(void) const
02153 {
02154    return orig_doc_type;
02155 }
02156 
02157 bool
02158 DjVuDocEditor::can_be_saved(void) const
02159 {
02160    return !(needs_rename()||needs_compression()||orig_doc_type==UNKNOWN_TYPE ||
02161         orig_doc_type==OLD_INDEXED);
02162 }
02163 
02164 int
02165 DjVuDocEditor::get_save_doc_type(void) const
02166 {
02167    if (orig_doc_type==SINGLE_PAGE)
02168       if (djvm_dir->get_files_num()==1)
02169         return SINGLE_PAGE;
02170       else
02171         return BUNDLED;
02172    else if (orig_doc_type==INDIRECT)
02173      return INDIRECT;
02174    else if (orig_doc_type==OLD_BUNDLED || orig_doc_type==BUNDLED)
02175      return BUNDLED;
02176    else
02177      return UNKNOWN_TYPE;
02178 }
02179 
02180 GURL
02181 DjVuDocEditor::get_doc_url(void) const
02182 {
02183    return doc_url.is_empty() ? init_url : doc_url;
02184 }
02185 
02186 
02187 
02188 #ifdef HAVE_NAMESPACES
02189 }
02190 # ifndef NOT_USING_DJVU_NAMESPACE
02191 using namespace DJVU;
02192 # endif
02193 #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