00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057 #ifdef HAVE_CONFIG_H
00058 # include "config.h"
00059 #endif
00060 #if NEED_GNUG_PRAGMAS
00061 # pragma implementation
00062 #endif
00063
00064 #include "DjVmDoc.h"
00065 #include "DjVmNav.h"
00066 #include "DataPool.h"
00067 #include "IFFByteStream.h"
00068 #include "GOS.h"
00069 #include "debug.h"
00070
00071
00072 #ifdef HAVE_NAMESPACES
00073 namespace DJVU {
00074 # ifdef NOT_DEFINED // Just to fool emacs c++ mode
00075 }
00076 #endif
00077 #endif
00078
00079 static const char octets[4]={0x41,0x54,0x26,0x54};
00080
00081
00082 static void
00083 save_file(
00084 IFFByteStream &iff_in, IFFByteStream &iff_out, const DjVmDir &dir,
00085 GMap<GUTF8String,GUTF8String> &incl)
00086 {
00087 GUTF8String chkid;
00088 if (iff_in.get_chunk(chkid))
00089 {
00090 iff_out.put_chunk(chkid,true);
00091 if(!chkid.cmp("FORM:",5))
00092 {
00093 for(;iff_in.get_chunk(chkid);iff_in.close_chunk())
00094 {
00095 iff_out.put_chunk(chkid);
00096 if(chkid == "INCL")
00097 {
00098 GUTF8String incl_str;
00099 char buffer[1024];
00100 int length;
00101 while((length=iff_in.read(buffer, 1024)))
00102 incl_str+=GUTF8String(buffer, length);
00103
00104 while(incl_str.length() && incl_str[0]=='\n')
00105 {
00106 incl_str=incl_str.substr(1,(unsigned int)(-1));
00107 }
00108 while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
00109 {
00110 incl_str.setat(incl_str.length()-1, 0);
00111 }
00112 GPosition pos=incl.contains(incl_str);
00113 if(pos)
00114 {
00115 iff_out.get_bytestream()->writestring(incl[pos]);
00116 }else
00117 {
00118 GP<DjVmDir::File> incl_file=dir.id_to_file(incl_str);
00119 if(incl_file)
00120 {
00121 DEBUG_MSG("INCL '"<<(const char *)incl_file->get_save_name()<<"'\n");
00122 const GUTF8String incl_name=incl_file->get_save_name();
00123 incl[incl_str]=incl_name;
00124 iff_out.get_bytestream()->writestring(incl_name);
00125 }else
00126 {
00127 DEBUG_MSG("BOGUS INCL '"<<(const char *)incl_str<<"'\n");
00128 iff_out.copy(*iff_in.get_bytestream());
00129 }
00130 }
00131 }else
00132 {
00133 iff_out.copy(*iff_in.get_bytestream());
00134 }
00135 iff_out.close_chunk();
00136 }
00137 }else
00138 {
00139 iff_out.copy(*iff_in.get_bytestream());
00140 }
00141 iff_out.close_chunk();
00142 iff_in.close_chunk();
00143 }
00144 }
00145
00146 DjVmDoc::DjVmDoc(void)
00147 {
00148 DEBUG_MSG("DjVmDoc::DjVmDoc(): Constructing empty DjVm document.\n");
00149 DEBUG_MAKE_INDENT(3);
00150 }
00151
00152 void
00153 DjVmDoc::init(void)
00154 {
00155 dir=DjVmDir::create();
00156 }
00157
00158 GP<DjVmDoc>
00159 DjVmDoc::create(void)
00160 {
00161 DjVmDoc *doc=new DjVmDoc();
00162 GP<DjVmDoc> retval=doc;
00163 doc->init();
00164 return retval;
00165 }
00166
00167 void
00168 DjVmDoc::insert_file(const GP<DjVmDir::File> & f,
00169 GP<DataPool> data_pool, int pos)
00170 {
00171 DEBUG_MSG("DjVmDoc::insert_file(): inserting file '" << f->get_load_name() <<
00172 "' at pos " << pos << "\n");
00173 DEBUG_MAKE_INDENT(3);
00174
00175 if (!f)
00176 G_THROW( ERR_MSG("DjVmDoc.no_zero_file") );
00177 if (data.contains(f->get_load_name()))
00178 G_THROW( ERR_MSG("DjVmDoc.no_duplicate") );
00179
00180 char buffer[4];
00181 if (data_pool->get_data(buffer, 0, 4)==4 && !memcmp(buffer, octets, 4))
00182 {
00183 data_pool=DataPool::create(data_pool, 4, -1);
00184 }
00185 data[f->get_load_name()]=data_pool;
00186 dir->insert_file(f, pos);
00187 }
00188
00189 void
00190 DjVmDoc::insert_file(
00191 ByteStream &data, DjVmDir::File::FILE_TYPE file_type,
00192 const GUTF8String &name, const GUTF8String &id, const GUTF8String &title,
00193 int pos)
00194 {
00195 const GP<DjVmDir::File> file(
00196 DjVmDir::File::create(name, id, title, file_type));
00197 const GP<DataPool> pool(DataPool::create());
00198
00199
00200 int nbytes;
00201 char buffer[1024];
00202 while ((nbytes = data.read(buffer, sizeof(buffer))))
00203 pool->add_data(buffer, nbytes);
00204 pool->set_eof();
00205
00206 insert_file(file, pool, pos);
00207 }
00208
00209 void
00210 DjVmDoc::insert_file(
00211 const GP<DataPool> &pool, DjVmDir::File::FILE_TYPE file_type,
00212 const GUTF8String &name, const GUTF8String &id, const GUTF8String &title,
00213 int pos)
00214 {
00215 const GP<DjVmDir::File> file(
00216 DjVmDir::File::create(name, id, title, file_type));
00217
00218 insert_file(file, pool, pos);
00219 }
00220
00221 void
00222 DjVmDoc::delete_file(const GUTF8String &id)
00223 {
00224 DEBUG_MSG("DjVmDoc::delete_file(): deleting file '" << id << "'\n");
00225 DEBUG_MAKE_INDENT(3);
00226
00227 if (!data.contains(id))
00228 G_THROW(GUTF8String( ERR_MSG("DjVmDoc.cant_delete") "\t") + id);
00229
00230 data.del(id);
00231 dir->delete_file(id);
00232 }
00233
00234 void
00235 DjVmDoc::set_djvm_nav(GP<DjVmNav> n)
00236 {
00237 if (n && ! n->isValidBookmark())
00238 G_THROW("Invalid bookmark data");
00239 nav = n;
00240 }
00241
00242 GP<DataPool>
00243 DjVmDoc::get_data(const GUTF8String &id) const
00244 {
00245 GPosition pos;
00246 if (!data.contains(id, pos))
00247 G_THROW(GUTF8String( ERR_MSG("DjVmDoc.cant_find") "\t") + id);
00248 const GP<DataPool> pool(data[pos]);
00249
00250 G_TRY
00251 {
00252 const GP<ByteStream> str_in(pool->get_stream());
00253 const GP<IFFByteStream> giff_in=IFFByteStream::create(str_in);
00254 IFFByteStream &iff_in=*giff_in;
00255 GUTF8String chkid;
00256 int size=iff_in.get_chunk(chkid);
00257 if (size<0 || size>0x7fffffff)
00258 G_THROW( ERR_MSG("DjVmDoc.not_IFF") "\t" + id);
00259 }
00260 G_CATCH_ALL
00261 {
00262 G_THROW( ERR_MSG("DjVmDoc.not_IFF") "\t" + id);
00263 }
00264 G_ENDCATCH;
00265 return pool;
00266 }
00267
00268 void
00269 DjVmDoc::write(const GP<ByteStream> &gstr)
00270 {
00271 const GMap<GUTF8String,void *> reserved;
00272 write(gstr,reserved);
00273 }
00274
00275 static inline GUTF8String
00276 get_name(const DjVmDir::File &file)
00277 {
00278 const GUTF8String save_name(file.get_save_name());
00279 return save_name.length()?save_name:(file.get_load_name());
00280 }
00281
00282 void
00283 DjVmDoc::write(const GP<ByteStream> &gstr,
00284 const GMap<GUTF8String,void *> &reserved)
00285 {
00286 DEBUG_MSG("DjVmDoc::write(): Storing document into the byte stream.\n");
00287 DEBUG_MAKE_INDENT(3);
00288
00289 GPList<DjVmDir::File> files_list=dir->resolve_duplicates(true);
00290 bool do_rename=false;
00291 GPosition pos(reserved);
00292
00293 GMap<GUTF8String,GUTF8String> incl;
00294 DEBUG_MSG("pass 1: looking for reserved names.");
00295 if(pos)
00296 {
00297
00298 for(pos=files_list;pos;++pos)
00299 {
00300 GP<DjVmDir::File> file=files_list[pos];
00301 if((do_rename=(reserved.contains(file->get_load_name())?true:false))
00302 ||(do_rename=(reserved.contains(file->get_save_name())?true:false)))
00303 {
00304 break;
00305 }
00306 }
00307
00308
00309 if(do_rename)
00310 {
00311 DEBUG_MSG("pass 1: renaming reserved names.");
00312 for(;;files_list=dir->resolve_duplicates(true))
00313 {
00314 GMap<GUTF8String,void *> this_doc;
00315 for(pos=files_list;pos;++pos)
00316 {
00317 GP<DjVmDir::File> file=files_list[pos];
00318 this_doc[::get_name(*file)]=0;
00319 }
00320 bool need_new_list=false;
00321 for(pos=files_list;pos;++pos)
00322 {
00323 GP<DjVmDir::File> file=files_list[pos];
00324 const GUTF8String name(::get_name(*file));
00325 if(reserved.contains(name))
00326 {
00327 GUTF8String new_name;
00328 int series=0;
00329 do
00330 {
00331 int dot=name.rsearch('.');
00332 if(dot>0)
00333 {
00334 new_name=name.substr(0,dot)+
00335 "_"+GUTF8String(++series)+name.substr(dot,-1);
00336 }else
00337 {
00338 new_name=name+"_"+GUTF8String(++series);
00339 }
00340 } while(reserved.contains(new_name)||this_doc.contains(new_name));
00341 dir->set_file_name(file->get_load_name(),new_name);
00342 need_new_list=true;
00343 }
00344 }
00345 if(!need_new_list)
00346 break;
00347 }
00348 }
00349 }
00350
00351 DEBUG_MSG("pass 2: create dummy DIRM chunk and calculate offsets...\n");
00352 for(pos=files_list;pos;++pos)
00353 {
00354 GP<DjVmDir::File> file=files_list[pos];
00355 file->offset=0xffffffff;
00356 GPosition data_pos=data.contains(file->get_load_name());
00357 if (!data_pos)
00358 G_THROW( ERR_MSG("DjVmDoc.no_data") "\t" + file->get_load_name());
00359 if(do_rename)
00360 {
00361 GP<ByteStream> gout(ByteStream::create());
00362 {
00363 const GP<IFFByteStream> giff_in(
00364 IFFByteStream::create(data[data_pos]->get_stream()));
00365 const GP<IFFByteStream> giff_out(IFFByteStream::create(gout));
00366 ::save_file(*giff_in,*giff_out,*dir,incl);
00367 }
00368 gout->seek(0L);
00369 data[data_pos]=DataPool::create(gout);
00370 }
00371 file->size=data[data_pos]->get_length();
00372 if (!file->size)
00373 G_THROW( ERR_MSG("DjVmDoc.zero_file") );
00374 }
00375
00376 const GP<ByteStream> tmp_str(ByteStream::create());
00377 const GP<IFFByteStream> gtmp_iff(IFFByteStream::create(tmp_str));
00378 IFFByteStream &tmp_iff=*gtmp_iff;
00379 tmp_iff.put_chunk("FORM:DJVM", 1);
00380 tmp_iff.put_chunk("DIRM");
00381 dir->encode(tmp_iff.get_bytestream(),do_rename);
00382 tmp_iff.close_chunk();
00383 if (nav)
00384 {
00385 tmp_iff.put_chunk("NAVM");
00386 nav->encode(tmp_iff.get_bytestream());
00387 tmp_iff.close_chunk();
00388 }
00389 tmp_iff.close_chunk();
00390 int offset=tmp_iff.tell();
00391
00392 for(pos=files_list;pos;++pos)
00393 {
00394 if ((offset & 1)!=0)
00395 offset++;
00396
00397 GP<DjVmDir::File> & file=files_list[pos];
00398 file->offset=offset;
00399 offset+=file->size;
00400 }
00401
00402 DEBUG_MSG("pass 3: store the file contents.\n");
00403
00404 GP<IFFByteStream> giff=IFFByteStream::create(gstr);
00405 IFFByteStream &iff=*giff;
00406 iff.put_chunk("FORM:DJVM", 1);
00407 iff.put_chunk("DIRM");
00408 dir->encode(iff.get_bytestream(),do_rename);
00409 iff.close_chunk();
00410 if (nav)
00411 {
00412 iff.put_chunk("NAVM");
00413 nav->encode(iff.get_bytestream());
00414 iff.close_chunk();
00415 }
00416
00417 for(pos=files_list;pos;++pos)
00418 {
00419 GP<DjVmDir::File> & file=files_list[pos];
00420
00421 const GP<DataPool> pool=get_data(file->get_load_name());
00422 const GP<ByteStream> str_in(pool->get_stream());
00423 if ((iff.tell() & 1)!=0)
00424 {
00425 iff.get_bytestream()->write8(0);
00426 }
00427 iff.copy(*str_in);
00428 }
00429
00430 iff.close_chunk();
00431 iff.flush();
00432
00433 DEBUG_MSG("done storing DjVm file.\n");
00434 }
00435
00436 void
00437 DjVmDoc::read(const GP<DataPool> & pool)
00438 {
00439 DEBUG_MSG("DjVmDoc::read(): reading the BUNDLED doc contents from the pool\n");
00440 DEBUG_MAKE_INDENT(3);
00441
00442 const GP<ByteStream> str(pool->get_stream());
00443
00444 GP<IFFByteStream> giff=IFFByteStream::create(str);
00445 IFFByteStream &iff=*giff;
00446 GUTF8String chkid;
00447 iff.get_chunk(chkid);
00448 if (chkid!="FORM:DJVM")
00449 G_THROW( ERR_MSG("DjVmDoc.no_form_djvm") );
00450
00451 iff.get_chunk(chkid);
00452 if (chkid!="DIRM")
00453 G_THROW( ERR_MSG("DjVmDoc.no_dirm_chunk") );
00454 dir->decode(iff.get_bytestream());
00455 iff.close_chunk();
00456
00457 data.empty();
00458
00459 if (dir->is_indirect())
00460 G_THROW( ERR_MSG("DjVmDoc.cant_read_indr") );
00461
00462 GPList<DjVmDir::File> files_list=dir->get_files_list();
00463 for(GPosition pos=files_list;pos;++pos)
00464 {
00465 DjVmDir::File * f=files_list[pos];
00466
00467 DEBUG_MSG("reading contents of file '" << f->get_load_name() << "'\n");
00468 data[f->get_load_name()]=DataPool::create(pool, f->offset, f->size);
00469 }
00470 }
00471
00472 void
00473 DjVmDoc::read(ByteStream & str_in)
00474 {
00475 DEBUG_MSG("DjVmDoc::read(): reading the BUNDLED doc contents from the stream\n");
00476 DEBUG_MAKE_INDENT(3);
00477
00478 GP<DataPool> pool=DataPool::create();
00479 char buffer[1024];
00480 int length;
00481 while((length=str_in.read(buffer, 1024)))
00482 pool->add_data(buffer, length);
00483 pool->set_eof();
00484
00485 read(pool);
00486 }
00487
00488 void
00489 DjVmDoc::read(const GURL &url)
00490 {
00491 DEBUG_MSG("DjVmDoc::read(): reading the doc contents from the HDD\n");
00492 DEBUG_MAKE_INDENT(3);
00493
00494 GP<DataPool> pool=DataPool::create(url);
00495 const GP<ByteStream> str(pool->get_stream());
00496 GP<IFFByteStream> giff=IFFByteStream::create(str);
00497 IFFByteStream &iff=*giff;
00498 GUTF8String chkid;
00499 iff.get_chunk(chkid);
00500 if (chkid!="FORM:DJVM")
00501 G_THROW( ERR_MSG("DjVmDoc.no_form_djvm2") );
00502
00503 iff.get_chunk(chkid);
00504 if (chkid!="DIRM")
00505 G_THROW( ERR_MSG("DjVmDoc.no_dirm_chunk") );
00506 dir->decode(iff.get_bytestream());
00507 iff.close_chunk();
00508
00509 if (dir->is_bundled())
00510 read(pool);
00511 else
00512 {
00513
00514
00515 GURL dirbase=url.base();
00516
00517 data.empty();
00518
00519 GPList<DjVmDir::File> files_list=dir->get_files_list();
00520 for(GPosition pos=files_list;pos;++pos)
00521 {
00522 DjVmDir::File * f=files_list[pos];
00523
00524 DEBUG_MSG("reading contents of file '" << f->get_load_name() << "'\n");
00525
00526 const GURL::UTF8 url(f->get_load_name(),dirbase);
00527 data[f->get_load_name()]=DataPool::create(url);
00528 }
00529 }
00530 }
00531
00532 void
00533 DjVmDoc::write_index(const GP<ByteStream> &str)
00534 {
00535 DEBUG_MSG("DjVmDoc::write_index(): Storing DjVm index file\n");
00536 DEBUG_MAKE_INDENT(3);
00537
00538 GPList<DjVmDir::File> files_list=dir->get_files_list();
00539 for(GPosition pos=files_list;pos;++pos)
00540 {
00541 GP<DjVmDir::File> file=files_list[pos];
00542 file->offset=0;
00543
00544 GPosition data_pos=data.contains(file->get_load_name());
00545 if (!data_pos)
00546 G_THROW( ERR_MSG("DjVmDoc.no_data") "\t" + file->get_load_name());
00547 file->size=data[data_pos]->get_length();
00548 if (!file->size)
00549 G_THROW( ERR_MSG("DjVmDoc.zero_file") );
00550 }
00551
00552 GP<IFFByteStream> giff=IFFByteStream::create(str);
00553 IFFByteStream &iff=*giff;
00554 iff.put_chunk("FORM:DJVM", 1);
00555 iff.put_chunk("DIRM");
00556 dir->encode(iff.get_bytestream());
00557 iff.close_chunk();
00558 if (nav)
00559 {
00560 iff.put_chunk("NAVM");
00561 nav->encode(iff.get_bytestream());
00562 iff.close_chunk();
00563 }
00564 iff.close_chunk();
00565 iff.flush();
00566 }
00567
00568 void
00569 DjVmDoc::save_page(
00570 const GURL &codebase, const DjVmDir::File &file) const
00571 {
00572 GMap<GUTF8String,GUTF8String> incl;
00573 save_file(codebase,file,&incl);
00574 }
00575
00576 void
00577 DjVmDoc::save_page(
00578 const GURL &codebase, const DjVmDir::File &file,
00579 GMap<GUTF8String,GUTF8String> &incl ) const
00580 {
00581 save_file(codebase,file,&incl);
00582 }
00583
00584 void
00585 DjVmDoc::save_file(
00586 const GURL &codebase, const DjVmDir::File &file) const
00587 {
00588 save_file(codebase,file,0);
00589 }
00590
00591 GUTF8String
00592 DjVmDoc::save_file(const GURL &codebase, const DjVmDir::File &file,
00593 GMap<GUTF8String,GUTF8String> &incl, const GP<DataPool> &pool) const
00594 {
00595 const GUTF8String save_name(file.get_save_name());
00596 const GURL::UTF8 new_url(save_name,codebase);
00597 DEBUG_MSG("storing file '"<<new_url<<"'\n");
00598 DataPool::load_file(new_url);
00599 const GP<ByteStream> str_in(pool->get_stream());
00600 const GP<ByteStream> str_out(ByteStream::create(new_url, "wb"));
00601 ::save_file( *IFFByteStream::create(str_in),
00602 *IFFByteStream::create(str_out), *dir, incl);
00603 return save_name;
00604 }
00605
00606 void
00607 DjVmDoc::save_file(
00608 const GURL &codebase, const DjVmDir::File &file,
00609 GMap<GUTF8String,GUTF8String> *incl) const
00610 {
00611 const GUTF8String load_name=file.get_load_name();
00612 if(!incl || !incl->contains(load_name))
00613 {
00614 GMap<GUTF8String,GUTF8String> new_incl;
00615 const GUTF8String save_name(
00616 save_file(codebase,file,new_incl,get_data(load_name)));
00617
00618 if(incl)
00619 {
00620 (*incl)[load_name]=save_name;
00621 for(GPosition pos=new_incl;pos;++pos)
00622 {
00623 save_file(codebase,file,incl);
00624 }
00625 }
00626 }
00627 }
00628
00629 void
00630 DjVmDoc::expand(const GURL &codebase, const GUTF8String &idx_name)
00631 {
00632 DEBUG_MSG("DjVmDoc::expand(): Expanding into '" << codebase << "'\n");
00633 DEBUG_MAKE_INDENT(3);
00634
00635
00636
00637 GPList<DjVmDir::File> files_list=dir->resolve_duplicates(false);
00638
00639
00640 for(GPosition pos=files_list;pos;++pos)
00641 {
00642 save_file(codebase,*files_list[pos]);
00643 }
00644
00645 if (idx_name.length())
00646 {
00647 const GURL::UTF8 idx_url(idx_name, codebase);
00648
00649 DEBUG_MSG("storing index file '" << idx_url << "'\n");
00650
00651 DataPool::load_file(idx_url);
00652 GP<ByteStream> str=ByteStream::create(idx_url, "wb");
00653 write_index(str);
00654 }
00655 }
00656
00657
00658 #ifdef HAVE_NAMESPACES
00659 }
00660 # ifndef NOT_USING_DJVU_NAMESPACE
00661 using namespace DJVU;
00662 # endif
00663 #endif