2 // This file is part of the aMule Project.
4 // Copyright (c) 2008 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 "MuleDebug.h"
27 #include "StringFunctions.h"
30 #if defined __WXMSW__ || defined __IRIX__
31 # include <wx/ffile.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__)
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();
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 ...
63 #define PATHCMP(a, b) wxStricmp(a, b)
64 #define PATHNCMP(a, b, n) wxStrnicmp(a, b, n)
66 #define PATHCMP(a, b) wxStrcmp(a, b)
67 #define PATHNCMP(a, b, n) wxStrncmp(a, b, n)
71 ////////////////////////////////////////////////////////////
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?
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();
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
);
102 // Nothing to do, the filename is probably Ok.
103 result
= DeepCopy(filename
);
111 /** Splits a full path into its path and filename component. */
112 inline void DoSplitPath(const wxString
& strPath
, wxString
* path
, wxString
* name
)
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
)
136 for (size_t i
= 0; i
< filename
.Length(); i
++) {
137 const wxChar c
= filename
[i
];
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
];
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();
182 return path
+ wxFileName::GetPathSeparator() + 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
);
195 return tmp
.GetFullPath();
199 /** Readies a path for use with wxAccess.. */
200 wxString
DoCleanPath(const wxString
& path
)
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();
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.
238 fn1
.Normalize(flags
, cwd
);
239 fn2
.Normalize(flags
, cwd
);
241 return (fn1
.GetFullPath() == fn2
.GetFullPath());
245 ////////////////////////////////////////////////////////////
246 // CPath implementation
253 CPath::CPath(const wxString
& filename
)
255 // Equivalent to the default constructor ...
260 wxCharBuffer fn
= filename2char(filename
);
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
);
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
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
)
287 , m_printable(DeepCopy(other
.m_printable
))
288 , m_filesystem(DeepCopy(other
.m_filesystem
))
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
);
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
)) {
372 } else if ((mode
& writable
) && !wxIsWritable(path
)) {
374 } else if ((mode
& readable
) && !wxIsReadable(path
)) {
382 bool CPath::IsFile(EAccess mode
) const
384 if (!wxFileName::FileExists(m_filesystem
)) {
386 } else if ((mode
& writable
) && !wxIsWritable(m_filesystem
)) {
388 } else if ((mode
& readable
) && !wxIsReadable(m_filesystem
)) {
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
421 ::DoSplitPath(m_printable
, &path
.m_printable
, NULL
);
422 ::DoSplitPath(m_filesystem
, &path
.m_filesystem
, NULL
);
428 CPath
CPath::GetFullName() const
431 ::DoSplitPath(m_printable
, NULL
, &path
.m_printable
);
432 ::DoSplitPath(m_filesystem
, NULL
, &path
.m_filesystem
);
439 sint64
CPath::GetFileSize() const
442 wxFile
f(m_filesystem
);
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
473 } else if (!other
.IsOk()) {
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
);
486 CPath
CPath::Cleanup(bool keepSpaces
, bool isFAT32
) const
489 result
.m_printable
= ::DoCleanup(m_printable
, keepSpaces
, isFAT32
);
490 result
.m_filesystem
= ::DoCleanup(m_filesystem
, keepSpaces
, isFAT32
);
496 CPath
CPath::AddPostfix(const wxString
& postfix
) const
498 wxASSERT(postfix
.IsAscii());
501 result
.m_printable
= ::DoAddPostfix(m_printable
, postfix
);
502 result
.m_filesystem
= ::DoAddPostfix(m_filesystem
, postfix
);
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.
519 if (ext
[0] == wxT('.')) {
520 result
.m_printable
<< ext
;
521 result
.m_filesystem
<< ext
;
523 result
.m_printable
<< wxT(".") << ext
;
524 result
.m_filesystem
<< wxT(".") << ext
;
531 CPath
CPath::RemoveExt() const
534 result
.m_printable
= DoRemoveExt(m_printable
);
535 result
.m_filesystem
= DoRemoveExt(m_filesystem
);
541 CPath
CPath::RemoveAllExt() const
543 CPath last
, current
= RemoveExt();
545 // Loop untill all extensions are removed
549 current
= last
.RemoveExt();
550 } while (last
!= 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) {
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.
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
615 #if defined __WXMSW__ || defined __IRIX__
620 if (backupFile
.Open(dst
.m_filesystem
)) {
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
)
670 if (::wxGetDiskSpace(path
.m_filesystem
, NULL
, &free
)) {
671 return free
.GetValue();
674 return wxInvalidOffset
;