1/*-------------------------------------------------------------------------
2 * drawElements C++ Base Library
3 * -----------------------------
4 *
5 * Copyright 2014 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Filesystem path class.
22 *//*--------------------------------------------------------------------*/
23
24#include "deFilePath.hpp"
25
26#include <vector>
27#include <stdexcept>
28
29#include <sys/stat.h>
30#include <sys/types.h>
31
32#if (DE_OS == DE_OS_WIN32)
33#	define VC_EXTRALEAN
34#	define WIN32_LEAN_AND_MEAN
35#	define NOMINMAX
36#	include <windows.h>
37#endif
38
39using std::string;
40
41namespace de
42{
43
44#if (DE_OS == DE_OS_WIN32)
45	const std::string FilePath::separator = "\\";
46#else
47	const std::string FilePath::separator = "/";
48#endif
49
50FilePath::FilePath (const std::vector<std::string>& components)
51{
52	for (size_t ndx = 0; ndx < components.size(); ndx++)
53	{
54		if (!m_path.empty() && !isSeparator(m_path[m_path.size()-1]))
55			m_path += separator;
56		m_path += components[ndx];
57	}
58}
59
60void FilePath::split (std::vector<std::string>& components) const
61{
62	components.clear();
63
64	int curCompStart = 0;
65	int pos;
66
67	if (isWinNetPath())
68		components.push_back(separator + separator);
69	else if (isRootPath() && !beginsWithDrive())
70		components.push_back(separator);
71
72	for (pos = 0; pos < (int)m_path.length(); pos++)
73	{
74		const char c = m_path[pos];
75
76		if (isSeparator(c))
77		{
78			if (pos - curCompStart > 0)
79				components.push_back(m_path.substr(curCompStart, pos - curCompStart));
80
81			curCompStart = pos+1;
82		}
83	}
84
85	if (pos - curCompStart > 0)
86		components.push_back(m_path.substr(curCompStart, pos - curCompStart));
87}
88
89FilePath FilePath::join (const std::vector<std::string>& components)
90{
91	return FilePath(components);
92}
93
94FilePath& FilePath::normalize (void)
95{
96	std::vector<std::string>	components;
97	std::vector<std::string>	reverseNormalizedComponents;
98
99	split(components);
100
101	m_path = "";
102
103	int numUp = 0;
104
105	// Do in reverse order and eliminate any . or .. components
106	for (int ndx = (int)components.size()-1; ndx >= 0; ndx--)
107	{
108		const std::string& comp = components[ndx];
109		if (comp == "..")
110			numUp += 1;
111		else if (comp == ".")
112			continue;
113		else if (numUp > 0)
114			numUp -= 1; // Skip part
115		else
116			reverseNormalizedComponents.push_back(comp);
117	}
118
119	if (isAbsolutePath() && numUp > 0)
120		throw std::runtime_error("Cannot normalize path: invalid path");
121
122	// Prepend necessary ".." components
123	while (numUp--)
124		reverseNormalizedComponents.push_back("..");
125
126	if (reverseNormalizedComponents.empty() && components.back() == ".")
127		reverseNormalizedComponents.push_back("."); // Composed of "." components only
128
129	*this = join(std::vector<std::string>(reverseNormalizedComponents.rbegin(), reverseNormalizedComponents.rend()));
130
131	return *this;
132}
133
134FilePath FilePath::normalize (const FilePath& path)
135{
136	return FilePath(path).normalize();
137}
138
139std::string FilePath::getBaseName (void) const
140{
141	std::vector<std::string> components;
142	split(components);
143	return !components.empty() ? components[components.size()-1] : std::string("");
144}
145
146std::string	FilePath::getDirName (void) const
147{
148	std::vector<std::string> components;
149	split(components);
150	if (components.size() > 1)
151	{
152		components.pop_back();
153		return FilePath(components).getPath();
154	}
155	else if (isAbsolutePath())
156		return separator;
157	else
158		return std::string(".");
159}
160
161std::string FilePath::getFileExtension (void) const
162{
163	std::string baseName = getBaseName();
164	size_t dotPos = baseName.find_last_of('.');
165	if (dotPos == std::string::npos)
166		return std::string("");
167	else
168		return baseName.substr(dotPos+1);
169}
170
171bool FilePath::exists (void) const
172{
173	FilePath	normPath	= FilePath::normalize(*this);
174	struct		stat		st;
175	int			result		= stat(normPath.getPath(), &st);
176	return result == 0;
177}
178
179FilePath::Type FilePath::getType (void) const
180{
181	FilePath	normPath	= FilePath::normalize(*this);
182	struct		stat		st;
183	int			result		= stat(normPath.getPath(), &st);
184
185	if (result != 0)
186		return TYPE_UNKNOWN;
187
188	int type = st.st_mode & S_IFMT;
189	if (type == S_IFREG)
190		return TYPE_FILE;
191	else if (type == S_IFDIR)
192		return TYPE_DIRECTORY;
193	else
194		return TYPE_UNKNOWN;
195}
196
197bool FilePath::beginsWithDrive (void) const
198{
199	for (int ndx = 0; ndx < (int)m_path.length(); ndx++)
200	{
201		if (m_path[ndx] == ':' && ndx+1 < (int)m_path.length() && isSeparator(m_path[ndx+1]))
202			return true; // First part is drive letter.
203		if (isSeparator(m_path[ndx]))
204			return false;
205	}
206	return false;
207}
208
209bool FilePath::isAbsolutePath (void) const
210{
211	return isRootPath() || isWinNetPath() || beginsWithDrive();
212}
213
214void FilePath_selfTest (void)
215{
216	DE_TEST_ASSERT(!FilePath(".").isAbsolutePath());
217	DE_TEST_ASSERT(!FilePath("..\\foo").isAbsolutePath());
218	DE_TEST_ASSERT(!FilePath("foo").isAbsolutePath());
219	DE_TEST_ASSERT(FilePath("\\foo/bar").isAbsolutePath());
220	DE_TEST_ASSERT(FilePath("/foo").isAbsolutePath());
221	DE_TEST_ASSERT(FilePath("\\").isAbsolutePath());
222	DE_TEST_ASSERT(FilePath("\\\\net\\loc").isAbsolutePath());
223	DE_TEST_ASSERT(FilePath("C:\\file.txt").isAbsolutePath());
224	DE_TEST_ASSERT(FilePath("c:/file.txt").isAbsolutePath());
225
226	DE_TEST_ASSERT(string(".") == FilePath(".//.").normalize().getPath());
227	DE_TEST_ASSERT(string(".") == FilePath(".").normalize().getPath());
228	DE_TEST_ASSERT((string("..") + FilePath::separator + "test") == FilePath("foo/../bar/../../test").normalize().getPath());
229	DE_TEST_ASSERT((FilePath::separator + "foo" + FilePath::separator + "foo.txt") == FilePath("/foo\\bar/..\\dir\\..\\foo.txt").normalize().getPath());
230	DE_TEST_ASSERT((string("c:") + FilePath::separator + "foo" + FilePath::separator + "foo.txt") == FilePath("c:/foo\\bar/..\\dir\\..\\foo.txt").normalize().getPath());
231	DE_TEST_ASSERT((FilePath::separator + FilePath::separator + "foo" + FilePath::separator + "foo.txt") == FilePath("\\\\foo\\bar/..\\dir\\..\\foo.txt").normalize().getPath());
232
233	DE_TEST_ASSERT(FilePath("foo/bar"		).getBaseName()	== "bar");
234	DE_TEST_ASSERT(FilePath("foo/bar/"		).getBaseName()	== "bar");
235	DE_TEST_ASSERT(FilePath("foo\\bar"		).getBaseName()	== "bar");
236	DE_TEST_ASSERT(FilePath("foo\\bar\\"	).getBaseName()	== "bar");
237	DE_TEST_ASSERT(FilePath("foo/bar"		).getDirName()	== "foo");
238	DE_TEST_ASSERT(FilePath("foo/bar/"		).getDirName()	== "foo");
239	DE_TEST_ASSERT(FilePath("foo\\bar"		).getDirName()	== "foo");
240	DE_TEST_ASSERT(FilePath("foo\\bar\\"	).getDirName()	== "foo");
241	DE_TEST_ASSERT(FilePath("/foo/bar/baz"	).getDirName()	== FilePath::separator + "foo" + FilePath::separator + "bar");
242}
243
244static void createDirectoryImpl (const char* path)
245{
246#if (DE_OS == DE_OS_WIN32)
247	if (!CreateDirectory(path, DE_NULL))
248		throw std::runtime_error("Failed to create directory");
249#elif (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_OSX) || (DE_OS == DE_OS_IOS) || (DE_OS == DE_OS_ANDROID) || (DE_OS == DE_OS_SYMBIAN) || (DE_OS == DE_OS_QNX) || (DE_OS == DE_OS_FUCHSIA)
250	if (mkdir(path, 0777) != 0)
251		throw std::runtime_error("Failed to create directory");
252#else
253#	error Implement createDirectoryImpl() for your platform.
254#endif
255}
256
257void createDirectory (const char* path)
258{
259	FilePath	dirPath		= FilePath::normalize(path);
260	FilePath	parentPath	(dirPath.getDirName());
261
262	if (dirPath.exists())
263		throw std::runtime_error("Destination exists already");
264	else if (!parentPath.exists())
265		throw std::runtime_error("Parent directory doesn't exist");
266	else if (parentPath.getType() != FilePath::TYPE_DIRECTORY)
267		throw std::runtime_error("Parent is not directory");
268
269	createDirectoryImpl(path);
270}
271
272void createDirectoryAndParents (const char* path)
273{
274	std::vector<std::string>	createPaths;
275	FilePath					curPath		(path);
276
277	if (curPath.exists())
278		throw std::runtime_error("Destination exists already");
279
280	while (!curPath.exists())
281	{
282		createPaths.push_back(curPath.getPath());
283
284		std::string parent = curPath.getDirName();
285		DE_CHECK_RUNTIME_ERR(parent != curPath.getPath());
286		curPath = FilePath(parent);
287	}
288
289	// Create in reverse order.
290	for (std::vector<std::string>::const_reverse_iterator parentIter = createPaths.rbegin(); parentIter != createPaths.rend(); parentIter++)
291		createDirectory(parentIter->c_str());
292}
293
294} // de
295