summaryrefslogtreecommitdiff
path: root/shared/resolve_path.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'shared/resolve_path.cpp')
-rw-r--r--shared/resolve_path.cpp319
1 files changed, 319 insertions, 0 deletions
diff --git a/shared/resolve_path.cpp b/shared/resolve_path.cpp
new file mode 100644
index 00000000..b17c3fb5
--- /dev/null
+++ b/shared/resolve_path.cpp
@@ -0,0 +1,319 @@
+#include "resolve_path.h"
+#include <boost/scoped_array.hpp>
+#include <wx/utils.h>
+#include <wx/datetime.h>
+#include "string_conv.h"
+#include "system_constants.h"
+#include "loki/ScopeGuard.h"
+
+#ifdef FFS_WIN
+#include "dll_loader.h"
+#include <wx/msw/wrapwin.h> //includes "windows.h"
+#include "long_path_prefix.h"
+
+#elif defined FFS_LINUX
+#include <map>
+#include "file_traverser.h"
+#include "stdlib.h"
+#endif
+
+using namespace ffs3;
+using namespace common;
+
+
+namespace
+{
+#ifdef FFS_WIN
+Zstring resolveRelativePath(const Zstring& relativeName, DWORD proposedBufferSize = 1000)
+{
+ boost::scoped_array<Zchar> fullPath(new Zchar[proposedBufferSize]);
+ const DWORD rv = ::GetFullPathName(
+ applyLongPathPrefix(relativeName).c_str(), //__in LPCTSTR lpFileName,
+ proposedBufferSize, //__in DWORD nBufferLength,
+ fullPath.get(), //__out LPTSTR lpBuffer,
+ NULL); //__out LPTSTR *lpFilePart
+ if (rv == 0 || rv == proposedBufferSize)
+ //ERROR! Don't do anything
+ return relativeName;
+ if (rv > proposedBufferSize)
+ return resolveRelativePath(relativeName, rv);
+
+ return fullPath.get();
+}
+
+#elif defined FFS_LINUX
+Zstring resolveRelativePath(const Zstring& relativeName) //additional: resolves symbolic links!!!
+{
+ char absolutePath[PATH_MAX + 1];
+ if (::realpath(relativeName.c_str(), absolutePath) == NULL)
+ //ERROR! Don't do anything
+ return relativeName;
+
+ return Zstring(absolutePath);
+}
+#endif
+
+
+bool replaceMacro(wxString& macro) //macro without %-characters, return true if replaced successfully
+{
+ if (macro.IsEmpty())
+ return false;
+
+ //there are equally named environment variables %TIME%, %DATE% existing, so replace these first!
+ if (macro.CmpNoCase(wxT("time")) == 0)
+ {
+ macro = wxDateTime::Now().FormatISOTime();
+ macro.Replace(wxT(":"), wxT(""));
+ return true;
+ }
+
+ if (macro.CmpNoCase(wxT("date")) == 0)
+ {
+ macro = wxDateTime::Now().FormatISODate();
+ return true;
+ }
+
+ if (macro.CmpNoCase(wxT("weekday")) == 0)
+ {
+ macro = wxDateTime::Now().Format(wxT("%A"));
+ return true;
+ }
+
+ if (macro.CmpNoCase(wxT("month")) == 0)
+ {
+ macro = wxDateTime::Now().Format(wxT("%B"));
+ return true;
+ }
+
+ if (macro.CmpNoCase(wxT("week")) == 0)
+ {
+ macro = wxDateTime::Now().Format(wxT("%U"));
+ return true;
+ }
+
+ if (macro.CmpNoCase(wxT("year")) == 0)
+ {
+ macro = wxDateTime::Now().Format(wxT("%Y"));
+ return true;
+ }
+
+ //try to apply environment variables
+ wxString envValue;
+ if (wxGetEnv(macro, &envValue))
+ {
+ macro = envValue;
+
+ //some postprocessing:
+ macro.Trim(true); //remove leading, trailing blanks
+ macro.Trim(false); //
+
+ //remove leading, trailing double-quotes
+ if (macro.StartsWith(wxT("\"")) &&
+ macro.EndsWith(wxT("\"")) &&
+ macro.length() >= 2)
+ macro = wxString(macro.c_str() + 1, macro.length() - 2);
+ return true;
+ }
+
+ return false;
+}
+
+
+void expandMacros(wxString& text)
+{
+ const wxChar SEPARATOR = '%';
+
+ if (text.Find(SEPARATOR) != wxNOT_FOUND)
+ {
+ wxString prefix = text.BeforeFirst(SEPARATOR);
+ wxString postfix = text.AfterFirst(SEPARATOR);
+ if (postfix.Find(SEPARATOR) != wxNOT_FOUND)
+ {
+ wxString potentialMacro = postfix.BeforeFirst(SEPARATOR);
+ wxString rest = postfix.AfterFirst(SEPARATOR); //text == prefix + SEPARATOR + potentialMacro + SEPARATOR + rest
+
+ if (replaceMacro(potentialMacro))
+ {
+ expandMacros(rest);
+ text = prefix + potentialMacro + rest;
+ }
+ else
+ {
+ rest = SEPARATOR + rest;
+ expandMacros(rest);
+ text = prefix + SEPARATOR + potentialMacro + rest;
+ }
+ }
+ }
+}
+
+
+#ifdef FFS_LINUX
+class TraverseMedia : public ffs3::TraverseCallback
+{
+public:
+ typedef std::map<Zstring, Zstring> DeviceList; //device name -> device path mapping
+
+ TraverseMedia(DeviceList& devices) : devices_(devices) {}
+
+ virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) {}
+ virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) {}
+ virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName)
+ {
+ devices_.insert(std::make_pair(shortName, fullName));
+ return Loki::Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs
+ }
+ virtual void onError(const wxString& errorText) {}
+
+private:
+ DeviceList& devices_;
+};
+#endif
+
+
+Zstring getVolumePath(const Zstring& volumeName) //empty string on error
+{
+#ifdef FFS_WIN
+ const size_t volGuidSize = 10000;
+ boost::scoped_array<wchar_t> volGuid(new wchar_t[volGuidSize]);
+
+ HANDLE hVol = ::FindFirstVolume(volGuid.get(), volGuidSize);
+ if (hVol != INVALID_HANDLE_VALUE)
+ {
+ Loki::ScopeGuard dummy = Loki::MakeGuard(::FindVolumeClose, hVol);
+ (void)dummy;
+
+ do
+ {
+ const size_t volNameSize = MAX_PATH + 1;
+ boost::scoped_array<wchar_t> volName(new wchar_t[volNameSize]);
+
+ if (::GetVolumeInformation(volGuid.get(), //__in_opt LPCTSTR lpRootPathName,
+ volName.get(), //__out LPTSTR lpVolumeNameBuffer,
+ volNameSize, //__in DWORD nVolumeNameSize,
+ NULL, //__out_opt LPDWORD lpVolumeSerialNumber,
+ NULL, //__out_opt LPDWORD lpMaximumComponentLength,
+ NULL, //__out_opt LPDWORD lpFileSystemFlags,
+ NULL, //__out LPTSTR lpFileSystemNameBuffer,
+ 0)) //__in DWORD nFileSystemNameSize
+ {
+ if (EqualFilename()(volumeName, Zstring(volName.get())))
+ {
+ //GetVolumePathNamesForVolumeName is not available for Windows 2000!
+ typedef BOOL (WINAPI *GetVolumePathNamesForVolumeNameWFunc)(LPCWSTR lpszVolumeName,
+ LPWCH lpszVolumePathNames,
+ DWORD cchBufferLength,
+ PDWORD lpcchReturnLength);
+
+ static const GetVolumePathNamesForVolumeNameWFunc getVolumePathNamesForVolumeName =
+ util::getDllFun<GetVolumePathNamesForVolumeNameWFunc>(L"kernel32.dll", "GetVolumePathNamesForVolumeNameW");
+
+ if (getVolumePathNamesForVolumeName != NULL)
+ {
+ const DWORD volPathSize = 10000;
+ boost::scoped_array<wchar_t> volPath(new wchar_t[volPathSize]);
+
+ DWORD returnedLen = 0;
+ if (getVolumePathNamesForVolumeName(volGuid.get(), //__in LPCTSTR lpszVolumeName,
+ volPath.get(), //__out LPTSTR lpszVolumePathNames,
+ volPathSize, //__in DWORD cchBufferLength,
+ &returnedLen)) //__out PDWORD lpcchReturnLength
+ {
+ return volPath.get(); //return first path name in double-null terminated list!
+ }
+ }
+ return volGuid.get(); //GUID looks ugly, but should be working correctly
+ }
+ }
+ }
+ while (::FindNextVolume(hVol, volGuid.get(), volGuidSize));
+ }
+
+#elif defined FFS_LINUX
+ //due to the naming convention on Linux /media/<volume name> this function is not that useful, but...
+
+ TraverseMedia::DeviceList deviceList;
+
+ TraverseMedia traverser(deviceList);
+ traverseFolder("/media", false, traverser); //traverse one level
+
+ TraverseMedia::DeviceList::const_iterator iter = deviceList.find(volumeName);
+ if (iter != deviceList.end())
+ return iter->second;
+#endif
+ return Zstring();
+}
+
+
+void expandVolumeName(Zstring& text) // [volname]:\folder [volname]\folder [volname]folder -> C:\folder
+{
+ Zstring before;
+ Zstring volname;
+ Zstring after;
+
+ size_t posStart = text.find(Zstr("["));
+ if (posStart != Zstring::npos)
+ {
+ size_t posEnd = text.find(Zstr("]"), posStart);
+ if (posEnd != Zstring::npos)
+ {
+ before = Zstring(text.c_str(), posStart);
+ volname = Zstring(text.c_str() + posStart + 1, posEnd - posStart - 1);
+ after = Zstring(text.c_str() + posEnd + 1);
+
+ if (after.StartsWith(Zstr(":")))
+ after = after.AfterFirst(Zstr(':'));
+ if (after.StartsWith(Zstring() + FILE_NAME_SEPARATOR))
+ after = after.AfterFirst(FILE_NAME_SEPARATOR);
+ }
+ }
+
+ if (volname.empty())
+ return;
+
+ Zstring volPath = getVolumePath(volname); //return empty string on error
+ if (volPath.empty())
+ return;
+
+ if (!volPath.EndsWith(FILE_NAME_SEPARATOR))
+ volPath += FILE_NAME_SEPARATOR;
+
+ text = before + volPath + after;
+}
+}
+
+
+Zstring ffs3::getFormattedDirectoryName(const Zstring& dirname)
+{
+ //Formatting is needed since functions expect the directory to end with '\' to be able to split the relative names.
+ //note: don't combine directory formatting with wxFileName, as it doesn't respect //?/ - prefix!
+
+ wxString dirnameTmp = zToWx(dirname);
+ expandMacros(dirnameTmp);
+
+ Zstring output = wxToZ(dirnameTmp);
+
+ expandVolumeName(output);
+
+ output.Trim();
+
+ if (output.empty()) //an empty string will later be returned as "\"; this is not desired
+ return Zstring();
+
+ /*
+ resolve relative names; required by:
+ WINDOWS:
+ - \\?\-prefix which needs absolute names
+ - Volume Shadow Copy: volume name needs to be part of each filename
+ - file icon buffer (at least for extensions that are actually read from disk, e.g. "exe")
+ - ::SHFileOperation(): Using relative path names is not thread safe
+ WINDOWS/LINUX:
+ - detection of dependent directories, e.g. "\" and "C:\test"
+ */
+ output = resolveRelativePath(output);
+
+ if (!output.EndsWith(FILE_NAME_SEPARATOR))
+ output += FILE_NAME_SEPARATOR;
+
+ return output;
+}
bgstack15