2 // This file is part of the aMule Project.
4 // Copyright (c) 2008-2011 aMule Team ( admin@amule.org / http://www.amule.org )
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
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
26 #include "StringFunctions.h" // Needed for filename2char()
29 #if defined __WINDOWS__ || defined __IRIX__
30 # include <wx/ffile.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 ...
42 #define PATHCMP(a, b) wxStricmp(a, b)
43 #define PATHNCMP(a, b, n) wxStrnicmp(a, b, n)
45 #define PATHCMP(a, b) wxStrcmp(a, b)
46 #define PATHNCMP(a, b, n) wxStrncmp(a, b, n)
50 ////////////////////////////////////////////////////////////
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?
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();
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
);
81 // Nothing to do, the filename is probably Ok.
82 result
= DeepCopy(filename
);
90 /** Splits a full path into its path and filename component. */
91 inline void DoSplitPath(const wxString
& strPath
, wxString
* path
, wxString
* name
)
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
)
115 for (size_t i
= 0; i
< filename
.Length(); i
++) {
116 const wxChar c
= filename
[i
];
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
];
149 /** Does the actual work of adding a postfix ... */
150 wxString
DoAddPostfix(const wxString
& src
, const wxString
& postfix
)
154 fn
.SetName(fn
.GetName() + postfix
);
156 return fn
.GetFullPath();
159 /** Removes the last extension of a filename. */
160 wxString
DoRemoveExt(const wxString
& path
)
162 // Using wxFilename which handles paths, etc.
163 wxFileName
tmp(path
);
166 return tmp
.GetFullPath();
170 /** Readies a path for use with wxAccess.. */
171 wxString
DoCleanPath(const wxString
& path
)
174 // stat fails on windows if there are trailing path-separators.
175 wxString cleanPath
= StripSeparators(path
, wxString::trailing
);
177 // Root paths must end with a separator (X:\ rather than X:).
178 // See comments in wxDirExists.
179 if ((cleanPath
.Length() == 2) && (cleanPath
.Last() == wxT(':'))) {
180 cleanPath
+= wxFileName::GetPathSeparator();
190 /** Returns true if the two paths are equal. */
191 bool IsSameAs(const wxString
& a
, const wxString
& b
)
193 // Cache the current directory
194 const wxString cwd
= wxGetCwd();
196 // We normalize everything, except env. variables, which
197 // can cause problems when the string is not encodable
198 // using wxConvLibc which wxWidgets uses for the purpose.
199 const int flags
= (wxPATH_NORM_ALL
| wxPATH_NORM_CASE
) & ~wxPATH_NORM_ENV_VARS
;
201 // Let wxFileName handle the tricky stuff involved in actually
202 // comparing two paths ... Currently, a path ending with a path-
203 // seperator will be unequal to the same path without a path-
204 // seperator, which is probably for the best, but can could
205 // lead to some unexpected behavior.
209 fn1
.Normalize(flags
, cwd
);
210 fn2
.Normalize(flags
, cwd
);
212 return (fn1
.GetFullPath() == fn2
.GetFullPath());
216 ////////////////////////////////////////////////////////////
217 // CPath implementation
224 CPath::CPath(const wxString
& filename
)
226 // Equivalent to the default constructor ...
231 wxCharBuffer fn
= filename2char(filename
);
233 // Filename is valid in the current locale. This means that
234 // it either originated from a (wx)system-call, or from a
235 // user with a properly setup system.
236 m_filesystem
= DeepCopy(filename
);
237 m_printable
= Demangle(fn
, filename
);
239 // It's not a valid filename in the current locale, so we'll
240 // have to do some magic. This ensures that the filename is
241 // saved as UTF8, even if the system is not unicode enabled,
242 // preserving the original filename till the user has fixed
245 // Magic fails on Windows where we always work with wide char file names.
246 m_filesystem
= DeepCopy(filename
);
247 m_printable
= m_filesystem
;
249 fn
= filename
.utf8_str();
250 m_filesystem
= wxConvFile
.cMB2WC(fn
);
252 // There's no need to try to unmangle the filename here.
253 m_printable
= DeepCopy(filename
);
257 wxASSERT(m_filesystem
.Length());
258 wxASSERT(m_printable
.Length());
262 CPath::CPath(const CPath
& other
)
263 : m_printable(DeepCopy(other
.m_printable
))
264 , m_filesystem(DeepCopy(other
.m_filesystem
))
269 CPath
CPath::FromUniv(const wxString
& path
)
271 wxCharBuffer fn
= path
.mb_str(wxConvISO8859_1
);
272 return CPath(wxConvFile
.cMB2WC(fn
));
276 wxString
CPath::ToUniv(const CPath
& path
)
278 // The logic behind this is that by saving the filename
279 // as a raw bytestream, we can always recreate the on-disk filename,
280 // as if we had read it using wx functions.
281 wxCharBuffer fn
= path
.m_filesystem
.mb_str(wxConvFile
);
282 return wxConvISO8859_1
.cMB2WC(fn
);
286 CPath
& CPath::operator=(const CPath
& other
)
288 if (this != &other
) {
289 m_printable
= DeepCopy(other
.m_printable
);
290 m_filesystem
= DeepCopy(other
.m_filesystem
);
297 bool CPath::operator==(const CPath
& other
) const
299 return ::IsSameAs(m_filesystem
, other
.m_filesystem
);
303 bool CPath::operator!=(const CPath
& other
) const
305 return !(*this == other
);
309 bool CPath::operator<(const CPath
& other
) const
311 return PATHCMP(m_filesystem
.c_str(), other
.m_filesystem
.c_str()) < 0;
315 bool CPath::IsOk() const
317 // Something is very wrong if one of the two is empty.
318 return m_printable
.Length() && m_filesystem
.Length();
322 bool CPath::FileExists() const
324 return wxFileName::FileExists(m_filesystem
);
328 bool CPath::DirExists() const
330 return wxFileName::DirExists(DoCleanPath(m_filesystem
));
334 bool CPath::IsDir(EAccess mode
) const
336 wxString path
= DoCleanPath(m_filesystem
);
337 if (!wxFileName::DirExists(path
)) {
339 } else if ((mode
& writable
) && !wxIsWritable(path
)) {
341 } else if ((mode
& readable
) && !wxIsReadable(path
)) {
349 bool CPath::IsFile(EAccess mode
) const
351 if (!wxFileName::FileExists(m_filesystem
)) {
353 } else if ((mode
& writable
) && !wxIsWritable(m_filesystem
)) {
355 } else if ((mode
& readable
) && !wxIsReadable(m_filesystem
)) {
363 wxString
CPath::GetRaw() const
365 // Copy as c-strings to ensure that the CPath objects can safely
366 // be passed across threads (avoiding wxString ref. counting).
367 return DeepCopy(m_filesystem
);
371 wxString
CPath::GetPrintable() const
373 // Copy as c-strings to ensure that the CPath objects can safely
374 // be passed across threads (avoiding wxString ref. counting).
375 return DeepCopy(m_printable
);
379 wxString
CPath::GetExt() const
381 return wxFileName(m_filesystem
).GetExt();
385 CPath
CPath::GetPath() const
388 ::DoSplitPath(m_printable
, &path
.m_printable
, NULL
);
389 ::DoSplitPath(m_filesystem
, &path
.m_filesystem
, NULL
);
395 CPath
CPath::GetFullName() const
398 ::DoSplitPath(m_printable
, NULL
, &path
.m_printable
);
399 ::DoSplitPath(m_filesystem
, NULL
, &path
.m_filesystem
);
406 sint64
CPath::GetFileSize() const
409 wxFile
f(m_filesystem
);
415 return wxInvalidOffset
;
419 bool CPath::IsSameDir(const CPath
& other
) const
421 wxString a
= m_filesystem
;
422 wxString b
= other
.m_filesystem
;
424 // This check is needed to avoid trouble in the
425 // case where one path is empty, and the other
426 // points to the root dir.
427 if (a
.Length() && b
.Length()) {
428 a
= StripSeparators(a
, wxString::trailing
);
429 b
= StripSeparators(b
, wxString::trailing
);
432 return ::IsSameAs(a
, b
);
436 CPath
CPath::JoinPaths(const CPath
& other
) const
440 } else if (!other
.IsOk()) {
445 // DeepCopy shouldn't be needed, as JoinPaths results in the creation of a new string.
446 joinedPath
.m_printable
= ::JoinPaths(m_printable
, other
.m_printable
);
447 joinedPath
.m_filesystem
= ::JoinPaths(m_filesystem
, other
.m_filesystem
);
453 CPath
CPath::Cleanup(bool keepSpaces
, bool isFAT32
) const
456 result
.m_printable
= ::DoCleanup(m_printable
, keepSpaces
, isFAT32
);
457 result
.m_filesystem
= ::DoCleanup(m_filesystem
, keepSpaces
, isFAT32
);
463 CPath
CPath::AddPostfix(const wxString
& postfix
) const
465 wxASSERT(postfix
.IsAscii());
468 result
.m_printable
= ::DoAddPostfix(m_printable
, postfix
);
469 result
.m_filesystem
= ::DoAddPostfix(m_filesystem
, postfix
);
475 CPath
CPath::AppendExt(const wxString
& ext
) const
477 wxASSERT(ext
.IsAscii());
479 // Though technically, and empty extension would simply
480 // be another . at the end of the filename, we ignore them.
486 if (ext
[0] == wxT('.')) {
487 result
.m_printable
<< ext
;
488 result
.m_filesystem
<< ext
;
490 result
.m_printable
<< wxT(".") << ext
;
491 result
.m_filesystem
<< wxT(".") << ext
;
498 CPath
CPath::RemoveExt() const
501 result
.m_printable
= DoRemoveExt(m_printable
);
502 result
.m_filesystem
= DoRemoveExt(m_filesystem
);
508 CPath
CPath::RemoveAllExt() const
510 CPath last
, current
= RemoveExt();
512 // Loop until all extensions are removed
516 current
= last
.RemoveExt();
517 } while (last
!= current
);
523 bool CPath::StartsWith(const CPath
& other
) const
525 // It doesn't make sense comparing invalid paths,
526 // especially since if 'other' was empty, it would
527 // be considered a prefix of any path.
528 if ((IsOk() && other
.IsOk()) == false) {
532 // Adding an seperator to avoid partial matches, such as
533 // "/usr/bi" matching "/usr/bin". TODO: Paths should be
534 // normalized first (in the constructor).
535 const wxString a
= StripSeparators(m_filesystem
, wxString::trailing
) + wxFileName::GetPathSeparator();
536 const wxString b
= StripSeparators(other
.m_filesystem
, wxString::trailing
) + wxFileName::GetPathSeparator();
538 if (a
.Length() < b
.Length()) {
539 // Cannot possibly be a prefix.
543 const size_t checkLen
= std::min(a
.Length(), b
.Length());
544 return PATHNCMP(a
.c_str(), b
.c_str(), checkLen
) == 0;
548 bool CPath::CloneFile(const CPath
& src
, const CPath
& dst
, bool overwrite
)
550 return ::wxCopyFile(src
.m_filesystem
, dst
.m_filesystem
, overwrite
);
554 bool CPath::RemoveFile(const CPath
& file
)
556 return ::wxRemoveFile(file
.m_filesystem
);
560 bool CPath::RenameFile(const CPath
& src
, const CPath
& dst
, bool overwrite
)
562 return ::wxRenameFile(src
.m_filesystem
, dst
.m_filesystem
, overwrite
);
566 bool CPath::BackupFile(const CPath
& src
, const wxString
& appendix
)
568 wxASSERT(appendix
.IsAscii());
570 CPath dst
= CPath(src
.m_filesystem
+ appendix
);
572 if (CPath::CloneFile(src
, dst
, true)) {
573 // Try to ensure that the backup gets physically written
574 #if defined __WINDOWS__ || defined __IRIX__
579 if (backupFile
.Open(dst
.m_filesystem
)) {
590 bool CPath::RemoveDir(const CPath
& file
)
592 return ::wxRmdir(file
.m_filesystem
);
596 bool CPath::MakeDir(const CPath
& file
)
598 return ::wxMkdir(file
.m_filesystem
);
602 bool CPath::FileExists(const wxString
& file
)
604 return CPath(file
).FileExists();
608 bool CPath::DirExists(const wxString
& path
)
610 return CPath(path
).DirExists();
614 sint64
CPath::GetFileSize(const wxString
& file
)
616 return CPath(file
).GetFileSize();
620 time_t CPath::GetModificationTime(const CPath
& file
)
622 return ::wxFileModificationTime(file
.m_filesystem
);
626 sint64
CPath::GetFreeSpaceAt(const CPath
& path
)
629 if (::wxGetDiskSpace(path
.m_filesystem
, NULL
, &free
)) {
630 return free
.GetValue();
633 return wxInvalidOffset
;
637 wxString
CPath::TruncatePath(size_t length
, bool isFilePath
) const
639 wxString file
= GetPrintable();
641 // Check if there's anything to do
642 if (file
.Length() <= length
) {
646 // If the path is a file name, then prefer to remove from the path, rather than the filename
648 wxString path
= wxFileName(file
).GetPath();
649 file
= wxFileName(file
).GetFullName();
651 if (path
.Length() >= length
) {
653 } else if (file
.Length() >= length
) {
656 // Minus 6 for "[...]" + separator
657 int pathlen
= (int)(length
- file
.Length() - 6);
660 path
= wxT("[...]") + path
.Right( pathlen
);
666 file
= ::JoinPaths(path
, file
);
669 if (file
.Length() > length
) {
671 file
= file
.Left(length
- 5) + wxT("[...]");
681 wxString
StripSeparators(wxString path
, wxString::stripType type
)
683 wxASSERT((type
== wxString::leading
) || (type
== wxString::trailing
));
684 const wxString seps
= wxFileName::GetPathSeparators();
686 while (!path
.IsEmpty()) {
687 size_t pos
= ((type
== wxString::leading
) ? 0 : path
.Length() - 1);
689 if (seps
.Contains(path
.GetChar(pos
))) {
700 wxString
JoinPaths(const wxString
& path
, const wxString
& file
)
702 if (path
.IsEmpty()) {
704 } else if (file
.IsEmpty()) {
708 return StripSeparators(path
, wxString::trailing
)
709 + wxFileName::GetPathSeparator()
710 + StripSeparators(file
, wxString::leading
);