source: trunk/libdjvu/DjVuDocEditor.cpp @ 206

Last change on this file since 206 was 206, checked in by Eugene Romanenko, 14 years ago

DJVU plugin: djvulibre updated to version 3.5.19

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