source: trunk/libdjvu/DjVuDocEditor.cpp @ 17

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

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

File size: 70.7 KB
Line 
1//C-  -*- C++ -*-
2//C- -------------------------------------------------------------------
3//C- DjVuLibre-3.5
4//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
5//C- Copyright (c) 2001  AT&T
6//C-
7//C- This software is subject to, and may be distributed under, the
8//C- GNU General Public License, Version 2. The license should have
9//C- accompanied the software or you may obtain a copy of the license
10//C- from the Free Software Foundation at http://www.fsf.org .
11//C-
12//C- This program is distributed in the hope that it will be useful,
13//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
14//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15//C- GNU General Public License for more details.
16//C-
17//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library
18//C- distributed by Lizardtech Software.  On July 19th 2002, Lizardtech
19//C- Software authorized us to replace the original DjVu(r) Reference
20//C- Library notice by the following text (see doc/lizard2002.djvu):
21//C-
22//C-  ------------------------------------------------------------------
23//C- | DjVu (r) Reference Library (v. 3.5)
24//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
25//C- | The DjVu Reference Library is protected by U.S. Pat. No.
26//C- | 6,058,214 and patents pending.
27//C- |
28//C- | This software is subject to, and may be distributed under, the
29//C- | GNU General Public License, Version 2. The license should have
30//C- | accompanied the software or you may obtain a copy of the license
31//C- | from the Free Software Foundation at http://www.fsf.org .
32//C- |
33//C- | The computer code originally released by LizardTech under this
34//C- | license and unmodified by other parties is deemed "the LIZARDTECH
35//C- | ORIGINAL CODE."  Subject to any third party intellectual property
36//C- | claims, LizardTech grants recipient a worldwide, royalty-free,
37//C- | non-exclusive license to make, use, sell, or otherwise dispose of
38//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the
39//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
40//C- | General Public License.   This grant only confers the right to
41//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
42//C- | the extent such infringement is reasonably necessary to enable
43//C- | recipient to make, have made, practice, sell, or otherwise dispose
44//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
45//C- | any greater extent that may be necessary to utilize further
46//C- | modifications or combinations.
47//C- |
48//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
49//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
50//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
51//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
52//C- +------------------------------------------------------------------
53//
54// $Id: DjVuDocEditor.cpp,v 1.13 2005/05/25 20:24:52 leonb Exp $
55// $Name:  $
56
57#ifdef HAVE_CONFIG_H
58# include "config.h"
59#endif
60#if NEED_GNUG_PRAGMAS
61# pragma implementation
62#endif
63
64#include "DjVuDocEditor.h"
65#include "DjVuImage.h"
66#include "IFFByteStream.h"
67#include "DataPool.h"
68#include "IW44Image.h"
69#include "GOS.h"
70#include "GURL.h"
71#include "DjVuAnno.h"
72#include "GRect.h"
73#include "DjVmNav.h"
74
75#include "debug.h"
76
77#include <ctype.h>
78
79
80#ifdef HAVE_NAMESPACES
81namespace DJVU {
82# ifdef NOT_DEFINED // Just to fool emacs c++ mode
83}
84#endif
85#endif
86
87
88static const char octets[4]={0x41,0x54,0x26,0x54};
89
90int        DjVuDocEditor::thumbnails_per_file=10;
91
92// This is a structure for active files and DataPools. It may contain
93// a DjVuFile, which is currently being used by someone (I check the list
94// and get rid of hanging files from time to time) or a DataPool,
95// which is "custom" with respect to the document (was modified or
96// inserted), or both.
97//
98// DjVuFile is set to smth!=0 when it's created using url_to_file().
99//          It's reset back to ZERO in clean_files_map() when
100//        it sees, that a given file is not used by anyone.
101// DataPool is updated when a file is inserted
102class DjVuDocEditor::File : public GPEnabled
103{
104public:
105  // 'pool' below may be non-zero only if it cannot be retrieved
106  // by the DjVuDocument, that is it either corresponds to a
107  // modified DjVuFile or it has been inserted. Otherwise it's ZERO
108  // Once someone assigns a non-zero DataPool, it remains non-ZERO
109  // (may be updated if the file gets modified) and may be reset
110  // only by save() or save_as() functions.
111  GP<DataPool>  pool;
112
113  // If 'file' is non-zero, it means, that it's being used by someone
114  // We check for unused files from time to time and ZERO them.
115  // But before we do it, we may save the DataPool in the case if
116  // file has been modified.
117  GP<DjVuFile>  file;
118};
119
120void
121DjVuDocEditor::check(void)
122{
123   if (!initialized) G_THROW( ERR_MSG("DjVuDocEditor.not_init") );
124}
125
126DjVuDocEditor::DjVuDocEditor(void)
127{
128   initialized=false;
129   refresh_cb=0;
130   refresh_cl_data=0;
131}
132
133DjVuDocEditor::~DjVuDocEditor(void)
134{
135   if (!tmp_doc_url.is_empty())
136   {
137     tmp_doc_url.deletefile();
138   }
139
140   GCriticalSectionLock lock(&thumb_lock);
141   thumb_map.empty();
142   DataPool::close_all();
143}
144
145void
146DjVuDocEditor::init(void)
147{
148   DEBUG_MSG("DjVuDocEditor::init() called\n");
149   DEBUG_MAKE_INDENT(3);
150
151      // If you remove this check be sure to delete thumb_map
152   if (initialized) G_THROW( ERR_MSG("DjVuDocEditor.init") );
153
154   doc_url=GURL::Filename::UTF8("noname.djvu");
155
156   const GP<DjVmDoc> doc(DjVmDoc::create());
157   const GP<ByteStream> gstr(ByteStream::create());
158   doc->write(gstr);
159   gstr->seek(0, SEEK_SET);
160   doc_pool=DataPool::create(gstr);
161
162   orig_doc_type=UNKNOWN_TYPE;
163   orig_doc_pages=0;
164
165   initialized=true;
166
167   DjVuDocument::init(doc_url, this);
168}
169
170void
171DjVuDocEditor::init(const GURL &url)
172{
173   DEBUG_MSG("DjVuDocEditor::init() called: url='" << url << "'\n");
174   DEBUG_MAKE_INDENT(3);
175
176      // If you remove this check be sure to delete thumb_map
177   if (initialized)
178     G_THROW( ERR_MSG("DjVuDocEditor.init") );
179
180      // First - create a temporary DjVuDocument and check its type
181   doc_pool=DataPool::create(url);
182   doc_url=url;
183   const GP<DjVuDocument> tmp_doc(DjVuDocument::create_wait(doc_url,this));
184   if (!tmp_doc->is_init_ok())
185      G_THROW( ERR_MSG("DjVuDocEditor.open_fail") "\t" +url.get_string());
186
187   orig_doc_type=tmp_doc->get_doc_type();
188   orig_doc_pages=tmp_doc->get_pages_num();
189   if (orig_doc_type==OLD_BUNDLED ||
190       orig_doc_type==OLD_INDEXED ||
191       orig_doc_type==SINGLE_PAGE)
192   {
193         // Suxx. I need to convert it NOW.
194         // We will unlink this file in the destructor
195      tmp_doc_url=GURL::Filename::Native(tmpnam(0));
196      const GP<ByteStream> gstr(ByteStream::create(tmp_doc_url, "wb"));
197      tmp_doc->write(gstr, true);        // Force DJVM format
198      gstr->flush();
199      doc_pool=DataPool::create(tmp_doc_url);
200   }
201
202      // OK. Now doc_pool contains data of the document in one of the
203      // new formats. It will be a lot easier to insert/delete pages now.
204
205      // 'doc_url' below of course doesn't refer to the file with the converted
206      // data, but we will take care of it by redirecting the request_data().
207   initialized=true;
208   DjVuDocument::init(doc_url, this);
209
210      // Cool. Now extract the thumbnails...
211   GCriticalSectionLock lock(&thumb_lock);
212   int pages_num=get_pages_num();
213   for(int page_num=0;page_num<pages_num;page_num++)
214   {
215         // Call DjVuDocument::get_thumbnail() here to bypass logic
216         // of DjVuDocEditor::get_thumbnail(). init() is the only safe
217         // place where we can still call DjVuDocument::get_thumbnail();
218      const GP<DataPool> pool(DjVuDocument::get_thumbnail(page_num, true));
219      if (pool)
220      {
221        thumb_map[page_to_id(page_num)]=pool;
222      }
223   }
224      // And remove then from DjVmDir so that DjVuDocument
225      // does not try to use them
226   unfile_thumbnails();
227}
228
229GP<DataPool>
230DjVuDocEditor::request_data(const DjVuPort * source, const GURL & url)
231{
232   DEBUG_MSG("DjVuDocEditor::request_data(): url='" << url << "'\n");
233   DEBUG_MAKE_INDENT(3);
234
235      // Check if we have either original data or converted (to new format),
236      // if all the story is about the DjVuDocument's data
237   if (url==doc_url)
238     return doc_pool;
239
240      // Now see if we have any file matching the url
241   const GP<DjVmDir::File> frec(djvm_dir->name_to_file(url.fname()));
242   if (frec)
243   {
244      GCriticalSectionLock lock(&files_lock);
245      GPosition pos;
246      if (files_map.contains(frec->get_load_name(), pos))
247      {
248         const GP<File> f(files_map[pos]);
249         if (f->file && f->file->get_init_data_pool())
250            return f->file->get_init_data_pool();// Favor DjVuFile's knowledge
251         else if (f->pool) return f->pool;
252      }
253   }
254
255      // Finally let DjVuDocument cope with it. It may be a connected DataPool
256      // for a BUNDLED format. Or it may be a file. Anyway, it was not
257      // manually included, so it should be in the document.
258   const GP<DataPool> pool(DjVuDocument::request_data(source, url));
259
260      // We do NOT update the 'File' structure, because our rule is that
261      // we keep a separate copy of DataPool in 'File' only if it cannot
262      // be retrieved from DjVuDocument (like it has been "inserted" or
263      // corresponds to a modified file).
264   return pool;
265}
266
267void
268DjVuDocEditor::clean_files_map(void)
269      // Will go thru the map of files looking for unreferenced
270      // files or records w/o DjVuFile and DataPool.
271      // These will be modified and/or removed.
272{
273   DEBUG_MSG("DjVuDocEditor::clean_files_map() called\n");
274   DEBUG_MAKE_INDENT(3);
275
276   GCriticalSectionLock lock(&files_lock);
277
278      // See if there are too old items in the "cache", which are
279      // not referenced by anyone. If the corresponding DjVuFile has been
280      // modified, obtain the new data and replace the 'pool'. Clear the
281      // DjVuFile anyway. If both DataPool and DjVuFile are zero, remove
282      // the entry.
283   for(GPosition pos=files_map;pos;)
284   {
285      const GP<File> f(files_map[pos]);
286      if (f->file && f->file->get_count()==1)
287      {
288         DEBUG_MSG("ZEROing file '" << f->file->get_url() << "'\n");
289         if (f->file->is_modified())
290            f->pool=f->file->get_djvu_data(false);
291         f->file=0;
292      }
293      if (!f->file && !f->pool)
294      {
295         DEBUG_MSG("Removing record '" << files_map.key(pos) << "'\n");
296         GPosition this_pos=pos;
297         ++pos;
298         files_map.del(this_pos);
299      } else ++pos;
300   }
301}
302
303GP<DjVuFile>
304DjVuDocEditor::url_to_file(const GURL & url, bool dont_create) const
305{
306   DEBUG_MSG("DjVuDocEditor::url_to_file(): url='" << url << "'\n");
307   DEBUG_MAKE_INDENT(3);
308
309      // Check if have a DjVuFile with this url cached (created before
310      // and either still active or left because it has been modified)
311   GP<DjVmDir::File> frec;
312   if((const DjVmDir *)djvm_dir)
313     frec=djvm_dir->name_to_file(url.fname());
314   if (frec)
315   {
316      GCriticalSectionLock lock(&(const_cast<DjVuDocEditor *>(this)->files_lock));
317      GPosition pos;
318      if (files_map.contains(frec->get_load_name(), pos))
319      {
320         const GP<File> f(files_map[pos]);
321         if (f->file)
322           return f->file;
323      }
324   }
325
326   const_cast<DjVuDocEditor *>(this)->clean_files_map();
327
328      // We don't have the file cached. Let DjVuDocument create the file.
329   const GP<DjVuFile> file(DjVuDocument::url_to_file(url, dont_create));
330
331      // And add it to our private "cache"
332   if (file && frec)
333   {
334      GCriticalSectionLock lock(&(const_cast<DjVuDocEditor *>(this)->files_lock));
335      GPosition pos;
336      if (files_map.contains(frec->get_load_name(), pos))
337      {
338         files_map[frec->get_load_name()]->file=file;
339      }else
340      {
341         const GP<File> f(new File());
342         f->file=file;
343         const_cast<DjVuDocEditor *>(this)->files_map[frec->get_load_name()]=f;
344      }
345   }
346
347   return file;
348}
349
350GUTF8String
351DjVuDocEditor::page_to_id(int page_num) const
352{
353   if (page_num<0 || page_num>=get_pages_num())
354     G_THROW( ERR_MSG("DjVuDocEditor.page_num") "\t"+GUTF8String(page_num));
355   const GP<DjVmDir::File> f(djvm_dir->page_to_file(page_num));
356   if (! f)
357     G_THROW( ERR_MSG("DjVuDocEditor.page_num") "\t"+GUTF8String(page_num));
358
359   return f->get_load_name();
360}
361
362GUTF8String
363DjVuDocEditor::find_unique_id(GUTF8String id)
364{
365  const GP<DjVmDir> dir(get_djvm_dir());
366
367  GUTF8String base, ext;
368  const int dot=id.rsearch('.');
369  if(dot >= 0)
370  {
371    base=id.substr(0,dot);
372    ext=id.substr(dot+1,(unsigned int)-1);
373  }else
374  {
375    base=id;
376  }
377
378  int cnt=0;
379  while (!(!dir->id_to_file(id) &&
380           !dir->name_to_file(id) &&
381           !dir->title_to_file(id)))
382  {
383     cnt++;
384     id=base+"_"+GUTF8String(cnt);
385     if (ext.length())
386       id+="."+ext;
387  }
388  return id;
389}
390
391GP<DataPool>
392DjVuDocEditor::strip_incl_chunks(const GP<DataPool> & pool_in)
393{
394   DEBUG_MSG("DjVuDocEditor::strip_incl_chunks() called\n");
395   DEBUG_MAKE_INDENT(3);
396
397   const GP<IFFByteStream> giff_in(
398     IFFByteStream::create(pool_in->get_stream()));
399
400   const GP<ByteStream> gbs_out(ByteStream::create());
401   const GP<IFFByteStream> giff_out(IFFByteStream::create(gbs_out));
402
403   IFFByteStream &iff_in=*giff_in;
404   IFFByteStream &iff_out=*giff_out;
405
406   bool have_incl=false;
407   int chksize;
408   GUTF8String chkid;
409   if (iff_in.get_chunk(chkid))
410   {
411      iff_out.put_chunk(chkid);
412      while((chksize=iff_in.get_chunk(chkid)))
413      {
414         if (chkid!="INCL")
415         {
416            iff_out.put_chunk(chkid);
417            iff_out.copy(*iff_in.get_bytestream());
418            iff_out.close_chunk();
419         } else
420         {
421           have_incl=true;
422         }
423         iff_in.close_chunk();
424      }
425      iff_out.close_chunk();
426   }
427
428   if (have_incl)
429   {
430      gbs_out->seek(0,SEEK_SET);
431      return DataPool::create(gbs_out);
432   } else return pool_in;
433}
434
435GUTF8String
436DjVuDocEditor::insert_file(const GURL &file_url, const GUTF8String &parent_id,
437                           int chunk_num, DjVuPort *source)
438      // Will open the 'file_name' and insert it into an existing DjVuFile
439      // with ID 'parent_id'. Will insert the INCL chunk at position chunk_num
440      // Will NOT process ANY files included into the file being inserted.
441      // Moreover it will strip out any INCL chunks in that file...
442{
443   DEBUG_MSG("DjVuDocEditor::insert_file(): fname='" << file_url <<
444             "', parent_id='" << parent_id << "'\n");
445   DEBUG_MAKE_INDENT(3);
446   const GP<DjVmDir> dir(get_djvm_dir());
447
448   if(!source)
449     source=this;
450      // Create DataPool and see if the file exists
451   GP<DataPool> file_pool;
452   if(file_url.is_empty()||file_url.is_local_file_url())
453   {
454     file_pool=DataPool::create(file_url);
455   }else
456   {
457     file_pool=source->request_data(source, file_url);
458     if(source != this)
459     {
460       file_pool=DataPool::create(file_pool->get_stream()->duplicate());
461     }
462   }
463   if(file_pool && file_url && DjVuDocument::djvu_import_codec)
464   {
465     (*DjVuDocument::djvu_import_codec)(file_pool,file_url,needs_compression_flag,can_compress_flag);
466   }
467
468      // Strip any INCL chunks
469   file_pool=strip_incl_chunks(file_pool);
470
471      // Check if parent ID is valid
472   GP<DjVmDir::File> parent_frec(dir->id_to_file(parent_id));
473   if (!parent_frec)
474     parent_frec=dir->name_to_file(parent_id);
475   if (!parent_frec)
476     parent_frec=dir->title_to_file(parent_id);
477   if (!parent_frec)
478     G_THROW( ERR_MSG("DjVuDocEditor.no_file") "\t" +parent_id);
479   const GP<DjVuFile> parent_file(get_djvu_file(parent_id));
480   if (!parent_file)
481     G_THROW( ERR_MSG("DjVuDocEditor.create_fail") "\t"+parent_id);
482
483      // Now obtain ID for the new file
484   const GUTF8String id(find_unique_id(file_url.fname()));
485
486      // Add it into the directory
487   const GP<DjVmDir::File> frec(
488     DjVmDir::File::create(id, id, id, DjVmDir::File::INCLUDE));
489   int pos=dir->get_file_pos(parent_frec);
490   if (pos>=0)
491     ++pos;
492   dir->insert_file(frec, pos);
493
494      // Add it to our "cache"
495   {
496      const GP<File> f(new File);
497      f->pool=file_pool;
498      GCriticalSectionLock lock(&files_lock);
499      files_map[id]=f;
500   }
501
502      // And insert it into the parent DjVuFile
503   parent_file->insert_file(id, chunk_num);
504
505   return id;
506}
507
508      // First it will insert the 'file_url' at position 'file_pos'.
509      //
510      // Then it will process all the INCL chunks in the file and try to do
511      // the same thing with the included files. If insertion of an included
512      // file fails, it will proceed with other INCL chunks until it does
513      // them all. In the very end we will throw exception to let the caller
514      // know about problems with included files.
515      //
516      // If the name of a file being inserted conflicts with some other
517      // name, which has been in DjVmDir prior to call to this function,
518      // it will be modified. name2id is the translation table to
519      // keep track of these modifications.
520      //
521      // Also, if a name is in name2id, we will not insert that file again.
522      //
523      // Will return TRUE if the file has been successfully inserted.
524      // FALSE, if the file contains NDIR chunk and has been skipped.
525bool
526DjVuDocEditor::insert_file(const GURL &file_url, bool is_page,
527  int & file_pos, GMap<GUTF8String, GUTF8String> & name2id,
528  DjVuPort *source)
529{
530
531  DEBUG_MSG("DjVuDocEditor::insert_file(): file_url='" << file_url <<
532             "', is_page='" << is_page << "'\n");
533  DEBUG_MAKE_INDENT(3);
534  if (refresh_cb)
535    refresh_cb(refresh_cl_data);
536
537
538      // We do not want to insert the same file twice (important when
539      // we insert a group of files at the same time using insert_group())
540      // So we check if we already did that and return if so.
541  if (name2id.contains(file_url.fname()))
542    return true;
543
544  if(!source)
545    source=this;
546
547  GP<DataPool> file_pool;
548  if(file_url.is_empty()||file_url.is_local_file_url())
549  {
550    file_pool=DataPool::create(file_url);
551  }
552  else
553  {
554    file_pool=source->request_data(source, file_url);
555    if(source != this)
556    {
557      file_pool=DataPool::create(file_pool->get_stream());
558    }
559  }
560       // Create DataPool and see if the file exists
561  if(file_pool && !file_url.is_empty() && DjVuDocument::djvu_import_codec)
562  {
563      (*DjVuDocument::djvu_import_codec)(file_pool,file_url,
564                                         needs_compression_flag,
565                                         can_compress_flag);
566  }
567
568         // Oh. It does exist... Check that it has IFF structure
569  {
570       const GP<IFFByteStream> giff(
571         IFFByteStream::create(file_pool->get_stream()));
572       IFFByteStream &iff=*giff;
573       GUTF8String chkid;
574
575       int length;
576       length=iff.get_chunk(chkid);
577       if (chkid!="FORM:DJVI" && chkid!="FORM:DJVU" &&
578         chkid!="FORM:BM44" && chkid!="FORM:PM44")
579       G_THROW( ERR_MSG("DjVuDocEditor.not_1_page") "\t"+file_url.get_string());
580
581       // Wonderful. It's even a DjVu file. Scan for NDIR chunks.
582       // If NDIR chunk is found, ignore the file
583       while(iff.get_chunk(chkid))
584       {
585         if (chkid=="NDIR")
586           return false;
587         iff.close_chunk();
588       }
589  }
590  return insert_file(file_pool,file_url,is_page,file_pos,name2id,source);
591}
592
593bool
594DjVuDocEditor::insert_file(const GP<DataPool> &file_pool,
595  const GURL &file_url, bool is_page,
596  int & file_pos, GMap<GUTF8String, GUTF8String> & name2id,
597  DjVuPort *source)
598{
599  GUTF8String errors;
600  if(file_pool)
601  {
602    const GP<DjVmDir> dir(get_djvm_dir());
603    G_TRY
604    {
605         // Now get a unique name for this file.
606         // Check the name2id first...
607      const GUTF8String name=file_url.fname();
608      GUTF8String id;
609      if (name2id.contains(name))
610      {
611        id=name2id[name];
612      }else
613      {
614           // Check to see if this page exists with a different name.
615        if(!is_page)
616        {
617          GPList<DjVmDir::File> list(dir->get_files_list());
618          for(GPosition pos=list;pos;++pos)
619          {
620            DEBUG_MSG("include " << list[pos]->is_include() 
621                      << " size=" << list[pos]->size << " length=" 
622                      << file_pool->get_length() << "\n");
623            if(list[pos]->is_include() 
624               && (!list[pos]->size
625                   || (list[pos]->size == file_pool->get_length())))
626            {
627              id=list[pos]->get_load_name();
628              GP<DjVuFile> file(get_djvu_file(id,false));
629              const GP<DataPool> pool(file->get_djvu_data(false));
630              if(file_pool->simple_compare(*pool))
631              {
632                // The files are the same, so just store the alias.
633                name2id[name]=id;
634              }
635              const GP<IFFByteStream> giff_old(IFFByteStream::create(pool->get_stream()));
636              const GP<IFFByteStream> giff_new(IFFByteStream::create(file_pool->get_stream()));
637              file=0;
638              if(giff_old->compare(*giff_new))
639              {
640                // The files are the same, so just store the alias.
641                name2id[name]=id;
642                return true;
643              }
644            } 
645          }
646        }
647        // Otherwise create a new unique ID and remember the translation
648        id=find_unique_id(name);
649        name2id[name]=id;
650      }
651
652         // Good. Before we continue with the included files we want to
653         // complete insertion of this one. Notice, that insertion of
654         // children may fail, in which case we will have to modify
655         // data for this file to get rid of invalid INCL
656
657         // Create a file record with the chosen ID
658      const GP<DjVmDir::File> file(DjVmDir::File::create(id, id, id,
659        is_page ? DjVmDir::File::PAGE : DjVmDir::File::INCLUDE ));
660
661         // And insert it into the directory
662      file_pos=dir->insert_file(file, file_pos);
663
664         // And add the File record (containing the file URL and DataPool)
665      {
666         const GP<File> f(new File);
667         f->pool=file_pool;
668         GCriticalSectionLock lock(&files_lock);
669         files_map[id]=f;
670      }
671
672         // The file has been added. If it doesn't include anything else,
673         // that will be enough. Otherwise repeat what we just did for every
674         // included child. Don't forget to modify the contents of INCL
675         // chunks due to name2id translation.
676         // We also want to include here our file with shared annotations,
677         // if it exists.
678      GUTF8String chkid;
679      const GP<IFFByteStream> giff_in(
680        IFFByteStream::create(file_pool->get_stream()));
681      IFFByteStream &iff_in=*giff_in;
682      const GP<ByteStream> gstr_out(ByteStream::create());
683      const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
684      IFFByteStream &iff_out=*giff_out;
685
686      const GP<DjVmDir::File> shared_frec(djvm_dir->get_shared_anno_file());
687
688      iff_in.get_chunk(chkid);
689      iff_out.put_chunk(chkid);
690      while(iff_in.get_chunk(chkid))
691      {
692         if (chkid!="INCL")
693         {
694            iff_out.put_chunk(chkid);
695            iff_out.copy(*iff_in.get_bytestream());
696            iff_in.close_chunk();
697            iff_out.close_chunk();
698            if (shared_frec && chkid=="INFO")
699            {
700               iff_out.put_chunk("INCL");
701               iff_out.get_bytestream()->writestring(shared_frec->get_load_name());
702               iff_out.close_chunk();
703            }
704         } else
705         {
706            GUTF8String name;
707            char buffer[1024];
708            int length;
709            while((length=iff_in.read(buffer, 1024)))
710               name+=GUTF8String(buffer, length);
711            while(isspace(name[0]))
712            {
713              name=name.substr(1,(unsigned int)-1);
714            }
715            while(isspace(name[(int)name.length()-1]))
716            {
717              name.setat(name.length()-1, 0);
718            }
719            const GURL::UTF8 full_url(name,file_url.base());
720            iff_in.close_chunk();
721
722            G_TRY {
723               if (insert_file(full_url, false, file_pos, name2id, source))
724               {
725                     // If the child file has been inserted (doesn't
726                     // contain NDIR chunk), add INCL chunk.
727                  GUTF8String id=name2id[name];
728                  iff_out.put_chunk("INCL");
729                  iff_out.get_bytestream()->writestring(id);
730                  iff_out.close_chunk();
731               }
732            } G_CATCH(exc) {
733                  // Should an error occur, we move on. INCL chunk will
734                  // not be copied.
735               if (errors.length())
736                 errors+="\n\n";
737               errors+=exc.get_cause();
738            } G_ENDCATCH;
739         }
740      } // while(iff_in.get_chunk(chkid))
741      iff_out.close_chunk();
742
743      // Increment the file_pos past the page inserted.
744      if (file_pos>=0) file_pos++;
745
746         // We have just inserted every included file. We may have modified
747         // contents of the INCL chunks. So we need to update the DataPool...
748      gstr_out->seek(0);
749      const GP<DataPool> new_file_pool(DataPool::create(gstr_out));
750      {
751            // It's important that we replace the pool here anyway.
752            // By doing this we load the file into memory. And this is
753            // exactly what insert_group() wants us to do because
754            // it creates temporary files.
755         GCriticalSectionLock lock(&files_lock);
756         files_map[id]->pool=new_file_pool;
757      }
758    } G_CATCH(exc) {
759      if (errors.length())
760        errors+="\n\n";
761      errors+=exc.get_cause();
762      G_THROW(errors);
763    } G_ENDCATCH;
764
765      // The only place where we intercept exceptions is when we process
766      // included files. We want to process all of them even if we failed to
767      // process one. But here we need to let the exception propagate...
768    if (errors.length())
769      G_THROW(errors);
770
771    return true;
772  }
773  return false;
774}
775
776void
777DjVuDocEditor::insert_group(const GList<GURL> & file_urls, int page_num,
778                             void (* _refresh_cb)(void *), void * _cl_data)
779      // The function will insert every file from the list at position
780      // corresponding to page_num. If page_num is negative, concatenation
781      // will occur. Included files will be processed as well
782{
783  refresh_cb=_refresh_cb;
784  refresh_cl_data=_cl_data;
785
786  G_TRY
787  {
788
789     // First translate the page_num to file_pos.
790    const GP<DjVmDir> dir(get_djvm_dir());
791    int file_pos;
792    if (page_num<0 || page_num>=dir->get_pages_num())
793    {
794      file_pos=-1;
795    }
796    else
797    {
798      file_pos=dir->get_page_pos(page_num);
799    }
800
801       // Now call the insert_file() for every page. We will remember the
802       // name2id translation table. Thus insert_file() will remember IDs
803       // it assigned to shared files
804    GMap<GUTF8String, GUTF8String> name2id;
805
806    GUTF8String errors;
807    for(GPosition pos=file_urls;pos;++pos)
808    {
809      const GURL &furl=file_urls[pos];
810      DEBUG_MSG( "Inserting file '" << furl << "'\n" );
811      G_TRY
812      {
813               // Check if it's a multipage document...
814        GP<DataPool> xdata_pool(DataPool::create(furl));
815        if(xdata_pool && furl.is_valid()
816           && furl.is_local_file_url() && DjVuDocument::djvu_import_codec)
817        {
818          (*DjVuDocument::djvu_import_codec)(xdata_pool,furl,
819                                             needs_compression_flag,
820                                             can_compress_flag);
821        }
822        GUTF8String chkid;
823        IFFByteStream::create(xdata_pool->get_stream())->get_chunk(chkid);
824        if (name2id.contains(furl.fname())||(chkid=="FORM:DJVM"))
825        {
826          GMap<GUTF8String,void *> map;
827          map_ids(map);
828          DEBUG_MSG("Read DjVuDocument furl='" << furl << "'\n");
829          GP<ByteStream> gbs(ByteStream::create());
830          GP<DjVuDocument> doca(DjVuDocument::create_noinit());
831          doca->set_verbose_eof(verbose_eof);
832          doca->set_recover_errors(recover_errors);
833          doca->init(furl /* ,this */ );
834          doca->wait_for_complete_init();
835          get_portcaster()->add_route(doca,this);
836          DEBUG_MSG("Saving DjVuDocument url='" << furl << "' with unique names\n");
837          doca->write(gbs,map);
838          gbs->seek(0L);
839          DEBUG_MSG("Loading unique names\n");
840          GP<DjVuDocument> doc(DjVuDocument::create(gbs));
841          doc->set_verbose_eof(verbose_eof);
842          doc->set_recover_errors(recover_errors);
843          doc->wait_for_complete_init();
844          get_portcaster()->add_route(doc,this);
845          gbs=0;
846          DEBUG_MSG("Inserting pages\n");
847          int pages_num=doc->get_pages_num();
848          for(int page_num=0;page_num<pages_num;page_num++)
849          {
850            const GURL url(doc->page_to_url(page_num));
851            insert_file(url, true, file_pos, name2id, doc);
852          }
853        }
854        else
855        {
856          insert_file(furl, true, file_pos, name2id, this);
857        }
858      } G_CATCH(exc)
859      {
860        if (errors.length())
861        {
862          errors+="\n\n";
863        }
864        errors+=exc.get_cause();
865      }
866      G_ENDCATCH;
867    }
868    if (errors.length())
869    {
870      G_THROW(errors);
871    }
872  } G_CATCH_ALL
873  {
874    refresh_cb=0;
875    refresh_cl_data=0;
876    G_RETHROW;
877  } G_ENDCATCH;
878  refresh_cb=0;
879  refresh_cl_data=0;
880}
881
882void
883DjVuDocEditor::insert_page(const GURL &file_url, int page_num)
884{
885   DEBUG_MSG("DjVuDocEditor::insert_page(): furl='" << file_url << "'\n");
886   DEBUG_MAKE_INDENT(3);
887
888   GList<GURL> list;
889   list.append(file_url);
890
891   insert_group(list, page_num);
892}
893
894void
895DjVuDocEditor::insert_page(GP<DataPool> & _file_pool,
896                           const GURL & file_url, int page_num)
897      // Use _file_pool as source of data, create a new DjVuFile
898      // with name file_name, and insert it as page number page_num
899{
900   DEBUG_MSG("DjVuDocEditor::insert_page(): pool size='" <<
901             _file_pool->get_size() << "'\n");
902   DEBUG_MAKE_INDENT(3);
903
904   const GP<DjVmDir> dir(get_djvm_dir());
905
906      // Strip any INCL chunks (we do not allow to insert hierarchies
907      // using this function)
908   const GP<DataPool> file_pool(strip_incl_chunks(_file_pool));
909   
910      // Now obtain ID for the new file
911   const GUTF8String id(find_unique_id(file_url.fname()));
912
913      // Add it into the directory
914   const GP<DjVmDir::File> frec(DjVmDir::File::create(
915     id, id, id, DjVmDir::File::PAGE));
916   int pos=dir->get_page_pos(page_num);
917   dir->insert_file(frec, pos);
918
919      // Add it to our "cache"
920   {
921      GP<File> f=new File;
922      f->pool=file_pool;
923      GCriticalSectionLock lock(&files_lock);
924      files_map[id]=f;
925   }
926}
927
928void
929DjVuDocEditor::generate_ref_map(const GP<DjVuFile> & file,
930                                GMap<GUTF8String, void *> & ref_map,
931                                GMap<GURL, void *> & visit_map)
932      // This private function is used to generate a list (implemented as map)
933      // of files referencing the given file. To get list of all parents
934      // for file with ID 'id' iterate map obtained as
935      // *((GMap<GUTF8String, void *> *) ref_map[id])
936{
937   const GURL url=file->get_url();
938   const GUTF8String id(djvm_dir->name_to_file(url.fname())->get_load_name());
939   if (!visit_map.contains(url))
940   {
941      visit_map[url]=0;
942
943      GPList<DjVuFile> files_list=file->get_included_files(false);
944      for(GPosition pos=files_list;pos;++pos)
945      {
946         GP<DjVuFile> child_file=files_list[pos];
947            // First: add the current file to the list of parents for
948            // the child being processed
949         GURL child_url=child_file->get_url();
950         const GUTF8String child_id(
951           djvm_dir->name_to_file(child_url.fname())->get_load_name());
952         GMap<GUTF8String, void *> * parents=0;
953         if (ref_map.contains(child_id))
954            parents=(GMap<GUTF8String, void *> *) ref_map[child_id];
955         else
956            ref_map[child_id]=parents=new GMap<GUTF8String, void *>();
957         (*parents)[id]=0;
958            // Second: go recursively
959         generate_ref_map(child_file, ref_map, visit_map);
960      }
961   }
962}
963
964void
965DjVuDocEditor::remove_file(const GUTF8String &id, bool remove_unref,
966                           GMap<GUTF8String, void *> & ref_map)
967      // Private function, which will remove file with ID id.
968      //
969      // If will also remove all INCL chunks in parent files pointing
970      // to this one
971      //
972      // Finally, if remove_unref is TRUE, we will go down the files
973      // hierarchy removing every file, which becomes unreferenced.
974      //
975      // ref_map will be used to find out list of parents referencing
976      // this file (required when removing INCL chunks)
977{
978      // First get rid of INCL chunks in parents
979   GMap<GUTF8String, void *> * parents=(GMap<GUTF8String, void *> *) ref_map[id];
980   if (parents)
981   {
982      for(GPosition pos=*parents;pos;++pos)
983      {
984         const GUTF8String parent_id((*parents).key(pos));
985         const GP<DjVuFile> parent(get_djvu_file(parent_id));
986         if (parent)
987           parent->unlink_file(id);
988      }
989      delete parents;
990      parents=0;
991      ref_map.del(id);
992   }
993
994      // We will accumulate errors here.
995   GUTF8String errors;
996
997      // Now modify the ref_map and process children if necessary
998   GP<DjVuFile> file=get_djvu_file(id);
999   if (file)
1000   {
1001      G_TRY {
1002         GPList<DjVuFile> files_list=file->get_included_files(false);
1003         for(GPosition pos=files_list;pos;++pos)
1004         {
1005            GP<DjVuFile> child_file=files_list[pos];
1006            GURL child_url=child_file->get_url();
1007            const GUTF8String child_id(
1008              djvm_dir->name_to_file(child_url.fname())->get_load_name());
1009            GMap<GUTF8String, void *> * parents=(GMap<GUTF8String, void *> *) ref_map[child_id];
1010            if (parents) parents->del(id);
1011
1012            if (remove_unref && (!parents || !parents->size()))
1013               remove_file(child_id, remove_unref, ref_map);
1014         }
1015      } G_CATCH(exc) {
1016         if (errors.length()) errors+="\n\n";
1017         errors+=exc.get_cause();
1018      } G_ENDCATCH;
1019   }
1020
1021      // Finally remove this file from the directory.
1022   djvm_dir->delete_file(id);
1023
1024      // And get rid of its thumbnail, if any
1025   GCriticalSectionLock lock(&thumb_lock);
1026   GPosition pos(thumb_map.contains(id));
1027   if (pos)
1028   {
1029     thumb_map.del(pos);
1030   }
1031   if (errors.length())
1032     G_THROW(errors);
1033}
1034
1035void
1036DjVuDocEditor::remove_file(const GUTF8String &id, bool remove_unref)
1037{
1038   DEBUG_MSG("DjVuDocEditor::remove_file(): id='" << id << "'\n");
1039   DEBUG_MAKE_INDENT(3);
1040
1041   if (!djvm_dir->id_to_file(id))
1042      G_THROW( ERR_MSG("DjVuDocEditor.no_file") "\t"+id);
1043
1044      // First generate a map of references (containing the list of parents
1045      // including this particular file. This will speed things up
1046      // significatly.
1047   GMap<GUTF8String, void *> ref_map;        // GMap<GUTF8String, GMap<GUTF8String, void *> *> in fact
1048   GMap<GURL, void *> visit_map;        // To avoid loops
1049
1050   int pages_num=djvm_dir->get_pages_num();
1051   for(int page_num=0;page_num<pages_num;page_num++)
1052      generate_ref_map(get_djvu_file(page_num), ref_map, visit_map);
1053
1054      // Now call the function, which will do the removal recursively
1055   remove_file(id, remove_unref, ref_map);
1056
1057      // And clear the ref_map
1058   GPosition pos;
1059   while((pos=ref_map))
1060   {
1061      GMap<GUTF8String, void *> * parents=(GMap<GUTF8String, void *> *) ref_map[pos];
1062      delete parents;
1063      ref_map.del(pos);
1064   }
1065}
1066
1067void
1068DjVuDocEditor::remove_page(int page_num, bool remove_unref)
1069{
1070   DEBUG_MSG("DjVuDocEditor::remove_page(): page_num=" << page_num << "\n");
1071   DEBUG_MAKE_INDENT(3);
1072
1073      // Translate the page_num to ID
1074   GP<DjVmDir> djvm_dir=get_djvm_dir();
1075   if (page_num<0 || page_num>=djvm_dir->get_pages_num())
1076      G_THROW( ERR_MSG("DjVuDocEditor.bad_page") "\t"+GUTF8String(page_num));
1077
1078      // And call general remove_file()
1079   remove_file(djvm_dir->page_to_file(page_num)->get_load_name(), remove_unref);
1080}
1081
1082void
1083DjVuDocEditor::remove_pages(const GList<int> & page_list, bool remove_unref)
1084{
1085   DEBUG_MSG("DjVuDocEditor::remove_pages() called\n");
1086   DEBUG_MAKE_INDENT(3);
1087
1088      // First we need to translate page numbers to IDs (they will
1089      // obviously be changing while we're removing pages one after another)
1090   GP<DjVmDir> djvm_dir=get_djvm_dir();
1091   GPosition pos ;
1092   if (djvm_dir)
1093   {
1094      GList<GUTF8String> id_list;
1095      for(pos=page_list;pos;++pos)
1096      {
1097         GP<DjVmDir::File> frec=djvm_dir->page_to_file(page_list[pos]);
1098         if (frec)
1099            id_list.append(frec->get_load_name());
1100      }
1101
1102      for(pos=id_list;pos;++pos)
1103      {
1104         GP<DjVmDir::File> frec=djvm_dir->id_to_file(id_list[pos]);
1105         if (frec)
1106            remove_page(frec->get_page_num(), remove_unref);
1107      }
1108   }
1109}
1110
1111void
1112DjVuDocEditor::move_file(const GUTF8String &id, int & file_pos,
1113                         GMap<GUTF8String, void *> & map)
1114      // NOTE! file_pos here is the desired position in DjVmDir *after*
1115      // the record with ID 'id' is removed.
1116{
1117   if (!map.contains(id))
1118   {
1119      map[id]=0;
1120
1121      GP<DjVmDir::File> file_rec=djvm_dir->id_to_file(id);
1122      if (file_rec)
1123      {
1124         file_rec=new DjVmDir::File(*file_rec);
1125         djvm_dir->delete_file(id);
1126         djvm_dir->insert_file(file_rec, file_pos);
1127
1128         if (file_pos>=0)
1129         {
1130            file_pos++;
1131       
1132               // We care to move included files only if we do not append
1133               // This is because the only reason why we move included
1134               // files is to made them available sooner than they would
1135               // be available if we didn't move them. By appending files
1136               // we delay the moment when the data for the file becomes
1137               // available, of course.
1138            GP<DjVuFile> djvu_file=get_djvu_file(id);
1139            if (djvu_file)
1140            {
1141               GPList<DjVuFile> files_list=djvu_file->get_included_files(false);
1142               for(GPosition pos=files_list;pos;++pos)
1143               {
1144                  const GUTF8String name(files_list[pos]->get_url().fname());
1145                  GP<DjVmDir::File> child_frec=djvm_dir->name_to_file(name);
1146
1147                     // If the child is positioned in DjVmDir AFTER the
1148                     // file being processed (position is file_pos or greater),
1149                     // move it to file_pos position
1150                  if (child_frec)
1151                     if (djvm_dir->get_file_pos(child_frec)>file_pos)
1152                        move_file(child_frec->get_load_name(), file_pos, map);
1153               }
1154            }
1155         }
1156      }
1157   }
1158}
1159
1160void
1161DjVuDocEditor::move_page(int page_num, int new_page_num)
1162{
1163   DEBUG_MSG("DjVuDocEditor::move_page(): page_num=" << page_num <<
1164             ", new_page_num=" << new_page_num << "\n");
1165   DEBUG_MAKE_INDENT(3);
1166
1167   if (page_num==new_page_num) return;
1168
1169   int pages_num=get_pages_num();
1170   if (page_num<0 || page_num>=pages_num)
1171      G_THROW( ERR_MSG("DjVuDocEditor.bad_page") "\t"+GUTF8String(page_num));
1172
1173   const GUTF8String id(page_to_id(page_num));
1174   int file_pos=-1;
1175   if (new_page_num>=0 && new_page_num<pages_num)
1176      if (new_page_num>page_num)        // Moving toward the end
1177      {
1178         if (new_page_num<pages_num-1)
1179            file_pos=djvm_dir->get_page_pos(new_page_num+1)-1;
1180      } else
1181         file_pos=djvm_dir->get_page_pos(new_page_num);
1182
1183   GMap<GUTF8String, void *> map;
1184   move_file(id, file_pos, map);
1185}
1186#ifdef _WIN32_WCE_EMULATION         // Work around odd behavior under WCE Emulation
1187#define CALLINGCONVENTION __cdecl
1188#else
1189#define CALLINGCONVENTION  /* */
1190#endif
1191
1192static int
1193CALLINGCONVENTION
1194cmp(const void * ptr1, const void * ptr2)
1195{
1196   int num1=*(int *) ptr1;
1197   int num2=*(int *) ptr2;
1198   return num1<num2 ? -1 : num1>num2 ? 1 : 0;
1199}
1200
1201static GList<int>
1202sortList(const GList<int> & list)
1203{
1204   GArray<int> a(list.size()-1);
1205   int cnt;
1206   GPosition pos;
1207   for(pos=list, cnt=0;pos;++pos, cnt++)
1208      a[cnt]=list[pos];
1209
1210   qsort((int *) a, a.size(), sizeof(int), cmp);
1211
1212   GList<int> l;
1213   for(int i=0;i<a.size();i++)
1214      l.append(a[i]);
1215
1216   return l;
1217}
1218
1219void
1220DjVuDocEditor::move_pages(const GList<int> & _page_list, int shift)
1221{
1222   if (!shift) return;
1223
1224   GList<int> page_list=sortList(_page_list);
1225
1226   GList<GUTF8String> id_list;
1227   for(GPosition pos=page_list;pos;++pos)
1228   {
1229      GP<DjVmDir::File> frec=djvm_dir->page_to_file(page_list[pos]);
1230      if (frec)
1231         id_list.append(frec->get_load_name());
1232   }
1233
1234   if (shift<0)
1235   {
1236         // We have to start here from the smallest page number
1237         // We will move it according to the 'shift', and all
1238         // further moves are guaranteed not to affect its page number.
1239
1240         // We will be changing the 'min_page' to make sure that
1241         // pages moved beyond the document will still be in correct order
1242      int min_page=0;
1243      for(GPosition pos=id_list;pos;++pos)
1244      {
1245         GP<DjVmDir::File> frec=djvm_dir->id_to_file(id_list[pos]);
1246         if (frec)
1247         {
1248            int page_num=frec->get_page_num();
1249            int new_page_num=page_num+shift;
1250            if (new_page_num<min_page)
1251               new_page_num=min_page++;
1252            move_page(page_num, new_page_num);
1253         }
1254      }
1255   } else
1256   {
1257         // We have to start here from the biggest page number
1258         // We will move it according to the 'shift', and all
1259         // further moves will not affect its page number.
1260
1261         // We will be changing the 'max_page' to make sure that
1262         // pages moved beyond the document will still be in correct order
1263      int max_page=djvm_dir->get_pages_num()-1;
1264      for(GPosition pos=id_list.lastpos();pos;--pos)
1265      {
1266         GP<DjVmDir::File> frec=djvm_dir->id_to_file(id_list[pos]);
1267         if (frec)
1268         {
1269            int page_num=frec->get_page_num();
1270            int new_page_num=page_num+shift;
1271            if (new_page_num>max_page)
1272               new_page_num=max_page--;
1273            move_page(page_num, new_page_num);
1274         }
1275      }
1276   }
1277}
1278
1279void
1280DjVuDocEditor::set_file_name(const GUTF8String &id, const GUTF8String &name)
1281{
1282   DEBUG_MSG("DjVuDocEditor::set_file_name(), id='" << id << "', name='" << name << "'\n");
1283   DEBUG_MAKE_INDENT(3);
1284
1285      // It's important to get the URL now, because later (after we
1286      // change DjVmDir) id_to_url() will be returning a modified value
1287   GURL url=id_to_url(id);
1288
1289      // Change DjVmDir. It will check if the name is unique
1290   djvm_dir->set_file_name(id, name);
1291
1292      // Now find DjVuFile (if any) and rename it
1293   GPosition pos;
1294   if (files_map.contains(id, pos))
1295   {
1296      GP<File> file=files_map[pos];
1297      GP<DataPool> pool=file->pool;
1298      if (pool) pool->load_file();
1299      GP<DjVuFile> djvu_file=file->file;
1300      if (djvu_file) djvu_file->set_name(name);
1301   }
1302}
1303
1304void
1305DjVuDocEditor::set_page_name(int page_num, const GUTF8String &name)
1306{
1307   DEBUG_MSG("DjVuDocEditor::set_page_name(), page_num='" << page_num << "'\n");
1308   DEBUG_MAKE_INDENT(3);
1309
1310   if (page_num<0 || page_num>=get_pages_num())
1311      G_THROW( ERR_MSG("DjVuDocEditor.bad_page") "\t"+GUTF8String(page_num));
1312
1313   set_file_name(page_to_id(page_num), name);
1314}
1315
1316void
1317DjVuDocEditor::set_file_title(const GUTF8String &id, const GUTF8String &title)
1318{
1319   DEBUG_MSG("DjVuDocEditor::set_file_title(), id='" << id << "', title='" << title << "'\n");
1320   DEBUG_MAKE_INDENT(3);
1321
1322      // Just change DjVmDir. It will check if the title is unique
1323   djvm_dir->set_file_title(id, title);
1324}
1325
1326void
1327DjVuDocEditor::set_page_title(int page_num, const GUTF8String &title)
1328{
1329   DEBUG_MSG("DjVuDocEditor::set_page_title(), page_num='" << page_num << "'\n");
1330   DEBUG_MAKE_INDENT(3);
1331
1332   if (page_num<0 || page_num>=get_pages_num())
1333      G_THROW( ERR_MSG("DjVuDocEditor.bad_page") "\t"+GUTF8String(page_num));
1334
1335   set_file_title(page_to_id(page_num), title);
1336}
1337
1338//****************************************************************************
1339//************************** Shared annotations ******************************
1340//****************************************************************************
1341
1342void
1343DjVuDocEditor::simplify_anno(void (* progress_cb)(float progress, void *),
1344                             void * cl_data)
1345      // It's important that no decoding is done while this function
1346      // is running. Otherwise the DjVuFile's decoding routines and
1347      // this function may attempt to decode/modify a file's
1348      // annotations at the same time.
1349{
1350      // Get the name of the SHARED_ANNO file. We will not
1351      // touch that file (will not move annotations from it)
1352   GP<DjVmDir::File> shared_file=djvm_dir->get_shared_anno_file();
1353   GUTF8String shared_id;
1354   if (shared_file)
1355      shared_id=shared_file->get_load_name();
1356
1357   GList<GURL> ignore_list;
1358   if (shared_id.length())
1359      ignore_list.append(id_to_url(shared_id));
1360
1361      // First, for every page get merged (or "flatten" or "projected")
1362      // annotations and store them inside the top-level page file
1363   int pages_num=djvm_dir->get_pages_num();
1364   for(int page_num=0;page_num<pages_num;page_num++)
1365   {
1366      GP<DjVuFile> djvu_file=get_djvu_file(page_num);
1367      if (!djvu_file)
1368         G_THROW( ERR_MSG("DjVuDocEditor.page_fail") "\t"+page_num);
1369      int max_level=0;
1370      GP<ByteStream> anno;
1371      anno=djvu_file->get_merged_anno(ignore_list, &max_level);
1372      if (anno && max_level>0)
1373      {
1374            // This is the moment when we try to modify DjVuFile's annotations
1375            // Make sure, that it's not being decoded
1376         GSafeFlags & file_flags=djvu_file->get_safe_flags();
1377         GMonitorLock lock(&file_flags);
1378         while(file_flags & DjVuFile::DECODING)
1379            file_flags.wait();
1380       
1381            // Merge all chunks in one by decoding and encoding DjVuAnno
1382         const GP<DjVuAnno> dec_anno(DjVuAnno::create());
1383         dec_anno->decode(anno);
1384         const GP<ByteStream> new_anno(ByteStream::create());
1385         dec_anno->encode(new_anno);
1386         new_anno->seek(0);
1387
1388            // And store it in the file
1389         djvu_file->anno=new_anno;
1390         djvu_file->rebuild_data_pool();
1391         if ((file_flags & (DjVuFile::DECODE_OK |
1392                            DjVuFile::DECODE_FAILED |
1393                            DjVuFile::DECODE_STOPPED))==0)
1394            djvu_file->anno=0;
1395      }
1396      if (progress_cb)
1397    progress_cb((float)(page_num/2.0/pages_num), cl_data);
1398   }
1399
1400      // Now remove annotations from every file except for
1401      // the top-level page files and SHARED_ANNO file.
1402      // Unlink empty files too.
1403   GPList<DjVmDir::File> files_list=djvm_dir->get_files_list();
1404   int cnt;
1405   GPosition pos;
1406   for(pos=files_list, cnt=0;pos;++pos, cnt++)
1407   {
1408      GP<DjVmDir::File> frec=files_list[pos];
1409      if (!frec->is_page() && frec->get_load_name()!=shared_id)
1410      {
1411         GP<DjVuFile> djvu_file=get_djvu_file(frec->get_load_name());
1412         if (djvu_file)
1413         {
1414            djvu_file->remove_anno();
1415            if (djvu_file->get_chunks_number()==0)
1416               remove_file(frec->get_load_name(), true);
1417         }
1418      }
1419      if (progress_cb)
1420         progress_cb((float)(0.5+cnt/2.0/files_list.size()), cl_data);
1421   }
1422}
1423
1424void
1425DjVuDocEditor::create_shared_anno_file(void (* progress_cb)(float progress, void *),
1426                                       void * cl_data)
1427{
1428   if (djvm_dir->get_shared_anno_file())
1429      G_THROW( ERR_MSG("DjVuDocEditor.share_fail") );
1430
1431      // Prepare file with ANTa chunk inside
1432   const GP<ByteStream> gstr(ByteStream::create());
1433   const GP<IFFByteStream> giff(IFFByteStream::create(gstr));
1434   IFFByteStream &iff=*giff;
1435   iff.put_chunk("FORM:DJVI");
1436   iff.put_chunk("ANTa");
1437   iff.close_chunk();
1438   iff.close_chunk();
1439   ByteStream &str=*gstr;
1440   str.flush();
1441   str.seek(0);
1442   const GP<DataPool> file_pool(DataPool::create(gstr));
1443
1444      // Get a unique ID for the new file
1445   const GUTF8String id(find_unique_id("shared_anno.iff"));
1446
1447      // Add it into the directory
1448   GP<DjVmDir::File> frec(DjVmDir::File::create(id, id, id,
1449     DjVmDir::File::SHARED_ANNO));
1450   djvm_dir->insert_file(frec, 1);
1451
1452      // Add it to our "cache"
1453   {
1454      GP<File> f=new File;
1455      f->pool=file_pool;
1456      GCriticalSectionLock lock(&files_lock);
1457      files_map[id]=f;
1458   }
1459
1460      // Now include this shared file into every top-level page file
1461   int pages_num=djvm_dir->get_pages_num();
1462   for(int page_num=0;page_num<pages_num;page_num++)
1463   {
1464      GP<DjVuFile> djvu_file=get_djvu_file(page_num);
1465      djvu_file->insert_file(id, 1);
1466
1467      if (progress_cb)
1468         progress_cb((float) page_num/pages_num, cl_data);
1469   }
1470}
1471
1472void 
1473DjVuDocEditor::set_djvm_nav(GP<DjVmNav> n)
1474{
1475  if (n && ! n->isValidBookmark())
1476    G_THROW("Invalid bookmark data");
1477  djvm_nav = n;
1478}
1479
1480GP<DjVuFile>
1481DjVuDocEditor::get_shared_anno_file(void)
1482{
1483   GP<DjVuFile> djvu_file;
1484
1485   GP<DjVmDir::File> frec=djvm_dir->get_shared_anno_file();
1486   if (frec)
1487      djvu_file=get_djvu_file(frec->get_load_name());
1488
1489   return djvu_file;
1490}
1491
1492GP<DataPool>
1493DjVuDocEditor::get_thumbnail(int page_num, bool dont_decode)
1494      // We override DjVuDocument::get_thumbnail() here because
1495      // pages may have been shuffled and those "thumbnail file records"
1496      // from the DjVmDir do not describe things correctly.
1497      //
1498      // So, first we will check the thumb_map[] if we have a predecoded
1499      // thumbnail for the given page. If this is the case, we will
1500      // return it. Otherwise we will ask DjVuDocument to generate
1501      // this thumbnail for us.
1502{
1503   const GUTF8String id(page_to_id(page_num));
1504
1505   GCriticalSectionLock lock(&thumb_lock);
1506   const GPosition pos(thumb_map.contains(id));
1507   if (pos)
1508   {
1509         // Get the image from the map
1510      return thumb_map[pos];
1511   } else
1512   {
1513      unfile_thumbnails();
1514      return DjVuDocument::get_thumbnail(page_num, dont_decode);
1515   }
1516}
1517
1518int
1519DjVuDocEditor::get_thumbnails_num(void) const
1520{
1521   GCriticalSectionLock lock((GCriticalSection *) &thumb_lock);
1522
1523   int cnt=0;
1524   int pages_num=get_pages_num();
1525   for(int page_num=0;page_num<pages_num;page_num++)
1526   {
1527     if (thumb_map.contains(page_to_id(page_num)))
1528       cnt++;
1529   }
1530   return cnt;
1531}
1532
1533int
1534DjVuDocEditor::get_thumbnails_size(void) const
1535{
1536   DEBUG_MSG("DjVuDocEditor::remove_thumbnails(): doing it\n");
1537   DEBUG_MAKE_INDENT(3);
1538
1539   GCriticalSectionLock lock((GCriticalSection *) &thumb_lock);
1540
1541   int pages_num=get_pages_num();
1542   for(int page_num=0;page_num<pages_num;page_num++)
1543   {
1544     const GPosition pos(thumb_map.contains(page_to_id(page_num)));
1545     if (pos)
1546     {
1547       const GP<ByteStream> gstr(thumb_map[pos]->get_stream());
1548       GP<IW44Image> iwpix=IW44Image::create_decode(IW44Image::COLOR);
1549       iwpix->decode_chunk(gstr);
1550     
1551       int width=iwpix->get_width();
1552       int height=iwpix->get_height();
1553       return width<height ? width : height;
1554    }
1555  }
1556  return -1;
1557}
1558
1559void
1560DjVuDocEditor::remove_thumbnails(void)
1561{
1562   DEBUG_MSG("DjVuDocEditor::remove_thumbnails(): doing it\n");
1563   DEBUG_MAKE_INDENT(3);
1564
1565   unfile_thumbnails();
1566
1567   DEBUG_MSG("clearing thumb_map\n");
1568   GCriticalSectionLock lock(&thumb_lock);
1569   thumb_map.empty();
1570}
1571
1572void
1573DjVuDocEditor::unfile_thumbnails(void)
1574      // Will erase all "THUMBNAILS" files from DjVmDir.
1575      // This function is useful when filing thumbnails (to get rid of
1576      // those files, which currently exist: they need to be replaced
1577      // anyway) and when calling DjVuDocument::get_thumbnail() to
1578      // be sure, that it will not use wrong information from DjVmDir
1579{
1580   DEBUG_MSG("DjVuDocEditor::unfile_thumbnails(): updating DjVmDir\n");
1581   DEBUG_MAKE_INDENT(3);
1582
1583   {
1584     GCriticalSectionLock lock(&threqs_lock);
1585     threqs_list.empty();
1586   }
1587   if((const DjVmDir *)djvm_dir)
1588   {
1589     GPList<DjVmDir::File> xfiles_list=djvm_dir->get_files_list();
1590     for(GPosition pos=xfiles_list;pos;++pos)
1591     {
1592       GP<DjVmDir::File> f=xfiles_list[pos];
1593       if (f->is_thumbnails())
1594         djvm_dir->delete_file(f->get_load_name());
1595     }
1596   }
1597}
1598
1599void
1600DjVuDocEditor::file_thumbnails(void)
1601      // The purpose of this function is to create files containing
1602      // thumbnail images and register them in DjVmDir.
1603      // If some of the thumbnail images are missing, they'll
1604      // be generated with generate_thumbnails()
1605{
1606   DEBUG_MSG("DjVuDocEditor::file_thumbnails(): updating DjVmDir\n");
1607   DEBUG_MAKE_INDENT(3);
1608   unfile_thumbnails();
1609
1610      // Generate thumbnails if they're missing due to some reason.
1611   int thumb_num=get_thumbnails_num();
1612   int size=thumb_num>0 ? get_thumbnails_size() : 128;
1613   if (thumb_num!=get_pages_num())
1614   {
1615     generate_thumbnails(size);
1616   }
1617
1618   DEBUG_MSG("filing thumbnails\n");
1619
1620   GCriticalSectionLock lock(&thumb_lock);
1621
1622      // The first thumbnail file always contains only one thumbnail
1623   int ipf=1;
1624   int image_num=0;
1625   int page_num=0, pages_num=djvm_dir->get_pages_num();
1626   GP<ByteStream> str(ByteStream::create());
1627   GP<IFFByteStream> iff(IFFByteStream::create(str));
1628   iff->put_chunk("FORM:THUM");
1629   for(;;)
1630   {
1631      GUTF8String id(page_to_id(page_num));
1632      const GPosition pos(thumb_map.contains(id));
1633      if (! pos)
1634      {
1635        G_THROW( ERR_MSG("DjVuDocEditor.no_thumb") "\t"+GUTF8String(page_num));
1636      }
1637      iff->put_chunk("TH44");
1638      iff->copy(*(thumb_map[pos]->get_stream()));
1639      iff->close_chunk();
1640      image_num++;
1641      page_num++;
1642      if (image_num>=ipf || page_num>=pages_num)
1643      {
1644         int i=id.rsearch('.');
1645         if(i<=0)
1646         {
1647           i=id.length();
1648         }
1649         id=id.substr(0,i)+".thumb";
1650            // Get unique ID for this file
1651         id=find_unique_id(id);
1652
1653            // Create a file record with the chosen ID
1654         GP<DjVmDir::File> file(DjVmDir::File::create(id, id, id,
1655           DjVmDir::File::THUMBNAILS));
1656
1657            // Set correct file position (so that it will cover the next
1658            // ipf pages)
1659         int file_pos=djvm_dir->get_page_pos(page_num-image_num);
1660         djvm_dir->insert_file(file, file_pos);
1661
1662            // Now add the File record (containing the file URL and DataPool)
1663            // After we do it a simple save_as() will save the document
1664            // with the thumbnails. This is because DjVuDocument will see
1665            // the file in DjVmDir and will ask for data. We will intercept
1666            // the request for data and will provide this DataPool
1667         iff->close_chunk();
1668         str->seek(0);
1669         const GP<DataPool> file_pool(DataPool::create(str));
1670         GP<File> f=new File;
1671         f->pool=file_pool;
1672         GCriticalSectionLock lock(&files_lock);
1673         files_map[id]=f;
1674
1675            // And create new streams
1676         str=ByteStream::create();
1677         iff=IFFByteStream::create(str);
1678         iff->put_chunk("FORM:THUM");
1679         image_num=0;
1680
1681            // Reset ipf to correct value (after we stored first
1682            // "exceptional" file with thumbnail for the first page)
1683         if (page_num==1) ipf=thumbnails_per_file;
1684         if (page_num>=pages_num) break;
1685      }
1686   }
1687}
1688
1689int
1690DjVuDocEditor::generate_thumbnails(int thumb_size, int page_num)
1691{
1692   DEBUG_MSG("DjVuDocEditor::generate_thumbnails(): doing it\n");
1693   DEBUG_MAKE_INDENT(3);
1694
1695   if(page_num<(djvm_dir->get_pages_num()))
1696   {
1697      const GUTF8String id(page_to_id(page_num));
1698      if (!thumb_map.contains(id))
1699        {
1700          const GP<DjVuImage> dimg(get_page(page_num, true));
1701         
1702          GRect rect(0, 0, thumb_size, dimg->get_height()*thumb_size/dimg->get_width());
1703          GP<GPixmap> pm=dimg->get_pixmap(rect, rect, get_thumbnails_gamma());
1704          if (!pm)
1705            {
1706              const GP<GBitmap> bm(dimg->get_bitmap(rect, rect, sizeof(int)));
1707              if (bm) 
1708                pm = GPixmap::create(*bm);
1709              else
1710                pm = GPixmap::create(rect.height(), rect.width(), &GPixel::WHITE);
1711            }
1712          // Store and compress the pixmap
1713          const GP<IW44Image> iwpix(IW44Image::create_encode(*pm));
1714          const GP<ByteStream> gstr(ByteStream::create());
1715          IWEncoderParms parms;
1716          parms.slices=97;
1717          parms.bytes=0;
1718          parms.decibels=0;
1719          iwpix->encode_chunk(gstr, parms);
1720          gstr->seek(0L);
1721          thumb_map[id]=DataPool::create(gstr);
1722        }
1723      ++page_num;
1724   }
1725   else
1726   {
1727     page_num = -1;
1728   }
1729   return page_num;
1730}
1731
1732void
1733DjVuDocEditor::generate_thumbnails(int thumb_size,
1734                                   bool (* cb)(int page_num, void *),
1735                                   void * cl_data)
1736{
1737   int page_num=0;
1738   do
1739   {
1740     page_num=generate_thumbnails(thumb_size,page_num);
1741     if (cb) if (cb(page_num, cl_data)) return;
1742   } while(page_num>=0);
1743}
1744
1745static void
1746store_file(const GP<DjVmDir> & src_djvm_dir, const GP<DjVmDoc> & djvm_doc,
1747           GP<DjVuFile> & djvu_file, GMap<GURL, void *> & map)
1748{
1749   GURL url=djvu_file->get_url();
1750   if (!map.contains(url))
1751   {
1752      map[url]=0;
1753
1754         // Store included files first
1755      GPList<DjVuFile> djvu_files_list=djvu_file->get_included_files(false);
1756      for(GPosition pos=djvu_files_list;pos;++pos)
1757         store_file(src_djvm_dir, djvm_doc, djvu_files_list[pos], map);
1758
1759         // Now store contents of this file
1760      GP<DataPool> file_data=djvu_file->get_djvu_data(false);
1761      GP<DjVmDir::File> frec=src_djvm_dir->name_to_file(url.name());
1762      if (frec)
1763      {
1764         frec=new DjVmDir::File(*frec);
1765         djvm_doc->insert_file(frec, file_data, -1);
1766      }
1767   }
1768}
1769
1770void
1771DjVuDocEditor::save_pages_as(
1772  const GP<ByteStream> &str, const GList<int> & _page_list)
1773{
1774   GList<int> page_list=sortList(_page_list);
1775
1776   GP<DjVmDoc> djvm_doc=DjVmDoc::create();
1777   GMap<GURL, void *> map;
1778   for(GPosition pos=page_list;pos;++pos)
1779   {
1780      GP<DjVmDir::File> frec=djvm_dir->page_to_file(page_list[pos]);
1781      if (frec)
1782      {
1783         GP<DjVuFile> djvu_file=get_djvu_file(frec->get_load_name());
1784         if (djvu_file)
1785            store_file(djvm_dir, djvm_doc, djvu_file, map);
1786      }
1787   }
1788   djvm_doc->write(str);
1789}
1790
1791void
1792DjVuDocEditor::save_file(const GUTF8String &file_id, const GURL &codebase,
1793  const bool only_modified, GMap<GUTF8String,GUTF8String> & map)
1794{
1795  if(only_modified)
1796  {
1797    for(GPosition pos=files_map;pos;++pos)
1798    {
1799      const GP<File> file_rec(files_map[pos]);
1800      const bool file_modified=file_rec->pool ||
1801        (file_rec->file && file_rec->file->is_modified());
1802      if(!file_modified)
1803      {
1804        const GUTF8String id=files_map.key(pos);
1805        const GUTF8String save_name(djvm_dir->id_to_file(id)->get_save_name());
1806        if(id == save_name)
1807        {
1808          map[id]=id;
1809        }
1810      }
1811    }
1812  }
1813  save_file(file_id,codebase,map);
1814}
1815
1816void
1817DjVuDocEditor::save_file(
1818  const GUTF8String &file_id, const GURL &codebase,
1819  GMap<GUTF8String,GUTF8String> & map)
1820{
1821   DEBUG_MSG("DjVuDocEditor::save_file(): ID='" << file_id << "'\n");
1822   DEBUG_MAKE_INDENT(3);
1823
1824   if (!map.contains(file_id))
1825   {
1826      const GP<DjVmDir::File> file(djvm_dir->id_to_file(file_id));
1827
1828      GP<DataPool> file_pool;
1829      const GPosition pos(files_map.contains(file_id));
1830      if (pos)
1831      {
1832         const GP<File> file_rec(files_map[pos]);
1833         if (file_rec->file)
1834            file_pool=file_rec->file->get_djvu_data(false);
1835         else
1836            file_pool=file_rec->pool;
1837      }
1838
1839      if (!file_pool)
1840      {
1841         DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
1842         file_pool=pcaster->request_data(this, id_to_url(file_id));
1843      }
1844
1845      if (file_pool)
1846      {
1847         GMap<GUTF8String,GUTF8String> incl;
1848         map[file_id]=get_djvm_doc()->save_file(codebase,*file,incl,file_pool);
1849         for(GPosition pos=incl;pos;++pos)
1850         {
1851           save_file(incl.key(pos),codebase ,map);
1852         }
1853      }else
1854      {
1855        map[file_id]=file->get_save_name();
1856      }
1857   }
1858}
1859
1860void
1861DjVuDocEditor::save(void)
1862{
1863   DEBUG_MSG("DjVuDocEditor::save(): saving the file\n");
1864   DEBUG_MAKE_INDENT(3);
1865
1866   if (!can_be_saved())
1867     G_THROW( ERR_MSG("DjVuDocEditor.cant_save") );
1868   save_as(GURL(), orig_doc_type!=INDIRECT);
1869}
1870
1871void
1872DjVuDocEditor::write(const GP<ByteStream> &gbs, bool force_djvm)
1873{
1874  DEBUG_MSG("DjVuDocEditor::write()\n");
1875  DEBUG_MAKE_INDENT(3);
1876  if (get_thumbnails_num()==get_pages_num())
1877  {
1878    file_thumbnails();
1879  }else
1880  { 
1881    remove_thumbnails();
1882  }
1883  clean_files_map();
1884  DjVuDocument::write(gbs,force_djvm);
1885}
1886
1887void
1888DjVuDocEditor::write(
1889  const GP<ByteStream> &gbs,const GMap<GUTF8String,void *> &reserved)
1890{
1891  DEBUG_MSG("DjVuDocEditor::write()\n");
1892  DEBUG_MAKE_INDENT(3);
1893  if (get_thumbnails_num()==get_pages_num())
1894  {
1895    file_thumbnails();
1896  }else
1897  { 
1898    remove_thumbnails();
1899  }
1900  clean_files_map();
1901  DjVuDocument::write(gbs,reserved);
1902}
1903
1904void
1905DjVuDocEditor::save_as(const GURL &where, bool bundled)
1906{
1907   DEBUG_MSG("DjVuDocEditor::save_as(): where='" << where << "'\n");
1908   DEBUG_MAKE_INDENT(3);
1909
1910      // First see if we need to generate (or just reshuffle) thumbnails...
1911      // If we have an icon for every page, we will just call
1912      // file_thumbnails(), which will update DjVmDir and will create
1913      // the actual bundles with thumbnails (very fast)
1914      // Otherwise we will remove the thumbnails completely because
1915      // we really don't want to deal with documents, which have only
1916      // some of their pages thumbnailed.
1917   if (get_thumbnails_num()==get_pages_num())
1918   {
1919     file_thumbnails();
1920   }else
1921   { 
1922     remove_thumbnails();
1923   }
1924
1925   GURL save_doc_url;
1926
1927   if (where.is_empty())
1928   {
1929         // Assume, that we just want to 'save'. Check, that it's possible
1930         // and proceed.
1931      bool can_be_saved_bundled=orig_doc_type==BUNDLED ||
1932                                orig_doc_type==OLD_BUNDLED ||
1933                                orig_doc_type==SINGLE_PAGE ||
1934                                orig_doc_type==OLD_INDEXED && orig_doc_pages==1;
1935      if ((bundled ^ can_be_saved_bundled)!=0)
1936         G_THROW( ERR_MSG("DjVuDocEditor.cant_save2") );
1937      save_doc_url=doc_url;
1938   } else
1939   {
1940      save_doc_url=where;
1941   }
1942
1943   int save_doc_type=bundled ? BUNDLED : INDIRECT;
1944
1945   clean_files_map();
1946
1947   GCriticalSectionLock lock(&files_lock);
1948
1949   DjVuPortcaster * pcaster=DjVuPort::get_portcaster();
1950
1951      // First consider saving in SINGLE_FILE format (one file)
1952   if(needs_compression())
1953   {
1954     DEBUG_MSG("Compressing on output\n");
1955     remove_thumbnails();
1956     if(! djvu_compress_codec)
1957     {
1958       G_THROW( ERR_MSG("DjVuDocEditor.no_codec") );
1959     }
1960     const GP<DjVmDoc> doc(get_djvm_doc());
1961     GP<ByteStream> mbs(ByteStream::create());
1962     doc->write(mbs);
1963     mbs->flush();
1964     mbs->seek(0,SEEK_SET);
1965     djvu_compress_codec(mbs,save_doc_url,(!(const DjVmDir *)djvm_dir)||(djvm_dir->get_files_num()==1)||(save_doc_type!=INDIRECT));
1966     files_map.empty();
1967     doc_url=GURL();
1968   }else
1969   {
1970     if (djvm_dir->get_files_num()==1)
1971     {
1972       // Here 'bundled' has no effect: we will save it as one page.
1973       DEBUG_MSG("saving one file...\n");
1974       GURL file_url=page_to_url(0);
1975       const GUTF8String file_id(djvm_dir->page_to_file(0)->get_load_name());
1976       GP<DataPool> file_pool;
1977       GPosition pos=files_map.contains(file_id);
1978       if (pos)
1979       {
1980         const GP<File> file_rec(files_map[pos]);
1981         if (file_rec->pool && (!file_rec->file ||
1982                                !file_rec->file->is_modified()))
1983         {
1984           file_pool=file_rec->pool;
1985         }else if (file_rec->file)
1986         {
1987           file_pool=file_rec->file->get_djvu_data(false);
1988         }
1989       }
1990       // Even if file has not been modified (pool==0) we still want
1991       // to save it.
1992       if (!file_pool)
1993         file_pool=pcaster->request_data(this, file_url);
1994       if (file_pool)
1995       {
1996         DEBUG_MSG("Saving '" << file_url << "' to '" << save_doc_url << "'\n");
1997         DataPool::load_file(save_doc_url);
1998         const GP<ByteStream> gstr_out(ByteStream::create(save_doc_url, "wb"));
1999         ByteStream &str_out=*gstr_out;
2000         str_out.writall(octets, 4);
2001         const GP<ByteStream> str_in(file_pool->get_stream());
2002         str_out.copy(*str_in);
2003       }
2004
2005       // Update the document's DataPool (to save memory)
2006       const GP<DjVmDoc> doc(get_djvm_doc());
2007       const GP<ByteStream> gstr=ByteStream::create();// One page: we can do it in the memory
2008       doc->write(gstr);
2009       gstr->seek(0, SEEK_SET);
2010       const GP<DataPool> pool(DataPool::create(gstr));
2011       doc_pool=pool;
2012       init_data_pool=pool;
2013
2014         // Also update DjVmDir (to reflect changes in offsets)
2015       djvm_dir=doc->get_djvm_dir();
2016     } else if (save_doc_type==INDIRECT)
2017     {
2018       DEBUG_MSG("Saving in INDIRECT format to '" << save_doc_url << "'\n");
2019       bool save_only_modified=!(save_doc_url!=doc_url || save_doc_type!=orig_doc_type);
2020       GPList<DjVmDir::File> xfiles_list=djvm_dir->resolve_duplicates(false);
2021       const GURL codebase=save_doc_url.base();
2022       int pages_num=djvm_dir->get_pages_num();
2023       GMap<GUTF8String, GUTF8String> map;
2024       // First go thru the pages
2025       for(int page_num=0;page_num<pages_num;page_num++)
2026       {
2027         const GUTF8String id(djvm_dir->page_to_file(page_num)->get_load_name());
2028         save_file(id, codebase, save_only_modified, map);
2029       }
2030       // Next go thru thumbnails and similar stuff
2031       GPosition pos;
2032       for(pos=xfiles_list;pos;++pos)
2033         save_file(xfiles_list[pos]->get_load_name(), codebase, save_only_modified, map);
2034
2035         // Finally - save the top-level index file
2036       for(pos=xfiles_list;pos;++pos)
2037       {
2038         const GP<DjVmDir::File> file(xfiles_list[pos]);
2039         file->offset=0;
2040         file->size=0;
2041       }
2042       DataPool::load_file(save_doc_url);
2043       const GP<ByteStream> gstr(ByteStream::create(save_doc_url, "wb"));
2044       const GP<IFFByteStream> giff(IFFByteStream::create(gstr));
2045       IFFByteStream &iff=*giff;
2046
2047       iff.put_chunk("FORM:DJVM", 1);
2048       iff.put_chunk("DIRM");
2049       djvm_dir->encode(giff->get_bytestream());
2050       iff.close_chunk();
2051       iff.close_chunk();
2052       iff.flush();
2053
2054       // Update the document data pool (not required, but will save memory)
2055       doc_pool=DataPool::create(save_doc_url);
2056       init_data_pool=doc_pool;
2057
2058       // No reason to update DjVmDir as for this format it doesn't
2059       // contain DJVM offsets
2060     } else if (save_doc_type==BUNDLED || save_doc_type==OLD_BUNDLED)
2061     {
2062        DEBUG_MSG("Saving in BUNDLED format to '" << save_doc_url << "'\n");
2063
2064         // Can't be very smart here. Simply overwrite the file.
2065        const GP<DjVmDoc> doc(get_djvm_doc());
2066        DataPool::load_file(save_doc_url);
2067        const GP<ByteStream> gstr(ByteStream::create(save_doc_url, "wb"));
2068        doc->write(gstr);
2069        gstr->flush();
2070
2071         // Update the document data pool (not required, but will save memory)
2072        doc_pool=DataPool::create(save_doc_url);
2073        init_data_pool=doc_pool;
2074
2075         // Also update DjVmDir (to reflect changes in offsets)
2076        djvm_dir=doc->get_djvm_dir();
2077     } else
2078     {
2079       G_THROW( ERR_MSG("DjVuDocEditor.cant_save") );
2080     }
2081
2082        // Now, after we have saved the document w/o any error, detach DataPools,
2083        // which are in the 'File's list to save memory. Detach everything.
2084        // Even in the case when File->file is non-zero. If File->file is zero,
2085        // remove the item from the list at all. If it's non-zero, it has
2086        // to stay there because by definition files_map[] contains the list
2087        // of all active files and customized DataPools
2088        //
2089        // In addition to it, look thru all active files and change their URLs
2090        // to reflect changes in the document's URL (if there was a change)
2091        // Another reason why file's URLs must be changed is that we may have
2092        // saved the document in a different format, which changes the rules
2093        // of file url composition.
2094     for(GPosition pos=files_map;pos;)
2095     {
2096        const GP<File> file_rec(files_map[pos]);
2097        file_rec->pool=0;
2098        if (file_rec->file==0)
2099        {
2100         GPosition this_pos=pos;
2101         ++pos;
2102         files_map.del(this_pos);
2103        } else
2104        {
2105            // Change the file's url;
2106         if (doc_url!=save_doc_url ||
2107             orig_doc_type!=save_doc_type)
2108            if (save_doc_type==BUNDLED)
2109               file_rec->file->move(save_doc_url);
2110            else file_rec->file->move(save_doc_url.base());
2111         ++pos;
2112        }
2113     }
2114
2115   }
2116   orig_doc_type=save_doc_type;
2117   doc_type=save_doc_type;
2118
2119   if (doc_url!=save_doc_url)
2120   {
2121     // Also update document's URL (we moved, didn't we?)
2122     doc_url=save_doc_url;
2123     init_url=save_doc_url;
2124   }
2125}
2126
2127GP<DjVuDocEditor> 
2128DjVuDocEditor::create_wait(void)
2129{
2130  DjVuDocEditor *doc=new DjVuDocEditor();
2131  const GP<DjVuDocEditor> retval(doc);
2132  doc->init();
2133  return retval;
2134}
2135
2136GP<DjVuDocEditor> 
2137DjVuDocEditor::create_wait(const GURL &url)
2138{
2139  DjVuDocEditor *doc=new DjVuDocEditor();
2140  const GP<DjVuDocEditor> retval(doc);
2141  doc->init(url);
2142  return retval;
2143}
2144
2145bool
2146DjVuDocEditor::inherits(const GUTF8String &class_name) const
2147{
2148   return (class_name == "DjVuDocEditor")||DjVuDocument::inherits(class_name);
2149}
2150
2151int
2152DjVuDocEditor::get_orig_doc_type(void) const
2153{
2154   return orig_doc_type;
2155}
2156
2157bool
2158DjVuDocEditor::can_be_saved(void) const
2159{
2160   return !(needs_rename()||needs_compression()||orig_doc_type==UNKNOWN_TYPE ||
2161            orig_doc_type==OLD_INDEXED);
2162}
2163
2164int
2165DjVuDocEditor::get_save_doc_type(void) const
2166{
2167   if (orig_doc_type==SINGLE_PAGE)
2168      if (djvm_dir->get_files_num()==1)
2169        return SINGLE_PAGE;
2170      else
2171        return BUNDLED;
2172   else if (orig_doc_type==INDIRECT)
2173     return INDIRECT;
2174   else if (orig_doc_type==OLD_BUNDLED || orig_doc_type==BUNDLED)
2175     return BUNDLED;
2176   else
2177     return UNKNOWN_TYPE;
2178}
2179
2180GURL
2181DjVuDocEditor::get_doc_url(void) const
2182{
2183   return doc_url.is_empty() ? init_url : doc_url;
2184}
2185
2186
2187
2188#ifdef HAVE_NAMESPACES
2189}
2190# ifndef NOT_USING_DJVU_NAMESPACE
2191using namespace DJVU;
2192# endif
2193#endif
Note: See TracBrowser for help on using the repository browser.