From: Vadim Zeitlin Date: Wed, 10 Nov 2004 23:58:38 +0000 (+0000) Subject: added ZIP classes by M.J.Wetherell (patch 1030239) X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/00375592f92f68c4ca3f44d8e839bcfd47adc4e1 added ZIP classes by M.J.Wetherell (patch 1030239) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@30436 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/build/bakefiles/common.bkl b/build/bakefiles/common.bkl index b3f93fa901..d0efe6ec53 100644 --- a/build/bakefiles/common.bkl +++ b/build/bakefiles/common.bkl @@ -473,7 +473,6 @@ $(TAB)copy "$(DOLLAR)(InputPath)" $(SETUPHDIR)\wx\setup.h on wxprec_$(id) - src/common/unzip.c src/common/extended.c src/msw/gsocket.cpp src/msw/gsockmsw.cpp diff --git a/build/bakefiles/files.bkl b/build/bakefiles/files.bkl index 33079e69fe..38638abd71 100644 --- a/build/bakefiles/files.bkl +++ b/build/bakefiles/files.bkl @@ -214,7 +214,7 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file! src/common/textfile.cpp src/common/tokenzr.cpp src/common/txtstrm.cpp - src/common/unzip.c + src/common/archive.cpp src/common/uri.cpp src/common/variant.cpp src/common/wfstream.cpp diff --git a/docs/changes.txt b/docs/changes.txt index ef2582f98f..7a503dfa36 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -217,6 +217,7 @@ OTHER CHANGES All: +- new classes for reading and writing ZIP files (M.J.Wetherell) - Norwegian (BokmÃ¥l) translation added (Hans F. Nordhaug) - wxDynamicLibrary::HasSymbol() added - added wxTextInputStream::operator>>(wchar_t) for compilers which support this diff --git a/docs/latex/wx/arc.tex b/docs/latex/wx/arc.tex new file mode 100644 index 0000000000..a7f3fdc7ce --- /dev/null +++ b/docs/latex/wx/arc.tex @@ -0,0 +1,433 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Name: arc.tex +%% Purpose: Overview of the archive classes +%% Author: M.J.Wetherell +%% RCS-ID: $Id$ +%% Copyright: 2004 M.J.Wetherell +%% License: wxWidgets license +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\section{Archive formats such as zip}\label{wxarc} + +The archive classes handle archive formats such as zip, tar, rar and cab. +Currently only the wxZip classes are included. + +For each archive type, there are the following classes (using zip here +as an example): + +\begin{twocollist}\twocolwidtha{4cm} +\twocolitem{\helpref{wxZipInputStream}{wxzipinputstream}}{Input stream} +\twocolitem{\helpref{wxZipOutputStream}{wxzipoutputstream}}{Output stream} +\twocolitem{\helpref{wxZipEntry}{wxzipentry}}{Holds the meta-data for an +entry (e.g. filename, timestamp, etc.)} +\end{twocollist} + +There are also abstract wxArchive classes that can be used to write code +that can handle any of the archive types, +see '\helpref{Generic archive programming}{wxarcgeneric}'. +Also see \helpref{wxFileSystem}{fs} for a higher level interface that +can handle archive files in a generic way. + +The classes are designed to handle archives on both seekable streams such +as disk files, or non-seekable streams such as pipes and sockets +(see '\helpref{Archives on non-seekable streams}{wxarcnoseek}'). + +\wxheading{See also} + +\helpref{wxFileSystem}{fs} + + +\subsection{Creating an archive}\label{wxarccreate} + +\helpref{Archive formats such as zip}{wxarc} + +Call \helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry} to +create each new entry in the archive, then write the entry's data. +Another call to PutNextEntry() closes the current entry and begins the next. + +For example: + +\begin{verbatim} + wxFFileOutputStream out(_T("test.zip")); + wxZipOutputStream zip(out); + wxTextOutputStream txt(zip); + + zip.PutNextEntry(_T("entry1.txt")); + txt << _T("Some text for entry1\n"); + + zip.PutNextEntry(_T("entry2.txt")); + txt << _T("Some text for entry2\n"); + +\end{verbatim} + + +\subsection{Extracting an archive}\label{wxarcextract} + +\helpref{Archive formats such as zip}{wxarc} + +\helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} returns an +entry object containing the meta-data for the next entry in the archive +(and gives away ownership). Reading from the input stream then returns +the entry's data. Eof() becomes true after an attempt has been made to +read past the end of the entry's data. + +When there are no more entries, GetNextEntry() returns NULL and sets Eof(). + +\begin{verbatim} + wxDEFINE_SCOPED_PTR_TYPE(wxZipEntry); + wxZipEntryPtr entry; + + wxFFileInputStream in(_T("test.zip")); + wxZipInputStream zip(in); + wxTextInputStream txt(zip); + wxString data; + + while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) + { + wxString name = entry->GetName(); // access meta-data + txt >> data; // access data + } + +\end{verbatim} + + +\subsection{Modifying an archive}\label{wxarcmodify} + +\helpref{Archive formats such as zip}{wxarc} + +To modify an existing archive, write a new copy of the archive to a new file, +making any necessary changes along the way and transferring any unchanged +entries using \helpref{CopyEntry()}{wxarchiveoutputstreamcopyentry}. +For archive types which compress entry data, CopyEntry() is likely to be +much more efficient than transferring the data using Read() and Write() +since it will copy them without decompressing and recompressing them. + +In general modifications are not possible without rewriting the archive, +though it may be possible in some limited cases. Even then, rewriting +the archive is usually a better choice since a failure can be handled +without losing the whole archive. + +For example to delete all entries matching the pattern "*.txt": + +\begin{verbatim} + wxFFileInputStream in(_T("in.zip")); + wxFFileOutputStream out(_T("out.zip")); + + wxZipInputStream inzip(in); + wxZipOutputStream outzip(out); + wxZipEntryPtr entry; + + // transfer any meta-data for the archive as a whole (the zip comment + // in the case of zip) + outzip.CopyArchiveMetaData(inzip); + + // call CopyEntry for each entry except those matching the pattern + while (entry.reset(inzip.GetNextEntry()), entry.get() != NULL) + if (!entry->GetName().Matches(_T("*.txt"))) + if (!outzip.CopyEntry(entry.release(), inzip)) + break; + + bool success = inzip.Eof() && outzip.Close(); + +\end{verbatim} + + +\subsection{Looking up an archive entry by name}\label{wxarcbyname} + +\helpref{Archive formats such as zip}{wxarc} + +Also see \helpref{wxFileSystem}{fs} for a higher level interface that is +more convenient for accessing archive entries by name. + +To open just one entry in an archive, the most efficient way is +to simply search for it linearly by calling + \helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} until the +required entry is found. This works both for archives on seekable and +non-seekable streams. + +The format of filenames in the archive is likely to be different +from the local filename format. For example zips and tars use +unix style names, with forward slashes as the path separator, +and absolute paths are not allowed. So if on Windows the file +"C:$\backslash$MYDIR$\backslash$MYFILE.TXT" is stored, then when reading +the entry back \helpref{GetName()}{wxarchiveentryname} will return +"MYDIR$\backslash$MYFILE.TXT". The conversion into the internal format +and back has lost some information. + +So to avoid ambiguity when searching for an entry matching a local name, +it is better to convert the local name to the archive's internal format +and search for that: + +\begin{verbatim} + wxDEFINE_SCOPED_PTR_TYPE(wxZipEntry); + wxZipEntryPtr entry; + + // convert the local name we are looking for into the internal format + wxString name = wxZipEntry::GetInternalName(localname); + + // open the zip + wxFFileInputStream in(_T("test.zip")); + wxZipInputStream zip(in); + + // call GetNextEntry() until the required internal name is found + do { + entry.reset(zip.GetNextEntry()); + } + while (entry.get() != NULL && entry->GetInternalName() != name); + + if (entry.get() != NULL) { + // read the entry's data... + } + +\end{verbatim} + +To access several entries randomly, it is most efficient to transfer the +entire catalogue of entries to a container such as a std::map or a + \helpref{wxHashMap}{wxhashmap} then entries looked up by name can be +opened using the \helpref{OpenEntry()}{wxarchiveinputstreamopenentry} method. + +\begin{verbatim} + WX_DECLARE_STRING_HASH_MAP(wxZipEntry*, ZipCatalog); + ZipCatalog::iterator it; + wxZipEntry *entry; + ZipCatalog cat; + + // open the zip + wxFFileInputStream in(_T("test.zip")); + wxZipInputStream zip(in); + + // load the zip catalog + while ((entry = zip.GetNextEntry()) != NULL) { + wxZipEntry*& current = cat[entry->GetInternalName()]; + // some archive formats can have multiple entries with the same name + // (e.g. tar) though it is an error in the case of zip + delete current; + current = entry; + } + + // open an entry by name + if ((it = cat.find(wxZipEntry::GetInternalName(localname))) != cat.end()) { + zip.OpenEntry(*it->second); + // ... now read entry's data + } + +\end{verbatim} + +To open more than one entry simultaneously you need more than one +underlying stream on the same archive: + +\begin{verbatim} + // opening another entry without closing the first requires another + // input stream for the same file + wxFFileInputStream in2(_T("test.zip")); + wxZipInputStream zip2(in2); + if ((it = cat.find(wxZipEntry::GetInternalName(local2))) != cat.end()) + zip2.OpenEntry(*it->second); + +\end{verbatim} + + +\subsection{Generic archive programming}\label{wxarcgeneric} + +\helpref{Archive formats such as zip}{wxarc} + +Also see \helpref{wxFileSystem}{fs} for a higher level interface that +can handle archive files in a generic way. + +The specific archive classes, such as the wxZip classes, inherit from +the following abstract classes which can be used to write code that can +handle any of the archive types: + +\begin{twocollist}\twocolwidtha{5cm} +\twocolitem{\helpref{wxArchiveInputStream}{wxarchiveinputstream}}{Input stream} +\twocolitem{\helpref{wxArchiveOutputStream}{wxarchiveoutputstream}}{Output stream} +\twocolitem{\helpref{wxArchiveEntry}{wxarchiveentry}}{Holds the meta-data for an +entry (e.g. filename)} +\end{twocollist} + +In order to able to write generic code it's necessary to be able to create +instances of the classes without knowing which archive type is being used. +So there is a class factory for each archive type, derived from + \helpref{wxArchiveClassFactory}{wxarchiveclassfactory}, which can create +the other classes. + +For example, given {\it wxArchiveClassFactory* factory}: + +\begin{verbatim} + // create streams without knowing their type + wxArchiveInputStreamPtr inarc(factory->NewStream(in)); + wxArchiveOutputStreamPtr outarc(factory->NewStream(out)); + + // create an empty entry object + wxArchiveEntryPtr entry(factory->NewEntry()); + +\end{verbatim} + +The class factory itself can either be created explicitly: + +\begin{verbatim} + wxArchiveClassFactory *factory = new wxZipClassFactory; + +\end{verbatim} + +or using wxWidgets' \helpref{RTTI}{runtimeclassoverview}: + +\begin{verbatim} +wxArchiveClassFactory *MakeFactory(const wxString& type) +{ + wxString name = _T("wx") + type.Left(1).Upper() + + type.Mid(1).Lower() + _T("ClassFactory"); + + wxObject *pObj = wxCreateDynamicObject(name); + wxArchiveClassFactory *pcf = wxDynamicCast(pObj, wxArchiveClassFactory); + + if (!pcf) { + wxLogError(_T("can't handle '%s' archives"), type.c_str()); + delete pObj; + } + + return pcf; +} + +\end{verbatim} + + +\subsection{Archives on non-seekable streams}\label{wxarcnoseek} + +\helpref{Archive formats such as zip}{wxarc} + +In general, handling archives on non-seekable streams is done in the same +way as for seekable streams, with a few caveats. + +The main limitation is that accessing entries randomly using + \helpref{OpenEntry()}{wxarchiveinputstreamopenentry} +is not possible, the entries can only be accessed sequentially in the order +they are stored within the archive. + +For each archive type, there will also be other limitations which will +depend on the order the entries' meta-data is stored within the archive. +These are not too difficult to deal with, and are outlined below. + +\wxheading{PutNextEntry and the entry size} + +When writing archives, some archive formats store the entry size before +the entry's data (tar has this limitation, zip doesn't). In this case +the entry's size must be passed to + \helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry} or an error +occurs. + +This is only an issue on non-seekable streams, since otherwise the archive +output stream can seek back and fix up the header once the size of the +entry is known. + +For generic programming, one way to handle this is to supply the size +whenever it is known, and rely on the error message from the output +stream when the operation is not supported. + +\wxheading{GetNextEntry and the weak reference mechanism} + +Some archive formats do not store all an entry's meta-data before the +entry's data (zip is an example). In this case, when reading from a +non-seekable stream, \helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} +can only return a partially populated \helpref{wxArchiveEntry}{wxarchiveentry} +object - not all the fields are set. + +The input stream then keeps a weak reference to the entry object and +updates it when more meta-data becomes available. A weak reference being +one that does not prevent you from deleting the wxArchiveEntry object - the +input stream only attempts to update it if it is still around. + +The documentation for each archive entry type gives the details +of what meta-data becomes available and when. For generic programming, +when the worst case must be assumed, you can rely on all the fields +of wxArchiveEntry being fully populated when GetNextEntry() returns, +with the the following exceptions: + +\begin{twocollist}\twocolwidtha{3cm} +\twocolitem{\helpref{GetSize()}{wxarchiveentrysize}}{Guaranteed to be +available after the entry has been read to \helpref{Eof()}{wxinputstreameof}, +or \helpref{CloseEntry()}{wxarchiveinputstreamcloseentry} has been called} +\twocolitem{\helpref{IsReadOnly()}{wxarchiveentryisreadonly}}{Guaranteed to +be available after the end of the archive has been reached, i.e. after +GetNextEntry() returns NULL and Eof() is true} +\end{twocollist} + +This mechanism allows \helpref{CopyEntry()}{wxarchiveoutputstreamcopyentry} +to always fully preserve entries' meta-data. No matter what order order +the meta-data occurs within the archive, the input stream will always +have read it before the output stream must write it. + +\wxheading{wxArchiveNotifier} + +Notifier objects can be used to get a notification whenever an input +stream updates a \helpref{wxArchiveEntry}{wxarchiveentry} object's data +via the weak reference mechanism. + +Consider the following code which renames an entry in an archive. +This is the usual way to modify an entry's meta-data, simply set the +required field before writing it with + \helpref{CopyEntry()}{wxarchiveoutputstreamcopyentry}: + +\begin{verbatim} + wxArchiveInputStreamPtr arc(factory->NewStream(in)); + wxArchiveOutputStreamPtr outarc(factory->NewStream(out)); + wxArchiveEntryPtr entry; + + outarc->CopyArchiveMetaData(*arc); + + while (entry.reset(arc->GetNextEntry()), entry.get() != NULL) { + if (entry->GetName() == from) + entry->SetName(to); + if (!outarc->CopyEntry(entry.release(), *arc)) + break; + } + + bool success = arc->Eof() && outarc->Close(); + +\end{verbatim} + +However, for non-seekable streams, this technique cannot be used for +fields such as \helpref{IsReadOnly()}{wxarchiveentryisreadonly}, +which are not necessarily set when + \helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} returns. In +this case a \helpref{wxArchiveNotifier}{wxarchivenotifier} can be used: + +\begin{verbatim} +class MyNotifier : public wxArchiveNotifier +{ +public: + void OnEntryUpdated(wxArchiveEntry& entry) { entry.SetIsReadOnly(false); } +}; + +\end{verbatim} + +The meta-data changes are done in your notifier's + \helpref{OnEntryUpdated()}{wxarchivenotifieronentryupdated} method, +then \helpref{SetNotifier()}{wxarchiveentrynotifier} is called before +CopyEntry(): + +\begin{verbatim} + wxArchiveInputStreamPtr arc(factory->NewStream(in)); + wxArchiveOutputStreamPtr outarc(factory->NewStream(out)); + wxArchiveEntryPtr entry; + MyNotifier notifier; + + outarc->CopyArchiveMetaData(*arc); + + while (entry.reset(arc->GetNextEntry()), entry.get() != NULL) { + entry->SetNotifier(notifier); + if (!outarc->CopyEntry(entry.release(), *arc)) + break; + } + + bool success = arc->Eof() && outarc->Close(); + +\end{verbatim} + +SetNotifier() calls OnEntryUpdated() immediately, then the input +stream calls it again whenever it sets more fields in the entry. Since +OnEntryUpdated() will be called at least once, this technique always +works even when it is not strictly necessary to use it. For example, +changing the entry name can be done this way too and it works on seekable +streams as well as non-seekable. + diff --git a/docs/latex/wx/archive.tex b/docs/latex/wx/archive.tex new file mode 100644 index 0000000000..304306fb05 --- /dev/null +++ b/docs/latex/wx/archive.tex @@ -0,0 +1,648 @@ +% +% automatically generated by HelpGen $Revision$ from +% wx/archive.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxArchiveClassFactory}}\label{wxarchiveclassfactory} + +An abstract base class which serves as a common interface to +archive class factories such as \helpref{wxZipClassFactory}{wxzipclassfactory}. + +For each supported archive type (such as zip) there is a class factory +derived from wxArchiveClassFactory, which allows archive objects to be +created in a generic way, without knowing the particular type of archive +being used. + +\wxheading{Derived from} + +\helpref{wxObject}{wxobject} + +\wxheading{Include files} + + + +\wxheading{See also} + +\helpref{Archive formats such as zip}{wxarc}\\ +\helpref{Generic archive programming}{wxarcgeneric}\\ +\helpref{wxArchiveEntry}{wxarchiveentry}\\ +\helpref{wxArchiveInputStream}{wxarchiveinputstream}\\ +\helpref{wxArchiveOutputStream}{wxarchiveoutputstream} + +\latexignore{\rtfignore{\wxheading{Members}}} + + +\membersection{wxArchiveClassFactory::Get/SetConv}\label{wxarchiveclassfactoryconv} + +\constfunc{wxMBConv\&}{GetConv}{\void} + +\func{void}{SetConv}{\param{wxMBConv\& }{conv}} + +The \helpref{wxMBConv}{wxmbconv} object that the created streams +will use when translating meta-data. The initial default, set by the +constructor, is wxConvLocal. + + +\membersection{wxArchiveClassFactory::GetInternalName}\label{wxarchiveclassfactorygetinternalname} + +\constfunc{wxString}{GetInternalName}{\param{const wxString\& }{name}, \param{wxPathFormat }{format = wxPATH\_NATIVE}} + +Calls the static GetInternalName() function for the archive entry type, +for example + \helpref{wxZipEntry::GetInternalName()}{wxzipentrygetinternalname}. + + +\membersection{wxArchiveClassFactory::NewEntry}\label{wxarchiveclassfactorynewentry} + +\constfunc{wxArchiveEntry*}{NewEntry}{\void} + +Create a new \helpref{wxArchiveEntry}{wxarchiveentry} object of the +appropriate type. + + +\membersection{wxArchiveClassFactory::NewStream}\label{wxarchiveclassfactorynewstream} + +\constfunc{wxArchiveInputStream*}{NewStream}{\param{wxInputStream\& }{stream}} + +\constfunc{wxArchiveOutputStream*}{NewStream}{\param{wxOutputStream\& }{stream}} + +Create a new \helpref{wxArchiveInputStream}{wxarchiveinputstream} +or \helpref{wxArchiveOutputStream}{wxarchiveoutputstream} of the +appropriate type. + + +% +% automatically generated by HelpGen $Revision$ from +% wx/archive.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxArchiveEntry}}\label{wxarchiveentry} + +An abstract base class which serves as a common interface to +archive entry classes such as \helpref{wxZipEntry}{wxzipentry}. +These hold the meta-data (filename, timestamp, etc.), for entries +in archive files such as zips and tars. + +\wxheading{Derived from} + +\helpref{wxObject}{wxobject} + +\wxheading{Include files} + + + +\wxheading{See also} + +\helpref{Archive formats such as zip}{wxarc}\\ +\helpref{Generic archive programming}{wxarcgeneric}\\ +\helpref{wxArchiveInputStream}{wxarchiveinputstream}\\ +\helpref{wxArchiveOutputStream}{wxarchiveoutputstream}\\ +\helpref{wxArchiveNotifier}{wxarchivenotifier} + +\wxheading{Non-seekable streams} + +This information applies only when reading archives from non-seekable +streams. When the stream is +seekable \helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} +returns a fully populated \helpref{wxArchiveEntry}{wxarchiveentry}. +See '\helpref{Archives on non-seekable streams}{wxarcnoseek}' for +more information. + +For generic programming, when the worst case must be assumed, you can +rely on all the fields of wxArchiveEntry being fully populated when +GetNextEntry() returns, with the the following exceptions: + +\begin{twocollist}\twocolwidtha{3cm} +\twocolitem{\helpref{GetSize()}{wxarchiveentrysize}}{Guaranteed to be +available after the entry has been read to \helpref{Eof()}{wxinputstreameof}, +or \helpref{CloseEntry()}{wxarchiveinputstreamcloseentry} has been called} +\twocolitem{\helpref{IsReadOnly()}{wxarchiveentryisreadonly}}{Guaranteed to +be available after the end of the archive has been reached, i.e. after +GetNextEntry() returns NULL and Eof() is true} +\end{twocollist} + + +\latexignore{\rtfignore{\wxheading{Members}}} + + +\membersection{wxArchiveEntry::Clone}\label{wxarchiveentryclone} + +\constfunc{wxArchiveEntry*}{Clone}{\void} + +Returns a copy of this entry object. + + +\membersection{wxArchiveEntry::Get/SetDateTime}\label{wxarchiveentrydatetime} + +\constfunc{wxDateTime}{GetDateTime}{\void} + +\func{void}{SetDateTime}{\param{const wxDateTime\& }{dt}} + +The entry's timestamp. + + +\membersection{wxArchiveEntry::GetInternalFormat}\label{wxarchiveentrygetinternalformat} + +\constfunc{wxPathFormat}{GetInternalFormat}{\void} + +Returns the path format used internally within the archive to store +filenames. + + +\membersection{wxArchiveEntry::GetInternalName}\label{wxarchiveentrygetinternalname} + +\constfunc{wxString}{GetInternalName}{\void} + +Returns the entry's filename in the internal format used within the +archive. The name can include directory components, i.e. it can be a +full path. + +The names of directory entries are returned without any trailing path +separator. This gives a canonical name that can be used in comparisons. + +\wxheading{See also} + +\helpref{Looking up an archive entry by name}{wxarcbyname} + + +\membersection{wxArchiveEntry::Get/SetName}\label{wxarchiveentryname} + +\constfunc{wxString}{GetName}{\param{wxPathFormat }{format = wxPATH\_NATIVE}} + +\func{void}{SetName}{\param{const wxString\& }{name}, \param{wxPathFormat }{format = wxPATH\_NATIVE}} + +The entry's name, by default in the native format. The name can include +directory components, i.e. it can be a full path. + +If this is a directory entry, (i.e. if \helpref{IsDir()}{wxarchiveentryisdir} +is true) then GetName() returns the name with a trailing path separator. + +Similarly, setting a name with a trailing path separator sets IsDir(). + + +\membersection{wxArchiveEntry::GetOffset}\label{wxarchiveentrygetoffset} + +\constfunc{off\_t}{GetOffset}{\void} + +Returns a numeric value unique to the entry within the archive. + + +\membersection{wxArchiveEntry::Get/SetSize}\label{wxarchiveentrysize} + +\constfunc{off\_t}{GetSize}{\void} + +\func{void}{SetSize}{\param{off\_t }{size}} + +The size of the entry's data in bytes. + + +\membersection{wxArchiveEntry::IsDir/SetIsDir}\label{wxarchiveentryisdir} + +\constfunc{bool}{IsDir}{\void} + +\func{void}{SetIsDir}{\param{bool }{isDir = true}} + +True if this is a directory entry. + +Directory entries are entries with no data, which are used to store +the meta-data of directories. They also make it possible for completely +empty directories to be stored. + +The names of entries within an archive can be complete paths, and +unarchivers typically create whatever directories are necessary as they +restore files, even if the archive contains no explicit directory entries. + + +\membersection{wxArchiveEntry::IsReadOnly/SetIsReadOnly}\label{wxarchiveentryisreadonly} + +\constfunc{bool}{IsReadOnly}{\void} + +\func{void}{SetIsReadOnly}{\param{bool }{isReadOnly = true}} + +True if the entry is a read-only file. + + +\membersection{wxArchiveEntry::Set/UnsetNotifier}\label{wxarchiveentrynotifier} + +\func{void}{SetNotifier}{\param{wxArchiveNotifier\& }{notifier}} + +\func{void}{UnsetNotifier}{\void} + +Sets the \helpref{notifier}{wxarchivenotifier} for this entry. +Whenever the \helpref{wxArchiveInputStream}{wxarchiveinputstream} updates +this entry, it will then invoke the associated +notifier's \helpref{OnEntryUpdated}{wxarchivenotifieronentryupdated} +method. + +Setting a notifier is not usually necessary. It is used to handle +certain cases when modifying an archive in a pipeline (i.e. between +non-seekable streams). + +\wxheading{See also} + +\helpref{Archives on non-seekable streams}{wxarcnoseek}\\ +\helpref{wxArchiveNotifier}{wxarchivenotifier} + + +% +% automatically generated by HelpGen $Revision$ from +% wx/archive.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxArchiveInputStream}}\label{wxarchiveinputstream} + +An abstract base class which serves as a common interface to +archive input streams such as \helpref{wxZipInputStream}{wxzipinputstream}. + +\helpref{GetNextEntry()}{wxarchiveinputstreamgetnextentry} returns an + \helpref{wxArchiveEntry}{wxarchiveentry} object containing the meta-data +for the next entry in the archive (and gives away ownership). Reading from +the wxArchiveInputStream then returns the entry's data. Eof() becomes true +after an attempt has been made to read past the end of the entry's data. +When there are no more entries, GetNextEntry() returns NULL and sets Eof(). + +\wxheading{Derived from} + +\helpref{wxFilterInputStream}{wxfilterinputstream} + +\wxheading{Include files} + + + +\wxheading{Data structures} +{\small \begin{verbatim} +typedef wxArchiveEntry entry\_type +\end{verbatim}} + +\wxheading{See also} + +\helpref{Archive formats such as zip}{wxarc}\\ +\helpref{wxArchiveEntry}{wxarchiveentry}\\ +\helpref{wxArchiveOutputStream}{wxarchiveoutputstream} + +\latexignore{\rtfignore{\wxheading{Members}}} + + +\membersection{wxArchiveInputStream::CloseEntry}\label{wxarchiveinputstreamcloseentry} + +\func{bool}{CloseEntry}{\void} + +Closes the current entry. On a non-seekable stream reads to the end of +the current entry first. + + +\membersection{wxArchiveInputStream::GetNextEntry}\label{wxarchiveinputstreamgetnextentry} + +\func{wxArchiveEntry*}{GetNextEntry}{\void} + +Closes the current entry if one is open, then reads the meta-data for +the next entry and returns it in a \helpref{wxArchiveEntry}{wxarchiveentry} +object, giving away ownership. Reading this wxArchiveInputStream then +returns the entry's data. + + +\membersection{wxArchiveInputStream::OpenEntry}\label{wxarchiveinputstreamopenentry} + +\func{bool}{OpenEntry}{\param{wxArchiveEntry\& }{entry}} + +Closes the current entry if one is open, then opens the entry specified +by the \helpref{wxArchiveEntry}{wxarchiveentry} object. + +{\it entry} must be from the same archive file that this +wxArchiveInputStream is reading, and it must be reading it from a +seekable stream. + +\wxheading{See also} + +\helpref{Looking up an archive entry by name}{wxarcbyname} + + +% +% automatically generated by HelpGen $Revision$ from +% wx/archive.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxArchiveIterator}}\label{wxarchiveiterator} + +An input iterator template class that can be used to transfer an archive's +catalogue to a container. It is only available if wxUSE\_STL is set to 1 +in setup.h, and the uses for it outlined below require a compiler which +supports member templates. + +\begin{verbatim} +template +class wxArchiveIterator +{ + // this constructor creates an 'end of sequence' object + wxArchiveIterator(); + + // template parameter 'Arc' should be the type of an archive input stream + wxArchiveIterator(Arc& arc) { + + /* ... */ +}; + +\end{verbatim} + +The first template parameter should be the type of archive input stream +(e.g. \helpref{wxArchiveInputStream}{wxarchiveinputstream}) and the +second can either be a pointer to an entry +(e.g. \helpref{wxArchiveEntry}{wxarchiveentry}*), or a string/pointer pair +(e.g. std::pair). + +The {\tt } header defines the following typedefs: + +\begin{verbatim} + typedef wxArchiveIterator wxArchiveIter; + + typedef wxArchiveIterator > wxArchivePairIter; + +\end{verbatim} + +The header for any implementation of this interface should define similar +typedefs for its types, for example in {\tt } there is: + +\begin{verbatim} + typedef wxArchiveIterator wxZipIter; + + typedef wxArchiveIterator > wxZipPairIter; + +\end{verbatim} + +Transferring the catalogue of an archive {\it arc} to a vector {\it cat}, +can then be done something like this: + +\begin{verbatim} + std::vector cat((wxArchiveIter)arc, wxArchiveIter()); + +\end{verbatim} + +When the iterator is dereferenced, it gives away ownership of an entry +object. So in the above example, when you have finished with {\it cat} +you must delete the pointers it contains. + +If you have smart pointers with normal copy semantics (i.e. not auto\_ptr +or \helpref{wxScopedPtr}{wxscopedptr}), then you can create an iterator +which uses them instead. For example, with a smart pointer class for +zip entries {\it ZipEntryPtr}: + +\begin{verbatim} + typedef std::vector ZipCatalog; + typedef wxArchiveIterator ZipIter; + ZipCatalog cat((ZipIter)zip, ZipIter()); + +\end{verbatim} + +Iterators that return std::pair objects can be used to +populate a std::multimap, to allow entries to be looked +up by name. The string is initialised using the wxArchiveEntry object's + \helpref{GetInternalName()}{wxarchiveentrygetinternalname} function. + +\begin{verbatim} + typedef std::multimap ZipCatalog; + ZipCatalog cat((wxZipPairIter)zip, wxZipPairIter()); + +\end{verbatim} + +Note that this iterator also gives away ownership of an entry +object each time it is dereferenced. So in the above example, when +you have finished with {\it cat} you must delete the pointers it contains. + +Or if you have them, a pair containing a smart pointer can be used +(again {\it ZipEntryPtr}), no worries about ownership: + +\begin{verbatim} + typedef std::multimap ZipCatalog; + typedef wxArchiveIterator > ZipPairIter; + ZipCatalog cat((ZipPairIter)zip, ZipPairIter()); + +\end{verbatim} + +\wxheading{Derived from} + +No base class + +\wxheading{Include files} + + + +\wxheading{See also} + +\helpref{wxArchiveEntry}{wxarchiveentry}\\ +\helpref{wxArchiveInputStream}{wxarchiveinputstream}\\ +\helpref{wxArchiveOutputStream}{wxarchiveoutputstream} + +\wxheading{Data structures} +{\small \begin{verbatim} +typedef std::input\_iterator\_tag iterator\_category +typedef T value\_type +typedef ptrdiff\_t difference\_type +typedef T* pointer +typedef T\& reference +\end{verbatim}} + +\latexignore{\rtfignore{\wxheading{Members}}} + + +\membersection{wxArchiveIterator::wxArchiveIterator}\label{wxarchiveiteratorwxarchiveiterator} + +\func{}{wxArchiveIterator}{\void} + +Construct an 'end of sequence' instance. + +\func{}{wxArchiveIterator}{\param{Arc\& }{arc}} + +Construct iterator that returns all the entries in the archive input +stream {\it arc}. + + +\membersection{wxArchiveIterator::operator*}\label{wxarchiveiteratoroperatorunknown} + +\constfunc{const T\&}{operator*}{\void} + +Returns an entry object from the archive input stream, giving away +ownership. + + +\membersection{wxArchiveIterator::operator++}\label{wxarchiveiteratoroperatorunknown} + +\func{wxArchiveIterator\&}{operator++}{\void} + +\func{wxArchiveIterator\&}{operator++}{\param{int}{}} + +Position the input iterator at the next entry in the archive input stream. + + +% +% automatically generated by HelpGen $Revision$ from +% wx/archive.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxArchiveNotifier}}\label{wxarchivenotifier} + +If you need to know when a + \helpref{wxArchiveInputStream}{wxarchiveinputstream} updates a + \helpref{wxArchiveEntry}{wxarchiveentry} object, you can create +a notifier by deriving from this abstract base class, overriding + \helpref{OnEntryUpdated()}{wxarchivenotifieronentryupdated}. An instance +of your notifier class can then be assigned to the wxArchiveEntry object +using \helpref{wxArchiveEntry::SetNotifier()}{wxarchiveentrynotifier}. +Your OnEntryUpdated() method will then be invoked whenever the input +stream updates the entry. + +Setting a notifier is not usually necessary. It is used to handle +certain cases when modifying an archive in a pipeline (i.e. between +non-seekable streams). +See \helpref{Archives on non-seekable streams}{wxarcnoseek}. + +\wxheading{Derived from} + +No base class + +\wxheading{Include files} + + + +\wxheading{See also} + +\helpref{Archives on non-seekable streams}{wxarcnoseek}\\ +\helpref{wxArchiveEntry}{wxarchiveentry}\\ +\helpref{wxArchiveInputStream}{wxarchiveinputstream}\\ +\helpref{wxArchiveOutputStream}{wxarchiveoutputstream} + +\latexignore{\rtfignore{\wxheading{Members}}} + + +\membersection{wxArchiveNotifier::OnEntryUpdated}\label{wxarchivenotifieronentryupdated} + +\func{void}{OnEntryUpdated}{\param{class wxArchiveEntry\& }{entry}} + +This method must be overridden in your derived class. + + +% +% automatically generated by HelpGen $Revision$ from +% wx/archive.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxArchiveOutputStream}}\label{wxarchiveoutputstream} + +An abstract base class which serves as a common interface to +archive output streams such as \helpref{wxZipOutputStream}{wxzipoutputstream}. + +\helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry} is used +to create a new entry in the output archive, then the entry's data is +written to the wxArchiveOutputStream. Another call to PutNextEntry() +closes the current entry and begins the next. + +\wxheading{Derived from} + +\helpref{wxFilterOutputStream}{wxfilteroutputstream} + +\wxheading{Include files} + + + +\wxheading{See also} + +\helpref{Archive formats such as zip}{wxarc}\\ +\helpref{wxArchiveEntry}{wxarchiveentry}\\ +\helpref{wxArchiveInputStream}{wxarchiveinputstream} + +\latexignore{\rtfignore{\wxheading{Members}}} + + +\membersection{wxArchiveOutputStream::\destruct{wxArchiveOutputStream}}\label{wxarchiveoutputstreamdtor} + +\func{}{\destruct{wxArchiveOutputStream}}{\void} + +Calls \helpref{Close()}{wxarchiveoutputstreamclose} if it has not already +been called. + + +\membersection{wxArchiveOutputStream::Close}\label{wxarchiveoutputstreamclose} + +\func{bool}{Close}{\void} + +Closes the archive, returning true if it was successfully written. +Called by the destructor if not called explicitly. + + +\membersection{wxArchiveOutputStream::CloseEntry}\label{wxarchiveoutputstreamcloseentry} + +\func{bool}{CloseEntry}{\void} + +Close the current entry. It is called implicitly whenever another new +entry is created with \helpref{CopyEntry()}{wxarchiveoutputstreamcopyentry} +or \helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry}, or +when the archive is closed. + + +\membersection{wxArchiveOutputStream::CopyArchiveMetaData}\label{wxarchiveoutputstreamcopyarchivemetadata} + +\func{bool}{CopyArchiveMetaData}{\param{wxArchiveInputStream\& }{stream}} + +Some archive formats have additional meta-data that applies to the archive +as a whole. For example in the case of zip there is a comment, which +is stored at the end of the zip file. CopyArchiveMetaData() can be used +to transfer such information when writing a modified copy of an archive. + +Since the position of the meta-data can vary between the various archive +formats, it is best to call CopyArchiveMetaData() before transferring +the entries. The \helpref{wxArchiveOutputStream}{wxarchiveoutputstream} +will then hold on to the meta-data and write it at the correct point in +the output file. + +When the input archive is being read from a non-seekable stream, the +meta-data may not be available when CopyArchiveMetaData() is called, +in which case the two streams set up a link and transfer the data +when it becomes available. + + +\membersection{wxArchiveOutputStream::CopyEntry}\label{wxarchiveoutputstreamcopyentry} + +\func{bool}{CopyEntry}{\param{wxArchiveEntry* }{entry}, \param{wxArchiveInputStream\& }{stream}} + +Takes ownership of {\it entry} and uses it to create a new entry in the +archive. {\it entry} is then opened in the input stream {\it stream} +and its contents copied to this stream. + +For archive types which compress entry data, CopyEntry() is likely to be +much more efficient than transferring the data using Read() and Write() +since it will copy them without decompressing and recompressing them. + +{\it entry} must be from the same archive file that {\it stream} is +accessing. For non-seekable streams, {\it entry} must also be the last +thing read from {\it stream}. + + +\membersection{wxArchiveOutputStream::PutNextDirEntry}\label{wxarchiveoutputstreamputnextdirentry} + +\func{bool}{PutNextDirEntry}{\param{const wxString\& }{name}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}} + +Create a new directory entry +(see \helpref{wxArchiveEntry::IsDir()}{wxarchiveentryisdir}) +with the given name and timestamp. + +\helpref{PutNextEntry()}{wxarchiveoutputstreamputnextentry} can +also be used to create directory entries, by supplying a name with +a trailing path separator. + + +\membersection{wxArchiveOutputStream::PutNextEntry}\label{wxarchiveoutputstreamputnextentry} + +\func{bool}{PutNextEntry}{\param{wxArchiveEntry* }{entry}} + +Takes ownership of {\it entry} and uses it to create a new entry in +the archive. The entry's data can then be written by writing to this +wxArchiveOutputStream. + +\func{bool}{PutNextEntry}{\param{const wxString\& }{name}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}, \param{off\_t }{size = wxInvalidOffset}} + +Create a new entry with the given name, timestamp and size. The entry's +data can then be written by writing to this wxArchiveOutputStream. + + diff --git a/docs/latex/wx/classes.tex b/docs/latex/wx/classes.tex index 0880b91ce4..08957a27f5 100644 --- a/docs/latex/wx/classes.tex +++ b/docs/latex/wx/classes.tex @@ -9,6 +9,7 @@ \input accessible.tex \input activevt.tex \input app.tex +\input archive.tex \input array.tex \input arrstrng.tex \input artprov.tex diff --git a/docs/latex/wx/topics.tex b/docs/latex/wx/topics.tex index fccb15c4cc..b9946e27d6 100644 --- a/docs/latex/wx/topics.tex +++ b/docs/latex/wx/topics.tex @@ -59,4 +59,5 @@ This chapter contains a selection of topic overviews, first things first: \input tenvvars.tex \input wxPython.tex \input re_syntax.tex +\input arc.tex diff --git a/docs/latex/wx/zipstrm.tex b/docs/latex/wx/zipstrm.tex index f0a740f97e..65e0f3f673 100644 --- a/docs/latex/wx/zipstrm.tex +++ b/docs/latex/wx/zipstrm.tex @@ -1,40 +1,706 @@ % -% automatically generated by HelpGen from -% zipstream.h at 02/May/99 19:54:25 +% automatically generated by HelpGen $Revision$ from +% wx/zipstrm.h at 16/Sep/04 12:19:29 % -\section{\class{wxZipInputStream}}\label{wxzipinputstream} +\section{\class{wxZipClassFactory}}\label{wxzipclassfactory} -This class is input stream from ZIP archive. The archive -must be local file (accessible via FILE*). -It has all features including GetSize and seeking. +Class factory for the zip archive format. See the base class +for details. -\wxheading{Note} +\wxheading{Derived from} -If you need to enumerate files in ZIP archive, you can use -\helpref{wxFileSystem}{wxfilesystem} together with wxZipFSHandler (see -\helpref{the overview}{fs}). +\helpref{wxArchiveClassFactory}{wxarchiveclassfactory} +\wxheading{Include files} + + + +\wxheading{See also} + +\helpref{Archive formats such as zip}{wxarc}\\ +\helpref{Generic archive programming}{wxarcgeneric} +\helpref{wxZipEntry}{wxzipentry}\\ +\helpref{wxZipInputStream}{wxzipinputstream}\\ +\helpref{wxZipOutputStream}{wxzipoutputstream} + + +% +% automatically generated by HelpGen $Revision$ from +% wx/zipstrm.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxZipEntry}}\label{wxzipentry} + +Holds the meta-data for an entry in a zip. \wxheading{Derived from} -\helpref{wxInputStream}{wxinputstream} +\helpref{wxArchiveEntry}{wxarchiveentry} \wxheading{Include files} +\wxheading{Data structures} + +Constants for \helpref{Get/SetMethod}{wxzipentrymethod}: + +\begin{verbatim} +// Compression Method, only 0 (store) and 8 (deflate) are supported here +// +enum wxZipMethod +{ + wxZIP_METHOD_STORE, + wxZIP_METHOD_SHRINK, + wxZIP_METHOD_REDUCE1, + wxZIP_METHOD_REDUCE2, + wxZIP_METHOD_REDUCE3, + wxZIP_METHOD_REDUCE4, + wxZIP_METHOD_IMPLODE, + wxZIP_METHOD_TOKENIZE, + wxZIP_METHOD_DEFLATE, + wxZIP_METHOD_DEFLATE64, + wxZIP_METHOD_BZIP2 = 12, + wxZIP_METHOD_DEFAULT = 0xffff +}; + +\end{verbatim} + +Constants for \helpref{Get/SetSystemMadeBy}{wxzipentrysystemmadeby}: + +\begin{verbatim} +// Originating File-System. +// +// These are Pkware's values. Note that Info-zip disagree on some of them, +// most notably NTFS. +// +enum wxZipSystem +{ + wxZIP_SYSTEM_MSDOS, + wxZIP_SYSTEM_AMIGA, + wxZIP_SYSTEM_OPENVMS, + wxZIP_SYSTEM_UNIX, + wxZIP_SYSTEM_VM_CMS, + wxZIP_SYSTEM_ATARI_ST, + wxZIP_SYSTEM_OS2_HPFS, + wxZIP_SYSTEM_MACINTOSH, + wxZIP_SYSTEM_Z_SYSTEM, + wxZIP_SYSTEM_CPM, + wxZIP_SYSTEM_WINDOWS_NTFS, + wxZIP_SYSTEM_MVS, + wxZIP_SYSTEM_VSE, + wxZIP_SYSTEM_ACORN_RISC, + wxZIP_SYSTEM_VFAT, + wxZIP_SYSTEM_ALTERNATE_MVS, + wxZIP_SYSTEM_BEOS, + wxZIP_SYSTEM_TANDEM, + wxZIP_SYSTEM_OS_400 +}; + +\end{verbatim} + +Constants for \helpref{Get/SetExternalAttributes}{wxzipentryexternalattributes}: + +\begin{verbatim} +// Dos/Win file attributes +// +enum wxZipAttributes +{ + wxZIP_A_RDONLY = 0x01, + wxZIP_A_HIDDEN = 0x02, + wxZIP_A_SYSTEM = 0x04, + wxZIP_A_SUBDIR = 0x10, + wxZIP_A_ARCH = 0x20, + + wxZIP_A_MASK = 0x37 +}; + +\end{verbatim} + +Constants for \helpref{Get/SetFlags}{wxzipentrygetflags}: + +\begin{verbatim} +// Values for the flags field in the zip headers +// +enum wxZipFlags +{ + wxZIP_ENCRYPTED = 0x0001, + wxZIP_DEFLATE_NORMAL = 0x0000, // normal compression + wxZIP_DEFLATE_EXTRA = 0x0002, // extra compression + wxZIP_DEFLATE_FAST = 0x0004, // fast compression + wxZIP_DEFLATE_SUPERFAST = 0x0006, // superfast compression + wxZIP_DEFLATE_MASK = 0x0006, + wxZIP_SUMS_FOLLOW = 0x0008, // crc and sizes come after the data + wxZIP_ENHANCED = 0x0010, + wxZIP_PATCH = 0x0020, + wxZIP_STRONG_ENC = 0x0040, + wxZIP_UNUSED = 0x0F80, + wxZIP_RESERVED = 0xF000 +}; + +\end{verbatim} + +\wxheading{See also} + +\helpref{Archive formats such as zip}{wxarc}\\ +\helpref{wxZipInputStream}{wxzipinputstream}\\ +\helpref{wxZipOutputStream}{wxzipoutputstream}\\ +\helpref{wxZipNotifier}{wxzipnotifier} + +\wxheading{Field availability} + +When reading a zip from a stream that is seekable, + \helpref{GetNextEntry()}{wxzipinputstreamgetnextentry} returns +a fully populated wxZipEntry object except for + \helpref{wxZipEntry::GetLocalExtra()}{wxzipentrylocalextra}. GetLocalExtra() +becomes available when the entry is opened, either by calling + \helpref{wxZipInputStream::OpenEntry}{wxzipinputstreamopenentry} or by +making an attempt to read the entry's data. + +For zips on \helpref{non-seekable}{wxarcnoseek} streams, the following +fields are always available when GetNextEntry() returns: + +\helpref{GetDateTime}{wxarchiveentrydatetime}\\ +\helpref{GetInternalFormat}{wxarchiveentrygetinternalformat}\\ +\helpref{GetInternalName}{wxzipentrygetinternalname}\\ +\helpref{GetFlags}{wxzipentrygetflags}\\ +\helpref{GetLocalExtra}{wxzipentrylocalextra}\\ +\helpref{GetMethod}{wxzipentrymethod}\\ +\helpref{GetName}{wxarchiveentryname}\\ +\helpref{GetOffset}{wxarchiveentrygetoffset}\\ +\helpref{IsDir}{wxarchiveentryisdir} + +The following fields are also usually available when GetNextEntry() +returns, however, if the zip was also written to a non-seekable stream +the zipper is permitted to store them after the entry's data. In that +case they become available when the entry's data has been read to Eof(), +or \helpref{CloseEntry()}{wxarchiveinputstreamcloseentry} has been called. +{\tt (GetFlags() \& wxZIP\_SUMS\_FOLLOW) != 0} indicates that one or +more of these come after the data: + +\helpref{GetCompressedSize}{wxzipentrygetcompressedsize}\\ +\helpref{GetCrc}{wxzipentrygetcrc}\\ +\helpref{GetSize}{wxarchiveentrysize} + +The following are stored at the end of the zip, and become available +when the end of the zip has been reached, i.e. after GetNextEntry() +returns NULL and Eof() is true: + +\helpref{GetComment}{wxzipentrycomment}\\ +\helpref{GetExternalAttributes}{wxzipentryexternalattributes}\\ +\helpref{GetExtra}{wxzipentryextra}\\ +\helpref{GetMode}{wxzipentrymode}\\ +\helpref{GetSystemMadeBy}{wxzipentrysystemmadeby}\\ +\helpref{IsReadOnly}{wxarchiveentryisreadonly}\\ +\helpref{IsMadeByUnix}{wxzipentryismadebyunix}\\ +\helpref{IsText}{wxzipentryistext} + + \latexignore{\rtfignore{\wxheading{Members}}} + +\membersection{wxZipEntry::wxZipEntry}\label{wxzipentrywxzipentry} + +\func{}{wxZipEntry}{\param{const wxString\& }{name = wxEmptyString}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}, \param{off\_t }{size = wxInvalidOffset}} + +Constructor. + +\func{}{wxZipEntry}{\param{const wxZipEntry\& }{entry}} + +Copy constructor. + + +\membersection{wxZipEntry::Clone}\label{wxzipentryclone} + +\constfunc{wxZipEntry*}{Clone}{\void} + +Make a copy of this entry. + + +\membersection{wxZipEntry::Get/SetComment}\label{wxzipentrycomment} + +\constfunc{wxString}{GetComment}{\void} + +\func{void}{SetComment}{\param{const wxString\& }{comment}} + +A short comment for this entry. + + +\membersection{wxZipEntry::GetCompressedSize}\label{wxzipentrygetcompressedsize} + +\constfunc{off\_t}{GetCompressedSize}{\void} + +The compressed size of this entry in bytes. + + +\membersection{wxZipEntry::GetCrc}\label{wxzipentrygetcrc} + +\constfunc{wxUint32}{GetCrc}{\void} + +CRC32 for this entry's data. + + +\membersection{wxZipEntry::Get/SetExternalAttributes}\label{wxzipentryexternalattributes} + +\constfunc{wxUint32}{GetExternalAttributes}{\void} + +\func{void}{SetExternalAttributes}{\param{wxUint32 }{attr}} + +The low 8 bits are always the DOS/Windows file attributes for this entry. +The values of these attributes are given in the +enumeration {\tt wxZipAttributes}. + +The remaining bits can store platform specific permission bits or +attributes, and their meaning depends on the value +of \helpref{SetSystemMadeBy()}{wxzipentrysystemmadeby}. +If \helpref{IsMadeByUnix()}{wxzipentryismadebyunix} is true then the +high 16 bits are unix mode bits. + +The following other accessors access these bits: + +\helpref{IsReadOnly/SetIsReadOnly}{wxarchiveentryisreadonly}\\ +\helpref{IsDir/SetIsDir}{wxarchiveentryisdir}\\ +\helpref{Get/SetMode}{wxzipentrymode} + + +\membersection{wxZipEntry::Get/SetExtra}\label{wxzipentryextra} + +\constfunc{const char*}{GetExtra}{\void} + +\constfunc{size\_t}{GetExtraLen}{\void} + +\func{void}{SetExtra}{\param{const char* }{extra}, \param{size\_t }{len}} + +The extra field from the entry's central directory record. + +The extra field is used to store platform or application specific +data. See Pkware's document 'appnote.txt' for information on its format. + + +\membersection{wxZipEntry::GetFlags}\label{wxzipentrygetflags} + +\constfunc{int}{GetFlags}{\void} + +Returns a combination of the bits flags in the enumeration {\tt wxZipFlags}. + + +\membersection{wxZipEntry::GetInternalName}\label{wxzipentrygetinternalname} + +\constfunc{wxString}{GetInternalName}{\void} + +Returns the entry's filename in the internal format used within the +archive. The name can include directory components, i.e. it can be a +full path. + +The names of directory entries are returned without any trailing path +separator. This gives a canonical name that can be used in comparisons. + +\func{wxString}{GetInternalName}{\param{const wxString\& }{name}, \param{wxPathFormat }{format = wxPATH\_NATIVE}, \param{bool* }{pIsDir = NULL}} + +A static member that translates a filename into the internal format used +within the archive. If the third parameter is provided, the bool pointed +to is set to indicate whether the name looks like a directory name +(i.e. has a trailing path separator). + +\wxheading{See also} + +\helpref{Looking up an archive entry by name}{wxarcbyname} + + +\membersection{wxZipEntry::Get/SetLocalExtra}\label{wxzipentrylocalextra} + +\constfunc{const char*}{GetLocalExtra}{\void} + +\constfunc{size\_t}{GetLocalExtraLen}{\void} + +\func{void}{SetLocalExtra}{\param{const char* }{extra}, \param{size\_t }{len}} + +The extra field from the entry's local record. + +The extra field is used to store platform or application specific +data. See Pkware's document 'appnote.txt' for information on its format. + + +\membersection{wxZipEntry::Get/SetMethod}\label{wxzipentrymethod} + +\constfunc{int}{GetMethod}{\void} + +\func{void}{SetMethod}{\param{int }{method}} + +The compression method. The enumeration {\tt wxZipMethod} lists the +possible values. + +The default constructor sets this to wxZIP\_METHOD\_DEFAULT, +which allows \helpref{wxZipOutputStream}{wxzipoutputstream} to +choose the method when writing the entry. + + +\membersection{wxZipEntry::Get/SetMode}\label{wxzipentrymode} + +\constfunc{int}{GetMode}{\void} + +If \helpref{IsMadeByUnix()}{wxzipentryismadebyunix} is true then +returns the unix permission bits stored in + \helpref{GetExternalAttributes()}{wxzipentryexternalattributes}. +Otherwise synthesises them from the DOS attributes. + +\func{void}{SetMode}{\param{int }{mode}} + +Sets the DOS attributes +in \helpref{GetExternalAttributes()}{wxzipentryexternalattributes} +to be consistent with the {\tt mode} given. + +If \helpref{IsMadeByUnix()}{wxzipentryismadebyunix} is true then also +stores {\tt mode} in GetExternalAttributes(). + +Note that the default constructor +sets \helpref{GetSystemMadeBy()}{wxzipentrysystemmadeby} to +wxZIP\_SYSTEM\_MSDOS by default. So to be able to store unix +permissions when creating zips, call SetSystemMadeBy(wxZIP\_SYSTEM\_UNIX). + + +\membersection{wxZipEntry::SetNotifier}\label{wxzipentrynotifier} + +\func{void}{SetNotifier}{\param{wxZipNotifier\& }{notifier}} + +\func{void}{UnsetNotifier}{\void} + +Sets the \helpref{notifier}{wxzipnotifier} for this entry. +Whenever the \helpref{wxZipInputStream}{wxzipinputstream} updates +this entry, it will then invoke the associated +notifier's \helpref{OnEntryUpdated}{wxzipnotifieronentryupdated} +method. + +Setting a notifier is not usually necessary. It is used to handle +certain cases when modifying an zip in a pipeline (i.e. between +non-seekable streams). + +\wxheading{See also} + +\helpref{Archives on non-seekable streams}{wxarcnoseek}\\ +\helpref{wxZipNotifier}{wxzipnotifier} + + +\membersection{wxZipEntry::Get/SetSystemMadeBy}\label{wxzipentrysystemmadeby} + +\constfunc{int}{GetSystemMadeBy}{\void} + +\func{void}{SetSystemMadeBy}{\param{int }{system}} + +The originating file-system. The default constructor sets this to +wxZIP\_SYSTEM\_MSDOS. Set it to wxZIP\_SYSTEM\_UNIX in order to be +able to store unix permissions using \helpref{SetMode()}{wxzipentrymode}. + + +\membersection{wxZipEntry::IsMadeByUnix}\label{wxzipentryismadebyunix} + +\constfunc{bool}{IsMadeByUnix}{\void} + +Returns true if \helpref{GetSystemMadeBy()}{wxzipentrysystemmadeby} +is a flavour of unix. + + +\membersection{wxZipEntry::IsText/SetIsText}\label{wxzipentryistext} + +\constfunc{bool}{IsText}{\void} + +\func{void}{SetIsText}{\param{bool }{isText = true}} + +Indicates that this entry's data is text in an 8-bit encoding. + + +\membersection{wxZipEntry::operator=}\label{wxzipentryoperatorassign} + +\func{wxZipEntry\& operator}{operator=}{\param{const wxZipEntry\& }{entry}} + +Assignment operator. + + +% +% automatically generated by HelpGen $Revision$ from +% wx/zipstrm.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxZipInputStream}}\label{wxzipinputstream} + +Input stream for reading zip files. + +\helpref{GetNextEntry()}{wxzipinputstreamgetnextentry} returns an + \helpref{wxZipEntry}{wxzipentry} object containing the meta-data +for the next entry in the zip (and gives away ownership). Reading from +the wxZipInputStream then returns the entry's data. Eof() becomes true +after an attempt has been made to read past the end of the entry's data. +When there are no more entries, GetNextEntry() returns NULL and sets Eof(). + +\wxheading{Derived from} + +\helpref{wxArchiveInputStream}{wxarchiveinputstream} + +\wxheading{Include files} + + + +\wxheading{Data structures} +{\small \begin{verbatim} +typedef wxZipEntry entry\_type +\end{verbatim}} + +\wxheading{See also} + +\helpref{Archive formats such as zip}{wxarc}\\ +\helpref{wxZipEntry}{wxzipentry}\\ +\helpref{wxZipOutputStream}{wxzipoutputstream} + +\latexignore{\rtfignore{\wxheading{Members}}} + + \membersection{wxZipInputStream::wxZipInputStream}\label{wxzipinputstreamwxzipinputstream} +\func{}{wxZipInputStream}{\param{wxInputStream\& }{stream}, \param{wxMBConv\& }{conv = wxConvLocal}} + +Constructor. In a Unicode build the second parameter {\tt conv} is +used to translate the filename and comment fields into Unicode. It has +no effect on the stream's data. + \func{}{wxZipInputStream}{\param{const wxString\& }{archive}, \param{const wxString\& }{file}} -Constructor. +Compatibility constructor. + + +\membersection{wxZipInputStream::CloseEntry}\label{wxzipinputstreamcloseentry} + +\func{bool}{CloseEntry}{\void} + +Closes the current entry. On a non-seekable stream reads to the end of +the current entry first. + + +\membersection{wxZipInputStream::GetComment}\label{wxzipinputstreamgetcomment} + +\func{wxString}{GetComment}{\void} + +Returns the zip comment. + +This is stored a the end of the zip, therefore when reading a zip +from a non-seekable stream, it returns the empty string until the +end of the zip has been reached, i.e. when GetNextEntry() returns +NULL. + + +\membersection{wxZipInputStream::GetNextEntry}\label{wxzipinputstreamgetnextentry} + +\func{wxZipEntry*}{GetNextEntry}{\void} + +Closes the current entry if one is open, then reads the meta-data for +the next entry and returns it in a \helpref{wxZipEntry}{wxzipentry} +object, giving away ownership. The stream is then open and can be read. + + +\membersection{wxZipInputStream::GetTotalEntries}\label{wxzipinputstreamgettotalentries} + +\func{int}{GetTotalEntries}{\void} + +For a zip on a seekable stream returns the total number of entries in +the zip. For zips on non-seekable streams returns the number of entries +returned so far by \helpref{GetNextEntry()}{wxzipinputstreamgetnextentry}. + + +\membersection{wxZipInputStream::OpenEntry}\label{wxzipinputstreamopenentry} + +\func{bool}{OpenEntry}{\param{wxZipEntry\& }{entry}} + +Closes the current entry if one is open, then opens the entry specified +by the {\it entry} object. + +{\it entry} should be from the same zip file, and the zip should +be on a seekable stream. + +\wxheading{See also} + +\helpref{Looking up an archive entry by name}{wxarcbyname} + + +% +% automatically generated by HelpGen $Revision$ from +% wx/zipstrm.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxZipNotifier}}\label{wxzipnotifier} + +If you need to know when a \helpref{wxZipInputStream}{wxzipinputstream} +updates a \helpref{wxZipEntry}{wxzipentry}, +you can create a notifier by deriving from this abstract base class, +overriding \helpref{OnEntryUpdated()}{wxzipnotifieronentryupdated}. +An instance of your notifier class can then be assigned to wxZipEntry +objects, using \helpref{wxZipEntry::SetNotifier()}{wxzipentrynotifier}. + +Setting a notifier is not usually necessary. It is used to handle +certain cases when modifying an zip in a pipeline (i.e. between +non-seekable streams). +See '\helpref{Archives on non-seekable streams}{wxarcnoseek}'. + +\wxheading{Derived from} + +No base class + +\wxheading{Include files} + + + +\wxheading{See also} + +\helpref{Archives on non-seekable streams}{wxarcnoseek}\\ +\helpref{wxZipEntry}{wxzipentry}\\ +\helpref{wxZipInputStream}{wxzipinputstream}\\ +\helpref{wxZipOutputStream}{wxzipoutputstream} + +\latexignore{\rtfignore{\wxheading{Members}}} + + +\membersection{wxZipNotifier::OnEntryUpdated}\label{wxzipnotifieronentryupdated} + +\func{void}{OnEntryUpdated}{\param{wxZipEntry\& }{entry}} + +Override this to receive notifications when +an \helpref{wxZipEntry}{wxzipentry} object changes. + + +% +% automatically generated by HelpGen $Revision$ from +% wx/zipstrm.h at 16/Sep/04 12:19:29 +% + +\section{\class{wxZipOutputStream}}\label{wxzipoutputstream} + +Output stream for writing zip files. + +\helpref{PutNextEntry()}{wxzipoutputstreamputnextentry} is used to create +a new entry in the output zip, then the entry's data is written to the +wxZipOutputStream. Another call to PutNextEntry() closes the current +entry and begins the next. + +\wxheading{Derived from} + +\helpref{wxArchiveOutputStream}{wxarchiveoutputstream} + +\wxheading{Include files} + + + +\wxheading{See also} + +\helpref{Archive formats such as zip}{wxarc}\\ +\helpref{wxZipEntry}{wxzipentry}\\ +\helpref{wxZipInputStream}{wxzipinputstream} + +\latexignore{\rtfignore{\wxheading{Members}}} + + +\membersection{wxZipOutputStream::wxZipOutputStream}\label{wxzipoutputstreamwxzipoutputstream} + +\func{}{wxZipOutputStream}{\param{wxOutputStream\& }{stream}, \param{int }{level = -1}, \param{wxMBConv\& }{conv = wxConvLocal}} + +Constructor. {\tt level} is the compression level to use. +It can be a value between 0 and 9 or -1 to use the default value +which currently is equivalent to 6. + +In a Unicode build the third parameter {\tt conv} is used to translate +the filename and comment fields to Unicode. It has no effect on the +stream's data. + + +\membersection{wxZipOutputStream::\destruct{wxZipOutputStream}}\label{wxzipoutputstreamdtor} + +\func{}{\destruct{wxZipOutputStream}}{\void} + +The destructor calls \helpref{Close()}{wxzipoutputstreamclose} to finish +writing the zip if it has not been called already. + + +\membersection{wxZipOutputStream::Close}\label{wxzipoutputstreamclose} + +\func{bool}{Close}{\void} + +Finishes writing the zip, returning true if successfully. +Called by the destructor if not called explicitly. + + +\membersection{wxZipOutputStream::CloseEntry}\label{wxzipoutputstreamcloseentry} + +\func{bool}{CloseEntry}{\void} + +Close the current entry. It is called implicitly whenever another new +entry is created with \helpref{CopyEntry()}{wxzipoutputstreamcopyentry} +or \helpref{PutNextEntry()}{wxzipoutputstreamputnextentry}, or +when the zip is closed. + + +\membersection{wxZipOutputStream::CopyArchiveMetaData}\label{wxzipoutputstreamcopyarchivemetadata} + +\func{bool}{CopyArchiveMetaData}{\param{wxZipInputStream\& }{inputStream}} + +Transfers the zip comment from the \helpref{wxZipInputStream}{wxzipinputstream} +to this output stream. + + +\membersection{wxZipOutputStream::CopyEntry}\label{wxzipoutputstreamcopyentry} + +\func{bool}{CopyEntry}{\param{wxZipEntry* }{entry}, \param{wxZipInputStream\& }{inputStream}} + +Takes ownership of {\tt entry} and uses it to create a new entry +in the zip. {\tt entry} is then opened in {\tt inputStream} and its contents +copied to this stream. + +CopyEntry() is much more efficient than transferring the data using +Read() and Write() since it will copy them without decompressing and +recompressing them. + +For zips on seekable streams, {\tt entry} must be from the same zip file +as {\tt stream}. For non-seekable streams, {\tt entry} must also be the +last thing read from {\tt inputStream}. + + +\membersection{wxZipOutputStream::Get/SetLevel}\label{wxzipoutputstreamlevel} + +\constfunc{int}{GetLevel}{\void} + +\func{void}{SetLevel}{\param{int }{level}} + +Set the compression level that will be used the next time an entry is +created. It can be a value between 0 and 9 or -1 to use the default value +which currently is equivalent to 6. + + +\membersection{wxZipOutputStream::PutNextDirEntry}\label{wxzipoutputstreamputnextdirentry} + +\func{bool}{PutNextDirEntry}{\param{const wxString\& }{name}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}} + +Create a new directory entry +(see \helpref{wxArchiveEntry::IsDir()}{wxarchiveentryisdir}) +with the given name and timestamp. + +\helpref{PutNextEntry()}{wxzipoutputstreamputnextentry} can +also be used to create directory entries, by supplying a name with +a trailing path separator. + + +\membersection{wxZipOutputStream::PutNextEntry}\label{wxzipoutputstreamputnextentry} + +\func{bool}{PutNextEntry}{\param{wxZipEntry* }{entry}} + +Takes ownership of {\tt entry} and uses it to create a new entry +in the zip. + +\func{bool}{PutNextEntry}{\param{const wxString\& }{name}, \param{const wxDateTime\& }{dt = wxDateTime::Now()}, \param{off\_t }{size = wxInvalidOffset}} + +Create a new entry with the given name, timestamp and size. + -\wxheading{Parameters} +\membersection{wxZipOutputStream::SetComment}\label{wxzipoutputstreamsetcomment} -\docparam{archive}{name of ZIP file} +\func{void}{SetComment}{\param{const wxString\& }{comment}} -\docparam{file}{name of file stored in the archive} +Sets a comment for the zip as a whole. It is written at the end of the +zip. diff --git a/include/wx/archive.h b/include/wx/archive.h new file mode 100644 index 0000000000..fb5a647383 --- /dev/null +++ b/include/wx/archive.h @@ -0,0 +1,353 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: archive.h +// Purpose: Streams for archive formats +// Author: Mike Wetherell +// RCS-ID: $Id$ +// Copyright: (c) 2004 Mike Wetherell +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_ARCHIVE_H__ +#define _WX_ARCHIVE_H__ + +#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) +#pragma interface "archive.h" +#endif + +#include "wx/defs.h" + +#if wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM + +#include "wx/stream.h" +#include "wx/filename.h" + + +///////////////////////////////////////////////////////////////////////////// +// wxArchiveNotifier + +class WXDLLIMPEXP_BASE wxArchiveNotifier +{ +public: + virtual ~wxArchiveNotifier() { } + + virtual void OnEntryUpdated(class wxArchiveEntry& entry) = 0; +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxArchiveEntry +// +// Holds an entry's meta data, such as filename and timestamp. + +class WXDLLIMPEXP_BASE wxArchiveEntry : public wxObject +{ +public: + virtual ~wxArchiveEntry() { } + + virtual wxDateTime GetDateTime() const = 0; + virtual wxFileOffset GetSize() const = 0; + virtual wxFileOffset GetOffset() const = 0; + virtual bool IsDir() const = 0; + virtual bool IsReadOnly() const = 0; + virtual wxString GetInternalName() const = 0; + virtual wxPathFormat GetInternalFormat() const = 0; + virtual wxString GetName(wxPathFormat format = wxPATH_NATIVE) const = 0; + + virtual void SetDateTime(const wxDateTime& dt) = 0; + virtual void SetSize(wxFileOffset size) = 0; + virtual void SetIsDir(bool isDir = true) = 0; + virtual void SetIsReadOnly(bool isReadOnly = true) = 0; + virtual void SetName(const wxString& name, + wxPathFormat format = wxPATH_NATIVE) = 0; + + wxArchiveEntry *Clone() const { return DoClone(); } + + void SetNotifier(wxArchiveNotifier& notifier); + virtual void UnsetNotifier() { m_notifier = NULL; } + +protected: + wxArchiveEntry() : m_notifier(NULL) { } + + virtual void SetOffset(wxFileOffset offset) = 0; + virtual wxArchiveEntry* DoClone() const = 0; + + wxArchiveNotifier *GetNotifier() const { return m_notifier; } + wxArchiveEntry& operator=(const wxArchiveEntry& entry); + +private: + wxArchiveNotifier *m_notifier; + + DECLARE_ABSTRACT_CLASS(wxArchiveEntry) +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxArchiveInputStream +// +// GetNextEntry() returns an wxArchiveEntry object containing the meta-data +// for the next entry in the archive (and gives away ownership). Reading from +// the wxArchiveInputStream then returns the entry's data. Eof() becomes true +// after an attempt has been made to read past the end of the entry's data. +// +// When there are no more entries, GetNextEntry() returns NULL and sets Eof(). + +class WXDLLIMPEXP_BASE wxArchiveInputStream : public wxFilterInputStream +{ +public: + typedef wxArchiveEntry entry_type; + + virtual ~wxArchiveInputStream() { } + + virtual bool OpenEntry(wxArchiveEntry& entry) = 0; + virtual bool CloseEntry() = 0; + + wxArchiveEntry *GetNextEntry() { return DoGetNextEntry(); } + + virtual char Peek() { return wxInputStream::Peek(); } + +protected: + wxArchiveInputStream(wxInputStream& stream, wxMBConv& conv); + + virtual wxArchiveEntry *DoGetNextEntry() = 0; + + wxMBConv& GetConv() const { return m_conv; } + +private: + wxMBConv& m_conv; +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxArchiveOutputStream +// +// PutNextEntry is used to create a new entry in the output archive, then +// the entry's data is written to the wxArchiveOutputStream. +// +// Only one entry can be open for output at a time; another call to +// PutNextEntry closes the current entry and begins the next. +// +// The overload 'bool PutNextEntry(wxArchiveEntry *entry)' takes ownership +// of the entry object. + +class WXDLLIMPEXP_BASE wxArchiveOutputStream : public wxFilterOutputStream +{ +public: + virtual ~wxArchiveOutputStream() { } + + virtual bool PutNextEntry(wxArchiveEntry *entry) = 0; + + virtual bool PutNextEntry(const wxString& name, + const wxDateTime& dt = wxDateTime::Now(), + wxFileOffset size = wxInvalidOffset) = 0; + + virtual bool PutNextDirEntry(const wxString& name, + const wxDateTime& dt = wxDateTime::Now()) = 0; + + virtual bool CopyEntry(wxArchiveEntry *entry, + wxArchiveInputStream& stream) = 0; + + virtual bool CopyArchiveMetaData(wxArchiveInputStream& stream) = 0; + + virtual bool CloseEntry() = 0; + virtual bool Close() = 0; + +protected: + wxArchiveOutputStream(wxOutputStream& stream, wxMBConv& conv); + + wxMBConv& GetConv() const { return m_conv; } + +private: + wxMBConv& m_conv; +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxArchiveClassFactory +// +// A wxArchiveClassFactory instance for a particular archive type allows +// the creation of the other classes that may be needed. + +class WXDLLIMPEXP_BASE wxArchiveClassFactory : public wxObject +{ +public: + virtual ~wxArchiveClassFactory() { } + + wxArchiveEntry *NewEntry() const + { return DoNewEntry(); } + wxArchiveInputStream *NewStream(wxInputStream& stream) const + { return DoNewStream(stream); } + wxArchiveOutputStream *NewStream(wxOutputStream& stream) const + { return DoNewStream(stream); } + + virtual wxString GetInternalName( + const wxString& name, + wxPathFormat format = wxPATH_NATIVE) const = 0; + + void SetConv(wxMBConv& conv) { m_pConv = &conv; } + wxMBConv& GetConv() const { return *m_pConv; } + +protected: + virtual wxArchiveEntry *DoNewEntry() const = 0; + virtual wxArchiveInputStream *DoNewStream(wxInputStream& stream) const = 0; + virtual wxArchiveOutputStream *DoNewStream(wxOutputStream& stream) const = 0; + + wxArchiveClassFactory() : m_pConv(&wxConvLocal) { } + wxArchiveClassFactory& operator=(const wxArchiveClassFactory& WXUNUSED(f)) + { return *this; } + +private: + wxMBConv *m_pConv; + + DECLARE_ABSTRACT_CLASS(wxArchiveClassFactory) +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxArchiveIterator +// +// An input iterator that can be used to transfer an archive's catalog to +// a container. + +#if wxUSE_STL || defined WX_TEST_ARCHIVE_ITERATOR +#include +#include + +template +void WXDLLIMPEXP_BASE _wxSetArchiveIteratorValue( + X& val, Y entry, void *WXUNUSED(d)) +{ + val = X(entry); +} +template +void WXDLLIMPEXP_BASE _wxSetArchiveIteratorValue( + std::pair& val, Z entry, Z WXUNUSED(d)) +{ + val = std::make_pair(X(entry->GetInternalName()), Y(entry)); +} + +#if defined _MSC_VER && _MSC_VER < 1300 +template +#else +template +#endif +class WXDLLIMPEXP_BASE wxArchiveIterator +{ +public: + typedef std::input_iterator_tag iterator_category; + typedef T value_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; + typedef T& reference; + + wxArchiveIterator() : m_rep(NULL) { } + + wxArchiveIterator(Arc& arc) { + typename Arc::entry_type* entry = arc.GetNextEntry(); + m_rep = entry ? new Rep(arc, entry) : NULL; + } + + wxArchiveIterator(const wxArchiveIterator& it) : m_rep(it.m_rep) { + if (m_rep) + m_rep->AddRef(); + } + + ~wxArchiveIterator() { + if (m_rep) + m_rep->UnRef(); + } + + const T& operator *() const { + return m_rep->GetValue(); + } + + const T* operator ->() const { + return &**this; + } + + wxArchiveIterator& operator =(const wxArchiveIterator& it) { + if (it.m_rep) + it.m_rep.AddRef(); + if (m_rep) + m_rep.UnRef(); + m_rep = it.m_rep; + return *this; + } + + wxArchiveIterator& operator ++() { + m_rep = m_rep->Next(); + return *this; + } + + wxArchiveIterator operator ++(int) { + wxArchiveIterator it(*this); + ++(*this); + return it; + } + + friend bool operator ==(const wxArchiveIterator& i, + const wxArchiveIterator& j) { + return i.m_rep == j.m_rep; + } + + friend bool operator !=(const wxArchiveIterator& i, + const wxArchiveIterator& j) { + return !(i == j); + } + +private: + class Rep { + Arc& m_arc; + typename Arc::entry_type* m_entry; + T m_value; + int m_ref; + + public: + Rep(Arc& arc, typename Arc::entry_type* entry) + : m_arc(arc), m_entry(entry), m_value(), m_ref(1) { } + ~Rep() + { delete m_entry; } + + void AddRef() { + m_ref++; + } + + void UnRef() { + if (--m_ref == 0) + delete this; + } + + Rep *Next() { + typename Arc::entry_type* entry = m_arc.GetNextEntry(); + if (!entry) { + UnRef(); + return NULL; + } + if (m_ref > 1) { + m_ref--; + return new Rep(m_arc, entry); + } + delete m_entry; + m_entry = entry; + m_value = T(); + return this; + } + + const T& GetValue() { + if (m_entry) { + _wxSetArchiveIteratorValue(m_value, m_entry, m_entry); + m_entry = NULL; + } + return m_value; + } + } *m_rep; +}; + +typedef wxArchiveIterator wxArchiveIter; +typedef wxArchiveIterator > wxArchivePairIter; + +#endif // wxUSE_STL || defined WX_TEST_ARCHIVE_ITERATOR + +#endif // wxUSE_STREAMS + +#endif // _WX_ARCHIVE_H__ diff --git a/include/wx/fs_zip.h b/include/wx/fs_zip.h index 6dc75eb25c..d449cb072a 100644 --- a/include/wx/fs_zip.h +++ b/include/wx/fs_zip.h @@ -37,12 +37,13 @@ class WXDLLIMPEXP_BASE wxZipFSHandler : public wxFileSystemHandler private: // these vars are used by FindFirst/Next: - void *m_Archive; + class wxZipInputStream *m_Archive; wxString m_Pattern, m_BaseDir, m_ZipFile; bool m_AllowDirs, m_AllowFiles; wxLongToLongHashMap *m_DirsFound; wxString DoFind(); + void CloseArchive(class wxZipInputStream *archive); DECLARE_NO_COPY_CLASS(wxZipFSHandler) }; diff --git a/include/wx/zipstrm.h b/include/wx/zipstrm.h index 632963b888..7b83d0bc0b 100644 --- a/include/wx/zipstrm.h +++ b/include/wx/zipstrm.h @@ -1,13 +1,14 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: zipstream.h -// Purpose: wxZipInputStream for reading files from ZIP archive -// Author: Vaclav Slavik -// Copyright: (c) 1999 Vaclav Slavik +// Name: zipstrm.h +// Purpose: Streams for Zip files +// Author: Mike Wetherell +// RCS-ID: $Id$ +// Copyright: (c) Mike Wetherell // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -#ifndef __ZIPSTREAM_H__ -#define __ZIPSTREAM_H__ +#ifndef _WX_WXZIPSTREAM_H__ +#define _WX_WXZIPSTREAM_H__ #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) #pragma interface "zipstrm.h" @@ -15,47 +16,507 @@ #include "wx/defs.h" -#if wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB +#if wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM -#include "wx/stream.h" +#include "wx/archive.h" +#include "wx/hashmap.h" +#include "wx/filename.h" -//-------------------------------------------------------------------------------- -// wxZipInputStream -// This class is input stream from ZIP archive. The archive -// must be local file (accessible via FILE*) -//-------------------------------------------------------------------------------- +///////////////////////////////////////////////////////////////////////////// +// constants + +// Compression Method, only 0 (store) and 8 (deflate) are supported here +// +enum wxZipMethod +{ + wxZIP_METHOD_STORE, + wxZIP_METHOD_SHRINK, + wxZIP_METHOD_REDUCE1, + wxZIP_METHOD_REDUCE2, + wxZIP_METHOD_REDUCE3, + wxZIP_METHOD_REDUCE4, + wxZIP_METHOD_IMPLODE, + wxZIP_METHOD_TOKENIZE, + wxZIP_METHOD_DEFLATE, + wxZIP_METHOD_DEFLATE64, + wxZIP_METHOD_BZIP2 = 12, + wxZIP_METHOD_DEFAULT = 0xffff +}; + +// Originating File-System. +// +// These are Pkware's values. Note that Info-zip disagree on some of them, +// most notably NTFS. +// +enum wxZipSystem +{ + wxZIP_SYSTEM_MSDOS, + wxZIP_SYSTEM_AMIGA, + wxZIP_SYSTEM_OPENVMS, + wxZIP_SYSTEM_UNIX, + wxZIP_SYSTEM_VM_CMS, + wxZIP_SYSTEM_ATARI_ST, + wxZIP_SYSTEM_OS2_HPFS, + wxZIP_SYSTEM_MACINTOSH, + wxZIP_SYSTEM_Z_SYSTEM, + wxZIP_SYSTEM_CPM, + wxZIP_SYSTEM_WINDOWS_NTFS, + wxZIP_SYSTEM_MVS, + wxZIP_SYSTEM_VSE, + wxZIP_SYSTEM_ACORN_RISC, + wxZIP_SYSTEM_VFAT, + wxZIP_SYSTEM_ALTERNATE_MVS, + wxZIP_SYSTEM_BEOS, + wxZIP_SYSTEM_TANDEM, + wxZIP_SYSTEM_OS_400 +}; + +// Dos/Win file attributes +// +enum wxZipAttributes +{ + wxZIP_A_RDONLY = 0x01, + wxZIP_A_HIDDEN = 0x02, + wxZIP_A_SYSTEM = 0x04, + wxZIP_A_SUBDIR = 0x10, + wxZIP_A_ARCH = 0x20, + + wxZIP_A_MASK = 0x37 +}; + +// Values for the flags field in the zip headers +// +enum wxZipFlags +{ + wxZIP_ENCRYPTED = 0x0001, + wxZIP_DEFLATE_NORMAL = 0x0000, // normal compression + wxZIP_DEFLATE_EXTRA = 0x0002, // extra compression + wxZIP_DEFLATE_FAST = 0x0004, // fast compression + wxZIP_DEFLATE_SUPERFAST = 0x0006, // superfast compression + wxZIP_DEFLATE_MASK = 0x0006, + wxZIP_SUMS_FOLLOW = 0x0008, // crc and sizes come after the data + wxZIP_ENHANCED = 0x0010, + wxZIP_PATCH = 0x0020, + wxZIP_STRONG_ENC = 0x0040, + wxZIP_UNUSED = 0x0F80, + wxZIP_RESERVED = 0xF000 +}; + +// Forward decls +// +class WXDLLIMPEXP_BASE wxZipEntry; +class WXDLLIMPEXP_BASE wxZipInputStream; + + +///////////////////////////////////////////////////////////////////////////// +// wxZipNotifier -class WXDLLIMPEXP_BASE wxZipInputStream : public wxInputStream +class WXDLLIMPEXP_BASE wxZipNotifier { public: - wxZipInputStream(const wxString& archive, const wxString& file); - // archive is name of .zip archive, file is name of file to be extracted. - // Remember that archive must be local file accesible via fopen, fread functions! - ~wxZipInputStream(); + virtual ~wxZipNotifier() { } + + virtual void OnEntryUpdated(wxZipEntry& entry) = 0; +}; + + +///////////////////////////////////////////////////////////////////////////// +// Zip Entry - holds the meta data for a file in the zip + +class WXDLLIMPEXP_BASE wxZipEntry : public wxArchiveEntry +{ +public: + wxZipEntry(const wxString& name = wxEmptyString, + const wxDateTime& dt = wxDateTime::Now(), + wxFileOffset size = wxInvalidOffset); + virtual ~wxZipEntry(); + + wxZipEntry(const wxZipEntry& entry); + wxZipEntry& operator=(const wxZipEntry& entry); + + // Get accessors + wxDateTime GetDateTime() const { return m_DateTime; } + wxFileOffset GetSize() const { return m_Size; } + wxFileOffset GetOffset() const { return m_Offset; } + wxString GetInternalName() const { return m_Name; } + int GetMethod() const { return m_Method; } + int GetFlags() const { return m_Flags; } + wxUint32 GetCrc() const { return m_Crc; } + wxFileOffset GetCompressedSize() const { return m_CompressedSize; } + int GetSystemMadeBy() const { return m_SystemMadeBy; } + wxString GetComment() const { return m_Comment; } + wxUint32 GetExternalAttributes() const { return m_ExternalAttributes; } + wxPathFormat GetInternalFormat() const { return wxPATH_UNIX; } + int GetMode() const; + const char *GetLocalExtra() const; + size_t GetLocalExtraLen() const; + const char *GetExtra() const; + size_t GetExtraLen() const; + wxString GetName(wxPathFormat format = wxPATH_NATIVE) const; + + // is accessors + inline bool IsDir() const; + inline bool IsText() const; + inline bool IsReadOnly() const; + inline bool IsMadeByUnix() const; + + // set accessors + void SetDateTime(const wxDateTime& dt) { m_DateTime = dt; } + void SetSize(wxFileOffset size) { m_Size = size; } + void SetMethod(int method) { m_Method = method; } + void SetComment(const wxString& comment) { m_Comment = comment; } + void SetExternalAttributes(wxUint32 attr ) { m_ExternalAttributes = attr; } + void SetSystemMadeBy(int system); + void SetMode(int mode); + void SetExtra(const char *extra, size_t len); + void SetLocalExtra(const char *extra, size_t len); + + inline void SetName(const wxString& name, + wxPathFormat format = wxPATH_NATIVE); + + static wxString GetInternalName(const wxString& name, + wxPathFormat format = wxPATH_NATIVE, + bool *pIsDir = NULL); + + // set is accessors + void SetIsDir(bool isDir = true); + inline void SetIsReadOnly(bool isReadOnly = true); + inline void SetIsText(bool isText = true); - virtual wxFileOffset GetLength() const {return m_Size;} - virtual bool Eof() const; + wxZipEntry *Clone() const { return ZipClone(); } + + void SetNotifier(wxZipNotifier& notifier); + void UnsetNotifier(); protected: - virtual size_t OnSysRead(void *buffer, size_t bufsize); - virtual wxFileOffset OnSysSeek(wxFileOffset seek, wxSeekMode mode); - virtual wxFileOffset OnSysTell() const {return m_Pos;} + // Internal attributes + enum { TEXT_ATTR = 1 }; + + // protected Get accessors + int GetVersionNeeded() const { return m_VersionNeeded; } + wxFileOffset GetKey() const { return m_Key; } + int GetVersionMadeBy() const { return m_VersionMadeBy; } + int GetDiskStart() const { return m_DiskStart; } + int GetInternalAttributes() const { return m_InternalAttributes; } + + void SetVersionNeeded(int version) { m_VersionNeeded = version; } + void SetOffset(wxFileOffset offset) { m_Offset = offset; } + void SetFlags(int flags) { m_Flags = flags; } + void SetVersionMadeBy(int version) { m_VersionMadeBy = version; } + void SetCrc(wxUint32 crc) { m_Crc = crc; } + void SetCompressedSize(wxFileOffset size) { m_CompressedSize = size; } + void SetKey(wxFileOffset offset) { m_Key = offset; } + void SetDiskStart(int start) { m_DiskStart = start; } + void SetInternalAttributes(int attr) { m_InternalAttributes = attr; } + + virtual wxZipEntry *ZipClone() const { return new wxZipEntry(*this); } + + void Notify(); private: + wxArchiveEntry* DoClone() const { return ZipClone(); } + + size_t ReadLocal(wxInputStream& stream, wxMBConv& conv); + size_t WriteLocal(wxOutputStream& stream, wxMBConv& conv) const; + + size_t ReadCentral(wxInputStream& stream, wxMBConv& conv); + size_t WriteCentral(wxOutputStream& stream, wxMBConv& conv) const; + + size_t ReadDescriptor(wxInputStream& stream); + size_t WriteDescriptor(wxOutputStream& stream, wxUint32 crc, + wxFileOffset compressedSize, wxFileOffset size); + + wxUint8 m_SystemMadeBy; // one of enum wxZipSystem + wxUint8 m_VersionMadeBy; // major * 10 + minor + + wxUint16 m_VersionNeeded; // ver needed to extract (20 i.e. v2.0) + wxUint16 m_Flags; + wxUint16 m_Method; // compression method (one of wxZipMethod) + wxDateTime m_DateTime; + wxUint32 m_Crc; + wxFileOffset m_CompressedSize; wxFileOffset m_Size; - wxFileOffset m_Pos; + wxString m_Name; // in internal format + wxFileOffset m_Key; // the original offset for copied entries + wxFileOffset m_Offset; // file offset of the entry + + wxString m_Comment; + wxUint16 m_DiskStart; // for multidisk archives, not unsupported + wxUint16 m_InternalAttributes; // bit 0 set for text files + wxUint32 m_ExternalAttributes; // system specific depends on SystemMadeBy + + class wxZipMemory *m_Extra; + class wxZipMemory *m_LocalExtra; + + wxZipNotifier *m_zipnotifier; + class wxZipWeakLinks *m_backlink; - // this void* is handle of archive . I'm sorry it is void and not proper - // type but I don't want to make unzip.h header public. - void *m_Archive; + friend class wxZipInputStream; + friend class wxZipOutputStream; + + DECLARE_DYNAMIC_CLASS(wxZipEntry) +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxZipOutputStream + +WX_DECLARE_LIST_WITH_DECL(wxZipEntry, _wxZipEntryList, class WXDLLIMPEXP_BASE); + +class WXDLLIMPEXP_BASE wxZipOutputStream : public wxArchiveOutputStream +{ +public: + wxZipOutputStream(wxOutputStream& stream, + int level = -1, + wxMBConv& conv = wxConvLocal); + virtual ~wxZipOutputStream(); + + bool PutNextEntry(wxZipEntry *entry) { return DoCreate(entry); } + + bool PutNextEntry(const wxString& name, + const wxDateTime& dt = wxDateTime::Now(), + wxFileOffset size = wxInvalidOffset); + + bool PutNextDirEntry(const wxString& name, + const wxDateTime& dt = wxDateTime::Now()); + + bool CopyEntry(wxZipEntry *entry, wxZipInputStream& inputStream); + bool CopyArchiveMetaData(wxZipInputStream& inputStream); + + void Sync(); + bool CloseEntry(); + bool Close(); + + void SetComment(const wxString& comment) { m_Comment = comment; } + + int GetLevel() const { return m_level; } + void SetLevel(int level); + +protected: + virtual size_t OnSysWrite(const void *buffer, size_t size); + virtual wxFileOffset OnSysTell() const { return m_entrySize; } + + struct Buffer { const char *m_data; size_t m_size; }; + virtual wxOutputStream *OpenCompressor(wxOutputStream& stream, + wxZipEntry& entry, + const Buffer bufs[]); + virtual bool CloseCompressor(wxOutputStream *comp); + + bool IsParentSeekable() const { return m_offsetAdjustment + != wxInvalidOffset; } + +private: + bool PutNextEntry(wxArchiveEntry *entry); + bool CopyEntry(wxArchiveEntry *entry, wxArchiveInputStream& stream); + bool CopyArchiveMetaData(wxArchiveInputStream& stream); + + bool IsOpened() const { return m_comp || m_pending; } + + bool DoCreate(wxZipEntry *entry, bool raw = false); + void CreatePendingEntry(const void *buffer, size_t size); + void CreatePendingEntry(); + + class wxStoredOutputStream *m_store; + class wxZlibOutputStream2 *m_deflate; + class wxZipStreamLink *m_backlink; + _wxZipEntryList m_entries; + char *m_initialData; + size_t m_initialSize; + wxZipEntry *m_pending; + bool m_raw; + wxFileOffset m_headerOffset; + size_t m_headerSize; + wxFileOffset m_entrySize; + wxUint32 m_crcAccumulator; + wxOutputStream *m_comp; + int m_level; + wxFileOffset m_offsetAdjustment; + wxString m_Comment; + + DECLARE_NO_COPY_CLASS(wxZipOutputStream) +}; + + +///////////////////////////////////////////////////////////////////////////// +// wxZipInputStream + +class WXDLLIMPEXP_BASE wxZipInputStream : public wxArchiveInputStream +{ +public: + typedef wxZipEntry entry_type; + + wxZipInputStream(wxInputStream& stream, wxMBConv& conv = wxConvLocal); + wxZipInputStream(const wxString& archive, const wxString& file); + virtual ~wxZipInputStream(); + + bool OpenEntry(wxZipEntry& entry) { return DoOpen(&entry); } + bool CloseEntry(); + + wxZipEntry *GetNextEntry(); + + wxString GetComment(); + int GetTotalEntries(); + + virtual wxFileOffset GetLength() const { return m_entry.GetSize(); } + +protected: + size_t OnSysRead(void *buffer, size_t size); + wxFileOffset OnSysTell() const { return m_decomp ? m_decomp->TellI() : 0; } + wxFileOffset OnSysSeek(wxFileOffset seek, wxSeekMode mode); + + virtual wxInputStream *OpenDecompressor(wxInputStream& stream); + virtual bool CloseDecompressor(wxInputStream *decomp); + +private: + void Init(); + wxInputStream& OpenFile(const wxString& archive); + + wxArchiveEntry *DoGetNextEntry() { return GetNextEntry(); } + + bool OpenEntry(wxArchiveEntry& entry); + + wxStreamError ReadLocal(bool readEndRec = false); + wxStreamError ReadCentral(); + + wxUint32 ReadSignature(); + bool FindEndRecord(); + bool LoadEndRecord(); + + bool AtHeader() const { return m_headerSize == 0; } + bool AfterHeader() const { return m_headerSize > 0 && !m_decomp; } + bool IsOpened() const { return m_decomp != NULL; } + + wxZipStreamLink *MakeLink(wxZipOutputStream *out); + + bool DoOpen(wxZipEntry *entry = NULL, bool raw = false); + bool OpenDecompressor(bool raw = false); + + class wxStoredInputStream *m_store; + class wxZlibInputStream2 *m_inflate; + class wxRawInputStream *m_rawin; + class wxFFileInputStream *m_ffile; + wxZipEntry m_entry; + bool m_raw; + size_t m_headerSize; + wxUint32 m_crcAccumulator; + wxInputStream *m_decomp; + bool m_parentSeekable; + class wxZipWeakLinks *m_weaklinks; + class wxZipStreamLink *m_streamlink; + wxFileOffset m_offsetAdjustment; + wxFileOffset m_position; + wxUint32 m_signature; + size_t m_TotalEntries; + wxString m_Comment; + + friend bool wxZipOutputStream::CopyEntry( + wxZipEntry *entry, wxZipInputStream& inputStream); + friend bool wxZipOutputStream::CopyArchiveMetaData( + wxZipInputStream& inputStream); DECLARE_NO_COPY_CLASS(wxZipInputStream) }; -#endif - // wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB +///////////////////////////////////////////////////////////////////////////// +// wxZipClassFactory +class WXDLLIMPEXP_BASE wxZipClassFactory : public wxArchiveClassFactory +{ +public: + wxZipEntry *NewEntry() const + { return new wxZipEntry; } + wxZipInputStream *NewStream(wxInputStream& stream) const + { return new wxZipInputStream(stream, GetConv()); } + wxZipOutputStream *NewStream(wxOutputStream& stream) const + { return new wxZipOutputStream(stream, -1, GetConv()); } + + wxString GetInternalName(const wxString& name, + wxPathFormat format = wxPATH_NATIVE) const + { return wxZipEntry::GetInternalName(name, format); } + +protected: + wxArchiveEntry *DoNewEntry() const + { return NewEntry(); } + wxArchiveInputStream *DoNewStream(wxInputStream& stream) const + { return NewStream(stream); } + wxArchiveOutputStream *DoNewStream(wxOutputStream& stream) const + { return NewStream(stream); } + +private: + DECLARE_DYNAMIC_CLASS(wxZipClassFactory) +}; + + +///////////////////////////////////////////////////////////////////////////// +// Iterators + +#if wxUSE_STL || defined WX_TEST_ARCHIVE_ITERATOR +typedef wxArchiveIterator wxZipIter; +typedef wxArchiveIterator > wxZipPairIter; #endif - // __ZIPSTREAM_H__ + + +///////////////////////////////////////////////////////////////////////////// +// wxZipEntry inlines + +bool wxZipEntry::IsText() const +{ + return (m_InternalAttributes & TEXT_ATTR) != 0; +} + +bool wxZipEntry::IsDir() const +{ + return (m_ExternalAttributes & wxZIP_A_SUBDIR) != 0; +} + +bool wxZipEntry::IsReadOnly() const +{ + return (m_ExternalAttributes & wxZIP_A_RDONLY) != 0; +} + +bool wxZipEntry::IsMadeByUnix() const +{ + const int pattern = + (1 << wxZIP_SYSTEM_OPENVMS) | + (1 << wxZIP_SYSTEM_UNIX) | + (1 << wxZIP_SYSTEM_ATARI_ST) | + (1 << wxZIP_SYSTEM_ACORN_RISC) | + (1 << wxZIP_SYSTEM_BEOS) | (1 << wxZIP_SYSTEM_TANDEM); + + // note: some unix zippers put madeby = dos + return (m_SystemMadeBy == wxZIP_SYSTEM_MSDOS + && (m_ExternalAttributes & ~0xFFFF)) + || ((pattern >> m_SystemMadeBy) & 1); +} + +void wxZipEntry::SetIsText(bool isText) +{ + if (isText) + m_InternalAttributes |= TEXT_ATTR; + else + m_InternalAttributes &= ~TEXT_ATTR; +} + +void wxZipEntry::SetIsReadOnly(bool isReadOnly) +{ + if (isReadOnly) + SetMode(GetMode() & ~0222); + else + SetMode(GetMode() | 0200); +} + +void wxZipEntry::SetName(const wxString& name, + wxPathFormat format /*=wxPATH_NATIVE*/) +{ + bool isDir; + m_Name = GetInternalName(name, format, &isDir); + SetIsDir(isDir); +} + + +#endif // wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM + +#endif // _WX_WXZIPSTREAM_H__ diff --git a/src/common/archive.cpp b/src/common/archive.cpp new file mode 100644 index 0000000000..6420d94852 --- /dev/null +++ b/src/common/archive.cpp @@ -0,0 +1,74 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: archive.cpp +// Purpose: Streams for archive formats +// Author: Mike Wetherell +// RCS-ID: $Id$ +// Copyright: (c) Mike Wetherell +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) + #pragma implementation "archive.h" +#endif + +// For compilers that support precompilation, includes "wx.h". +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#ifndef WX_PRECOMP + #include "wx/defs.h" +#endif + +#if wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM + +#include "wx/archive.h" +#include "wx/html/forcelnk.h" + +IMPLEMENT_ABSTRACT_CLASS(wxArchiveEntry, wxObject) +IMPLEMENT_ABSTRACT_CLASS(wxArchiveClassFactory, wxObject) + +FORCE_LINK(zipstrm) + + +///////////////////////////////////////////////////////////////////////////// +// wxArchiveInputStream + +wxArchiveInputStream::wxArchiveInputStream(wxInputStream& stream, + wxMBConv& conv) + : wxFilterInputStream(stream), + m_conv(conv) +{ +} + + +///////////////////////////////////////////////////////////////////////////// +// wxArchiveOutputStream + +wxArchiveOutputStream::wxArchiveOutputStream(wxOutputStream& stream, + wxMBConv& conv) + : wxFilterOutputStream(stream), + m_conv(conv) +{ +} + + +///////////////////////////////////////////////////////////////////////////// +// wxArchiveEntry + +void wxArchiveEntry::SetNotifier(wxArchiveNotifier& notifier) +{ + UnsetNotifier(); + m_notifier = ¬ifier; + m_notifier->OnEntryUpdated(*this); +} + +wxArchiveEntry& wxArchiveEntry::operator=(const wxArchiveEntry& entry) +{ + m_notifier = entry.m_notifier; + return *this; +} + +#endif diff --git a/src/common/fs_zip.cpp b/src/common/fs_zip.cpp index 65fbf89cc2..458db30ee3 100644 --- a/src/common/fs_zip.cpp +++ b/src/common/fs_zip.cpp @@ -27,15 +27,10 @@ #endif #include "wx/filesys.h" +#include "wx/wfstream.h" #include "wx/zipstrm.h" #include "wx/fs_zip.h" -/* Not the right solution (paths in makefiles) but... */ -#ifdef __BORLANDC__ -#include "../common/unzip.h" -#else -#include "unzip.h" -#endif //---------------------------------------------------------------------------- // wxZipFSHandler @@ -56,13 +51,22 @@ wxZipFSHandler::wxZipFSHandler() : wxFileSystemHandler() wxZipFSHandler::~wxZipFSHandler() { if (m_Archive) - unzClose((unzFile)m_Archive); + CloseArchive(m_Archive); if (m_DirsFound) delete m_DirsFound; } +void wxZipFSHandler::CloseArchive(wxZipInputStream *archive) +{ + wxInputStream *stream = archive->GetFilterInputStream(); + delete archive; + delete stream; +} + + + bool wxZipFSHandler::CanOpen(const wxString& location) { wxString p = GetProtocol(location); @@ -125,7 +129,7 @@ wxString wxZipFSHandler::FindFirst(const wxString& spec, int flags) if (m_Archive) { - unzClose((unzFile)m_Archive); + CloseArchive(m_Archive); m_Archive = NULL; } @@ -147,26 +151,18 @@ wxString wxZipFSHandler::FindFirst(const wxString& spec, int flags) m_ZipFile = left; wxString nativename = wxFileSystem::URLToFileName(m_ZipFile).GetFullPath(); - m_Archive = (void*) unzOpen(nativename.mb_str(wxConvFile)); + m_Archive = new wxZipInputStream(*new wxFFileInputStream(nativename)); m_Pattern = right.AfterLast(wxT('/')); m_BaseDir = right.BeforeLast(wxT('/')); if (m_Archive) { - if (unzGoToFirstFile((unzFile)m_Archive) != UNZ_OK) - { - unzClose((unzFile)m_Archive); - m_Archive = NULL; - } - else + if (m_AllowDirs) { - if (m_AllowDirs) - { - delete m_DirsFound; - m_DirsFound = new wxLongToLongHashMap(); - } - return DoFind(); + delete m_DirsFound; + m_DirsFound = new wxLongToLongHashMap(); } + return DoFind(); } return wxEmptyString; } @@ -183,16 +179,20 @@ wxString wxZipFSHandler::FindNext() wxString wxZipFSHandler::DoFind() { - static char namebuf[1024]; // char, not wxChar! - char *c; wxString namestr, dir, filename; wxString match = wxEmptyString; while (match == wxEmptyString) { - unzGetCurrentFileInfo((unzFile)m_Archive, NULL, namebuf, 1024, NULL, 0, NULL, 0); - for (c = namebuf; *c; c++) if (*c == '\\') *c = '/'; - namestr = wxString::FromAscii(namebuf); // TODO what encoding does ZIP use? + wxZipEntry *entry = m_Archive->GetNextEntry(); + if (!entry) + { + CloseArchive(m_Archive); + m_Archive = NULL; + break; + } + namestr = entry->GetName(wxPATH_UNIX); + delete entry; if (m_AllowDirs) { @@ -221,13 +221,6 @@ wxString wxZipFSHandler::DoFind() if (m_AllowFiles && !filename.IsEmpty() && m_BaseDir == dir && wxMatchWild(m_Pattern, filename, false)) match = m_ZipFile + wxT("#zip:") + namestr; - - if (unzGoToNextFile((unzFile)m_Archive) != UNZ_OK) - { - unzClose((unzFile)m_Archive); - m_Archive = NULL; - break; - } } return match; diff --git a/src/common/zipstrm.cpp b/src/common/zipstrm.cpp index 93048447e3..1f291dbc43 100644 --- a/src/common/zipstrm.cpp +++ b/src/common/zipstrm.cpp @@ -1,13 +1,14 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: zipstream.cpp -// Purpose: input stream for ZIP archive access -// Author: Vaclav Slavik -// Copyright: (c) 1999 Vaclav Slavik +// Name: zipstrm.cpp +// Purpose: Streams for Zip files +// Author: Mike Wetherell +// RCS-ID: $Id$ +// Copyright: (c) Mike Wetherell // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) -#pragma implementation "zipstrm.h" + #pragma implementation "zipstrm.h" #endif // For compilers that support precompilation, includes "wx.h". @@ -17,124 +18,1756 @@ #pragma hdrstop #endif -#if wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB +#ifndef WX_PRECOMP + #include "wx/defs.h" +#endif + +#if wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM + +#include "wx/zipstrm.h" +#include "wx/log.h" +#include "wx/intl.h" +#include "wx/datstrm.h" +#include "wx/zstream.h" +#include "wx/mstream.h" +#include "wx/utils.h" +#include "wx/buffer.h" +#include "wx/ptr_scpd.h" +#include "wx/wfstream.h" +#include "wx/html/forcelnk.h" +#include "zlib.h" + +// value for the 'version needed to extract' field (20 means 2.0) +enum { + VERSION_NEEDED_TO_EXTRACT = 20 +}; + +// signatures for the various records (PKxx) +enum { + CENTRAL_MAGIC = 0x02014b50, // central directory record + LOCAL_MAGIC = 0x04034b50, // local header + END_MAGIC = 0x06054b50, // end of central directory record + SUMS_MAGIC = 0x08074b50 // data descriptor (info-zip) +}; + +// unix file attributes. zip stores them in the high 16 bits of the +// 'external attributes' field, hence the extra zeros. +enum { + wxZIP_S_IFMT = 0xF0000000, + wxZIP_S_IFDIR = 0x40000000, + wxZIP_S_IFREG = 0x80000000 +}; + +// minimum sizes for the various records +enum { + CENTRAL_SIZE = 46, + LOCAL_SIZE = 30, + END_SIZE = 22, + SUMS_SIZE = 12 +}; + +// The number of bytes that must be written to an wxZipOutputStream before +// a zip entry is created. The purpose of this latency is so that +// OpenCompressor() can see a little data before deciding which compressor +// it should use. +enum { + OUTPUT_LATENCY = 4096 +}; + +// Some offsets into the local header +enum { + SUMS_OFFSET = 14 +}; + +IMPLEMENT_DYNAMIC_CLASS(wxZipEntry, wxArchiveEntry) +IMPLEMENT_DYNAMIC_CLASS(wxZipClassFactory, wxArchiveClassFactory) + +FORCE_LINK_ME(zipstrm) + + +///////////////////////////////////////////////////////////////////////////// +// Helpers + +// read a string of a given length +// +static wxString ReadString(wxInputStream& stream, wxUint16 len, wxMBConv& conv) +{ +#if wxUSE_UNICODE + wxCharBuffer buf(len); + stream.Read(buf.data(), len); + wxString str(buf, conv); +#else + wxString str; + (void)conv; + { + wxStringBuffer buf(str, len); + stream.Read(buf, len); + } +#endif + + return str; +} + +// Decode a little endian wxUint32 number from a character array +// +static inline wxUint32 CrackUint32(const char *m) +{ + const unsigned char *n = (const unsigned char*)m; + return (n[3] << 24) | (n[2] << 16) | (n[1] << 8) | n[0]; +} + +// Temporarily lower the logging level in debug mode to avoid a warning +// from SeekI about seeking on a stream with data written back to it. +// +static wxFileOffset QuietSeek(wxInputStream& stream, wxFileOffset pos) +{ +#ifdef __WXDEBUG__ + wxLogLevel level = wxLog::GetLogLevel(); + wxLog::SetLogLevel(wxLOG_Debug - 1); + wxFileOffset result = stream.SeekI(pos); + wxLog::SetLogLevel(level); + return result; +#else + return stream.SeekI(pos); +#endif +} + + +///////////////////////////////////////////////////////////////////////////// +// Stored input stream +// Trival decompressor for files which are 'stored' in the zip file. + +class wxStoredInputStream : public wxFilterInputStream +{ +public: + wxStoredInputStream(wxInputStream& stream); + + void Open(wxFileOffset len) { Close(); m_len = len; } + void Close() { m_pos = 0; m_lasterror = wxSTREAM_NO_ERROR; } + + virtual char Peek() { return wxInputStream::Peek(); } + virtual size_t GetSize() const { return m_len; } + +protected: + virtual size_t OnSysRead(void *buffer, size_t size); + virtual wxFileOffset OnSysTell() const { return m_pos; } + +private: + wxFileOffset m_pos; + wxFileOffset m_len; + + DECLARE_NO_COPY_CLASS(wxStoredInputStream) +}; + +wxStoredInputStream::wxStoredInputStream(wxInputStream& stream) + : wxFilterInputStream(stream), + m_pos(0), + m_len(0) +{ +} + +size_t wxStoredInputStream::OnSysRead(void *buffer, size_t size) +{ + size_t count = wxMin(size, m_len - m_pos + (size_t)0); + count = m_parent_i_stream->Read(buffer, count).LastRead(); + m_pos += count; + + if (m_pos == m_len) + m_lasterror = wxSTREAM_EOF; + else if (!*m_parent_i_stream) + m_lasterror = wxSTREAM_READ_ERROR; + + return count; +} + + +///////////////////////////////////////////////////////////////////////////// +// Stored output stream +// Trival compressor for files which are 'stored' in the zip file. + +class wxStoredOutputStream : public wxFilterOutputStream +{ +public: + wxStoredOutputStream(wxOutputStream& stream) : + wxFilterOutputStream(stream), m_pos(0) { } + + bool Close() { + m_pos = 0; + m_lasterror = wxSTREAM_NO_ERROR; + return true; + } + +protected: + virtual size_t OnSysWrite(const void *buffer, size_t size); + virtual wxFileOffset OnSysTell() const { return m_pos; } + +private: + wxFileOffset m_pos; + DECLARE_NO_COPY_CLASS(wxStoredOutputStream) +}; + +size_t wxStoredOutputStream::OnSysWrite(const void *buffer, size_t size) +{ + if (!IsOk() || !size) + return 0; + size_t count = m_parent_o_stream->Write(buffer, size).LastWrite(); + if (count != size) + m_lasterror = wxSTREAM_WRITE_ERROR; + m_pos += count; + return count; +} + + +///////////////////////////////////////////////////////////////////////////// +// wxRawInputStream +// +// Used to handle the unusal case of raw copying an entry of unknown +// length. This can only happen when the zip being copied from is being +// read from a non-seekable stream, and also was original written to a +// non-seekable stream. +// +// In this case there's no option but to decompress the stream to find +// it's length, but we can still write the raw compressed data to avoid the +// compression overhead (which is the greater one). +// +// Usage is like this: +// m_rawin = new wxRawInputStream(*m_parent_i_stream); +// m_decomp = m_rawin->Open(OpenDecompressor(m_rawin->GetTee())); +// +// The wxRawInputStream owns a wxTeeInputStream object, the role of which +// is something like the unix 'tee' command; it is a transparent filter, but +// allows the data read to be read a second time via an extra method 'GetData'. +// +// The wxRawInputStream then draws data through the tee using a decompressor +// then instead of returning the decompressed data, retuns the raw data +// from wxTeeInputStream::GetData(). + +class wxTeeInputStream : public wxFilterInputStream +{ +public: + wxTeeInputStream(wxInputStream& stream); + + size_t GetCount() const { return m_end - m_start; } + size_t GetData(char *buffer, size_t size); + + void Open(); + bool Final(); + + wxInputStream& Read(void *buffer, size_t size); + +protected: + virtual size_t OnSysRead(void *buffer, size_t size); + virtual wxFileOffset OnSysTell() const { return m_pos; } + +private: + wxFileOffset m_pos; + wxMemoryBuffer m_buf; + size_t m_start; + size_t m_end; + + DECLARE_NO_COPY_CLASS(wxTeeInputStream) +}; + +wxTeeInputStream::wxTeeInputStream(wxInputStream& stream) + : wxFilterInputStream(stream), + m_pos(0), m_buf(8192), m_start(0), m_end(0) +{ +} + +void wxTeeInputStream::Open() +{ + m_pos = m_start = m_end = 0; + m_lasterror = wxSTREAM_NO_ERROR; +} + +bool wxTeeInputStream::Final() +{ + bool final = m_end == m_buf.GetDataLen(); + m_end = m_buf.GetDataLen(); + return final; +} + +wxInputStream& wxTeeInputStream::Read(void *buffer, size_t size) +{ + size_t count = wxInputStream::Read(buffer, size).LastRead(); + m_end = m_buf.GetDataLen(); + m_buf.AppendData(buffer, count); + return *this; +} + +size_t wxTeeInputStream::OnSysRead(void *buffer, size_t size) +{ + size_t count = m_parent_i_stream->Read(buffer, size).LastRead(); + m_lasterror = m_parent_i_stream->GetLastError(); + return count; +} + +size_t wxTeeInputStream::GetData(char *buffer, size_t size) +{ + if (m_wbacksize) { + size_t len = m_buf.GetDataLen(); + len = len > m_wbacksize ? len - m_wbacksize : 0; + m_buf.SetDataLen(len); + if (m_end > len) { + wxFAIL; // we've already returned data that's now being ungot + m_end = len; + } + m_parent_i_stream->Ungetch(m_wback, m_wbacksize); + free(m_wback); + m_wback = NULL; + m_wbacksize = 0; + m_wbackcur = 0; + } + + if (size > GetCount()) + size = GetCount(); + if (size) { + memcpy(buffer, m_buf + m_start, size); + m_start += size; + wxASSERT(m_start <= m_end); + } + + if (m_start == m_end && m_start > 0 && m_buf.GetDataLen() > 0) { + size_t len = m_buf.GetDataLen(); + char *buf = (char*)m_buf.GetWriteBuf(len); + len -= m_end; + memmove(buf, buf + m_end, len); + m_buf.UngetWriteBuf(len); + m_start = m_end = 0; + } + + return size; +} + +class wxRawInputStream : public wxFilterInputStream +{ +public: + wxRawInputStream(wxInputStream& stream); + virtual ~wxRawInputStream() { delete m_tee; } + + wxInputStream* Open(wxInputStream *decomp); + wxInputStream& GetTee() const { return *m_tee; } + +protected: + virtual size_t OnSysRead(void *buffer, size_t size); + virtual wxFileOffset OnSysTell() const { return m_pos; } + +private: + wxFileOffset m_pos; + wxTeeInputStream *m_tee; + + enum { BUFSIZE = 8192 }; + wxCharBuffer m_dummy; + + DECLARE_NO_COPY_CLASS(wxRawInputStream) +}; + +wxRawInputStream::wxRawInputStream(wxInputStream& stream) + : wxFilterInputStream(stream), + m_pos(0), + m_tee(new wxTeeInputStream(stream)), + m_dummy(BUFSIZE) +{ +} + +wxInputStream *wxRawInputStream::Open(wxInputStream *decomp) +{ + if (decomp) { + m_parent_i_stream = decomp; + m_pos = 0; + m_lasterror = wxSTREAM_NO_ERROR; + m_tee->Open(); + return this; + } else { + return NULL; + } +} + +size_t wxRawInputStream::OnSysRead(void *buffer, size_t size) +{ + char *buf = (char*)buffer; + size_t count = 0; + + while (count < size && IsOk()) + { + while (m_parent_i_stream->IsOk() && m_tee->GetCount() == 0) + m_parent_i_stream->Read(m_dummy.data(), BUFSIZE); + + size_t n = m_tee->GetData(buf + count, size - count); + count += n; + + if (n == 0 && m_tee->Final()) + m_lasterror = m_parent_i_stream->GetLastError(); + } + + m_pos += count; + return count; +} + + +///////////////////////////////////////////////////////////////////////////// +// Zlib streams than can be reused without recreating. + +class wxZlibOutputStream2 : public wxZlibOutputStream +{ +public: + wxZlibOutputStream2(wxOutputStream& stream, int level) : + wxZlibOutputStream(stream, level, wxZLIB_NO_HEADER) { } + + bool Open(wxOutputStream& stream); + bool Close() { DoFlush(true); m_pos = wxInvalidOffset; return IsOk(); } +}; + +bool wxZlibOutputStream2::Open(wxOutputStream& stream) +{ + wxCHECK(m_pos == wxInvalidOffset, false); + + m_deflate->next_out = m_z_buffer; + m_deflate->avail_out = m_z_size; + m_pos = 0; + m_lasterror = wxSTREAM_NO_ERROR; + m_parent_o_stream = &stream; + + if (deflateReset(m_deflate) != Z_OK) { + wxLogError(_("can't re-initialize zlib deflate stream")); + m_lasterror = wxSTREAM_WRITE_ERROR; + return false; + } + + return true; +} + +class wxZlibInputStream2 : public wxZlibInputStream +{ +public: + wxZlibInputStream2(wxInputStream& stream) : + wxZlibInputStream(stream, wxZLIB_NO_HEADER) { } + + bool Open(wxInputStream& stream); +}; + +bool wxZlibInputStream2::Open(wxInputStream& stream) +{ + m_inflate->avail_in = 0; + m_pos = 0; + m_lasterror = wxSTREAM_NO_ERROR; + m_parent_i_stream = &stream; + + if (inflateReset(m_inflate) != Z_OK) { + wxLogError(_("can't re-initialize zlib inflate stream")); + m_lasterror = wxSTREAM_READ_ERROR; + return false; + } + + return true; +} + + +///////////////////////////////////////////////////////////////////////////// +// Class to hold wxZipEntry's Extra and LocalExtra fields + +class wxZipMemory +{ +public: + wxZipMemory() : m_data(NULL), m_size(0), m_capacity(0), m_ref(1) { } + + wxZipMemory *AddRef() { m_ref++; return this; } + void Release() { if (--m_ref == 0) delete this; } + + char *GetData() const { return m_data; } + size_t GetSize() const { return m_size; } + size_t GetCapacity() const { return m_capacity; } + + wxZipMemory *Unique(size_t size); + +private: + ~wxZipMemory() { delete m_data; } + + char *m_data; + size_t m_size; + size_t m_capacity; + int m_ref; +}; + +wxZipMemory *wxZipMemory::Unique(size_t size) +{ + wxZipMemory *zm; + + if (m_ref > 1) { + --m_ref; + zm = new wxZipMemory; + } else { + zm = this; + } + + if (zm->m_capacity < size) { + delete zm->m_data; + zm->m_data = new char[size]; + zm->m_capacity = size; + } + + zm->m_size = size; + return zm; +} + +static inline wxZipMemory *AddRef(wxZipMemory *zm) +{ + if (zm) + zm->AddRef(); + return zm; +} + +static inline void Release(wxZipMemory *zm) +{ + if (zm) + zm->Release(); +} + +static void Copy(wxZipMemory*& dest, wxZipMemory *src) +{ + Release(dest); + dest = AddRef(src); +} + +static void Unique(wxZipMemory*& zm, size_t size) +{ + if (!zm && size) + zm = new wxZipMemory; + if (zm) + zm = zm->Unique(size); +} + + +///////////////////////////////////////////////////////////////////////////// +// Collection of weak references to entries + +WX_DECLARE_HASH_MAP(long, wxZipEntry*, wxIntegerHash, + wxIntegerEqual, _wxOffsetZipEntryMap); + +class wxZipWeakLinks +{ +public: + wxZipWeakLinks() : m_ref(1) { } + + void Release(const wxZipInputStream* WXUNUSED(x)) + { if (--m_ref == 0) delete this; } + void Release(wxFileOffset key) + { RemoveEntry(key); if (--m_ref == 0) delete this; } + + wxZipWeakLinks *AddEntry(wxZipEntry *entry, wxFileOffset key); + void RemoveEntry(wxFileOffset key) { m_entries.erase(key); } + wxZipEntry *GetEntry(wxFileOffset key) const; + bool IsEmpty() const { return m_entries.empty(); } + +private: + ~wxZipWeakLinks() { wxASSERT(IsEmpty()); } + + int m_ref; + _wxOffsetZipEntryMap m_entries; +}; + +wxZipWeakLinks *wxZipWeakLinks::AddEntry(wxZipEntry *entry, wxFileOffset key) +{ + m_entries[key] = entry; + m_ref++; + return this; +} + +wxZipEntry *wxZipWeakLinks::GetEntry(wxFileOffset key) const +{ + _wxOffsetZipEntryMap::const_iterator it = m_entries.find(key); + return it != m_entries.end() ? it->second : NULL; +} + + +///////////////////////////////////////////////////////////////////////////// +// ZipEntry + +wxZipEntry::wxZipEntry( + const wxString& name /*=wxEmptyString*/, + const wxDateTime& dt /*=wxDateTime::Now()*/, + wxFileOffset size /*=wxInvalidOffset*/) + : + m_SystemMadeBy(wxZIP_SYSTEM_MSDOS), + m_VersionMadeBy(wxMAJOR_VERSION * 10 + wxMINOR_VERSION), + m_VersionNeeded(VERSION_NEEDED_TO_EXTRACT), + m_Flags(0), + m_Method(wxZIP_METHOD_DEFAULT), + m_DateTime(dt), + m_Crc(0), + m_CompressedSize(wxInvalidOffset), + m_Size(size), + m_Key(wxInvalidOffset), + m_Offset(wxInvalidOffset), + m_DiskStart(0), + m_InternalAttributes(0), + m_ExternalAttributes(0), + m_Extra(NULL), + m_LocalExtra(NULL), + m_zipnotifier(NULL), + m_backlink(NULL) +{ + if (!name.empty()) + SetName(name); +} + +wxZipEntry::~wxZipEntry() +{ + if (m_backlink) + m_backlink->Release(m_Key); + Release(m_Extra); + Release(m_LocalExtra); +} + +wxZipEntry::wxZipEntry(const wxZipEntry& e) + : m_SystemMadeBy(e.m_SystemMadeBy), + m_VersionMadeBy(e.m_VersionMadeBy), + m_VersionNeeded(e.m_VersionNeeded), + m_Flags(e.m_Flags), + m_Method(e.m_Method), + m_DateTime(e.m_DateTime), + m_Crc(e.m_Crc), + m_CompressedSize(e.m_CompressedSize), + m_Size(e.m_Size), + m_Name(e.m_Name), + m_Key(e.m_Key), + m_Offset(e.m_Offset), + m_Comment(e.m_Comment), + m_DiskStart(e.m_DiskStart), + m_InternalAttributes(e.m_InternalAttributes), + m_ExternalAttributes(e.m_ExternalAttributes), + m_Extra(AddRef(e.m_Extra)), + m_LocalExtra(AddRef(e.m_LocalExtra)), + m_zipnotifier(e.m_zipnotifier), + m_backlink(NULL) +{ +} + +wxZipEntry& wxZipEntry::operator=(const wxZipEntry& e) +{ + if (&e != this) { + m_SystemMadeBy = e.m_SystemMadeBy; + m_VersionMadeBy = e.m_VersionMadeBy; + m_VersionNeeded = e.m_VersionNeeded; + m_Flags = e.m_Flags; + m_Method = e.m_Method; + m_DateTime = e.m_DateTime; + m_Crc = e.m_Crc; + m_CompressedSize = e.m_CompressedSize; + m_Size = e.m_Size; + m_Name = e.m_Name; + m_Key = e.m_Key; + m_Offset = e.m_Offset; + m_Comment = e.m_Comment; + m_DiskStart = e.m_DiskStart; + m_InternalAttributes = e.m_InternalAttributes; + m_ExternalAttributes = e.m_ExternalAttributes; + Copy(m_Extra, e.m_Extra); + Copy(m_LocalExtra, e.m_LocalExtra); + m_zipnotifier = e.m_zipnotifier; + if (m_backlink) { + m_backlink->Release(m_Key); + m_backlink = NULL; + } + } + return *this; +} + +wxString wxZipEntry::GetName(wxPathFormat format /*=wxPATH_NATIVE*/) const +{ + bool isDir = IsDir() && !m_Name.empty(); + + switch (wxFileName::GetFormat(format)) { + case wxPATH_DOS: + { + wxString name(isDir ? m_Name + _T("\\") : m_Name); + for (size_t i = name.length() - 1; i > 0; --i) + if (name[i] == _T('/')) + name[i] = _T('\\'); + return name; + } + + case wxPATH_UNIX: + return isDir ? m_Name + _T("/") : m_Name; + + default: + ; + } + + wxFileName fn; + + if (isDir) + fn.AssignDir(m_Name, wxPATH_UNIX); + else + fn.Assign(m_Name, wxPATH_UNIX); + + return fn.GetFullPath(format); +} + +// Static - Internally tars and zips use forward slashes for the path +// separator, absolute paths aren't allowed, and directory names have a +// trailing slash. This function converts a path into this internal format, +// but without a trailing slash for a directory. +// +wxString wxZipEntry::GetInternalName(const wxString& name, + wxPathFormat format /*=wxPATH_NATIVE*/, + bool *pIsDir /*=NULL*/) +{ + wxString internal; + + if (wxFileName::GetFormat(format) != wxPATH_UNIX) + internal = wxFileName(name, format).GetFullPath(wxPATH_UNIX); + else + internal = name; + + bool isDir = !internal.empty() && internal.Last() == '/'; + if (pIsDir) + *pIsDir = isDir; + if (isDir) + internal.erase(internal.length() - 1); + + while (!internal.empty() && *internal.begin() == '/') + internal.erase(0, 1); + while (!internal.empty() && internal.compare(0, 2, _T("./")) == 0) + internal.erase(0, 2); + if (internal == _T(".") || internal == _T("..")) + internal = wxEmptyString; + + return internal; +} + +void wxZipEntry::SetSystemMadeBy(int system) +{ + int mode = GetMode(); + bool wasUnix = IsMadeByUnix(); + + m_SystemMadeBy = system; + + if (!wasUnix && IsMadeByUnix()) { + SetIsDir(IsDir()); + SetMode(mode); + } else if (wasUnix && !IsMadeByUnix()) { + m_ExternalAttributes &= 0xffff; + } +} + +void wxZipEntry::SetIsDir(bool isDir /*=true*/) +{ + if (isDir) + m_ExternalAttributes |= wxZIP_A_SUBDIR; + else + m_ExternalAttributes &= ~wxZIP_A_SUBDIR; + + if (IsMadeByUnix()) { + m_ExternalAttributes &= ~wxZIP_S_IFMT; + if (isDir) + m_ExternalAttributes |= wxZIP_S_IFDIR; + else + m_ExternalAttributes |= wxZIP_S_IFREG; + } +} + +// Return unix style permission bits +// +int wxZipEntry::GetMode() const +{ + // return unix permissions if present + if (IsMadeByUnix()) + return (m_ExternalAttributes >> 16) & 0777; + + // otherwise synthesize from the dos attribs + int mode = 0644; + if (m_ExternalAttributes & wxZIP_A_RDONLY) + mode &= ~0200; + if (m_ExternalAttributes & wxZIP_A_SUBDIR) + mode |= 0111; + + return mode; +} + +// Set unix permissions +// +void wxZipEntry::SetMode(int mode) +{ + // Set dos attrib bits to be compatible + if (mode & 0222) + m_ExternalAttributes &= ~wxZIP_A_RDONLY; + else + m_ExternalAttributes |= wxZIP_A_RDONLY; + + // set the actual unix permission bits if the system type allows + if (IsMadeByUnix()) { + m_ExternalAttributes &= ~(0777L << 16); + m_ExternalAttributes |= (mode & 0777L) << 16; + } +} + +const char *wxZipEntry::GetExtra() const +{ + return m_Extra ? m_Extra->GetData() : NULL; +} + +size_t wxZipEntry::GetExtraLen() const +{ + return m_Extra ? m_Extra->GetSize() : 0; +} + +void wxZipEntry::SetExtra(const char *extra, size_t len) +{ + Unique(m_Extra, len); + if (len) + memcpy(m_Extra->GetData(), extra, len); +} + +const char *wxZipEntry::GetLocalExtra() const +{ + return m_LocalExtra ? m_LocalExtra->GetData() : NULL; +} + +size_t wxZipEntry::GetLocalExtraLen() const +{ + return m_LocalExtra ? m_LocalExtra->GetSize() : 0; +} + +void wxZipEntry::SetLocalExtra(const char *extra, size_t len) +{ + Unique(m_LocalExtra, len); + if (len) + memcpy(m_LocalExtra->GetData(), extra, len); +} + +void wxZipEntry::SetNotifier(wxZipNotifier& notifier) +{ + wxArchiveEntry::UnsetNotifier(); + m_zipnotifier = ¬ifier; + m_zipnotifier->OnEntryUpdated(*this); +} + +void wxZipEntry::Notify() +{ + if (m_zipnotifier) + m_zipnotifier->OnEntryUpdated(*this); + else if (GetNotifier()) + GetNotifier()->OnEntryUpdated(*this); +} + +void wxZipEntry::UnsetNotifier() +{ + wxArchiveEntry::UnsetNotifier(); + m_zipnotifier = NULL; +} + +size_t wxZipEntry::ReadLocal(wxInputStream& stream, wxMBConv& conv) +{ + wxUint16 nameLen, extraLen; + wxUint32 compressedSize, size, crc; + + wxDataInputStream ds(stream); + + ds >> m_VersionNeeded >> m_Flags >> m_Method; + SetDateTime(wxDateTime().SetFromDOS(ds.Read32())); + ds >> crc >> compressedSize >> size >> nameLen >> extraLen; + + bool sumsValid = (m_Flags & wxZIP_SUMS_FOLLOW) == 0; + + if (sumsValid || crc) + m_Crc = crc; + if ((sumsValid || compressedSize) || m_Method == wxZIP_METHOD_STORE) + m_CompressedSize = compressedSize; + if ((sumsValid || size) || m_Method == wxZIP_METHOD_STORE) + m_Size = size; + + SetName(ReadString(stream, nameLen, conv), wxPATH_UNIX); + + if (extraLen || GetLocalExtraLen()) { + Unique(m_LocalExtra, extraLen); + if (extraLen) + stream.Read(m_LocalExtra->GetData(), extraLen); + } + + return LOCAL_SIZE + nameLen + extraLen; +} + +size_t wxZipEntry::WriteLocal(wxOutputStream& stream, wxMBConv& conv) const +{ + wxString unixName = GetName(wxPATH_UNIX); + const wxWX2MBbuf name_buf = conv.cWX2MB(unixName); + const char *name = name_buf; + if (!name) name = ""; + wxUint16 nameLen = strlen(name); + + wxDataOutputStream ds(stream); + + ds << m_VersionNeeded << m_Flags << m_Method; + ds.Write32(GetDateTime().GetAsDOS()); + + ds.Write32(m_Crc); + ds.Write32(m_CompressedSize != wxInvalidOffset ? m_CompressedSize : 0); + ds.Write32(m_Size != wxInvalidOffset ? m_Size : 0); + + ds << nameLen; + wxUint16 extraLen = GetLocalExtraLen(); + ds.Write16(extraLen); + + stream.Write(name, nameLen); + if (extraLen) + stream.Write(m_LocalExtra->GetData(), extraLen); + + return LOCAL_SIZE + nameLen + extraLen; +} + +size_t wxZipEntry::ReadCentral(wxInputStream& stream, wxMBConv& conv) +{ + wxUint16 nameLen, extraLen, commentLen; + + wxDataInputStream ds(stream); + + ds >> m_VersionMadeBy >> m_SystemMadeBy; + + SetVersionNeeded(ds.Read16()); + SetFlags(ds.Read16()); + SetMethod(ds.Read16()); + SetDateTime(wxDateTime().SetFromDOS(ds.Read32())); + SetCrc(ds.Read32()); + SetCompressedSize(ds.Read32()); + SetSize(ds.Read32()); + + ds >> nameLen >> extraLen >> commentLen + >> m_DiskStart >> m_InternalAttributes >> m_ExternalAttributes; + SetOffset(ds.Read32()); + + SetName(ReadString(stream, nameLen, conv), wxPATH_UNIX); + + if (extraLen || GetExtraLen()) { + Unique(m_Extra, extraLen); + if (extraLen) + stream.Read(m_Extra->GetData(), extraLen); + } + + if (commentLen) + m_Comment = ReadString(stream, commentLen, conv); + else + m_Comment.clear(); + + return CENTRAL_SIZE + nameLen + extraLen + commentLen; +} + +size_t wxZipEntry::WriteCentral(wxOutputStream& stream, wxMBConv& conv) const +{ + wxString unixName = GetName(wxPATH_UNIX); + const wxWX2MBbuf name_buf = conv.cWX2MB(unixName); + const char *name = name_buf; + if (!name) name = ""; + wxUint16 nameLen = strlen(name); + + const wxWX2MBbuf comment_buf = conv.cWX2MB(m_Comment); + const char *comment = comment_buf; + if (!comment) comment = ""; + wxUint16 commentLen = strlen(comment); + + wxUint16 extraLen = GetExtraLen(); + + wxDataOutputStream ds(stream); + + ds << CENTRAL_MAGIC << m_VersionMadeBy << m_SystemMadeBy; + + ds.Write16(GetVersionNeeded()); + ds.Write16(GetFlags()); + ds.Write16(GetMethod()); + ds.Write32(GetDateTime().GetAsDOS()); + ds.Write32(GetCrc()); + ds.Write32(GetCompressedSize()); + ds.Write32(GetSize()); + ds.Write16(nameLen); + ds.Write16(extraLen); + + ds << commentLen << m_DiskStart << m_InternalAttributes + << m_ExternalAttributes << (wxUint32)GetOffset(); + + stream.Write(name, nameLen); + if (extraLen) + stream.Write(GetExtra(), extraLen); + stream.Write(comment, commentLen); + + return CENTRAL_SIZE + nameLen + extraLen + commentLen; +} + +// Info-zip prefixes this record with a signature, but pkzip doesn't. So if +// the 1st value is the signature then it is probably an info-zip record, +// though there is a small chance that it is in fact a pkzip record which +// happens to have the signature as it's CRC. +// +size_t wxZipEntry::ReadDescriptor(wxInputStream& stream) +{ + wxDataInputStream ds(stream); + + m_Crc = ds.Read32(); + m_CompressedSize = ds.Read32(); + m_Size = ds.Read32(); + + // if 1st value is the signature then this is probably an info-zip record + if (m_Crc == SUMS_MAGIC) + { + char buf[8]; + stream.Read(buf, sizeof(buf)); + wxUint32 u1 = CrackUint32(buf); + wxUint32 u2 = CrackUint32(buf + 4); + + // look for the signature of the following record to decide which + if ((u1 == LOCAL_MAGIC || u1 == CENTRAL_MAGIC) && + (u2 != LOCAL_MAGIC && u2 != CENTRAL_MAGIC)) + { + // it's a pkzip style record after all! + stream.Ungetch(buf, sizeof(buf)); + } + else + { + // it's an info-zip record as expected + stream.Ungetch(buf + 4, sizeof(buf) - 4); + m_Crc = m_CompressedSize; + m_CompressedSize = m_Size; + m_Size = u1; + return SUMS_SIZE + 4; + } + } + + return SUMS_SIZE; +} + +size_t wxZipEntry::WriteDescriptor(wxOutputStream& stream, wxUint32 crc, + wxFileOffset compressedSize, wxFileOffset size) +{ + m_Crc = crc; + m_CompressedSize = compressedSize; + m_Size = size; + + wxDataOutputStream ds(stream); + + ds.Write32(crc); + ds.Write32(compressedSize); + ds.Write32(size); + + return SUMS_SIZE; +} + + +///////////////////////////////////////////////////////////////////////////// +// wxZipEndRec - holds the end of central directory record + +class wxZipEndRec +{ +public: + wxZipEndRec(); + + int GetDiskNumber() const { return m_DiskNumber; } + int GetStartDisk() const { return m_StartDisk; } + int GetEntriesHere() const { return m_EntriesHere; } + int GetTotalEntries() const { return m_TotalEntries; } + wxFileOffset GetSize() const { return m_Size; } + wxFileOffset GetOffset() const { return m_Offset; } + wxString GetComment() const { return m_Comment; } + + void SetDiskNumber(int num) { m_DiskNumber = num; } + void SetStartDisk(int num) { m_StartDisk = num; } + void SetEntriesHere(int num) { m_EntriesHere = num; } + void SetTotalEntries(int num) { m_TotalEntries = num; } + void SetSize(wxFileOffset size) { m_Size = (wxUint32)size; } + void SetOffset(wxFileOffset offset) { m_Offset = (wxUint32)offset; } + void SetComment(const wxString& comment) { m_Comment = comment; } + + bool Read(wxInputStream& stream, wxMBConv& conv); + bool Write(wxOutputStream& stream, wxMBConv& conv) const; + +private: + wxUint16 m_DiskNumber; + wxUint16 m_StartDisk; + wxUint16 m_EntriesHere; + wxUint16 m_TotalEntries; + wxUint32 m_Size; + wxUint32 m_Offset; + wxString m_Comment; +}; + +wxZipEndRec::wxZipEndRec() + : m_DiskNumber(0), + m_StartDisk(0), + m_EntriesHere(0), + m_TotalEntries(0), + m_Size(0), + m_Offset(0) +{ +} + +bool wxZipEndRec::Write(wxOutputStream& stream, wxMBConv& conv) const +{ + const wxWX2MBbuf comment_buf = conv.cWX2MB(m_Comment); + const char *comment = comment_buf; + if (!comment) comment = ""; + wxUint16 commentLen = strlen(comment); + + wxDataOutputStream ds(stream); + + ds << END_MAGIC << m_DiskNumber << m_StartDisk << m_EntriesHere + << m_TotalEntries << m_Size << m_Offset << commentLen; + + stream.Write(comment, commentLen); + + return stream.IsOk(); +} + +bool wxZipEndRec::Read(wxInputStream& stream, wxMBConv& conv) +{ + wxDataInputStream ds(stream); + wxUint16 commentLen; + + ds >> m_DiskNumber >> m_StartDisk >> m_EntriesHere + >> m_TotalEntries >> m_Size >> m_Offset >> commentLen; + + if (commentLen) + m_Comment = ReadString(stream, commentLen, conv); + + if (stream.IsOk()) + if (m_DiskNumber == 0 && m_StartDisk == 0 && + m_EntriesHere == m_TotalEntries) + return true; + else + wxLogError(_("unsupported zip archive")); + + return false; +} + + +///////////////////////////////////////////////////////////////////////////// +// A weak link from an input stream to an output stream + +class wxZipStreamLink +{ +public: + wxZipStreamLink(wxZipOutputStream *stream) : m_ref(1), m_stream(stream) { } + + wxZipStreamLink *AddRef() { m_ref++; return this; } + wxZipOutputStream *GetOutputStream() const { return m_stream; } + + void Release(class wxZipInputStream *WXUNUSED(s)) + { if (--m_ref == 0) delete this; } + void Release(class wxZipOutputStream *WXUNUSED(s)) + { m_stream = NULL; if (--m_ref == 0) delete this; } + +private: + ~wxZipStreamLink() { } + + int m_ref; + wxZipOutputStream *m_stream; +}; + + +///////////////////////////////////////////////////////////////////////////// +// Input stream + +wxDECLARE_SCOPED_PTR(wxZipEntry, _wxZipEntryPtr) +wxDEFINE_SCOPED_PTR (wxZipEntry, _wxZipEntryPtr) + +// constructor +// +wxZipInputStream::wxZipInputStream(wxInputStream& stream, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveInputStream(stream, conv) +{ + m_ffile = NULL; + Init(); +} + +// Compatibility constructor +// +wxZipInputStream::wxZipInputStream(const wxString& archive, + const wxString& file) + : wxArchiveInputStream(OpenFile(archive), wxConvLocal) +{ + // no error messages + wxLogNull nolog; + Init(); + _wxZipEntryPtr entry; + + if (m_ffile->Ok()) { + do { + entry.reset(GetNextEntry()); + } + while (entry.get() != NULL && entry->GetInternalName() != file); + } + + if (entry.get() == NULL) + m_lasterror = wxSTREAM_READ_ERROR; +} + +wxInputStream& wxZipInputStream::OpenFile(const wxString& archive) +{ + wxLogNull nolog; + m_ffile = new wxFFileInputStream(archive); + return *m_ffile; +} + +void wxZipInputStream::Init() +{ + m_store = new wxStoredInputStream(*m_parent_i_stream); + m_inflate = NULL; + m_rawin = NULL; + m_raw = false; + m_headerSize = 0; + m_decomp = NULL; + m_parentSeekable = false; + m_weaklinks = new wxZipWeakLinks; + m_streamlink = NULL; + m_offsetAdjustment = 0; + m_position = wxInvalidOffset; + m_signature = 0; + m_TotalEntries = 0; + m_lasterror = m_parent_i_stream->GetLastError(); +} + +wxZipInputStream::~wxZipInputStream() +{ + CloseDecompressor(m_decomp); + + delete m_store; + delete m_inflate; + delete m_rawin; + delete m_ffile; + + m_weaklinks->Release(this); + + if (m_streamlink) + m_streamlink->Release(this); +} + +wxString wxZipInputStream::GetComment() +{ + if (m_position == wxInvalidOffset) + if (!LoadEndRecord()) + return wxEmptyString; + + if (!m_parentSeekable && Eof() && m_signature) { + m_lasterror = wxSTREAM_NO_ERROR; + m_lasterror = ReadLocal(true); + } + + return m_Comment; +} + +int wxZipInputStream::GetTotalEntries() +{ + if (m_position == wxInvalidOffset) + LoadEndRecord(); + return m_TotalEntries; +} + +wxZipStreamLink *wxZipInputStream::MakeLink(wxZipOutputStream *out) +{ + wxZipStreamLink *link = NULL; + + if (!m_parentSeekable && (IsOpened() || !Eof())) { + link = new wxZipStreamLink(out); + if (m_streamlink) + m_streamlink->Release(this); + m_streamlink = link->AddRef(); + } + + return link; +} -#include "wx/log.h" -#include "wx/intl.h" -#include "wx/stream.h" -#include "wx/wfstream.h" -#include "wx/zipstrm.h" -#include "wx/utils.h" +bool wxZipInputStream::LoadEndRecord() +{ + wxCHECK(m_position == wxInvalidOffset, false); + if (!IsOk()) + return false; -/* Not the right solution (paths in makefiles) but... */ -#ifdef __BORLANDC__ -#include "../common/unzip.h" -#else -#include "unzip.h" -#endif + m_position = 0; + + // First find the end-of-central-directory record. + if (!FindEndRecord()) { + // failed, so either this is a non-seekable stream (ok), or not a zip + if (m_parentSeekable) { + m_lasterror = wxSTREAM_READ_ERROR; + wxLogError(_("invalid zip file")); + return false; + } + else { + wxLogNull nolog; + wxFileOffset pos = m_parent_i_stream->TellI(); + // FIXME + //if (pos != wxInvalidOffset) + if (pos >= 0 && pos <= LONG_MAX) + m_offsetAdjustment = m_position = pos; + return true; + } + } + + wxZipEndRec endrec; + + // Read in the end record + wxFileOffset endPos = m_parent_i_stream->TellI() - 4; + if (!endrec.Read(*m_parent_i_stream, GetConv())) { + if (!*m_parent_i_stream) { + m_lasterror = wxSTREAM_READ_ERROR; + return false; + } + // TODO: try this out + wxLogWarning(_("assuming this is a multi-part zip concatenated")); + } + + m_TotalEntries = endrec.GetTotalEntries(); + m_Comment = endrec.GetComment(); + // Now find the central-directory. we have the file offset of + // the CD, so look there first. + if (m_parent_i_stream->SeekI(endrec.GetOffset()) != wxInvalidOffset && + ReadSignature() == CENTRAL_MAGIC) { + m_signature = CENTRAL_MAGIC; + m_position = endrec.GetOffset(); + m_offsetAdjustment = 0; + return true; + } + + // If it's not there, then it could be that the zip has been appended + // to a self extractor, so take the CD size (also in endrec), subtract + // it from the file offset of the end-central-directory and look there. + if (m_parent_i_stream->SeekI(endPos - endrec.GetSize()) + != wxInvalidOffset && ReadSignature() == CENTRAL_MAGIC) { + m_signature = CENTRAL_MAGIC; + m_position = endPos - endrec.GetSize(); + m_offsetAdjustment = m_position - endrec.GetOffset(); + return true; + } + + wxLogError(_("can't find central directory in zip")); + m_lasterror = wxSTREAM_READ_ERROR; + return false; +} -wxZipInputStream::wxZipInputStream(const wxString& archive, const wxString& file) : wxInputStream() +// Find the end-of-central-directory record. +// If found the stream will be positioned just past the 4 signature bytes. +// +bool wxZipInputStream::FindEndRecord() { - unz_file_info zinfo; + // usually it's 22 bytes in size and the last thing in the file + { + wxLogNull nolog; + if (m_parent_i_stream->SeekI(-END_SIZE, wxFromEnd) == wxInvalidOffset) + return false; + } - m_Pos = 0; - m_Size = 0; - m_Archive = (void*) unzOpen(archive.mb_str(wxConvFile)); - if (m_Archive == NULL) - { - m_lasterror = wxSTREAM_READ_ERROR; - return; + m_parentSeekable = true; + m_signature = 0; + char magic[4]; + if (m_parent_i_stream->Read(magic, 4).LastRead() != 4) + return false; + if ((m_signature = CrackUint32(magic)) == END_MAGIC) + return true; + + // unfortunately, the record has a comment field that can be up to 65535 + // bytes in length, so if the signature not found then search backwards. + wxFileOffset pos = m_parent_i_stream->TellI(); + const int BUFSIZE = 1024; + wxCharBuffer buf(BUFSIZE); + + memcpy(buf.data(), magic, 3); + wxFileOffset minpos = wxMax(pos - 65535L, 0); + + while (pos > minpos) { + size_t len = pos - wxMax(pos - (BUFSIZE - 3), minpos); + memcpy(buf.data() + len, buf, 3); + pos -= len; + + if (m_parent_i_stream->SeekI(pos, wxFromStart) == wxInvalidOffset || + m_parent_i_stream->Read(buf.data(), len).LastRead() != len) + return false; + + char *p = buf.data() + len; + + while (p-- > buf.data()) { + if ((m_signature = CrackUint32(p)) == END_MAGIC) { + size_t remainder = buf.data() + len - p; + if (remainder > 4) + m_parent_i_stream->Ungetch(p + 4, remainder - 4); + return true; + } + } } - // TODO what encoding does ZIP use? - if (unzLocateFile((unzFile)m_Archive, file.ToAscii(), 0) != UNZ_OK) - { - m_lasterror = wxSTREAM_READ_ERROR; - return; + + return false; +} + +wxZipEntry *wxZipInputStream::GetNextEntry() +{ + if (m_position == wxInvalidOffset) + if (!LoadEndRecord()) + return NULL; + + m_lasterror = m_parentSeekable ? ReadCentral() : ReadLocal(); + if (!IsOk()) + return NULL; + + _wxZipEntryPtr entry(new wxZipEntry(m_entry)); + entry->m_backlink = m_weaklinks->AddEntry(entry.get(), entry->GetKey()); + return entry.release(); +} + +wxStreamError wxZipInputStream::ReadCentral() +{ + if (!AtHeader()) + CloseEntry(); + + if (m_signature == END_MAGIC) + return wxSTREAM_EOF; + + if (m_signature != CENTRAL_MAGIC) { + wxLogError(_("error reading zip central directory")); + return wxSTREAM_READ_ERROR; } - unzGetCurrentFileInfo((unzFile)m_Archive, &zinfo, (char*) NULL, 0, (void*) NULL, 0, (char*) NULL, 0); + if (QuietSeek(*m_parent_i_stream, m_position + 4) == wxInvalidOffset) + return wxSTREAM_READ_ERROR; - if (unzOpenCurrentFile((unzFile)m_Archive) != UNZ_OK) - { - m_lasterror = wxSTREAM_READ_ERROR; - return; + m_position += m_entry.ReadCentral(*m_parent_i_stream, GetConv()); + if (m_parent_i_stream->GetLastError() == wxSTREAM_READ_ERROR) { + m_signature = 0; + return wxSTREAM_READ_ERROR; } - m_Size = zinfo.uncompressed_size; + + m_signature = ReadSignature(); + + if (m_offsetAdjustment) + m_entry.SetOffset(m_entry.GetOffset() + m_offsetAdjustment); + m_entry.SetKey(m_entry.GetOffset()); + + return wxSTREAM_NO_ERROR; } +wxStreamError wxZipInputStream::ReadLocal(bool readEndRec /*=false*/) +{ + if (!AtHeader()) + CloseEntry(); + + if (!m_signature) + m_signature = ReadSignature(); + if (m_signature == CENTRAL_MAGIC || m_signature == END_MAGIC) { + if (m_streamlink && !m_streamlink->GetOutputStream()) { + m_streamlink->Release(this); + m_streamlink = NULL; + } + } -wxZipInputStream::~wxZipInputStream() + while (m_signature == CENTRAL_MAGIC) { + if (m_weaklinks->IsEmpty() && m_streamlink == NULL) + return wxSTREAM_EOF; + + m_position += m_entry.ReadCentral(*m_parent_i_stream, GetConv()); + m_signature = 0; + if (m_parent_i_stream->GetLastError() == wxSTREAM_READ_ERROR) + return wxSTREAM_READ_ERROR; + + wxZipEntry *entry = m_weaklinks->GetEntry(m_entry.GetOffset()); + if (entry) { + entry->SetSystemMadeBy(m_entry.GetSystemMadeBy()); + entry->SetVersionMadeBy(m_entry.GetVersionMadeBy()); + entry->SetComment(m_entry.GetComment()); + entry->SetDiskStart(m_entry.GetDiskStart()); + entry->SetInternalAttributes(m_entry.GetInternalAttributes()); + entry->SetExternalAttributes(m_entry.GetExternalAttributes()); + Copy(entry->m_Extra, m_entry.m_Extra); + entry->Notify(); + m_weaklinks->RemoveEntry(entry->GetOffset()); + } + + m_signature = ReadSignature(); + } + + if (m_signature == END_MAGIC) { + if (readEndRec || m_streamlink) { + wxZipEndRec endrec; + endrec.Read(*m_parent_i_stream, GetConv()); + m_Comment = endrec.GetComment(); + m_signature = 0; + if (m_streamlink) { + m_streamlink->GetOutputStream()->SetComment(endrec.GetComment()); + m_streamlink->Release(this); + m_streamlink = NULL; + } + } + return wxSTREAM_EOF; + } + + if (m_signature != LOCAL_MAGIC) { + wxLogError(_("error reading zip local header")); + return wxSTREAM_READ_ERROR; + } + + m_headerSize = m_entry.ReadLocal(*m_parent_i_stream, GetConv()); + m_signature = 0; + m_entry.SetOffset(m_position); + m_entry.SetKey(m_position); + + if (m_parent_i_stream->GetLastError() == wxSTREAM_READ_ERROR) { + return wxSTREAM_READ_ERROR; + } else { + m_TotalEntries++; + return wxSTREAM_NO_ERROR; + } +} + +wxUint32 wxZipInputStream::ReadSignature() { - if (m_Archive) - { - if (m_Size != 0) - unzCloseCurrentFile((unzFile)m_Archive); - unzClose((unzFile)m_Archive); + char magic[4]; + m_parent_i_stream->Read(magic, 4); + return m_parent_i_stream->LastRead() == 4 ? CrackUint32(magic) : 0; +} + +bool wxZipInputStream::OpenEntry(wxArchiveEntry& entry) +{ + wxZipEntry *zipEntry = wxStaticCast(&entry, wxZipEntry); + return zipEntry ? OpenEntry(*zipEntry) : false; +} + +// Open an entry +// +bool wxZipInputStream::DoOpen(wxZipEntry *entry, bool raw) +{ + if (m_position == wxInvalidOffset) + if (!LoadEndRecord()) + return false; + if (m_lasterror == wxSTREAM_READ_ERROR) + return false; + wxCHECK(!IsOpened(), false); + + m_raw = raw; + + if (entry) { + if (AfterHeader() && entry->GetKey() == m_entry.GetOffset()) + return true; + // can only open the current entry on a non-seekable stream + wxCHECK(m_parentSeekable, false); + } + + m_lasterror = wxSTREAM_READ_ERROR; + + if (entry) + m_entry = *entry; + + if (m_parentSeekable) { + if (QuietSeek(*m_parent_i_stream, m_entry.GetOffset()) + == wxInvalidOffset) + return false; + if (ReadSignature() != LOCAL_MAGIC) { + wxLogError(_("bad zipfile offset to entry")); + return false; + } + } + + if (m_parentSeekable || AtHeader()) { + m_headerSize = m_entry.ReadLocal(*m_parent_i_stream, GetConv()); + if (m_parentSeekable) { + wxZipEntry *ref = m_weaklinks->GetEntry(m_entry.GetKey()); + if (ref) { + Copy(ref->m_LocalExtra, m_entry.m_LocalExtra); + ref->Notify(); + m_weaklinks->RemoveEntry(ref->GetKey()); + } + if (entry && entry != ref) { + Copy(entry->m_LocalExtra, m_entry.m_LocalExtra); + entry->Notify(); + } + } + } + + m_lasterror = m_parent_i_stream->GetLastError(); + return IsOk(); +} + +bool wxZipInputStream::OpenDecompressor(bool raw /*=false*/) +{ + wxASSERT(AfterHeader()); + + wxFileOffset compressedSize = m_entry.GetCompressedSize(); + + if (raw) + m_raw = true; + + if (m_raw) { + if (compressedSize != wxInvalidOffset) { + m_store->Open(compressedSize); + m_decomp = m_store; + } else { + if (!m_rawin) + m_rawin = new wxRawInputStream(*m_parent_i_stream); + m_decomp = m_rawin->Open(OpenDecompressor(m_rawin->GetTee())); + } + } else { + if (compressedSize != wxInvalidOffset && + (m_entry.GetMethod() != wxZIP_METHOD_DEFLATE || + wxZlibInputStream::CanHandleGZip())) { + m_store->Open(compressedSize); + m_decomp = OpenDecompressor(*m_store); + } else { + m_decomp = OpenDecompressor(*m_parent_i_stream); + } } + + m_crcAccumulator = crc32(0, Z_NULL, 0); + m_lasterror = m_decomp ? m_decomp->GetLastError() : wxSTREAM_READ_ERROR; + return IsOk(); } -bool wxZipInputStream::Eof() const +// Can be overriden to add support for additional decompression methods +// +wxInputStream *wxZipInputStream::OpenDecompressor(wxInputStream& stream) { - wxASSERT_MSG( m_Pos <= m_Size, - _T("wxZipInputStream: invalid current position") ); + switch (m_entry.GetMethod()) { + case wxZIP_METHOD_STORE: + if (m_entry.GetSize() == wxInvalidOffset) { + wxLogError(_("stored file length not in Zip header")); + break; + } + m_store->Open(m_entry.GetSize()); + return m_store; + + case wxZIP_METHOD_DEFLATE: + if (!m_inflate) + m_inflate = new wxZlibInputStream2(stream); + else + m_inflate->Open(stream); + return m_inflate; - return m_Pos >= m_Size; + default: + wxLogError(_("unsupported Zip compression method")); + } + + return NULL; } +bool wxZipInputStream::CloseDecompressor(wxInputStream *decomp) +{ + if (decomp && decomp == m_rawin) + return CloseDecompressor(m_rawin->GetFilterInputStream()); + if (decomp != m_store && decomp != m_inflate) + delete decomp; + return true; +} -size_t wxZipInputStream::OnSysRead(void *buffer, size_t bufsize) +// Closes the current entry and positions the underlying stream at the start +// of the next entry +// +bool wxZipInputStream::CloseEntry() { - wxASSERT_MSG( m_Pos <= m_Size, - _T("wxZipInputStream: invalid current position") ); + if (AtHeader()) + return true; + if (m_lasterror == wxSTREAM_READ_ERROR) + return false; - if ( m_Pos >= m_Size ) - { - m_lasterror = wxSTREAM_EOF; - return 0; + if (!m_parentSeekable) { + if (!IsOpened() && !OpenDecompressor(true)) + return false; + + const int BUFSIZE = 8192; + wxCharBuffer buf(BUFSIZE); + while (IsOk()) + Read(buf.data(), BUFSIZE); + + m_position += m_headerSize + m_entry.GetCompressedSize(); } - if (m_Pos + bufsize > m_Size + (size_t)0) - bufsize = m_Size - m_Pos; + if (m_lasterror == wxSTREAM_EOF) + m_lasterror = wxSTREAM_NO_ERROR; - unzReadCurrentFile((unzFile)m_Archive, buffer, bufsize); - m_Pos += bufsize; + CloseDecompressor(m_decomp); + m_decomp = NULL; + m_entry = wxZipEntry(); + m_headerSize = 0; + m_raw = false; - return bufsize; + return IsOk(); } +size_t wxZipInputStream::OnSysRead(void *buffer, size_t size) +{ + if (!IsOpened()) + if ((AtHeader() && !DoOpen()) || !OpenDecompressor()) + m_lasterror = wxSTREAM_READ_ERROR; + if (!IsOk() || !size) + return 0; + + size_t count = m_decomp->Read(buffer, size).LastRead(); + if (!m_raw) + m_crcAccumulator = crc32(m_crcAccumulator, (Byte*)buffer, count); + m_lasterror = m_decomp->GetLastError(); + + if (Eof()) { + if ((m_entry.GetFlags() & wxZIP_SUMS_FOLLOW) != 0) { + m_headerSize += m_entry.ReadDescriptor(*m_parent_i_stream); + wxZipEntry *entry = m_weaklinks->GetEntry(m_entry.GetKey()); + + if (entry) { + entry->SetCrc(m_entry.GetCrc()); + entry->SetCompressedSize(m_entry.GetCompressedSize()); + entry->SetSize(m_entry.GetSize()); + entry->Notify(); + } + } + + if (!m_raw) { + m_lasterror = wxSTREAM_READ_ERROR; + + if (m_parent_i_stream->IsOk()) { + if (m_entry.GetSize() != TellI()) + wxLogError(_("reading zip stream (entry %s): bad length"), + m_entry.GetName().c_str()); + else if (m_crcAccumulator != m_entry.GetCrc()) + wxLogError(_("reading zip stream (entry %s): bad crc"), + m_entry.GetName().c_str()); + else + m_lasterror = wxSTREAM_EOF; + } + } + } + return count; +} +// Borrowed from VS's zip stream (c) 1999 Vaclav Slavik +// wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode) { + if (!m_ffile || AtHeader()) + return wxInvalidOffset; + // NB: since ZIP files don't natively support seeking, we have to // implement a brute force workaround -- reading all the data // between current and the new position (or between beginning of // the file and new position...) wxFileOffset nextpos; + wxFileOffset pos = TellI(); switch ( mode ) { - case wxFromCurrent : nextpos = seek + m_Pos; break; + case wxFromCurrent : nextpos = seek + pos; break; case wxFromStart : nextpos = seek; break; - case wxFromEnd : nextpos = m_Size - 1 + seek; break; - default : nextpos = m_Pos; break; /* just to fool compiler, never happens */ + case wxFromEnd : nextpos = GetSize() - 1 + seek; break; + default : nextpos = pos; break; /* just to fool compiler, never happens */ } size_t toskip; - if ( nextpos > m_Pos ) + if ( nextpos >= pos ) { - toskip = nextpos - m_Pos; + toskip = nextpos - pos; } else { - unzCloseCurrentFile((unzFile)m_Archive); - if (unzOpenCurrentFile((unzFile)m_Archive) != UNZ_OK) + wxZipEntry current(m_entry); + CloseEntry(); + if (!OpenEntry(current)) { m_lasterror = wxSTREAM_READ_ERROR; - return m_Pos; + return pos; } toskip = nextpos; } @@ -147,14 +1780,464 @@ wxFileOffset wxZipInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode) while ( toskip > 0 ) { sz = wxMin(toskip, BUFSIZE); - unzReadCurrentFile((unzFile)m_Archive, buffer, sz); + Read(buffer, sz); toskip -= sz; } } - m_Pos = nextpos; - return m_Pos; + pos = nextpos; + return pos; } -#endif - // wxUSE_STREAMS && wxUSE_ZIPSTREAM && wxUSE_ZLIB + +///////////////////////////////////////////////////////////////////////////// +// Output stream + +#include +WX_DEFINE_LIST(_wxZipEntryList); + +wxZipOutputStream::wxZipOutputStream(wxOutputStream& stream, + int level /*=-1*/, + wxMBConv& conv /*=wxConvLocal*/) + : wxArchiveOutputStream(stream, conv), + m_store(new wxStoredOutputStream(stream)), + m_deflate(NULL), + m_backlink(NULL), + m_initialData(new char[OUTPUT_LATENCY]), + m_initialSize(0), + m_pending(NULL), + m_raw(false), + m_headerOffset(0), + m_headerSize(0), + m_entrySize(0), + m_comp(NULL), + m_level(level), + m_offsetAdjustment(wxInvalidOffset) +{ +} + +wxZipOutputStream::~wxZipOutputStream() +{ + Close(); + WX_CLEAR_LIST(_wxZipEntryList, m_entries); + delete m_store; + delete m_deflate; + delete m_pending; + delete [] m_initialData; + if (m_backlink) + m_backlink->Release(this); +} + +bool wxZipOutputStream::PutNextEntry( + const wxString& name, + const wxDateTime& dt /*=wxDateTime::Now()*/, + wxFileOffset size /*=wxInvalidOffset*/) +{ + return PutNextEntry(new wxZipEntry(name, dt, size)); +} + +bool wxZipOutputStream::PutNextDirEntry( + const wxString& name, + const wxDateTime& dt /*=wxDateTime::Now()*/) +{ + wxZipEntry *entry = new wxZipEntry(name, dt); + entry->SetIsDir(); + return PutNextEntry(entry); +} + +bool wxZipOutputStream::CopyEntry(wxZipEntry *entry, + wxZipInputStream& inputStream) +{ + _wxZipEntryPtr e(entry); + + return + inputStream.DoOpen(e.get(), true) && + DoCreate(e.release(), true) && + Write(inputStream).IsOk() && inputStream.Eof(); +} + +bool wxZipOutputStream::PutNextEntry(wxArchiveEntry *entry) +{ + wxZipEntry *zipEntry = wxStaticCast(entry, wxZipEntry); + if (!zipEntry) + delete entry; + return PutNextEntry(zipEntry); +} + +bool wxZipOutputStream::CopyEntry(wxArchiveEntry *entry, + wxArchiveInputStream& stream) +{ + wxZipEntry *zipEntry = wxStaticCast(entry, wxZipEntry); + + if (!zipEntry || !stream.OpenEntry(*zipEntry)) { + delete entry; + return false; + } + + return CopyEntry(zipEntry, wx_static_cast(wxZipInputStream&, stream)); +} + +bool wxZipOutputStream::CopyArchiveMetaData(wxZipInputStream& inputStream) +{ + m_Comment = inputStream.GetComment(); + if (m_backlink) + m_backlink->Release(this); + m_backlink = inputStream.MakeLink(this); + return true; +} + +bool wxZipOutputStream::CopyArchiveMetaData(wxArchiveInputStream& stream) +{ + return CopyArchiveMetaData(wx_static_cast(wxZipInputStream&, stream)); +} + +void wxZipOutputStream::SetLevel(int level) +{ + if (level != m_level) { + if (m_comp != m_deflate) + delete m_deflate; + m_deflate = NULL; + m_level = level; + } +} + +bool wxZipOutputStream::DoCreate(wxZipEntry *entry, bool raw /*=false*/) +{ + CloseEntry(); + + m_pending = entry; + if (!m_pending) + return false; + + // write the signature bytes right away + wxDataOutputStream ds(*m_parent_o_stream); + ds << LOCAL_MAGIC; + + // and if this is the first entry test for seekability + if (m_headerOffset == 0) { + bool logging = wxLog::IsEnabled(); + wxLogNull nolog; + wxFileOffset here = m_parent_o_stream->TellO(); + + if (here != wxInvalidOffset && here >= 4) { + if (m_parent_o_stream->SeekO(here - 4) == here - 4) { + m_offsetAdjustment = here - 4; + wxLog::EnableLogging(logging); + m_parent_o_stream->SeekO(here); + } + } + } + + m_pending->SetOffset(m_headerOffset); + + m_crcAccumulator = crc32(0, Z_NULL, 0); + + if (raw) + m_raw = true; + + m_lasterror = wxSTREAM_NO_ERROR; + return true; +} + +// Can be overriden to add support for additional compression methods +// +wxOutputStream *wxZipOutputStream::OpenCompressor( + wxOutputStream& stream, + wxZipEntry& entry, + const Buffer bufs[]) +{ + if (entry.GetMethod() == wxZIP_METHOD_DEFAULT) { + if (GetLevel() == 0 + && (IsParentSeekable() + || entry.GetCompressedSize() != wxInvalidOffset + || entry.GetSize() != wxInvalidOffset)) { + entry.SetMethod(wxZIP_METHOD_STORE); + } else { + int size = 0; + for (int i = 0; bufs[i].m_data; ++i) + size += bufs[i].m_size; + entry.SetMethod(size <= 6 ? + wxZIP_METHOD_STORE : wxZIP_METHOD_DEFLATE); + } + } + + switch (entry.GetMethod()) { + case wxZIP_METHOD_STORE: + if (entry.GetCompressedSize() == wxInvalidOffset) + entry.SetCompressedSize(entry.GetSize()); + return m_store; + + case wxZIP_METHOD_DEFLATE: + { + int defbits = wxZIP_DEFLATE_NORMAL; + switch (GetLevel()) { + case 0: case 1: + defbits = wxZIP_DEFLATE_SUPERFAST; + break; + case 2: case 3: case 4: + defbits = wxZIP_DEFLATE_FAST; + break; + case 8: case 9: + defbits = wxZIP_DEFLATE_EXTRA; + break; + } + entry.SetFlags((entry.GetFlags() & ~wxZIP_DEFLATE_MASK) | + defbits | wxZIP_SUMS_FOLLOW); + + if (!m_deflate) + m_deflate = new wxZlibOutputStream2(stream, GetLevel()); + else + m_deflate->Open(stream); + + return m_deflate; + } + + default: + wxLogError(_("unsupported Zip compression method")); + } + + return NULL; +} + +bool wxZipOutputStream::CloseCompressor(wxOutputStream *comp) +{ + if (comp == m_deflate) + m_deflate->Close(); + else if (comp != m_store) + delete comp; + return true; +} + +// This is called when OUPUT_LATENCY bytes has been written to the +// wxZipOutputStream to actually create the zip entry. +// +void wxZipOutputStream::CreatePendingEntry(const void *buffer, size_t size) +{ + wxASSERT(IsOk() && m_pending && !m_comp); + _wxZipEntryPtr spPending(m_pending); + m_pending = NULL; + + Buffer bufs[] = { + { m_initialData, m_initialSize }, + { (const char*)buffer, size }, + { NULL, 0 } + }; + + if (m_raw) + m_comp = m_store; + else + m_comp = OpenCompressor(*m_store, *spPending, + m_initialSize ? bufs : bufs + 1); + + if (IsParentSeekable() + || (spPending->m_Crc + && spPending->m_CompressedSize != wxInvalidOffset + && spPending->m_Size != wxInvalidOffset)) + spPending->m_Flags &= ~wxZIP_SUMS_FOLLOW; + else + if (spPending->m_CompressedSize != wxInvalidOffset) + spPending->m_Flags |= wxZIP_SUMS_FOLLOW; + + m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv()); + m_lasterror = m_parent_o_stream->GetLastError(); + + if (IsOk()) { + m_entries.push_back(spPending.release()); + OnSysWrite(m_initialData, m_initialSize); + } + + m_initialSize = 0; +} + +// This is called to write out the zip entry when Close has been called +// before OUTPUT_LATENCY bytes has been written to the wxZipOutputStream. +// +void wxZipOutputStream::CreatePendingEntry() +{ + wxASSERT(IsOk() && m_pending && !m_comp); + _wxZipEntryPtr spPending(m_pending); + m_pending = NULL; + m_lasterror = wxSTREAM_WRITE_ERROR; + + if (!m_raw) { + // Initially compresses the data to memory, then fall back to 'store' + // if the compressor makes the data larger rather than smaller. + wxMemoryOutputStream mem; + Buffer bufs[] = { { m_initialData, m_initialSize }, { NULL, 0 } }; + wxOutputStream *comp = OpenCompressor(mem, *spPending, bufs); + + if (!comp) + return; + if (comp != m_store) { + bool ok = comp->Write(m_initialData, m_initialSize).IsOk(); + CloseCompressor(comp); + if (!ok) + return; + } + + m_entrySize = m_initialSize; + m_crcAccumulator = crc32(0, (Byte*)m_initialData, m_initialSize); + + if (mem.GetSize() > 0 && mem.GetSize() < m_initialSize) { + m_initialSize = mem.GetSize(); + mem.CopyTo(m_initialData, m_initialSize); + } else { + spPending->SetMethod(wxZIP_METHOD_STORE); + } + + spPending->SetSize(m_entrySize); + spPending->SetCrc(m_crcAccumulator); + spPending->SetCompressedSize(m_initialSize); + } + + spPending->m_Flags &= ~wxZIP_SUMS_FOLLOW; + m_headerSize = spPending->WriteLocal(*m_parent_o_stream, GetConv()); + + if (m_parent_o_stream->IsOk()) { + m_entries.push_back(spPending.release()); + m_comp = m_store; + m_store->Write(m_initialData, m_initialSize); + } + + m_initialSize = 0; + m_lasterror = m_parent_o_stream->GetLastError(); +} + +// Write the 'central directory' and the 'end-central-directory' records. +// +bool wxZipOutputStream::Close() +{ + CloseEntry(); + + if (m_lasterror == wxSTREAM_WRITE_ERROR || m_entries.size() == 0) + return false; + + wxZipEndRec endrec; + + endrec.SetEntriesHere(m_entries.size()); + endrec.SetTotalEntries(m_entries.size()); + endrec.SetOffset(m_headerOffset); + endrec.SetComment(m_Comment); + + _wxZipEntryList::iterator it; + wxFileOffset size = 0; + + for (it = m_entries.begin(); it != m_entries.end(); ++it) { + size += (*it)->WriteCentral(*m_parent_o_stream, GetConv()); + delete *it; + } + m_entries.clear(); + + endrec.SetSize(size); + endrec.Write(*m_parent_o_stream, GetConv()); + + m_lasterror = m_parent_o_stream->GetLastError(); + if (!IsOk()) + return false; + m_lasterror = wxSTREAM_EOF; + return true; +} + +// Finish writing the current entry +// +bool wxZipOutputStream::CloseEntry() +{ + if (IsOk() && m_pending) + CreatePendingEntry(); + if (!IsOk()) + return false; + if (!m_comp) + return true; + + CloseCompressor(m_comp); + m_comp = NULL; + + wxFileOffset compressedSize = m_store->TellO(); + + wxZipEntry& entry = *m_entries.back(); + + // When writing raw the crc and size can't be checked + if (m_raw) { + m_crcAccumulator = entry.GetCrc(); + m_entrySize = entry.GetSize(); + } + + // Write the sums in the trailing 'data descriptor' if necessary + if (entry.m_Flags & wxZIP_SUMS_FOLLOW) { + wxASSERT(!IsParentSeekable()); + m_headerOffset += + entry.WriteDescriptor(*m_parent_o_stream, m_crcAccumulator, + compressedSize, m_entrySize); + m_lasterror = m_parent_o_stream->GetLastError(); + } + + // If the local header didn't have the correct crc and size written to + // it then seek back and fix it + else if (m_crcAccumulator != entry.GetCrc() + || m_entrySize != entry.GetSize() + || compressedSize != entry.GetCompressedSize()) + { + if (IsParentSeekable()) { + wxFileOffset here = m_parent_o_stream->TellO(); + wxFileOffset headerOffset = m_headerOffset + m_offsetAdjustment; + m_parent_o_stream->SeekO(headerOffset + SUMS_OFFSET); + entry.WriteDescriptor(*m_parent_o_stream, m_crcAccumulator, + compressedSize, m_entrySize); + m_parent_o_stream->SeekO(here); + m_lasterror = m_parent_o_stream->GetLastError(); + } else { + m_lasterror = wxSTREAM_WRITE_ERROR; + } + } + + m_headerOffset += m_headerSize + compressedSize; + m_headerSize = m_entrySize = 0; + m_store->Close(); + m_raw = false; + + if (IsOk()) + m_lasterror = m_parent_o_stream->GetLastError(); + else + wxLogError(_("error writing zip entry '%s': bad crc or length"), + entry.GetName().c_str()); + return IsOk(); +} + +void wxZipOutputStream::Sync() +{ + if (IsOk() && m_pending) + CreatePendingEntry(NULL, 0); + if (!m_comp) + m_lasterror = wxSTREAM_WRITE_ERROR; + if (IsOk()) { + m_comp->Sync(); + m_lasterror = m_comp->GetLastError(); + } +} + +size_t wxZipOutputStream::OnSysWrite(const void *buffer, size_t size) +{ + if (IsOk() && m_pending) { + if (m_initialSize + size < OUTPUT_LATENCY) { + memcpy(m_initialData + m_initialSize, buffer, size); + m_initialSize += size; + return size; + } else { + CreatePendingEntry(buffer, size); + } + } + + if (!m_comp) + m_lasterror = wxSTREAM_WRITE_ERROR; + if (!IsOk() || !size) + return 0; + + if (m_comp->Write(buffer, size).LastWrite() != size) + m_lasterror = wxSTREAM_WRITE_ERROR; + m_crcAccumulator = crc32(m_crcAccumulator, (Byte*)buffer, size); + m_entrySize += m_comp->LastWrite(); + + return m_comp->LastWrite(); +} + +#endif // wxUSE_ZLIB && wxUSE_STREAMS && wxUSE_ZIPSTREAM diff --git a/tests/archive/archivetest.cpp b/tests/archive/archivetest.cpp new file mode 100644 index 0000000000..e836c3a16e --- /dev/null +++ b/tests/archive/archivetest.cpp @@ -0,0 +1,1649 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: tests/archive/archive.cpp +// Purpose: Test the archive classes +// Author: Mike Wetherell +// RCS-ID: $Id$ +// Copyright: (c) 2004 Mike Wetherell +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ +# pragma hdrstop +#endif + +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif + +#if wxUSE_STREAMS + +#define WX_TEST_ARCHIVE_ITERATOR + +#include "wx/zipstrm.h" +#include "wx/mstream.h" +#include "wx/wfstream.h" +#include "wx/dir.h" +#include "wx/cppunit.h" +#include +#include +#include + +// Check whether member templates can be used +// +#if defined __GNUC__ && \ + (__GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)) +# define WXARC_MEMBER_TEMPLATES +#endif +#if defined _MSC_VER && _MSC_VER >= 1310 +# define WXARC_MEMBER_TEMPLATES +#endif +#if defined __BORLANDC__ && __BORLANDC__ >= 0x530 +# define WXARC_MEMBER_TEMPLATES +#endif +#if defined __DMC__ && __DMC__ >= 0x832 +# define WXARC_MEMBER_TEMPLATES +#endif +#if defined __MWERKS__ && __MWERKS__ >= 0x2200 +# define WXARC_MEMBER_TEMPLATES +#endif +#if defined __HP_aCC && __HP_aCC > 33300 +# define WXARC_MEMBER_TEMPLATES +#endif +#if defined __SUNPRO_CC && __SUNPRO_CC > 0x500 +# define WXARC_MEMBER_TEMPLATES +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// Bit flags for options for the tests + +enum Options +{ + PipeIn = 0x01, // input streams are non-seekable + PipeOut = 0x02, // output streams are non-seekable + Stub = 0x04, // the archive should be appended to a stub + AllOptions = 0x07 +}; + + +/////////////////////////////////////////////////////////////////////////////// +// These structs are passed as the template parameter of the test case to +// specify a set of classes to use in the test. This allows either the generic +// wxArchiveXXX interface to be exercised or the specific interface for a +// particular archive format e.g. wxZipXXX. + +struct ArchiveClasses +{ + typedef wxArchiveEntry EntryT; + typedef wxArchiveInputStream InputStreamT; + typedef wxArchiveOutputStream OutputStreamT; + typedef wxArchiveClassFactory ClassFactoryT; + typedef wxArchiveNotifier NotifierT; + typedef wxArchiveIter IterT; + typedef wxArchivePairIter PairIterT; +}; + +struct ZipClasses +{ + typedef wxZipEntry EntryT; + typedef wxZipInputStream InputStreamT; + typedef wxZipOutputStream OutputStreamT; + typedef wxZipClassFactory ClassFactoryT; + typedef wxZipNotifier NotifierT; + typedef wxZipIter IterT; + typedef wxZipPairIter PairIterT; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// A class to hold a test entry + +class TestEntry +{ +public: + TestEntry(const wxDateTime& dt, int len, const char *data); + ~TestEntry() { delete [] m_data; } + + wxDateTime GetDateTime() const { return m_dt; } + wxFileOffset GetLength() const { return m_len; } + size_t GetSize() const { return m_len; } + const char *GetData() const { return m_data; } + wxString GetComment() const { return m_comment; } + bool IsText() const { return m_isText; } + + void SetComment(const wxString& comment) { m_comment = comment; } + void SetDateTime(const wxDateTime& dt) { m_dt = dt; } + +private: + wxDateTime m_dt; + size_t m_len; + const char *m_data; + wxString m_comment; + bool m_isText; +}; + +TestEntry::TestEntry(const wxDateTime& dt, int len, const char *data) + : m_dt(dt), + m_len(len), + m_isText(len > 0) +{ + char *d = new char[len]; + memcpy(d, data, len); + m_data = d; + + for (int i = 0; i < len && m_isText; i++) + m_isText = (signed char)m_data[i] > 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// TestOutputStream and TestInputStream are memory streams which can be +// seekable or non-seekable. + +class TestOutputStream : public wxOutputStream +{ +public: + TestOutputStream(int options); + + ~TestOutputStream() { delete [] m_data; } + + int GetOptions() const { return m_options; } + size_t GetSize() const { return m_size; } + + // gives away the data, this stream is then empty, and can be reused + void GetData(const char*& data, size_t& size); + + enum { STUB_SIZE = 2048, INITIAL_SIZE = 0x18000, SEEK_LIMIT = 0x100000 }; + +private: + void Init(); + + wxFileOffset OnSysSeek(wxFileOffset pos, wxSeekMode mode); + wxFileOffset OnSysTell() const; + size_t OnSysWrite(const void *buffer, size_t size); + + int m_options; + size_t m_pos; + size_t m_capacity; + size_t m_size; + char *m_data; +}; + +TestOutputStream::TestOutputStream(int options) + : m_options(options) +{ + Init(); +} + +void TestOutputStream::Init() +{ + m_data = NULL; + m_size = 0; + m_capacity = 0; + m_pos = 0; + + if (m_options & Stub) { + wxCharBuffer buf(STUB_SIZE); + memset(buf.data(), 0, STUB_SIZE); + Write(buf, STUB_SIZE); + } +} + +wxFileOffset TestOutputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode) +{ + if ((m_options & PipeOut) == 0) { + switch (mode) { + case wxFromStart: break; + case wxFromCurrent: pos += m_pos; break; + case wxFromEnd: pos += m_size; break; + } + if (pos < 0 || pos > SEEK_LIMIT) + return wxInvalidOffset; + m_pos = pos; + return m_pos; + } + return wxInvalidOffset; +} + +wxFileOffset TestOutputStream::OnSysTell() const +{ + return (m_options & PipeOut) == 0 ? m_pos : wxInvalidOffset; +} + +size_t TestOutputStream::OnSysWrite(const void *buffer, size_t size) +{ + if (!IsOk() || !size) + return 0; + m_lasterror = wxSTREAM_WRITE_ERROR; + + size_t newsize = m_pos + size; + wxCHECK(newsize > m_pos, 0); + + if (m_capacity < newsize) { + size_t capacity = m_capacity ? m_capacity : INITIAL_SIZE; + + while (capacity < newsize) { + capacity <<= 1; + wxCHECK(capacity > m_capacity, 0); + } + + char *buf = new char[capacity]; + if (m_data) + memcpy(buf, m_data, m_capacity); + delete [] m_data; + m_data = buf; + m_capacity = capacity; + } + + memcpy(m_data + m_pos, buffer, size); + m_pos += size; + if (m_pos > m_size) + m_size = m_pos; + m_lasterror = wxSTREAM_NO_ERROR; + + return size; +} + +void TestOutputStream::GetData(const char*& data, size_t& size) +{ + data = m_data; + size = m_size; + + if (m_options & Stub) { + char *d = m_data; + size += STUB_SIZE; + + if (size > m_capacity) { + d = new char[size]; + memcpy(d + STUB_SIZE, m_data, m_size); + delete [] m_data; + } + else { + memmove(d + STUB_SIZE, d, m_size); + } + + memset(d, 0, STUB_SIZE); + data = d; + } + + Init(); + Reset(); +} + +class TestInputStream : public wxInputStream +{ +public: + // ctor takes the data from the output stream, which is then empty + TestInputStream(TestOutputStream& out) : m_data(NULL) { SetData(out); } + // this ctor 'dups' + TestInputStream(const TestInputStream& in); + ~TestInputStream() { delete [] m_data; } + + void Rewind(); + size_t GetSize() const { return m_size; } + void SetData(TestOutputStream& out); + +private: + wxFileOffset OnSysSeek(wxFileOffset pos, wxSeekMode mode); + wxFileOffset OnSysTell() const; + size_t OnSysRead(void *buffer, size_t size); + + int m_options; + size_t m_pos; + size_t m_size; + const char *m_data; +}; + +TestInputStream::TestInputStream(const TestInputStream& in) + : m_options(in.m_options), + m_pos(in.m_pos), + m_size(in.m_size) +{ + char *p = new char[m_size]; + memcpy(p, in.m_data, m_size); + m_data = p; +} + +void TestInputStream::Rewind() +{ + if ((m_options & Stub) && (m_options & PipeIn)) + m_pos = TestOutputStream::STUB_SIZE * 2; + else + m_pos = 0; + + if (m_wbacksize) { + free(m_wback); + m_wback = NULL; + m_wbacksize = 0; + m_wbackcur = 0; + } +} + +void TestInputStream::SetData(TestOutputStream& out) +{ + delete [] m_data; + m_options = out.GetOptions(); + out.GetData(m_data, m_size); + Rewind(); + Reset(); +} + +wxFileOffset TestInputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode) +{ + if ((m_options & PipeIn) == 0) { + switch (mode) { + case wxFromStart: break; + case wxFromCurrent: pos += m_pos; break; + case wxFromEnd: pos += m_size; break; + } + if (pos < 0 || pos > TestOutputStream::SEEK_LIMIT) + return wxInvalidOffset; + m_pos = pos; + return m_pos; + } + return wxInvalidOffset; +} + +wxFileOffset TestInputStream::OnSysTell() const +{ + return (m_options & PipeIn) == 0 ? m_pos : wxInvalidOffset; +} + +size_t TestInputStream::OnSysRead(void *buffer, size_t size) +{ + if (!IsOk() || !size) + return 0; + if (m_size <= m_pos) { + m_lasterror = wxSTREAM_EOF; + return 0; + } + + if (m_size - m_pos < size) + size = m_size - m_pos; + memcpy(buffer, m_data + m_pos, size); + m_pos += size; + return size; +} + + +/////////////////////////////////////////////////////////////////////////////// +// minimal non-intrusive reference counting pointer for testing the iterators + +template class Ptr +{ +public: + explicit Ptr(T* p = NULL) : m_p(p), m_count(new int) { *m_count = 1; } + Ptr(const Ptr& sp) : m_p(sp.m_p), m_count(sp.m_count) { ++*m_count; } + ~Ptr() { Free(); } + + Ptr& operator =(const Ptr& sp) { + if (&sp != this) { + Free(); + m_p = sp.m_p; + m_count = sp.m_count; + ++*m_count; + } + return *this; + } + + T* get() const { return m_p; } + T* operator->() const { return m_p; } + T& operator*() const { return *m_p; } + +private: + void Free() { + if (--*m_count == 0) { + delete m_p; + delete m_count; + } + } + + T *m_p; + int *m_count; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Clean-up for temp directory + +class TempDir +{ +public: + TempDir(); + ~TempDir(); + wxString GetName() const { return m_tmp; } + +private: + void RemoveDir(wxString& path); + wxString m_tmp; + wxString m_original; +}; + +TempDir::TempDir() +{ + wxString tmp = wxFileName::CreateTempFileName(_T("arctest-")); + if (tmp != wxEmptyString) { + wxRemoveFile(tmp); + m_original = wxGetCwd(); + CPPUNIT_ASSERT(wxMkdir(tmp, 0700)); + m_tmp = tmp; + CPPUNIT_ASSERT(wxSetWorkingDirectory(tmp)); + } +} + +TempDir::~TempDir() +{ + if (m_tmp != wxEmptyString) { + wxSetWorkingDirectory(m_original); + RemoveDir(m_tmp); + } +} + +void TempDir::RemoveDir(wxString& path) +{ + wxCHECK_RET(!m_tmp.empty() && path.substr(0, m_tmp.length()) == m_tmp, + _T("remove '") + path + _T("' fails safety check")); + + const wxChar *files[] = { + _T("text/empty"), + _T("text/small"), + _T("bin/bin1000"), + _T("bin/bin4095"), + _T("bin/bin4096"), + _T("bin/bin4097"), + _T("bin/bin16384"), + _T("zero/zero5"), + _T("zero/zero1024"), + _T("zero/zero32768"), + _T("zero/zero16385"), + _T("zero/newname"), + _T("newfile"), + }; + + const wxChar *dirs[] = { + _T("text/"), _T("bin/"), _T("zero/"), _T("empty/") + }; + + wxString tmp = m_tmp + wxFileName::GetPathSeparator(); + size_t i; + + for (i = 0; i < WXSIZEOF(files); i++) + wxRemoveFile(tmp + wxFileName(files[i], wxPATH_UNIX).GetFullPath()); + + for (i = 0; i < WXSIZEOF(dirs); i++) + wxRmdir(tmp + wxFileName(dirs[i], wxPATH_UNIX).GetFullPath()); + + if (!wxRmdir(m_tmp)) + wxLogSysError(_T("can't remove temporary dir '%s'"), m_tmp.c_str()); +} + + +/////////////////////////////////////////////////////////////////////////////// +// wxFFile streams for piping to/from an external program + +#if defined __UNIX__ || defined __MINGW32__ +# define WXARC_popen popen +# define WXARC_pclose pclose +#elif defined _MSC_VER || defined __BORLANDC__ +# define WXARC_popen _popen +# define WXARC_pclose _pclose +#else +# define WXARC_NO_POPEN +# define WXARC_popen(cmd, type) NULL +# define WXARC_pclose(fp) +#endif + +#ifdef __WXMSW__ +# define WXARC_b "b" +#else +# define WXARC_b +#endif + +class PFileInputStream : public wxFFileInputStream +{ +public: + PFileInputStream(const wxString& cmd) : + wxFFileInputStream(WXARC_popen(cmd.mb_str(), "r" WXARC_b)) { } + ~PFileInputStream() + { WXARC_pclose(m_file->fp()); m_file->Detach(); } +}; + +class PFileOutputStream : public wxFFileOutputStream +{ +public: + PFileOutputStream(const wxString& cmd) : + wxFFileOutputStream(WXARC_popen(cmd.mb_str(), "w" WXARC_b)) { } + ~PFileOutputStream() + { WXARC_pclose(m_file->fp()); m_file->Detach(); } +}; + + +/////////////////////////////////////////////////////////////////////////////// +// The test case + +template +class ArchiveTestCase : public CppUnit::TestCase +{ +public: + ArchiveTestCase(const wxString& name, + int id, + typename Classes::ClassFactoryT *factory, + int options, + const wxString& archiver = wxEmptyString, + const wxString& unarchiver = wxEmptyString); + + ~ArchiveTestCase(); + +protected: + // the classes to test + typedef typename Classes::EntryT EntryT; + typedef typename Classes::InputStreamT InputStreamT; + typedef typename Classes::OutputStreamT OutputStreamT; + typedef typename Classes::ClassFactoryT ClassFactoryT; + typedef typename Classes::NotifierT NotifierT; + typedef typename Classes::IterT IterT; + typedef typename Classes::PairIterT PairIterT; + + // the entry point for the test + void runTest(); + + // create the test data + void CreateTestData(); + TestEntry& Add(const char *name, const char *data, int len = -1); + TestEntry& Add(const char *name, int len = 0, int value = EOF); + + // 'archive up' the test data + void CreateArchive(wxOutputStream& out); + void CreateArchive(wxOutputStream& out, const wxString& archiver); + + // perform various modifications on the archive + void ModifyArchive(wxInputStream& in, wxOutputStream& out); + + // extract the archive and verify its contents + void ExtractArchive(wxInputStream& in); + void ExtractArchive(wxInputStream& in, const wxString& unarchiver); + void VerifyDir(wxString& path, size_t rootlen = 0); + + // tests for the iterators + void TestIterator(wxInputStream& in); + void TestPairIterator(wxInputStream& in); + void TestSmartIterator(wxInputStream& in); + void TestSmartPairIterator(wxInputStream& in); + + // try reading two entries at the same time + void ReadSimultaneous(TestInputStream& in); + + // overridables + virtual void OnCreateArchive(OutputStreamT& WXUNUSED(arc)) { } + virtual void OnSetNotifier(EntryT& entry); + + virtual void OnArchiveExtracted(InputStreamT& WXUNUSED(arc), + int WXUNUSED(expectedTotal)) { } + + virtual void OnCreateEntry( OutputStreamT& WXUNUSED(arc), + TestEntry& WXUNUSED(testEntry), + EntryT *entry = NULL) { (void)entry; } + + virtual void OnEntryExtracted( EntryT& WXUNUSED(entry), + const TestEntry& WXUNUSED(testEntry), + InputStreamT *arc = NULL) { (void)arc; } + + typedef std::map TestEntries; + TestEntries m_testEntries; // test data + std::auto_ptr m_factory; // factory to make classes + int m_options; // test options + wxDateTime m_timeStamp; // timestamp to give test entries + int m_id; // select between the possibilites + wxString m_archiver; // external archiver + wxString m_unarchiver; // external unarchiver +}; + +template +ArchiveTestCase::ArchiveTestCase(const wxString& name, + int id, + ClassFactoryT *factory, + int options, + const wxString& archiver, + const wxString& unarchiver) + : CppUnit::TestCase(std::string(name.mb_str())), + m_factory(factory), + m_options(options), + m_timeStamp(1, wxDateTime::Mar, 2005, 12, 0), + m_id(id), + m_archiver(archiver), + m_unarchiver(unarchiver) +{ +} + +template +ArchiveTestCase::~ArchiveTestCase() +{ + TestEntries::iterator it; + for (it = m_testEntries.begin(); it != m_testEntries.end(); ++it) + delete it->second; +} + +template +void ArchiveTestCase::runTest() +{ + TestOutputStream out(m_options); + + CreateTestData(); + + if (m_archiver.empty()) + CreateArchive(out); + else + CreateArchive(out, m_archiver); + + // check archive could be created + CPPUNIT_ASSERT(out.GetSize() > 0); + + TestInputStream in(out); + + TestIterator(in); + in.Rewind(); + TestPairIterator(in); + in.Rewind(); + TestSmartIterator(in); + in.Rewind(); + TestSmartPairIterator(in); + in.Rewind(); + + if ((m_options & PipeIn) == 0) { + ReadSimultaneous(in); + in.Rewind(); + } + + ModifyArchive(in, out); + in.SetData(out); + + if (m_unarchiver.empty()) + ExtractArchive(in); + else + ExtractArchive(in, m_unarchiver); + + // check that all the test entries were found in the archive + CPPUNIT_ASSERT(m_testEntries.empty()); +} + +template +void ArchiveTestCase::CreateTestData() +{ + Add("text/"); + Add("text/empty", ""); + Add("text/small", "Small text file for testing\n" + "archive streams in wxWidgets\n"); + + Add("bin/"); + Add("bin/bin1000", 1000); + Add("bin/bin4095", 4095); + Add("bin/bin4096", 4096); + Add("bin/bin4097", 4097); + Add("bin/bin16384", 16384); + + Add("zero/"); + Add("zero/zero5", 5, 0); + Add("zero/zero1024", 1024, 109); + Add("zero/zero32768", 32768, 106); + Add("zero/zero16385", 16385, 119); + + Add("empty/"); +} + +template +TestEntry& ArchiveTestCase::Add(const char *name, + const char *data, + int len /*=-1*/) +{ + if (len == -1) + len = strlen(data); + TestEntry*& entry = m_testEntries[wxString(name, *wxConvCurrent)]; + wxASSERT(entry == NULL); + entry = new TestEntry(m_timeStamp, len, data); + m_timeStamp += wxTimeSpan(0, 1, 30); + return *entry; +} + +template +TestEntry& ArchiveTestCase::Add(const char *name, + int len /*=0*/, + int value /*=EOF*/) +{ + wxCharBuffer buf(len); + for (int i = 0; i < len; i++) + buf.data()[i] = value == EOF ? rand() : value; + return Add(name, buf, len); +} + +// Create an archive using the wx archive classes, write it to 'out' +// +template +void ArchiveTestCase::CreateArchive(wxOutputStream& out) +{ + std::auto_ptr arc(m_factory->NewStream(out)); + TestEntries::iterator it; + + OnCreateArchive(*arc); + + // We want to try creating entries in various different ways, 'choices' + // is just a number used to select between all the various possibilities. + int choices = m_id; + + for (it = m_testEntries.begin(); it != m_testEntries.end(); ++it) { + choices += 5; + TestEntry& testEntry = *it->second; + wxString name = it->first; + + // It should be possible to create a directory entry just by supplying + // a name that looks like a directory, or alternatively any old name + // can be identified as a directory using SetIsDir or PutNextDirEntry + bool setIsDir = name.Last() == _T('/') && (choices & 1); + if (setIsDir) + name.erase(name.length() - 1); + + // provide some context for the error message so that we know which + // iteration of the loop we were on + std::string error_entry((_T(" '") + name + _T("'")).mb_str()); + std::string error_context(" failed for entry" + error_entry); + + if ((choices & 2) || testEntry.IsText()) { + // try PutNextEntry(EntryT *pEntry) + std::auto_ptr entry(m_factory->NewEntry()); + entry->SetName(name, wxPATH_UNIX); + if (setIsDir) + entry->SetIsDir(); + entry->SetDateTime(testEntry.GetDateTime()); + entry->SetSize(testEntry.GetLength()); + OnCreateEntry(*arc, testEntry, entry.get()); + CPPUNIT_ASSERT_MESSAGE("PutNextEntry" + error_context, + arc->PutNextEntry(entry.release())); + } + else { + // try the convenience methods + OnCreateEntry(*arc, testEntry); + if (setIsDir) + CPPUNIT_ASSERT_MESSAGE("PutNextDirEntry" + error_context, + arc->PutNextDirEntry(name, testEntry.GetDateTime())); + else + CPPUNIT_ASSERT_MESSAGE("PutNextEntry" + error_context, + arc->PutNextEntry(name, testEntry.GetDateTime(), + testEntry.GetLength())); + } + + if (name.Last() != _T('/')) { + // for non-dirs write the data + arc->Write(testEntry.GetData(), testEntry.GetSize()); + CPPUNIT_ASSERT_MESSAGE("LastWrite check" + error_context, + arc->LastWrite() == testEntry.GetSize()); + // should work with or without explicit CloseEntry + if (choices & 3) + CPPUNIT_ASSERT_MESSAGE("CloseEntry" + error_context, + arc->CloseEntry()); + } + + CPPUNIT_ASSERT_MESSAGE("IsOk" + error_context, arc->IsOk()); + } + + // should work with or without explicit Close + if (m_id % 2) + CPPUNIT_ASSERT(arc->Close()); +} + +// Create an archive using an external archive program +// +template +void ArchiveTestCase::CreateArchive(wxOutputStream& out, + const wxString& archiver) +{ + // for an external archiver the test data need to be written to + // temp files + TempDir tmpdir; + + // write the files + TestEntries::iterator i; + for (i = m_testEntries.begin(); i != m_testEntries.end(); ++i) { + wxFileName fn(i->first, wxPATH_UNIX); + TestEntry& entry = *i->second; + + if (fn.IsDir()) { + fn.Mkdir(0777, wxPATH_MKDIR_FULL); + } else { + wxFileName::Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL); + wxFFileOutputStream fileout(fn.GetFullPath()); + fileout.Write(entry.GetData(), entry.GetSize()); + } + } + + for (i = m_testEntries.begin(); i != m_testEntries.end(); ++i) { + wxFileName fn(i->first, wxPATH_UNIX); + TestEntry& entry = *i->second; + wxDateTime dt = entry.GetDateTime(); +#ifdef __WXMSW__ + if (fn.IsDir()) + entry.SetDateTime(wxDateTime()); + else +#endif + fn.SetTimes(NULL, &dt, NULL); + } + + if ((m_options & PipeOut) == 0) { + wxFileName fn(tmpdir.GetName()); + fn.SetExt(_T("arc")); + wxString tmparc = fn.GetFullPath(); + + // call the archiver to create an archive file + system(wxString::Format(archiver, tmparc.c_str()).mb_str()); + + // then load the archive file + { + wxFFileInputStream in(tmparc); + if (in.Ok()) + out.Write(in); + } + + wxRemoveFile(tmparc); + } + else { + // for the non-seekable test, have the archiver output to "-" + // and read the archive via a pipe + PFileInputStream in(wxString::Format(archiver, _T("-"))); + if (in.Ok()) + out.Write(in); + } +} + +// Do a standard set of modification on an archive, delete an entry, +// rename an entry and add an entry +// +template +void ArchiveTestCase::ModifyArchive(wxInputStream& in, + wxOutputStream& out) +{ + std::auto_ptr arcIn(m_factory->NewStream(in)); + std::auto_ptr arcOut(m_factory->NewStream(out)); + std::auto_ptr entry; + + const wxString deleteName = _T("bin/bin1000"); + const wxString renameFrom = _T("zero/zero1024"); + const wxString renameTo = _T("zero/newname"); + const wxString newName = _T("newfile"); + const char *newData = "New file added as a test\n"; + + arcOut->CopyArchiveMetaData(*arcIn); + + while (entry.reset(arcIn->GetNextEntry()), entry.get() != NULL) { + OnSetNotifier(*entry); + wxString name = entry->GetName(wxPATH_UNIX); + + // provide some context for the error message so that we know which + // iteration of the loop we were on + std::string error_entry((_T(" '") + name + _T("'")).mb_str()); + std::string error_context(" failed for entry" + error_entry); + + if (name == deleteName) { + TestEntries::iterator it = m_testEntries.find(name); + CPPUNIT_ASSERT_MESSAGE( + "deletion failed (already deleted?) for" + error_entry, + it != m_testEntries.end()); + TestEntry *p = it->second; + m_testEntries.erase(it); + delete p; + } + else { + if (name == renameFrom) { + entry->SetName(renameTo); + TestEntries::iterator it = m_testEntries.find(renameFrom); + CPPUNIT_ASSERT_MESSAGE( + "rename failed (already renamed?) for" + error_entry, + it != m_testEntries.end()); + TestEntry *p = it->second; + m_testEntries.erase(it); + m_testEntries[renameTo] = p; + } + + CPPUNIT_ASSERT_MESSAGE("CopyEntry" + error_context, + arcOut->CopyEntry(entry.release(), *arcIn)); + } + } + + // check that the deletion and rename were done + CPPUNIT_ASSERT(m_testEntries.count(deleteName) == 0); + CPPUNIT_ASSERT(m_testEntries.count(renameFrom) == 0); + CPPUNIT_ASSERT(m_testEntries.count(renameTo) == 1); + + // check that the end of the input archive was reached without error + CPPUNIT_ASSERT(arcIn->Eof()); + + // try adding a new entry + TestEntry& testEntry = Add(newName.mb_str(), newData); + entry.reset(m_factory->NewEntry()); + entry->SetName(newName); + entry->SetDateTime(testEntry.GetDateTime()); + entry->SetSize(testEntry.GetLength()); + OnCreateEntry(*arcOut, testEntry, entry.get()); + OnSetNotifier(*entry); + CPPUNIT_ASSERT(arcOut->PutNextEntry(entry.release())); + CPPUNIT_ASSERT(arcOut->Write(newData, strlen(newData)).IsOk()); + + // should work with or without explicit Close + if (m_id % 2) + CPPUNIT_ASSERT(arcOut->Close()); +} + +// Extract an archive using the wx archive classes +// +template +void ArchiveTestCase::ExtractArchive(wxInputStream& in) +{ + typedef Ptr EntryPtr; + typedef std::list Entries; + typedef typename Entries::iterator EntryIter; + + std::auto_ptr arc(m_factory->NewStream(in)); + int expectedTotal = m_testEntries.size(); + EntryPtr entry; + Entries entries; + + if ((m_options & PipeIn) == 0) + OnArchiveExtracted(*arc, expectedTotal); + + while (entry = EntryPtr(arc->GetNextEntry()), entry.get() != NULL) { + wxString name = entry->GetName(wxPATH_UNIX); + + // provide some context for the error message so that we know which + // iteration of the loop we were on + std::string error_entry((_T(" '") + name + _T("'")).mb_str()); + std::string error_context(" failed for entry" + error_entry); + + TestEntries::iterator it = m_testEntries.find(name); + CPPUNIT_ASSERT_MESSAGE( + "archive contains an entry that shouldn't be there" + error_entry, + it != m_testEntries.end()); + + const TestEntry& testEntry = *it->second; + + wxDateTime dt = testEntry.GetDateTime(); + if (dt.IsValid()) + CPPUNIT_ASSERT_MESSAGE("timestamp check" + error_context, + dt == entry->GetDateTime()); + + // non-seekable entries are allowed to have GetSize == wxInvalidOffset + // until the end of the entry's data has been read past + CPPUNIT_ASSERT_MESSAGE("entry size check" + error_context, + testEntry.GetLength() == entry->GetSize() || + ((m_options & PipeIn) != 0 && entry->GetSize() == wxInvalidOffset)); + CPPUNIT_ASSERT_MESSAGE( + "arc->GetSize() == entry->GetSize()" + error_context, + arc->GetSize() == (size_t)entry->GetSize()); + + if (name.Last() != _T('/')) + { + CPPUNIT_ASSERT_MESSAGE("!IsDir" + error_context, + !entry->IsDir()); + wxCharBuffer buf(testEntry.GetSize() + 1); + CPPUNIT_ASSERT_MESSAGE("Read until Eof" + error_context, + arc->Read(buf.data(), testEntry.GetSize() + 1).Eof()); + CPPUNIT_ASSERT_MESSAGE("LastRead check" + error_context, + arc->LastRead() == testEntry.GetSize()); + CPPUNIT_ASSERT_MESSAGE("data compare" + error_context, + !memcmp(buf.data(), testEntry.GetData(), testEntry.GetSize())); + } else { + CPPUNIT_ASSERT_MESSAGE("IsDir" + error_context, entry->IsDir()); + } + + // GetSize() must return the right result in all cases after all the + // data has been read + CPPUNIT_ASSERT_MESSAGE("entry size check" + error_context, + testEntry.GetLength() == entry->GetSize()); + CPPUNIT_ASSERT_MESSAGE( + "arc->GetSize() == entry->GetSize()" + error_context, + arc->GetSize() == (size_t)entry->GetSize()); + + if ((m_options & PipeIn) == 0) { + OnEntryExtracted(*entry, testEntry, arc.get()); + delete it->second; + m_testEntries.erase(it); + } else { + entries.push_back(entry); + } + } + + // check that the end of the input archive was reached without error + CPPUNIT_ASSERT(arc->Eof()); + + // for non-seekable streams these data are only guaranteed to be + // available once the end of the archive has been reached + if (m_options & PipeIn) { + for (EntryIter i = entries.begin(); i != entries.end(); ++i) { + wxString name = (*i)->GetName(wxPATH_UNIX); + TestEntries::iterator j = m_testEntries.find(name); + OnEntryExtracted(**i, *j->second); + delete j->second; + m_testEntries.erase(j); + } + OnArchiveExtracted(*arc, expectedTotal); + } +} + +// Extract an archive using an external unarchive program +// +template +void ArchiveTestCase::ExtractArchive(wxInputStream& in, + const wxString& unarchiver) +{ + // for an external unarchiver, unarchive to a tempdir + TempDir tmpdir; + + if ((m_options & PipeIn) == 0) { + wxFileName fn(tmpdir.GetName()); + fn.SetExt(_T("arc")); + wxString tmparc = fn.GetFullPath(); + + if (m_options & Stub) + in.SeekI(TestOutputStream::STUB_SIZE * 2); + + // write the archive to a temporary file + { + wxFFileOutputStream out(tmparc); + if (out.Ok()) + out.Write(in); + } + + // call unarchiver + system(wxString::Format(unarchiver, tmparc.c_str()).mb_str()); + wxRemoveFile(tmparc); + } + else { + // for the non-seekable test, have the archiver extract "-" and + // feed it the archive via a pipe + PFileOutputStream out(wxString::Format(unarchiver, _T("-"))); + if (out.Ok()) + out.Write(in); + } + + wxString dir = tmpdir.GetName(); + VerifyDir(dir); +} + +// Verifies the files produced by an external unarchiver are as expected +// +template +void ArchiveTestCase::VerifyDir(wxString& path, size_t rootlen /*=0*/) +{ + wxDir dir; + path += wxFileName::GetPathSeparator(); + int pos = path.length(); + wxString name; + + if (!rootlen) + rootlen = pos; + + if (dir.Open(path) && dir.GetFirst(&name)) { + do { + path.replace(pos, wxString::npos, name); + name = m_factory->GetInternalName( + path.substr(rootlen, wxString::npos)); + + bool isDir = wxDirExists(path); + if (isDir) + name += _T("/"); + + // provide some context for the error message so that we know which + // iteration of the loop we were on + std::string error_entry((_T(" '") + name + _T("'")).mb_str()); + std::string error_context(" failed for entry" + error_entry); + + TestEntries::iterator it = m_testEntries.find(name); + CPPUNIT_ASSERT_MESSAGE( + "archive contains an entry that shouldn't be there" + + error_entry, + it != m_testEntries.end()); + + const TestEntry& testEntry = *it->second; + size_t size = 0; + +#ifndef __WXMSW__ + CPPUNIT_ASSERT_MESSAGE("timestamp check" + error_context, + testEntry.GetDateTime() == + wxFileName(path).GetModificationTime()); +#endif + if (!isDir) { + wxFFileInputStream in(path); + CPPUNIT_ASSERT_MESSAGE( + "entry not found in archive" + error_entry, in.Ok()); + + size = in.GetSize(); + wxCharBuffer buf(size); + CPPUNIT_ASSERT_MESSAGE("Read" + error_context, + in.Read(buf.data(), size).LastRead() == size); + CPPUNIT_ASSERT_MESSAGE("size check" + error_context, + testEntry.GetSize() == size); + CPPUNIT_ASSERT_MESSAGE("data compare" + error_context, + memcmp(buf.data(), testEntry.GetData(), size) == 0); + } + else { + VerifyDir(path, rootlen); + } + + delete it->second; + m_testEntries.erase(it); + } + while (dir.GetNext(&name)); + } +} + +// test the simple iterators that give away ownership of an entry +// +template +void ArchiveTestCase::TestIterator(wxInputStream& in) +{ + typedef std::list ArchiveCatalog; + typedef typename ArchiveCatalog::iterator CatalogIter; + + std::auto_ptr arc(m_factory->NewStream(in)); + size_t count = 0; + +#ifdef WXARC_MEMBER_TEMPLATES + ArchiveCatalog cat((IterT)*arc, IterT()); +#else + ArchiveCatalog cat; + for (IterT i(*arc); i != IterT(); ++i) + cat.push_back(*i); +#endif + + for (CatalogIter it = cat.begin(); it != cat.end(); ++it) { + std::auto_ptr entry(*it); + count += m_testEntries.count(entry->GetName(wxPATH_UNIX)); + } + + CPPUNIT_ASSERT(m_testEntries.size() == cat.size()); + CPPUNIT_ASSERT(count == cat.size()); +} + +// test the pair iterators that can be used to load a std::map or wxHashMap +// these also give away ownership of entries +// +template +void ArchiveTestCase::TestPairIterator(wxInputStream& in) +{ + typedef std::map ArchiveCatalog; + typedef typename ArchiveCatalog::iterator CatalogIter; + + std::auto_ptr arc(m_factory->NewStream(in)); + size_t count = 0; + +#ifdef WXARC_MEMBER_TEMPLATES + ArchiveCatalog cat((PairIterT)*arc, PairIterT()); +#else + ArchiveCatalog cat; + for (PairIterT i(*arc); i != PairIterT(); ++i) + cat.push_back(*i); +#endif + + for (CatalogIter it = cat.begin(); it != cat.end(); ++it) { + std::auto_ptr entry(it->second); + count += m_testEntries.count(entry->GetName(wxPATH_UNIX)); + } + + CPPUNIT_ASSERT(m_testEntries.size() == cat.size()); + CPPUNIT_ASSERT(count == cat.size()); +} + +// simple iterators using smart pointers, no need to worry about ownership +// +template +void ArchiveTestCase::TestSmartIterator(wxInputStream& in) +{ + typedef std::list > ArchiveCatalog; + typedef typename ArchiveCatalog::iterator CatalogIter; + typedef wxArchiveIterator > Iter; + + std::auto_ptr arc(m_factory->NewStream(in)); + +#ifdef WXARC_MEMBER_TEMPLATES + ArchiveCatalog cat((Iter)*arc, Iter()); +#else + ArchiveCatalog cat; + for (Iter i(*arc); i != Iter(); ++i) + cat.push_back(*i); +#endif + + CPPUNIT_ASSERT(m_testEntries.size() == cat.size()); + + for (CatalogIter it = cat.begin(); it != cat.end(); ++it) + CPPUNIT_ASSERT(m_testEntries.count((*it)->GetName(wxPATH_UNIX))); +} + +// pair iterator using smart pointers +// +template +void ArchiveTestCase::TestSmartPairIterator(wxInputStream& in) +{ + typedef std::map > ArchiveCatalog; + typedef typename ArchiveCatalog::iterator CatalogIter; + typedef wxArchiveIterator > > PairIter; + + std::auto_ptr arc(m_factory->NewStream(in)); + +#ifdef WXARC_MEMBER_TEMPLATES + ArchiveCatalog cat((PairIter)*arc, PairIter()); +#else + ArchiveCatalog cat; + for (PairIter i(*arc); i != PairIter(); ++i) + cat.push_back(*i); +#endif + + CPPUNIT_ASSERT(m_testEntries.size() == cat.size()); + + for (CatalogIter it = cat.begin(); it != cat.end(); ++it) + CPPUNIT_ASSERT(m_testEntries.count(it->second->GetName(wxPATH_UNIX))); +} + +// try reading two entries at the same time +// +template +void ArchiveTestCase::ReadSimultaneous(TestInputStream& in) +{ + typedef std::map > ArchiveCatalog; + typedef wxArchiveIterator > > PairIter; + + // create two archive input streams + TestInputStream in2(in); + std::auto_ptr arc(m_factory->NewStream(in)); + std::auto_ptr arc2(m_factory->NewStream(in2)); + + // load the catalog +#ifdef WXARC_MEMBER_TEMPLATES + ArchiveCatalog cat((PairIter)*arc, PairIter()); +#else + ArchiveCatalog cat; + for (PairIter i(*arc); i != PairIter(); ++i) + cat.push_back(*i); +#endif + + // the names of two entries to read + const wxChar *name = _T("text/small"); + const wxChar *name2 = _T("bin/bin1000"); + + // open them + typename ArchiveCatalog::iterator j; + CPPUNIT_ASSERT((j = cat.find(name)) != cat.end()); + CPPUNIT_ASSERT(arc->OpenEntry(*j->second)); + CPPUNIT_ASSERT((j = cat.find(name2)) != cat.end()); + CPPUNIT_ASSERT(arc2->OpenEntry(*j->second)); + + // get pointers to the expected data + TestEntries::iterator k; + CPPUNIT_ASSERT((k = m_testEntries.find(name)) != m_testEntries.end()); + TestEntry *entry = k->second; + CPPUNIT_ASSERT((k = m_testEntries.find(name2)) != m_testEntries.end()); + TestEntry *entry2 = k->second; + + size_t count = 0, count2 = 0; + size_t size = entry->GetSize(), size2 = entry2->GetSize(); + const char *data = entry->GetData(), *data2 = entry2->GetData(); + + // read and check the two entries in parallel, character by character + while (arc->IsOk() || arc2->IsOk()) { + char ch = arc->GetC(); + if (arc->LastRead() == 1) { + CPPUNIT_ASSERT(count < size); + CPPUNIT_ASSERT(ch == data[count++]); + } + char ch2 = arc2->GetC(); + if (arc2->LastRead() == 1) { + CPPUNIT_ASSERT(count2 < size2); + CPPUNIT_ASSERT(ch2 == data2[count2++]); + } + } + + CPPUNIT_ASSERT(arc->Eof()); + CPPUNIT_ASSERT(arc2->Eof()); + CPPUNIT_ASSERT(count == size); + CPPUNIT_ASSERT(count2 == size2); +} + +// Nothing useful can be done with a generic notifier yet, so just test one +// can be set +// +template +class ArchiveNotifier : public NotifierT +{ +public: + void OnEntryUpdated(EntryT& WXUNUSED(entry)) { } +}; + +template +void ArchiveTestCase::OnSetNotifier(EntryT& entry) +{ + static ArchiveNotifier notifier; + entry.SetNotifier(notifier); +} + + +/////////////////////////////////////////////////////////////////////////////// +// ArchiveTestCase could be used directly, but instead this +// derived class is used so that zip specific features can be tested. + +class ZipTestCase : public ArchiveTestCase +{ +public: + ZipTestCase(const wxString& name, + int id, + int options, + const wxString& archiver = wxEmptyString, + const wxString& unarchiver = wxEmptyString) + : + ArchiveTestCase(name, id, new wxZipClassFactory, + options, archiver, unarchiver), + m_count(0) + { } + +protected: + void OnCreateArchive(wxZipOutputStream& zip); + + void OnArchiveExtracted(wxZipInputStream& zip, int expectedTotal); + + void OnCreateEntry(wxZipOutputStream& zip, + TestEntry& testEntry, + wxZipEntry *entry); + + void OnEntryExtracted(wxZipEntry& entry, + const TestEntry& testEntry, + wxZipInputStream *arc); + + void OnSetNotifier(EntryT& entry); + + int m_count; + wxString m_comment; +}; + +void ZipTestCase::OnCreateArchive(wxZipOutputStream& zip) +{ + m_comment << _T("Comment for test ") << m_id; + zip.SetComment(m_comment); +} + +void ZipTestCase::OnArchiveExtracted(wxZipInputStream& zip, int expectedTotal) +{ + CPPUNIT_ASSERT(zip.GetComment() == m_comment); + CPPUNIT_ASSERT(zip.GetTotalEntries() == expectedTotal); +} + +void ZipTestCase::OnCreateEntry(wxZipOutputStream& zip, + TestEntry& testEntry, + wxZipEntry *entry) +{ + zip.SetLevel((m_id + m_count) % 10); + + if (entry) { + switch ((m_id + m_count) % 5) { + case 0: + { + wxString comment = _T("Comment for ") + entry->GetName(); + entry->SetComment(comment); + // lowercase the expected result, and the notifier should do + // the same for the zip entries when ModifyArchive() runs + testEntry.SetComment(comment.Lower()); + break; + } + case 2: + entry->SetMethod(wxZIP_METHOD_STORE); + break; + case 4: + entry->SetMethod(wxZIP_METHOD_DEFLATE); + break; + } + entry->SetIsText(testEntry.IsText()); + } + + m_count++; +} + +void ZipTestCase::OnEntryExtracted(wxZipEntry& entry, + const TestEntry& testEntry, + wxZipInputStream *arc) +{ + // provide some context for the error message so that we know which + // iteration of the loop we were on + std::string error_entry((_T(" '") + entry.GetName() + _T("'")).mb_str()); + std::string error_context(" failed for entry" + error_entry); + + CPPUNIT_ASSERT_MESSAGE("GetComment" + error_context, + entry.GetComment() == testEntry.GetComment()); + + // for seekable streams, GetNextEntry() doesn't read the local header so + // call OpenEntry() to do it + if (arc && (m_options & PipeIn) == 0 && entry.IsDir()) + arc->OpenEntry(entry); + + CPPUNIT_ASSERT_MESSAGE("IsText" + error_context, + entry.IsText() == testEntry.IsText()); + + CPPUNIT_ASSERT_MESSAGE("Extra/LocalExtra mismatch for entry" + error_entry, + (entry.GetExtraLen() != 0 && entry.GetLocalExtraLen() != 0) || + (entry.GetExtraLen() == 0 && entry.GetLocalExtraLen() == 0)); +} + +// check the notifier mechanism by using it to fold the entry comments to +// lowercase +// +class ZipNotifier : public wxZipNotifier +{ +public: + void OnEntryUpdated(wxZipEntry& entry); +}; + +void ZipNotifier::OnEntryUpdated(wxZipEntry& entry) +{ + entry.SetComment(entry.GetComment().Lower()); +} + +void ZipTestCase::OnSetNotifier(EntryT& entry) +{ + static ZipNotifier notifier; + entry.SetNotifier(notifier); +} + + +/////////////////////////////////////////////////////////////////////////////// +// 'zip - -' produces local headers without the size field set. This is a +// case not covered by all the other tests, so this class tests it as a +// special case + +class ZipPipeTestCase : public CppUnit::TestCase +{ +public: + ZipPipeTestCase(const wxString& name, int options) : + CppUnit::TestCase(std::string(name.mb_str())), m_options(options) { } + +protected: + void runTest(); + int m_options; +}; + +void ZipPipeTestCase::runTest() +{ + TestOutputStream out(m_options); + + wxString testdata = _T("test data to pipe through zip"); + wxString cmd = _T("echo ") + testdata + _T(" | zip -q - -"); + + { + PFileInputStream in(cmd); + if (in.Ok()) + out.Write(in); + } + + TestInputStream in(out); + wxZipInputStream zip(in); + + std::auto_ptr entry(zip.GetNextEntry()); + CPPUNIT_ASSERT(entry.get() != NULL); + + if ((m_options & PipeIn) == 0) + CPPUNIT_ASSERT(entry->GetSize() != wxInvalidOffset); + + char buf[64]; + size_t len = zip.Read(buf, sizeof(buf) - 1).LastRead(); + + while (len > 0 && buf[len - 1] <= 32) + --len; + buf[len] = 0; + + CPPUNIT_ASSERT(zip.Eof()); + CPPUNIT_ASSERT(wxString(buf, *wxConvCurrent) == testdata); +} + + +/////////////////////////////////////////////////////////////////////////////// +// The suite + +class ArchiveTestSuite : public CppUnit::TestSuite +{ +public: + ArchiveTestSuite(); + static CppUnit::Test *suite() + { return (new ArchiveTestSuite)->makeSuite(); } + +private: + int m_id; + wxPathList m_path; + + ArchiveTestSuite *makeSuite(); + void AddCmd(wxArrayString& cmdlist, const wxString& cmd); + bool IsInPath(const wxString& cmd); + + wxString Description(const wxString& type, + int options, + bool genericInterface = false, + const wxString& archiver = wxEmptyString, + const wxString& unarchiver = wxEmptyString); +}; + +ArchiveTestSuite::ArchiveTestSuite() + : CppUnit::TestSuite("ArchiveTestSuite"), + m_id(0) +{ + m_path.AddEnvList(_T("PATH")); +} + +// add the command for an external archiver to the list, testing for it in +// the path first +// +void ArchiveTestSuite::AddCmd(wxArrayString& cmdlist, const wxString& cmd) +{ + if (cmdlist.empty()) + cmdlist.push_back(_T("")); + if (IsInPath(cmd)) + cmdlist.push_back(cmd); +} + +bool ArchiveTestSuite::IsInPath(const wxString& cmd) +{ + wxString c = cmd.BeforeFirst(_T(' ')); +#ifdef __WXMSW__ + c += _T(".exe"); +#endif + return !m_path.FindValidPath(c).empty(); +} + +// make the test suite +// +ArchiveTestSuite *ArchiveTestSuite::makeSuite() +{ + typedef wxArrayString::iterator Iter; + wxArrayString zippers; + wxArrayString unzippers; + + AddCmd(zippers, _T("zip -qr %s *")); + AddCmd(unzippers, _T("unzip -q %s")); + + for (int genInterface = 0; genInterface < 2; genInterface++) + for (Iter i = unzippers.begin(); i != unzippers.end(); ++i) + for (Iter j = zippers.begin(); j != zippers.end(); ++j) + for (int options = 0; options <= AllOptions; options++) + { + // unzip doesn't support piping in the zip + if ((options & PipeIn) && !i->empty()) + continue; +#ifdef WXARC_NO_POPEN + // if no popen then can use piped output of zip + if ((options & PipeOut) && !j->empty()) + continue; +#endif + wxString name = Description(_T("wxZip"), options, + genInterface != 0, *j, *i); + + if (genInterface) + addTest(new ArchiveTestCase( + name, m_id, + new wxZipClassFactory, + options, *j, *i)); + else + addTest(new ZipTestCase(name, m_id, options, *j, *i)); + + m_id++; + } + +#ifndef WXARC_NO_POPEN + // if have popen then can check the piped output of 'zip - -' + if (IsInPath(_T("zip"))) + for (int options = 0; options <= PipeIn; options += PipeIn) { + wxString name = Description(_T("ZipPipeTestCase"), options); + addTest(new ZipPipeTestCase(name, options)); + m_id++; + } +#endif + + return this; +} + +// make a display string for the option bits +// +wxString ArchiveTestSuite::Description(const wxString& type, + int options, + bool genericInterface, + const wxString& archiver, + const wxString& unarchiver) +{ + wxString descr; + descr << m_id << _T(" "); + + if (genericInterface) + descr << _T("wxArchive (") << type << _T(")"); + else + descr << type; + + if (!archiver.empty()) + descr << _T(" ") << archiver.BeforeFirst(_T(' ')); + if (!unarchiver.empty()) + descr << _T(" ") << unarchiver.BeforeFirst(_T(' ')); + + wxString optstr; + + if ((options & PipeIn) != 0) + optstr += _T("|PipeIn"); + if ((options & PipeOut) != 0) + optstr += _T("|PipeOut"); + if ((options & Stub) != 0) + optstr += _T("|Stub"); + if (!optstr.empty()) + optstr = _T(" (") + optstr.substr(1) + _T(")"); + + descr << optstr; + + return descr; +} + +// register in the unnamed registry so that these tests are run by default +CPPUNIT_TEST_SUITE_REGISTRATION(ArchiveTestSuite); + +// also include in it's own registry so that these tests can be run alone +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ArchiveTestSuite, "ArchiveTestSuite"); + +#endif // wxUSE_STREAMS diff --git a/tests/test.bkl b/tests/test.bkl index 9d9dec6178..6b03777e68 100644 --- a/tests/test.bkl +++ b/tests/test.bkl @@ -9,6 +9,7 @@ template_append="wx_append_base"> test.cpp + archive/archivetest.cpp arrays/arrays.cpp datetime/datetimetest.cpp fileconf/fileconftest.cpp