aboutsummaryrefslogtreecommitdiff
path: root/mmc_updater/src
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2013-12-02 00:55:24 +0100
committerPetr Mrázek <peterix@gmail.com>2013-12-02 00:55:24 +0100
commit6aa9bd0f77dcb5128167fae62e32aa5252fe85c6 (patch)
tree632994a61888929af9289927d338bd19a2b3f32c /mmc_updater/src
parent613699b3626aea750093ab7eaaeccaa28c0e87c6 (diff)
downloadPrismLauncher-6aa9bd0f77dcb5128167fae62e32aa5252fe85c6.tar.gz
PrismLauncher-6aa9bd0f77dcb5128167fae62e32aa5252fe85c6.tar.bz2
PrismLauncher-6aa9bd0f77dcb5128167fae62e32aa5252fe85c6.zip
Renew the updater branch
Now with some actual consensus on what the updater will do!
Diffstat (limited to 'mmc_updater/src')
-rw-r--r--mmc_updater/src/AppInfo.cpp23
-rw-r--r--mmc_updater/src/AppInfo.h39
-rw-r--r--mmc_updater/src/CMakeLists.txt121
-rw-r--r--mmc_updater/src/DirIterator.cpp85
-rw-r--r--mmc_updater/src/DirIterator.h43
-rw-r--r--mmc_updater/src/FileUtils.cpp557
-rw-r--r--mmc_updater/src/FileUtils.h141
-rw-r--r--mmc_updater/src/Log.cpp65
-rw-r--r--mmc_updater/src/Log.h46
-rw-r--r--mmc_updater/src/MacBundle.cpp53
-rw-r--r--mmc_updater/src/MacBundle.h35
-rw-r--r--mmc_updater/src/Platform.h30
-rw-r--r--mmc_updater/src/ProcessUtils.cpp536
-rw-r--r--mmc_updater/src/ProcessUtils.h97
-rw-r--r--mmc_updater/src/StandardDirs.cpp63
-rw-r--r--mmc_updater/src/StandardDirs.h22
-rw-r--r--mmc_updater/src/StandardDirs.mm18
-rw-r--r--mmc_updater/src/StlSymbolsLeopard.cpp75
-rw-r--r--mmc_updater/src/StringUtils.h46
-rw-r--r--mmc_updater/src/UpdateDialog.cpp25
-rw-r--r--mmc_updater/src/UpdateDialog.h29
-rw-r--r--mmc_updater/src/UpdateDialogAscii.cpp70
-rw-r--r--mmc_updater/src/UpdateDialogAscii.h32
-rw-r--r--mmc_updater/src/UpdateDialogCocoa.h32
-rw-r--r--mmc_updater/src/UpdateDialogCocoa.mm194
-rw-r--r--mmc_updater/src/UpdateDialogGtk.cpp155
-rw-r--r--mmc_updater/src/UpdateDialogGtk.h42
-rw-r--r--mmc_updater/src/UpdateDialogGtkFactory.cpp59
-rw-r--r--mmc_updater/src/UpdateDialogGtkFactory.h13
-rw-r--r--mmc_updater/src/UpdateDialogWin32.cpp215
-rw-r--r--mmc_updater/src/UpdateDialogWin32.h39
-rw-r--r--mmc_updater/src/UpdateInstaller.cpp439
-rw-r--r--mmc_updater/src/UpdateInstaller.h70
-rw-r--r--mmc_updater/src/UpdateMessage.h42
-rw-r--r--mmc_updater/src/UpdateObserver.h15
-rw-r--r--mmc_updater/src/UpdateScript.cpp98
-rw-r--r--mmc_updater/src/UpdateScript.h86
-rw-r--r--mmc_updater/src/UpdaterOptions.cpp156
-rw-r--r--mmc_updater/src/UpdaterOptions.h27
-rw-r--r--mmc_updater/src/main.cpp201
-rw-r--r--mmc_updater/src/resources/Info.plist38
-rw-r--r--mmc_updater/src/resources/icon128.pngbin0 -> 3802 bytes
-rw-r--r--mmc_updater/src/resources/icon64.pngbin0 -> 2182 bytes
-rw-r--r--mmc_updater/src/resources/mac.icnsbin0 -> 43606 bytes
-rw-r--r--mmc_updater/src/resources/updater.icobin0 -> 82726 bytes
-rw-r--r--mmc_updater/src/resources/updater.rc30
-rw-r--r--mmc_updater/src/tests/CMakeLists.txt51
-rw-r--r--mmc_updater/src/tests/TestFileUtils.cpp50
-rw-r--r--mmc_updater/src/tests/TestFileUtils.h10
-rw-r--r--mmc_updater/src/tests/TestUpdateScript.cpp48
-rw-r--r--mmc_updater/src/tests/TestUpdateScript.h9
-rw-r--r--mmc_updater/src/tests/TestUpdaterOptions.cpp68
-rw-r--r--mmc_updater/src/tests/TestUpdaterOptions.h8
-rw-r--r--mmc_updater/src/tests/TestUtils.h108
-rw-r--r--mmc_updater/src/tests/file_list.xml52
-rw-r--r--mmc_updater/src/tests/new_app.cpp8
-rw-r--r--mmc_updater/src/tests/old_app.cpp7
-rwxr-xr-xmmc_updater/src/tests/test-update.rb218
-rw-r--r--mmc_updater/src/tests/v2_file_list.xml67
59 files changed, 4906 insertions, 0 deletions
diff --git a/mmc_updater/src/AppInfo.cpp b/mmc_updater/src/AppInfo.cpp
new file mode 100644
index 00000000..a5a9bb63
--- /dev/null
+++ b/mmc_updater/src/AppInfo.cpp
@@ -0,0 +1,23 @@
+#include "AppInfo.h"
+
+#include "FileUtils.h"
+#include "Platform.h"
+#include "StringUtils.h"
+#include "StandardDirs.h"
+
+#include <iostream>
+
+std::string AppInfo::logFilePath()
+{
+ return StandardDirs::appDataPath(organizationName(),appName()) + '/' + "update-log.txt";
+}
+
+std::string AppInfo::updateErrorMessage(const std::string& details)
+{
+ std::string result = "There was a problem installing the update:\n\n";
+ result += details;
+ result += "\n\nYou can try downloading and installing the latest version of "
+ "MultiMC from http://multimc.org/";
+ return result;
+}
+
diff --git a/mmc_updater/src/AppInfo.h b/mmc_updater/src/AppInfo.h
new file mode 100644
index 00000000..51d95886
--- /dev/null
+++ b/mmc_updater/src/AppInfo.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <string>
+
+/** This class provides project-specific updater properties,
+ * such as the name of the application being updated and
+ * the path to log details of the update install to.
+ */
+class AppInfo
+{
+ public:
+ // Basic application information
+ static std::string name();
+ static std::string appName();
+ static std::string organizationName();
+
+ static std::string logFilePath();
+
+ /** Returns a message to display to the user in the event
+ * of a problem installing the update.
+ */
+ static std::string updateErrorMessage(const std::string& details);
+};
+
+inline std::string AppInfo::name()
+{
+ return "MultiMC Updater";
+}
+
+inline std::string AppInfo::appName()
+{
+ return "MultiMC";
+}
+
+inline std::string AppInfo::organizationName()
+{
+ return "MultiMC Contributors";
+}
+
diff --git a/mmc_updater/src/CMakeLists.txt b/mmc_updater/src/CMakeLists.txt
new file mode 100644
index 00000000..9b39bb83
--- /dev/null
+++ b/mmc_updater/src/CMakeLists.txt
@@ -0,0 +1,121 @@
+
+add_subdirectory(tests)
+
+find_package(Threads REQUIRED)
+include(GenerateCppResourceFile)
+
+set (UPDATER_SOURCES
+ AppInfo.cpp
+ AppInfo.h
+ DirIterator.cpp
+ DirIterator.h
+ FileUtils.cpp
+ FileUtils.h
+ Log.cpp
+ Log.h
+ ProcessUtils.cpp
+ ProcessUtils.h
+ StandardDirs.cpp
+ StandardDirs.h
+ UpdateDialog.cpp
+ UpdateInstaller.cpp
+ UpdateInstaller.h
+ UpdateScript.cpp
+ UpdateScript.h
+ UpdaterOptions.cpp
+ UpdaterOptions.h
+)
+
+add_definitions(-DTIXML_USE_STL)
+
+if (WIN32)
+ set(UPDATER_SOURCES ${UPDATER_SOURCES} UpdateDialogWin32.cpp UpdateDialogWin32.h)
+endif()
+
+if (UNIX)
+ set(UPDATER_SOURCES ${UPDATER_SOURCES} UpdateDialogAscii.cpp UpdateDialogAscii.h)
+ add_definitions(-Wall -Werror -Wconversion)
+if (APPLE)
+ set(MAC_DOCK_ICON_CPP_FILE ${CMAKE_CURRENT_BINARY_DIR}/mac_dock_icon.cpp)
+ set(MAC_INFO_PLIST_FILE ${CMAKE_CURRENT_BINARY_DIR}/mac_info_plist.cpp)
+ generate_cpp_resource_file(resource_macdockicon ${CMAKE_CURRENT_SOURCE_DIR}/resources/mac.icns ${MAC_DOCK_ICON_CPP_FILE})
+ generate_cpp_resource_file(resource_macplist ${CMAKE_CURRENT_SOURCE_DIR}/resources/Info.plist ${MAC_INFO_PLIST_FILE})
+ set(UPDATER_SOURCES ${UPDATER_SOURCES}
+ MacBundle.h
+ MacBundle.cpp
+ StandardDirs.mm
+ StlSymbolsLeopard.cpp
+ UpdateDialogCocoa.mm
+ UpdateDialogCocoa.h
+ mac_dock_icon.cpp
+ mac_info_plist.cpp
+ )
+else() # linuxes and other similar systems
+ find_package(GTK2 REQUIRED gtk)
+ include_directories(${GTK2_INCLUDE_DIRS})
+ add_library(updatergtk SHARED UpdateDialogGtk.cpp UpdateDialogGtk.h)
+ target_link_libraries(updatergtk ${GTK2_LIBRARIES})
+
+ # embed the GTK helper library into the updater binary.
+ # At runtime it will be extracted and loaded if the
+ # GTK libraries are available
+ get_property(GTK_UPDATER_LIB TARGET updatergtk PROPERTY LOCATION)
+ set(GTK_BIN_CPP_FILE ${CMAKE_CURRENT_BINARY_DIR}/libupdatergtk.cpp)
+ generate_cpp_resource_file(resource_updatergtk ${GTK_UPDATER_LIB} ${GTK_BIN_CPP_FILE})
+ add_dependencies(resource_updatergtk updatergtk)
+
+ set(UPDATER_SOURCES ${UPDATER_SOURCES} UpdateDialogGtkFactory.cpp UpdateDialogGtkFactory.h ${GTK_BIN_CPP_FILE})
+endif()
+endif()
+
+add_library(updatershared STATIC ${UPDATER_SOURCES})
+
+target_link_libraries(updatershared
+ anyoption
+ tinyxml
+)
+
+if (UNIX)
+ if (APPLE)
+ find_library(COCOA_LIBRARY Cocoa)
+ find_library(SECURITY_LIBRARY Security)
+ target_link_libraries(updatershared ${SECURITY_LIBRARY} ${COCOA_LIBRARY})
+ else()
+ add_dependencies(updatershared resource_updatergtk)
+ endif()
+ target_link_libraries(updatershared pthread dl)
+endif()
+
+if (WIN32)
+ set(EXE_FLAGS WIN32 resources/updater.rc)
+endif()
+
+add_executable(updater ${EXE_FLAGS} main.cpp)
+
+target_link_libraries(updater
+ updatershared
+)
+
+
+#### Updater Executable ####
+IF(WIN32)
+INSTALL(TARGETS updater
+ BUNDLE DESTINATION . COMPONENT Runtime
+ LIBRARY DESTINATION . COMPONENT Runtime
+ RUNTIME DESTINATION . COMPONENT Runtime
+)
+ENDIF()
+IF(UNIX)
+IF(APPLE)
+INSTALL(TARGETS updater
+ BUNDLE DESTINATION . COMPONENT Runtime
+ RUNTIME DESTINATION MultiMC.app/Contents/MacOS COMPONENT Runtime
+)
+ELSE()
+INSTALL(TARGETS updater
+ BUNDLE DESTINATION . COMPONENT Runtime
+ RUNTIME DESTINATION bin COMPONENT Runtime
+)
+ENDIF()
+ENDIF()
+
diff --git a/mmc_updater/src/DirIterator.cpp b/mmc_updater/src/DirIterator.cpp
new file mode 100644
index 00000000..a4604f05
--- /dev/null
+++ b/mmc_updater/src/DirIterator.cpp
@@ -0,0 +1,85 @@
+#include "DirIterator.h"
+
+#include "Log.h"
+#include "StringUtils.h"
+
+#ifdef PLATFORM_UNIX
+ #include <dirent.h>
+#endif
+
+#include <string.h>
+
+DirIterator::DirIterator(const char* path)
+{
+ m_path = path;
+
+#ifdef PLATFORM_UNIX
+ m_dir = opendir(path);
+ m_entry = 0;
+#else
+ // to list the contents of a directory, the first
+ // argument to FindFirstFile needs to be a wildcard
+ // of the form: C:\path\to\dir\*
+ std::string searchPath = m_path;
+ if (!endsWith(searchPath,"/"))
+ {
+ searchPath.append("/");
+ }
+ searchPath.append("*");
+ m_findHandle = FindFirstFile(searchPath.c_str(),&m_findData);
+ m_firstEntry = true;
+#endif
+}
+
+DirIterator::~DirIterator()
+{
+#ifdef PLATFORM_UNIX
+ closedir(m_dir);
+#else
+ FindClose(m_findHandle);
+#endif
+}
+
+bool DirIterator::next()
+{
+#ifdef PLATFORM_UNIX
+ m_entry = readdir(m_dir);
+ return m_entry != 0;
+#else
+ bool result;
+ if (m_firstEntry)
+ {
+ m_firstEntry = false;
+ return m_findHandle != INVALID_HANDLE_VALUE;
+ }
+ else
+ {
+ result = FindNextFile(m_findHandle,&m_findData);
+ }
+ return result;
+#endif
+}
+
+std::string DirIterator::fileName() const
+{
+#ifdef PLATFORM_UNIX
+ return m_entry->d_name;
+#else
+ return m_findData.cFileName;
+#endif
+}
+
+std::string DirIterator::filePath() const
+{
+ return m_path + '/' + fileName();
+}
+
+bool DirIterator::isDir() const
+{
+#ifdef PLATFORM_UNIX
+ return m_entry->d_type == DT_DIR;
+#else
+ return (m_findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+#endif
+}
+
diff --git a/mmc_updater/src/DirIterator.h b/mmc_updater/src/DirIterator.h
new file mode 100644
index 00000000..f3fbb955
--- /dev/null
+++ b/mmc_updater/src/DirIterator.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "Platform.h"
+
+#include <string>
+
+#ifdef PLATFORM_UNIX
+#include <dirent.h>
+#endif
+
+/** Simple class for iterating over the files in a directory
+ * and reporting their names and types.
+ */
+class DirIterator
+{
+ public:
+ DirIterator(const char* path);
+ ~DirIterator();
+
+ // iterate to the next entry in the directory
+ bool next();
+
+ // methods to return information about
+ // the current entry
+ std::string fileName() const;
+ std::string filePath() const;
+ bool isDir() const;
+
+ private:
+ std::string m_path;
+
+#ifdef PLATFORM_UNIX
+ DIR* m_dir;
+ dirent* m_entry;
+#endif
+
+#ifdef PLATFORM_WINDOWS
+ HANDLE m_findHandle;
+ WIN32_FIND_DATA m_findData;
+ bool m_firstEntry;
+#endif
+};
+
diff --git a/mmc_updater/src/FileUtils.cpp b/mmc_updater/src/FileUtils.cpp
new file mode 100644
index 00000000..10435e49
--- /dev/null
+++ b/mmc_updater/src/FileUtils.cpp
@@ -0,0 +1,557 @@
+#include "FileUtils.h"
+
+#include "DirIterator.h"
+#include "Log.h"
+#include "Platform.h"
+#include "StringUtils.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <string.h>
+#include <fstream>
+#include <iostream>
+
+#ifdef PLATFORM_UNIX
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <libgen.h>
+#endif
+
+FileUtils::IOException::IOException(const std::string& error)
+{
+ init(errno,error);
+}
+
+FileUtils::IOException::IOException(int errorCode, const std::string& error)
+{
+ init(errorCode,error);
+}
+
+void FileUtils::IOException::init(int errorCode, const std::string& error)
+{
+ m_error = error;
+
+#ifdef PLATFORM_UNIX
+ m_errorCode = errorCode;
+
+ if (m_errorCode > 0)
+ {
+ m_error += " details: " + std::string(strerror(m_errorCode));
+ }
+#endif
+
+#ifdef PLATFORM_WINDOWS
+ m_errorCode = 0;
+ m_error += " GetLastError returned: " + intToStr(GetLastError());
+#endif
+}
+
+FileUtils::IOException::~IOException() throw ()
+{
+}
+
+FileUtils::IOException::Type FileUtils::IOException::type() const
+{
+#ifdef PLATFORM_UNIX
+ switch (m_errorCode)
+ {
+ case 0:
+ return NoError;
+ case EROFS:
+ return ReadOnlyFileSystem;
+ case ENOSPC:
+ return DiskFull;
+ default:
+ return Unknown;
+ }
+#else
+ return Unknown;
+#endif
+}
+
+bool FileUtils::fileExists(const char* path) throw (IOException)
+{
+#ifdef PLATFORM_UNIX
+ struct stat fileInfo;
+ if (lstat(path,&fileInfo) != 0)
+ {
+ if (errno == ENOENT)
+ {
+ return false;
+ }
+ else
+ {
+ throw IOException("Error checking for file " + std::string(path));
+ }
+ }
+ return true;
+#else
+ DWORD result = GetFileAttributes(path);
+ if (result == INVALID_FILE_ATTRIBUTES)
+ {
+ return false;
+ }
+ return true;
+#endif
+}
+
+int FileUtils::fileMode(const char* path) throw (IOException)
+{
+#ifdef PLATFORM_UNIX
+ struct stat fileInfo;
+ if (stat(path,&fileInfo) != 0)
+ {
+ throw IOException("Error reading file permissions for " + std::string(path));
+ }
+ return fileInfo.st_mode;
+#else
+ // not implemented for Windows
+ return 0;
+#endif
+}
+
+void FileUtils::chmod(const char* path, int mode) throw (IOException)
+{
+#ifdef PLATFORM_UNIX
+ if (::chmod(path,static_cast<mode_t>(mode)) != 0)
+ {
+ throw IOException("Failed to set permissions on " + std::string(path) + " to " + intToStr(mode));
+ }
+#else
+ // TODO - Not implemented under Windows - all files
+ // get default permissions
+#endif
+}
+
+void FileUtils::moveFile(const char* src, const char* dest) throw (IOException)
+{
+#ifdef PLATFORM_UNIX
+ if (rename(src,dest) != 0)
+ {
+ throw IOException("Unable to rename " + std::string(src) + " to " + std::string(dest));
+ }
+#else
+ if (!MoveFile(src,dest))
+ {
+ throw IOException("Unable to rename " + std::string(src) + " to " + std::string(dest));
+ }
+#endif
+}
+
+void FileUtils::mkpath(const char* dir) throw (IOException)
+{
+ std::string currentPath;
+ std::istringstream stream(dir);
+ while (!stream.eof())
+ {
+ std::string segment;
+ std::getline(stream,segment,'/');
+ currentPath += segment;
+ if (!currentPath.empty() && !fileExists(currentPath.c_str()))
+ {
+ mkdir(currentPath.c_str());
+ }
+ currentPath += '/';
+ }
+}
+
+void FileUtils::mkdir(const char* dir) throw (IOException)
+{
+#ifdef PLATFORM_UNIX
+ if (::mkdir(dir,S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) != 0)
+ {
+ throw IOException("Unable to create directory " + std::string(dir));
+ }
+#else
+ if (!CreateDirectory(dir,0 /* default security attributes */))
+ {
+ throw IOException("Unable to create directory " + std::string(dir));
+ }
+#endif
+}
+
+void FileUtils::rmdir(const char* dir) throw (IOException)
+{
+#ifdef PLATFORM_UNIX
+ if (::rmdir(dir) != 0)
+ {
+ throw IOException("Unable to remove directory " + std::string(dir));
+ }
+#else
+ if (!RemoveDirectory(dir))
+ {
+ throw IOException("Unable to remove directory " + std::string(dir));
+ }
+#endif
+}
+
+void FileUtils::createSymLink(const char* link, const char* target) throw (IOException)
+{
+#ifdef PLATFORM_UNIX
+ if (symlink(target,link) != 0)
+ {
+ throw IOException("Unable to create symlink " + std::string(link) + " to " + std::string(target));
+ }
+#else
+ // symlinks are not supported under Windows (at least, not universally.
+ // Windows Vista and later do actually support symlinks)
+ LOG(Warn,"Skipping symlink creation - not implemented in Windows");
+#endif
+}
+
+void FileUtils::removeFile(const char* src) throw (IOException)
+{
+#ifdef PLATFORM_UNIX
+ if (unlink(src) != 0)
+ {
+ if (errno != ENOENT)
+ {
+ throw IOException("Unable to remove file " + std::string(src));
+ }
+ }
+#else
+ if (!DeleteFile(src))
+ {
+ if (GetLastError() == ERROR_ACCESS_DENIED)
+ {
+ // if another process is using the file, try moving it to
+ // a temporary directory and then
+ // scheduling it for deletion on reboot
+ std::string tempDeletePathBase = tempPath();
+ tempDeletePathBase += '/';
+ tempDeletePathBase += fileName(src);
+
+ int suffix = 0;
+ std::string tempDeletePath = tempDeletePathBase;
+ while (fileExists(tempDeletePath.c_str()))
+ {
+ ++suffix;
+ tempDeletePath = tempDeletePathBase + '_' + intToStr(suffix);
+ }
+
+ LOG(Warn,"Unable to remove file " + std::string(src) + " - it may be in use. Moving to "
+ + tempDeletePath + " and scheduling delete on reboot.");
+ moveFile(src,tempDeletePath.c_str());
+ MoveFileEx(tempDeletePath.c_str(),0,MOVEFILE_DELAY_UNTIL_REBOOT);
+ }
+ else if (GetLastError() != ERROR_FILE_NOT_FOUND)
+ {
+ throw IOException("Unable to remove file " + std::string(src));
+ }
+ }
+#endif
+}
+
+std::string FileUtils::fileName(const char* path)
+{
+#ifdef PLATFORM_UNIX
+ char* pathCopy = strdup(path);
+ std::string basename = ::basename(pathCopy);
+ free(pathCopy);
+ return basename;
+#else
+ char baseName[MAX_PATH];
+ char extension[MAX_PATH];
+ _splitpath_s(path,
+ 0, /* drive */
+ 0, /* drive length */
+ 0, /* dir */
+ 0, /* dir length */
+ baseName,
+ MAX_PATH, /* baseName length */
+ extension,
+ MAX_PATH /* extension length */
+ );
+ return std::string(baseName) + std::string(extension);
+#endif
+}
+
+std::string FileUtils::dirname(const char* path)
+{
+#ifdef PLATFORM_UNIX
+ char* pathCopy = strdup(path);
+ std::string dirname = ::dirname(pathCopy);
+ free(pathCopy);
+ return dirname;
+#else
+ char drive[3];
+ char dir[MAX_PATH];
+
+ _splitpath_s(path,
+ drive, /* drive */
+ 3, /* drive length */
+ dir,
+ MAX_PATH, /* dir length */
+ 0, /* filename */
+ 0, /* filename length */
+ 0, /* extension */
+ 0 /* extension length */
+ );
+
+ std::string result;
+ if (drive[0])
+ {
+ result += std::string(drive);
+ }
+ result += dir;
+
+ return result;
+#endif
+}
+
+void FileUtils::touch(const char* path) throw (IOException)
+{
+#ifdef PLATFORM_UNIX
+ // see http://pubs.opengroup.org/onlinepubs/9699919799/utilities/touch.html
+ //
+ // we use utimes/futimes instead of utimensat/futimens for compatibility
+ // with older Linux and Mac
+
+ if (fileExists(path))
+ {
+ utimes(path,0 /* use current date/time */);
+ }
+ else
+ {
+ int fd = creat(path,S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (fd != -1)
+ {
+ futimes(fd,0 /* use current date/time */);
+ close(fd);
+ }
+ else
+ {
+ throw IOException("Unable to touch file " + std::string(path));
+ }
+ }
+#else
+ HANDLE result = CreateFile(path,GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ 0);
+ if (result == INVALID_HANDLE_VALUE)
+ {
+ throw IOException("Unable to touch file " + std::string(path));
+ }
+ else
+ {
+ CloseHandle(result);
+ }
+#endif
+}
+
+void FileUtils::rmdirRecursive(const char* path) throw (IOException)
+{
+ // remove dir contents
+ DirIterator dir(path);
+ while (dir.next())
+ {
+ std::string name = dir.fileName();
+ if (name != "." && name != "..")
+ {
+ if (dir.isDir())
+ {
+ rmdir(dir.filePath().c_str());
+ }
+ else
+ {
+ removeFile(dir.filePath().c_str());
+ }
+ }
+ }
+
+ // remove the directory itself
+ rmdir(path);
+}
+
+std::string FileUtils::canonicalPath(const char* path)
+{
+#ifdef PLATFORM_UNIX
+ // on Linux and Mac OS 10.6, realpath() can allocate the required
+ // amount of memory automatically, however Mac OS 10.5 does not support
+ // this, so we used a fixed-sized buffer on all platforms
+ char canonicalPathBuffer[PATH_MAX+1];
+ if (realpath(path,canonical