diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index f2ad6229c..be045f100 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -18,6 +18,7 @@ include_directories(
set(llcommon_SOURCE_FILES
aialert.cpp
+ aifile.cpp
aiframetimer.cpp
aithreadid.cpp
imageids.cpp
@@ -108,6 +109,7 @@ set(llcommon_HEADER_FILES
CMakeLists.txt
aialert.h
+ aifile.h
aiframetimer.h
airecursive.h
aithreadid.h
diff --git a/indra/llcommon/aifile.cpp b/indra/llcommon/aifile.cpp
new file mode 100644
index 000000000..4013f3cf7
--- /dev/null
+++ b/indra/llcommon/aifile.cpp
@@ -0,0 +1,118 @@
+/**
+ * @file aifile.cpp
+ * @brief POSIX file operations that throw on error.
+ *
+ * Copyright (c) 2013, Aleric Inglewood.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution.
+ *
+ * CHANGELOG
+ * and additional copyright holders.
+ *
+ * 03/11/2013
+ * Initial version, written by Aleric Inglewood @ SL
+ */
+
+#include "linden_common.h"
+#include "aifile.h"
+#include "aialert.h"
+
+#if LL_WINDOWS
+#include
+#include // Windows errno
+#else
+#include
+#endif
+
+AIFile::AIFile(std::string const& filename, char const* accessmode)
+{
+ mFp = AIFile::fopen(filename, accessmode);
+}
+
+AIFile::~AIFile()
+{
+ AIFile::close(mFp);
+}
+
+// Like THROW_MALERTE but appends "LLFile::strerr(errn) << " (" << errn << ')'" as argument to replace [ERROR].
+#define THROW_ERROR(...) \
+ do { \
+ int errn = errno; \
+ std::ostringstream error; \
+ error << LLFile::strerr(errn) << " (" << errn << ')'; \
+ THROW_MALERT_CLASS(AIAlertCode, errn, __VA_ARGS__ ("[ERROR]", error.str())); \
+ } while(0)
+
+//static
+void AIFile::mkdir(std::string const& dirname, int perms)
+{
+ int rc = LLFile::mkdir(dirname, perms);
+ if (rc < 0 && rc != EEXIST)
+ {
+ THROW_ERROR("AIFile_mkdir_Failed_to_create_DIRNAME", AIArgs("[DIRNAME]", dirname));
+ }
+}
+
+//static
+void AIFile::rmdir(std::string const& dirname)
+{
+ int rc = LLFile::rmdir(dirname);
+ if (rc < 0 && rc != ENOENT)
+ {
+ THROW_ERROR("AIFile_rmdir_Failed_to_remove_DIRNAME", AIArgs("[DIRNAME]", dirname));
+ }
+}
+
+//static
+LLFILE* AIFile::fopen(std::string const& filename, const char* mode)
+{
+ LLFILE* fp = LLFile::fopen(filename, mode);
+ if (!fp)
+ {
+ THROW_ERROR("AIFile_fopen_Failed_to_open_FILENAME", AIArgs("[FILENAME]", filename));
+ }
+ return fp;
+}
+
+//static
+void AIFile::close(LLFILE* file)
+{
+ if (LLFile::close(file) < 0)
+ {
+ THROW_ERROR("AIFile_close_Failed_to_close_file", AIArgs);
+ }
+}
+
+//static
+void AIFile::remove(std::string const& filename)
+{
+ if (LLFile::remove(filename) < 0)
+ {
+ THROW_ERROR("AIFile_remove_Failed_to_remove_FILENAME", AIArgs("[FILENAME]", filename));
+ }
+}
+
+//static
+void AIFile::rename(std::string const& filename, std::string const& newname)
+{
+ if (LLFile::rename(filename, newname) < 0)
+ {
+ THROW_ERROR("AIFile_rename_Failed_to_rename_FILE_to_NEWFILE", AIArgs("[FILE]", filename)("[NEWFILE]", newname));
+ }
+}
+
diff --git a/indra/llcommon/aifile.h b/indra/llcommon/aifile.h
new file mode 100644
index 000000000..1b110496a
--- /dev/null
+++ b/indra/llcommon/aifile.h
@@ -0,0 +1,59 @@
+/**
+ * @file aifile.h
+ * @brief Declaration of AIFile.
+ *
+ * Copyright (c) 2013, Aleric Inglewood.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution.
+ *
+ * CHANGELOG
+ * and additional copyright holders.
+ *
+ * 02/11/2013
+ * Initial version, written by Aleric Inglewood @ SL
+ */
+
+#ifndef AIFILE_H
+#define AIFILE_H
+
+#include "llfile.h"
+
+// As LLFile, but throws AIAlert instead of printing a warning.
+class LL_COMMON_API AIFile
+{
+ private:
+ LLFILE* mFp;
+
+ public:
+ // Scoped file (exception safe). Throws AIAlertCode with errno on failure.
+ AIFile(std::string const& filename, char const* accessmode);
+ ~AIFile();
+
+ operator LLFILE* () const { return mFp; }
+
+ // All these functions take UTF8 path/filenames.
+ static LLFILE* fopen(std::string const& filename, char const* accessmode);
+ static void close(LLFILE* file);
+
+ static void mkdir(std::string const& dirname, int perms = 0700); // Does NOT throw when dirname already exists.
+ static void rmdir(std::string const& dirname); // Does NOT throw when dirname does not exist.
+ static void remove(std::string const& filename); // Does NOT throw when filename does not exist.
+ static void rename(std::string const& filename, std::string const& newname);
+};
+
+#endif // AIFILE_H
diff --git a/indra/newview/skins/default/xui/en-us/strings.xml b/indra/newview/skins/default/xui/en-us/strings.xml
index b65877527..980bbcaa1 100644
--- a/indra/newview/skins/default/xui/en-us/strings.xml
+++ b/indra/newview/skins/default/xui/en-us/strings.xml
@@ -4367,4 +4367,12 @@ Try enclosing path to the editor with double quotes.
"[PREFIX]: "
+
+ Failed to create folder [DIRNAME]: [ERROR]
+ Failed to remove folder [DIRNAME]: [ERROR]
+ Failed to open file "[FILENAME]": [ERROR]
+ Failed to close file: [ERROR]
+ Failed to remove file [FILENAME]: [ERROR]
+ Failed to rename file [FILE] to [NEWFILE]: [ERROR]
+