Added file modification time support. v1.0.1
authorMichael McMaster <email@michaelmcmaster.name>
Fri, 27 May 2011 11:34:21 +0000 (21:34 +1000)
committerMichael McMaster <email@michaelmcmaster.name>
Fri, 27 May 2011 11:34:21 +0000 (21:34 +1000)
13 files changed:
Decompressor.cc
FileReader.cc
FileWriter.cc
NEWS
README
debian/changelog
debian/control
debian/rules
gzip.cc
zip.cc
zip.hh
zipper.cc
zipper.hh

index 79dbe07..94c4942 100644 (file)
@@ -65,6 +65,11 @@ namespace
                        }
                }
 
+               virtual const timeval& getModificationTime() const
+               {
+                       return m_reader->getModTime();
+               }
+
        private:
                ReaderPtr m_reader;
        };
index 1517fdd..b43c393 100644 (file)
@@ -28,6 +28,9 @@
 
 using namespace zipper;
 
+const timeval zipper::s_now = {0,0};
+
+
 class FileReader::FileReaderImpl
 {
 public:
@@ -47,7 +50,7 @@ public:
                                errMsg;
                        throw IOException(message.str());
                }
-               initSize();
+               initStats();
        }
 
        FileReaderImpl(const std::string& filename, int fd, bool closeFd) :
@@ -55,7 +58,7 @@ public:
                m_fd(fd),
                m_closeOnExit(closeFd)
        {
-               initSize();
+               initStats();
        }
 
        ~FileReaderImpl()
@@ -64,6 +67,7 @@ public:
        }
 
        const std::string& getSourceName() const { return m_filename; }
+       const timeval& getModTime() const { return m_modTime; }
 
        zsize_t getSize() const { return m_size; }
 
@@ -101,10 +105,10 @@ public:
        }
 
 private:
-       void initSize()
+       void initStats()
        {
                // If we fail here, we need to essentially run the dtor manually.
-               // initSize is called from the constructors, and so the dtor will
+               // initStats is called from the constructors, and so the dtor will
                // NOT run if an exception is thrown.
 
                struct stat buf;
@@ -124,6 +128,8 @@ private:
                else
                {
                        m_size = buf.st_size;
+                       m_modTime.tv_sec = buf.st_mtime;
+                       m_modTime.tv_usec = 0;
                }
        }
 
@@ -139,6 +145,7 @@ private:
        int m_fd;
        bool m_closeOnExit;
        zsize_t m_size;
+       timeval m_modTime;
 };
 
 FileReader::FileReader(const std::string& filename) :
@@ -162,6 +169,12 @@ FileReader::getSourceName() const
        return m_impl->getSourceName();
 }
 
+const timeval&
+FileReader::getModTime() const
+{
+       return m_impl->getModTime();
+}
+
 zsize_t
 FileReader::getSize() const
 {
index d4ccf7f..94cb48b 100644 (file)
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <unistd.h>
+#include <utime.h>
 
 using namespace zipper;
 
 class FileWriter::FileWriterImpl
 {
 public:
-       FileWriterImpl(const std::string& filename, mode_t createPermissions) :
+       FileWriterImpl(
+               const std::string& filename,
+               mode_t createPermissions,
+               const timeval& modTime
+               ) :
                m_filename(filename),
+               m_modTime(modTime),
                m_fd(-1),
-               m_closeOnExit(true)
+               m_closeOnExit(true),
+               m_setModTimeOnExit(true)
        {
                m_fd =
                        ::open(
@@ -57,13 +64,30 @@ public:
        FileWriterImpl(const std::string& filename, int fd, bool closeFd) :
                m_filename(filename),
                m_fd(fd),
-               m_closeOnExit(closeFd)
+               m_closeOnExit(closeFd),
+               m_setModTimeOnExit(false)
        {
        }
 
        ~FileWriterImpl()
        {
                close();
+
+               if (m_setModTimeOnExit)
+               {
+                       struct timeval times[2];
+                       if (s_now.tv_sec == m_modTime.tv_sec)
+                       {
+                               gettimeofday(&times[0], NULL);
+                               times[1] = times[0];
+                       }
+                       else
+                       {
+                               times[0] = m_modTime;
+                               times[1] = m_modTime;
+                       }
+                       utimes(m_filename.c_str(), times);
+               }
        }
 
        virtual void writeData(
@@ -113,12 +137,17 @@ private:
        }
 
        std::string m_filename;
+       timeval m_modTime;
        int m_fd;
        bool m_closeOnExit;
+       bool m_setModTimeOnExit;
 };
 
-FileWriter::FileWriter(const std::string& filename, mode_t createPermissions) :
-       m_impl(new FileWriterImpl(filename, createPermissions))
+FileWriter::FileWriter(
+       const std::string& filename,
+       mode_t createPermissions,
+       const timeval& modTime) :
+       m_impl(new FileWriterImpl(filename, createPermissions, modTime))
 {
 }
 
diff --git a/NEWS b/NEWS
index a236cc5..967e5e1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,7 @@
-2011-05-22  Version 1.0.1
+2011-05-27  Version 1.0.1
        - Removed private classes from doxygen output
-       - Removed private symbols from shared library
+       - Added the zipper utility executable
+       - Added timestamp support
 
 2011-05-21     Version 1.0.0
        - Initial release
diff --git a/README b/README
index 5c22b5d..ea954f4 100644 (file)
--- a/README
+++ b/README
@@ -9,8 +9,8 @@ libzipper currently supports plain, zip, and gzip formats.
 
 libzipper is not a general-purpose archive management library, as it
 does not provide access to the filesystem attributes of each file.
-(ie. libzipper does not support the concepts of file owner, group,
-permissions, or timestamps.
+(ie. libzipper does not support the concepts of file owner, group or
+permissions.
 
 Missing Features
        - zip64 support (for >4Gb zip files)
index 57966f9..144992c 100644 (file)
@@ -2,8 +2,9 @@ libzipper1 (1.0.1-1) unstable; urgency=low
 
   * Fixed packaging to meet debian policy (renamed package, fixed shlibs file
     and symlinks
+  * Created the libzipper-tools and libzipper-docs binary packages.
 
- -- Michael McMaster <michael@codesrc.com>  Sat, 22 May 2011 21:48:11 +1000
+ -- Michael McMaster <michael@codesrc.com>  Fri, 27 May 2011 19:41:00 +1000
 
 libzipper1 (1.0.0-1) unstable; urgency=low
 
index 863f2f8..458a2c1 100644 (file)
@@ -1,7 +1,7 @@
 Source: libzipper1
 Priority: optional
 Maintainer: Michael McMaster <michael@codesrc.com>
-Build-Depends: debhelper (>= 7.0.50~), autotools-dev, cdbs, zlib1g-dev, doxygen, graphviz
+Build-Depends: debhelper (>= 7.0.50~), autotools-dev, cdbs, pkg-config, zlib1g-dev, doxygen, texlive-font-utils, graphviz
 Standards-Version: 3.9.2
 Section: libs
 Homepage: http://www.codesrc.com/src/libzipper
@@ -12,6 +12,7 @@ Package: libzipper-dev
 Section: libdevel
 Architecture: any
 Depends: libzipper1 (= ${binary:Version}), ${misc:Depends}
+Recommends: pkg-config
 Suggests: libzipper-doc
 Homepage: http://www.codesrc.com/src/libzipper
 Description: simple interface for reading and writing compressed files
@@ -55,7 +56,7 @@ Description: utilities for reading and writing compressed files
 Package: libzipper-doc
 Section: doc
 Architecture: any
-Depends: libjs-jquery, ${misc:Depends}
+Depends: ${misc:Depends}
 Homepage: http://www.codesrc.com/src/libzipper
 Description: simple interface for reading and writing compressed files
  libzipper offers a flexible C++ interface for reading and writing compressed
index cb7dd9c..844da17 100755 (executable)
@@ -5,9 +5,19 @@ DEB_DH_MAKESHLIBS_ARGS_ALL=--version-info "libzipper1 (>= 1.0.1)"
 include /usr/share/cdbs/1/rules/debhelper.mk
 include /usr/share/cdbs/1/class/autotools.mk
 
-# Make use of the libjs-jquery package
-# Required by lintian rule: embedded-javascript-library
+# Doxygen may copy an old version of jquery.js into the HTML documentation
+# tree, even though it is not used.  This causes the
+# "embedded-javascript-library" lintian error.
+# Delete the file to avoid creating a dependency on the libjs-jquery package in
+# order to resolve the lintian error.
+# This is fixed in doxygen 1.7.4-2
 install/libzipper-doc::
-       rm $(DEB_DESTDIR)/usr/share/doc/libzipper/html/jquery.js
-       ln -s ../../../javascript/jquery/jquery.js $(DEB_DESTDIR)/usr/share/doc/libzipper/html/jquery.js
+       if [ -f $(DEB_DESTDIR)/usr/share/doc/libzipper/html/jquery.js ]; then \
+               if grep jquery.js $(DEB_DESTDIR)/usr/share/doc/libzipper/html/*.html; then \
+                       echo "ERROR: doxygen is making use of jquery.js in the HTML "; \
+                       echo "documentation. Please add a dependency on libjs-jquery."; \
+               else \
+                       rm $(DEB_DESTDIR)/usr/share/doc/libzipper/html/jquery.js; \
+               fi \
+       fi
 
diff --git a/gzip.cc b/gzip.cc
index c1b9dd4..7ff1642 100644 (file)
--- a/gzip.cc
+++ b/gzip.cc
@@ -56,12 +56,15 @@ namespace
                FileEntry(
                        const ReaderPtr& reader,
                        zsize_t dataOffset,
-                       const std::string& filename
+                       const std::string& filename,
+                       time_t modTime
                        ) :
                        m_reader(reader),
                        m_dataOffset(dataOffset),
                        m_fileName(filename)
                {
+                       m_modTime.tv_sec = modTime;
+                       m_modTime.tv_usec = 0;
                }
 
                virtual bool isDecompressSupported() const
@@ -76,6 +79,7 @@ namespace
 
                virtual zsize_t getCompressedSize() const { return -1; }
                virtual zsize_t getUncompressedSize() const { return -1; }
+               virtual const timeval& getModificationTime() const { return m_modTime; }
 
                virtual void decompress(Writer& writer)
                {
@@ -104,6 +108,7 @@ namespace
                ReaderPtr m_reader;
                zsize_t m_dataOffset;
                std::string m_fileName;
+               timeval m_modTime;
        };
 }
 
@@ -134,6 +139,8 @@ zipper::ungzip(const ReaderPtr& reader)
        bool fcomment = (header[3] & 0x10) != 0;
        bool fhcrc = (header[3] & 2) != 0;
 
+       time_t modTime = read32_le(&header[4]);
+
        size_t offset(10);
 
        if (fextra)
@@ -176,7 +183,8 @@ zipper::ungzip(const ReaderPtr& reader)
 
        std::vector<CompressedFilePtr> result;
        result.push_back(
-               CompressedFilePtr(new FileEntry(reader, offset, embeddedName)));
+               CompressedFilePtr(
+                       new FileEntry(reader, offset, embeddedName, modTime)));
 
        return result;
 }
@@ -229,6 +237,9 @@ zipper::gzip(
        {
                uint8_t buffer[ChunkSize];
                memcpy(buffer, Header, sizeof(Header));
+
+               write32_le(reader.getModTime().tv_sec, &buffer[4]); // modtime
+
                zsize_t pos(sizeof(Header));
 
                zsize_t filenameSize(filename.size());
diff --git a/zip.cc b/zip.cc
index 4171517..caca8ef 100644 (file)
--- a/zip.cc
+++ b/zip.cc
 #include <cassert>
 #include <iostream>
 
+#include <time.h>
 #include <string.h>
 
 using namespace zipper;
 
 namespace
 {
+       time_t convertDosDateTime(uint16_t date, uint16_t time)
+       {
+               struct tm parts;
+               memset(&parts, 0, sizeof(parts));
+               parts.tm_sec = time & 0x1F;
+               parts.tm_min = (time & 0x7E0) >> 5;
+               parts.tm_hour = (time  >> 11);
+               parts.tm_mday = date & 0x1F;
+               parts.tm_mon = ((date & 0x1E0) >> 5) - 1;
+               parts.tm_year = (date >> 9) + 80;
+               return mktime(&parts);
+       }
+
+       void convertDosDateTime(time_t in, uint16_t& date, uint16_t& time)
+       {
+               struct tm buf;
+               struct tm* parts(localtime_r(&in, &buf));
+
+               time =
+                       parts->tm_sec +
+                       (parts->tm_min << 5) +
+                       (parts->tm_hour << 11);
+
+               date =
+                       parts->tm_mday +
+                       ((parts->tm_mon + 1) << 5) +
+                       ((parts->tm_year - 80) << 9);
+       }
+
        class FileEntry : public CompressedFile
        {
        public:
@@ -42,6 +72,7 @@ namespace
                        uint32_t crc,
                        zsize_t compressedSize,
                        zsize_t uncompressedSize,
+                       time_t modTime,
                        zsize_t localHeaderOffset,
                        std::string fileName
                        ) :
@@ -55,6 +86,8 @@ namespace
                        m_localHeaderOffset(localHeaderOffset),
                        m_fileName(fileName)
                {
+                       m_modTime.tv_sec = modTime;
+                       m_modTime.tv_usec = 0;
                }
 
                virtual bool isDecompressSupported() const
@@ -75,6 +108,8 @@ namespace
                        return m_uncompressedSize;
                }
 
+               virtual const timeval& getModificationTime() const { return m_modTime; }
+
                virtual void decompress(Writer& writer)
                {
                        enum
@@ -175,6 +210,7 @@ namespace
                uint32_t m_crc;
                zsize_t m_compressedSize;
                zsize_t m_uncompressedSize;
+               timeval m_modTime;
                zsize_t m_localHeaderOffset;
                std::string m_fileName;
        };
@@ -281,6 +317,8 @@ namespace
                        uint16_t versionNeeded(read16_le(buffer, pos + 6));
                        uint16_t gpFlag(read16_le(buffer, pos + 8));
                        uint16_t compressionMethod(read16_le(buffer, pos + 10));
+                       uint16_t modTime(read16_le(buffer, pos + 12));
+                       uint16_t modDate(read16_le(buffer, pos + 14));
                        uint32_t crc(read32_le(buffer, pos + 16));
                        uint32_t compressedSize(read32_le(buffer, pos + 20));
                        uint32_t uncompressedSize(read32_le(buffer, pos + 24));
@@ -311,6 +349,7 @@ namespace
                                                crc,
                                                compressedSize,
                                                uncompressedSize,
+                                               convertDosDateTime(modDate, modTime),
                                                localHeaderOffset,
                                                fileName
                                                )
@@ -336,7 +375,7 @@ zipper::zip(
        {
                ChunkSize = 64*1024,
                WindowBits = 15,
-               CRC32Pos = 14
+               TimePos = 10
        };
 
        static uint8_t Header[] =
@@ -388,12 +427,16 @@ zipper::zip(
                outRecord.crc32);
 
        // Go back and complete the header.
-       uint8_t trailer[12];
-       write32_le(outRecord.crc32, &trailer[0]);
-       write32_le(outRecord.compressedSize, &trailer[4]);
-       write32_le(outRecord.uncompressedSize, &trailer[8]);
+       convertDosDateTime(
+               reader.getModTime().tv_sec, outRecord.dosDate, outRecord.dosTime);
+       uint8_t trailer[16];
+       write16_le(outRecord.dosTime, &trailer[0]);
+       write16_le(outRecord.dosDate, &trailer[2]);
+       write32_le(outRecord.crc32, &trailer[4]);
+       write32_le(outRecord.compressedSize, &trailer[8]);
+       write32_le(outRecord.uncompressedSize, &trailer[12]);
        writer->writeData(
-               outRecord.localHeaderOffset + CRC32Pos, sizeof(trailer), &trailer[0]);
+               outRecord.localHeaderOffset + TimePos, sizeof(trailer), &trailer[0]);
 }
 
 void
@@ -412,9 +455,7 @@ zipper::zipFinalise(
                20, 0x00, // Version (2.0)
                20, 0x00, // Version Needed to extract (2.0)
                0,0, // gp flag.
-               8,0, // deflate method
-               0,0, // file time
-               0,0 // file date
+               8,0 // deflate method
        };
 
        zsize_t outPos(writer->getSize());
@@ -426,6 +467,11 @@ zipper::zipFinalise(
                memcpy(buffer, FileHeader, sizeof(FileHeader));
                zsize_t pos(sizeof(FileHeader));
 
+               write16_le(records[i].dosTime, &buffer[pos]);
+               pos += 2;
+               write16_le(records[i].dosDate, &buffer[pos]);
+               pos += 2;
+
                write32_le(records[i].crc32, &buffer[pos]);
                pos += 4;
 
diff --git a/zip.hh b/zip.hh
index c6df4b0..94bb7d4 100644 (file)
--- a/zip.hh
+++ b/zip.hh
@@ -28,6 +28,8 @@ namespace zipper
                uint32_t crc32;
                zsize_t compressedSize;
                zsize_t uncompressedSize;
+               uint16_t dosDate;
+               uint16_t dosTime;
                std::string filename;
        };
 
index 9f57ff2..64f1014 100644 (file)
--- a/zipper.cc
+++ b/zipper.cc
@@ -39,7 +39,7 @@ static void usage()
                "under certain conditions.\n\n" <<
 
                "Usage: \n" <<
-                       argv0 << " {zip|gzip} archive [file...]\n" <<
+                       argv0 << " {zip|gzip} archive file [files...]\n" <<
                        argv0 << " {unzip|gunzip} archive" << std::endl;
 }
 
@@ -148,7 +148,8 @@ command_extract(const std::deque<std::string>& options)
 
                        builtPath << '/';
                }
-               FileWriter writer(entries[f]->getPath(), 0660);
+               FileWriter writer(
+                       entries[f]->getPath(), 0660, entries[f]->getModificationTime());
                entries[f]->decompress(writer);
        }
 }
index 35fc230..50e6ec9 100644 (file)
--- a/zipper.hh
+++ b/zipper.hh
@@ -26,6 +26,7 @@
 #include <cstdint>
 
 #include <sys/stat.h> // For mode_t
+#include <sys/time.h> // For timeval
 
 /**
 \mainpage libzipper C++ (de)compression library
@@ -42,8 +43,8 @@ are compressed to save space.
 
 libzipper is not a general-purpose archive management library, as it
 does not provide access to the filesystem attributes of each file.
-(ie. libzipper does not support the concepts of file owner, group,
-permissions, or timestamps.
+(ie. libzipper does not support the concepts of file owner, group or
+permissions.
 
 \section formats Supported Formats
 <ul>
@@ -111,6 +112,11 @@ public:
                return Name;
        }
 
+       virtual const timeval& getModTime() const
+       {
+               return zipper::s_now;
+       }
+
        virtual zsize_t getSize() const { return m_data.size(); }
 
        virtual void readData(zsize_t offset, zsize_t bytes, uint8_t* dest) const
@@ -216,6 +222,10 @@ namespace zipper
                uint32_t capabilities;
        };
 
+       /// \brief When passed as a method parameter, it requests that the
+       /// current time be used instead.
+       extern const timeval s_now;
+
        /// \brief Returns the capability details of the given format.
        const Container& getContainer(ContainerFormat format);
 
@@ -285,6 +295,11 @@ namespace zipper
                /// the input filename.
                virtual const std::string& getSourceName() const = 0;
 
+               /// Return the last-modified timestamp of the data.
+               /// If the special s_now value is returned, the current time should be
+               /// used instead.
+               virtual const timeval& getModTime() const = 0;
+
                /// Returns the number of bytes available via readData()
                ///
                /// \invariant getSize() is stable throughout the lifetime
@@ -306,6 +321,7 @@ namespace zipper
                virtual void readData(
                        zsize_t offset, zsize_t bytes, uint8_t* dest
                        ) const = 0;
+
        };
 
        /// \brief FileReader is a file-based implementation of the Reader
@@ -335,6 +351,9 @@ namespace zipper
                virtual const std::string& getSourceName() const;
 
                /// Inherited from Reader
+               virtual const timeval& getModTime() const;
+
+               /// Inherited from Reader
                virtual zsize_t getSize() const;
 
                /// Inherited from Reader
@@ -404,7 +423,15 @@ namespace zipper
                ///
                /// \param createPermissions The permissions set on the file if it is to
                /// be created.
-               FileWriter(const std::string& filename, mode_t createPermissions);
+               ///
+               /// \param modTime Set a specific modification time on the created file.
+               /// If the special s_now value is provided, the current time will be
+               /// used.
+               ///
+               FileWriter(
+                       const std::string& filename,
+                       mode_t createPermissions = 0664,
+                       const timeval& modTime = s_now);
 
                /// Write data to the supplied file.
                ///
@@ -478,6 +505,9 @@ namespace zipper
                /// getUncompressedSize() will return -1 of the FileSize capability
                /// bit of the container is false.
                virtual zsize_t getUncompressedSize() const = 0;
+
+               /// Return the modification time of the original file
+               virtual const timeval& getModificationTime() const = 0;
        };
        /// \typedef CompressedFilePtr
        /// A shared pointer to a CompressedFile