Removed setFNConv()
[amule.git] / src / libs / common / Path.cpp
blob89c39777e26699366e27261e85bd36f53aeca830
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.
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.
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 __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
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
50 ////////////////////////////////////////////////////////////
51 // Helper functions
54 /** Creates a deep copy of the string, avoiding its ref. counting. */
55 inline wxString DeepCopy(const wxString& str)
57 return wxString(str.c_str(), str.Length());
61 wxString Demangle(const wxCharBuffer& fn, const wxString& filename)
63 wxString result = wxConvUTF8.cMB2WC(fn);
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();
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;
80 default:
81 // Nothing to do, the filename is probably Ok.
82 result = DeepCopy(filename);
86 return result;
90 /** Splits a full path into its path and filename component. */
91 inline void DoSplitPath(const wxString& strPath, wxString* path, wxString* name)
93 bool hasExt = false;
94 wxString ext, vol;
96 wxString* pVol = (path ? &vol : NULL);
97 wxString* pExt = (name ? &ext : NULL);
99 wxFileName::SplitPath(strPath, pVol, path, name, pExt, &hasExt);
101 if (hasExt && pExt) {
102 *name += wxT(".") + ext;
105 if (path && vol.Length()) {
106 *path = vol + wxFileName::GetVolumeSeparator() + *path;
111 /** Removes invalid chars from a filename. */
112 wxString DoCleanup(const wxString& filename, bool keepSpaces, bool isFAT32)
114 wxString result;
115 for (size_t i = 0; i < filename.Length(); i++) {
116 const wxChar c = filename[i];
118 switch (c) {
119 case wxT('/'):
120 continue;
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;
134 default:
135 if ((c == wxT(' ')) && !keepSpaces) {
136 result += wxT("%20");
137 } else if (c >= 32) {
138 // Many illegal for filenames in windows
139 // below the 32th char (which is space).
140 result += filename[i];
145 return result;
149 /** Does the actual work of adding a postfix ... */
150 wxString DoAddPostfix(const wxString& src, const wxString& postfix)
152 const wxFileName srcFn(src);
153 wxString result = srcFn.GetName() + postfix;
155 if (srcFn.HasExt()) {
156 result += wxT(".") + srcFn.GetExt();
159 wxString path = srcFn.GetPath();
160 if (path.Length()) {
161 return path + wxFileName::GetPathSeparator() + result;
164 return result;
167 /** Removes the last extension of a filename. */
168 wxString DoRemoveExt(const wxString& path)
170 // Using wxFilename which handles paths, etc.
171 wxFileName tmp(path);
172 tmp.ClearExt();
174 return tmp.GetFullPath();
178 /** Readies a path for use with wxAccess.. */
179 wxString DoCleanPath(const wxString& path)
181 #ifdef __WINDOWS__
182 // stat fails on windows if there are trailing path-separators.
183 wxString cleanPath = StripSeparators(path, wxString::trailing);
185 // Root paths must end with a separator (X:\ rather than X:).
186 // See comments in wxDirExists.
187 if ((cleanPath.Length() == 2) && (cleanPath.Last() == wxT(':'))) {
188 cleanPath += wxFileName::GetPathSeparator();
191 return cleanPath;
192 #else
193 return path;
194 #endif
198 /** Returns true if the two paths are equal. */
199 bool IsSameAs(const wxString& a, const wxString& b)
201 // Cache the current directory
202 const wxString cwd = wxGetCwd();
204 // We normalize everything, except env. variables, which
205 // can cause problems when the string is not encodable
206 // using wxConvLibc which wxWidgets uses for the purpose.
207 const int flags = (wxPATH_NORM_ALL | wxPATH_NORM_CASE) & ~wxPATH_NORM_ENV_VARS;
209 // Let wxFileName handle the tricky stuff involved in actually
210 // comparing two paths ... Currently, a path ending with a path-
211 // seperator will be unequal to the same path without a path-
212 // seperator, which is probably for the best, but can could
213 // lead to some unexpected behavior.
214 wxFileName fn1(a);
215 wxFileName fn2(b);
217 fn1.Normalize(flags, cwd);
218 fn2.Normalize(flags, cwd);
220 return (fn1.GetFullPath() == fn2.GetFullPath());
224 ////////////////////////////////////////////////////////////
225 // CPath implementation
227 CPath::CPath()
232 CPath::CPath(const wxString& filename)
234 // Equivalent to the default constructor ...
235 if (!filename) {
236 return;
239 wxCharBuffer fn = filename2char(filename);
240 if (fn) {
241 // Filename is valid in the current locale. This means that
242 // it either originated from a (wx)system-call, or from a
243 // user with a properly setup system.
244 m_filesystem = DeepCopy(filename);
245 m_printable = Demangle(fn, filename);
246 } else {
247 // It's not a valid filename in the current locale, so we'll
248 // have to do some magic. This ensures that the filename is
249 // saved as UTF8, even if the system is not unicode enabled,
250 // preserving the original filename till the user has fixed
251 // his system ...
252 #ifdef __WINDOWS__
253 // Magic fails on Windows where we always work with wide char file names.
254 m_filesystem = DeepCopy(filename);
255 m_printable = m_filesystem;
256 #else
257 fn = wxConvUTF8.cWC2MB(filename);
258 m_filesystem = wxConvFile.cMB2WC(fn);
260 // There's no need to try to unmangle the filename here.
261 m_printable = DeepCopy(filename);
262 #endif
265 wxASSERT(m_filesystem.Length());
266 wxASSERT(m_printable.Length());
270 CPath::CPath(const CPath& other)
271 : m_printable(DeepCopy(other.m_printable))
272 , m_filesystem(DeepCopy(other.m_filesystem))
277 CPath CPath::FromUniv(const wxString& path)
279 wxCharBuffer fn = wxConvISO8859_1.cWC2MB(path);
281 return CPath(wxConvFile.cMB2WC(fn));
286 wxString CPath::ToUniv(const CPath& path)
288 // The logic behind this is that by saving the filename
289 // as a raw bytestream (which is what ISO8859-1 amounts
290 // to), we can always recreate the on-disk filename, as
291 // if we had read it using wx functions.
292 wxCharBuffer fn = wxConvFile.cWC2MB(path.m_filesystem);
294 return wxConvISO8859_1.cMB2WC(fn);
298 CPath& CPath::operator=(const CPath& other)
300 if (this != &other) {
301 m_printable = DeepCopy(other.m_printable);
302 m_filesystem = DeepCopy(other.m_filesystem);
305 return *this;
309 bool CPath::operator==(const CPath& other) const
311 return ::IsSameAs(m_filesystem, other.m_filesystem);
315 bool CPath::operator!=(const CPath& other) const
317 return !(*this == other);
321 bool CPath::operator<(const CPath& other) const
323 return PATHCMP(m_filesystem.c_str(), other.m_filesystem.c_str()) < 0;
327 bool CPath::IsOk() const
329 // Something is very wrong if one of the two is empty.
330 return m_printable.Length() && m_filesystem.Length();
334 bool CPath::FileExists() const
336 return wxFileName::FileExists(m_filesystem);
340 bool CPath::DirExists() const
342 return wxFileName::DirExists(DoCleanPath(m_filesystem));
346 bool CPath::IsDir(EAccess mode) const
348 wxString path = DoCleanPath(m_filesystem);
349 if (!wxFileName::DirExists(path)) {
350 return false;
351 } else if ((mode & writable) && !wxIsWritable(path)) {
352 return false;
353 } else if ((mode & readable) && !wxIsReadable(path)) {
354 return false;
357 return true;
361 bool CPath::IsFile(EAccess mode) const
363 if (!wxFileName::FileExists(m_filesystem)) {
364 return false;
365 } else if ((mode & writable) && !wxIsWritable(m_filesystem)) {
366 return false;
367 } else if ((mode & readable) && !wxIsReadable(m_filesystem)) {
368 return false;
371 return true;
375 wxString CPath::GetRaw() const
377 // Copy as c-strings to ensure that the CPath objects can safely
378 // be passed across threads (avoiding wxString ref. counting).
379 return DeepCopy(m_filesystem);
383 wxString CPath::GetPrintable() const
385 // Copy as c-strings to ensure that the CPath objects can safely
386 // be passed across threads (avoiding wxString ref. counting).
387 return DeepCopy(m_printable);
391 wxString CPath::GetExt() const
393 return wxFileName(m_filesystem).GetExt();
397 CPath CPath::GetPath() const
399 CPath path;
400 ::DoSplitPath(m_printable, &path.m_printable, NULL);
401 ::DoSplitPath(m_filesystem, &path.m_filesystem, NULL);
403 return path;
407 CPath CPath::GetFullName() const
409 CPath path;
410 ::DoSplitPath(m_printable, NULL, &path.m_printable);
411 ::DoSplitPath(m_filesystem, NULL, &path.m_filesystem);
413 return path;
418 sint64 CPath::GetFileSize() const
420 if (FileExists()) {
421 wxFile f(m_filesystem);
422 if (f.IsOpened()) {
423 return f.Length();
427 return wxInvalidOffset;
431 bool CPath::IsSameDir(const CPath& other) const
433 wxString a = m_filesystem;
434 wxString b = other.m_filesystem;
436 // This check is needed to avoid trouble in the
437 // case where one path is empty, and the other
438 // points to the root dir.
439 if (a.Length() && b.Length()) {
440 a = StripSeparators(a, wxString::trailing);
441 b = StripSeparators(b, wxString::trailing);
444 return ::IsSameAs(a, b);
448 CPath CPath::JoinPaths(const CPath& other) const
450 if (!IsOk()) {
451 return CPath(other);
452 } else if (!other.IsOk()) {
453 return CPath(*this);
456 CPath joinedPath;
457 // DeepCopy shouldn't be needed, as JoinPaths results in the creation of a new string.
458 joinedPath.m_printable = ::JoinPaths(m_printable, other.m_printable);
459 joinedPath.m_filesystem = ::JoinPaths(m_filesystem, other.m_filesystem);
461 return joinedPath;
465 CPath CPath::Cleanup(bool keepSpaces, bool isFAT32) const
467 CPath result;
468 result.m_printable = ::DoCleanup(m_printable, keepSpaces, isFAT32);
469 result.m_filesystem = ::DoCleanup(m_filesystem, keepSpaces, isFAT32);
471 return result;
475 CPath CPath::AddPostfix(const wxString& postfix) const
477 wxASSERT(postfix.IsAscii());
479 CPath result;
480 result.m_printable = ::DoAddPostfix(m_printable, postfix);
481 result.m_filesystem = ::DoAddPostfix(m_filesystem, postfix);
483 return result;
487 CPath CPath::AppendExt(const wxString& ext) const
489 wxASSERT(ext.IsAscii());
491 // Though technically, and empty extension would simply
492 // be another . at the end of the filename, we ignore them.
493 if (ext.IsEmpty()) {
494 return *this;
497 CPath result(*this);
498 if (ext[0] == wxT('.')) {
499 result.m_printable << ext;
500 result.m_filesystem << ext;
501 } else {
502 result.m_printable << wxT(".") << ext;
503 result.m_filesystem << wxT(".") << ext;
506 return result;
510 CPath CPath::RemoveExt() const
512 CPath result;
513 result.m_printable = DoRemoveExt(m_printable);
514 result.m_filesystem = DoRemoveExt(m_filesystem);
516 return result;
520 CPath CPath::RemoveAllExt() const
522 CPath last, current = RemoveExt();
524 // Loop until all extensions are removed
525 do {
526 last = current;
528 current = last.RemoveExt();
529 } while (last != current);
531 return current;
535 bool CPath::StartsWith(const CPath& other) const
537 // It doesn't make sense comparing invalid paths,
538 // especially since if 'other' was empty, it would
539 // be considered a prefix of any path.
540 if ((IsOk() && other.IsOk()) == false) {
541 return false;
544 // Adding an seperator to avoid partial matches, such as
545 // "/usr/bi" matching "/usr/bin". TODO: Paths should be
546 // normalized first (in the constructor).
547 const wxString a = StripSeparators(m_filesystem, wxString::trailing) + wxFileName::GetPathSeparator();
548 const wxString b = StripSeparators(other.m_filesystem, wxString::trailing) + wxFileName::GetPathSeparator();
550 if (a.Length() < b.Length()) {
551 // Cannot possibly be a prefix.
552 return false;
555 const size_t checkLen = std::min(a.Length(), b.Length());
556 return PATHNCMP(a.c_str(), b.c_str(), checkLen) == 0;
560 bool CPath::CloneFile(const CPath& src, const CPath& dst, bool overwrite)
562 return ::wxCopyFile(src.m_filesystem, dst.m_filesystem, overwrite);
566 bool CPath::RemoveFile(const CPath& file)
568 return ::wxRemoveFile(file.m_filesystem);
572 bool CPath::RenameFile(const CPath& src, const CPath& dst, bool overwrite)
574 return ::wxRenameFile(src.m_filesystem, dst.m_filesystem, overwrite);
578 bool CPath::BackupFile(const CPath& src, const wxString& appendix)
580 wxASSERT(appendix.IsAscii());
582 CPath dst = CPath(src.m_filesystem + appendix);
584 if (CPath::CloneFile(src, dst, true)) {
585 // Try to ensure that the backup gets physically written
586 #if defined __WINDOWS__ || defined __IRIX__
587 wxFFile backupFile;
588 #else
589 wxFile backupFile;
590 #endif
591 if (backupFile.Open(dst.m_filesystem)) {
592 backupFile.Flush();
595 return true;
598 return false;
602 bool CPath::RemoveDir(const CPath& file)
604 return ::wxRmdir(file.m_filesystem);
608 bool CPath::MakeDir(const CPath& file)
610 return ::wxMkdir(file.m_filesystem);
614 bool CPath::FileExists(const wxString& file)
616 return CPath(file).FileExists();
620 bool CPath::DirExists(const wxString& path)
622 return CPath(path).DirExists();
626 sint64 CPath::GetFileSize(const wxString& file)
628 return CPath(file).GetFileSize();
632 time_t CPath::GetModificationTime(const CPath& file)
634 return ::wxFileModificationTime(file.m_filesystem);
638 sint64 CPath::GetFreeSpaceAt(const CPath& path)
640 wxLongLong free;
641 if (::wxGetDiskSpace(path.m_filesystem, NULL, &free)) {
642 return free.GetValue();
645 return wxInvalidOffset;
649 wxString CPath::TruncatePath(size_t length, bool isFilePath) const
651 wxString file = GetPrintable();
653 // Check if there's anything to do
654 if (file.Length() <= length) {
655 return file;
658 // If the path is a file name, then prefer to remove from the path, rather than the filename
659 if (isFilePath) {
660 wxString path = wxFileName(file).GetPath();
661 file = wxFileName(file).GetFullName();
663 if (path.Length() >= length) {
664 path.Clear();
665 } else if (file.Length() >= length) {
666 path.Clear();
667 } else {
668 // Minus 6 for "[...]" + separator
669 int pathlen = (int)(length - file.Length() - 6);
671 if (pathlen > 0) {
672 path = wxT("[...]") + path.Right( pathlen );
673 } else {
674 path.Clear();
678 file = ::JoinPaths(path, file);
681 if (file.Length() > length) {
682 if (length > 5) {
683 file = file.Left(length - 5) + wxT("[...]");
684 } else {
685 file.Clear();
689 return file;
693 wxString StripSeparators(wxString path, wxString::stripType type)
695 wxASSERT((type == wxString::leading) || (type == wxString::trailing));
696 const wxString seps = wxFileName::GetPathSeparators();
698 while (!path.IsEmpty()) {
699 size_t pos = ((type == wxString::leading) ? 0 : path.Length() - 1);
701 if (seps.Contains(path.GetChar(pos))) {
702 path.Remove(pos, 1);
703 } else {
704 break;
708 return path;
712 wxString JoinPaths(const wxString& path, const wxString& file)
714 if (path.IsEmpty()) {
715 return file;
716 } else if (file.IsEmpty()) {
717 return path;
720 return StripSeparators(path, wxString::trailing)
721 + wxFileName::GetPathSeparator()
722 + StripSeparators(file, wxString::leading);