Upstream tarball 9882
[amule.git] / src / libs / common / Path.cpp
blob5dd942b7620baa5ad6361e6d4b67700b6942c7bd
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2008 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.
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
25 #include "Path.h"
26 #include "MuleDebug.h"
27 #include "StringFunctions.h"
29 #include <wx/file.h>
30 #if defined __WXMSW__ || defined __IRIX__
31 # include <wx/ffile.h>
32 #endif
33 #include <wx/utils.h>
34 #include <wx/filename.h>
37 // This is required in order to ensure that wx can "handle" filenames
38 // using a different encoding than the current system-wide setting. If
39 // this is not done, such filenames will fail during convertion to/from
40 // multibyte (as in cWC2MB/cMB2WC).
41 #if !wxUSE_GUI && !defined(__WXMSW__)
42 void* setFNConv()
44 // This uses the same method as wxApp::Initialize under GTK2
45 wxString encName = wxLocale::GetSystemEncodingName().Upper();
46 if (encName.IsEmpty() || (encName == wxT("US-ASCII"))) {
47 encName = wxT("UTF-8");
50 return wxConvFileName = new wxConvBrokenFileNames(encName);
53 // Ensure intialization
54 static void* s_foo = setFNConv();
55 #endif
58 // Windows has case-insensitive paths, so we use a
59 // case-insensitive cmp for that platform. TODO:
60 // Perhaps it would be better to simply lowercase
61 // m_filesystem in the contructor ...
62 #ifdef __WXMSW__
63 #define PATHCMP(a, b) wxStricmp(a, b)
64 #define PATHNCMP(a, b, n) wxStrnicmp(a, b, n)
65 #else
66 #define PATHCMP(a, b) wxStrcmp(a, b)
67 #define PATHNCMP(a, b, n) wxStrncmp(a, b, n)
68 #endif
71 ////////////////////////////////////////////////////////////
72 // Helper functions
75 /** Creates a deep copy of the string, avoiding its ref. counting. */
76 inline wxString DeepCopy(const wxString& str)
78 return wxString(str.c_str(), str.Length());
82 wxString Demangle(const wxCharBuffer& fn, const wxString& filename)
84 wxString result = wxConvUTF8.cMB2WC(fn);
86 // FIXME: Is this actually needed for osx/msw?
87 if (!result) {
88 // We only try to further demangle if the current locale is
89 // UTF-8, C or POSIX. This is because in any other case, the
90 // current locale is probably the best choice for printing.
91 static wxFontEncoding enc = wxLocale::GetSystemEncoding();
93 switch (enc) {
94 // SYSTEM is needed for ANSI encodings such as
95 // "POSIX" and "C", which are only 7bit.
96 case wxFONTENCODING_SYSTEM:
97 case wxFONTENCODING_UTF8:
98 result = wxConvISO8859_1.cMB2WC(fn);
99 break;
101 default:
102 // Nothing to do, the filename is probably Ok.
103 result = DeepCopy(filename);
107 return result;
111 /** Splits a full path into its path and filename component. */
112 inline void DoSplitPath(const wxString& strPath, wxString* path, wxString* name)
114 bool hasExt = false;
115 wxString ext, vol;
117 wxString* pVol = (path ? &vol : NULL);
118 wxString* pExt = (name ? &ext : NULL);
120 wxFileName::SplitPath(strPath, pVol, path, name, pExt, &hasExt);
122 if (hasExt && pExt) {
123 *name += wxT(".") + ext;
126 if (path && vol.Length()) {
127 *path = vol + wxFileName::GetVolumeSeparator() + *path;
132 /** Removes invalid chars from a filename. */
133 wxString DoCleanup(const wxString& filename, bool keepSpaces, bool isFAT32)
135 wxString result;
136 for (size_t i = 0; i < filename.Length(); i++) {
137 const wxChar c = filename[i];
139 switch (c) {
140 case wxT('/'):
141 continue;
143 case wxT('\"'):
144 case wxT('*'):
145 case wxT('<'):
146 case wxT('>'):
147 case wxT('?'):
148 case wxT('|'):
149 case wxT('\\'):
150 case wxT(':'):
151 if (isFAT32) {
152 continue;
155 default:
156 if ((c == wxT(' ')) && !keepSpaces) {
157 result += wxT("%20");
158 } else if (c >= 32) {
159 // Many illegal for filenames in windows
160 // below the 32th char (which is space).
161 result += filename[i];
166 return result;
170 /** Does the actual work of adding a postfix ... */
171 wxString DoAddPostfix(const wxString& src, const wxString& postfix)
173 const wxFileName srcFn(src);
174 wxString result = srcFn.GetName() + postfix;
176 if (srcFn.HasExt()) {
177 result += wxT(".") + srcFn.GetExt();
180 wxString path = srcFn.GetPath();
181 if (path.Length()) {
182 return path + wxFileName::GetPathSeparator() + result;
185 return result;
188 /** Removes the last extension of a filename. */
189 wxString DoRemoveExt(const wxString& path)
191 // Using wxFilename which handles paths, etc.
192 wxFileName tmp(path);
193 tmp.ClearExt();
195 return tmp.GetFullPath();
199 /** Readies a path for use with wxAccess.. */
200 wxString DoCleanPath(const wxString& path)
202 #ifdef __WXMSW__
203 // stat fails on windows if there are trailing path-separators.
204 wxString cleanPath = StripSeparators(path, wxString::trailing);
206 // Root paths must end with a separator (X:\ rather than X:).
207 // See comments in wxDirExists.
208 if ((cleanPath.Length() == 2) && (cleanPath.Last() == wxT(':'))) {
209 cleanPath += wxFileName::GetPathSeparator();
212 return cleanPath;
213 #else
214 return path;
215 #endif
219 /** Returns true if the two paths are equal. */
220 bool IsSameAs(const wxString& a, const wxString& b)
222 // Cache the current directory
223 const wxString cwd = wxGetCwd();
225 // We normalize everything, except env. variables, which
226 // can cause problems when the string is not encodable
227 // using wxConvLibc which wxWidgets uses for the purpose.
228 const int flags = (wxPATH_NORM_ALL | wxPATH_NORM_CASE) & ~wxPATH_NORM_ENV_VARS;
230 // Let wxFileName handle the tricky stuff involved in actually
231 // comparing two paths ... Currently, a path ending with a path-
232 // seperator will be unequal to the same path without a path-
233 // seperator, which is probably for the best, but can could
234 // lead to some unexpected behavior.
235 wxFileName fn1(a);
236 wxFileName fn2(b);
238 fn1.Normalize(flags, cwd);
239 fn2.Normalize(flags, cwd);
241 return (fn1.GetFullPath() == fn2.GetFullPath());
245 ////////////////////////////////////////////////////////////
246 // CPath implementation
248 CPath::CPath()
253 CPath::CPath(const wxString& filename)
255 // Equivalent to the default constructor ...
256 if (!filename) {
257 return;
260 wxCharBuffer fn = filename2char(filename);
261 if (fn) {
262 // Filename is valid in the current locale. This means that
263 // it either originated from a (wx)system-call, or from a
264 // user with a properly setup system.
265 m_filesystem = DeepCopy(filename);
266 m_printable = Demangle(fn, filename);
267 } else {
268 // It's not a valid filename in the current locale, so we'll
269 // have to do some magic. This ensures that the filename is
270 // saved as UTF8, even if the system is not unicode enabled,
271 // preserving the original filename till the user has fixed
272 // his system ...
273 fn = wxConvUTF8.cWC2MB(filename);
274 m_filesystem = wxConvFile.cMB2WC(fn);
276 // There's no need to try to unmangle the filename here.
277 m_printable = DeepCopy(filename);
280 wxASSERT(m_filesystem.Length());
281 wxASSERT(m_printable.Length());
285 CPath::CPath(const CPath& other)
286 : CPrintable()
287 , m_printable(DeepCopy(other.m_printable))
288 , m_filesystem(DeepCopy(other.m_filesystem))
293 CPath::~CPath()
298 CPath CPath::FromUniv(const wxString& path)
300 wxCharBuffer fn = wxConvISO8859_1.cWC2MB(path);
302 return CPath(wxConvFile.cMB2WC(fn));
307 wxString CPath::ToUniv(const CPath& path)
309 // The logic behind this is that by saving the filename
310 // as a raw bytestream (which is what ISO8859-1 amounts
311 // to), we can always recreate the on-disk filename, as
312 // if we had read it using wx functions.
313 wxCharBuffer fn = wxConvFile.cWC2MB(path.m_filesystem);
315 return wxConvISO8859_1.cMB2WC(fn);
319 CPath& CPath::operator=(const CPath& other)
321 if (this != &other) {
322 m_printable = DeepCopy(other.m_printable);
323 m_filesystem = DeepCopy(other.m_filesystem);
326 return *this;
330 bool CPath::operator==(const CPath& other) const
332 return ::IsSameAs(m_filesystem, other.m_filesystem);
336 bool CPath::operator!=(const CPath& other) const
338 return !(*this == other);
342 bool CPath::operator<(const CPath& other) const
344 return PATHCMP(m_filesystem.c_str(), other.m_filesystem.c_str()) < 0;
348 bool CPath::IsOk() const
350 // Something is very wrong if one of the two is empty.
351 return m_printable.Length() && m_filesystem.Length();
355 bool CPath::FileExists() const
357 return wxFileName::FileExists(m_filesystem);
361 bool CPath::DirExists() const
363 return wxFileName::DirExists(DoCleanPath(m_filesystem));
367 bool CPath::IsDir(EAccess mode) const
369 wxString path = DoCleanPath(m_filesystem);
370 if (!wxFileName::DirExists(path)) {
371 return false;
372 } else if ((mode & writable) && !wxIsWritable(path)) {
373 return false;
374 } else if ((mode & readable) && !wxIsReadable(path)) {
375 return false;
378 return true;
382 bool CPath::IsFile(EAccess mode) const
384 if (!wxFileName::FileExists(m_filesystem)) {
385 return false;
386 } else if ((mode & writable) && !wxIsWritable(m_filesystem)) {
387 return false;
388 } else if ((mode & readable) && !wxIsReadable(m_filesystem)) {
389 return false;
392 return true;
396 wxString CPath::GetRaw() const
398 // Copy as c-strings to ensure that the CPath objects can safely
399 // be passed across threads (avoiding wxString ref. counting).
400 return DeepCopy(m_filesystem);
404 wxString CPath::GetPrintable() const
406 // Copy as c-strings to ensure that the CPath objects can safely
407 // be passed across threads (avoiding wxString ref. counting).
408 return DeepCopy(m_printable);
412 wxString CPath::GetExt() const
414 return wxFileName(m_filesystem).GetExt();
418 CPath CPath::GetPath() const
420 CPath path;
421 ::DoSplitPath(m_printable, &path.m_printable, NULL);
422 ::DoSplitPath(m_filesystem, &path.m_filesystem, NULL);
424 return path;
428 CPath CPath::GetFullName() const
430 CPath path;
431 ::DoSplitPath(m_printable, NULL, &path.m_printable);
432 ::DoSplitPath(m_filesystem, NULL, &path.m_filesystem);
434 return path;
439 sint64 CPath::GetFileSize() const
441 if (FileExists()) {
442 wxFile f(m_filesystem);
443 if (f.IsOpened()) {
444 return f.Length();
448 return wxInvalidOffset;
452 bool CPath::IsSameDir(const CPath& other) const
454 wxString a = m_filesystem;
455 wxString b = other.m_filesystem;
457 // This check is needed to avoid trouble in the
458 // case where one path is empty, and the other
459 // points to the root dir.
460 if (a.Length() && b.Length()) {
461 a = StripSeparators(a, wxString::trailing);
462 b = StripSeparators(b, wxString::trailing);
465 return ::IsSameAs(a, b);
469 CPath CPath::JoinPaths(const CPath& other) const
471 if (!IsOk()) {
472 return CPath(other);
473 } else if (!other.IsOk()) {
474 return CPath(*this);
477 CPath joinedPath;
478 // DeepCopy shouldn't be needed, as JoinPaths results in the creation of a new string.
479 joinedPath.m_printable = ::JoinPaths(m_printable, other.m_printable);
480 joinedPath.m_filesystem = ::JoinPaths(m_filesystem, other.m_filesystem);
482 return joinedPath;
486 CPath CPath::Cleanup(bool keepSpaces, bool isFAT32) const
488 CPath result;
489 result.m_printable = ::DoCleanup(m_printable, keepSpaces, isFAT32);
490 result.m_filesystem = ::DoCleanup(m_filesystem, keepSpaces, isFAT32);
492 return result;
496 CPath CPath::AddPostfix(const wxString& postfix) const
498 wxASSERT(postfix.IsAscii());
500 CPath result;
501 result.m_printable = ::DoAddPostfix(m_printable, postfix);
502 result.m_filesystem = ::DoAddPostfix(m_filesystem, postfix);
504 return result;
508 CPath CPath::AppendExt(const wxString& ext) const
510 wxASSERT(ext.IsAscii());
512 // Though technically, and empty extension would simply
513 // be another . at the end of the filename, we ignore them.
514 if (ext.IsEmpty()) {
515 return *this;
518 CPath result(*this);
519 if (ext[0] == wxT('.')) {
520 result.m_printable << ext;
521 result.m_filesystem << ext;
522 } else {
523 result.m_printable << wxT(".") << ext;
524 result.m_filesystem << wxT(".") << ext;
527 return result;
531 CPath CPath::RemoveExt() const
533 CPath result;
534 result.m_printable = DoRemoveExt(m_printable);
535 result.m_filesystem = DoRemoveExt(m_filesystem);
537 return result;
541 CPath CPath::RemoveAllExt() const
543 CPath last, current = RemoveExt();
545 // Loop untill all extensions are removed
546 do {
547 last = current;
549 current = last.RemoveExt();
550 } while (last != current);
552 return current;
556 bool CPath::StartsWith(const CPath& other) const
558 // It doesn't make sense comparing invalid paths,
559 // especially since if 'other' was empty, it would
560 // be considered a prefix of any path.
561 if ((IsOk() && other.IsOk()) == false) {
562 return false;
565 // Adding an seperator to avoid partial matches, such as
566 // "/usr/bi" matching "/usr/bin". TODO: Paths should be
567 // normalized first (in the constructor).
568 const wxString a = StripSeparators(m_filesystem, wxString::trailing) + wxFileName::GetPathSeparator();
569 const wxString b = StripSeparators(other.m_filesystem, wxString::trailing) + wxFileName::GetPathSeparator();
571 if (a.Length() < b.Length()) {
572 // Cannot possibly be a prefix.
573 return false;
576 const size_t checkLen = std::min(a.Length(), b.Length());
577 return PATHNCMP(a.c_str(), b.c_str(), checkLen) == 0;
581 wxString CPath::GetPrintableString() const
583 return DeepCopy(m_printable);
587 bool CPath::CloneFile(const CPath& src, const CPath& dst, bool overwrite)
589 return ::wxCopyFile(src.m_filesystem, dst.m_filesystem, overwrite);
593 bool CPath::RemoveFile(const CPath& file)
595 return ::wxRemoveFile(file.m_filesystem);
599 bool CPath::RenameFile(const CPath& src, const CPath& dst, bool overwrite)
601 return ::wxRenameFile(src.m_filesystem, dst.m_filesystem, overwrite);
605 bool CPath::BackupFile(const CPath& src, const wxString& appendix)
607 wxASSERT(appendix.IsAscii());
609 CPath dst = CPath(src.m_filesystem + appendix);
611 if (CPath::CloneFile(src, dst, true)) {
612 // Try to ensure that the backup gets physically written
613 // Now - does this have any effect reopening a already closed file
614 // to flush it ???
615 #if defined __WXMSW__ || defined __IRIX__
616 wxFFile backupFile;
617 #else
618 wxFile backupFile;
619 #endif
620 if (backupFile.Open(dst.m_filesystem)) {
621 backupFile.Flush();
624 return true;
627 return false;
631 bool CPath::RemoveDir(const CPath& file)
633 return ::wxRmdir(file.m_filesystem);
637 bool CPath::MakeDir(const CPath& file)
639 return ::wxMkdir(file.m_filesystem);
643 bool CPath::FileExists(const wxString& file)
645 return CPath(file).FileExists();
649 bool CPath::DirExists(const wxString& path)
651 return CPath(path).DirExists();
655 sint64 CPath::GetFileSize(const wxString& file)
657 return CPath(file).GetFileSize();
661 time_t CPath::GetModificationTime(const CPath& file)
663 return ::wxFileModificationTime(file.m_filesystem);
667 sint64 CPath::GetFreeSpaceAt(const CPath& path)
669 wxLongLong free;
670 if (::wxGetDiskSpace(path.m_filesystem, NULL, &free)) {
671 return free.GetValue();
674 return wxInvalidOffset;