Upstream tarball 10148
[amule.git] / src / libs / common / Path.cpp
blob2c85b3f230379fcbc346295ce994aa26d7bcfc2e
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 "StringFunctions.h" // Needed for filename2char()
28 #include <wx/file.h>
29 #if defined __WXMSW__ || defined __IRIX__
30 # include <wx/ffile.h>
31 #endif
32 #include <wx/utils.h>
33 #include <wx/filename.h>
36 // This is required in order to ensure that wx can "handle" filenames
37 // using a different encoding than the current system-wide setting. If
38 // this is not done, such filenames will fail during conversion to/from
39 // multibyte (as in cWC2MB/cMB2WC).
40 #if !wxUSE_GUI && !defined(__WXMSW__)
41 void* setFNConv()
43 // This uses the same method as wxApp::Initialize under GTK2
44 wxString encName = wxLocale::GetSystemEncodingName().Upper();
45 if (encName.IsEmpty() || (encName == wxT("US-ASCII"))) {
46 encName = wxT("UTF-8");
49 return wxConvFileName = new wxConvBrokenFileNames(encName);
52 // Ensure intialization
53 static void* s_foo = setFNConv();
54 #endif
57 // Windows has case-insensitive paths, so we use a
58 // case-insensitive cmp for that platform. TODO:
59 // Perhaps it would be better to simply lowercase
60 // m_filesystem in the constructor ...
61 #ifdef __WXMSW__
62 #define PATHCMP(a, b) wxStricmp(a, b)
63 #define PATHNCMP(a, b, n) wxStrnicmp(a, b, n)
64 #else
65 #define PATHCMP(a, b) wxStrcmp(a, b)
66 #define PATHNCMP(a, b, n) wxStrncmp(a, b, n)
67 #endif
70 ////////////////////////////////////////////////////////////
71 // Helper functions
74 /** Creates a deep copy of the string, avoiding its ref. counting. */
75 inline wxString DeepCopy(const wxString& str)
77 return wxString(str.c_str(), str.Length());
81 wxString Demangle(const wxCharBuffer& fn, const wxString& filename)
83 wxString result = wxConvUTF8.cMB2WC(fn);
85 // FIXME: Is this actually needed for osx/msw?
86 if (!result) {
87 // We only try to further demangle if the current locale is
88 // UTF-8, C or POSIX. This is because in any other case, the
89 // current locale is probably the best choice for printing.
90 static wxFontEncoding enc = wxLocale::GetSystemEncoding();
92 switch (enc) {
93 // SYSTEM is needed for ANSI encodings such as
94 // "POSIX" and "C", which are only 7bit.
95 case wxFONTENCODING_SYSTEM:
96 case wxFONTENCODING_UTF8:
97 result = wxConvISO8859_1.cMB2WC(fn);
98 break;
100 default:
101 // Nothing to do, the filename is probably Ok.
102 result = DeepCopy(filename);
106 return result;
110 /** Splits a full path into its path and filename component. */
111 inline void DoSplitPath(const wxString& strPath, wxString* path, wxString* name)
113 bool hasExt = false;
114 wxString ext, vol;
116 wxString* pVol = (path ? &vol : NULL);
117 wxString* pExt = (name ? &ext : NULL);
119 wxFileName::SplitPath(strPath, pVol, path, name, pExt, &hasExt);
121 if (hasExt && pExt) {
122 *name += wxT(".") + ext;
125 if (path && vol.Length()) {
126 *path = vol + wxFileName::GetVolumeSeparator() + *path;
131 /** Removes invalid chars from a filename. */
132 wxString DoCleanup(const wxString& filename, bool keepSpaces, bool isFAT32)
134 wxString result;
135 for (size_t i = 0; i < filename.Length(); i++) {
136 const wxChar c = filename[i];
138 switch (c) {
139 case wxT('/'):
140 continue;
142 case wxT('\"'):
143 case wxT('*'):
144 case wxT('<'):
145 case wxT('>'):
146 case wxT('?'):
147 case wxT('|'):
148 case wxT('\\'):
149 case wxT(':'):
150 if (isFAT32) {
151 continue;
154 default:
155 if ((c == wxT(' ')) && !keepSpaces) {
156 result += wxT("%20");
157 } else if (c >= 32) {
158 // Many illegal for filenames in windows
159 // below the 32th char (which is space).
160 result += filename[i];
165 return result;
169 /** Does the actual work of adding a postfix ... */
170 wxString DoAddPostfix(const wxString& src, const wxString& postfix)
172 const wxFileName srcFn(src);
173 wxString result = srcFn.GetName() + postfix;
175 if (srcFn.HasExt()) {
176 result += wxT(".") + srcFn.GetExt();
179 wxString path = srcFn.GetPath();
180 if (path.Length()) {
181 return path + wxFileName::GetPathSeparator() + result;
184 return result;
187 /** Removes the last extension of a filename. */
188 wxString DoRemoveExt(const wxString& path)
190 // Using wxFilename which handles paths, etc.
191 wxFileName tmp(path);
192 tmp.ClearExt();
194 return tmp.GetFullPath();
198 /** Readies a path for use with wxAccess.. */
199 wxString DoCleanPath(const wxString& path)
201 #ifdef __WXMSW__
202 // stat fails on windows if there are trailing path-separators.
203 wxString cleanPath = StripSeparators(path, wxString::trailing);
205 // Root paths must end with a separator (X:\ rather than X:).
206 // See comments in wxDirExists.
207 if ((cleanPath.Length() == 2) && (cleanPath.Last() == wxT(':'))) {
208 cleanPath += wxFileName::GetPathSeparator();
211 return cleanPath;
212 #else
213 return path;
214 #endif
218 /** Returns true if the two paths are equal. */
219 bool IsSameAs(const wxString& a, const wxString& b)
221 // Cache the current directory
222 const wxString cwd = wxGetCwd();
224 // We normalize everything, except env. variables, which
225 // can cause problems when the string is not encodable
226 // using wxConvLibc which wxWidgets uses for the purpose.
227 const int flags = (wxPATH_NORM_ALL | wxPATH_NORM_CASE) & ~wxPATH_NORM_ENV_VARS;
229 // Let wxFileName handle the tricky stuff involved in actually
230 // comparing two paths ... Currently, a path ending with a path-
231 // seperator will be unequal to the same path without a path-
232 // seperator, which is probably for the best, but can could
233 // lead to some unexpected behavior.
234 wxFileName fn1(a);
235 wxFileName fn2(b);
237 fn1.Normalize(flags, cwd);
238 fn2.Normalize(flags, cwd);
240 return (fn1.GetFullPath() == fn2.GetFullPath());
244 ////////////////////////////////////////////////////////////
245 // CPath implementation
247 CPath::CPath()
252 CPath::CPath(const wxString& filename)
254 // Equivalent to the default constructor ...
255 if (!filename) {
256 return;
259 wxCharBuffer fn = filename2char(filename);
260 if (fn) {
261 // Filename is valid in the current locale. This means that
262 // it either originated from a (wx)system-call, or from a
263 // user with a properly setup system.
264 m_filesystem = DeepCopy(filename);
265 m_printable = Demangle(fn, filename);
266 } else {
267 // It's not a valid filename in the current locale, so we'll
268 // have to do some magic. This ensures that the filename is
269 // saved as UTF8, even if the system is not unicode enabled,
270 // preserving the original filename till the user has fixed
271 // his system ...
272 #ifdef __WXMSW__
273 // Magic fails on Windows where we always work with wide char file names.
274 m_filesystem = DeepCopy(filename);
275 m_printable = m_filesystem;
276 #else
277 fn = wxConvUTF8.cWC2MB(filename);
278 m_filesystem = wxConvFile.cMB2WC(fn);
280 // There's no need to try to unmangle the filename here.
281 m_printable = DeepCopy(filename);
282 #endif
285 wxASSERT(m_filesystem.Length());
286 wxASSERT(m_printable.Length());
290 CPath::CPath(const CPath& other)
291 : CPrintable()
292 , m_printable(DeepCopy(other.m_printable))
293 , m_filesystem(DeepCopy(other.m_filesystem))
298 CPath::~CPath()
303 CPath CPath::FromUniv(const wxString& path)
305 wxCharBuffer fn = wxConvISO8859_1.cWC2MB(path);
307 return CPath(wxConvFile.cMB2WC(fn));
312 wxString CPath::ToUniv(const CPath& path)
314 // The logic behind this is that by saving the filename
315 // as a raw bytestream (which is what ISO8859-1 amounts
316 // to), we can always recreate the on-disk filename, as
317 // if we had read it using wx functions.
318 wxCharBuffer fn = wxConvFile.cWC2MB(path.m_filesystem);
320 return wxConvISO8859_1.cMB2WC(fn);
324 CPath& CPath::operator=(const CPath& other)
326 if (this != &other) {
327 m_printable = DeepCopy(other.m_printable);
328 m_filesystem = DeepCopy(other.m_filesystem);
331 return *this;
335 bool CPath::operator==(const CPath& other) const
337 return ::IsSameAs(m_filesystem, other.m_filesystem);
341 bool CPath::operator!=(const CPath& other) const
343 return !(*this == other);
347 bool CPath::operator<(const CPath& other) const
349 return PATHCMP(m_filesystem.c_str(), other.m_filesystem.c_str()) < 0;
353 bool CPath::IsOk() const
355 // Something is very wrong if one of the two is empty.
356 return m_printable.Length() && m_filesystem.Length();
360 bool CPath::FileExists() const
362 return wxFileName::FileExists(m_filesystem);
366 bool CPath::DirExists() const
368 return wxFileName::DirExists(DoCleanPath(m_filesystem));
372 bool CPath::IsDir(EAccess mode) const
374 wxString path = DoCleanPath(m_filesystem);
375 if (!wxFileName::DirExists(path)) {
376 return false;
377 } else if ((mode & writable) && !wxIsWritable(path)) {
378 return false;
379 } else if ((mode & readable) && !wxIsReadable(path)) {
380 return false;
383 return true;
387 bool CPath::IsFile(EAccess mode) const
389 if (!wxFileName::FileExists(m_filesystem)) {
390 return false;
391 } else if ((mode & writable) && !wxIsWritable(m_filesystem)) {
392 return false;
393 } else if ((mode & readable) && !wxIsReadable(m_filesystem)) {
394 return false;
397 return true;
401 wxString CPath::GetRaw() const
403 // Copy as c-strings to ensure that the CPath objects can safely
404 // be passed across threads (avoiding wxString ref. counting).
405 return DeepCopy(m_filesystem);
409 wxString CPath::GetPrintable() const
411 // Copy as c-strings to ensure that the CPath objects can safely
412 // be passed across threads (avoiding wxString ref. counting).
413 return DeepCopy(m_printable);
417 wxString CPath::GetExt() const
419 return wxFileName(m_filesystem).GetExt();
423 CPath CPath::GetPath() const
425 CPath path;
426 ::DoSplitPath(m_printable, &path.m_printable, NULL);
427 ::DoSplitPath(m_filesystem, &path.m_filesystem, NULL);
429 return path;
433 CPath CPath::GetFullName() const
435 CPath path;
436 ::DoSplitPath(m_printable, NULL, &path.m_printable);
437 ::DoSplitPath(m_filesystem, NULL, &path.m_filesystem);
439 return path;
444 sint64 CPath::GetFileSize() const
446 if (FileExists()) {
447 wxFile f(m_filesystem);
448 if (f.IsOpened()) {
449 return f.Length();
453 return wxInvalidOffset;
457 bool CPath::IsSameDir(const CPath& other) const
459 wxString a = m_filesystem;
460 wxString b = other.m_filesystem;
462 // This check is needed to avoid trouble in the
463 // case where one path is empty, and the other
464 // points to the root dir.
465 if (a.Length() && b.Length()) {
466 a = StripSeparators(a, wxString::trailing);
467 b = StripSeparators(b, wxString::trailing);
470 return ::IsSameAs(a, b);
474 CPath CPath::JoinPaths(const CPath& other) const
476 if (!IsOk()) {
477 return CPath(other);
478 } else if (!other.IsOk()) {
479 return CPath(*this);
482 CPath joinedPath;
483 // DeepCopy shouldn't be needed, as JoinPaths results in the creation of a new string.
484 joinedPath.m_printable = ::JoinPaths(m_printable, other.m_printable);
485 joinedPath.m_filesystem = ::JoinPaths(m_filesystem, other.m_filesystem);
487 return joinedPath;
491 CPath CPath::Cleanup(bool keepSpaces, bool isFAT32) const
493 CPath result;
494 result.m_printable = ::DoCleanup(m_printable, keepSpaces, isFAT32);
495 result.m_filesystem = ::DoCleanup(m_filesystem, keepSpaces, isFAT32);
497 return result;
501 CPath CPath::AddPostfix(const wxString& postfix) const
503 wxASSERT(postfix.IsAscii());
505 CPath result;
506 result.m_printable = ::DoAddPostfix(m_printable, postfix);
507 result.m_filesystem = ::DoAddPostfix(m_filesystem, postfix);
509 return result;
513 CPath CPath::AppendExt(const wxString& ext) const
515 wxASSERT(ext.IsAscii());
517 // Though technically, and empty extension would simply
518 // be another . at the end of the filename, we ignore them.
519 if (ext.IsEmpty()) {
520 return *this;
523 CPath result(*this);
524 if (ext[0] == wxT('.')) {
525 result.m_printable << ext;
526 result.m_filesystem << ext;
527 } else {
528 result.m_printable << wxT(".") << ext;
529 result.m_filesystem << wxT(".") << ext;
532 return result;
536 CPath CPath::RemoveExt() const
538 CPath result;
539 result.m_printable = DoRemoveExt(m_printable);
540 result.m_filesystem = DoRemoveExt(m_filesystem);
542 return result;
546 CPath CPath::RemoveAllExt() const
548 CPath last, current = RemoveExt();
550 // Loop until all extensions are removed
551 do {
552 last = current;
554 current = last.RemoveExt();
555 } while (last != current);
557 return current;
561 bool CPath::StartsWith(const CPath& other) const
563 // It doesn't make sense comparing invalid paths,
564 // especially since if 'other' was empty, it would
565 // be considered a prefix of any path.
566 if ((IsOk() && other.IsOk()) == false) {
567 return false;
570 // Adding an seperator to avoid partial matches, such as
571 // "/usr/bi" matching "/usr/bin". TODO: Paths should be
572 // normalized first (in the constructor).
573 const wxString a = StripSeparators(m_filesystem, wxString::trailing) + wxFileName::GetPathSeparator();
574 const wxString b = StripSeparators(other.m_filesystem, wxString::trailing) + wxFileName::GetPathSeparator();
576 if (a.Length() < b.Length()) {
577 // Cannot possibly be a prefix.
578 return false;
581 const size_t checkLen = std::min(a.Length(), b.Length());
582 return PATHNCMP(a.c_str(), b.c_str(), checkLen) == 0;
586 wxString CPath::GetPrintableString() const
588 return DeepCopy(m_printable);
592 bool CPath::CloneFile(const CPath& src, const CPath& dst, bool overwrite)
594 return ::wxCopyFile(src.m_filesystem, dst.m_filesystem, overwrite);
598 bool CPath::RemoveFile(const CPath& file)
600 return ::wxRemoveFile(file.m_filesystem);
604 bool CPath::RenameFile(const CPath& src, const CPath& dst, bool overwrite)
606 return ::wxRenameFile(src.m_filesystem, dst.m_filesystem, overwrite);
610 bool CPath::BackupFile(const CPath& src, const wxString& appendix)
612 wxASSERT(appendix.IsAscii());
614 CPath dst = CPath(src.m_filesystem + appendix);
616 if (CPath::CloneFile(src, dst, true)) {
617 // Try to ensure that the backup gets physically written
618 #if defined __WXMSW__ || defined __IRIX__
619 wxFFile backupFile;
620 #else
621 wxFile backupFile;
622 #endif
623 if (backupFile.Open(dst.m_filesystem)) {
624 backupFile.Flush();
627 return true;
630 return false;
634 bool CPath::RemoveDir(const CPath& file)
636 return ::wxRmdir(file.m_filesystem);
640 bool CPath::MakeDir(const CPath& file)
642 return ::wxMkdir(file.m_filesystem);
646 bool CPath::FileExists(const wxString& file)
648 return CPath(file).FileExists();
652 bool CPath::DirExists(const wxString& path)
654 return CPath(path).DirExists();
658 sint64 CPath::GetFileSize(const wxString& file)
660 return CPath(file).GetFileSize();
664 time_t CPath::GetModificationTime(const CPath& file)
666 return ::wxFileModificationTime(file.m_filesystem);
670 sint64 CPath::GetFreeSpaceAt(const CPath& path)
672 wxLongLong free;
673 if (::wxGetDiskSpace(path.m_filesystem, NULL, &free)) {
674 return free.GetValue();
677 return wxInvalidOffset;
681 wxString CPath::TruncatePath(size_t length, bool isFilePath) const
683 wxString file = GetPrintable();
685 // Check if there's anything to do
686 if (file.Length() <= length) {
687 return file;
690 // If the path is a file name, then prefer to remove from the path, rather than the filename
691 if (isFilePath) {
692 wxString path = wxFileName(file).GetPath();
693 file = wxFileName(file).GetFullName();
695 if (path.Length() >= length) {
696 path.Clear();
697 } else if (file.Length() >= length) {
698 path.Clear();
699 } else {
700 // Minus 6 for "[...]" + separator
701 int pathlen = (int)(length - file.Length() - 6);
703 if (pathlen > 0) {
704 path = wxT("[...]") + path.Right( pathlen );
705 } else {
706 path.Clear();
710 file = ::JoinPaths(path, file);
713 if (file.Length() > length) {
714 if (length > 5) {
715 file = file.Left(length - 5) + wxT("[...]");
716 } else {
717 file.Clear();
721 return file;
725 wxString StripSeparators(wxString path, wxString::stripType type)
727 wxASSERT((type == wxString::leading) || (type == wxString::trailing));
728 const wxString seps = wxFileName::GetPathSeparators();
730 while (!path.IsEmpty()) {
731 size_t pos = ((type == wxString::leading) ? 0 : path.Length() - 1);
733 if (seps.Contains(path.GetChar(pos))) {
734 path.Remove(pos, 1);
735 } else {
736 break;
740 return path;
744 wxString JoinPaths(const wxString& path, const wxString& file)
746 if (path.IsEmpty()) {
747 return file;
748 } else if (file.IsEmpty()) {
749 return path;
752 return StripSeparators(path, wxString::trailing)
753 + wxFileName::GetPathSeparator()
754 + StripSeparators(file, wxString::leading);