Browse Source

Adding resource filesystem

Panagiotis Christopoulos Charitos 10 years ago
parent
commit
50ecb91c3d

+ 118 - 0
include/anki/resource/ResourceFilesystem.h

@@ -0,0 +1,118 @@
+// Copyright (C) 2009-2015, Panagiotis Christopoulos Charitos.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include "anki/util/String.h"
+#include "anki/util/StringList.h"
+#include "anki/util/File.h"
+#include "anki/util/Ptr.h"
+
+namespace anki {
+
+/// @addtogroup resource
+/// @{
+
+/// Resource filesystem file. An interface that abstracts the resource file.
+class ResourceFile
+{
+public:
+	using SeekOrigin = File::SeekOrigin;
+
+	ResourceFile(GenericMemoryPoolAllocator<U8> alloc)
+		: m_alloc(alloc)
+	{}
+
+	virtual ~ResourceFile()
+	{}
+
+	/// Read data from the file
+	virtual ANKI_USE_RESULT Error read(void* buff, PtrSize size) = 0;
+
+	/// Read all the contents of a text file
+	/// If the file is not rewined it will probably fail
+	virtual ANKI_USE_RESULT Error readAllText(
+		GenericMemoryPoolAllocator<U8> alloc, String& out) = 0;
+
+	/// Read 32bit unsigned integer. Set the endianness if the file's
+	/// endianness is different from the machine's
+	virtual ANKI_USE_RESULT Error readU32(U32& u) = 0;
+
+	/// Read 32bit float. Set the endianness if the file's endianness is
+	/// different from the machine's
+	virtual ANKI_USE_RESULT Error readF32(F32& f) = 0;
+
+	/// Set the position indicator to a new position
+	/// @param offset Number of bytes to offset from origin
+	/// @param origin Position used as reference for the offset
+	virtual ANKI_USE_RESULT Error seek(PtrSize offset, SeekOrigin origin) = 0;
+
+	Atomic<I32>& getRefcount()
+	{
+		return m_refcount;
+	}
+
+	GenericMemoryPoolAllocator<U8> getAllocator() const
+	{
+		return m_alloc;
+	}
+
+private:
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	Atomic<I32> m_refcount = {0};
+};
+
+/// Resource file smart pointer.
+using ResourceFilePtr = IntrusivePtr<ResourceFile>;
+
+/// Resource filesystem.
+class ResourceFilesystem
+{
+public:
+	ResourceFilesystem(GenericMemoryPoolAllocator<U8> alloc)
+		: m_alloc(alloc)
+	{}
+
+	~ResourceFilesystem();
+
+	/// Search the path list to find the file. Then open the file for reading.
+	ANKI_USE_RESULT Error openFile(
+		const CString& filename, ResourceFilePtr& file);
+
+	/// Add a filesystem path or an archive
+	ANKI_USE_RESULT Error addNewPath(const CString& path);
+
+private:
+	class Path: public NonCopyable
+	{
+	public:
+		StringList m_files; ///< Files inside the directory.
+		String m_path; ///< A directory or an archive.
+		Bool8 m_isArchive = false;
+
+		Path() = default;
+
+		Path(Path&& b)
+			: m_files(std::move(b.m_files))
+			, m_path(std::move(b.m_path))
+			, m_isArchive(std::move(b.m_isArchive))
+		{}
+
+		Path& operator=(Path&& b)
+		{
+			m_files = std::move(b.m_files);
+			m_path = std::move(b.m_path);
+			m_isArchive = std::move(b.m_isArchive);
+			return *this;
+		}
+	};
+
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	List<Path> m_paths;
+};
+/// @}
+
+} // end namespace anki
+

+ 1 - 3
include/anki/resource/ResourcePointer.h

@@ -3,8 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#ifndef ANKI_RESOURCE_RESOURCE_POINTER_H
-#define ANKI_RESOURCE_RESOURCE_POINTER_H
+#pragma once
 
 #include "anki/resource/ResourceManager.h"
 #include "anki/util/Ptr.h"
@@ -77,4 +76,3 @@ private:
 
 #include "anki/resource/ResourcePointer.inl.h"
 
-#endif

+ 2 - 1
include/anki/resource/ResourcePointer.inl.h

@@ -77,7 +77,8 @@ Error ResourcePointer<T>::load(
 //==============================================================================
 template<typename T>
 template<typename... TArgs>
-Error ResourcePointer<T>::loadToCache(ResourceManager* resources, TArgs&&... args)
+Error ResourcePointer<T>::loadToCache(
+	ResourceManager* resources, TArgs&&... args)
 {
 	StringAuto fname(resources->getTempAllocator());
 

+ 7 - 9
include/anki/util/File.h

@@ -3,8 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#ifndef ANKI_UTIL_FILE_H
-#define ANKI_UTIL_FILE_H
+#pragma once
 
 #include "anki/util/String.h"
 #include "anki/util/Enum.h"
@@ -29,9 +28,9 @@ namespace anki {
 /// can read from regular C files, zip files and on Android from the packed
 /// asset files.
 /// To identify the file:
-/// - If the path contains ".ankizip" (eg /path/to/arch.ankizip/path/file.ext) 
+/// - If the path contains ".ankizip" (eg /path/to/arch.ankizip/path/file.ext)
 ///   it tries to open the archive and read the file from there.
-/// - If the filename starts with '$' it will try to load a system specific 
+/// - If the filename starts with '$' it will try to load a system specific
 ///   file. For Android this is a file in the .apk
 /// - If the above are false then try to load a regular C file
 class File: public NonCopyable
@@ -71,7 +70,7 @@ public:
 	/// Closes the file if it's open
 	~File();
 
-	/// Move 
+	/// Move
 	File& operator=(File&& b);
 
 	/// Open a file.
@@ -99,11 +98,11 @@ public:
 	ANKI_USE_RESULT Error readAllText(
 		GenericMemoryPoolAllocator<U8> alloc, String& out);
 
-	/// Read 32bit unsigned integer. Set the endianness if the file's 
+	/// Read 32bit unsigned integer. Set the endianness if the file's
 	/// endianness is different from the machine's
 	ANKI_USE_RESULT Error readU32(U32& u);
 
-	/// Read 32bit float. Set the endianness if the file's endianness is 
+	/// Read 32bit float. Set the endianness if the file's endianness is
 	/// different from the machine's
 	ANKI_USE_RESULT Error readF32(F32& f);
 
@@ -125,7 +124,7 @@ private:
 		NONE = 0,
 		C, ///< C file
 		ZIP, ///< Ziped file
-		SPECIAL ///< For example file is located in the android apk 
+		SPECIAL ///< For example file is located in the android apk
 	};
 
 	void* m_file = nullptr; ///< A native file type
@@ -171,4 +170,3 @@ private:
 
 } // end namespace anki
 
-#endif

+ 16 - 1
include/anki/util/Filesystem.h

@@ -18,11 +18,26 @@ Bool fileExists(const CString& filename);
 
 /// Get file extension.
 void getFileExtension(
-	const CString& filename, GenericMemoryPoolAllocator<U8> alloc, String& out);
+	const CString& filename,
+	GenericMemoryPoolAllocator<U8> alloc,
+	String& out);
 
 /// Return true if directory exists?
 Bool directoryExists(const CString& dir);
 
+/// Callback for the @ref walkDirectoryTree.
+/// - 1st parameter: The file or directory name.
+/// - 2nd parameter: User data passed to walkDirectoryTree.
+/// - 3rd parameter: True if it's directory, false if it's regular file.
+using WalkDirectoryTreeCallback = Error(*)(const CString&, void*, Bool);
+
+/// Walk a directory and it's subdirectories. Will walk and list all the
+// directories and files of a directory.
+ANKI_USE_RESULT Error walkDirectoryTree(
+	const CString& dir,
+	void* userData,
+	WalkDirectoryTreeCallback callback);
+
 /// Equivalent to: rm -rf dir
 ANKI_USE_RESULT Error removeDirectory(const CString& dir);
 

+ 11 - 14
include/anki/util/List.h

@@ -3,8 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#ifndef ANKI_UTIL_LIST_H
-#define ANKI_UTIL_LIST_H
+#pragma once
 
 #include "anki/util/Allocator.h"
 #include "anki/util/NonCopyable.h"
@@ -47,8 +46,8 @@ public:
 	ListIterator() = default;
 
 	ListIterator(const ListIterator& b)
-	:	m_node(b.m_node),
-		m_list(b.m_list)
+		: m_node(b.m_node)
+		, m_list(b.m_list)
 	{}
 
 	/// Allow conversion from iterator to const iterator.
@@ -56,13 +55,13 @@ public:
 		typename YValueReference, typename YList>
 	ListIterator(const ListIterator<YNodePointer,
 		YValuePointer, YValueReference, YList>& b)
-	:	m_node(b.m_node),
-		m_list(b.m_list)
+		: m_node(b.m_node)
+		, m_list(b.m_list)
 	{}
 
 	ListIterator(TNodePointer node, TListPointer list)
-	:	m_node(node),
-		m_list(list)
+		: m_node(node)
+		, m_list(list)
 	{
 		ANKI_ASSERT(list);
 	}
@@ -182,7 +181,7 @@ public:
 
 	/// Move.
 	List(List&& b)
-	:	List()
+		: List()
 	{
 		move(b);
 	}
@@ -379,13 +378,13 @@ public:
 
 	/// Construct using an allocator.
 	ListAuto(GenericMemoryPoolAllocator<T> alloc)
-	:	Base(),
-		m_alloc(alloc)
+		: Base()
+		, m_alloc(alloc)
 	{}
 
 	/// Move.
 	ListAuto(ListAuto&& b)
-	:	Base()
+		: Base()
 	{
 		move(b);
 	}
@@ -463,5 +462,3 @@ private:
 
 #include "anki/util/List.inl.h"
 
-#endif
-

+ 4 - 6
include/anki/util/StdTypes.h

@@ -3,8 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#ifndef ANKI_UTIL_STD_TYPES_H
-#define ANKI_UTIL_STD_TYPES_H
+#pragma once
 
 #include "anki/Config.h"
 #include <cstdint>
@@ -72,7 +71,7 @@ using Bool = bool; ///< Fast boolean type
 using Bool8 = U8; ///< Small 8bit boolean type
 
 /// Error codes
-enum class ErrorCode
+enum class ErrorCode: I32
 {
 	NONE,
 	OUT_OF_MEMORY,
@@ -92,12 +91,12 @@ class Error
 public:
 	/// Construct using an error code.
 	Error(ErrorCode code)
-	:	m_code(code)
+		: m_code(code)
 	{}
 
 	/// Copy.
 	Error(const Error& b)
-	:	m_code(b.m_code)
+		: m_code(b.m_code)
 	{}
 
 	/// Copy.
@@ -174,4 +173,3 @@ private:
 
 } // end namespace anki
 
-#endif

+ 60 - 62
include/anki/util/String.h

@@ -3,8 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#ifndef ANKI_UTIL_STRING_H
-#define ANKI_UTIL_STRING_H
+#pragma once
 
 #include "anki/util/DArray.h"
 #include "anki/util/Array.h"
@@ -70,22 +69,22 @@ public:
 
 	CString()  = default;
 
-	CString(const Char* ptr) 
-	:	m_ptr(ptr)
+	CString(const Char* ptr)
+		: m_ptr(ptr)
 	{
 		checkInit();
 	}
 
 	/// Copy constructor.
-	CString(const CString& b) 
-	:	m_ptr(b.m_ptr),
-		m_length(b.m_length)
+	CString(const CString& b)
+		: m_ptr(b.m_ptr)
+		, m_length(b.m_length)
 	{
 		checkInit();
 	}
 
 	/// Copy.
-	CString& operator=(const CString& b) 
+	CString& operator=(const CString& b)
 	{
 		m_ptr = b.m_ptr;
 		m_length = b.m_length;
@@ -93,38 +92,38 @@ public:
 	}
 
 	/// Return true if the string is initialized.
-	operator Bool() const 
+	operator Bool() const
 	{
 		return !isEmpty();
 	}
 
 	/// Return char at the specified position.
-	const Char& operator[](U pos) const 
+	const Char& operator[](U pos) const
 	{
 		checkInit();
 		ANKI_ASSERT(pos <= getLength());
 		return m_ptr[pos];
 	}
 
-	const Char* begin() const 
+	const Char* begin() const
 	{
 		checkInit();
 		return &m_ptr[0];
 	}
 
-	const Char* end() const 
+	const Char* end() const
 	{
 		checkInit();
 		return &m_ptr[getLength()];
 	}
 
 	/// Return true if the string is not initialized.
-	Bool isEmpty() const 
+	Bool isEmpty() const
 	{
 		return m_ptr == nullptr || getLength() == 0;
 	}
 
-	Bool operator==(const CString& b) const 
+	Bool operator==(const CString& b) const
 	{
 		if(m_ptr == nullptr || b.m_ptr == nullptr)
 		{
@@ -136,12 +135,12 @@ public:
 		}
 	}
 
-	Bool operator!=(const CString& b) const 
+	Bool operator!=(const CString& b) const
 	{
 		return !((*this) == b);
 	}
 
-	Bool operator<(const CString& b) const 
+	Bool operator<(const CString& b) const
 	{
 		if(m_ptr == nullptr || b.m_ptr == nullptr)
 		{
@@ -153,7 +152,7 @@ public:
 		}
 	}
 
-	Bool operator<=(const CString& b) const 
+	Bool operator<=(const CString& b) const
 	{
 		if(m_ptr == nullptr || b.m_ptr == nullptr)
 		{
@@ -165,7 +164,7 @@ public:
 		}
 	}
 
-	Bool operator>(const CString& b) const 
+	Bool operator>(const CString& b) const
 	{
 		if(m_ptr == nullptr || b.m_ptr == nullptr)
 		{
@@ -177,7 +176,7 @@ public:
 		}
 	}
 
-	Bool operator>=(const CString& b) const 
+	Bool operator>=(const CString& b) const
 	{
 		if(m_ptr == nullptr || b.m_ptr == nullptr)
 		{
@@ -190,24 +189,24 @@ public:
 	}
 
 	/// Get the underlying C string.
-	const char* get() const 
+	const char* get() const
 	{
 		checkInit();
 		return m_ptr;
 	}
 
 	/// Get the string length.
-	U getLength() const 
+	U getLength() const
 	{
 		if(m_length == 0 && m_ptr != nullptr)
 		{
 			m_length = std::strlen(m_ptr);
 		}
-		
+
 		return m_length;
 	}
 
-	PtrSize find(const CString& cstr, PtrSize position = 0) const 
+	PtrSize find(const CString& cstr, PtrSize position = 0) const
 	{
 		checkInit();
 		ANKI_ASSERT(position < getLength());
@@ -265,15 +264,15 @@ private:
 	mutable U32 m_length = 0;
 
 	/// Constructor for friends
-	CString(const Char* ptr, U32 length) 
-	:	m_ptr(ptr),
-		m_length(length)
+	CString(const Char* ptr, U32 length)
+		: m_ptr(ptr)
+		, m_length(length)
 	{
 		checkInit();
 		ANKI_ASSERT(std::strlen(ptr) == length);
 	}
 
-	void checkInit() const 
+	void checkInit() const
 	{
 		ANKI_ASSERT(m_ptr != nullptr);
 	}
@@ -291,17 +290,17 @@ public:
 	static const PtrSize NPOS = MAX_PTR_SIZE;
 
 	/// Default constructor.
-	String() 
+	String()
 	{}
 
 	/// Move constructor.
-	String(String&& b) 
+	String(String&& b)
 	{
 		move(b);
 	}
 
 	/// Requires manual destruction.
-	~String() 
+	~String()
 	{}
 
 	/// Initialize using a const string.
@@ -332,52 +331,52 @@ public:
 	}
 
 	/// Move one string to this one.
-	String& operator=(String&& b) 
+	String& operator=(String&& b)
 	{
 		move(b);
 		return *this;
 	}
 
 	/// Return char at the specified position.
-	const Char& operator[](U pos) const 
+	const Char& operator[](U pos) const
 	{
 		checkInit();
 		return m_data[pos];
 	}
 
 	/// Return char at the specified position as a modifiable reference.
-	Char& operator[](U pos) 
+	Char& operator[](U pos)
 	{
 		checkInit();
 		return m_data[pos];
 	}
 
-	Iterator begin() 
+	Iterator begin()
 	{
 		checkInit();
 		return &m_data[0];
 	}
 
-	ConstIterator begin() const 
+	ConstIterator begin() const
 	{
 		checkInit();
 		return &m_data[0];
 	}
 
-	Iterator end() 
+	Iterator end()
 	{
 		checkInit();
 		return &m_data[m_data.getSize() - 1];
 	}
 
-	ConstIterator end() const 
+	ConstIterator end() const
 	{
 		checkInit();
 		return &m_data[m_data.getSize() - 1];
 	}
 
 	/// Return true if strings are equal
-	Bool operator==(const String& b) const 
+	Bool operator==(const String& b) const
 	{
 		checkInit();
 		b.checkInit();
@@ -385,13 +384,13 @@ public:
 	}
 
 	/// Return true if strings are not equal
-	Bool operator!=(const String& b) const 
+	Bool operator!=(const String& b) const
 	{
 		return !(*this == b);
 	}
 
 	/// Return true if this is less than b
-	Bool operator<(const String& b) const 
+	Bool operator<(const String& b) const
 	{
 		checkInit();
 		b.checkInit();
@@ -399,7 +398,7 @@ public:
 	}
 
 	/// Return true if this is less or equal to b
-	Bool operator<=(const String& b) const 
+	Bool operator<=(const String& b) const
 	{
 		checkInit();
 		b.checkInit();
@@ -407,7 +406,7 @@ public:
 	}
 
 	/// Return true if this is greater than b
-	Bool operator>(const String& b) const 
+	Bool operator>(const String& b) const
 	{
 		checkInit();
 		b.checkInit();
@@ -415,7 +414,7 @@ public:
 	}
 
 	/// Return true if this is greater or equal to b
-	Bool operator>=(const String& b) const 
+	Bool operator>=(const String& b) const
 	{
 		checkInit();
 		b.checkInit();
@@ -423,48 +422,48 @@ public:
 	}
 
 	/// Return true if strings are equal
-	Bool operator==(const CStringType& cstr) const 
+	Bool operator==(const CStringType& cstr) const
 	{
 		checkInit();
 		return std::strcmp(&m_data[0], cstr.get()) == 0;
 	}
 
 	/// Return true if strings are not equal
-	Bool operator!=(const CStringType& cstr) const 
+	Bool operator!=(const CStringType& cstr) const
 	{
 		return !(*this == cstr);
 	}
 
 	/// Return true if this is less than cstr.
-	Bool operator<(const CStringType& cstr) const 
+	Bool operator<(const CStringType& cstr) const
 	{
 		checkInit();
 		return std::strcmp(&m_data[0], cstr.get()) < 0;
 	}
 
 	/// Return true if this is less or equal to cstr.
-	Bool operator<=(const CStringType& cstr) const 
+	Bool operator<=(const CStringType& cstr) const
 	{
 		checkInit();
 		return std::strcmp(&m_data[0], cstr.get()) <= 0;
 	}
 
 	/// Return true if this is greater than cstr.
-	Bool operator>(const CStringType& cstr) const 
+	Bool operator>(const CStringType& cstr) const
 	{
 		checkInit();
 		return std::strcmp(&m_data[0], cstr.get()) > 0;
 	}
 
 	/// Return true if this is greater or equal to cstr.
-	Bool operator>=(const CStringType& cstr) const 
+	Bool operator>=(const CStringType& cstr) const
 	{
 		checkInit();
 		return std::strcmp(&m_data[0], cstr.get()) >= 0;
 	}
 
 	/// Return the string's length. It doesn't count the terminating character.
-	PtrSize getLength() const 
+	PtrSize getLength() const
 	{
 		auto size = m_data.getSize();
 		auto out = (size != 0) ? (size - 1) : 0;
@@ -473,7 +472,7 @@ public:
 	}
 
 	/// Return the CString.
-	CStringType toCString() const 
+	CStringType toCString() const
 	{
 		checkInit();
 		return CStringType(&m_data[0], getLength());
@@ -504,17 +503,17 @@ public:
 	void sprintf(TAllocator alloc, CString fmt, ...);
 
 	/// Return true if it's empty.
-	Bool isEmpty() const 
+	Bool isEmpty() const
 	{
 		return m_data.isEmpty();
 	}
 
 	/// Find a substring of this string.
 	/// @param[in] cstr The substring to search.
-	/// @param position Position of the first character in the string to be 
+	/// @param position Position of the first character in the string to be
 	///                 considered in the search.
 	/// @return A valid position if the string is found or NPOS if not found.
-	PtrSize find(const CStringType& cstr, PtrSize position = 0) const 
+	PtrSize find(const CStringType& cstr, PtrSize position = 0) const
 	{
 		checkInit();
 		return toCString().find(cstr, position);
@@ -522,10 +521,10 @@ public:
 
 	/// Find a substring of this string.
 	/// @param[in] str The substring to search.
-	/// @param position Position of the first character in the string to be 
+	/// @param position Position of the first character in the string to be
 	///                 considered in the search.
 	/// @return A valid position if the string is found or NPOS if not found.
-	PtrSize find(const String& str, PtrSize position) const 
+	PtrSize find(const String& str, PtrSize position) const
 	{
 		str.checkInit();
 		return find(str.toCString(), position);
@@ -575,19 +574,19 @@ public:
 	/// Create with allocator.
 	template<typename TAllocator>
 	StringAuto(TAllocator alloc)
-	:	Base(),
-		m_alloc(alloc)
+		: Base()
+		, m_alloc(alloc)
 	{}
 
 	/// Move constructor.
 	StringAuto(StringAuto&& b)
-	:	Base()
+		: Base()
 	{
 		move(b);
 	}
 
 	/// Automatic destruction.
-	~StringAuto() 
+	~StringAuto()
 	{
 		Base::destroy(m_alloc);
 	}
@@ -617,7 +616,7 @@ public:
 	}
 
 	/// Move one string to this one.
-	StringAuto& operator=(StringAuto&& b) 
+	StringAuto& operator=(StringAuto&& b)
 	{
 		move(b);
 		return *this;
@@ -664,4 +663,3 @@ private:
 
 #include "anki/util/String.inl.h"
 
-#endif

+ 4 - 6
include/anki/util/StringList.h

@@ -3,8 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#ifndef ANKI_UTIL_STRING_LIST_H
-#define ANKI_UTIL_STRING_LIST_H
+#pragma once
 
 #include "anki/util/String.h"
 #include "anki/util/List.h"
@@ -67,13 +66,13 @@ public:
 	/// Create using an allocator.
 	template<typename TAllocator>
 	StringListAuto(TAllocator alloc)
-	:	Base(),
-		m_alloc(alloc)
+		: Base()
+		, m_alloc(alloc)
 	{}
 
 	/// Move.
 	StringListAuto(StringListAuto&& b)
-	:	Base()
+		: Base()
 	{
 		move(b);
 	}
@@ -119,4 +118,3 @@ private:
 
 #include "anki/util/StringList.inl.h"
 
-#endif

+ 339 - 0
src/resource/ResourceFilesystem.cpp

@@ -0,0 +1,339 @@
+// Copyright (C) 2009-2015, Panagiotis Christopoulos Charitos.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include "anki/resource/ResourceFilesystem.h"
+#include "anki/util/Filesystem.h"
+#include <contrib/minizip/unzip.h>
+
+namespace anki {
+
+//==============================================================================
+// File classes                                                                =
+//==============================================================================
+
+/// C resource file
+class CResourceFile final: public ResourceFile
+{
+public:
+	File m_file;
+
+	CResourceFile(GenericMemoryPoolAllocator<U8> alloc)
+		: ResourceFile(alloc)
+	{}
+
+	ANKI_USE_RESULT Error read(void* buff, PtrSize size)
+	{
+		return m_file.read(buff, size);
+	}
+
+	ANKI_USE_RESULT Error readAllText(
+		GenericMemoryPoolAllocator<U8> alloc, String& out)
+	{
+		return m_file.readAllText(alloc, out);
+	}
+
+	ANKI_USE_RESULT Error readU32(U32& u)
+	{
+		return m_file.readU32(u);
+	}
+
+	ANKI_USE_RESULT Error readF32(F32& f)
+	{
+		return m_file.readF32(f);
+	}
+
+	ANKI_USE_RESULT Error seek(PtrSize offset, SeekOrigin origin)
+	{
+		return m_file.seek(offset, origin);
+	}
+};
+
+/// ZIP file
+class ZipResourceFile final: public ResourceFile
+{
+public:
+	unzFile m_archive = nullptr;
+	PtrSize m_size = 0;
+
+	ZipResourceFile(GenericMemoryPoolAllocator<U8> alloc)
+		: ResourceFile(alloc)
+	{}
+
+	~ZipResourceFile()
+	{
+		if(m_archive)
+		{
+			// It's open
+			unzClose(m_archive);
+			m_archive = nullptr;
+			m_size = 0;
+		}
+	}
+
+	ANKI_USE_RESULT Error open(
+		const CString& archive,
+		const CString& archivedFname)
+	{
+		// Open archive
+		m_archive = unzOpen(&archive[0]);
+		if(m_archive == nullptr)
+		{
+			ANKI_LOGE("Failed to open archive");
+			return ErrorCode::FILE_ACCESS;
+		}
+
+		// Locate archived
+		const int caseSensitive = 1;
+		if(unzLocateFile(m_archive, &archivedFname[0], caseSensitive) != UNZ_OK)
+		{
+			ANKI_LOGE("Failed to locate file in archive");
+			return ErrorCode::FILE_ACCESS;
+		}
+
+		// Open file
+		if(unzOpenCurrentFile(m_archive) != UNZ_OK)
+		{
+			ANKI_LOGE("unzOpenCurrentFile() failed");
+			return ErrorCode::FILE_ACCESS;
+		}
+
+		// Get size just in case
+		unz_file_info zinfo;
+		zinfo.uncompressed_size = 0;
+		unzGetCurrentFileInfo(
+			m_archive, &zinfo, nullptr, 0, nullptr, 0, nullptr, 0);
+		m_size = zinfo.uncompressed_size;
+		ANKI_ASSERT(m_size != 0);
+
+		return ErrorCode::NONE;
+	}
+
+	void close()
+	{
+		if(m_archive)
+		{
+			unzClose(m_archive);
+			m_archive = nullptr;
+			m_size = 0;
+		}
+	}
+
+	ANKI_USE_RESULT Error read(void* buff, PtrSize size)
+	{
+		I64 readSize = unzReadCurrentFile(m_archive, buff, size);
+
+		if(I64(size) != readSize)
+		{
+			ANKI_LOGE("File read failed");
+			return ErrorCode::FILE_ACCESS;
+		}
+
+		return ErrorCode::NONE;
+	}
+
+	ANKI_USE_RESULT Error readAllText(
+		GenericMemoryPoolAllocator<U8> alloc, String& out)
+	{
+		ANKI_ASSERT(m_size);
+		out.create(alloc, '?', m_size);
+		return read(&out[0], m_size);
+	}
+
+	ANKI_USE_RESULT Error readU32(U32& u)
+	{
+		// Assume machine and file have same endianness
+		ANKI_CHECK(read(&u, sizeof(u)));
+		return ErrorCode::NONE;
+	}
+
+	ANKI_USE_RESULT Error readF32(F32& u)
+	{
+		// Assume machine and file have same endianness
+		ANKI_CHECK(read(&u, sizeof(u)));
+		return ErrorCode::NONE;
+	}
+
+	ANKI_USE_RESULT Error seek(PtrSize offset, SeekOrigin origin)
+	{
+		// Rewind if needed
+		if(origin == SeekOrigin::BEGINNING)
+		{
+			if(unzCloseCurrentFile(m_archive) || unzOpenCurrentFile(m_archive))
+			{
+				ANKI_LOGE("Rewind failed");
+				return ErrorCode::FUNCTION_FAILED;
+			}
+		}
+
+		// Move forward by reading dummy data
+		Array<char, 128> buff;
+		while(offset != 0)
+		{
+			PtrSize toRead = min(offset, sizeof(buff));
+			ANKI_CHECK(read(&buff[0], toRead));
+			offset -= toRead;
+		}
+
+		return ErrorCode::NONE;
+	}
+};
+
+//==============================================================================
+// ResourceFilesystem                                                          =
+//==============================================================================
+
+//==============================================================================
+ResourceFilesystem::~ResourceFilesystem()
+{
+	for(Path& p : m_paths)
+	{
+		p.m_files.destroy(m_alloc);
+		p.m_path.destroy(m_alloc);
+	}
+
+	m_paths.destroy(m_alloc);
+}
+
+//==============================================================================
+Error ResourceFilesystem::addNewPath(const CString& path)
+{
+	static const CString extension(".ankizip");
+
+	auto pos = path.find(extension);
+	if(pos != CString::NPOS && pos == path.getLength() - extension.getLength())
+	{
+		// It's an archive
+
+		// Open
+		unzFile zfile = unzOpen(&path[0]);
+		if(!zfile)
+		{
+			ANKI_LOGE("Failed to open archive");
+			return ErrorCode::FILE_ACCESS;
+		}
+
+		// List files
+		if(unzGoToFirstFile(zfile) != UNZ_OK)
+		{
+			unzClose(zfile);
+			ANKI_LOGE("unzGoToFirstFile() failed. Empty archive?");
+			return ErrorCode::FILE_ACCESS;
+		}
+
+		Path p;
+		p.m_isArchive = true;
+		p.m_path.sprintf(m_alloc, "%s", &path[0]);
+
+		do
+		{
+			Array<char, 1024> filename;
+
+			unz_file_info info;
+			if(unzGetCurrentFileInfo(zfile, &info, &filename[0],
+				filename.getSize(), nullptr, 0, nullptr, 0) != UNZ_OK)
+			{
+				unzClose(zfile);
+				ANKI_LOGE("unzGetCurrentFileInfo() failed");
+				return ErrorCode::FILE_ACCESS;
+			}
+
+			// If compressed size is zero then it's a dir
+			if(info.uncompressed_size > 0)
+			{
+				p.m_files.pushBackSprintf(m_alloc, "%s", &filename[0]);
+			}
+		} while(unzGoToNextFile(zfile) == UNZ_OK);
+
+		m_paths.emplaceFront(m_alloc, std::move(p));
+		unzClose(zfile);
+	}
+	else
+	{
+		// It's simple directory
+
+		m_paths.emplaceFront(m_alloc, std::move(Path()));
+		m_paths.getBack().m_path.sprintf(m_alloc, "%s", &path[0]);
+		m_paths.getBack().m_isArchive = false;
+
+		ANKI_CHECK(walkDirectoryTree(path, this,
+			[](const CString& fname, void* ud, Bool isDir) -> Error
+		{
+			if(isDir)
+			{
+				return ErrorCode::NONE;
+			}
+
+			ResourceFilesystem* self = static_cast<ResourceFilesystem*>(ud);
+
+			Path& p = self->m_paths.getBack();
+			p.m_files.pushBackSprintf(self->m_alloc, "%s", &fname[0]);
+			return ErrorCode::NONE;
+		}));
+
+		if(m_paths.getBack().m_files.getSize() < 1)
+		{
+			ANKI_LOGE("Directory is empty: %s", &path[0]);
+			return ErrorCode::USER_DATA;
+		}
+	}
+
+	return ErrorCode::NONE;
+}
+
+//==============================================================================
+Error ResourceFilesystem::openFile(
+	const CString& ufilename, ResourceFilePtr& filePtr)
+{
+	// Search for the fname in reverse order
+	for(const Path& p : m_paths)
+	{
+		for(const String& filename : p.m_files)
+		{
+			if(filename != ufilename)
+			{
+				continue;
+			}
+
+			ResourceFile* rfile;
+			Error err = ErrorCode::NONE;
+
+			// Found
+			if(p.m_isArchive)
+			{
+				ZipResourceFile* file =
+					m_alloc.newInstance<ZipResourceFile>(m_alloc);
+				rfile = file;
+
+				err = file->open(p.m_path.toCString(), filename.toCString());
+			}
+			else
+			{
+				StringAuto newFname(m_alloc);
+				newFname.sprintf("%s/%s", &p.m_path[0], &filename[0]);
+
+				CResourceFile* file =
+					m_alloc.newInstance<CResourceFile>(m_alloc);
+				rfile = file;
+
+				err = file->m_file.open(&newFname[0], File::OpenFlag::READ);
+			}
+
+			if(err)
+			{
+				m_alloc.deleteInstance(rfile);
+				return err;
+			}
+
+			// Done
+			filePtr.reset(rfile);
+			return ErrorCode::NONE;
+		}
+	}
+
+	ANKI_LOGE("File not found: %s", &ufilename[0]);
+	return ErrorCode::USER_DATA;
+}
+
+} // end namespace anki

+ 11 - 11
src/util/File.cpp

@@ -96,7 +96,6 @@ Error File::open(const CString& filename, OpenFlag flags)
 	//
 	// Set endianess
 	//
-
 	if(!err)
 	{
 		// If the open() DIDN'T provided us the file endianess
@@ -163,13 +162,13 @@ Error File::openZipFile(
 {
 	if((flags & OpenFlag::WRITE) != OpenFlag::NONE)
 	{
-		// Cannot write inside archives
+		ANKI_LOGE("Cannot write inside archives");
 		return ErrorCode::FILE_ACCESS;
 	}
 
 	if((flags & OpenFlag::READ) == OpenFlag::NONE)
 	{
-		// Missing OpenFlag::READ flag
+		ANKI_LOGE("Missing OpenFlag::READ flag");
 		return ErrorCode::FILE_ACCESS;
 	}
 
@@ -177,7 +176,7 @@ Error File::openZipFile(
 	unzFile zfile = unzOpen(&archive[0]);
 	if(zfile == nullptr)
 	{
-		// Failed to open archive
+		ANKI_LOGE("Failed to open archive");
 		return ErrorCode::FILE_ACCESS;
 	}
 
@@ -186,7 +185,7 @@ Error File::openZipFile(
 	if(unzLocateFile(zfile, &archived[0], caseSensitive) != UNZ_OK)
 	{
 		unzClose(zfile);
-		// Failed to locate file in archive
+		ANKI_LOGE("Failed to locate file in archive");
 		return ErrorCode::FILE_ACCESS;
 	}
 
@@ -194,7 +193,7 @@ Error File::openZipFile(
 	if(unzOpenCurrentFile(zfile) != UNZ_OK)
 	{
 		unzClose(zfile);
-		// unzOpenCurrentFile failed
+		ANKI_LOGE("unzOpenCurrentFile() failed");
 		return ErrorCode::FILE_ACCESS;
 	}
 
@@ -219,13 +218,13 @@ Error File::openAndroidFile(const CString& filename, OpenFlag flags)
 {
 	if((flags & OpenFlag::WRITE) != OpenFlag::NONE)
 	{
-		// Cannot write inside archives
+		ANKI_LOGE("Cannot write inside archives");
 		return ErrorCode::FILE_ACCESS;
 	}
 
 	if((flags & OpenFlag::READ) != OpenFlag::NONE)
 	{
-		// Missing OpenFlag::READ flag"
+		ANKI_LOGE("Missing OpenFlag::READ flag");
 		return ErrorCode::FILE_ACCESS;
 	}
 
@@ -240,6 +239,7 @@ Error File::openAndroidFile(const CString& filename, OpenFlag flags)
 
 	if(m_file == nullptr)
 	{
+		ANKI_LOGE("AAssetManager_open() failed");
 		return ErrorCode::FILE_ACCESS;
 	}
 
@@ -551,11 +551,11 @@ Error File::seek(PtrSize offset, SeekOrigin origin)
 		if(!err)
 		{
 			// Move forward by reading dummy data
-			char buff[128];
+			Array<char, 128> buff;
 			while(!err && offset != 0)
 			{
-				PtrSize toRead = std::min(offset, sizeof(buff));
-				err = read(buff, toRead);
+				PtrSize toRead = min(offset, sizeof(buff));
+				err = read(&buff[0], toRead);
 				offset -= toRead;
 			}
 		}

+ 86 - 6
src/util/FilesystemPosix.cpp

@@ -3,13 +3,15 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include "anki/util/File.h"
+#include "anki/util/Filesystem.h"
 #include "anki/util/Assert.h"
+#include "anki/util/Thread.h"
 #include <cstring>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <cerrno>
+#include <ftw.h> // For walkDirectoryTree
 
 // Define PATH_MAX if needed
 #ifndef PATH_MAX
@@ -46,6 +48,84 @@ Bool directoryExists(const CString& filename)
 	}
 }
 
+//==============================================================================
+static Mutex walkDirMtx;
+static WalkDirectoryTreeCallback walkDirCallback = nullptr;
+static void* walkDirUserData = nullptr;
+static CString walkDirDir;
+
+static int ftwCallback(
+	const char* fpath,
+	const struct stat* /*sb*/,
+	int typeflag)
+{
+	Error err = ErrorCode::NONE;
+
+	if(typeflag == FTW_F || typeflag == FTW_D)
+	{
+		CString path(fpath);
+
+		// Don't list yourself
+		if(ANKI_UNLIKELY(path == walkDirDir))
+		{
+			return static_cast<int>(ErrorCode::NONE);
+		}
+
+		// Remove the directory from the fpath
+		auto pos = path.find(walkDirDir);
+		ANKI_ASSERT(pos == 0);
+		ANKI_ASSERT(path.getLength() - walkDirDir.getLength() > 0);
+		fpath = fpath + walkDirDir.getLength() + 1;
+
+		// Call the callback
+		err = walkDirCallback(fpath, walkDirUserData, typeflag != FTW_F);
+	}
+	else
+	{
+		ANKI_LOGW("Permission denied %s\n", fpath);
+	}
+
+	return err._getCodeInt();
+}
+
+Error walkDirectoryTree(
+	const CString& dir0,
+	void* userData,
+	WalkDirectoryTreeCallback callback)
+{
+	ANKI_ASSERT(callback != nullptr);
+
+	// Copy dir to remove the backslash
+	CString dir = dir0;
+	Array<char, PATH_MAX> dirNoSlash;
+	if(dir[dir.getLength() - 1] == '/')
+	{
+		ANKI_ASSERT(dir.getLength() < dirNoSlash.getSize());
+		strcpy(&dirNoSlash[0], &dir[0]);
+		dirNoSlash[dir.getLength() - 1] = '\0';
+		dir = CString(&dirNoSlash[0]);
+	}
+
+	// Continue
+	LockGuard<Mutex> lock(walkDirMtx);
+
+	walkDirCallback = callback;
+	walkDirUserData = userData;
+	walkDirDir = dir;
+	const int MAX_OPEN_FILE_DESCRS = 1;
+
+	int ierr = ftw(&dir[0], &ftwCallback, MAX_OPEN_FILE_DESCRS);
+	Error err = static_cast<ErrorCode>(ierr);
+
+	if(ierr < 0)
+	{
+		ANKI_LOGE("%s\n", strerror(errno));
+		err = ErrorCode::FUNCTION_FAILED;
+	}
+
+	return err;
+}
+
 //==============================================================================
 Error removeDirectory(const CString& dirname)
 {
@@ -53,27 +133,27 @@ Error removeDirectory(const CString& dirname)
 	struct dirent* entry;
 	char path[PATH_MAX];
 
-	if(path == nullptr) 
+	if(path == nullptr)
 	{
 		ANKI_LOGE("Out of memory error");
 		return ErrorCode::FUNCTION_FAILED;
 	}
 
 	dir = opendir(dirname.get());
-	if(dir == nullptr) 
+	if(dir == nullptr)
 	{
 		ANKI_LOGE("opendir() failed");
 		return ErrorCode::FUNCTION_FAILED;
 	}
 
-	while((entry = readdir(dir)) != nullptr) 
+	while((entry = readdir(dir)) != nullptr)
 	{
-		if(strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) 
+		if(strcmp(entry->d_name, ".") && strcmp(entry->d_name, ".."))
 		{
 			std::snprintf(
 				path, (size_t)PATH_MAX, "%s/%s", dirname.get(), entry->d_name);
 
-			if(entry->d_type == DT_DIR) 
+			if(entry->d_type == DT_DIR)
 			{
 				Error err = removeDirectory(CString(path));
 				if(err)

BIN
tests/data/dir.ankizip


+ 0 - 0
tests/data/dir/a.txt


+ 1 - 0
tests/data/dir/subdir0/hello.txt

@@ -0,0 +1 @@
+hello

+ 7 - 0
tests/framework/Framework.h

@@ -205,6 +205,13 @@ extern void deleteTesterSingleton();
 #define ANKI_TEST_EXPECT_NO_ERR(x_) \
 	ANKI_TEST_EXPECT_EQ_IMPL(__FILE__, __LINE__, __func__, x_, ErrorCode::NONE)
 
+/// Check error code.
+#define ANKI_TEST_EXPECT_ANY_ERR(x_) \
+	ANKI_TEST_EXPECT_NEQ_IMPL(__FILE__, __LINE__, __func__, x_, ErrorCode::NONE)
+
+/// Check error code.
+#define ANKI_TEST_EXPECT_ERR(x_, y_) \
+	ANKI_TEST_EXPECT_EQ_IMPL(__FILE__, __LINE__, __func__, x_, y_)
 
 } // end namespace anki
 

+ 38 - 0
tests/resource/ResourceFilesystem.cpp

@@ -0,0 +1,38 @@
+// Copyright (C) 2009-2015, Panagiotis Christopoulos Charitos.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include "tests/framework/Framework.h"
+#include "anki/resource/ResourceFilesystem.h"
+
+namespace anki {
+
+//==============================================================================
+ANKI_TEST(Resource, ResourceFilesystem)
+{
+	printf("Test requires the data dir\n");
+
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+	ResourceFilesystem fs(alloc);
+
+	{
+		ANKI_TEST_EXPECT_NO_ERR(fs.addNewPath("data/dir/../dir/"));
+		ResourceFilePtr file;
+		ANKI_TEST_EXPECT_NO_ERR(fs.openFile("subdir0/hello.txt", file));
+		StringAuto txt(alloc);
+		ANKI_TEST_EXPECT_NO_ERR(file->readAllText(alloc, txt));
+		ANKI_TEST_EXPECT_EQ(txt, "hello\n");
+	}
+
+	{
+		ANKI_TEST_EXPECT_NO_ERR(fs.addNewPath("./data/dir.ankizip"));
+		ResourceFilePtr file;
+		ANKI_TEST_EXPECT_NO_ERR(fs.openFile("subdir0/hello.txt", file));
+		StringAuto txt(alloc);
+		ANKI_TEST_EXPECT_NO_ERR(file->readAllText(alloc, txt));
+		ANKI_TEST_EXPECT_EQ(txt, "hell\n");
+	}
+}
+
+} // end namespace anki

+ 31 - 0
tests/util/Filesystem.cpp

@@ -59,3 +59,34 @@ ANKI_TEST(Util, HomeDir)
 	printf("home dir %s\n", &out[0]);
 	ANKI_TEST_EXPECT_GT(out.getLength(), 0);
 }
+
+ANKI_TEST(Util, WalkDir)
+{
+	// Walk crnt dir
+	U32 dirCount = 0;
+
+	ANKI_TEST_EXPECT_NO_ERR(walkDirectoryTree(".", &dirCount,
+		[](const CString& fname, void* pDirCount, Bool isDir) -> Error
+		{
+			if(isDir)
+			{
+				++(*static_cast<U32*>(pDirCount));
+			}
+
+			return ErrorCode::NONE;
+		}));
+
+	ANKI_TEST_EXPECT_GT(dirCount, 0);
+
+	// Test error
+	dirCount = 0;
+	ANKI_TEST_EXPECT_ERR(walkDirectoryTree(".", &dirCount,
+		[](const CString& fname, void* pDirCount, Bool isDir) -> Error
+		{
+			++(*static_cast<U32*>(pDirCount));
+			return ErrorCode::FUNCTION_FAILED;
+		}),
+		ErrorCode::FUNCTION_FAILED);
+
+	ANKI_TEST_EXPECT_EQ(dirCount, 1);
+}