1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2008-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5 //
6 // Any parts of this program derived from the xMule, lMule or eMule project,
7 // or contributed by third-party developers are copyrighted by their
8 // respective authors.
9 //
10 // This program is free software; you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation; either version 2 of the License, or
13 // (at your option) any later version.
14 //
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
23 //
24 
25 #include "Path.h"
26 #include "StringFunctions.h"		// Needed for filename2char()
27 
28 #include <wx/file.h>
29 #if defined __WINDOWS__ || defined __IRIX__
30 #	include <wx/ffile.h>
31 #endif
32 #include <wx/utils.h>
33 #include <wx/filename.h>
34 #include <algorithm>	// Needed for std::min
35 
36 
37 // Windows has case-insensitive paths, so we use a
38 // case-insensitive cmp for that platform. TODO:
39 // Perhaps it would be better to simply lowercase
40 // m_filesystem in the constructor ...
41 #ifdef __WINDOWS__
42 	#define PATHCMP(a, b)		wxStricmp(a, b)
43 	#define PATHNCMP(a, b, n)	wxStrnicmp(a, b, n)
44 #else
45 	#define PATHCMP(a, b)		wxStrcmp(a, b)
46 	#define PATHNCMP(a, b, n)	wxStrncmp(a, b, n)
47 #endif
48 
49 
50 ////////////////////////////////////////////////////////////
51 // Helper functions
52 
53 
54 /** Creates a deep copy of the string, avoiding its ref. counting. */
DeepCopy(const wxString & str)55 inline wxString DeepCopy(const wxString& str)
56 {
57 	return wxString(str.c_str(), str.Length());
58 }
59 
60 
Demangle(const wxCharBuffer & fn,const wxString & filename)61 static wxString Demangle(const wxCharBuffer& fn, const wxString& filename)
62 {
63 	wxString result = wxConvUTF8.cMB2WC(fn);
64 
65 	// FIXME: Is this actually needed for osx/msw?
66 	if (!result) {
67 		// We only try to further demangle if the current locale is
68 		// UTF-8, C or POSIX. This is because in any other case, the
69 		// current locale is probably the best choice for printing.
70 		static wxFontEncoding enc = wxLocale::GetSystemEncoding();
71 
72 		switch (enc) {
73 			// SYSTEM is needed for ANSI encodings such as
74 			// "POSIX" and "C", which are only 7bit.
75 			case wxFONTENCODING_SYSTEM:
76 			case wxFONTENCODING_UTF8:
77 				result = wxConvISO8859_1.cMB2WC(fn);
78 				break;
79 
80 			default:
81 				// Nothing to do, the filename is probably Ok.
82 				result = DeepCopy(filename);
83 		}
84 	}
85 
86 	return result;
87 }
88 
89 
90 /** Splits a full path into its path and filename component. */
DoSplitPath(const wxString & strPath,wxString * path,wxString * name)91 inline void DoSplitPath(const wxString& strPath, wxString* path, wxString* name)
92 {
93 	bool hasExt = false;
94 	wxString ext, vol;
95 
96 	wxString* pVol = (path ? &vol : NULL);
97 	wxString* pExt = (name ? &ext : NULL);
98 
99 	wxFileName::SplitPath(strPath, pVol, path, name, pExt, &hasExt);
100 
101 	if (hasExt && pExt) {
102 		*name += wxT(".") + ext;
103 	}
104 
105 	if (path && vol.Length()) {
106 		*path = vol + wxFileName::GetVolumeSeparator() + *path;
107 	}
108 }
109 
110 
111 /** Removes invalid chars from a filename. */
DoCleanup(const wxString & filename,bool keepSpaces,bool isFAT32)112 static wxString DoCleanup(const wxString& filename, bool keepSpaces, bool isFAT32)
113 {
114 	wxString result;
115 	for (size_t i = 0; i < filename.Length(); i++) {
116 		const wxChar c = filename[i];
117 
118 		switch (c) {
119 			case wxT('/'):
120 				continue;
121 
122 			case wxT('\"'):
123 			case wxT('*'):
124 			case wxT('<'):
125 			case wxT('>'):
126 			case wxT('?'):
127 			case wxT('|'):
128 			case wxT('\\'):
129 			case wxT(':'):
130 				if (isFAT32) {
131 					continue;
132 				}
133 
134 			/* fall through */
135 			default:
136 				if ((c == wxT(' ')) && !keepSpaces) {
137 					result += wxT("%20");
138 				} else if (c >= 32) {
139 					// Many illegal for filenames in windows
140 					// below the 32th char (which is space).
141 					result += filename[i];
142 				}
143 		}
144 	}
145 
146 	return result;
147 }
148 
149 
150 /** Does the actual work of adding a postfix ... */
DoAddPostfix(const wxString & src,const wxString & postfix)151 static wxString DoAddPostfix(const wxString& src, const wxString& postfix)
152 {
153 	wxFileName fn(src);
154 
155 	fn.SetName(fn.GetName() + postfix);
156 
157 	return fn.GetFullPath();
158 }
159 
160 /** Removes the last extension of a filename. */
DoRemoveExt(const wxString & path)161 static wxString DoRemoveExt(const wxString& path)
162 {
163 	// Using wxFilename which handles paths, etc.
164 	wxFileName tmp(path);
165 	tmp.ClearExt();
166 
167 	return tmp.GetFullPath();
168 }
169 
170 
171 /** Readies a path for use with wxAccess.. */
DoCleanPath(const wxString & path)172 static wxString DoCleanPath(const wxString& path)
173 {
174 #ifdef __WINDOWS__
175 	// stat fails on windows if there are trailing path-separators.
176 	wxString cleanPath = StripSeparators(path, wxString::trailing);
177 
178 	// Root paths must end with a separator (X:\ rather than X:).
179 	// See comments in wxDirExists.
180 	if ((cleanPath.Length() == 2) && (cleanPath.Last() == wxT(':'))) {
181 		cleanPath += wxFileName::GetPathSeparator();
182 	}
183 
184 	return cleanPath;
185 #else
186 	return path;
187 #endif
188 }
189 
190 
191 /** Returns true if the two paths are equal. */
IsSameAs(const wxString & a,const wxString & b)192 static bool IsSameAs(const wxString& a, const wxString& b)
193 {
194 	// Cache the current directory
195 	const wxString cwd = wxGetCwd();
196 
197 	// We normalize everything, except env. variables, which
198 	// can cause problems when the string is not encodable
199 	// using wxConvLibc which wxWidgets uses for the purpose.
200 	const int flags = (wxPATH_NORM_ALL | wxPATH_NORM_CASE) & ~wxPATH_NORM_ENV_VARS;
201 
202 	// Let wxFileName handle the tricky stuff involved in actually
203 	// comparing two paths ... Currently, a path ending with a path-
204 	// seperator will be unequal to the same path without a path-
205 	// seperator, which is probably for the best, but can could
206 	// lead to some unexpected behavior.
207 	wxFileName fn1(a);
208 	wxFileName fn2(b);
209 
210 	fn1.Normalize(flags, cwd);
211 	fn2.Normalize(flags, cwd);
212 
213 	return (fn1.GetFullPath() == fn2.GetFullPath());
214 }
215 
216 
217 ////////////////////////////////////////////////////////////
218 // CPath implementation
219 
CPath()220 CPath::CPath()
221 {
222 }
223 
224 
CPath(const wxString & filename)225 CPath::CPath(const wxString& filename)
226 {
227 	// Equivalent to the default constructor ...
228 	if (!filename) {
229 		return;
230 	}
231 
232 	wxCharBuffer fn = filename2char(filename);
233 	if (fn.data()) {
234 		// Filename is valid in the current locale. This means that
235 		// it either originated from a (wx)system-call, or from a
236 		// user with a properly setup system.
237 		m_filesystem = DeepCopy(filename);
238 		m_printable  = Demangle(fn, filename);
239 	} else {
240 		// It's not a valid filename in the current locale, so we'll
241 		// have to do some magic. This ensures that the filename is
242 		// saved as UTF8, even if the system is not unicode enabled,
243 		// preserving the original filename till the user has fixed
244 		// his system ...
245 #ifdef __WINDOWS__
246 		// Magic fails on Windows where we always work with wide char file names.
247 		m_filesystem = DeepCopy(filename);
248 		m_printable = m_filesystem;
249 #else
250 		fn = filename.utf8_str();
251 		m_filesystem = wxConvFile.cMB2WC(fn);
252 
253 		// There's no need to try to unmangle the filename here.
254 		m_printable = DeepCopy(filename);
255 #endif
256 	}
257 
258 	wxASSERT(m_filesystem.Length());
259 	wxASSERT(m_printable.Length());
260 }
261 
262 
CPath(const CPath & other)263 CPath::CPath(const CPath& other)
264 	: m_printable(DeepCopy(other.m_printable))
265 	, m_filesystem(DeepCopy(other.m_filesystem))
266 {}
267 
268 
269 
FromUniv(const wxString & path)270 CPath CPath::FromUniv(const wxString& path)
271 {
272 	wxCharBuffer fn = path.mb_str(wxConvISO8859_1);
273 	return CPath(wxConvFile.cMB2WC(fn));
274 }
275 
276 
ToUniv(const CPath & path)277 wxString CPath::ToUniv(const CPath& path)
278 {
279 	// The logic behind this is that by saving the filename
280 	// as a raw bytestream, we can always recreate the on-disk filename,
281 	// as if we had read it using wx functions.
282 	wxCharBuffer fn = path.m_filesystem.mb_str(wxConvFile);
283 	return wxConvISO8859_1.cMB2WC(fn);
284 }
285 
286 
operator =(const CPath & other)287 CPath& CPath::operator=(const CPath& other)
288 {
289 	if (this != &other) {
290 		m_printable = DeepCopy(other.m_printable);
291 		m_filesystem = DeepCopy(other.m_filesystem);
292 	}
293 
294 	return *this;
295 }
296 
297 
operator ==(const CPath & other) const298 bool CPath::operator==(const CPath& other) const
299 {
300 	return ::IsSameAs(m_filesystem, other.m_filesystem);
301 }
302 
303 
operator !=(const CPath & other) const304 bool CPath::operator!=(const CPath& other) const
305 {
306 	return !(*this == other);
307 }
308 
309 
operator <(const CPath & other) const310 bool CPath::operator<(const CPath& other) const
311 {
312 	return PATHCMP(m_filesystem.c_str(), other.m_filesystem.c_str()) < 0;
313 }
314 
315 
IsOk() const316 bool CPath::IsOk() const
317 {
318 	// Something is very wrong if one of the two is empty.
319 	return m_printable.Length() && m_filesystem.Length();
320 }
321 
322 
FileExists() const323 bool CPath::FileExists() const
324 {
325 	return wxFileName::FileExists(m_filesystem);
326 }
327 
328 
DirExists() const329 bool CPath::DirExists() const
330 {
331 	return wxFileName::DirExists(DoCleanPath(m_filesystem));
332 }
333 
334 
IsDir(EAccess mode) const335 bool CPath::IsDir(EAccess mode) const
336 {
337 	wxString path = DoCleanPath(m_filesystem);
338 	if (!wxFileName::DirExists(path)) {
339 		return false;
340 	} else if ((mode & writable) && !wxIsWritable(path)) {
341 		return false;
342 	} else if ((mode & readable) && !wxIsReadable(path)) {
343 		return false;
344 	}
345 
346 	return true;
347 }
348 
349 
IsFile(EAccess mode) const350 bool CPath::IsFile(EAccess mode) const
351 {
352 	if (!wxFileName::FileExists(m_filesystem)) {
353 		return false;
354 	} else if ((mode & writable) && !wxIsWritable(m_filesystem)) {
355 		return false;
356 	} else if ((mode & readable) && !wxIsReadable(m_filesystem)) {
357 		return false;
358 	}
359 
360 	return true;
361 }
362 
363 
GetRaw() const364 wxString CPath::GetRaw() const
365 {
366 	// Copy as c-strings to ensure that the CPath objects can safely
367 	// be passed across threads (avoiding wxString ref. counting).
368 	return DeepCopy(m_filesystem);
369 }
370 
371 
GetPrintable() const372 wxString CPath::GetPrintable() const
373 {
374 	// Copy as c-strings to ensure that the CPath objects can safely
375 	// be passed across threads (avoiding wxString ref. counting).
376 	return DeepCopy(m_printable);
377 }
378 
379 
GetExt() const380 wxString CPath::GetExt() const
381 {
382 	return wxFileName(m_filesystem).GetExt();
383 }
384 
385 
GetPath() const386 CPath CPath::GetPath() const
387 {
388 	CPath path;
389 	::DoSplitPath(m_printable, &path.m_printable, NULL);
390 	::DoSplitPath(m_filesystem, &path.m_filesystem, NULL);
391 
392 	return path;
393 }
394 
395 
GetFullName() const396 CPath CPath::GetFullName() const
397 {
398 	CPath path;
399 	::DoSplitPath(m_printable, NULL, &path.m_printable);
400 	::DoSplitPath(m_filesystem, NULL, &path.m_filesystem);
401 
402 	return path;
403 
404 }
405 
406 
GetFileSize() const407 sint64 CPath::GetFileSize() const
408 {
409 	if (FileExists()) {
410 		wxFile f(m_filesystem);
411 		if (f.IsOpened()) {
412 			return f.Length();
413 		}
414 	}
415 
416 	return wxInvalidOffset;
417 }
418 
419 
IsSameDir(const CPath & other) const420 bool CPath::IsSameDir(const CPath& other) const
421 {
422 	wxString a = m_filesystem;
423 	wxString b = other.m_filesystem;
424 
425 	// This check is needed to avoid trouble in the
426 	// case where one path is empty, and the other
427 	// points to the root dir.
428 	if (a.Length() && b.Length()) {
429 		a = StripSeparators(a, wxString::trailing);
430 		b = StripSeparators(b, wxString::trailing);
431 	}
432 
433 	return ::IsSameAs(a, b);
434 }
435 
436 
JoinPaths(const CPath & other) const437 CPath CPath::JoinPaths(const CPath& other) const
438 {
439 	if (!IsOk()) {
440 		return CPath(other);
441 	} else if (!other.IsOk()) {
442 		return CPath(*this);
443 	}
444 
445 	CPath joinedPath;
446 	// DeepCopy shouldn't be needed, as JoinPaths results in the creation of a new string.
447 	joinedPath.m_printable = ::JoinPaths(m_printable, other.m_printable);
448 	joinedPath.m_filesystem = ::JoinPaths(m_filesystem, other.m_filesystem);
449 
450 	return joinedPath;
451 }
452 
453 
Cleanup(bool keepSpaces,bool isFAT32) const454 CPath CPath::Cleanup(bool keepSpaces, bool isFAT32) const
455 {
456 	CPath result;
457 	result.m_printable = ::DoCleanup(m_printable, keepSpaces, isFAT32);
458 	result.m_filesystem = ::DoCleanup(m_filesystem, keepSpaces, isFAT32);
459 
460 	return result;
461 }
462 
463 
AddPostfix(const wxString & postfix) const464 CPath CPath::AddPostfix(const wxString& postfix) const
465 {
466 	wxASSERT(postfix.IsAscii());
467 
468 	CPath result;
469 	result.m_printable = ::DoAddPostfix(m_printable, postfix);
470 	result.m_filesystem = ::DoAddPostfix(m_filesystem, postfix);
471 
472 	return result;
473 }
474 
475 
AppendExt(const wxString & ext) const476 CPath CPath::AppendExt(const wxString& ext) const
477 {
478 	wxASSERT(ext.IsAscii());
479 
480 	// Though technically, and empty extension would simply
481 	// be another . at the end of the filename, we ignore them.
482 	if (ext.IsEmpty()) {
483 		return *this;
484 	}
485 
486 	CPath result(*this);
487 	if (ext[0] == wxT('.')) {
488 		result.m_printable << ext;
489 		result.m_filesystem << ext;
490 	} else {
491 		result.m_printable << wxT(".") << ext;
492 		result.m_filesystem << wxT(".") << ext;
493 	}
494 
495 	return result;
496 }
497 
498 
RemoveExt() const499 CPath CPath::RemoveExt() const
500 {
501 	CPath result;
502 	result.m_printable = DoRemoveExt(m_printable);
503 	result.m_filesystem = DoRemoveExt(m_filesystem);
504 
505 	return result;
506 }
507 
508 
RemoveAllExt() const509 CPath CPath::RemoveAllExt() const
510 {
511 	CPath last, current = RemoveExt();
512 
513 	// Loop until all extensions are removed
514 	do {
515 		last = current;
516 
517 		current = last.RemoveExt();
518 	} while (last != current);
519 
520 	return current;
521 }
522 
523 
StartsWith(const CPath & other) const524 bool CPath::StartsWith(const CPath& other) const
525 {
526 	// It doesn't make sense comparing invalid paths,
527 	// especially since if 'other' was empty, it would
528 	// be considered a prefix of any path.
529 	if ((IsOk() && other.IsOk()) == false) {
530 		return false;
531 	}
532 
533 	// Adding an seperator to avoid partial matches, such as
534 	// "/usr/bi" matching "/usr/bin". TODO: Paths should be
535 	// normalized first (in the constructor).
536 	const wxString a = StripSeparators(m_filesystem, wxString::trailing) + wxFileName::GetPathSeparator();
537 	const wxString b = StripSeparators(other.m_filesystem, wxString::trailing) + wxFileName::GetPathSeparator();
538 
539 	if (a.Length() < b.Length()) {
540 		// Cannot possibly be a prefix.
541 		return false;
542 	}
543 
544 	const size_t checkLen = std::min(a.Length(), b.Length());
545 	return PATHNCMP(a.c_str(), b.c_str(), checkLen) == 0;
546 }
547 
548 
CloneFile(const CPath & src,const CPath & dst,bool overwrite)549 bool CPath::CloneFile(const CPath& src, const CPath& dst, bool overwrite)
550 {
551 	return ::wxCopyFile(src.m_filesystem, dst.m_filesystem, overwrite);
552 }
553 
554 
RemoveFile(const CPath & file)555 bool CPath::RemoveFile(const CPath& file)
556 {
557 	return ::wxRemoveFile(file.m_filesystem);
558 }
559 
560 
RenameFile(const CPath & src,const CPath & dst,bool overwrite)561 bool CPath::RenameFile(const CPath& src, const CPath& dst, bool overwrite)
562 {
563 	return ::wxRenameFile(src.m_filesystem, dst.m_filesystem, overwrite);
564 }
565 
566 
BackupFile(const CPath & src,const wxString & appendix)567 bool CPath::BackupFile(const CPath& src, const wxString& appendix)
568 {
569 	wxASSERT(appendix.IsAscii());
570 
571 	CPath dst = CPath(src.m_filesystem + appendix);
572 
573 	if (CPath::CloneFile(src, dst, true)) {
574 		// Try to ensure that the backup gets physically written
575 #if defined __WINDOWS__ || defined __IRIX__
576 		wxFFile backupFile;
577 #else
578 		wxFile backupFile;
579 #endif
580 		if (backupFile.Open(dst.m_filesystem)) {
581 			backupFile.Flush();
582 		}
583 
584 		return true;
585 	}
586 
587 	return false;
588 }
589 
590 
RemoveDir(const CPath & file)591 bool CPath::RemoveDir(const CPath& file)
592 {
593 	return ::wxRmdir(file.m_filesystem);
594 }
595 
596 
MakeDir(const CPath & file)597 bool CPath::MakeDir(const CPath& file)
598 {
599 	return ::wxMkdir(file.m_filesystem);
600 }
601 
602 
FileExists(const wxString & file)603 bool CPath::FileExists(const wxString& file)
604 {
605 	return CPath(file).FileExists();
606 }
607 
608 
DirExists(const wxString & path)609 bool CPath::DirExists(const wxString& path)
610 {
611 	return CPath(path).DirExists();
612 }
613 
614 
GetFileSize(const wxString & file)615 sint64 CPath::GetFileSize(const wxString& file)
616 {
617 	return CPath(file).GetFileSize();
618 }
619 
620 
GetModificationTime(const CPath & file)621 time_t CPath::GetModificationTime(const CPath& file)
622 {
623 	return ::wxFileModificationTime(file.m_filesystem);
624 }
625 
626 
GetFreeSpaceAt(const CPath & path)627 sint64 CPath::GetFreeSpaceAt(const CPath& path)
628 {
629 	wxLongLong free;
630 	if (::wxGetDiskSpace(path.m_filesystem, NULL, &free)) {
631 		return free.GetValue();
632 	}
633 
634 	return wxInvalidOffset;
635 }
636 
637 
TruncatePath(size_t length,bool isFilePath) const638 wxString CPath::TruncatePath(size_t length, bool isFilePath) const
639 {
640 	wxString file = GetPrintable();
641 
642 	// Check if there's anything to do
643 	if (file.Length() <= length) {
644 		return file;
645 	}
646 
647 	// If the path is a file name, then prefer to remove from the path, rather than the filename
648 	if (isFilePath) {
649 		wxString path = wxFileName(file).GetPath();
650 		file          = wxFileName(file).GetFullName();
651 
652 		if (path.Length() >= length) {
653 			path.Clear();
654 		} else if (file.Length() >= length) {
655 			path.Clear();
656 		} else {
657 			// Minus 6 for "[...]" + separator
658 			int pathlen = (int)(length - file.Length() - 6);
659 
660 			if (pathlen > 0) {
661 				path = wxT("[...]") + path.Right( pathlen );
662 			} else {
663 				path.Clear();
664 			}
665 		}
666 
667 		file = ::JoinPaths(path, file);
668 	}
669 
670 	if (file.Length() > length) {
671 		if (length > 5) {
672 			file = file.Left(length - 5) + wxT("[...]");
673 		} else {
674 			file.Clear();
675 		}
676 	}
677 
678 	return file;
679 }
680 
681 
StripSeparators(wxString path,wxString::stripType type)682 wxString StripSeparators(wxString path, wxString::stripType type)
683 {
684 	wxASSERT((type == wxString::leading) || (type == wxString::trailing));
685 	const wxString seps = wxFileName::GetPathSeparators();
686 
687 	while (!path.IsEmpty()) {
688 		size_t pos = ((type == wxString::leading) ? 0 : path.Length() - 1);
689 
690 		if (seps.Contains(path.GetChar(pos))) {
691 			path.Remove(pos, 1);
692 		} else {
693 			break;
694 		}
695 	}
696 
697 	return path;
698 }
699 
700 
JoinPaths(const wxString & path,const wxString & file)701 wxString JoinPaths(const wxString& path, const wxString& file)
702 {
703 	if (path.IsEmpty()) {
704 		return file;
705 	} else if (file.IsEmpty()) {
706 		return path;
707 	}
708 
709 	return StripSeparators(path, wxString::trailing)
710 	   + wxFileName::GetPathSeparator()
711 	   + StripSeparators(file, wxString::leading);
712 }
713