From e4fff12cf632af7fd44286c2b618d7b7a1235c5c Mon Sep 17 00:00:00 2001 From: skyjake Date: Mon, 26 Nov 2012 09:29:57 +0200 Subject: [PATCH] Refactor|libdeng2: Archive and ZipArchive use PathTree for indexing Todo: PathTree does not yet allow removing nodes from the tree, which is required by Archive. --- doomsday/libdeng2/include/de/data/archive.h | 75 +- doomsday/libdeng2/include/de/data/path.h | 8 + doomsday/libdeng2/include/de/data/pathtree.h | 773 +++++++++--------- .../libdeng2/include/de/data/ziparchive.h | 12 +- doomsday/libdeng2/src/data/archive.cpp | 205 +++-- doomsday/libdeng2/src/data/path.cpp | 4 + doomsday/libdeng2/src/data/pathtree.cpp | 100 ++- doomsday/libdeng2/src/data/pathtreenode.cpp | 10 +- doomsday/libdeng2/src/data/ziparchive.cpp | 77 +- doomsday/libdeng2/src/filesys/archivefeed.cpp | 5 +- 10 files changed, 673 insertions(+), 596 deletions(-) diff --git a/doomsday/libdeng2/include/de/data/archive.h b/doomsday/libdeng2/include/de/data/archive.h index a4854424cb..38066e3e3c 100644 --- a/doomsday/libdeng2/include/de/data/archive.h +++ b/doomsday/libdeng2/include/de/data/archive.h @@ -26,10 +26,9 @@ #include "../String" #include "../Time" #include "../File" -#include "../Zeroed" +#include "../PathTree" -#include -#include +#include namespace de { @@ -74,14 +73,17 @@ namespace de public: /// Base class for format-related errors. @ingroup errors DENG2_ERROR(FormatError); - + + /// Provided path was not valid. @ingroup errors + DENG2_ERROR(InvalidPathError); + /// The requested entry does not exist in the archive. @ingroup errors DENG2_ERROR(NotFoundError); /// There is an error related to content processing. @ingroup errors DENG2_ERROR(ContentError); - typedef QSet Names; + typedef std::set Names; // alphabetical order public: /** @@ -131,27 +133,28 @@ namespace de * * @return @c true or @c false. */ - bool has(String const &path) const; + bool has(Path const &path) const; /** * List the files in a specific folder of the archive. * * @param folder Folder path to look in. + * @param names Entry names collected in a set. The names are relative to a + * @a folder and are in alphabetical order. * - * @return Entry names collected in a set. The names are relative to a - * @a folder. + * @return Number of names returned in @a names. */ - Names listFiles(String const &folder = "") const; + dint listFiles(Names &names, Path const &folder = Path()) const; /** * List the folders in a specific folder of the archive. * * @param folder Folder path to look in. - * - * @return Folder entry names collected in a set. The names are - * relative to @a folder. + * @param names Folder entry names collected in a set. The names are + * relative to @a folder and are in alphabetical order. + * @return Number of names returned in @a names. */ - Names listFolders(String const &folder = "") const; + dint listFolders(Names &names, Path const &folder = Path()) const; /** * Returns information about the specified path. @@ -160,7 +163,7 @@ namespace de * * @return Type, size, and other metadata about the entry. */ - File::Status status(String const &path) const; + File::Status status(Path const &path) const; /** * Returns the deserialized data of an entry for read-only access. The @@ -176,7 +179,7 @@ namespace de * * @return Immutable contents of the entry. */ - Block const &entryBlock(String const &path) const; + Block const &entryBlock(Path const &path) const; /** * Returns the deserialized data of an entry for read and write access. @@ -191,7 +194,7 @@ namespace de * * @return Modifiable contents of the entry. */ - Block &entryBlock(String const &path); + Block &entryBlock(Path const &path); /** * Adds an entry to the archive. The entry will not be committed to the @@ -200,7 +203,7 @@ namespace de * @param path Path of the entry within the archive. * @param data Data of the entry. */ - void add(String const &path, IByteArray const &data); + void add(Path const &path, IByteArray const &data); /** * Removes an entry from the archive. If there is deserialized data for @@ -208,7 +211,7 @@ namespace de * * @param path Path of the entry. */ - void remove(String const &path); + void remove(Path const &path); /** * Clears the index of the archive. All entries are deleted. @@ -240,7 +243,7 @@ namespace de * Interface for derived classes: */ /// Base class for archive entries. - struct Entry + struct Entry : public PathTree::Node { dsize offset; ///< Offset from the start of the source array. dsize size; ///< Deserialized size. @@ -252,10 +255,15 @@ namespace de Block *data; /// Cached copy of the serialized data. Can be @c NULL. Entry has ownership. - mutable Block *dataInArchive; + Block mutable *dataInArchive; - Entry() : offset(0), size(0), sizeInArchive(0), maybeChanged(false), - data(0), dataInArchive(0) + Entry(PathTree::NodeArgs const &args) : Node(args), + offset(0), + size(0), + sizeInArchive(0), + maybeChanged(false), + data(0), + dataInArchive(0) {} virtual ~Entry() @@ -266,15 +274,14 @@ namespace de } }; - typedef QMap Index; - /** - * Constructs a new entry. Subclass can extend Entry with more data if - * necessary. + * Sets the index used by the Archive. A concrete subclass must call + * this in their constructor; Archive does not create an index on its + * own. * - * @return New entry, ownership given to the caller. + * @param tree PathTree with entries of suitable type. Ownership given to Archive. */ - virtual Entry *newEntry() = 0; + void setIndex(PathTree *tree); /** * Reads an entry from the source archive. The implementation of this @@ -285,15 +292,17 @@ namespace de * @param path Path of the entry within the archive. * @param data Data is written here. */ - virtual void readFromSource(Entry const *entry, String const &path, IBlock &data) const = 0; + virtual void readFromSource(Entry const &entry, Path const &path, IBlock &data) const = 0; /** - * Inserts an entry into the archive's index. + * Inserts an entry into the archive's index. If the path already + * exists in the index, the old entry is deleted first. * * @param path Path of the entry. - * @param entry Entry to insert. Ownership given to Archive. + * + * @return Inserted entry. */ - void insertToIndex(String const &path, Entry *entry); + Entry &insertEntry(Path const &path); /** * Returns the full entry index so that derived classes can iterate the @@ -301,7 +310,7 @@ namespace de * * @return Entry index. */ - Index const &index() const; + PathTree const &index() const; private: struct Instance; diff --git a/doomsday/libdeng2/include/de/data/path.h b/doomsday/libdeng2/include/de/data/path.h index bbba2fa894..9a18356e4b 100644 --- a/doomsday/libdeng2/include/de/data/path.h +++ b/doomsday/libdeng2/include/de/data/path.h @@ -145,6 +145,14 @@ class DENG2_PUBLIC Path : public ISerializable, public LogEntry::Arg::Base */ Path(String const &path, QChar sep = '/'); + /** + * Construct a path from a UTF-8 C-style string. + * + * @param nullTerminatedCStr Path to be parsed. All white space is included in the path. + * @param sep Character used to separate path segments. + */ + Path(char const *nullTerminatedCStr, char sep = '/'); + /** * Construct a path by duplicating @a other. */ diff --git a/doomsday/libdeng2/include/de/data/pathtree.h b/doomsday/libdeng2/include/de/data/pathtree.h index 5824b93084..b200bf2248 100644 --- a/doomsday/libdeng2/include/de/data/pathtree.h +++ b/doomsday/libdeng2/include/de/data/pathtree.h @@ -30,442 +30,481 @@ namespace de { + +/** + * Data structure for modelling a hierarchical relationship tree of + * Path + data value pairs. @ingroup data + * + * @em Segment is the term given to a components of a hierarchical path. + * For example, the path
"c:/somewhere/something"
contains three + * path segments:
[ 0: "c:", 1: "somewhere", 2: "something" ]
+ * + * Segments are separated by @em separator @em characters. For instance, + * UNIX file paths use forward slashes as separators. + * + * Internally, segments are "pooled" such that only one instance of a + * segment is included in the model of the whole tree. Potentially, this + * significantly reduces the memory overhead which would otherwise be + * necessary to represent the complete hierarchy as a set of fully composed + * paths. + * + * Separators are not included in the hierarchy model. Not including the + * separators allows for optimal dynamic replacement when recomposing the + * original paths (also reducing the memory overhead for the whole data + * set). One potential use for this feature when representing file path + * hierarchies is "ambidextrously" recomposing paths with either forward or + * backward slashes, irrespective of the separator used at path insertion + * time. + * + * Somewhat similar to a Prefix Tree (Trie) representationally although + * that is where the similarity ends. + */ +class DENG2_PUBLIC PathTree +{ + struct Instance; // needs to be friended by Node + +public: + class Node; // forward declaration + /** - * Data structure for modelling a hierarchical relationship tree of - * Path + data value pairs. @ingroup data - * - * @em Segment is the term given to a components of a hierarchical path. - * For example, the path
"c:/somewhere/something"
contains three - * path segments:
[ 0: "c:", 1: "somewhere", 2: "something" ]
- * - * Segments are separated by @em separator @em characters. For instance, - * UNIX file paths use forward slashes as separators. - * - * Internally, segments are "pooled" such that only one instance of a - * segment is included in the model of the whole tree. Potentially, this - * significantly reduces the memory overhead which would otherwise be - * necessary to represent the complete hierarchy as a set of fully composed - * paths. - * - * Separators are not included in the hierarchy model. Not including the - * separators allows for optimal dynamic replacement when recomposing the - * original paths (also reducing the memory overhead for the whole data - * set). One potential use for this feature when representing file path - * hierarchies is "ambidextrously" recomposing paths with either forward or - * backward slashes, irrespective of the separator used at path insertion - * time. - * - * Somewhat similar to a Prefix Tree (Trie) representationally although - * that is where the similarity ends. + * Flags that affect the properties of the tree. */ - class DENG2_PUBLIC PathTree + enum Flag { - struct Instance; // needs to be friended by Node + MultiLeaf = 0x1 ///< There can be more than one leaf with a given name. + }; + Q_DECLARE_FLAGS(Flags, Flag) - public: - /** - * Flags that affect the properties of the tree. - */ - enum Flag - { - MultiLeaf = 0x1 ///< There can be more than one leaf with a given name. - }; - Q_DECLARE_FLAGS(Flags, Flag) + /** + * Flags used to alter the behavior of path comparisons. + */ + enum ComparisonFlag + { + NoBranch = 0x1, ///< Do not consider branches as possible candidates. + NoLeaf = 0x2, ///< Do not consider leaves as possible candidates. + MatchParent = 0x4, ///< Only consider nodes whose parent matches the provided reference node. + MatchFull = 0x8 /**< Whole path must match completely (i.e., path begins + from the same root point) otherwise allow partial + (i.e., relative) matches. */ + }; + Q_DECLARE_FLAGS(ComparisonFlags, ComparisonFlag) - /** - * Flags used to alter the behavior of path comparisons. - */ - enum ComparisonFlag - { - NoBranch = 0x1, ///< Do not consider branches as possible candidates. - NoLeaf = 0x2, ///< Do not consider leaves as possible candidates. - MatchParent = 0x4, ///< Only consider nodes whose parent matches that referenced. - MatchFull = 0x8 /**< Whole path must match completely (i.e., path begins - from the same root point) otherwise allow partial - (i.e., relative) matches. */ - }; - Q_DECLARE_FLAGS(ComparisonFlags, ComparisonFlag) - - /// Identifier associated with each unique path segment. - typedef duint32 SegmentId; - - /// Node type identifiers. - enum NodeType - { - Branch, - Leaf - }; - - /// @return Print-ready name for node @a type. - static String const &nodeTypeName(NodeType type); + /// Identifier associated with each unique path segment. + typedef duint32 SegmentId; - /** - * Identifier used with the search and iteration algorithms in place of - * a hash when the user does not wish to narrow the set of considered - * nodes. - */ - static Path::hash_type const no_hash; + /// Node type identifiers. + enum NodeType + { + Branch, + Leaf + }; + + /// @return Print-ready name for node @a type. + static String const &nodeTypeName(NodeType type); + + /** + * Identifier used with the search and iteration algorithms in place of + * a hash when the user does not wish to narrow the set of considered + * nodes. + */ + static Path::hash_type const no_hash; #ifdef DENG2_DEBUG - void debugPrint(QChar separator = '/') const; - void debugPrintHashDistribution() const; + void debugPrint(QChar separator = '/') const; + void debugPrintHashDistribution() const; #endif - /** - * Base class for all nodes of a PathTree. @ingroup data - */ - class DENG2_PUBLIC Node - { - protected: - Node(PathTree &tree, NodeType type, SegmentId segmentId, Node *parent = 0); - - virtual ~Node(); - - public: - /// @return PathTree which owns this node. - PathTree &tree() const; - - /// @return Parent of this node else @c NULL. - Node *parent() const; - - /// @return @c true iff this node is a leaf. - bool isLeaf() const; - - /// @return Type of this node. - inline NodeType type() const { - return isLeaf()? Leaf : Branch; - } - - /// @return Name for this node's path segment. - String const &name() const; - - /// @return Hash for this node's path segment. - Path::hash_type hash() const; - - /** - * @param searchPattern Mapped search pattern (path). - * @param flags Path comparison flags. - * - * @return Zero iff the candidate path matched this. - * - * @todo An alternative version of this whose candidate path is specified - * using another tree node (possibly from another PathTree), would - * allow for further optimizations elsewhere (in the file system - * for example) -ds - * - * @todo This logic should be encapsulated in de::Path or de::Path::Segment. -ds - */ - int comparePath(de::Path const &searchPattern, ComparisonFlags flags) const; - - /** - * Composes the path for this node. The whole path is upwardly - * reconstructed toward the root of the hierarchy. - * - * @param sep Segments in the composed path hierarchy are separatered - * with this character. Paths to branches always include - * a terminating separator. - * - * @return The composed uri. - */ - Path composePath(QChar sep = '/') const; - - friend class PathTree; - friend struct PathTree::Instance; - - protected: - SegmentId segmentId() const; - - private: - struct Instance; - Instance *d; - }; + /** + * Parameters passed to the Node constructor. Using this makes it more + * convenient to write Node-derived classes, as one doesn't have to + * spell out all the arguments provided by PathTree. + * + * Not public as only PathTree itself constructs instances, others can + * threat this as an opaque type. + */ + struct NodeArgs + { + PathTree &tree; + NodeType type; + SegmentId segmentId; + Node *parent; - public: - /// The requested entry could not be found in the hierarchy. - DENG2_ERROR(NotFoundError); + NodeArgs(PathTree &pt, NodeType nt, SegmentId id, Node *p = 0) + : tree(pt), type(nt), segmentId(id), parent(p) {} + }; - typedef QMultiHash Nodes; - typedef QList FoundPaths; + /** + * Base class for all nodes of a PathTree. @ingroup data + */ + class DENG2_PUBLIC Node + { + protected: + Node(NodeArgs const &args); - public: - explicit PathTree(Flags flags = 0); + virtual ~Node(); - virtual ~PathTree(); + public: + /// @return PathTree which owns this node. + PathTree &tree() const; - /// @return @c true iff there are no paths in the hierarchy. Same as @c size() == 0 - bool empty() const; + /// @return Parent of this node else @c NULL. + Node *parent() const; - /// @return Total number of unique paths in the hierarchy. - int size() const; + /// @return @c true iff this node is a leaf. + bool isLeaf() const; - /// @return Total number of unique paths in the hierarchy. Same as @ref size(). - inline int count() const { - return size(); + /// @return Type of this node. + inline NodeType type() const { + return isLeaf()? Leaf : Branch; } - /** - * Add a new path into the hierarchy. Duplicates are automatically pruned. - * Separators in the path are completely ignored. - * - * @param path New path to be added to the tree. Note that this path is - * NOT resolved before insertion, so any symbolics contained - * within will also be present in the name hierarchy. - * - * @return Tail node for the inserted path else @c NULL. For example, given - * the path @c "c:/somewhere/something" this is the node for the - * path segment "something". - */ - Node *insert(Path const &path); + /// @return Name for this node's path segment. + String const &name() const; - /** - * Destroy the tree's contents, free'ing all nodes. - */ - void clear(); + /// @return Hash for this node's path segment. + Path::hash_type hash() const; /** - * Find a single node in the hierarchy. + * @param searchPattern Mapped search pattern (path). + * @param flags Path comparison flags. * - * @param path Relative or absolute path to be searched for. Note that - * this path is NOT resolved before searching. This means - * that any symbolics contained within must also be present - * in the tree's name hierarchy. - * @param flags Comparison behavior flags. - * - * @return Found node. - */ - Node const &find(Path const &path, ComparisonFlags flags) const; - - /** - * @copydoc find() - */ - Node &find(Path const &path, ComparisonFlags flags); - - /** - * Collate all referenced paths in the hierarchy into a list. + * @return Zero iff the candidate path matched this. * - * @param found Set of paths that match the result. - * @param flags Comparison behavior flags. - * @param sep Segments in the composed path will be separated - * with this character. Paths to branches always include - * a terminating separator. + * @todo An alternative version of this whose candidate path is specified + * using another tree node (possibly from another PathTree), would + * allow for further optimizations elsewhere (in the file system + * for example) -ds * - * @return Number of paths found. + * @todo This logic should be encapsulated in de::Path or de::Path::Segment. -ds */ - int findAllPaths(FoundPaths &found, ComparisonFlags flags = 0, QChar sep = '/') const; + int comparePath(de::Path const &searchPattern, ComparisonFlags flags) const; /** - * Traverse the node hierarchy making a callback for visited node. Traversal - * ends when all selected nodes have been visited or a callback returns a - * non-zero value. + * Composes the path for this node. The whole path is upwardly + * reconstructed toward the root of the hierarchy. * - * @param flags Path comparison flags. - * @param parent Used in combination with @a flags= PCF_MATCH_PARENT - * to limit the traversal to only the child nodes of - * this node. - * @param hashKey If not @c PathTree::no_hash only consider nodes whose - * hashed name matches this. - * @param callback Callback function ptr. - * @param parameters Passed to the callback. + * @param sep Segments in the composed path hierarchy are separatered + * with this character. Paths to branches always include + * a terminating separator. * - * @return @c 0 iff iteration completed wholly. + * @return The composed path. */ - int traverse(ComparisonFlags flags, Node *parent, Path::hash_type hashKey, - int (*callback) (Node &node, void *parameters), void *parameters = 0) const; + Path composePath(QChar sep = '/') const; - /** - * Provides access to the nodes for efficent traversals. - * - * @param type Type of nodes to return: Leaf or Branch. - * - * @return Collection of nodes. - * @see PathTreeIterator - */ - Nodes const &nodes(NodeType type) const; + friend class PathTree; + friend struct PathTree::Instance; - /** - * Provides access to the leaf nodes for efficent traversals. - * @return Collection of nodes. - * @see PathTreeIterator - */ - inline Nodes const &leafNodes() const { - return nodes(Leaf); - } + protected: + SegmentId segmentId() const; - /** - * Provides access to the branch nodes for efficent traversals. - * @return Collection of nodes. - * @see PathTreeIterator - */ - inline Nodes const &branchNodes() const { - return nodes(Branch); - } + private: + struct Instance; + Instance *d; + }; - /* - * Methods usually only needed by Node (or derivative classes). - */ +public: + /// The requested entry could not be found in the hierarchy. + DENG2_ERROR(NotFoundError); - /// @return The path segment associated with @a segmentId. - String const &segmentName(SegmentId segmentId) const; + typedef QMultiHash Nodes; + typedef QList FoundPaths; - /// @return Hash associated with @a segmentId. - Path::hash_type segmentHash(SegmentId segmentId) const; +public: + explicit PathTree(Flags flags = 0); - protected: - /** - * Construct a new Node instance. Derived classes can override this to - * construct specialized nodes. - * - * @param type Type of the node. - * @param segmentId Path segment ID of the node. - * @param parent Parent node of the new node. - * - * @return New node. Caller gets ownership. - */ - virtual Node *newNode(NodeType type, SegmentId segmentId, Node *parent); + virtual ~PathTree(); - private: - Instance *d; - }; + /// @return @c true iff there are no paths in the hierarchy. Same as @c size() == 0 + bool empty() const; + + /// @return Total number of unique paths in the hierarchy. + int size() const; - Q_DECLARE_OPERATORS_FOR_FLAGS(PathTree::Flags) - Q_DECLARE_OPERATORS_FOR_FLAGS(PathTree::ComparisonFlags) + /// @return Total number of unique paths in the hierarchy. Same as @ref size(). + inline int count() const { + return size(); + } /** - * Utility template for specialized PathTree classes. @ingroup data + * Add a new path into the hierarchy. Duplicates are automatically pruned. + * Separators in the path are completely ignored. + * + * @param path New path to be added to the tree. Note that this path is + * NOT resolved before insertion, so any symbolics contained + * within will also be present in the name hierarchy. + * + * @return Tail node for the inserted path else @c NULL. For example, given + * the path @c "c:/somewhere/something" this is the node for the + * path segment "something". */ - template - class PathTreeT : public PathTree - { - public: - typedef Type Node; // shadow PathTree::Node - typedef QMultiHash Nodes; + Node *insert(Path const &path); - public: - explicit PathTreeT(Flags flags = 0) : PathTree(flags) {} + /** + * Removes matching nodes from the tree. + * + * @param path Path to remove. + * @param flags Search behavior. + * + * @return @c true, if one or more nodes were removed; otherwise, @c false. + */ + bool remove(Path const &path, ComparisonFlags flags = 0); - inline Type *insert(Path const &path) { - return static_cast(PathTree::insert(path)); - } + /** + * Destroy the tree's contents, free'ing all nodes. + */ + void clear(); - inline Type const &find(Path const &path, ComparisonFlags flags) const { - return static_cast(PathTree::find(path, flags)); - } + /** + * Determines if a path exists in the tree. + * + * @param path Path to look for. + * @param flags Search behavior. + * + * @return @c true, if the node exists; otherwise @c false. + */ + bool has(Path const &path, ComparisonFlags flags = 0); - inline Type &find(Path const &path, ComparisonFlags flags) { - return static_cast(PathTree::find(path, flags)); - } + /** + * Find a single node in the hierarchy. + * + * @param path Relative or absolute path to be searched for. Note that + * this path is NOT resolved before searching. This means + * that any symbolics contained within must also be present + * in the tree's name hierarchy. + * @param flags Comparison behavior flags. + * + * @return Found node. + */ + Node const &find(Path const &path, ComparisonFlags flags) const; - inline int traverse(ComparisonFlags flags, Type *parent, Path::hash_type hashKey, - int (*callback) (Type &node, void *parameters), void *parameters = 0) const { - return PathTree::traverse(flags, parent, hashKey, - reinterpret_cast(callback), - parameters); - } + /** + * @copydoc find() + */ + Node &find(Path const &path, ComparisonFlags flags); - protected: - PathTree::Node *newNode(NodeType type, SegmentId segmentId, PathTree::Node *parent) { - return new Type(*this, type, segmentId, parent); - } - }; + /** + * Collate all referenced paths in the hierarchy into a list. + * + * @param found Set of paths that match the result. + * @param flags Comparison behavior flags. + * @param sep Segments in the composed path will be separated + * with this character. Paths to branches always include + * a terminating separator. + * + * @return Number of paths found. + */ + int findAllPaths(FoundPaths &found, ComparisonFlags flags = 0, QChar sep = '/') const; /** - * Iterator template for PathTree nodes. Can be used to iterate any set of - * Nodes returned by a PathTree (PathTree::nodes(), PathTree::leafNodes(), - * PathTree::branchNodes()). @ingroup data + * Traverse the node hierarchy making a callback for visited node. Traversal + * ends when all selected nodes have been visited or a callback returns a + * non-zero value. * - * Example of using the iterator. - * @code - * PathTreeIterator it(myTree.leafNodes()); - * while(it.hasNext()) { - * MyTree::Node &node = it.next(); - * // ... - * } - * @endcode + * @param flags Path comparison flags. + * @param parent Used in combination with ComparisonFlag::MatchParent + * to limit the traversal to only the child nodes of + * this node. + * @param hashKey If not @c PathTree::no_hash only consider nodes whose + * hashed name matches this. + * @param callback Callback function ptr. + * @param parameters Passed to the callback. * - * @note Follows the Qt iterator conventions. + * @return @c 0 iff iteration completed wholly. */ - template - class PathTreeIterator - { - public: - PathTreeIterator(PathTree::Nodes const &nodes) : _nodes(nodes) { - _next = _iter = _nodes.begin(); - if(_next != _nodes.end()) ++_next; - _current = _nodes.end(); - } + int traverse(ComparisonFlags flags, Node const *parent, Path::hash_type hashKey, + int (*callback) (Node &node, void *parameters), void *parameters = 0) const; - inline bool hasNext() const { - return _iter != _nodes.end(); - } + /** + * Provides access to the nodes for efficent traversals. + * + * @param type Type of nodes to return: Leaf or Branch. + * + * @return Collection of nodes. + * @see PathTreeIterator + */ + Nodes const &nodes(NodeType type) const; - /** - * Advances the iterator over one node. - * - * @return The node that the iterator jumped over while advancing. - */ - inline typename TreeType::Node &next() { - _current = _iter; - typename TreeType::Node &val = value(); - _iter = _next; - if(_next != _nodes.end()) ++_next; - return val; - } + /** + * Provides access to the leaf nodes for efficent traversals. + * @return Collection of nodes. + * @see PathTreeIterator + */ + inline Nodes const &leafNodes() const { + return nodes(Leaf); + } - Path::hash_type key() const { - DENG2_ASSERT(_current != _nodes.end()); - return _current.key(); - } + /** + * Provides access to the branch nodes for efficent traversals. + * @return Collection of nodes. + * @see PathTreeIterator + */ + inline Nodes const &branchNodes() const { + return nodes(Branch); + } - typename TreeType::Node &value() const { - DENG2_ASSERT(_current != _nodes.end()); - return *static_cast(_current.value()); - } + /* + * Methods usually only needed by Node (or derivative classes). + */ - private: - PathTree::Nodes const &_nodes; - PathTree::Nodes::const_iterator _iter, _next, _current; - }; + /// @return The path segment associated with @a segmentId. + String const &segmentName(SegmentId segmentId) const; + /// @return Hash associated with @a segmentId. + Path::hash_type segmentHash(SegmentId segmentId) const; + +protected: /** - * PathTree node with a custom integer value and a void pointer. @ingroup data + * Construct a new Node instance. Derived classes can override this to + * construct specialized nodes. + * + * @param args Parameters for initializing the node. + * + * @return New node. Caller gets ownership. */ - class DENG2_PUBLIC UserDataNode : public PathTree::Node - { - public: - UserDataNode(PathTree &tree, PathTree::NodeType type, PathTree::SegmentId segmentId, - PathTree::Node *parent = 0, void *userPointer = 0, int userValue = 0); + virtual Node *newNode(NodeArgs const &args); - /** - * Sets the user-specified custom pointer. - * - * @param ptr Pointer to user's data. Ownership not transferred. - * - * @return Reference to this node. - */ - UserDataNode &setUserPointer(void *ptr); +private: + Instance *d; +}; - /// @return User-specified custom pointer. - void *userPointer() const; +Q_DECLARE_OPERATORS_FOR_FLAGS(PathTree::Flags) +Q_DECLARE_OPERATORS_FOR_FLAGS(PathTree::ComparisonFlags) - /** - * Sets the user-specified custom value. - * - * @return Reference to this node. - */ - UserDataNode &setUserValue(int value); +/** + * Utility template for specialized PathTree classes. @ingroup data + */ +template +class PathTreeT : public PathTree +{ +public: + typedef Type Node; // shadow PathTree::Node + typedef QMultiHash Nodes; + +public: + explicit PathTreeT(Flags flags = 0) : PathTree(flags) {} + + inline Type *insert(Path const &path) { + return static_cast(PathTree::insert(path)); + } + + inline Type const &find(Path const &path, ComparisonFlags flags) const { + return static_cast(PathTree::find(path, flags)); + } + + inline Type &find(Path const &path, ComparisonFlags flags) { + return static_cast(PathTree::find(path, flags)); + } + + inline int traverse(ComparisonFlags flags, Type const *parent, Path::hash_type hashKey, + int (*callback) (Type &node, void *parameters), void *parameters = 0) const { + return PathTree::traverse(flags, parent, hashKey, + reinterpret_cast(callback), + parameters); + } + +protected: + PathTree::Node *newNode(NodeArgs const &args) { + return new Type(args); + } +}; + +/** + * Iterator template for PathTree nodes. Can be used to iterate any set of + * Nodes returned by a PathTree (PathTree::nodes(), PathTree::leafNodes(), + * PathTree::branchNodes()). @ingroup data + * + * Example of using the iterator: + * @code + * PathTreeIterator iter(myTree.leafNodes()); + * while(iter.hasNext()) { + * MyTree::Node &node = iter.next(); + * // ... + * } + * @endcode + * + * @note Follows the Qt iterator conventions. + */ +template +class PathTreeIterator +{ +public: + PathTreeIterator(PathTree::Nodes const &nodes) : _nodes(nodes) { + _next = _iter = _nodes.begin(); + if(_next != _nodes.end()) ++_next; + _current = _nodes.end(); + } - /// @return User-specified custom value. - int userValue() const; + inline bool hasNext() const { + return _iter != _nodes.end(); + } - private: - /// User-specified data pointer associated with this node. - void *_pointer; + /** + * Advances the iterator over one node. + * + * @return The node that the iterator jumped over while advancing. + */ + inline typename TreeType::Node &next() { + _current = _iter; + typename TreeType::Node &val = value(); + _iter = _next; + if(_next != _nodes.end()) ++_next; + return val; + } + + Path::hash_type key() const { + DENG2_ASSERT(_current != _nodes.end()); + return _current.key(); + } + + typename TreeType::Node &value() const { + DENG2_ASSERT(_current != _nodes.end()); + return *static_cast(_current.value()); + } + +private: + PathTree::Nodes const &_nodes; + PathTree::Nodes::const_iterator _iter, _next, _current; +}; + +/** + * PathTree node with a custom integer value and a void pointer. @ingroup data + */ +class DENG2_PUBLIC UserDataNode : public PathTree::Node +{ +public: + UserDataNode(PathTree::NodeArgs const &args, void *userPointer = 0, int userValue = 0); - /// User-specified value associated with this node. - int _value; - }; + /** + * Sets the user-specified custom pointer. + * + * @param ptr Pointer to user's data. Ownership not transferred. + * + * @return Reference to this node. + */ + UserDataNode &setUserPointer(void *ptr); + + /// @return User-specified custom pointer. + void *userPointer() const; + + /** + * Sets the user-specified custom value. + * + * @return Reference to this node. + */ + UserDataNode &setUserValue(int value); + + /// @return User-specified custom value. + int userValue() const; + +private: + /// User-specified data pointer associated with this node. + void *_pointer; + + /// User-specified value associated with this node. + int _value; +}; - typedef PathTreeT UserDataPathTree; +typedef PathTreeT UserDataPathTree; } // namespace de diff --git a/doomsday/libdeng2/include/de/data/ziparchive.h b/doomsday/libdeng2/include/de/data/ziparchive.h index 26280a2538..0191a5fd3d 100644 --- a/doomsday/libdeng2/include/de/data/ziparchive.h +++ b/doomsday/libdeng2/include/de/data/ziparchive.h @@ -90,9 +90,7 @@ namespace de static bool recognize(File const &file); protected: - Entry *newEntry(); - - void readFromSource(Entry const *entry, String const &path, IBlock &uncompressedData) const; + void readFromSource(Entry const &entry, Path const &path, IBlock &uncompressedData) const; struct ZipEntry : public Entry { @@ -100,12 +98,16 @@ namespace de duint32 crc32; ///< CRC32 checksum. dsize localHeaderOffset; ///< Offset of the local file header. - ZipEntry() : Entry(), compression(0), crc32(0), localHeaderOffset(0) - {} + ZipEntry(PathTree::NodeArgs const &args) : Entry(args), + compression(0), crc32(0), localHeaderOffset(0) {} /// Recalculates CRC32 of the entry. void update(); }; + + typedef PathTreeT Index; + + Index const &index() const; }; } diff --git a/doomsday/libdeng2/src/data/archive.cpp b/doomsday/libdeng2/src/data/archive.cpp index ce2104daca..5b9a408288 100644 --- a/doomsday/libdeng2/src/data/archive.cpp +++ b/doomsday/libdeng2/src/data/archive.cpp @@ -1,5 +1,4 @@ -/* - * The Doomsday Engine Project -- libdeng2 +/** @file archive.cpp Collection of named memory blocks stored inside a byte array. * * Copyright (c) 2004-2012 Jaakko Keränen * @@ -28,27 +27,19 @@ struct Archive::Instance /// Source data provided at construction. IByteArray const *source; - /// Index maps entry paths to their metadata. - Archive::Index index; + /// Index maps entry paths to their metadata. Created by concrete subclasses. + PathTree *index; /// Contents of the archive have been modified. bool modified; - Instance(Archive &a, IByteArray const *src) : self(a), source(src), modified(false) + Instance(Archive &a, IByteArray const *src) : self(a), source(src), index(0), modified(false) {} - void readEntry(String const &path, IBlock &deserializedData) const + void readEntry(Path const &path, IBlock &deserializedData) const { - Archive::Index::const_iterator found = index.find(path); - if(found == index.end()) - { - /// @throw NotFoundError @a path was not found in the archive. - throw NotFoundError("Archive::readEntry", - "Entry '" + path + "' cannot not found in the archive"); - } - - Entry const &entry = *found.value(); - + Entry const &entry = static_cast( + index->find(path, PathTree::MatchFull | PathTree::NoBranch)); if(!entry.size) { // Empty entry; nothing to do. @@ -63,7 +54,13 @@ struct Archive::Instance return; } - self.readFromSource(&entry, path, deserializedData); + self.readFromSource(entry, path, deserializedData); + } + + static int addToNames(PathTree::Node &entry, void *ptr) + { + reinterpret_cast(ptr)->insert(entry.name()); + return 0; // continue } }; @@ -92,9 +89,10 @@ void Archive::cache(CacheAttachment attach) // Nothing to read from. return; } - DENG2_FOR_EACH(Index, i, d->index) + PathTreeIterator iter(d->index->leafNodes()); + while(iter.hasNext()) { - Entry &entry = *i.value(); + Entry &entry = static_cast(iter.next()); if(!entry.data && !entry.dataInArchive) { entry.dataInArchive = new Block(*d->source, entry.offset, entry.sizeInArchive); @@ -106,80 +104,63 @@ void Archive::cache(CacheAttachment attach) } } -bool Archive::has(String const &path) const +bool Archive::has(Path const &path) const { - return d->index.find(path) != d->index.end(); + DENG2_ASSERT(d->index != 0); + + return d->index->has(path, PathTree::MatchFull | PathTree::NoBranch); } -Archive::Names Archive::listFiles(String const &folder) const +dint Archive::listFiles(Archive::Names& names, Path const &folder) const { - Names names; - - String prefix = folder.empty()? "" : folder / ""; - DENG2_FOR_EACH_CONST(Index, i, d->index) - { - if(i.key().beginsWith(prefix)) - { - String relative = i.key().substr(prefix.size()); - if(relative.fileNamePath().empty()) - { - // This is a file in the folder. - names.insert(relative); - } - } - } - return names; + DENG2_ASSERT(d->index != 0); + + names.clear(); + + // Find the folder in the index. + PathTree::Node const &parent = d->index->find(folder, PathTree::MatchFull | PathTree::NoLeaf); + + // Traverse the parent's nodes. + d->index->traverse(PathTree::NoBranch | PathTree::MatchParent, &parent, PathTree::no_hash, + Archive::Instance::addToNames, &names); + + return names.size(); } -Archive::Names Archive::listFolders(String const &folder) const +dint Archive::listFolders(Archive::Names &names, Path const &folder) const { - Names names; - - String prefix = folder.empty()? "" : folder / ""; - DENG2_FOR_EACH_CONST(Index, i, d->index) - { - if(i.key().beginsWith(prefix)) - { - String relative = String(i.key().substr(prefix.size())).fileNamePath(); - if(!relative.empty() && relative.fileNamePath().empty()) - { - // This is a subfolder in the folder. - names.insert(relative); - } - } - } - return names; + DENG2_ASSERT(d->index != 0); + + names.clear(); + + // Find the folder in the index. + PathTree::Node const &parent = d->index->find(folder, PathTree::MatchFull | PathTree::NoLeaf); + + // Traverse the parent's nodes. + d->index->traverse(PathTree::NoLeaf | PathTree::MatchParent, &parent, PathTree::no_hash, + Archive::Instance::addToNames, &names); + + return names.size(); } -File::Status Archive::status(String const &path) const +File::Status Archive::status(Path const &path) const { - Index::const_iterator found = d->index.find(path); - if(found == d->index.end()) - { - /// @throw NotFoundError @a path was not found in the archive. - throw NotFoundError("Archive::fileStatus", - "Entry '" + path + "' cannot not found in the archive"); - } + DENG2_ASSERT(d->index != 0); + + Entry const &found = static_cast(d->index->find(path, PathTree::MatchFull)); return File::Status( - // Is it a folder? - (!found.value()->size && path.endsWith("/"))? File::Status::FOLDER : File::Status::FILE, - found.value()->size, - found.value()->modifiedAt); + found.isLeaf()? File::Status::FILE : File::Status::FOLDER, + found.size, + found.modifiedAt); } -Block const &Archive::entryBlock(String const &path) const +Block const &Archive::entryBlock(Path const &path) const { - Index::const_iterator found = d->index.find(path); - if(found == d->index.end()) - { - /// @throw NotFoundError @a path was not found in the archive. - throw NotFoundError("Archive::block", - "Entry '" + path + "' cannot not found in the archive"); - } - + DENG2_ASSERT(d->index != 0); + // We'll need to modify the entry. - Entry &entry = *const_cast(found.value()); + Entry &entry = static_cast(d->index->find(path, PathTree::MatchFull | PathTree::NoBranch)); if(entry.data) { // Got it. @@ -191,58 +172,62 @@ Block const &Archive::entryBlock(String const &path) const return *entry.data; } -Block &Archive::entryBlock(String const &path) +Block &Archive::entryBlock(Path const &path) { Block const &block = const_cast(this)->entryBlock(path); // Mark for recompression. - d->index.find(path).value()->maybeChanged = true; + static_cast(d->index->find(path, PathTree::MatchFull | PathTree::NoBranch)).maybeChanged = true; d->modified = true; return const_cast(block); } -void Archive::add(String const &path, IByteArray const &data) +void Archive::add(Path const &path, IByteArray const &data) { + if(path.isEmpty()) + { + /// @throws InvalidPathError Provided path was not a valid path. + throw InvalidPathError("Archive::add", + QString("'%1' is an invalid path for an entry").arg(path)); + } + // Get rid of the earlier entry with this path. if(has(path)) { remove(path); } - Entry *entry = newEntry(); - entry->data = new Block(data); - entry->modifiedAt = Time(); + DENG2_ASSERT(d->index != 0); + + Entry *entry = static_cast(d->index->insert(path)); + + DENG2_ASSERT(entry != 0); // does this actually happen and when? + + entry->data = new Block(data); + entry->modifiedAt = Time(); entry->maybeChanged = true; // The rest of the data gets updated when the archive is written. - d->index[path] = entry; d->modified = true; } -void Archive::remove(String const &path) +void Archive::remove(Path const &path) { - Index::iterator found = d->index.find(path); - if(found != d->index.end()) + DENG2_ASSERT(d->index != 0); + + if(d->index->remove(path, PathTree::MatchFull | PathTree::NoBranch)) { - delete found.value(); - d->index.erase(found); d->modified = true; - return; } - /// @throw NotFoundError The path does not exist in the archive. - throw NotFoundError("Archive::remove", "Entry '" + path + "' not found"); } void Archive::clear() { - // Free deserialized data. - DENG2_FOR_EACH(Index, i, d->index) - { - delete i.value(); - } - d->index.clear(); + DENG2_ASSERT(d->index != 0); + + d->index->clear(); d->modified = true; } @@ -251,18 +236,26 @@ bool Archive::modified() const return d->modified; } -void Archive::insertToIndex(String const &path, Entry *entry) +void Archive::setIndex(PathTree *tree) { - if(d->index.contains(path)) - { - delete d->index[path]; - } - d->index[path] = entry; + d->index = tree; } -Archive::Index const &Archive::index() const +Archive::Entry &Archive::insertEntry(Path const &path) { - return d->index; + DENG2_ASSERT(d->index != 0); + + // Remove any existing node at this path. + d->index->remove(path, PathTree::MatchFull | PathTree::NoBranch); + + return static_cast(*d->index->insert(path)); +} + +PathTree const &Archive::index() const +{ + DENG2_ASSERT(d->index != 0); + + return *d->index; } } // namespace de diff --git a/doomsday/libdeng2/src/data/path.cpp b/doomsday/libdeng2/src/data/path.cpp index 23bf39b8d8..f162724b94 100644 --- a/doomsday/libdeng2/src/data/path.cpp +++ b/doomsday/libdeng2/src/data/path.cpp @@ -242,6 +242,10 @@ Path::Path(String const &path, QChar sep) : LogEntry::Arg::Base(), d(new Instance(path, sep)) {} +Path::Path(char const *nullTerminatedCStr, char sep) + : LogEntry::Arg::Base(), d(new Instance(QString::fromUtf8(nullTerminatedCStr), sep)) +{} + Path::Path(Path const &other) : ISerializable(), LogEntry::Arg::Base(), d(new Instance(other.d->path, other.d->separator)) diff --git a/doomsday/libdeng2/src/data/pathtree.cpp b/doomsday/libdeng2/src/data/pathtree.cpp index 416ad4c119..46eab1c5cb 100644 --- a/doomsday/libdeng2/src/data/pathtree.cpp +++ b/doomsday/libdeng2/src/data/pathtree.cpp @@ -46,8 +46,7 @@ struct PathTree::Instance PathTree::Nodes leafHash; PathTree::Nodes branchHash; - Instance(PathTree &d, int _flags) - : self(d), flags(_flags), size(0) + Instance(PathTree &d, int _flags) : self(d), flags(_flags), size(0) {} ~Instance() @@ -115,7 +114,7 @@ struct PathTree::Instance // Are we out of indices? if(!segmentId) return NULL; - PathTree::Node *node = self.newNode(nodeType, segmentId, parent); + PathTree::Node *node = self.newNode(PathTree::NodeArgs(self, nodeType, segmentId, parent)); // Insert the new node into the hash. if(nodeType == PathTree::Leaf) @@ -161,6 +160,44 @@ struct PathTree::Instance return node; } + PathTree::Node const *find(Path const &searchPath, PathTree::ComparisonFlags compFlags) const + { + if(!searchPath.isEmpty() && size) + { + Path::hash_type hashKey = searchPath.lastSegment().hash(); + if(!(compFlags & PathTree::NoLeaf)) + { + for(Nodes::const_iterator i = leafHash.find(hashKey); + i != leafHash.end() && i.key() == hashKey; ++i) + { + PathTree::Node const &node = **i; + if(!node.comparePath(searchPath, compFlags)) + { + // This is the leaf node we're looking for. + return &node; + } + } + } + + if(!(compFlags & PathTree::NoBranch)) + { + for(Nodes::const_iterator i = branchHash.find(hashKey); + i != branchHash.end() && i.key() == hashKey; ++i) + { + PathTree::Node const &node = **i; + if(!node.comparePath(searchPath, compFlags)) + { + // This is the branch node we're looking for. + return &node; + } + } + } + } + + // The referenced node could not be found. + return 0; + } + static void clearPathHash(PathTree::Nodes &ph) { LOG_AS("PathTree::clearPathHash"); @@ -185,6 +222,12 @@ PathTree::Node *PathTree::insert(Path const &path) return node; } +bool PathTree::remove(Path const &path, ComparisonFlags flags) +{ + DENG2_ASSERT(false); + return false; +} + PathTree::PathTree(Flags flags) { d = new Instance(*this, flags); @@ -219,43 +262,20 @@ void PathTree::clear() d->clear(); } +bool PathTree::has(Path const &path, ComparisonFlags flags) +{ + return d->find(path, flags) != 0; +} + PathTree::Node const &PathTree::find(Path const &searchPath, ComparisonFlags flags) const { - if(!searchPath.isEmpty() && d->size) + Node const *found = d->find(searchPath, flags); + if(!found) { - Path::hash_type hashKey = searchPath.lastSegment().hash(); - if(!(flags & NoLeaf)) - { - Nodes &nodes = d->leafHash; - for(Nodes::const_iterator i = nodes.find(hashKey); - i != nodes.end() && i.key() == hashKey; ++i) - { - Node const &node = **i; - if(!node.comparePath(searchPath, flags)) - { - // This is the node we're looking for. - return node; - } - } - } - - if(!(flags & NoBranch)) - { - Nodes &nodes = d->branchHash; - for(Nodes::const_iterator i = nodes.find(hashKey); i != nodes.end() && i.key() == hashKey; ++i) - { - Node const &node = **i; - if(!node.comparePath(searchPath, flags)) - { - // This is the node we're looking for. - return node; - } - } - } + /// @throw NotFoundError The referenced node could not be found. + throw NotFoundError("PathTree::find", "No paths found matching \"" + searchPath + "\""); } - - /// @throw NotFoundError The referenced node could not be found. - throw NotFoundError("PathTree::find", "No paths found matching \"" + searchPath + "\""); + return *found; } PathTree::Node &PathTree::find(Path const &path, ComparisonFlags flags) @@ -274,9 +294,9 @@ Path::hash_type PathTree::segmentHash(SegmentId segmentId) const return d->segments.userValue(segmentId); } -PathTree::Node *PathTree::newNode(PathTree::NodeType type, PathTree::SegmentId segmentId, PathTree::Node *parent) +PathTree::Node *PathTree::newNode(NodeArgs const &args) { - return new Node(*this, type, segmentId, parent); + return new Node(args); } PathTree::Nodes const &PathTree::nodes(NodeType type) const @@ -310,7 +330,7 @@ int PathTree::findAllPaths(FoundPaths &found, ComparisonFlags flags, QChar separ } static int iteratePathsInHash(PathTree const &pathTree, Path::hash_type hashKey, - PathTree::NodeType type, int flags, PathTree::Node *parent, + PathTree::NodeType type, int flags, PathTree::Node const *parent, int (*callback) (PathTree::Node &, void *), void *parameters) { int result = 0; @@ -351,7 +371,7 @@ static int iteratePathsInHash(PathTree const &pathTree, Path::hash_type hashKey, return result; } -int PathTree::traverse(ComparisonFlags flags, PathTree::Node *parent, Path::hash_type hashKey, +int PathTree::traverse(ComparisonFlags flags, PathTree::Node const *parent, Path::hash_type hashKey, int (*callback) (PathTree::Node &, void *), void *parameters) const { int result = 0; diff --git a/doomsday/libdeng2/src/data/pathtreenode.cpp b/doomsday/libdeng2/src/data/pathtreenode.cpp index 2986b4ad19..567cf80d67 100644 --- a/doomsday/libdeng2/src/data/pathtreenode.cpp +++ b/doomsday/libdeng2/src/data/pathtreenode.cpp @@ -44,10 +44,9 @@ struct PathTree::Node::Instance {} }; -PathTree::Node::Node(PathTree &tree, PathTree::NodeType type, PathTree::SegmentId fragmentId, - PathTree::Node *parent) +PathTree::Node::Node(PathTree::NodeArgs const &args) { - d = new Instance(tree, type == PathTree::Leaf, fragmentId, parent); + d = new Instance(args.tree, args.type == PathTree::Leaf, args.segmentId, args.parent); } PathTree::Node::~Node() @@ -282,9 +281,8 @@ Path PathTree::Node::composePath(QChar sep) const return Path(parm.composedPath, sep); } -UserDataNode::UserDataNode(PathTree &tree, PathTree::NodeType type, PathTree::SegmentId segmentId, - PathTree::Node *parent, void *userPointer, int userValue) - : PathTree::Node(tree, type, segmentId, parent), +UserDataNode::UserDataNode(PathTree::NodeArgs const &args, void *userPointer, int userValue) + : PathTree::Node(args), _pointer(userPointer), _value(userValue) {} diff --git a/doomsday/libdeng2/src/data/ziparchive.cpp b/doomsday/libdeng2/src/data/ziparchive.cpp index 605e728fc1..c5ecd85094 100644 --- a/doomsday/libdeng2/src/data/ziparchive.cpp +++ b/doomsday/libdeng2/src/data/ziparchive.cpp @@ -257,10 +257,14 @@ struct CentralEnd : public ISerializable { using namespace internal; ZipArchive::ZipArchive() : Archive() -{} +{ + setIndex(new Index); +} ZipArchive::ZipArchive(IByteArray const &archive) : Archive(archive) { + setIndex(new Index); + Reader reader(archive, littleEndianByteOrder); // Locate the central directory. Start from the earliest location where @@ -341,31 +345,29 @@ ZipArchive::ZipArchive(IByteArray const &archive) : Archive(archive) "Entry '" + fileName + "' is encrypted and thus cannot be read"); } - // Make an index entry for this. - QScopedPointer entry(new ZipEntry); - entry->size = header.size; - entry->sizeInArchive = header.compressedSize; - entry->compression = header.compression; - entry->crc32 = header.crc32; - entry->localHeaderOffset = header.relOffset; - - // Unpack the last modified time from the ZIP entry header. - DOSDate lastModDate(header.lastModDate); - DOSTime lastModTime(header.lastModTime); - entry->modifiedAt = QDateTime(QDate(lastModDate.year + 1980, lastModDate.month, lastModDate.dayOfMonth), - QTime(lastModTime.hours, lastModTime.minutes, lastModTime.seconds)); - // Read the local file header, which contains the correct extra // field size (Info-ZIP!). reader.mark(); reader.setOffset(header.relOffset); - LocalFileHeader localHeader; reader >> localHeader; - entry->offset = reader.offset() + header.fileNameSize + localHeader.extraFieldSize; + // Make an index entry for this. + ZipEntry &entry = static_cast(insertEntry(fileName)); + + entry.size = header.size; + entry.sizeInArchive = header.compressedSize; + entry.compression = header.compression; + entry.crc32 = header.crc32; + entry.localHeaderOffset = header.relOffset; + + // Unpack the last modified time from the ZIP entry header. + DOSDate lastModDate(header.lastModDate); + DOSTime lastModTime(header.lastModTime); + entry.modifiedAt = QDateTime(QDate(lastModDate.year + 1980, lastModDate.month, lastModDate.dayOfMonth), + QTime(lastModTime.hours, lastModTime.minutes, lastModTime.seconds)); - insertToIndex(fileName, entry.take()); + entry.offset = reader.offset() + header.fileNameSize + localHeader.extraFieldSize; // Back to the central directory. reader.rewind(); @@ -375,9 +377,9 @@ ZipArchive::ZipArchive(IByteArray const &archive) : Archive(archive) ZipArchive::~ZipArchive() {} -void ZipArchive::readFromSource(Entry const *e, String const &, IBlock &uncompressedData) const +void ZipArchive::readFromSource(Entry const &e, Path const &, IBlock &uncompressedData) const { - ZipEntry const &entry = *static_cast(e); + ZipEntry const &entry = static_cast(e); if(entry.compression == NO_COMPRESSION) { @@ -438,6 +440,11 @@ void ZipArchive::readFromSource(Entry const *e, String const &, IBlock &uncompre } } +ZipArchive::Index const &ZipArchive::index() const +{ + return static_cast(Archive::index()); +} + void ZipArchive::operator >> (Writer &to) const { /** @@ -447,12 +454,14 @@ void ZipArchive::operator >> (Writer &to) const Writer writer(to, littleEndianByteOrder); // First write the local headers. - DENG2_FOR_EACH_CONST(Index, i, index()) + for(PathTreeIterator iter(index().leafNodes()); iter.hasNext(); ) { // We will be updating relevant members of the entry. - ZipEntry &entry = *static_cast(const_cast(i.value())); + ZipEntry &entry = iter.next(); entry.update(); + String const fullPath = entry.composePath(); + // This is where the local file header is located. entry.localHeaderOffset = writer.offset(); @@ -466,13 +475,13 @@ void ZipArchive::operator >> (Writer &to) const header.crc32 = entry.crc32; header.compressedSize = entry.sizeInArchive; header.size = entry.size; - header.fileNameSize = i.key().size(); + header.fileNameSize = fullPath.size(); // Can we use the data already in the source archive? if((entry.dataInArchive || source()) && !entry.maybeChanged) { // Yes, we can. - writer << header << FixedByteArray(i.key().toLatin1()); + writer << header << FixedByteArray(fullPath.toLatin1()); IByteArray::Offset newOffset = writer.offset(); if(entry.dataInArchive) { @@ -516,7 +525,7 @@ void ZipArchive::operator >> (Writer &to) const // Compression was ok. header.compression = entry.compression = DEFLATED; header.compressedSize = entry.sizeInArchive = stream.total_out; - writer << header << FixedByteArray(i.key().toLatin1()); + writer << header << FixedByteArray(fullPath.toLatin1()); entry.offset = writer.offset(); writer << FixedByteArray(archived, 0, entry.sizeInArchive); } @@ -525,7 +534,7 @@ void ZipArchive::operator >> (Writer &to) const // We won't compress. header.compression = entry.compression = NO_COMPRESSION; header.compressedSize = entry.sizeInArchive = entry.data->size(); - writer << header << FixedByteArray(i.key().toLatin1()); + writer << header << FixedByteArray(fullPath.toLatin1()); entry.offset = writer.offset(); writer << FixedByteArray(*entry.data); } @@ -539,9 +548,10 @@ void ZipArchive::operator >> (Writer &to) const summary.offset = writer.offset(); // Write the central directory. - DENG2_FOR_EACH_CONST(Index, i, index()) + for(PathTreeIterator iter(index().leafNodes()); iter.hasNext(); ) { - ZipEntry const &entry = *static_cast(i.value()); + ZipEntry const &entry = iter.next(); + String const fullPath = entry.composePath(); CentralFileHeader header; header.signature = SIG_CENTRAL_FILE_HEADER; @@ -554,10 +564,10 @@ void ZipArchive::operator >> (Writer &to) const header.crc32 = entry.crc32; header.compressedSize = entry.sizeInArchive; header.size = entry.size; - header.fileNameSize = i.key().size(); + header.fileNameSize = fullPath.size(); header.relOffset = entry.localHeaderOffset; - writer << header << FixedByteArray(i.key().toLatin1()); + writer << header << FixedByteArray(fullPath.toLatin1()); } // Size of the central directory. @@ -582,13 +592,6 @@ bool ZipArchive::recognize(File const &file) ext == ".box" || ext == ".pk3" || ext == ".zip"); } -Archive::Entry *ZipArchive::newEntry() -{ - ZipEntry *entry = new ZipEntry; - entry->compression = NO_COMPRESSION; // Will be updated. - return entry; -} - void ZipArchive::ZipEntry::update() { if(data) diff --git a/doomsday/libdeng2/src/filesys/archivefeed.cpp b/doomsday/libdeng2/src/filesys/archivefeed.cpp index 1855d974df..53bb8bda02 100644 --- a/doomsday/libdeng2/src/filesys/archivefeed.cpp +++ b/doomsday/libdeng2/src/filesys/archivefeed.cpp @@ -111,7 +111,8 @@ struct ArchiveFeed::Instance void populate(Folder &folder) { // Get a list of the files in this directory. - Archive::Names names = archive().listFiles(basePath); + Archive::Names names; + archive().listFiles(names, basePath); DENG2_FOR_EACH(Archive::Names, i, names) { @@ -139,7 +140,7 @@ struct ArchiveFeed::Instance } // Also populate subfolders. - names = archive().listFolders(basePath); + archive().listFolders(names, basePath); for(Archive::Names::iterator i = names.begin(); i != names.end(); ++i) {