From 87a912818b2cbbe9414adad7ab9360235e38f79b Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 8 Jun 2006 22:33:08 +0000 Subject: [PATCH] add file timestamps --- dtool/src/dtoolutil/filename.cxx | 35 +++++ dtool/src/dtoolutil/filename.h | 2 + panda/src/downloadertools/multify.cxx | 38 +++++- panda/src/express/multifile.I | 16 +++ panda/src/express/multifile.cxx | 122 +++++++++++++++++- panda/src/express/multifile.h | 6 + panda/src/express/virtualFile.cxx | 19 +++ panda/src/express/virtualFile.h | 1 + panda/src/express/virtualFileMount.h | 1 + .../src/express/virtualFileMountMultifile.cxx | 23 ++++ panda/src/express/virtualFileMountMultifile.h | 1 + panda/src/express/virtualFileMountSystem.cxx | 20 +++ panda/src/express/virtualFileMountSystem.h | 1 + panda/src/express/virtualFileSimple.cxx | 19 +++ panda/src/express/virtualFileSimple.h | 1 + 15 files changed, 294 insertions(+), 11 deletions(-) diff --git a/dtool/src/dtoolutil/filename.cxx b/dtool/src/dtoolutil/filename.cxx index 0ef9a74c53..601268daf4 100644 --- a/dtool/src/dtoolutil/filename.cxx +++ b/dtool/src/dtoolutil/filename.cxx @@ -1166,6 +1166,41 @@ compare_timestamps(const Filename &other, return other_missing_is_old ? 1 : -1; } +//////////////////////////////////////////////////////////////////// +// Function: Filename::get_timestamp +// Access: Published +// Description: Returns a time_t value that represents the time the +// file was last modified, to within whatever precision +// the operating system records this information (on a +// Windows95 system, for instance, this may only be +// accurate to within 2 seconds). +// +// If the timestamp cannot be determined, either because +// it is not supported by the operating system or +// because there is some error (such as file not found), +// returns 0. +//////////////////////////////////////////////////////////////////// +time_t Filename:: +get_timestamp() const { + string os_specific = get_filename_index(0).to_os_specific(); + +#ifdef WIN32_VC + struct _stat this_buf; + + if (_stat(os_specific.c_str(), &this_buf) == 0) { + return this_buf.st_mtime; + } +#else // WIN32_VC + struct stat this_buf; + + if (stat(os_specific.c_str(), &this_buf) == 0) { + return this_buf.st_mtime; + } +#endif + + return 0; +} + //////////////////////////////////////////////////////////////////// // Function: Filename::resolve_filename // Access: Published diff --git a/dtool/src/dtoolutil/filename.h b/dtool/src/dtoolutil/filename.h index 833b159d14..2d5bf74b3c 100644 --- a/dtool/src/dtoolutil/filename.h +++ b/dtool/src/dtoolutil/filename.h @@ -159,6 +159,8 @@ PUBLISHED: int compare_timestamps(const Filename &other, bool this_missing_is_old = true, bool other_missing_is_old = true) const; + time_t get_timestamp() const; + bool resolve_filename(const DSearchPath &searchpath, const string &default_extension = string()); bool make_relative_to(Filename directory, bool allow_backups = true); diff --git a/panda/src/downloadertools/multify.cxx b/panda/src/downloadertools/multify.cxx index 3f4511e774..b273ef8c1f 100644 --- a/panda/src/downloadertools/multify.cxx +++ b/panda/src/downloadertools/multify.cxx @@ -388,6 +388,31 @@ extract_files(int argc, char *argv[]) { return true; } +const char * +format_timestamp(time_t timestamp) { + static const size_t buffer_size = 512; + static char buffer[buffer_size]; + + if (timestamp == 0) { + // A zero timestamp is a special case. + return " (no date) "; + } + + time_t now = time(NULL); + struct tm *tm_p = localtime(×tamp); + + if (timestamp > now || (now - timestamp > 86400 * 365)) { + // A timestamp in the future, or more than a year in the past, + // gets a year appended. + strftime(buffer, buffer_size, "%b %d %Y", tm_p); + } else { + // Otherwise, within the past year, show the date and time. + strftime(buffer, buffer_size, "%b %d %H:%M", tm_p); + } + + return buffer; +} + bool list_files(int argc, char *argv[]) { if (!multifile_name.exists()) { @@ -419,23 +444,28 @@ list_files(int argc, char *argv[]) { ratio = (double)internal_length / (double)orig_length; } if (ratio > 1.0) { - printf("%12d worse %c %s\n", + printf("%12d worse %c %s %s\n", multifile->get_subfile_length(i), encrypted_symbol, + format_timestamp(multifile->get_subfile_timestamp(i)), subfile_name.c_str()); } else { - printf("%12d %3.0f%% %c %s\n", + printf("%12d %3.0f%% %c %s %s\n", multifile->get_subfile_length(i), 100.0 - ratio * 100.0, encrypted_symbol, + format_timestamp(multifile->get_subfile_timestamp(i)), subfile_name.c_str()); } } else { - printf("%12d %c %s\n", + printf("%12d %c %s %s\n", multifile->get_subfile_length(i), - encrypted_symbol, subfile_name.c_str()); + encrypted_symbol, + format_timestamp(multifile->get_subfile_timestamp(i)), + subfile_name.c_str()); } } } + cout << "Last modification " << format_timestamp(multifile->get_timestamp()) << "\n"; fflush(stdout); if (multifile->get_scale_factor() != 1) { cout << "Scale factor is " << multifile->get_scale_factor() << "\n"; diff --git a/panda/src/express/multifile.I b/panda/src/express/multifile.I index f6e7e06b83..4354e1e7a7 100644 --- a/panda/src/express/multifile.I +++ b/panda/src/express/multifile.I @@ -63,6 +63,21 @@ needs_repack() const { return _needs_repack || (_scale_factor != _new_scale_factor); } +//////////////////////////////////////////////////////////////////// +// Function: Multifile::get_timestamp +// Access: Published +// Description: Returns the modification timestamp of the overall +// Multifile. This indicates the most recent date at +// which subfiles were added or removed from the +// Multifile. Note that it is logically possible for an +// individual subfile to have a more recent timestamp +// than the overall timestamp. +//////////////////////////////////////////////////////////////////// +INLINE time_t Multifile:: +get_timestamp() const { + return _timestamp; +} + //////////////////////////////////////////////////////////////////// // Function: Multifile::get_scale_factor // Access: Published @@ -197,6 +212,7 @@ Subfile() { _index_start = 0; _data_start = 0; _data_length = 0; + _timestamp = 0; _source = (istream *)NULL; _flags = 0; _compression_level = 0; diff --git a/panda/src/express/multifile.cxx b/panda/src/express/multifile.cxx index 5227cb40a1..eeb2a3d3f0 100644 --- a/panda/src/express/multifile.cxx +++ b/panda/src/express/multifile.cxx @@ -36,7 +36,9 @@ const size_t Multifile::_header_size = 6; // change in the major version is intolerable; while a Multifile with // an older minor version may still be read. const int Multifile::_current_major_ver = 1; -const int Multifile::_current_minor_ver = 0; + +const int Multifile::_current_minor_ver = 1; +// Bumped to version 1.1 on 6/8/06 to add timestamps. // To confirm that the supplied password matches, we write the // Mutifile magic header at the beginning of the encrypted stream. @@ -63,6 +65,7 @@ const size_t Multifile::_encrypt_header_size = 6; // uint32 Scale factor. This scales all address references within // the file. Normally 1, this may be set larger to // support Multifiles larger than 4GB. +// uint32 An overall modification timestamp for the entire multifile. // // (2) Zero or more index entries, one for each subfile within the @@ -82,6 +85,7 @@ const size_t Multifile::_encrypt_header_size = 6; // subfile, if it is compressed or encrypted. This field // is only present if one or both of the SF_compressed // or SF_encrypted bits are set in _flags. +// uint32 A modification timestamp for the subfile. // uint16 The length in bytes of the subfile's name. // char[n] The subfile's name. // @@ -105,6 +109,8 @@ Multifile() { _next_index = 0; _last_index = 0; _needs_repack = false; + _timestamp = 0; + _timestamp_dirty = false; _scale_factor = 1; _new_scale_factor = 1; _encryption_flag = false; @@ -162,6 +168,8 @@ open_read(const Filename &multifile_name) { if (!fname.open_read(_read_file)) { return false; } + _timestamp = fname.get_timestamp(); + _timestamp_dirty = true; _read = &_read_file; _multifile_name = multifile_name; return read_index(); @@ -188,6 +196,8 @@ open_write(const Filename &multifile_name) { if (!fname.open_write(_write_file, true)) { return false; } + _timestamp = time(NULL); + _timestamp_dirty = true; _write = &_write_file; _multifile_name = multifile_name; return true; @@ -215,6 +225,12 @@ open_read_write(const Filename &multifile_name) { if (!fname.open_read_write(_read_write_file)) { return false; } + if (exists) { + _timestamp = fname.get_timestamp(); + } else { + _timestamp = time(NULL); + } + _timestamp_dirty = true; _read = &_read_write_file; _write = &_read_write_file; _multifile_name = multifile_name; @@ -248,8 +264,11 @@ close() { _next_index = 0; _last_index = 0; _needs_repack = false; + _timestamp = 0; + _timestamp_dirty = false; _scale_factor = 1; _new_scale_factor = 1; + _encryption_flag = false; _file_major_ver = 0; _file_minor_ver = 0; @@ -335,6 +354,9 @@ add_subfile(const string &subfile_name, const Filename &filename, add_new_subfile(subfile, compression_level); } + _timestamp = time(NULL); + _timestamp_dirty = true; + return name; } @@ -343,9 +365,9 @@ add_subfile(const string &subfile_name, const Filename &filename, // Access: Published // Description: Adds a file on disk to the subfile. If a subfile // already exists with the same name, its contents are -// compared to the disk file, and it is replaced only if -// it is different; otherwise, the multifile is left -// unchanged. +// compared byte-for-byte to the disk file, and it is +// replaced only if it is different; otherwise, the +// multifile is left unchanged. //////////////////////////////////////////////////////////////////// string Multifile:: update_subfile(const string &subfile_name, const Filename &filename, @@ -376,6 +398,9 @@ update_subfile(const string &subfile_name, const Filename &filename, add_new_subfile(subfile, compression_level); } + _timestamp = time(NULL); + _timestamp_dirty = true; + return name; } @@ -412,6 +437,13 @@ flush() { if (!write_header()) { return false; } + + } else { + if (_file_minor_ver != _current_minor_ver) { + // If we *do* have an index already, but this is an old version + // multifile, we have to completely rewrite it anyway. + return repack(); + } } nassertr(_write != (ostream *)NULL, false); @@ -484,8 +516,8 @@ flush() { // Now go back and fill in the proper addresses for the data start. // We didn't do it in the first pass, because we don't really want - // to keep all those pile handles open, and so we didn't have to - // determine each pile's length ahead of time. + // to keep all those file handles open, and so we didn't have to + // determine each file's length ahead of time. for (pi = _new_subfiles.begin(); pi != _new_subfiles.end(); ++pi) { Subfile *subfile = (*pi); subfile->rewrite_index_data_start(*_write, this); @@ -494,6 +526,18 @@ flush() { _new_subfiles.clear(); } + // Also update the overall timestamp. + if (_timestamp_dirty) { + nassertr(!_write->fail(), false); + static const size_t timestamp_pos = _header_size + 2 + 2 + 4; + _write->seekp(timestamp_pos); + nassertr(!_write->fail(), false); + + StreamWriter writer(*_write); + writer.add_uint32(_timestamp); + _timestamp_dirty = false; + } + _write->flush(); if (!wrote_ok || _write->fail()) { express_cat.info() @@ -725,6 +769,9 @@ remove_subfile(int index) { _removed_subfiles.push_back(subfile); _subfiles.erase(_subfiles.begin() + index); + _timestamp = time(NULL); + _timestamp_dirty = true; + _needs_repack = true; } @@ -756,6 +803,21 @@ get_subfile_length(int index) const { return _subfiles[index]->_uncompressed_length; } +//////////////////////////////////////////////////////////////////// +// Function: Multifile::get_subfile_timestamp +// Access: Published +// Description: Returns the modification time of the nth +// subfile. If this is called on an older .mf file, +// which did not store individual timestamps in the +// file, this will return the modification time of the +// overall multifile. +//////////////////////////////////////////////////////////////////// +time_t Multifile:: +get_subfile_timestamp(int index) const { + nassertr(index >= 0 && index < (int)_subfiles.size(), 0); + return _subfiles[index]->_timestamp; +} + //////////////////////////////////////////////////////////////////// // Function: Multifile::is_subfile_compressed // Access: Published @@ -951,6 +1013,18 @@ compare_subfile(int index, const Filename &filename) { return false; } + // Check the file size. + in2.seekg(0, ios::end); + streampos file_size = in2.tellg(); + + if (file_size != (streampos)get_subfile_length(index)) { + // The files have different sizes. + delete in1; + return false; + } + + // Check the file data, byte-for-byte. + in2.seekg(0); int byte1 = in1->get(); int byte2 = in2.get(); while (!in1->fail() && !in1->eof() && @@ -967,6 +1041,7 @@ compare_subfile(int index, const Filename &filename) { delete in1; nassertr(!failed, false); + return true; } @@ -1036,6 +1111,8 @@ read_subfile(int index, string &result) { bool Multifile:: open_read(istream *multifile_stream) { close(); + _timestamp = time(NULL); + _timestamp_dirty = true; _read = multifile_stream; return read_index(); } @@ -1053,6 +1130,8 @@ open_read(istream *multifile_stream) { bool Multifile:: open_write(ostream *multifile_stream) { close(); + _timestamp = time(NULL); + _timestamp_dirty = true; _write = multifile_stream; return true; } @@ -1071,6 +1150,8 @@ open_write(ostream *multifile_stream) { bool Multifile:: open_read_write(iostream *multifile_stream) { close(); + _timestamp = time(NULL); + _timestamp_dirty = true; _read = multifile_stream; _write = multifile_stream; @@ -1303,6 +1384,10 @@ read_index() { return false; } + if (_file_minor_ver >= 1) { + _timestamp = reader.get_uint32(); + _timestamp_dirty = false; + } // Now read the index out. _next_index = _read->tellg(); @@ -1360,6 +1445,9 @@ read_index() { //////////////////////////////////////////////////////////////////// bool Multifile:: write_header() { + _file_major_ver = _current_major_ver; + _file_minor_ver = _current_minor_ver; + nassertr(_write != (ostream *)NULL, false); nassertr(_write->tellp() == (streampos)0, false); _write->write(_header, _header_size); @@ -1368,6 +1456,8 @@ write_header() { writer.add_int16(_current_minor_ver); writer.add_uint32(_scale_factor); + writer.add_uint32(_timestamp); + _next_index = _write->tellp(); _next_index = pad_to_streampos(_next_index); _last_index = 0; @@ -1421,6 +1511,12 @@ read_index(istream &read, streampos fpos, Multifile *multifile) { } else { _uncompressed_length = _data_length; } + if (multifile->_file_minor_ver < 1) { + _timestamp = multifile->get_timestamp(); + } else { + _timestamp = reader.get_uint32(); + } + size_t name_length = reader.get_uint16(); if (read.eof() || read.fail()) { _flags |= SF_index_invalid; @@ -1471,6 +1567,7 @@ write_index(ostream &write, streampos fpos, Multifile *multifile) { if ((_flags & (SF_compressed | SF_encrypted)) != 0) { dg.add_uint32(_uncompressed_length); } + dg.add_uint32(_timestamp); dg.add_uint16(_name.length()); // For no real good reason, we'll invert all the bits in the name. @@ -1497,7 +1594,7 @@ write_index(ostream &write, streampos fpos, Multifile *multifile) { } //////////////////////////////////////////////////////////////////// -// Function: Multifile::Subfile::write_index +// Function: Multifile::Subfile::write_data // Access: Public // Description: Writes the data record for the Subfile to the // indicated ostream: the actual contents of the @@ -1621,6 +1718,16 @@ write_data(ostream &write, istream *read, streampos fpos, // Subfile. (In case we are running during repack()). _data_start = fpos; + // Get the modification timestamp for this subfile. This is read + // from the source file, if we have a filename; otherwise, it's the + // current time. + if (!_source_filename.empty()) { + _timestamp = _source_filename.get_timestamp(); + } + if (_timestamp == 0) { + _timestamp = time(NULL); + } + _source = (istream *)NULL; _source_filename = Filename(); source_file.close(); @@ -1651,6 +1758,7 @@ rewrite_index_data_start(ostream &write, Multifile *multifile) { if ((_flags & (SF_compressed | SF_encrypted)) != 0) { writer.add_uint32(_uncompressed_length); } + writer.add_uint32(_timestamp); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/express/multifile.h b/panda/src/express/multifile.h index fdc24bf0b4..fde54f0de6 100644 --- a/panda/src/express/multifile.h +++ b/panda/src/express/multifile.h @@ -54,6 +54,8 @@ PUBLISHED: INLINE bool is_write_valid() const; INLINE bool needs_repack() const; + INLINE time_t get_timestamp() const; + void set_scale_factor(size_t scale_factor); INLINE size_t get_scale_factor() const; @@ -77,6 +79,7 @@ PUBLISHED: void remove_subfile(int index); const string &get_subfile_name(int index) const; size_t get_subfile_length(int index) const; + time_t get_subfile_timestamp(int index) const; bool is_subfile_compressed(int index) const; bool is_subfile_encrypted(int index) const; size_t get_subfile_internal_length(int index) const; @@ -131,6 +134,7 @@ private: streampos _data_start; size_t _data_length; size_t _uncompressed_length; + time_t _timestamp; istream *_source; Filename _source_filename; int _flags; @@ -162,6 +166,8 @@ private: streampos _last_index; bool _needs_repack; + time_t _timestamp; + bool _timestamp_dirty; size_t _scale_factor; size_t _new_scale_factor; diff --git a/panda/src/express/virtualFile.cxx b/panda/src/express/virtualFile.cxx index 86d6ec6dba..266ac221d8 100644 --- a/panda/src/express/virtualFile.cxx +++ b/panda/src/express/virtualFile.cxx @@ -177,6 +177,25 @@ get_file_size(istream *stream) const { return 0; } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFile::get_timestamp +// Access: Published, Virtual +// Description: Returns a time_t value that represents the time the +// file was last modified, to within whatever precision +// the operating system records this information (on a +// Windows95 system, for instance, this may only be +// accurate to within 2 seconds). +// +// If the timestamp cannot be determined, either because +// it is not supported by the operating system or +// because there is some error (such as file not found), +// returns 0. +//////////////////////////////////////////////////////////////////// +time_t VirtualFile:: +get_timestamp() const { + return 0; +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFile::close_read_file // Access: Public diff --git a/panda/src/express/virtualFile.h b/panda/src/express/virtualFile.h index a302702648..c7995bf970 100644 --- a/panda/src/express/virtualFile.h +++ b/panda/src/express/virtualFile.h @@ -58,6 +58,7 @@ PUBLISHED: virtual istream *open_read_file(bool auto_unwrap) const; void close_read_file(istream *stream) const; virtual streampos get_file_size(istream *stream) const; + virtual time_t get_timestamp() const; public: INLINE void set_original_filename(const Filename &filename); diff --git a/panda/src/express/virtualFileMount.h b/panda/src/express/virtualFileMount.h index e670168b21..48dcb005d2 100644 --- a/panda/src/express/virtualFileMount.h +++ b/panda/src/express/virtualFileMount.h @@ -53,6 +53,7 @@ public: virtual istream *open_read_file(const Filename &file) const=0; void close_read_file(istream *stream) const; virtual streampos get_file_size(const Filename &file, istream *stream) const=0; + virtual time_t get_timestamp(const Filename &file) const=0; virtual bool scan_directory(vector_string &contents, const Filename &dir) const=0; diff --git a/panda/src/express/virtualFileMountMultifile.cxx b/panda/src/express/virtualFileMountMultifile.cxx index 1bc0268686..c78409c90a 100644 --- a/panda/src/express/virtualFileMountMultifile.cxx +++ b/panda/src/express/virtualFileMountMultifile.cxx @@ -102,6 +102,29 @@ get_file_size(const Filename &file, istream *) const { return _multifile->get_subfile_length(subfile_index); } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountMultifile::get_timestamp +// Access: Published, Virtual +// Description: Returns a time_t value that represents the time the +// file was last modified, to within whatever precision +// the operating system records this information (on a +// Windows95 system, for instance, this may only be +// accurate to within 2 seconds). +// +// If the timestamp cannot be determined, either because +// it is not supported by the operating system or +// because there is some error (such as file not found), +// returns 0. +//////////////////////////////////////////////////////////////////// +time_t VirtualFileMountMultifile:: +get_timestamp(const Filename &file) const { + int subfile_index = _multifile->find_subfile(file); + if (subfile_index < 0) { + return 0; + } + return _multifile->get_subfile_timestamp(subfile_index); +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileMountMultifile::scan_directory // Access: Public, Virtual diff --git a/panda/src/express/virtualFileMountMultifile.h b/panda/src/express/virtualFileMountMultifile.h index 2420fa402b..48ed20a8d9 100644 --- a/panda/src/express/virtualFileMountMultifile.h +++ b/panda/src/express/virtualFileMountMultifile.h @@ -46,6 +46,7 @@ public: virtual istream *open_read_file(const Filename &file) const; virtual streampos get_file_size(const Filename &file, istream *stream) const; + virtual time_t get_timestamp(const Filename &file) const; virtual bool scan_directory(vector_string &contents, const Filename &dir) const; diff --git a/panda/src/express/virtualFileMountSystem.cxx b/panda/src/express/virtualFileMountSystem.cxx index 16b360690f..db0bb3db48 100644 --- a/panda/src/express/virtualFileMountSystem.cxx +++ b/panda/src/express/virtualFileMountSystem.cxx @@ -141,6 +141,26 @@ get_file_size(const Filename &, istream *stream) const { return size; } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountSystem::get_timestamp +// Access: Published, Virtual +// Description: Returns a time_t value that represents the time the +// file was last modified, to within whatever precision +// the operating system records this information (on a +// Windows95 system, for instance, this may only be +// accurate to within 2 seconds). +// +// If the timestamp cannot be determined, either because +// it is not supported by the operating system or +// because there is some error (such as file not found), +// returns 0. +//////////////////////////////////////////////////////////////////// +time_t VirtualFileMountSystem:: +get_timestamp(const Filename &file) const { + Filename pathname(_physical_filename, file); + return pathname.get_timestamp(); +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileMountSystem::scan_directory // Access: Public, Virtual diff --git a/panda/src/express/virtualFileMountSystem.h b/panda/src/express/virtualFileMountSystem.h index 94258917e2..a80b202be8 100644 --- a/panda/src/express/virtualFileMountSystem.h +++ b/panda/src/express/virtualFileMountSystem.h @@ -42,6 +42,7 @@ public: virtual istream *open_read_file(const Filename &file) const; virtual streampos get_file_size(const Filename &file, istream *stream) const; + virtual time_t get_timestamp(const Filename &file) const; virtual bool scan_directory(vector_string &contents, const Filename &dir) const; diff --git a/panda/src/express/virtualFileSimple.cxx b/panda/src/express/virtualFileSimple.cxx index 070ac5597e..ce00ba3136 100644 --- a/panda/src/express/virtualFileSimple.cxx +++ b/panda/src/express/virtualFileSimple.cxx @@ -124,6 +124,25 @@ get_file_size(istream *stream) const { return _mount->get_file_size(_local_filename, stream); } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSimple::get_timestamp +// Access: Published, Virtual +// Description: Returns a time_t value that represents the time the +// file was last modified, to within whatever precision +// the operating system records this information (on a +// Windows95 system, for instance, this may only be +// accurate to within 2 seconds). +// +// If the timestamp cannot be determined, either because +// it is not supported by the operating system or +// because there is some error (such as file not found), +// returns 0. +//////////////////////////////////////////////////////////////////// +time_t VirtualFileSimple:: +get_timestamp() const { + return _mount->get_timestamp(_local_filename); +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileSimple::scan_local_directory // Access: Protected, Virtual diff --git a/panda/src/express/virtualFileSimple.h b/panda/src/express/virtualFileSimple.h index 831643cbbf..4c9d054047 100644 --- a/panda/src/express/virtualFileSimple.h +++ b/panda/src/express/virtualFileSimple.h @@ -45,6 +45,7 @@ public: virtual istream *open_read_file(bool auto_unwrap) const; virtual streampos get_file_size(istream *stream) const; + virtual time_t get_timestamp() const; protected: virtual bool scan_local_directory(VirtualFileList *file_list,