source: trunk/libdjvu/DjVuDocEditor.cpp @ 280

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

DJVU plugin: djvulibre updated to version 3.5.22

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