Main Page   Class Hierarchy   Alphabetical List   Compound List   File List   Compound Members   File Members  

panda/src/downloader/downloadDb.cxx

Go to the documentation of this file.
00001 // Filename: downloadDb.cxx
00002 // Created by:  shochet (08Sep00)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
00008 //
00009 // All use of this software is subject to the terms of the Panda 3d
00010 // Software license.  You should have received a copy of this license
00011 // along with this source code; you will also find a current copy of
00012 // the license at http://www.panda3d.org/license.txt .
00013 //
00014 // To contact the maintainers of this program write to
00015 // panda3d@yahoogroups.com .
00016 //
00017 ////////////////////////////////////////////////////////////////////
00018 
00019 #include "config_downloader.h"
00020 #include "downloadDb.h"
00021 #include <algorithm>
00022 
00023 ////////////////////////////////////////////////////////////////////
00024 // Defines
00025 ////////////////////////////////////////////////////////////////////
00026 
00027 // Written at the top of the file so we know this is a downloadDb
00028 PN_uint32 DownloadDb::_magic_number = 0xfeedfeed;
00029 
00030 // Written at the top of the file to signify we are not done
00031 // writing to the file yet. If you load a db with this magic
00032 // number that means the previous time it got written out was
00033 // probably interrupted in the middle of the write.
00034 PN_uint32 DownloadDb::_bogus_magic_number = 0x11111111;
00035 
00036 
00037 static string
00038 back_to_front_slash(const string &str) {
00039   string result = str;
00040   string::iterator si;
00041   for (si = result.begin(); si != result.end(); ++si) {
00042     if ((*si) == '\\') {
00043       (*si) = '/';
00044     }
00045   }
00046 
00047   return result;
00048 }
00049 
00050 
00051 ////////////////////////////////////////////////////////////////////
00052 //     Function: DownloadDb::Constructor
00053 //       Access: Public
00054 //  Description: Create a download db with these client and server dbs
00055 ////////////////////////////////////////////////////////////////////
00056 DownloadDb::
00057 DownloadDb(Ramfile &server_file, Filename &client_file) {
00058   if (downloader_cat.is_debug())
00059     downloader_cat.debug()
00060       << "DownloadDb constructor called" << endl;
00061   _client_db = read_db(client_file, 0);
00062   _client_db._filename = client_file;
00063   _server_db = read_db(server_file, 1);
00064 }
00065 
00066 ////////////////////////////////////////////////////////////////////
00067 //     Function: DownloadDb::Constructor
00068 //       Access: Public
00069 //  Description: Create a download db with these client and server dbs
00070 ////////////////////////////////////////////////////////////////////
00071 DownloadDb::
00072 DownloadDb(Filename &server_file, Filename &client_file) {
00073   if (downloader_cat.is_debug())
00074     downloader_cat.debug()
00075       << "DownloadDb constructor called" << endl;
00076   _client_db = read_db(client_file, 0);
00077   _client_db._filename = client_file;
00078   _server_db = read_db(server_file, 1);
00079   _server_db._filename = server_file;
00080 }
00081 
00082 ////////////////////////////////////////////////////////////////////
00083 //     Function: DownloadDb::Constructor
00084 //       Access: Public
00085 //  Description: Primarily used for testing.
00086 ////////////////////////////////////////////////////////////////////
00087 DownloadDb::
00088 DownloadDb(void) {
00089   _client_db = Db();
00090   _server_db = Db();
00091 }
00092 
00093 ////////////////////////////////////////////////////////////////////
00094 //     Function: DownloadDb::Destructor
00095 //       Access: Public
00096 //  Description:
00097 ////////////////////////////////////////////////////////////////////
00098 DownloadDb::
00099 ~DownloadDb(void) {
00100   if (downloader_cat.is_debug())
00101     downloader_cat.debug()
00102       << "DownloadDb destructor called" << endl;    
00103 }
00104 
00105 
00106 ////////////////////////////////////////////////////////////////////
00107 //     Function: DownloadDb::output
00108 //       Access: Public
00109 //  Description:
00110 ////////////////////////////////////////////////////////////////////
00111 void DownloadDb::
00112 output(ostream &out) const {
00113   out << "[" << _server_db._filename << " " << _client_db._filename << "]";
00114 }
00115 
00116 ////////////////////////////////////////////////////////////////////
00117 //     Function: DownloadDb::write
00118 //       Access: Public
00119 //  Description:
00120 ////////////////////////////////////////////////////////////////////
00121 void DownloadDb::
00122 write(ostream &out) const {
00123   out << "DownloadDb" << endl;
00124   out << "============================================================" << endl;
00125   out << "  Client DB file: " << _client_db._filename << endl;
00126   out << "============================================================" << endl;
00127   _client_db.write(out);
00128   out << endl;
00129   out << "============================================================" << endl;
00130   out << "  Server DB file: " << _server_db._filename << endl;
00131   out << "============================================================" << endl;
00132   _server_db.write(out);
00133   write_version_map(out);
00134   out << endl;
00135 }
00136 
00137 
00138 ////////////////////////////////////////////////////////////////////
00139 //     Function: DownloadDb::
00140 //       Access: Public
00141 //  Description:
00142 ////////////////////////////////////////////////////////////////////
00143 bool DownloadDb::
00144 write_client_db(Filename &file) {
00145   return write_db(file, _client_db, 0);
00146 }
00147 
00148 
00149 ////////////////////////////////////////////////////////////////////
00150 //     Function: DownloadDb::
00151 //       Access: Public
00152 //  Description:
00153 ////////////////////////////////////////////////////////////////////
00154 bool DownloadDb::
00155 write_server_db(Filename &file) {
00156   return write_db(file, _server_db, 1);
00157 }
00158 
00159 ////////////////////////////////////////////////////////////////////
00160 //     Function: DownloadDb::
00161 //       Access: Public
00162 //  Description:
00163 ////////////////////////////////////////////////////////////////////
00164 bool DownloadDb::
00165 client_multifile_exists(string mfname) const {
00166   return (_client_db.multifile_exists(mfname));
00167 }
00168 
00169 ////////////////////////////////////////////////////////////////////
00170 //     Function: DownloadDb::
00171 //       Access: Public
00172 //  Description: A multifile is complete when it is completely
00173 //               downloaded. Note: it may already be decompressed
00174 //               or extracted and it is still complete
00175 ////////////////////////////////////////////////////////////////////
00176 bool DownloadDb::
00177 client_multifile_complete(string mfname) const {
00178   int client_status = _client_db.get_multifile_record_named(mfname)->_status;
00179   return (client_status >= Status_complete);
00180 }
00181 
00182 ////////////////////////////////////////////////////////////////////
00183 //     Function: DownloadDb::
00184 //       Access: Public
00185 //  Description:
00186 ////////////////////////////////////////////////////////////////////
00187 bool DownloadDb::
00188 client_multifile_decompressed(string mfname) const {
00189   int client_status = _client_db.get_multifile_record_named(mfname)->_status;
00190   return (client_status >= Status_decompressed);
00191 }
00192 
00193 ////////////////////////////////////////////////////////////////////
00194 //     Function: DownloadDb::
00195 //       Access: Public
00196 //  Description:
00197 ////////////////////////////////////////////////////////////////////
00198 bool DownloadDb::
00199 client_multifile_extracted(string mfname) const {
00200   int client_status = _client_db.get_multifile_record_named(mfname)->_status;
00201   return (client_status >= Status_extracted);
00202 }
00203 
00204 
00205 ////////////////////////////////////////////////////////////////////
00206 //     Function: DownloadDb::
00207 //       Access: Public
00208 //  Description: Return the hash value of the file we are working on
00209 ////////////////////////////////////////////////////////////////////
00210 HashVal DownloadDb::
00211 get_client_multifile_hash(string mfname) const {
00212   return _client_db.get_multifile_record_named(mfname)->_hash;
00213 }
00214 
00215 
00216 ////////////////////////////////////////////////////////////////////
00217 //     Function: DownloadDb::
00218 //       Access: Public
00219 //  Description: Return the hash value of the server file
00220 ////////////////////////////////////////////////////////////////////
00221 HashVal DownloadDb::
00222 get_server_multifile_hash(string mfname) const {
00223   return _server_db.get_multifile_record_named(mfname)->_hash;
00224 }
00225 
00226 
00227 ////////////////////////////////////////////////////////////////////
00228 //     Function: DownloadDb::
00229 //       Access: Public
00230 //  Description: Set the hash value of file we are working on
00231 ////////////////////////////////////////////////////////////////////
00232 void DownloadDb::
00233 set_client_multifile_hash(string mfname, HashVal val) {
00234   _client_db.get_multifile_record_named(mfname)->_hash = val;
00235   write_client_db(_client_db._filename);
00236 }
00237 
00238 
00239 ////////////////////////////////////////////////////////////////////
00240 //     Function: DownloadDb::
00241 //       Access: Public
00242 //  Description: Set the hash value of file we are working on
00243 ////////////////////////////////////////////////////////////////////
00244 void DownloadDb::
00245 set_server_multifile_hash(string mfname, HashVal val) {
00246   _server_db.get_multifile_record_named(mfname)->_hash = val;
00247 }
00248 
00249 // Operations on multifiles
00250 
00251 ////////////////////////////////////////////////////////////////////
00252 //     Function: DownloadDb::
00253 //       Access: Public
00254 //  Description:
00255 ////////////////////////////////////////////////////////////////////
00256 void DownloadDb::
00257 delete_client_multifile(string mfname) {
00258 }
00259 
00260 ////////////////////////////////////////////////////////////////////
00261 //     Function: DownloadDb::
00262 //       Access: Public
00263 //  Description:
00264 ////////////////////////////////////////////////////////////////////
00265 void DownloadDb::
00266 add_client_multifile(string server_mfname) {
00267   PT(MultifileRecord) server_mfr = _server_db.get_multifile_record_named(server_mfname);
00268   PT(MultifileRecord) client_mfr = new MultifileRecord;
00269   client_mfr->_name = server_mfr->_name;
00270   client_mfr->_phase = server_mfr->_phase;
00271   _client_db.add_multifile_record(client_mfr);
00272 }
00273 
00274 
00275 ////////////////////////////////////////////////////////////////////
00276 //     Function: DownloadDb::
00277 //       Access: Public
00278 //  Description:
00279 ////////////////////////////////////////////////////////////////////
00280 void DownloadDb::
00281 expand_client_multifile(string mfname) {
00282 }
00283 
00284 
00285 ////////////////////////////////////////////////////////////////////
00286 //     Function: DownloadDb::read_db
00287 //       Access: Published
00288 //  Description:
00289 ////////////////////////////////////////////////////////////////////
00290 DownloadDb::Db DownloadDb::
00291 read_db(Filename &file, bool want_server_info) {
00292   // Open the multifile for reading
00293   ifstream read_stream;
00294   file.set_binary();
00295 
00296   Db db;
00297 
00298   if (!file.open_read(read_stream)) {
00299     downloader_cat.error()
00300       << "DownloadDb::read() - Failed to open input file: "
00301       << file << endl;
00302     return db;
00303   }
00304 
00305   if (!db.read(read_stream, want_server_info)) {
00306     downloader_cat.error()
00307       << "DownloadDb::read() - Read failed: "
00308       << file << endl;
00309     return db;
00310   }
00311   if (want_server_info) {
00312     if (!read_version_map(read_stream)) {
00313       downloader_cat.error()
00314         << "DownloadDb::read() - read_version_map() failed: "
00315         << file << endl;
00316     }
00317   }
00318 
00319   return db;
00320 }
00321 
00322 ////////////////////////////////////////////////////////////////////
00323 //     Function: DownloadDb::read_db
00324 //       Access: Published
00325 //  Description:
00326 ////////////////////////////////////////////////////////////////////
00327 DownloadDb::Db DownloadDb::
00328 read_db(Ramfile &file, bool want_server_info) {
00329   // Open the multifile for reading
00330   istringstream read_stream(file._data);
00331   Db db;
00332 
00333   if (!db.read(read_stream, want_server_info)) {
00334     downloader_cat.error()
00335       << "DownloadDb::read() - Read failed" << endl;
00336     return db;
00337   }
00338   if (want_server_info) {
00339     if (!read_version_map(read_stream)) {
00340       downloader_cat.error()
00341         << "DownloadDb::read() - read_version_map() failed" << endl;
00342     }
00343   }
00344 
00345   return db;
00346 }
00347 
00348 
00349 ////////////////////////////////////////////////////////////////////
00350 //     Function: DownloadDb::write_db
00351 //       Access: Published
00352 //  Description:
00353 ////////////////////////////////////////////////////////////////////
00354 bool DownloadDb::
00355 write_db(Filename &file, Db db, bool want_server_info) {
00356   ofstream write_stream;
00357   file.set_binary();
00358   if (!file.open_write(write_stream)) {
00359     downloader_cat.error()
00360       << "DownloadDb::write_db() - Failed to open output file: "
00361       << file << endl;
00362     return false;
00363   }
00364 
00365   downloader_cat.spam()
00366     << "Writing to file: " << file << endl;
00367 
00368   // Initialize the write stream with a bogus header
00369   db.write_bogus_header(write_stream);
00370   db.write(write_stream, want_server_info);
00371   if (want_server_info) {
00372     write_version_map(write_stream);
00373   }
00374   // Now write the real header
00375   db.write_header(write_stream);
00376   write_stream.close();
00377   return true;
00378 }
00379 
00380 
00381 ////////////////////////////////////////////////////////////////////
00382 //     Function: DownloadDb::create_new_server_db
00383 //       Access: Public
00384 //  Description: Used on the server side makefiles to create a
00385 //               new clean server db
00386 ////////////////////////////////////////////////////////////////////
00387 void DownloadDb::
00388 create_new_server_db(void) {
00389   _server_db = Db();
00390 }
00391 
00392 
00393 ////////////////////////////////////////////////////////////////////
00394 //     Function: DownloadDb::
00395 //       Access: Public
00396 //  Description:
00397 ////////////////////////////////////////////////////////////////////
00398 void DownloadDb::
00399 server_add_multifile(string mfname, Phase phase, int size, int status) {
00400   PT(MultifileRecord) mfr = new MultifileRecord(mfname, phase, size, status);
00401   _server_db.add_multifile_record(mfr);
00402 }
00403 
00404 
00405 ////////////////////////////////////////////////////////////////////
00406 //     Function: DownloadDb::
00407 //       Access: Public
00408 //  Description:
00409 ////////////////////////////////////////////////////////////////////
00410 void DownloadDb::
00411 server_add_file(string mfname, string fname) {
00412   // Make the new file record
00413   PT(FileRecord) fr = new FileRecord(fname);
00414 
00415   // Find the multifile with mfname
00416   pvector< PT(MultifileRecord) >::iterator i = _server_db._mfile_records.begin();
00417   for(; i != _server_db._mfile_records.end(); ++i) {
00418     if (mfname == (*i)->_name) {
00419       (*i)->add_file_record(fr);
00420       return;
00421     }
00422   }
00423 
00424   // Uh-oh, did not find it
00425   downloader_cat.error() << "Could not find record named "
00426                          << mfname << " in database " << endl;
00427   return;
00428 }
00429 
00430 
00431 ////////////////////////////////////////////////////////////////////
00432 // Multifile methods
00433 ////////////////////////////////////////////////////////////////////
00434 
00435 
00436 ////////////////////////////////////////////////////////////////////
00437 //     Function: DownloadDb::MultifileRecord::Constructor
00438 //       Access: Public
00439 //  Description:
00440 ////////////////////////////////////////////////////////////////////
00441 DownloadDb::MultifileRecord::
00442 MultifileRecord(void) {
00443   _name = "";
00444   _phase = 0;
00445   _size = 0;
00446   _status = Status_incomplete;
00447 }
00448 
00449 
00450 ////////////////////////////////////////////////////////////////////
00451 //     Function: DownloadDb::MultifileRecord::Constructor
00452 //       Access: Public
00453 //  Description:
00454 ////////////////////////////////////////////////////////////////////
00455 DownloadDb::MultifileRecord::
00456 MultifileRecord(string name, Phase phase, int size, int status) {
00457   _name = name;
00458   _phase = phase;
00459   _size = size;
00460   _status = status;
00461 }
00462 
00463 
00464 ////////////////////////////////////////////////////////////////////
00465 //     Function: DownloadDb::MultifileRecord::write
00466 //       Access: Public
00467 //  Description:
00468 ////////////////////////////////////////////////////////////////////
00469 void DownloadDb::MultifileRecord::
00470 write(ostream &out) const {
00471   out << "==================================================" << endl;
00472   out << "MultifileRecord: " << _name    << endl
00473       << "    phase: " << _phase   << endl
00474       << "     size: " << _size    << endl
00475       << "   status: " << _status  << endl
00476       << "     hash: [" << _hash.get_value(0)
00477       << " " << _hash.get_value(1)
00478       << " " << _hash.get_value(2)
00479       << " " << _hash.get_value(3)
00480       << "]" << endl;
00481   out << "--------------------------------------------------" << endl;
00482   pvector< PT(FileRecord) >::const_iterator i = _file_records.begin();
00483   for(; i != _file_records.end(); ++i) {
00484     (*i)->write(out);
00485   }
00486 }
00487 
00488 
00489 
00490 ////////////////////////////////////////////////////////////////////
00491 //     Function: DownloadDb::MultifileRecord::
00492 //       Access: Public
00493 //  Description:
00494 ////////////////////////////////////////////////////////////////////
00495 int DownloadDb::MultifileRecord::
00496 get_num_files(void) const {
00497   return _file_records.size();
00498 }
00499 
00500 ////////////////////////////////////////////////////////////////////
00501 //     Function: DownloadDb::MultifileRecord::
00502 //       Access: Public
00503 //  Description:
00504 ////////////////////////////////////////////////////////////////////
00505 string DownloadDb::MultifileRecord::
00506 get_file_name(int index) const {
00507   return _file_records[index]->_name;
00508 }
00509 
00510 
00511 ////////////////////////////////////////////////////////////////////
00512 //     Function: DownloadDb::MultifileRecord::
00513 //       Access: Public
00514 //  Description:
00515 ////////////////////////////////////////////////////////////////////
00516 bool DownloadDb::MultifileRecord::
00517 file_exists(string fname) const {
00518   pvector< PT(FileRecord) >::const_iterator i = _file_records.begin();
00519   for(; i != _file_records.end(); ++i) {
00520     if (fname == (*i)->_name) {
00521       return true;
00522     }
00523   }
00524   return false;
00525 }
00526 
00527 
00528 ////////////////////////////////////////////////////////////////////
00529 //     Function: DownloadDb::MultifileRecord::
00530 //       Access: Public
00531 //  Description:
00532 ////////////////////////////////////////////////////////////////////
00533 PT(DownloadDb::FileRecord) DownloadDb::MultifileRecord::
00534 get_file_record_named(string fname) const {
00535   pvector< PT(FileRecord) >::const_iterator i = _file_records.begin();
00536   for(; i != _file_records.end(); ++i) {
00537     if (fname == (*i)->_name) {
00538       return (*i);
00539     }
00540   }
00541   // Did not find it, just return an empty version
00542   downloader_cat.error() << "Could not find record named "
00543                          << fname << " in multifile " << _name << endl;
00544   PT(FileRecord) foo = new FileRecord;
00545   return foo;
00546 }
00547 
00548 
00549 ////////////////////////////////////////////////////////////////////
00550 //     Function: DownloadDb::MultifileRecord::
00551 //       Access: Public
00552 //  Description:
00553 ////////////////////////////////////////////////////////////////////
00554 void DownloadDb::MultifileRecord::
00555 add_file_record(PT(FileRecord) fr) {
00556   _file_records.push_back(fr);
00557 }
00558 
00559 
00560 
00561 ////////////////////////////////////////////////////////////////////
00562 // Db methods
00563 ////////////////////////////////////////////////////////////////////
00564 
00565 
00566 
00567 
00568 ////////////////////////////////////////////////////////////////////
00569 //     Function: DownloadDb::Db::constructor
00570 //       Access: Public
00571 //  Description:
00572 ////////////////////////////////////////////////////////////////////
00573 DownloadDb::Db::
00574 Db(void) {
00575   // The head is a magic number and the number of multifiles in the db
00576   _header_length = sizeof(_magic_number) + sizeof(PN_int32);
00577 }
00578 
00579 
00580 ////////////////////////////////////////////////////////////////////
00581 //     Function: DownloadDb::Db::output
00582 //       Access: Public
00583 //  Description:
00584 ////////////////////////////////////////////////////////////////////
00585 void DownloadDb::Db::
00586 write(ostream &out) const {
00587   pvector< PT(MultifileRecord) >::const_iterator i = _mfile_records.begin();
00588   for(; i != _mfile_records.end(); ++i) {
00589     (*i)->write(out);
00590   }
00591 }
00592 
00593 
00594 ////////////////////////////////////////////////////////////////////
00595 //     Function: DownloadDb::Db::
00596 //       Access: Public
00597 //  Description:
00598 ////////////////////////////////////////////////////////////////////
00599 int DownloadDb::Db::
00600 get_num_multifiles(void) const {
00601   return _mfile_records.size();
00602 }
00603 
00604 ////////////////////////////////////////////////////////////////////
00605 //     Function: DownloadDb::Db::
00606 //       Access: Public
00607 //  Description:
00608 ////////////////////////////////////////////////////////////////////
00609 string DownloadDb::Db::
00610 get_multifile_name(int index) const {
00611   return _mfile_records[index]->_name;
00612 }
00613 
00614 ////////////////////////////////////////////////////////////////////
00615 //     Function: DownloadDb::Db::
00616 //       Access: Public
00617 //  Description:
00618 ////////////////////////////////////////////////////////////////////
00619 bool DownloadDb::Db::
00620 multifile_exists(string mfname) const {
00621   pvector< PT(MultifileRecord) >::const_iterator i = _mfile_records.begin();
00622   for(; i != _mfile_records.end(); ++i) {
00623     if (mfname == (*i)->_name) {
00624       return true;
00625     }
00626   }
00627   return false;
00628 }
00629 
00630 ////////////////////////////////////////////////////////////////////
00631 //     Function: DownloadDb::Db::
00632 //       Access: Public
00633 //  Description:
00634 ////////////////////////////////////////////////////////////////////
00635 PT(DownloadDb::MultifileRecord) DownloadDb::Db::
00636 get_multifile_record_named(string mfname) const {
00637   pvector< PT(MultifileRecord) >::const_iterator i = _mfile_records.begin();
00638   for(; i != _mfile_records.end(); ++i) {
00639     if (mfname == (*i)->_name) {
00640       return (*i);
00641     }
00642   }
00643   // Did not find it, just return an empty version
00644   downloader_cat.error() << "Could not find record named "
00645                          << mfname << " in database " << endl;
00646   PT(MultifileRecord) foo = new MultifileRecord;
00647   return foo;
00648 }
00649 
00650 ////////////////////////////////////////////////////////////////////
00651 //     Function: DownloadDb::Db::
00652 //       Access: Public
00653 //  Description:
00654 ////////////////////////////////////////////////////////////////////
00655 void DownloadDb::Db::
00656 add_multifile_record(PT(MultifileRecord) mfr) {
00657   _mfile_records.push_back(mfr);
00658 }
00659 
00660 
00661 ////////////////////////////////////////////////////////////////////
00662 //     Function: DownloadDb::Db::parse_header
00663 //       Access: Private
00664 //  Description: Verifies magic number, returns the number of
00665 //               multifiles or -1 if invalid
00666 ////////////////////////////////////////////////////////////////////
00667 int DownloadDb::Db::
00668 parse_header(uchar *start, int size) {
00669   _datagram.clear();
00670   _datagram.append_data(start, size);
00671 
00672   // Make sure we have a good header
00673   DatagramIterator di(_datagram);
00674   PN_uint32 magic_number = di.get_uint32();
00675   downloader_cat.debug()
00676     << "Parsed magic number: " << magic_number << endl;
00677   // If the magic number is equal to the bogus magic number
00678   // it signifies that the previous write was interrupted
00679   if (magic_number == _bogus_magic_number) {
00680     downloader_cat.error()
00681       << "DownloadDb::parse_header() - "
00682       << "Bogus magic number, previous write incomplete: "
00683       << magic_number << " expected: " << _magic_number << endl;
00684     return -1;
00685   }
00686   // If the magic number does not match at all, something is
00687   // really wrong
00688   else if (magic_number != _magic_number) {
00689     downloader_cat.error()
00690       << "DownloadDb::parse_header() - Invalid magic number: "
00691       << magic_number << " expected: " << _magic_number << endl;
00692     return -1;
00693   }
00694 
00695   PN_int32 num_multifiles = di.get_int32();
00696   downloader_cat.debug()
00697     << "Parsed number of multifiles: " << num_multifiles << endl;
00698 
00699   // If we got all the way here, must be done
00700   return num_multifiles;
00701 }
00702 
00703 
00704 
00705 ////////////////////////////////////////////////////////////////////
00706 //     Function: DownloadDb::Db::parse_fr_header
00707 //       Access: Private
00708 //  Description: Parses a file record (fr) header and returns
00709 //               the length of the next file record
00710 ////////////////////////////////////////////////////////////////////
00711 int DownloadDb::Db::
00712 parse_record_header(uchar *start, int size) {
00713   _datagram.clear();
00714   _datagram.append_data(start, size);
00715 
00716   DatagramIterator di(_datagram);
00717   PN_int32 record_length = di.get_int32();
00718   downloader_cat.spam()
00719     << "Parsed record header length: " << record_length << endl;
00720 
00721   // If we got all the way here, must be done
00722   return record_length;
00723 }
00724 
00725 
00726 ////////////////////////////////////////////////////////////////////
00727 //     Function: DownloadDb::Db::parse_mfr
00728 //       Access: Private
00729 //  Description: Parses a multifile record (mfr) and returns one
00730 ////////////////////////////////////////////////////////////////////
00731 PT(DownloadDb::MultifileRecord) DownloadDb::Db::
00732 parse_mfr(uchar *start, int size) {
00733 
00734   PT(DownloadDb::MultifileRecord) mfr = new DownloadDb::MultifileRecord;
00735 
00736   _datagram.clear();
00737   _datagram.append_data(start, size);
00738 
00739   DatagramIterator di(_datagram);
00740   PN_int32 mfr_name_length = di.get_int32();
00741   mfr->_name = di.extract_bytes(mfr_name_length);
00742   mfr->_phase = di.get_float64();
00743   mfr->_size = di.get_int32();
00744   mfr->_status = di.get_int32();
00745   mfr->_num_files = di.get_int32();
00746 
00747   // At one time, we stored files in the database with a backslash
00748   // separator.  Nowadays we use a forward slash, but we should make
00749   // sure we properly convert any old records we might read.
00750   mfr->_name = back_to_front_slash(mfr->_name);
00751   
00752   // Read the hash value
00753   HashVal hash;
00754   hash.set_value(0, di.get_uint32());
00755   hash.set_value(1, di.get_uint32());
00756   hash.set_value(2, di.get_uint32());
00757   hash.set_value(3, di.get_uint32());
00758   mfr->_hash = hash;
00759 
00760   downloader_cat.debug()
00761     << "Parsed multifile record: " << mfr->_name << " phase: " << mfr->_phase
00762      << " size: " << mfr->_size
00763     << " status: " << mfr->_status << " num_files: " << mfr->_num_files << endl;
00764 
00765   // Return the new MultifileRecord
00766   return mfr;
00767 }
00768 
00769 
00770 
00771 
00772 ////////////////////////////////////////////////////////////////////
00773 //     Function: DownloadDb::Db::parse_fr
00774 //       Access: Private
00775 //  Description: Parses a file record (fr) and returns one
00776 ////////////////////////////////////////////////////////////////////
00777 PT(DownloadDb::FileRecord) DownloadDb::Db::
00778 parse_fr(uchar *start, int size) {
00779 
00780   PT(DownloadDb::FileRecord) fr = new DownloadDb::FileRecord;
00781 
00782   _datagram.clear();
00783   _datagram.append_data(start, size);
00784 
00785   DatagramIterator di(_datagram);
00786   PN_int32 fr_name_length = di.get_int32();
00787   fr->_name = di.extract_bytes(fr_name_length);
00788 
00789   // At one time, we stored files in the database with a backslash
00790   // separator.  Nowadays we use a forward slash, but we should make
00791   // sure we properly convert any old records we might read.
00792   fr->_name = back_to_front_slash(fr->_name);
00793 
00794   downloader_cat.spam()
00795     << "Parsed file record: " << fr->_name << endl;
00796 
00797   // Return the new MultifileRecord
00798   return fr;
00799 }
00800 
00801 
00802 
00803 
00804 ////////////////////////////////////////////////////////////////////
00805 //     Function: DownloadDb::Db::read
00806 //       Access: Private
00807 //  Description:
00808 ////////////////////////////////////////////////////////////////////
00809 bool DownloadDb::Db::
00810 read(istream &read_stream, bool want_server_info) {
00811 
00812   // Make a little buffer to read the header into
00813   uchar *header_buf = new uchar[_header_length];
00814   // Read the header
00815   read_stream.read((char *)header_buf, _header_length);
00816   if (read_stream.gcount() != _header_length) {
00817     downloader_cat.error() << "DownloadDb::read() - Empty file" << endl;
00818     return false;
00819   }
00820 
00821   // Parse the header
00822   int num_multifiles = parse_header(header_buf, _header_length);
00823   if (num_multifiles == -1) {
00824     delete header_buf;
00825     downloader_cat.error() << "DownloadDb::read() - Invalid header" << endl;
00826     return false;
00827   }
00828 
00829   delete header_buf;
00830 
00831   // Now that we know how many multifiles this db has, we can iterate
00832   // reading them off one by one
00833   for (int i = 0; i < num_multifiles; i++) {
00834     // The multifile record header is just one int which
00835     // represents the size of the record
00836     int mfr_header_length = sizeof(PN_int32);
00837 
00838     // Make a little buffer to read the multifile record header into
00839     header_buf = new uchar[mfr_header_length];
00840 
00841     // Read the header
00842     read_stream.read((char *)header_buf, mfr_header_length);
00843 
00844     // Parse the header
00845     int mfr_length = parse_record_header(header_buf, mfr_header_length);
00846     delete header_buf;
00847 
00848     // Ok, now that we know the size of the mfr, read it in
00849     // Make a buffer to read the multifile record into
00850     header_buf = new uchar[mfr_length];
00851 
00852     // Read the mfr -- do not count the header length twice
00853     read_stream.read((char *)header_buf, (mfr_length - mfr_header_length));
00854 
00855     // Parse the mfr
00856     PT(DownloadDb::MultifileRecord) mfr = parse_mfr(header_buf, mfr_length);
00857     delete header_buf;
00858 
00859     // Only read in the individual file info if you are the server
00860     if (want_server_info) {
00861 
00862       // Read off all the file records this multifile has
00863       for (int j = 0; j<mfr->_num_files; j++) {
00864         // The file record header is just one int which
00865         // represents the size of the record
00866         int fr_header_length = sizeof(PN_int32);
00867 
00868         // Make a little buffer to read the file record header into
00869         header_buf = new uchar[fr_header_length];
00870 
00871         // Read the header
00872         read_stream.read((char *)header_buf, fr_header_length);
00873 
00874         // Parse the header
00875         int fr_length = parse_record_header(header_buf, fr_header_length);
00876         delete header_buf;
00877 
00878         // Ok, now that we know the size of the mfr, read it in
00879         // Make a buffer to read the file record into
00880         header_buf = new uchar[fr_length];
00881 
00882         // Read the file record -- do not count the header length twice
00883         read_stream.read((char *)header_buf, (fr_length - fr_header_length));
00884 
00885         // Parse the file recrod
00886         PT(DownloadDb::FileRecord) fr = parse_fr(header_buf, fr_length);
00887 
00888         // Add this file record to the current multifilerecord
00889         mfr->add_file_record(fr);
00890       }
00891     }
00892 
00893     // Add the current multifilerecord to our database
00894     add_multifile_record(mfr);
00895 
00896   }
00897 
00898   return true;
00899 }
00900 
00901 
00902 
00903 ////////////////////////////////////////////////////////////////////
00904 //     Function: DownloadDb::Db::write
00905 //       Access: Private
00906 //  Description:
00907 ////////////////////////////////////////////////////////////////////
00908 bool DownloadDb::Db::
00909 write(ofstream &write_stream, bool want_server_info) {
00910   // Declare these outside the loop so we do not keep creating
00911   // and deleting them
00912   PN_float64 phase;
00913   PN_int32 size;
00914   PN_int32 status;
00915   PN_int32 num_files;
00916   PN_int32 name_length;
00917   PN_int32 header_length;
00918   HashVal hash;
00919 
00920   // Iterate over the multifiles writing them to the stream
00921   pvector< PT(MultifileRecord) >::const_iterator i = _mfile_records.begin();
00922   for(; i != _mfile_records.end(); ++i) {
00923     _datagram.clear();
00924 
00925     // Cache some properties so we do not have to keep asking for them
00926     phase = (*i)->_phase;
00927     size = (*i)->_size;
00928     status = (*i)->_status;
00929     num_files = (*i)->get_num_files();
00930     name_length = (*i)->_name.length();
00931 
00932     // Compute the length of this datagram
00933     header_length =
00934       sizeof(header_length) +  // Size of this header length
00935       sizeof(name_length) +    // Size of the size of the name string
00936       (*i)->_name.length() +      // Size of the name string
00937       sizeof(phase) + sizeof(size) +
00938       sizeof(status) + sizeof(num_files) +
00939       sizeof(PN_uint32)*4; // Size of hash value
00940 
00941     // Add the length of this entire datagram
00942     _datagram.add_int32(header_length);
00943 
00944     // Add the length of the name
00945     _datagram.add_int32(name_length);
00946     // Add the name
00947     _datagram.append_data((*i)->_name.c_str(), (*i)->_name.length());
00948 
00949     // Add all the properties
00950     _datagram.add_float64(phase);
00951     _datagram.add_int32(size);
00952     _datagram.add_int32(status);
00953     _datagram.add_int32(num_files);
00954     
00955     hash = (*i)->_hash;
00956     _datagram.add_uint32(hash.get_value(0));
00957     _datagram.add_uint32(hash.get_value(1));
00958     _datagram.add_uint32(hash.get_value(2));
00959     _datagram.add_uint32(hash.get_value(3));
00960 
00961 
00962     // Now put this datagram on the write stream
00963     string msg = _datagram.get_message();
00964     write_stream.write(msg.data(), msg.length());
00965 
00966     // Only write out the file information if you are the server
00967     if (want_server_info) {
00968       // Now iterate over this multifile's files writing them to the stream
00969       // Iterate over the multifiles writing them to the stream
00970       pvector< PT(FileRecord) >::const_iterator j = (*i)->_file_records.begin();
00971       for(; j != (*i)->_file_records.end(); ++j) {
00972         // Clear the datagram before we jam a bunch of stuff on it
00973         _datagram.clear();
00974 
00975         name_length = (*j)->_name.length();
00976 
00977         // Compute the length of this datagram
00978         header_length =
00979           sizeof(header_length) +  // Size of this header length
00980           sizeof(name_length) +    // Size of the size of the name string
00981           (*j)->_name.length();    // Size of the name string
00982 
00983         // Add the length of this entire datagram
00984         _datagram.add_int32(header_length);
00985 
00986         // Add the length of the name
00987         _datagram.add_int32(name_length);
00988         // Add the name
00989         _datagram.append_data((*j)->_name.c_str(), (*j)->_name.length());
00990 
00991         // Now put this datagram on the write stream
00992         string msg = _datagram.get_message();
00993         write_stream.write(msg.data(), msg.length());
00994       }
00995     }
00996   }
00997 
00998   return true;
00999 }
01000 
01001 ////////////////////////////////////////////////////////////////////
01002 //     Function: DownloadDb::Db::write_bogus_header
01003 //       Access: Private
01004 //  Description: Writes the bogus header uncompressed with platform-
01005 //               independent byte ordering. This header will get
01006 //               overwritten with the real magic number as the last
01007 //               step in the write
01008 ////////////////////////////////////////////////////////////////////
01009 bool DownloadDb::Db::
01010 write_bogus_header(ofstream &write_stream) {
01011   _datagram.clear();
01012 
01013   // Write the db magic number
01014   _datagram.add_uint32(_bogus_magic_number);
01015 
01016   // Write the number of multifiles
01017   _datagram.add_int32(get_num_multifiles());
01018 
01019   string msg = _datagram.get_message();
01020   write_stream.write(msg.data(), msg.length());
01021   return true;
01022 }
01023 
01024 ////////////////////////////////////////////////////////////////////
01025 //     Function: DownloadDb::Db::write_header
01026 //       Access: Private
01027 //  Description: Writes the header uncompressed with platform-
01028 //               independent byte ordering
01029 ////////////////////////////////////////////////////////////////////
01030 bool DownloadDb::Db::
01031 write_header(ofstream &write_stream) {
01032   _datagram.clear();
01033 
01034   // Write the db magic number
01035   _datagram.add_uint32(_magic_number);
01036 
01037   // Write the number of multifiles
01038   _datagram.add_int32(get_num_multifiles());
01039 
01040   string msg = _datagram.get_message();
01041   // Seek back to the beginning of the write stream
01042   write_stream.seekp(0);
01043   // Overwrite the old bogus header with the real header
01044   write_stream.write(msg.data(), msg.length());
01045   return true;
01046 }
01047 
01048 
01049 
01050 ////////////////////////////////////////////////////////////////////
01051 // FileRecord methods
01052 ////////////////////////////////////////////////////////////////////
01053 
01054 
01055 ////////////////////////////////////////////////////////////////////
01056 //     Function: DownloadDb::FileRecord::Constructor
01057 //       Access: Public
01058 //  Description:
01059 ////////////////////////////////////////////////////////////////////
01060 DownloadDb::FileRecord::
01061 FileRecord(void) {
01062   _name = "";
01063 }
01064 
01065 
01066 ////////////////////////////////////////////////////////////////////
01067 //     Function: DownloadDb::FileRecord::Constructor
01068 //       Access: Public
01069 //  Description:
01070 ////////////////////////////////////////////////////////////////////
01071 DownloadDb::FileRecord::
01072 FileRecord(string name) {
01073   _name = name;
01074 }
01075 
01076 ////////////////////////////////////////////////////////////////////
01077 //     Function: DownloadDb::FileRecord::output
01078 //       Access: Public
01079 //  Description:
01080 ////////////////////////////////////////////////////////////////////
01081 void DownloadDb::FileRecord::
01082 write(ostream &out) const {
01083   out << " FileRecord: " << _name << endl;
01084 }
01085 
01086 ////////////////////////////////////////////////////////////////////
01087 //     Function: DownloadDb::add_version
01088 //       Access: Published
01089 //  Description: Appends a new version of the file onto the end of the
01090 //               list, or changes the hash associated with a version
01091 //               previously added.
01092 //
01093 //               Note: version numbers start at 1
01094 ////////////////////////////////////////////////////////////////////
01095 void DownloadDb::
01096 add_version(const Filename &name, const HashVal &hash, int version) {
01097   nassertv(version >= 1);
01098 
01099   VectorHash &vhash = _versions[name];
01100   int size = vhash.size();
01101 
01102   // We should not skip over versions as we add them.
01103   nassertv(version <= size+1);
01104 
01105   if (version-1 < size) {
01106     // If you are overwriting an old hash value, just rewrite the value
01107     vhash[version-1] = hash;
01108 
01109   } else {
01110     // Otherwise, extend the vector.
01111     vhash.push_back(hash);
01112   }
01113 }
01114 
01115 ////////////////////////////////////////////////////////////////////
01116 //     Function: DownloadDb::insert_new_version
01117 //       Access: Published
01118 //  Description: Inserts a new version 1 copy of the file, sliding all
01119 //               the other versions up by one.
01120 ////////////////////////////////////////////////////////////////////
01121 void DownloadDb::
01122 insert_new_version(const Filename &name, const HashVal &hash) {
01123   VectorHash &vhash = _versions[name];
01124   vhash.insert(vhash.begin(), hash);
01125 }
01126 
01127 ////////////////////////////////////////////////////////////////////
01128 //     Function: DownloadDb::has_version
01129 //       Access: Published
01130 //  Description: Returns true if the indicated file has version
01131 //               information, false otherwise.  Some files recorded in
01132 //               the database may not bother to track versions.
01133 ////////////////////////////////////////////////////////////////////
01134 bool DownloadDb::
01135 has_version(const Filename &name) const {
01136   return (_versions.find(name) != _versions.end());
01137 }
01138 
01139 ////////////////////////////////////////////////////////////////////
01140 //     Function: DownloadDb::get_num_versions
01141 //       Access: Published
01142 //  Description: Returns the number of versions stored for the
01143 //               indicated file.
01144 ////////////////////////////////////////////////////////////////////
01145 int DownloadDb::
01146 get_num_versions(const Filename &name) const {
01147   VersionMap::const_iterator vmi = _versions.find(name);
01148   if (vmi == _versions.end()) {
01149     return 0;
01150   }
01151 
01152   return (int)(*vmi).second.size();
01153 }
01154 
01155 ////////////////////////////////////////////////////////////////////
01156 //     Function: DownloadDb::set_num_versions
01157 //       Access: Published
01158 //  Description: Reduces the number of versions of a particular file
01159 //               stored in the ddb by throwing away all versions
01160 //               higher than the indicated index.
01161 ////////////////////////////////////////////////////////////////////
01162 void DownloadDb::
01163 set_num_versions(const Filename &name, int num_versions) {
01164   VersionMap::iterator vmi = _versions.find(name);
01165   if (vmi == _versions.end()) {
01166     nassertv(num_versions == 0);
01167     return;
01168   }
01169 
01170   VectorHash &vhash = (*vmi).second;
01171 
01172   nassertv(num_versions <= (int)vhash.size());
01173   vhash.erase(vhash.begin() + num_versions, vhash.end());
01174 }
01175 
01176 ////////////////////////////////////////////////////////////////////
01177 //     Function: DownloadDb::get_version
01178 //       Access: Published
01179 //  Description: Returns the version number of this particular file,
01180 //               determined by looking up the hash generated from the
01181 //               file.  Returns -1 if the version number cannot be
01182 //               determined.
01183 ////////////////////////////////////////////////////////////////////
01184 int DownloadDb::
01185 get_version(const Filename &name, const HashVal &hash) const {
01186   VersionMap::const_iterator vmi = _versions.find(name);
01187   if (vmi == _versions.end()) {
01188     downloader_cat.debug()
01189       << "DownloadDb::get_version() - can't find: " << name << endl;
01190     return -1;
01191   }
01192   const VectorHash &vhash = (*vmi).second;
01193   VectorHash::const_iterator i = find(vhash.begin(), vhash.end(), hash);
01194   if (i != vhash.end())
01195     return (i - vhash.begin() + 1);
01196   downloader_cat.debug()
01197     << "DownloadDb::get_version() - can't find hash: " << hash << endl;
01198   return -1;
01199 }
01200 
01201 ////////////////////////////////////////////////////////////////////
01202 //     Function: DownloadDb::get_hash
01203 //       Access: Published
01204 //  Description: Returns the MD5 hash associated with the indicated
01205 //               version of the indicated file.
01206 ////////////////////////////////////////////////////////////////////
01207 const HashVal &DownloadDb::
01208 get_hash(const Filename &name, int version) const {
01209   static HashVal bogus_hash;
01210 
01211   VersionMap::const_iterator vmi = _versions.find(name);
01212   if (vmi == _versions.end()) {
01213     downloader_cat.error()
01214       << "DownloadDb::get_hash() - can't find: " << name << endl;
01215     return bogus_hash;
01216   }
01217 
01218   const VectorHash &vhash = (*vmi).second;
01219   if (version < 1 || version > (int)vhash.size()) {
01220     downloader_cat.error()
01221       << "DownloadDb::get_hash() - no version " << version 
01222       << " for " << name << endl;
01223     return bogus_hash;
01224   }
01225   return vhash[version - 1];
01226 }
01227 
01228 ////////////////////////////////////////////////////////////////////
01229 //     Function: DownloadDb::write_version_map
01230 //       Access: Protected
01231 //  Description:
01232 ////////////////////////////////////////////////////////////////////
01233 void DownloadDb::
01234 write_version_map(ofstream &write_stream) {
01235   _master_datagram.clear();
01236 
01237   VersionMap::iterator vmi;
01238   VectorHash::iterator i;
01239   string name;
01240   HashVal hash;
01241 
01242   _master_datagram.add_int32(_versions.size());
01243   for (vmi = _versions.begin(); vmi != _versions.end(); ++vmi) {
01244     name = (*vmi).first;
01245     downloader_cat.spam()
01246       << "DownloadDb::write_version_map() - writing file: "
01247       << name << " of length: " << name.length() << endl;
01248     _master_datagram.add_int32(name.length());
01249     _master_datagram.append_data(name.c_str(), name.length());
01250     _master_datagram.add_int32((*vmi).second.size());
01251     for (i = (*vmi).second.begin(); i != (*vmi).second.end(); ++i) {
01252       // *i will point to a HashVal
01253       hash = *i;
01254       // Write out each uint separately
01255       _master_datagram.add_uint32(hash.get_value(0));
01256       _master_datagram.add_uint32(hash.get_value(1));
01257       _master_datagram.add_uint32(hash.get_value(2));
01258       _master_datagram.add_uint32(hash.get_value(3));
01259     }
01260   }
01261   string msg = _master_datagram.get_message();
01262   write_stream.write((char *)msg.data(), msg.length());
01263 }
01264 
01265 ////////////////////////////////////////////////////////////////////
01266 //     Function: DownloadDb::read_version_map
01267 //       Access: Protected
01268 //  Description:
01269 ////////////////////////////////////////////////////////////////////
01270 bool DownloadDb::
01271 read_version_map(istream &read_stream) {
01272   _master_datagram.clear();
01273   char *buffer = new char[sizeof(PN_uint64)];
01274   read_stream.read(buffer, sizeof(PN_int32));
01275   _master_datagram.append_data(buffer, sizeof(PN_int32));
01276   DatagramIterator di(_master_datagram);
01277   int num_entries = di.get_int32();
01278 
01279   for (int i = 0; i < num_entries; i++) {
01280 
01281     // Get the length of the file name
01282     _master_datagram.clear();
01283     read_stream.read(buffer, sizeof(PN_int32));
01284     _master_datagram.append_data(buffer, sizeof(PN_int32));
01285     DatagramIterator di2(_master_datagram);
01286     int name_length = di2.get_int32();
01287     downloader_cat.spam()
01288       << "DownloadDb::read_version_map() - name length: " << name_length
01289       << endl;
01290 
01291     // Get the file name
01292     _master_datagram.clear();
01293     char *namebuffer = new char[name_length];
01294     read_stream.read(namebuffer, name_length);
01295     _master_datagram.append_data(namebuffer, name_length);
01296     DatagramIterator di4(_master_datagram);
01297     string name = di4.extract_bytes(name_length);
01298     downloader_cat.spam()
01299       << "DownloadDb::read_version_map() - name: " << name << endl;
01300 
01301     // Get number of hash values for name
01302     _master_datagram.clear();
01303     read_stream.read(buffer, sizeof(PN_int32));
01304     _master_datagram.append_data(buffer, sizeof(PN_int32));
01305     DatagramIterator di5(_master_datagram);
01306     int length = di5.get_int32();
01307     downloader_cat.spam()
01308       << "DownloadDb::read_version_map() - number of values: " << length
01309       << endl;
01310 
01311     for (int j = 0; j < length; j++) {
01312       _master_datagram.clear();
01313       // Read all 4 uint values for the hash
01314       read_stream.read(buffer, sizeof(PN_uint32));
01315       _master_datagram.append_data(buffer, sizeof(PN_uint32));
01316       read_stream.read(buffer, sizeof(PN_uint32));
01317       _master_datagram.append_data(buffer, sizeof(PN_uint32));
01318       read_stream.read(buffer, sizeof(PN_uint32));
01319       _master_datagram.append_data(buffer, sizeof(PN_uint32));
01320       read_stream.read(buffer, sizeof(PN_uint32));
01321       _master_datagram.append_data(buffer, sizeof(PN_uint32));
01322       DatagramIterator di3(_master_datagram);
01323       HashVal hash;
01324       hash.set_value(0, di3.get_uint32());
01325       hash.set_value(1, di3.get_uint32());
01326       hash.set_value(2, di3.get_uint32());
01327       hash.set_value(3, di3.get_uint32());
01328       add_version(name, hash, j + 1);
01329     }
01330     delete namebuffer;
01331   }
01332   delete buffer;
01333   return true;
01334 }
01335 
01336 ////////////////////////////////////////////////////////////////////
01337 //     Function: DownloadDb::write_version_map
01338 //       Access: Public
01339 //  Description:
01340 ////////////////////////////////////////////////////////////////////
01341 void DownloadDb::
01342 write_version_map(ostream &out) const {
01343   out << "Version Map: " << endl;
01344   VersionMap::const_iterator vmi;
01345   VectorHash::const_iterator i;
01346   for (vmi = _versions.begin(); vmi != _versions.end(); ++vmi) {
01347     out << "  " << (*vmi).first << endl;
01348     for (i = (*vmi).second.begin(); i != (*vmi).second.end(); ++i) {
01349       HashVal hash = *i;
01350       out << "    " << hash << endl;
01351     }
01352   }
01353   out << endl;
01354 }

Generated on Fri May 2 00:36:46 2003 for Panda by doxygen1.3